source: josm/trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPresetReader.java@ 8126

Last change on this file since 8126 was 8126, checked in by Don-vip, 9 years ago

fix Sonar issue squid:S2444 - Lazy initialization of "static" fields should be "synchronized"

  • Property svn:eol-style set to native
File size: 14.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.tagging;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.BufferedReader;
7import java.io.File;
8import java.io.IOException;
9import java.io.InputStream;
10import java.io.InputStreamReader;
11import java.io.Reader;
12import java.util.ArrayDeque;
13import java.util.ArrayList;
14import java.util.Collection;
15import java.util.Deque;
16import java.util.HashMap;
17import java.util.Iterator;
18import java.util.LinkedList;
19import java.util.List;
20import java.util.Map;
21import java.util.Set;
22
23import javax.swing.JOptionPane;
24
25import org.openstreetmap.josm.Main;
26import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference;
27import org.openstreetmap.josm.io.CachedFile;
28import org.openstreetmap.josm.io.UTFInputStreamReader;
29import org.openstreetmap.josm.tools.XmlObjectParser;
30import org.xml.sax.SAXException;
31
32/**
33 * The tagging presets reader.
34 * @since 6068
35 */
36public final class TaggingPresetReader {
37
38 /**
39 * The accepted MIME types sent in the HTTP Accept header.
40 * @since 6867
41 */
42 public static final String PRESET_MIME_TYPES = "application/xml, text/xml, text/plain; q=0.8, application/zip, application/octet-stream; q=0.5";
43
44 private TaggingPresetReader() {
45 // Hide default constructor for utils classes
46 }
47
48 private static volatile File zipIcons = null;
49
50 /**
51 * Returns the set of preset source URLs.
52 * @return The set of preset source URLs.
53 */
54 public static Set<String> getPresetSources() {
55 return new TaggingPresetPreference.PresetPrefHelper().getActiveUrls();
56 }
57
58 /**
59 * Holds a reference to a chunk of items/objects.
60 */
61 public static class Chunk {
62 /** The chunk id, can be referenced later */
63 public String id;
64 }
65
66 /**
67 * Holds a reference to an earlier item/object.
68 */
69 public static class Reference {
70 /** Reference matching a chunk id defined earlier **/
71 public String ref;
72 }
73
74 private static XmlObjectParser buildParser() {
75 XmlObjectParser parser = new XmlObjectParser();
76 parser.mapOnStart("item", TaggingPreset.class);
77 parser.mapOnStart("separator", TaggingPresetSeparator.class);
78 parser.mapBoth("group", TaggingPresetMenu.class);
79 parser.map("text", TaggingPresetItems.Text.class);
80 parser.map("link", TaggingPresetItems.Link.class);
81 parser.map("preset_link", TaggingPresetItems.PresetLink.class);
82 parser.mapOnStart("optional", TaggingPresetItems.Optional.class);
83 parser.mapOnStart("roles", TaggingPresetItems.Roles.class);
84 parser.map("role", TaggingPresetItems.Role.class);
85 parser.map("checkgroup", TaggingPresetItems.CheckGroup.class);
86 parser.map("check", TaggingPresetItems.Check.class);
87 parser.map("combo", TaggingPresetItems.Combo.class);
88 parser.map("multiselect", TaggingPresetItems.MultiSelect.class);
89 parser.map("label", TaggingPresetItems.Label.class);
90 parser.map("space", TaggingPresetItems.Space.class);
91 parser.map("key", TaggingPresetItems.Key.class);
92 parser.map("list_entry", TaggingPresetItems.PresetListEntry.class);
93 parser.map("item_separator", TaggingPresetItems.ItemSeparator.class);
94 parser.mapBoth("chunk", Chunk.class);
95 parser.map("reference", Reference.class);
96 return parser;
97 }
98
99 /**
100 * Reads all tagging presets from the input reader.
101 * @param in The input reader
102 * @param validate if {@code true}, XML validation will be performed
103 * @return collection of tagging presets
104 * @throws SAXException if any XML error occurs
105 */
106 public static Collection<TaggingPreset> readAll(Reader in, boolean validate) throws SAXException {
107 XmlObjectParser parser = buildParser();
108
109 Deque<TaggingPreset> all = new LinkedList<>();
110 TaggingPresetMenu lastmenu = null;
111 TaggingPresetItems.Roles lastrole = null;
112 final List<TaggingPresetItems.Check> checks = new LinkedList<>();
113 List<TaggingPresetItems.PresetListEntry> listEntries = new LinkedList<>();
114 final Map<String, List<Object>> byId = new HashMap<>();
115 final Deque<String> lastIds = new ArrayDeque<>();
116 /** lastIdIterators contains non empty iterators of items to be handled before obtaining the next item from the XML parser */
117 final Deque<Iterator<Object>> lastIdIterators = new ArrayDeque<>();
118
119 if (validate) {
120 parser.startWithValidation(in, Main.getXMLBase()+"/tagging-preset-1.0", "resource://data/tagging-preset.xsd");
121 } else {
122 parser.start(in);
123 }
124 while (parser.hasNext() || !lastIdIterators.isEmpty()) {
125 final Object o;
126 if (!lastIdIterators.isEmpty()) {
127 // obtain elements from lastIdIterators with higher priority
128 o = lastIdIterators.peek().next();
129 if (!lastIdIterators.peek().hasNext()) {
130 // remove iterator if is empty
131 lastIdIterators.pop();
132 }
133 } else {
134 o = parser.next();
135 }
136 if (o instanceof Chunk) {
137 if (!lastIds.isEmpty() && ((Chunk) o).id.equals(lastIds.peek())) {
138 // pop last id on end of object, don't process further
139 lastIds.pop();
140 ((Chunk) o).id = null;
141 continue;
142 } else {
143 // if preset item contains an id, store a mapping for later usage
144 String lastId = ((Chunk) o).id;
145 lastIds.push(lastId);
146 byId.put(lastId, new ArrayList<>());
147 continue;
148 }
149 } else if (!lastIds.isEmpty()) {
150 // add object to mapping for later usage
151 byId.get(lastIds.peek()).add(o);
152 continue;
153 }
154 if (o instanceof Reference) {
155 // if o is a reference, obtain the corresponding objects from the mapping,
156 // and iterate over those before consuming the next element from parser.
157 final String ref = ((Reference) o).ref;
158 if (byId.get(ref) == null) {
159 throw new SAXException(tr("Reference {0} is being used before it was defined", ref));
160 }
161 Iterator<Object> it = byId.get(ref).iterator();
162 if (it.hasNext()) {
163 lastIdIterators.push(it);
164 } else {
165 Main.warn("Ignoring reference '"+ref+"' denoting an empty chunk");
166 }
167 continue;
168 }
169 if (!(o instanceof TaggingPresetItem) && !checks.isEmpty()) {
170 all.getLast().data.addAll(checks);
171 checks.clear();
172 }
173 if (o instanceof TaggingPresetMenu) {
174 TaggingPresetMenu tp = (TaggingPresetMenu) o;
175 if (tp == lastmenu) {
176 lastmenu = tp.group;
177 } else {
178 tp.group = lastmenu;
179 tp.setDisplayName();
180 lastmenu = tp;
181 all.add(tp);
182 }
183 lastrole = null;
184 } else if (o instanceof TaggingPresetSeparator) {
185 TaggingPresetSeparator tp = (TaggingPresetSeparator) o;
186 tp.group = lastmenu;
187 all.add(tp);
188 lastrole = null;
189 } else if (o instanceof TaggingPreset) {
190 TaggingPreset tp = (TaggingPreset) o;
191 tp.group = lastmenu;
192 tp.setDisplayName();
193 all.add(tp);
194 lastrole = null;
195 } else {
196 if (!all.isEmpty()) {
197 if (o instanceof TaggingPresetItems.Roles) {
198 all.getLast().data.add((TaggingPresetItem) o);
199 if (all.getLast().roles != null) {
200 throw new SAXException(tr("Roles cannot appear more than once"));
201 }
202 all.getLast().roles = (TaggingPresetItems.Roles) o;
203 lastrole = (TaggingPresetItems.Roles) o;
204 } else if (o instanceof TaggingPresetItems.Role) {
205 if (lastrole == null)
206 throw new SAXException(tr("Preset role element without parent"));
207 lastrole.roles.add((TaggingPresetItems.Role) o);
208 } else if (o instanceof TaggingPresetItems.Check) {
209 checks.add((TaggingPresetItems.Check) o);
210 } else if (o instanceof TaggingPresetItems.PresetListEntry) {
211 listEntries.add((TaggingPresetItems.PresetListEntry) o);
212 } else if (o instanceof TaggingPresetItems.CheckGroup) {
213 all.getLast().data.add((TaggingPresetItem) o);
214 // Make sure list of checks is empty to avoid adding checks several times
215 // when used in chunks (fix #10801)
216 ((TaggingPresetItems.CheckGroup) o).checks.clear();
217 ((TaggingPresetItems.CheckGroup) o).checks.addAll(checks);
218 checks.clear();
219 } else {
220 if (!checks.isEmpty()) {
221 all.getLast().data.addAll(checks);
222 checks.clear();
223 }
224 all.getLast().data.add((TaggingPresetItem) o);
225 if (o instanceof TaggingPresetItems.ComboMultiSelect) {
226 ((TaggingPresetItems.ComboMultiSelect) o).addListEntries(listEntries);
227 } else if (o instanceof TaggingPresetItems.Key) {
228 if (((TaggingPresetItems.Key) o).value == null) {
229 ((TaggingPresetItems.Key) o).value = ""; // Fix #8530
230 }
231 }
232 listEntries = new LinkedList<>();
233 lastrole = null;
234 }
235 } else
236 throw new SAXException(tr("Preset sub element without parent"));
237 }
238 }
239 if (!all.isEmpty() && !checks.isEmpty()) {
240 all.getLast().data.addAll(checks);
241 checks.clear();
242 }
243 return all;
244 }
245
246 /**
247 * Reads all tagging presets from the given source.
248 * @param source a given filename, URL or internal resource
249 * @param validate if {@code true}, XML validation will be performed
250 * @return collection of tagging presets
251 * @throws SAXException if any XML error occurs
252 * @throws IOException if any I/O error occurs
253 */
254 public static Collection<TaggingPreset> readAll(String source, boolean validate) throws SAXException, IOException {
255 Collection<TaggingPreset> tp;
256 CachedFile cf = new CachedFile(source).setHttpAccept(PRESET_MIME_TYPES);
257 try (
258 // zip may be null, but Java 7 allows it: https://blogs.oracle.com/darcy/entry/project_coin_null_try_with
259 InputStream zip = cf.findZipEntryInputStream("xml", "preset")
260 ) {
261 if (zip != null) {
262 zipIcons = cf.getFile();
263 }
264 try (InputStreamReader r = UTFInputStreamReader.create(zip == null ? cf.getInputStream() : zip)) {
265 tp = readAll(new BufferedReader(r), validate);
266 }
267 }
268 return tp;
269 }
270
271 /**
272 * Reads all tagging presets from the given sources.
273 * @param sources Collection of tagging presets sources.
274 * @param validate if {@code true}, presets will be validated against XML schema
275 * @return Collection of all presets successfully read
276 */
277 public static Collection<TaggingPreset> readAll(Collection<String> sources, boolean validate) {
278 return readAll(sources, validate, true);
279 }
280
281 /**
282 * Reads all tagging presets from the given sources.
283 * @param sources Collection of tagging presets sources.
284 * @param validate if {@code true}, presets will be validated against XML schema
285 * @param displayErrMsg if {@code true}, a blocking error message is displayed in case of I/O exception.
286 * @return Collection of all presets successfully read
287 */
288 public static Collection<TaggingPreset> readAll(Collection<String> sources, boolean validate, boolean displayErrMsg) {
289 LinkedList<TaggingPreset> allPresets = new LinkedList<>();
290 for(String source : sources) {
291 try {
292 allPresets.addAll(readAll(source, validate));
293 } catch (IOException e) {
294 Main.error(e, false);
295 Main.error(source);
296 if (source.startsWith("http")) {
297 Main.addNetworkError(source, e);
298 }
299 if (displayErrMsg) {
300 JOptionPane.showMessageDialog(
301 Main.parent,
302 tr("Could not read tagging preset source: {0}",source),
303 tr("Error"),
304 JOptionPane.ERROR_MESSAGE
305 );
306 }
307 } catch (SAXException e) {
308 Main.error(e);
309 Main.error(source);
310 JOptionPane.showMessageDialog(
311 Main.parent,
312 "<html>" + tr("Error parsing {0}: ", source) + "<br><br><table width=600>" + e.getMessage() + "</table></html>",
313 tr("Error"),
314 JOptionPane.ERROR_MESSAGE
315 );
316 }
317 }
318 return allPresets;
319 }
320
321 /**
322 * Reads all tagging presets from sources stored in preferences.
323 * @param validate if {@code true}, presets will be validated against XML schema
324 * @param displayErrMsg if {@code true}, a blocking error message is displayed in case of I/O exception.
325 * @return Collection of all presets successfully read
326 */
327 public static Collection<TaggingPreset> readFromPreferences(boolean validate, boolean displayErrMsg) {
328 return readAll(getPresetSources(), validate, displayErrMsg);
329 }
330
331 public static File getZipIcons() {
332 return zipIcons;
333 }
334}
Note: See TracBrowser for help on using the repository browser.