diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7f93135..d64cd49 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ac72c34..a80b22c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 0adc8e1..1aa94a4 100755 --- a/gradlew +++ b/gradlew @@ -145,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -153,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -202,11 +202,11 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 6689b85..7101f8e 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/systems.comodal.json_iterator/build.gradle b/systems.comodal.json_iterator/build.gradle index e498d01..ad81631 100644 --- a/systems.comodal.json_iterator/build.gradle +++ b/systems.comodal.json_iterator/build.gradle @@ -128,8 +128,8 @@ publishing { name = "GitHubPackages" url = "https://maven.pkg.github.com/comodal/json-iterator" credentials { - username = System.getenv("GITHUB_ACTOR") - password = System.getenv("GITHUB_TOKEN") + username = System.getenv("GITHUB_ACTOR") ?: project.findProperty("gpr.user.write") + password = System.getenv("GITHUB_TOKEN") ?: project.findProperty("gpr.token.write") } } } diff --git a/systems.comodal.json_iterator/src/main/java/systems/comodal/jsoniter/CharsJsonIterator.java b/systems.comodal.json_iterator/src/main/java/systems/comodal/jsoniter/CharsJsonIterator.java index d32dc09..c670731 100644 --- a/systems.comodal.json_iterator/src/main/java/systems/comodal/jsoniter/CharsJsonIterator.java +++ b/systems.comodal.json_iterator/src/main/java/systems/comodal/jsoniter/CharsJsonIterator.java @@ -158,7 +158,7 @@ void skipPastEndQuote() { } @Override - final int parseNumber() { + int parseNumber() { for (int i = head, len = 0; ; i++) { if (i == tail) { head = tail; @@ -206,7 +206,7 @@ private char[] handleEscapes(final int from, final int len) { } @Override - final R parse(final CharBufferFunction applyChars) { + R parse(final CharBufferFunction applyChars) { final int from = head; final int len = parse(from); if (numEscapes > 0) { @@ -218,7 +218,7 @@ final R parse(final CharBufferFunction applyChars) { } @Override - final R parse(final C context, final ContextCharBufferFunction applyChars) { + R parse(final C context, final ContextCharBufferFunction applyChars) { final int from = head; final int len = parse(from); if (numEscapes > 0) { @@ -278,7 +278,7 @@ long parse(final C context, final ContextCharBufferToLongFunction applyCh } @Override - final boolean parse(final CharBufferPredicate testChars) { + boolean parse(final CharBufferPredicate testChars) { final int from = head; final int len = parse(from); if (numEscapes > 0) { @@ -290,7 +290,7 @@ final boolean parse(final CharBufferPredicate testChars) { } @Override - final boolean parse(final C context, final ContextCharBufferPredicate testChars) { + boolean parse(final C context, final ContextCharBufferPredicate testChars) { final int from = head; final int len = parse(from); if (numEscapes > 0) { @@ -302,7 +302,7 @@ final boolean parse(final C context, final ContextCharBufferPredicate tes } @Override - final void parse(final CharBufferConsumer testChars) { + void parse(final CharBufferConsumer testChars) { final int from = head; final int len = parse(from); if (numEscapes > 0) { @@ -314,7 +314,7 @@ final void parse(final CharBufferConsumer testChars) { } @Override - final void parse(final C context, final ContextCharBufferConsumer testChars) { + void parse(final C context, final ContextCharBufferConsumer testChars) { final int from = head; final int len = parse(from); if (numEscapes > 0) { @@ -326,12 +326,12 @@ final void parse(final C context, final ContextCharBufferConsumer testCha } @Override - final boolean fieldEquals(final String field, final int offset, final int len) { + boolean fieldEquals(final String field, final int offset, final int len) { return JsonIterator.fieldEquals(field, buf, offset, len); } @Override - final boolean breakOut(final FieldBufferPredicate fieldBufferFunction, final int offset, final int len) { + boolean breakOut(final FieldBufferPredicate fieldBufferFunction, final int offset, final int len) { if (numEscapes > 0) { final char[] chars = handleEscapes(offset, len); return !fieldBufferFunction.test(chars, 0, chars.length, this); @@ -341,7 +341,7 @@ final boolean breakOut(final FieldBufferPredicate fieldBufferFunction, final int } @Override - final boolean breakOut(final C context, final ContextFieldBufferPredicate fieldBufferFunction, final int offset, final int len) { + boolean breakOut(final C context, final ContextFieldBufferPredicate fieldBufferFunction, final int offset, final int len) { if (numEscapes > 0) { final char[] chars = handleEscapes(offset, len); return !fieldBufferFunction.test(context, chars, 0, chars.length, this); @@ -361,7 +361,7 @@ long test(final C context, final long mask, final ContextFieldBufferMaskedPr } @Override - final R apply(final FieldBufferFunction fieldBufferFunction, final int offset, final int len) { + R apply(final FieldBufferFunction fieldBufferFunction, final int offset, final int len) { if (numEscapes > 0) { final char[] chars = handleEscapes(offset, len); return fieldBufferFunction.apply(chars, 0, chars.length, this); @@ -371,7 +371,7 @@ final R apply(final FieldBufferFunction fieldBufferFunction, final int of } @Override - final R apply(final C context, final ContextFieldBufferFunction fieldBufferFunction, final int offset, final int len) { + R apply(final C context, final ContextFieldBufferFunction fieldBufferFunction, final int offset, final int len) { if (numEscapes > 0) { final char[] chars = handleEscapes(offset, len); return fieldBufferFunction.apply(context, chars, 0, chars.length, this); @@ -381,45 +381,45 @@ final R apply(final C context, final ContextFieldBufferFunction fie } @Override - final BigDecimal parseBigDecimal(final CharBufferFunction parseChars) { + BigDecimal parseBigDecimal(final CharBufferFunction parseChars) { final int len = parseNumber(); return parseChars.apply(buf, head - len, len); } @Override - final String parsedNumberAsString(final int len) { + String parsedNumberAsString(final int len) { return new String(buf, head - len, len); } @Override - final R parseNumber(final CharBufferFunction applyChars, final int len) { - return applyChars.apply(buf, 0, len); + R parseNumber(final CharBufferFunction applyChars, final int len) { + return applyChars.apply(buf, head - len, len); } @Override - final R parseNumber(final C context, - final ContextCharBufferFunction applyChars, - final int len) { - return applyChars.apply(context, buf, 0, len); + R parseNumber(final C context, + final ContextCharBufferFunction applyChars, + final int len) { + return applyChars.apply(context, buf, head - len, len); } @Override int parseNumber(final CharBufferToIntFunction applyChars, final int len) { - return applyChars.applyAsInt(buf, 0, len); + return applyChars.applyAsInt(buf, head - len, len); } @Override int parseNumber(final C context, final ContextCharBufferToIntFunction applyChars, final int len) { - return applyChars.applyAsInt(context, buf, 0, len); + return applyChars.applyAsInt(context, buf, head - len, len); } @Override long parseNumber(final CharBufferToLongFunction applyChars, final int len) { - return applyChars.applyAsLong(buf, 0, len); + return applyChars.applyAsLong(buf, head - len, len); } @Override long parseNumber(final C context, final ContextCharBufferToLongFunction applyChars, final int len) { - return applyChars.applyAsLong(context, buf, 0, len); + return applyChars.applyAsLong(context, buf, head - len, len); } } diff --git a/systems.comodal.json_iterator/src/main/java/systems/comodal/jsoniter/JIUtil.java b/systems.comodal.json_iterator/src/main/java/systems/comodal/jsoniter/JIUtil.java index 31b69c5..9e66b35 100644 --- a/systems.comodal.json_iterator/src/main/java/systems/comodal/jsoniter/JIUtil.java +++ b/systems.comodal.json_iterator/src/main/java/systems/comodal/jsoniter/JIUtil.java @@ -39,11 +39,63 @@ public static long compileReplacePattern(final byte byteToFind) { | (pattern << 56); } + + public static String escapeQuotesChecked(final String str) { + final int len = str.length(); + int from = 0; + do { + from = str.indexOf('"', from); + if (from < 0) { + return str; + } + int i = from - 1; + if (i < 0) { + return escapeQuotes(str, from); + } + if (str.charAt(i) == '\\') { + int escapes = 1; + while (--i >= 0) { + if (str.charAt(i) == '\\') { + ++escapes; + } else { + break; + } + } + if ((escapes & 1) == 0) { + return escapeQuotes(str, from); + } + } else { + return escapeQuotes(str, from); + } + } while (++from < len); + return str; + } + public static String escapeQuotes(final String str) { + return escapeQuotes(str, -1); + } + + private static String escapeQuotes(final String str, final int firstUnescapedQuote) { final char[] chars = str.toCharArray(); final char[] escaped = new char[chars.length << 1]; + + int from, to; + if (firstUnescapedQuote < 0) { + from = 0; + to = 0; + } else if (firstUnescapedQuote > 0) { + System.arraycopy(chars, 0, escaped, 0, firstUnescapedQuote); + escaped[firstUnescapedQuote] = '\\'; + from = firstUnescapedQuote; + to = firstUnescapedQuote + 1; + } else { + escaped[0] = '\\'; + from = 0; + to = 1; + } + char c; - for (int escapes = 0, from = 0, dest = 0, to = 0; ; to++) { + for (int escapes = 0, dest = to; ; ++to) { if (to == chars.length) { if (from == 0) { return str; diff --git a/systems.comodal.json_iterator/src/test/java/systems/comodal/jsoniter/TestInteger.java b/systems.comodal.json_iterator/src/test/java/systems/comodal/jsoniter/TestInteger.java index 98791a6..c704137 100644 --- a/systems.comodal.json_iterator/src/test/java/systems/comodal/jsoniter/TestInteger.java +++ b/systems.comodal.json_iterator/src/test/java/systems/comodal/jsoniter/TestInteger.java @@ -4,6 +4,10 @@ import org.junit.jupiter.params.provider.MethodSource; import systems.comodal.jsoniter.factories.JsonIteratorFactory; +import java.math.RoundingMode; +import java.time.Instant; + +import static java.util.concurrent.TimeUnit.MICROSECONDS; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -197,4 +201,30 @@ void test_max_int(final JsonIteratorFactory factory) { ji.readArray(); assertEquals(Integer.MIN_VALUE, ji.readInt()); } + + @ParameterizedTest + @MethodSource("systems.comodal.jsoniter.TestFactories#factories") + void testParseMicroseconds(final JsonIteratorFactory factory) { + final CharBufferFunction MICROS_PARSER = (buf, offset, len) -> { + final int secondsLength = len - 6; + final long seconds = Long.parseLong(new String(buf, offset, secondsLength)); + final long micros = Long.parseLong(new String(buf, offset + secondsLength, 6)); + return Instant.ofEpochSecond(seconds, MICROSECONDS.toNanos(micros)); + }; + + final var json = "{\"timestamp\": 1694687692989999}"; + var ji = factory.create(json); + ji.skipObjField(); + var instant = ji.applyNumberChars(MICROS_PARSER); + final var expected = Instant.ofEpochSecond(1694687692, 989999000); + assertEquals(expected, instant); + + ji = factory.create(json); + ji.skipObjField(); + final var micros = ji.readBigDecimal().movePointLeft(6); + final var seconds = micros.setScale(0, RoundingMode.DOWN); + final var nanos = micros.subtract(seconds).movePointRight(9); + instant = Instant.ofEpochSecond(seconds.longValue(), nanos.longValue()); + assertEquals(expected, instant); + } } diff --git a/systems.comodal.json_iterator/src/test/java/systems/comodal/jsoniter/TestString.java b/systems.comodal.json_iterator/src/test/java/systems/comodal/jsoniter/TestString.java index c0c4696..6eb55f3 100644 --- a/systems.comodal.json_iterator/src/test/java/systems/comodal/jsoniter/TestString.java +++ b/systems.comodal.json_iterator/src/test/java/systems/comodal/jsoniter/TestString.java @@ -33,15 +33,21 @@ void test_ascii_string(final JsonIteratorFactory factory) { @Test void testEscapeQuotes() { + final var escaped = """ + {\\"hello\\": \\"world\\"}"""; + var nestedJson = """ {"hello": "world"}"""; - assertEquals(""" - {\\"hello\\": \\"world\\"}""", JIUtil.escapeQuotes(nestedJson)); + assertEquals(escaped, JIUtil.escapeQuotes(nestedJson)); + assertEquals(escaped, JIUtil.escapeQuotesChecked(nestedJson)); nestedJson = """ {"hello": "\\"world\\""}"""; - assertEquals(""" - {\\"hello\\": \\"\\"world\\"\\"}""", JIUtil.escapeQuotes(nestedJson)); + assertEquals(escaped, JIUtil.escapeQuotes(nestedJson)); + assertEquals(escaped, JIUtil.escapeQuotesChecked(nestedJson)); + + assertTrue(escaped == JIUtil.escapeQuotes(escaped)); + assertTrue(escaped == JIUtil.escapeQuotesChecked(escaped)); } @ParameterizedTest