49
49
import java .io .StreamTokenizer ;
50
50
import java .io .StringReader ;
51
51
import java .text .ParseException ;
52
+ import java .util .ArrayDeque ;
52
53
import java .util .ArrayList ;
53
54
import java .util .Collections ;
55
+ import java .util .Deque ;
54
56
import java .util .List ;
55
57
import java .util .Locale ;
56
58
@@ -67,6 +69,7 @@ public class WellKnownText {
67
69
public static final String RPAREN = ")" ;
68
70
public static final String COMMA = "," ;
69
71
public static final String NAN = "NaN" ;
72
+ public static final int MAX_DEPTH_OF_GEO_COLLECTION = 1000 ;
70
73
71
74
private final String NUMBER = "<NUMBER>" ;
72
75
private final String EOF = "END-OF-STREAM" ;
@@ -278,6 +281,16 @@ public Geometry fromWKT(String wkt) throws IOException, ParseException {
278
281
*/
279
282
private Geometry parseGeometry (StreamTokenizer stream ) throws IOException , ParseException {
280
283
final String type = nextWord (stream ).toLowerCase (Locale .ROOT );
284
+ switch (type ) {
285
+ case "geometrycollection" :
286
+ return parseGeometryCollection (stream );
287
+ default :
288
+ return parseSimpleGeometry (stream , type );
289
+ }
290
+ }
291
+
292
+ private Geometry parseSimpleGeometry (StreamTokenizer stream , String type ) throws IOException , ParseException {
293
+ assert "geometrycollection" .equals (type ) == false ;
281
294
switch (type ) {
282
295
case "point" :
283
296
return parsePoint (stream );
@@ -294,7 +307,7 @@ private Geometry parseGeometry(StreamTokenizer stream) throws IOException, Parse
294
307
case "bbox" :
295
308
return parseBBox (stream );
296
309
case "geometrycollection" :
297
- return parseGeometryCollection ( stream );
310
+ throw new IllegalStateException ( "Unexpected type: geometrycollection" );
298
311
case "circle" : // Not part of the standard, but we need it for internal serialization
299
312
return parseCircle (stream );
300
313
}
@@ -305,12 +318,56 @@ private GeometryCollection<Geometry> parseGeometryCollection(StreamTokenizer str
305
318
if (nextEmptyOrOpen (stream ).equals (EMPTY )) {
306
319
return GeometryCollection .EMPTY ;
307
320
}
308
- List <Geometry > shapes = new ArrayList <>();
309
- shapes .add (parseGeometry (stream ));
310
- while (nextCloserOrComma (stream ).equals (COMMA )) {
311
- shapes .add (parseGeometry (stream ));
321
+
322
+ List <Geometry > topLevelShapes = new ArrayList <>();
323
+ Deque <List <Geometry >> deque = new ArrayDeque <>();
324
+ deque .push (topLevelShapes );
325
+ boolean isFirstIteration = true ;
326
+ List <Geometry > currentLevelShapes = null ;
327
+ while (!deque .isEmpty ()) {
328
+ List <Geometry > previousShapes = deque .pop ();
329
+ if (currentLevelShapes != null ) {
330
+ previousShapes .add (new GeometryCollection <>(currentLevelShapes ));
331
+ }
332
+ currentLevelShapes = previousShapes ;
333
+
334
+ if (isFirstIteration == true ) {
335
+ isFirstIteration = false ;
336
+ } else {
337
+ if (nextCloserOrComma (stream ).equals (COMMA ) == false ) {
338
+ // Done with current level, continue with parent level
339
+ continue ;
340
+ }
341
+ }
342
+ while (true ) {
343
+ final String type = nextWord (stream ).toLowerCase (Locale .ROOT );
344
+ if (type .equals ("geometrycollection" )) {
345
+ if (nextEmptyOrOpen (stream ).equals (EMPTY ) == false ) {
346
+ // GEOMETRYCOLLECTION() -> 1 depth, GEOMETRYCOLLECTION(GEOMETRYCOLLECTION()) -> 2 depth
347
+ // When parsing the top level geometry collection, the queue size is zero.
348
+ // When max depth is 1, we don't want to push any sub geometry collection in the queue.
349
+ // Therefore, we subtract 2 from max depth.
350
+ if (deque .size () >= MAX_DEPTH_OF_GEO_COLLECTION - 2 ) {
351
+ throw new IllegalArgumentException (
352
+ "a geometry collection with a depth greater than " + MAX_DEPTH_OF_GEO_COLLECTION + " is not supported"
353
+ );
354
+ }
355
+ deque .push (currentLevelShapes );
356
+ currentLevelShapes = new ArrayList <>();
357
+ continue ;
358
+ }
359
+ currentLevelShapes .add (GeometryCollection .EMPTY );
360
+ } else {
361
+ currentLevelShapes .add (parseSimpleGeometry (stream , type ));
362
+ }
363
+
364
+ if (nextCloserOrComma (stream ).equals (COMMA ) == false ) {
365
+ break ;
366
+ }
367
+ }
312
368
}
313
- return new GeometryCollection <>(shapes );
369
+
370
+ return new GeometryCollection <>(topLevelShapes );
314
371
}
315
372
316
373
private Point parsePoint (StreamTokenizer stream ) throws IOException , ParseException {
0 commit comments