Skip to content

Commit e6fc01c

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

File tree

2 files changed

+75
-6
lines changed

2 files changed

+75
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6363
- Pass parent filter to inner hit query ([#13903](https://github.com/opensearch-project/OpenSearch/pull/13903))
6464
- Fix NPE on restore searchable snapshot ([#13911](https://github.com/opensearch-project/OpenSearch/pull/13911))
6565
- Fix double invocation of postCollection when MultiBucketCollector is present ([#14015](https://github.com/opensearch-project/OpenSearch/pull/14015))
66+
- Switch to iterative version of WKT format parser ([#14086](https://github.com/opensearch-project/OpenSearch/pull/14086))
6667

6768
### Security
6869

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

Lines changed: 74 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,80 @@ 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+
* Iterative version of
317+
* <!--
318+
* ```java
319+
* private GeometryCollection<Geometry> parseGeometryCollectionA(StreamTokenizer stream) throws IOException, ParseException {
320+
* if (nextEmptyOrOpen(stream).equals(EMPTY)) {
321+
* return GeometryCollection.EMPTY;
322+
* }
323+
* List<Geometry> shapes = new ArrayList<>();
324+
* shapes.add(parseGeometry(stream));
325+
* while (nextCloserOrComma(stream).equals(COMMA)) {
326+
* shapes.add(parseGeometry(stream));
327+
* }
328+
* return new GeometryCollection<>(shapes);
329+
* }
330+
* -->
331+
* to avoid StackOverflowError when there is a deeply nested structure of GeometryCollection.
332+
*/
304333
private GeometryCollection<Geometry> parseGeometryCollection(StreamTokenizer stream) throws IOException, ParseException {
305334
if (nextEmptyOrOpen(stream).equals(EMPTY)) {
306335
return GeometryCollection.EMPTY;
307336
}
308-
List<Geometry> shapes = new ArrayList<>();
309-
shapes.add(parseGeometry(stream));
310-
while (nextCloserOrComma(stream).equals(COMMA)) {
311-
shapes.add(parseGeometry(stream));
337+
338+
List<Geometry> topLevelShapes = new ArrayList<>();
339+
Stack<List<Geometry>> stack = new Stack<>();
340+
stack.push(topLevelShapes);
341+
boolean isFirstIteration = true;
342+
List<Geometry> currentLevelShapes = null;
343+
while (!stack.isEmpty()) {
344+
List<Geometry> previousShapes = stack.pop();
345+
if (currentLevelShapes != null) {
346+
previousShapes.add(new GeometryCollection<>(currentLevelShapes));
347+
}
348+
currentLevelShapes = previousShapes;
349+
350+
if (isFirstIteration == true) {
351+
isFirstIteration = false;
352+
} else {
353+
if (!nextCloserOrComma(stream).equals(COMMA)) {
354+
// Done with current level, continue with parent level
355+
continue;
356+
}
357+
}
358+
while (true) {
359+
final String type = nextWord(stream).toLowerCase(Locale.ROOT);
360+
switch (type) {
361+
case "geometrycollection":
362+
if (nextEmptyOrOpen(stream).equals(EMPTY)) {
363+
currentLevelShapes.add(GeometryCollection.EMPTY);
364+
break;
365+
} else {
366+
stack.push(currentLevelShapes);
367+
currentLevelShapes = new ArrayList<>();
368+
continue;
369+
}
370+
default:
371+
currentLevelShapes.add(parseSimpleGeometry(stream, type));
372+
break;
373+
}
374+
375+
if (!nextCloserOrComma(stream).equals(COMMA)) {
376+
break;
377+
}
378+
}
312379
}
313-
return new GeometryCollection<>(shapes);
380+
381+
return new GeometryCollection<>(topLevelShapes);
314382
}
315383

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

0 commit comments

Comments
 (0)