This is an automated email from the ASF dual-hosted git repository. mattjuntunen pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-geometry.git
commit e711ca0b45836948813c4372392c7ff0c14177af Author: Matt Juntunen <mattjuntu...@apache.org> AuthorDate: Fri Jul 23 21:25:17 2021 -0400 GEOMETRY-137: making SimpleTextParser throw IOException when parsed string exceeds max length --- .../io/core/internal/SimpleTextParser.java | 119 +++++++++++++-------- .../io/core/internal/SimpleTextParserTest.java | 8 +- 2 files changed, 81 insertions(+), 46 deletions(-) diff --git a/commons-geometry-io-core/src/main/java/org/apache/commons/geometry/io/core/internal/SimpleTextParser.java b/commons-geometry-io-core/src/main/java/org/apache/commons/geometry/io/core/internal/SimpleTextParser.java index e3e6f59..4ff7f6b 100644 --- a/commons-geometry-io-core/src/main/java/org/apache/commons/geometry/io/core/internal/SimpleTextParser.java +++ b/commons-geometry-io-core/src/main/java/org/apache/commons/geometry/io/core/internal/SimpleTextParser.java @@ -43,7 +43,7 @@ public class SimpleTextParser { private static final int DEFAULT_MAX_STRING_LENGTH = 1024; /** Error message used when a string exceeds the configured maximum length. */ - private static final String STRING_LENGTH_ERR_MSG = "String length exceeds maximum value of "; + private static final String STRING_LENGTH_ERR_MSG = "string length exceeds maximum value of "; /** Initial token position number. */ private static final int INITIAL_TOKEN_POS = -1; @@ -336,9 +336,8 @@ public class SimpleTextParser { * @param pred predicate function passed characters read from the input; reading continues * until the predicate returns false * @return this instance - * @throws IllegalStateException if the length of the produced string exceeds the configured + * @throws IOException if an I/O error occurs or the length of the produced string exceeds the configured * {@link #getMaxStringLength() maximum string length} - * @throws IOException if an I/O error occurs * @see #getCurrentToken() * @see #consume(IntPredicate, IntConsumer) */ @@ -348,14 +347,11 @@ public class SimpleTextParser { String token = null; if (hasMoreCharacters()) { - final StringBuilder sb = new StringBuilder(); + final StringCollector collector = new StringCollector(line, col, pred); - consume(pred, ch -> { - sb.append((char) ch); - validateStringLength(sb.length()); - }); + consume(collector, collector); - token = sb.toString(); + token = collector.getString(); } setToken(line, col, token); @@ -370,9 +366,8 @@ public class SimpleTextParser { * @param pred predicate function passed characters read from the input; reading continues * until the predicate returns false * @return this instance - * @throws IllegalStateException if the length of the produced string exceeds the configured + * @throws IOException if an I/O error occurs or the length of the produced string exceeds the configured * {@link #getMaxStringLength() maximum string length} - * @throws IOException if an I/O error occurs * @see #getCurrentToken() * @see #consume(IntPredicate, IntConsumer) */ @@ -383,14 +378,11 @@ public class SimpleTextParser { String token = null; if (hasMoreCharacters()) { - final StringBuilder sb = new StringBuilder(); + final StringCollector collector = new StringCollector(line, col, pred); - consumeWithLineContinuation(lineContinuationChar, pred, ch -> { - sb.append((char) ch); - validateStringLength(sb.length()); - }); + consumeWithLineContinuation(lineContinuationChar, collector, collector); - token = sb.toString(); + token = collector.getString(); } setToken(line, col, token); @@ -403,9 +395,8 @@ public class SimpleTextParser { * ('\r', '\n', or '\r\n') at the end of the line is consumed but is not included in the token. * The token will be null if the end of the stream has been reached prior to the method call. * @return this instance - * @throws IllegalStateException if the length of the produced string exceeds the configured + * @throws IOException if an I/O error occurs or the length of the produced string exceeds the configured * {@link #getMaxStringLength() maximum string length} - * @throws IOException if an I/O error occurs * @see #getCurrentToken() */ public SimpleTextParser nextLine() throws IOException { @@ -421,9 +412,8 @@ public class SimpleTextParser { * character in the stream is not alphanumeric and will be null if the end of the stream has * been reached prior to the method call. * @return this instance - * @throws IllegalStateException if the length of the produced string exceeds the configured + * @throws IOException if an I/O error occurs or the length of the produced string exceeds the configured * {@link #getMaxStringLength() maximum string length} - * @throws IOException if an I/O error occurs * @see #getCurrentToken() */ public SimpleTextParser nextAlphanumeric() throws IOException { @@ -660,30 +650,25 @@ public class SimpleTextParser { * until the predicate returns false * @return string containing characters matching {@code pred} or null if the parser has already * reached the end of the stream - * @throws IllegalStateException if the length of the produced string exceeds the configured + * @throws IOException if an I/O error occurs or the length of the produced string exceeds the configured * {@link #getMaxStringLength() maximum string length} - * @throws IOException if an I/O error occurs * @see #getCurrentToken() */ public String peek(final IntPredicate pred) throws IOException { String token = null; if (hasMoreCharacters()) { - final StringBuilder sb = new StringBuilder(); + final StringCollector collector = new StringCollector(lineNumber, columnNumber, pred); int i = -1; int ch = buffer.charAt(++i); - while (ch != EOF && pred.test(ch)) { - sb.append((char) ch); - - if (i > maxStringLength) { - throw new IllegalStateException(STRING_LENGTH_ERR_MSG + maxStringLength); - } + while (ch != EOF && collector.test(ch)) { + collector.accept(ch); ch = buffer.charAt(++i); } - token = sb.toString(); + token = collector.getString(); } return token; @@ -1043,18 +1028,6 @@ public class SimpleTextParser { } } - /** Validate that the given string length is less than the maximum configured length, - * throwing an exception if not. - * @param len string length to check - * @throws IllegalStateException if {@code len} is greater than the maximum configured - * string length - */ - private void validateStringLength(final int len) { - if (len > maxStringLength) { - throw new IllegalStateException(STRING_LENGTH_ERR_MSG + maxStringLength); - } - } - /** Ensure that a token read operation has been performed, throwing an exception if not. * @throws IllegalStateException if no token read operation has been performed */ @@ -1187,6 +1160,66 @@ public class SimpleTextParser { a.equalsIgnoreCase(b); } + /** Internal class used to collect strings from the character stream while ensuring that the + * collected strings do not exceed the maximum configured string length. + */ + private final class StringCollector implements IntPredicate, IntConsumer { + + /** String builder instance. */ + private final StringBuilder sb = new StringBuilder(); + + /** Start position line. */ + private final int line; + + /** Start position column. */ + private final int col; + + /** Character predicate. */ + private final IntPredicate pred; + + /** Construct a new instance with the given start position and character predicate. + * @param line start position line + * @param col start position col + * @param pred character predicate + */ + StringCollector(final int line, final int col, final IntPredicate pred) { + this.line = line; + this.col = col; + this.pred = pred; + } + + /** {@inheritDoc} */ + @Override + public boolean test(final int value) { + return pred.test(value) && !hasExceededMaxStringLength(); + } + + /** {@inheritDoc} */ + @Override + public void accept(final int value) { + sb.append((char) value); + } + + /** Get the string collected by this instance. + * @return the string collected by this instance + * @throws IOException if the string exceeds the maximum configured length + */ + public String getString() throws IOException { + if (hasExceededMaxStringLength()) { + throw parseError(line, col, STRING_LENGTH_ERR_MSG + maxStringLength); + } + + return sb.toString(); + } + + /** Return true if this collector has exceeded the maximum configured string length. + * @return true if this collector has exceeded the maximum string length + */ + private boolean hasExceededMaxStringLength() { + return sb.length() > maxStringLength; + } + } + /** Exception used to indicate a parsing error. */ private static final class ParseException extends IOException { diff --git a/commons-geometry-io-core/src/test/java/org/apache/commons/geometry/io/core/internal/SimpleTextParserTest.java b/commons-geometry-io-core/src/test/java/org/apache/commons/geometry/io/core/internal/SimpleTextParserTest.java index 740503f..ac27eaf 100644 --- a/commons-geometry-io-core/src/test/java/org/apache/commons/geometry/io/core/internal/SimpleTextParserTest.java +++ b/commons-geometry-io-core/src/test/java/org/apache/commons/geometry/io/core/internal/SimpleTextParserTest.java @@ -441,7 +441,7 @@ class SimpleTextParserTest { // act/assert GeometryTestUtils.assertThrowsWithMessage(() -> { p.next(c -> !Character.isWhitespace(c)); - }, IllegalStateException.class, "String length exceeds maximum value of 4"); + }, IOException.class, "Parsing failed at line 1, column 1: string length exceeds maximum value of 4"); } @Test @@ -839,13 +839,15 @@ class SimpleTextParserTest { @Test void testPeek_predicateArg_exceedsMaxStringLength() throws IOException { // arrange - final SimpleTextParser p = parser("abcdef"); + final SimpleTextParser p = parser("\n abcdefg"); p.setMaxStringLength(4); + p.discardLine() + .discard(SimpleTextParser::isWhitespace); // act/assert GeometryTestUtils.assertThrowsWithMessage(() -> { p.peek(SimpleTextParser::isNotWhitespace); - }, IllegalStateException.class, "String length exceeds maximum value of 4"); + }, IOException.class, "Parsing failed at line 2, column 3: string length exceeds maximum value of 4"); } @Test