source: josm/trunk/src/org/openstreetmap/josm/io/imagery/ImageryReader.java@ 10809

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

sonar - squid:S1166 - Exception handlers should preserve the original exceptions

  • Property svn:eol-style set to native
File size: 19.4 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io.imagery;
3
4import java.io.BufferedReader;
5import java.io.Closeable;
6import java.io.IOException;
7import java.util.ArrayList;
8import java.util.Arrays;
9import java.util.HashMap;
10import java.util.List;
11import java.util.Map;
12import java.util.Objects;
13import java.util.Stack;
14
15import javax.xml.parsers.ParserConfigurationException;
16
17import org.openstreetmap.josm.Main;
18import org.openstreetmap.josm.data.imagery.ImageryInfo;
19import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryBounds;
20import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType;
21import org.openstreetmap.josm.data.imagery.Shape;
22import org.openstreetmap.josm.io.CachedFile;
23import org.openstreetmap.josm.tools.HttpClient;
24import org.openstreetmap.josm.tools.LanguageInfo;
25import org.openstreetmap.josm.tools.MultiMap;
26import org.openstreetmap.josm.tools.Utils;
27import org.xml.sax.Attributes;
28import org.xml.sax.InputSource;
29import org.xml.sax.SAXException;
30import org.xml.sax.helpers.DefaultHandler;
31
32public class ImageryReader implements Closeable {
33
34 private final String source;
35 private CachedFile cachedFile;
36 private boolean fastFail;
37
38 private enum State {
39 INIT, // initial state, should always be at the bottom of the stack
40 IMAGERY, // inside the imagery element
41 ENTRY, // inside an entry
42 ENTRY_ATTRIBUTE, // note we are inside an entry attribute to collect the character data
43 PROJECTIONS, // inside projections block of an entry
44 MIRROR, // inside an mirror entry
45 MIRROR_ATTRIBUTE, // note we are inside an mirror attribute to collect the character data
46 MIRROR_PROJECTIONS, // inside projections block of an mirror entry
47 CODE,
48 BOUNDS,
49 SHAPE,
50 NO_TILE,
51 NO_TILESUM,
52 METADATA,
53 UNKNOWN, // element is not recognized in the current context
54 }
55
56 /**
57 * Constructs a {@code ImageryReader} from a given filename, URL or internal resource.
58 *
59 * @param source can be:<ul>
60 * <li>relative or absolute file name</li>
61 * <li>{@code file:///SOME/FILE} the same as above</li>
62 * <li>{@code http://...} a URL. It will be cached on disk.</li>
63 * <li>{@code resource://SOME/FILE} file from the classpath (usually in the current *.jar)</li>
64 * <li>{@code josmdir://SOME/FILE} file inside josm user data directory (since r7058)</li>
65 * <li>{@code josmplugindir://SOME/FILE} file inside josm plugin directory (since r7834)</li></ul>
66 */
67 public ImageryReader(String source) {
68 this.source = source;
69 }
70
71 /**
72 * Parses imagery source.
73 * @return list of imagery info
74 * @throws SAXException if any SAX error occurs
75 * @throws IOException if any I/O error occurs
76 */
77 public List<ImageryInfo> parse() throws SAXException, IOException {
78 Parser parser = new Parser();
79 try {
80 cachedFile = new CachedFile(source);
81 cachedFile.setFastFail(fastFail);
82 try (BufferedReader in = cachedFile
83 .setMaxAge(CachedFile.DAYS)
84 .setCachingStrategy(CachedFile.CachingStrategy.IfModifiedSince)
85 .getContentReader()) {
86 InputSource is = new InputSource(in);
87 Utils.parseSafeSAX(is, parser);
88 return parser.entries;
89 }
90 } catch (SAXException e) {
91 throw e;
92 } catch (ParserConfigurationException e) {
93 Main.error(e); // broken SAXException chaining
94 throw new SAXException(e);
95 }
96 }
97
98 private static class Parser extends DefaultHandler {
99 private StringBuilder accumulator = new StringBuilder();
100
101 private Stack<State> states;
102
103 private List<ImageryInfo> entries;
104
105 /**
106 * Skip the current entry because it has mandatory attributes
107 * that this version of JOSM cannot process.
108 */
109 private boolean skipEntry;
110
111 private ImageryInfo entry;
112 /** In case of mirror parsing this contains the mirror entry */
113 private ImageryInfo mirrorEntry;
114 private ImageryBounds bounds;
115 private Shape shape;
116 // language of last element, does only work for simple ENTRY_ATTRIBUTE's
117 private String lang;
118 private List<String> projections;
119 private MultiMap<String, String> noTileHeaders;
120 private MultiMap<String, String> noTileChecksums;
121 private Map<String, String> metadataHeaders;
122
123 @Override
124 public void startDocument() {
125 accumulator = new StringBuilder();
126 skipEntry = false;
127 states = new Stack<>();
128 states.push(State.INIT);
129 entries = new ArrayList<>();
130 entry = null;
131 bounds = null;
132 projections = null;
133 noTileHeaders = null;
134 noTileChecksums = null;
135 }
136
137 @Override
138 public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
139 accumulator.setLength(0);
140 State newState = null;
141 switch (states.peek()) {
142 case INIT:
143 if ("imagery".equals(qName)) {
144 newState = State.IMAGERY;
145 }
146 break;
147 case IMAGERY:
148 if ("entry".equals(qName)) {
149 entry = new ImageryInfo();
150 skipEntry = false;
151 newState = State.ENTRY;
152 noTileHeaders = new MultiMap<>();
153 noTileChecksums = new MultiMap<>();
154 metadataHeaders = new HashMap<>();
155 }
156 break;
157 case MIRROR:
158 if (Arrays.asList(new String[] {
159 "type",
160 "url",
161 "min-zoom",
162 "max-zoom",
163 "tile-size",
164 }).contains(qName)) {
165 newState = State.MIRROR_ATTRIBUTE;
166 lang = atts.getValue("lang");
167 } else if ("projections".equals(qName)) {
168 projections = new ArrayList<>();
169 newState = State.MIRROR_PROJECTIONS;
170 }
171 break;
172 case ENTRY:
173 if (Arrays.asList(new String[] {
174 "name",
175 "id",
176 "type",
177 "description",
178 "default",
179 "url",
180 "eula",
181 "min-zoom",
182 "max-zoom",
183 "attribution-text",
184 "attribution-url",
185 "logo-image",
186 "logo-url",
187 "terms-of-use-text",
188 "terms-of-use-url",
189 "country-code",
190 "icon",
191 "tile-size",
192 "valid-georeference",
193 "epsg4326to3857Supported",
194 }).contains(qName)) {
195 newState = State.ENTRY_ATTRIBUTE;
196 lang = atts.getValue("lang");
197 } else if ("bounds".equals(qName)) {
198 try {
199 bounds = new ImageryBounds(
200 atts.getValue("min-lat") + ',' +
201 atts.getValue("min-lon") + ',' +
202 atts.getValue("max-lat") + ',' +
203 atts.getValue("max-lon"), ",");
204 } catch (IllegalArgumentException e) {
205 Main.trace(e);
206 break;
207 }
208 newState = State.BOUNDS;
209 } else if ("projections".equals(qName)) {
210 projections = new ArrayList<>();
211 newState = State.PROJECTIONS;
212 } else if ("mirror".equals(qName)) {
213 projections = new ArrayList<>();
214 newState = State.MIRROR;
215 mirrorEntry = new ImageryInfo();
216 } else if ("no-tile-header".equals(qName)) {
217 noTileHeaders.put(atts.getValue("name"), atts.getValue("value"));
218 newState = State.NO_TILE;
219 } else if ("no-tile-checksum".equals(qName)) {
220 noTileChecksums.put(atts.getValue("type"), atts.getValue("value"));
221 newState = State.NO_TILESUM;
222 } else if ("metadata-header".equals(qName)) {
223 metadataHeaders.put(atts.getValue("header-name"), atts.getValue("metadata-key"));
224 newState = State.METADATA;
225 }
226 break;
227 case BOUNDS:
228 if ("shape".equals(qName)) {
229 shape = new Shape();
230 newState = State.SHAPE;
231 }
232 break;
233 case SHAPE:
234 if ("point".equals(qName)) {
235 try {
236 shape.addPoint(atts.getValue("lat"), atts.getValue("lon"));
237 } catch (IllegalArgumentException e) {
238 Main.trace(e);
239 break;
240 }
241 }
242 break;
243 case PROJECTIONS:
244 case MIRROR_PROJECTIONS:
245 if ("code".equals(qName)) {
246 newState = State.CODE;
247 }
248 break;
249 default: // Do nothing
250 }
251 /**
252 * Did not recognize the element, so the new state is UNKNOWN.
253 * This includes the case where we are already inside an unknown
254 * element, i.e. we do not try to understand the inner content
255 * of an unknown element, but wait till it's over.
256 */
257 if (newState == null) {
258 newState = State.UNKNOWN;
259 }
260 states.push(newState);
261 if (newState == State.UNKNOWN && "true".equals(atts.getValue("mandatory"))) {
262 skipEntry = true;
263 }
264 }
265
266 @Override
267 public void characters(char[] ch, int start, int length) {
268 accumulator.append(ch, start, length);
269 }
270
271 @Override
272 public void endElement(String namespaceURI, String qName, String rqName) {
273 switch (states.pop()) {
274 case INIT:
275 throw new RuntimeException("parsing error: more closing than opening elements");
276 case ENTRY:
277 if ("entry".equals(qName)) {
278 entry.setNoTileHeaders(noTileHeaders);
279 noTileHeaders = null;
280 entry.setNoTileChecksums(noTileChecksums);
281 noTileChecksums = null;
282 entry.setMetadataHeaders(metadataHeaders);
283 metadataHeaders = null;
284
285 if (!skipEntry) {
286 entries.add(entry);
287 }
288 entry = null;
289 }
290 break;
291 case MIRROR:
292 if ("mirror".equals(qName)) {
293 if (mirrorEntry != null) {
294 entry.addMirror(mirrorEntry);
295 mirrorEntry = null;
296 }
297 }
298 break;
299 case MIRROR_ATTRIBUTE:
300 if (mirrorEntry != null) {
301 switch(qName) {
302 case "type":
303 boolean found = false;
304 for (ImageryType type : ImageryType.values()) {
305 if (Objects.equals(accumulator.toString(), type.getTypeString())) {
306 mirrorEntry.setImageryType(type);
307 found = true;
308 break;
309 }
310 }
311 if (!found) {
312 mirrorEntry = null;
313 }
314 break;
315 case "url":
316 mirrorEntry.setUrl(accumulator.toString());
317 break;
318 case "min-zoom":
319 case "max-zoom":
320 Integer val = null;
321 try {
322 val = Integer.valueOf(accumulator.toString());
323 } catch (NumberFormatException e) {
324 val = null;
325 }
326 if (val == null) {
327 mirrorEntry = null;
328 } else {
329 if ("min-zoom".equals(qName)) {
330 mirrorEntry.setDefaultMinZoom(val);
331 } else {
332 mirrorEntry.setDefaultMaxZoom(val);
333 }
334 }
335 break;
336 case "tile-size":
337 Integer tileSize = null;
338 try {
339 tileSize = Integer.valueOf(accumulator.toString());
340 } catch (NumberFormatException e) {
341 tileSize = null;
342 }
343 if (tileSize == null) {
344 mirrorEntry = null;
345 } else {
346 entry.setTileSize(tileSize.intValue());
347 }
348 break;
349 default: // Do nothing
350 }
351 }
352 break;
353 case ENTRY_ATTRIBUTE:
354 switch(qName) {
355 case "name":
356 entry.setName(lang == null ? LanguageInfo.getJOSMLocaleCode(null) : lang, accumulator.toString());
357 break;
358 case "description":
359 entry.setDescription(lang, accumulator.toString());
360 break;
361 case "id":
362 entry.setId(accumulator.toString());
363 break;
364 case "type":
365 boolean found = false;
366 for (ImageryType type : ImageryType.values()) {
367 if (Objects.equals(accumulator.toString(), type.getTypeString())) {
368 entry.setImageryType(type);
369 found = true;
370 break;
371 }
372 }
373 if (!found) {
374 skipEntry = true;
375 }
376 break;
377 case "default":
378 switch (accumulator.toString()) {
379 case "true":
380 entry.setDefaultEntry(true);
381 break;
382 case "false":
383 entry.setDefaultEntry(false);
384 break;
385 default:
386 skipEntry = true;
387 }
388 break;
389 case "url":
390 entry.setUrl(accumulator.toString());
391 break;
392 case "eula":
393 entry.setEulaAcceptanceRequired(accumulator.toString());
394 break;
395 case "min-zoom":
396 case "max-zoom":
397 Integer val = null;
398 try {
399 val = Integer.valueOf(accumulator.toString());
400 } catch (NumberFormatException e) {
401 val = null;
402 }
403 if (val == null) {
404 skipEntry = true;
405 } else {
406 if ("min-zoom".equals(qName)) {
407 entry.setDefaultMinZoom(val);
408 } else {
409 entry.setDefaultMaxZoom(val);
410 }
411 }
412 break;
413 case "attribution-text":
414 entry.setAttributionText(accumulator.toString());
415 break;
416 case "attribution-url":
417 entry.setAttributionLinkURL(accumulator.toString());
418 break;
419 case "logo-image":
420 entry.setAttributionImage(accumulator.toString());
421 break;
422 case "logo-url":
423 entry.setAttributionImageURL(accumulator.toString());
424 break;
425 case "terms-of-use-text":
426 entry.setTermsOfUseText(accumulator.toString());
427 break;
428 case "terms-of-use-url":
429 entry.setTermsOfUseURL(accumulator.toString());
430 break;
431 case "country-code":
432 entry.setCountryCode(accumulator.toString());
433 break;
434 case "icon":
435 entry.setIcon(accumulator.toString());
436 break;
437 case "tile-size":
438 Integer tileSize = null;
439 try {
440 tileSize = Integer.valueOf(accumulator.toString());
441 } catch (NumberFormatException e) {
442 tileSize = null;
443 }
444 if (tileSize == null) {
445 skipEntry = true;
446 } else {
447 entry.setTileSize(tileSize.intValue());
448 }
449 break;
450 case "valid-georeference":
451 entry.setGeoreferenceValid(Boolean.valueOf(accumulator.toString()));
452 break;
453 case "epsg4326to3857Supported":
454 entry.setEpsg4326To3857Supported(Boolean.valueOf(accumulator.toString()));
455 break;
456 default: // Do nothing
457 }
458 break;
459 case BOUNDS:
460 entry.setBounds(bounds);
461 bounds = null;
462 break;
463 case SHAPE:
464 bounds.addShape(shape);
465 shape = null;
466 break;
467 case CODE:
468 projections.add(accumulator.toString());
469 break;
470 case PROJECTIONS:
471 entry.setServerProjections(projections);
472 projections = null;
473 break;
474 case MIRROR_PROJECTIONS:
475 mirrorEntry.setServerProjections(projections);
476 projections = null;
477 break;
478 case NO_TILE:
479 case NO_TILESUM:
480 case METADATA:
481 case UNKNOWN:
482 default:
483 // nothing to do for these or the unknown type
484 }
485 }
486 }
487
488 /**
489 * Sets whether opening HTTP connections should fail fast, i.e., whether a
490 * {@link HttpClient#setConnectTimeout(int) low connect timeout} should be used.
491 * @param fastFail whether opening HTTP connections should fail fast
492 * @see CachedFile#setFastFail(boolean)
493 */
494 public void setFastFail(boolean fastFail) {
495 this.fastFail = fastFail;
496 }
497
498 @Override
499 public void close() throws IOException {
500 Utils.close(cachedFile);
501 }
502}
Note: See TracBrowser for help on using the repository browser.