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

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

sonar - squid:S1066 - Collapsible "if" statements should be merged

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