Skip to content

Commit 62efef0

Browse files
committed
Switch to iterative version of WKT format parser
Signed-off-by: Heemin Kim <[email protected]>
1 parent 10c0b77 commit 62efef0

File tree

3 files changed

+67
-6
lines changed

3 files changed

+67
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1717
### Removed
1818

1919
### Fixed
20+
- Switch to iterative version of WKT format parser ([#14086](https://github.com/opensearch-project/OpenSearch/pull/14086))
2021

2122
### Security
2223

libs/geo/src/main/java/org/opensearch/geometry/utils/WellKnownText.java

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import java.util.Collections;
5454
import java.util.List;
5555
import java.util.Locale;
56+
import java.util.Stack;
5657

5758
/**
5859
* Utility class for converting to and from WKT
@@ -278,6 +279,16 @@ public Geometry fromWKT(String wkt) throws IOException, ParseException {
278279
*/
279280
private Geometry parseGeometry(StreamTokenizer stream) throws IOException, ParseException {
280281
final String type = nextWord(stream).toLowerCase(Locale.ROOT);
282+
switch (type) {
283+
case "geometrycollection":
284+
return parseGeometryCollection(stream);
285+
default:
286+
return parseSimpleGeometry(stream, type);
287+
}
288+
}
289+
290+
private Geometry parseSimpleGeometry(StreamTokenizer stream, String type) throws IOException, ParseException {
291+
assert "geometrycollection".equals(type) == false;
281292
switch (type) {
282293
case "point":
283294
return parsePoint(stream);
@@ -294,23 +305,67 @@ private Geometry parseGeometry(StreamTokenizer stream) throws IOException, Parse
294305
case "bbox":
295306
return parseBBox(stream);
296307
case "geometrycollection":
297-
return parseGeometryCollection(stream);
308+
throw new IllegalStateException("Unexpected type: geometrycollection");
298309
case "circle": // Not part of the standard, but we need it for internal serialization
299310
return parseCircle(stream);
300311
}
301312
throw new IllegalArgumentException("Unknown geometry type: " + type);
302313
}
303314

315+
/**
316+
* Parse geometry collection iteratively
317+
*
318+
* Parsing geometry collection recursively can lead to StackOverflowError when there is a deeply nested structure of GeometryCollection
319+
*/
304320
private GeometryCollection<Geometry> parseGeometryCollection(StreamTokenizer stream) throws IOException, ParseException {
305321
if (nextEmptyOrOpen(stream).equals(EMPTY)) {
306322
return GeometryCollection.EMPTY;
307323
}
308-
List<Geometry> shapes = new ArrayList<>();
309-
shapes.add(parseGeometry(stream));
310-
while (nextCloserOrComma(stream).equals(COMMA)) {
311-
shapes.add(parseGeometry(stream));
324+
325+
List<Geometry> topLevelShapes = new ArrayList<>();
326+
Stack<List<Geometry>> stack = new Stack<>();
327+
stack.push(topLevelShapes);
328+
boolean isFirstIteration = true;
329+
List<Geometry> currentLevelShapes = null;
330+
while (!stack.isEmpty()) {
331+
List<Geometry> previousShapes = stack.pop();
332+
if (currentLevelShapes != null) {
333+
previousShapes.add(new GeometryCollection<>(currentLevelShapes));
334+
}
335+
currentLevelShapes = previousShapes;
336+
337+
if (isFirstIteration == true) {
338+
isFirstIteration = false;
339+
} else {
340+
if (!nextCloserOrComma(stream).equals(COMMA)) {
341+
// Done with current level, continue with parent level
342+
continue;
343+
}
344+
}
345+
while (true) {
346+
final String type = nextWord(stream).toLowerCase(Locale.ROOT);
347+
switch (type) {
348+
case "geometrycollection":
349+
if (nextEmptyOrOpen(stream).equals(EMPTY)) {
350+
currentLevelShapes.add(GeometryCollection.EMPTY);
351+
break;
352+
} else {
353+
stack.push(currentLevelShapes);
354+
currentLevelShapes = new ArrayList<>();
355+
continue;
356+
}
357+
default:
358+
currentLevelShapes.add(parseSimpleGeometry(stream, type));
359+
break;
360+
}
361+
362+
if (!nextCloserOrComma(stream).equals(COMMA)) {
363+
break;
364+
}
365+
}
312366
}
313-
return new GeometryCollection<>(shapes);
367+
368+
return new GeometryCollection<>(topLevelShapes);
314369
}
315370

316371
private Point parsePoint(StreamTokenizer stream) throws IOException, ParseException {

libs/geo/src/test/java/org/opensearch/geometry/GeometryCollectionTests.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ public void testBasicSerialization() throws IOException, ParseException {
6262

6363
assertEquals("GEOMETRYCOLLECTION EMPTY", wkt.toWKT(GeometryCollection.EMPTY));
6464
assertEquals(GeometryCollection.EMPTY, wkt.fromWKT("GEOMETRYCOLLECTION EMPTY)"));
65+
66+
assertEquals(
67+
new GeometryCollection<Geometry>(Arrays.asList(GeometryCollection.EMPTY)),
68+
wkt.fromWKT("GEOMETRYCOLLECTION (GEOMETRYCOLLECTION EMPTY)")
69+
);
6570
}
6671

6772
@SuppressWarnings("ConstantConditions")

0 commit comments

Comments
 (0)