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

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

sonar - remove some transient modifiers

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