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

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

findbugs - SF_SWITCH_NO_DEFAULT + various sonar fixes

  • Property svn:eol-style set to native
File size: 19.3 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 break;
206 }
207 newState = State.BOUNDS;
208 } else if ("projections".equals(qName)) {
209 projections = new ArrayList<>();
210 newState = State.PROJECTIONS;
211 } else if ("mirror".equals(qName)) {
212 projections = new ArrayList<>();
213 newState = State.MIRROR;
214 mirrorEntry = new ImageryInfo();
215 } else if ("no-tile-header".equals(qName)) {
216 noTileHeaders.put(atts.getValue("name"), atts.getValue("value"));
217 newState = State.NO_TILE;
218 } else if ("no-tile-checksum".equals(qName)) {
219 noTileChecksums.put(atts.getValue("type"), atts.getValue("value"));
220 newState = State.NO_TILESUM;
221 } else if ("metadata-header".equals(qName)) {
222 metadataHeaders.put(atts.getValue("header-name"), atts.getValue("metadata-key"));
223 newState = State.METADATA;
224 }
225 break;
226 case BOUNDS:
227 if ("shape".equals(qName)) {
228 shape = new Shape();
229 newState = State.SHAPE;
230 }
231 break;
232 case SHAPE:
233 if ("point".equals(qName)) {
234 try {
235 shape.addPoint(atts.getValue("lat"), atts.getValue("lon"));
236 } catch (IllegalArgumentException e) {
237 break;
238 }
239 }
240 break;
241 case PROJECTIONS:
242 case MIRROR_PROJECTIONS:
243 if ("code".equals(qName)) {
244 newState = State.CODE;
245 }
246 break;
247 default: // Do nothing
248 }
249 /**
250 * Did not recognize the element, so the new state is UNKNOWN.
251 * This includes the case where we are already inside an unknown
252 * element, i.e. we do not try to understand the inner content
253 * of an unknown element, but wait till it's over.
254 */
255 if (newState == null) {
256 newState = State.UNKNOWN;
257 }
258 states.push(newState);
259 if (newState == State.UNKNOWN && "true".equals(atts.getValue("mandatory"))) {
260 skipEntry = true;
261 }
262 }
263
264 @Override
265 public void characters(char[] ch, int start, int length) {
266 accumulator.append(ch, start, length);
267 }
268
269 @Override
270 public void endElement(String namespaceURI, String qName, String rqName) {
271 switch (states.pop()) {
272 case INIT:
273 throw new RuntimeException("parsing error: more closing than opening elements");
274 case ENTRY:
275 if ("entry".equals(qName)) {
276 entry.setNoTileHeaders(noTileHeaders);
277 noTileHeaders = null;
278 entry.setNoTileChecksums(noTileChecksums);
279 noTileChecksums = null;
280 entry.setMetadataHeaders(metadataHeaders);
281 metadataHeaders = null;
282
283 if (!skipEntry) {
284 entries.add(entry);
285 }
286 entry = null;
287 }
288 break;
289 case MIRROR:
290 if ("mirror".equals(qName)) {
291 if (mirrorEntry != null) {
292 entry.addMirror(mirrorEntry);
293 mirrorEntry = null;
294 }
295 }
296 break;
297 case MIRROR_ATTRIBUTE:
298 if (mirrorEntry != null) {
299 switch(qName) {
300 case "type":
301 boolean found = false;
302 for (ImageryType type : ImageryType.values()) {
303 if (Objects.equals(accumulator.toString(), type.getTypeString())) {
304 mirrorEntry.setImageryType(type);
305 found = true;
306 break;
307 }
308 }
309 if (!found) {
310 mirrorEntry = null;
311 }
312 break;
313 case "url":
314 mirrorEntry.setUrl(accumulator.toString());
315 break;
316 case "min-zoom":
317 case "max-zoom":
318 Integer val = null;
319 try {
320 val = Integer.valueOf(accumulator.toString());
321 } catch (NumberFormatException e) {
322 val = null;
323 }
324 if (val == null) {
325 mirrorEntry = null;
326 } else {
327 if ("min-zoom".equals(qName)) {
328 mirrorEntry.setDefaultMinZoom(val);
329 } else {
330 mirrorEntry.setDefaultMaxZoom(val);
331 }
332 }
333 break;
334 case "tile-size":
335 Integer tileSize = null;
336 try {
337 tileSize = Integer.valueOf(accumulator.toString());
338 } catch (NumberFormatException e) {
339 tileSize = null;
340 }
341 if (tileSize == null) {
342 mirrorEntry = null;
343 } else {
344 entry.setTileSize(tileSize.intValue());
345 }
346 break;
347 default: // Do nothing
348 }
349 }
350 break;
351 case ENTRY_ATTRIBUTE:
352 switch(qName) {
353 case "name":
354 entry.setName(lang == null ? LanguageInfo.getJOSMLocaleCode(null) : lang, accumulator.toString());
355 break;
356 case "description":
357 entry.setDescription(lang, accumulator.toString());
358 break;
359 case "id":
360 entry.setId(accumulator.toString());
361 break;
362 case "type":
363 boolean found = false;
364 for (ImageryType type : ImageryType.values()) {
365 if (Objects.equals(accumulator.toString(), type.getTypeString())) {
366 entry.setImageryType(type);
367 found = true;
368 break;
369 }
370 }
371 if (!found) {
372 skipEntry = true;
373 }
374 break;
375 case "default":
376 switch (accumulator.toString()) {
377 case "true":
378 entry.setDefaultEntry(true);
379 break;
380 case "false":
381 entry.setDefaultEntry(false);
382 break;
383 default:
384 skipEntry = true;
385 }
386 break;
387 case "url":
388 entry.setUrl(accumulator.toString());
389 break;
390 case "eula":
391 entry.setEulaAcceptanceRequired(accumulator.toString());
392 break;
393 case "min-zoom":
394 case "max-zoom":
395 Integer val = null;
396 try {
397 val = Integer.valueOf(accumulator.toString());
398 } catch (NumberFormatException e) {
399 val = null;
400 }
401 if (val == null) {
402 skipEntry = true;
403 } else {
404 if ("min-zoom".equals(qName)) {
405 entry.setDefaultMinZoom(val);
406 } else {
407 entry.setDefaultMaxZoom(val);
408 }
409 }
410 break;
411 case "attribution-text":
412 entry.setAttributionText(accumulator.toString());
413 break;
414 case "attribution-url":
415 entry.setAttributionLinkURL(accumulator.toString());
416 break;
417 case "logo-image":
418 entry.setAttributionImage(accumulator.toString());
419 break;
420 case "logo-url":
421 entry.setAttributionImageURL(accumulator.toString());
422 break;
423 case "terms-of-use-text":
424 entry.setTermsOfUseText(accumulator.toString());
425 break;
426 case "terms-of-use-url":
427 entry.setTermsOfUseURL(accumulator.toString());
428 break;
429 case "country-code":
430 entry.setCountryCode(accumulator.toString());
431 break;
432 case "icon":
433 entry.setIcon(accumulator.toString());
434 break;
435 case "tile-size":
436 Integer tileSize = null;
437 try {
438 tileSize = Integer.valueOf(accumulator.toString());
439 } catch (NumberFormatException e) {
440 tileSize = null;
441 }
442 if (tileSize == null) {
443 skipEntry = true;
444 } else {
445 entry.setTileSize(tileSize.intValue());
446 }
447 break;
448 case "valid-georeference":
449 entry.setGeoreferenceValid(Boolean.valueOf(accumulator.toString()));
450 break;
451 case "epsg4326to3857Supported":
452 entry.setEpsg4326To3857Supported(Boolean.valueOf(accumulator.toString()));
453 break;
454 default: // Do nothing
455 }
456 break;
457 case BOUNDS:
458 entry.setBounds(bounds);
459 bounds = null;
460 break;
461 case SHAPE:
462 bounds.addShape(shape);
463 shape = null;
464 break;
465 case CODE:
466 projections.add(accumulator.toString());
467 break;
468 case PROJECTIONS:
469 entry.setServerProjections(projections);
470 projections = null;
471 break;
472 case MIRROR_PROJECTIONS:
473 mirrorEntry.setServerProjections(projections);
474 projections = null;
475 break;
476 case NO_TILE:
477 case NO_TILESUM:
478 case METADATA:
479 case UNKNOWN:
480 default:
481 // nothing to do for these or the unknown type
482 }
483 }
484 }
485
486 /**
487 * Sets whether opening HTTP connections should fail fast, i.e., whether a
488 * {@link HttpClient#setConnectTimeout(int) low connect timeout} should be used.
489 * @param fastFail whether opening HTTP connections should fail fast
490 * @see CachedFile#setFastFail(boolean)
491 */
492 public void setFastFail(boolean fastFail) {
493 this.fastFail = fastFail;
494 }
495
496 @Override
497 public void close() throws IOException {
498 Utils.close(cachedFile);
499 }
500}
Note: See TracBrowser for help on using the repository browser.