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

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

sonar - squid:S1948 - Fields in a "Serializable" class should either be transient or serializable

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