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

Last change on this file since 8792 was 8710, checked in by simon04, 9 years ago

see #11795 - taginfoextract: avoid "Could not get presets icon" by not loading icons

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