From 469fe96531bff6d4e34fec1cf5aa4a2cd0325937 Mon Sep 17 00:00:00 2001 From: azam Date: Sun, 14 May 2023 17:22:11 +0900 Subject: [PATCH 1/4] Added more tests. Added benchmark. Finalize API. --- .github/workflows/bench.yml | 53 +++ .github/workflows/build.yml | 2 +- .github/workflows/package.yml | 2 +- .github/workflows/publish.yml | 2 +- .gitignore | 2 + README.md | 56 ++- pom.xml | 117 +++++- .../io/azam/azamcodec/AzamCodecBench.java | 76 ++++ .../java/io/azam/azamcodec/AzamCodec.java | 396 ++++++++++++------ .../java/io/azam/azamcodec/AzamCodecTest.java | 355 +++++++++++++--- 10 files changed, 863 insertions(+), 198 deletions(-) create mode 100755 .github/workflows/bench.yml mode change 100755 => 100644 pom.xml create mode 100644 src/bench/java/io/azam/azamcodec/AzamCodecBench.java diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml new file mode 100755 index 0000000..46301b2 --- /dev/null +++ b/.github/workflows/bench.yml @@ -0,0 +1,53 @@ +name: bench + +on: + push: + branches: + - main + - develop + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + schedule: + - cron: '0 9 * * 4' + workflow_dispatch: + +jobs: + test: + name: bench-${{matrix.distribution}}-${{matrix.version}} + runs-on: ubuntu-latest + strategy: + matrix: + name: + - jdk8 + - jdk11 + include: + - name: jdk8 + version: 8 + distribution: temurin + - name: jdk11 + version: 11 + distribution: temurin + - name: jdk16 + version: 16 + distribution: temurin + - name: jdk17 + version: 17 + distribution: temurin + steps: + - name: checkout-${{matrix.name}} + uses: actions/checkout@v2 + - name: setup-toolchain-${{matrix.name}} + uses: actions/setup-java@v2 + with: + java-version: ${{matrix.version}} + distribution: ${{matrix.distribution}} + cache: maven + - name: build-${{matrix.name}} + run: mvn -P benchmark clean package -B -V + - name: benchmark-${{matrix.name}} + run: java -jar target/benchmark.jar AzamCodecBench -rf json + - name: upload-bench-results-${{matrix.name}} + uses: actions/upload-artifact@v1 + with: + name: azamcodec_bench_${{matrix.name}}_${{github.sha}} + path: ./jmh-result.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 00e3d94..dcf66b7 100755 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -48,4 +48,4 @@ jobs: distribution: ${{matrix.distribution}} cache: maven - name: verify-${{matrix.name}} - run: mvn verify --settings .settings.xml -Dgpg.skip -B -V + run: mvn clean verify --settings .settings.xml -Dgpg.skip -B -V diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 0516007..5bcc604 100755 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -34,4 +34,4 @@ jobs: MAVEN_PASSWORD: ${{secrets.MAVEN_PASSWORD}} MAVEN_GPG_KEYNAME: ${{secrets.MAVEN_GPG_KEYNAME}} MAVEN_GPG_PASSPHRASE: ${{secrets.MAVEN_GPG_PASSPHRASE}} - run: mvn package -P release --batch-mode --settings .settings.xml -DskipTests=true -DperformRelease=false -Dmaven.deploy.skip=true --update-snapshots -B -V + run: mvn clean package -P release --batch-mode --settings .settings.xml -DskipTests=true -DperformRelease=false -Dmaven.deploy.skip=true --update-snapshots -B -V diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 3ed1d5e..d455154 100755 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -33,7 +33,7 @@ jobs: MAVEN_PASSWORD: ${{secrets.MAVEN_PASSWORD}} MAVEN_GPG_KEYNAME: ${{secrets.MAVEN_GPG_KEYNAME}} MAVEN_GPG_PASSPHRASE: ${{secrets.MAVEN_GPG_PASSPHRASE}} - run: mvn deploy -P release --batch-mode --settings .settings.xml -DperformRelease=false --update-snapshots -B -V + run: mvn clean deploy -P release --batch-mode --settings .settings.xml -DperformRelease=false --update-snapshots -B -V - name: artifacts uses: softprops/action-gh-release@v1 with: diff --git a/.gitignore b/.gitignore index c8067d3..b94224c 100755 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +jmh-result.json + # https://github.com/github/gitignore/blob/master/Java.gitignore # Compiled class file diff --git a/README.md b/README.md index bd144c7..1fb000f 100755 --- a/README.md +++ b/README.md @@ -19,27 +19,67 @@ This library is published to [Maven Central](https://central.sonatype.com/artifa ```java // Decode to ints -int[] ints = AzamCodec.azamDecodeAllInts(""); +int[] ints = AzamCodec.azamDecodeInts("xytxvyyfh5wgg1"); // -559038737, 21, 49153 // Decode to big endian bytes -byte[] bytes = AzamCodec.azamDecodeAllBytes(""); +byte[][] bytes = AzamCodec.azamDecodeBytes("xytxvyyfh5wgg1"); // 0xdeadbeef, 0x15, 0xc001 ``` ### Encoding ```java // Encode from ints -String encodedInts = AzamCodec.azamEncodeInts(new int[]{}); +String encodedInts = AzamCodec.azamEncodeInts(new int[]{ -559038737, 21, 49153 }); // "xytxvyyfh5wgg1" // Encode from bytes -String encodedBytes = AzamCodec.azamEncodeBytes(new byte[][]{}); +String encodedBytes = AzamCodec.azamEncodeBytes(new byte[][]{ // + new byte[] { 0xde, 0xad, 0xbe, 0xef }, // + new byte[] { 0x15 }, // + new byte[] { 0xc0, 0x01 } // +}); // "xytxvyyfh5wgg1" +``` + +### Practical example + +Azam Codec is designed to be a sortable identifier representation, so using it to represent multi sectioned identifier is the best example. + +```java +public class RecordId { + int tenantId; + int objectId; + int recordId; + + public RecordId(String id) throws ParseException { + int[] sections = AzamCodec.azamDecodeInts(id); + this.tenantId = sections[0]; + this.objectId = sections[1]; + this.recordId = sections[2]; + } + + public String toString() { + return AzamCodec.azamEncodeInts(new int[] { this.tenantId, this.objectId, this.recordId }); + } +} ``` ## Development Standard Java development method applies. -Please run the following before sending a PR: +Please make sure that code is correctly formatted and tests are passing before sending a PR. -* You can format sources to match style with ```mvn formatter:format xml-format:xml-format``` - * You can also use VS Code extension -* Make sure tests are passing and source is properly formatted by running ```mvn verify``` +### Format source code +```sh +mvn formatter:format xml-format:xml-format +``` + +### Test +```sh +mvn verify +``` + +### Benchmark + +```sh +mvn -P benchmark package +java -jar target/benchmark.jar AzamCodecBench +``` diff --git a/pom.xml b/pom.xml old mode 100755 new mode 100644 index 4487110..c3c32c1 --- a/pom.xml +++ b/pom.xml @@ -52,6 +52,18 @@ 4.13.1 test + + org.openjdk.jmh + jmh-core + 1.36 + test + + + org.openjdk.jmh + jmh-generator-annprocess + 1.36 + test + @@ -73,13 +85,35 @@ + + org.codehaus.mojo + build-helper-maven-plugin + 3.4.0 + + + add-bench-source + generate-test-sources + + add-test-source + + + + src/bench/java + + + + + org.apache.maven.plugins maven-javadoc-plugin 2.10.4 - + 7 + + target/generated-test-sources + @@ -191,5 +225,86 @@ + + benchmark + + false + + + true + true + + + + org.openjdk.jmh + jmh-core + 1.36 + + + org.openjdk.jmh + jmh-generator-annprocess + 1.36 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.0 + + + ${project.basedir}/src/main/java + ${project.basedir}/src/bench/java + + + + org.openjdk.jmh + jmh-generator-annprocess + 1.36 + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.4.1 + + + package + + shade + + + + benchmark + + + org.openjdk.jmh.Main + + + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + + + + diff --git a/src/bench/java/io/azam/azamcodec/AzamCodecBench.java b/src/bench/java/io/azam/azamcodec/AzamCodecBench.java new file mode 100644 index 0000000..922420c --- /dev/null +++ b/src/bench/java/io/azam/azamcodec/AzamCodecBench.java @@ -0,0 +1,76 @@ +package io.azam.azamcodec; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +import java.text.ParseException; +import java.util.concurrent.TimeUnit; + +import static io.azam.azamcodec.AzamCodec.*; + +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@State(Scope.Benchmark) +@Fork(value = 1, jvmArgs = {"-Xms256M", "-Xmx256M"}) +@Warmup(iterations = 1) +@Measurement(iterations = 1) +public class AzamCodecBench { + public static void main(String[] args) throws RunnerException { + Options opt = new OptionsBuilder().include(AzamCodecBench.class.getSimpleName()).build(); + new Runner(opt).run(); + } + + @Benchmark + public void azamDecodeInts1(Blackhole bh) throws ParseException { + bh.consume(azamDecodeInts("zzzzzzzf")); + } + + @Benchmark + public void azamDecodeInts2(Blackhole bh) throws ParseException { + bh.consume(azamDecodeInts("zzzzzzzfzzzzzzzf")); + } + + @Benchmark + public void azamDecodeInts3(Blackhole bh) throws ParseException { + bh.consume(azamDecodeInts("zzzzzzzfzzzzzzzfzzzzzzzf")); + } + + @Benchmark + public void azamDecodeInts4(Blackhole bh) throws ParseException { + bh.consume(azamDecodeInts("zzzzzzzfzzzzzzzfzzzzzzzfzzzzzzzf")); + } + + @Benchmark + public void azamDecodeInts5(Blackhole bh) throws ParseException { + bh.consume(azamDecodeInts("zzzzzzzfzzzzzzzfzzzzzzzfzzzzzzzfzzzzzzzf")); + } + + @Benchmark + public void azamEncodeInts1(Blackhole bh) throws ParseException { + bh.consume(azamEncodeInts(0xffffffff)); + } + + @Benchmark + public void azamEncodeInts2(Blackhole bh) throws ParseException { + bh.consume(azamEncodeInts(0xffffffff, 0xffffffff)); + } + + @Benchmark + public void azamEncodeInts3(Blackhole bh) throws ParseException { + bh.consume(azamEncodeInts(0xffffffff, 0xffffffff, 0xffffffff)); + } + + @Benchmark + public void azamEncodeInts4(Blackhole bh) throws ParseException { + bh.consume(azamEncodeInts(0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff)); + } + + @Benchmark + public void azamEncodeInts5(Blackhole bh) throws ParseException { + bh.consume(azamEncodeInts(0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff)); + } +} diff --git a/src/main/java/io/azam/azamcodec/AzamCodec.java b/src/main/java/io/azam/azamcodec/AzamCodec.java index 02cfca9..bf5c1f0 100755 --- a/src/main/java/io/azam/azamcodec/AzamCodec.java +++ b/src/main/java/io/azam/azamcodec/AzamCodec.java @@ -10,6 +10,12 @@ import java.nio.charset.StandardCharsets; import java.text.ParseException; +/** + * Azam Codec encoder/decoder. + * + * @author azam + * @since 0.0.1 + */ public class AzamCodec { final static int[] LOWER_ALPHABETS = new int[] { // '0', '1', '2', '3', '4', '5', '6', '7', // @@ -20,6 +26,14 @@ public class AzamCodec { 'r', 's', 't', 'v', 'w', 'x', 'y', 'z' // }; + /** + * Consume bytes from `input`, generates Azam Codec encoded string as bytes, and writes to + * `output` + * + * @param output Output stream + * @param input Input stream + * @throws IOException + */ public static void azamEncodeStream(OutputStream output, InputStream input) throws IOException { int buf = -1; int prevBuf = -1; @@ -96,6 +110,14 @@ public static void azamEncodeStream(OutputStream output, InputStream input) thro } } + /** + * Consume bytes from multiple streams `inputs` sequentially, generates Azam Codec encoded string + * as bytes, and writes to `output` + * + * @param output Output stream + * @param inputs Input streams + * @throws IOException + */ public static void azamEncodeStreams(OutputStream output, InputStream... inputs) throws IOException { if (inputs == null) @@ -105,99 +127,159 @@ public static void azamEncodeStreams(OutputStream output, InputStream... inputs) throw new IllegalArgumentException("Arguments contains null value"); azamEncodeStream(output, input); } - } - public static String azamEncodeBytes(byte[]... values) throws IOException { + /** + * For each byte array `values`, generate Azam Codec encoded string section, concatenate all + * sections and returns the string. + * + * @param values Input byte arrays + * @return Azam Codec encoded string + */ + public static String azamEncodeBytes(byte[]... values) { if (values == null) throw new IllegalArgumentException("Value is null"); - ByteArrayOutputStream output = new ByteArrayOutputStream(values.length); - for (byte[] value : values) { - if (value == null) - throw new IllegalArgumentException("Value contains null value"); - ByteArrayInputStream input = new ByteArrayInputStream(value); - azamEncodeStream(output, input); + try (ByteArrayOutputStream output = new ByteArrayOutputStream(values.length)) { + for (byte[] value : values) { + if (value == null) + throw new IllegalArgumentException("Value contains null value"); + try (ByteArrayInputStream input = new ByteArrayInputStream(value)) { + azamEncodeStream(output, input); + } + } + // All Azam Codec characters are ASCII so this should do fine + return new String(output.toByteArray(), StandardCharsets.US_ASCII); + } catch (IOException io) { + // We should not get here because we are only using byte array streams, + // unless there are severe memory IO errors + throw new IllegalArgumentException("Unexpected IOException", io); } - // All Azam Codec characters are ASCII so this should do fine - return new String(output.toByteArray(), StandardCharsets.US_ASCII); } - public static String azamEncode(Number... values) throws IOException { + /** + * For each {@link java.lang.Number} array of `values`, generate Azam Codec encoded string section + * based on the number's byte representation in Big-Endian, concatenate all sections and returns + * the string. + * + * Supported instances are: + * + * + * + * @param values Input numbers + * @return Azam Codec encoded string + */ + public static String azamEncodeNumbers(Number... values) { if (values == null) throw new IllegalArgumentException("Value is null"); - ByteArrayOutputStream output = new ByteArrayOutputStream(values.length); - for (Number value : values) { - if (value == null) - throw new IllegalArgumentException("Value contains null"); - byte[] bytes; - if (value instanceof Integer) { - int data = value.intValue(); - bytes = new byte[] { // - (byte) ((data >> 24) & 0xff), // - (byte) ((data >> 16) & 0xff), // - (byte) ((data >> 8) & 0xff), // - (byte) (data & 0xff), // - }; - } else if (value instanceof Long) { - long data = value.longValue(); - bytes = new byte[] { // - (byte) ((data >> 56) & 0xff), // - (byte) ((data >> 48) & 0xff), // - (byte) ((data >> 40) & 0xff), // - (byte) ((data >> 32) & 0xff), // - (byte) ((data >> 24) & 0xff), // - (byte) ((data >> 16) & 0xff), // - (byte) ((data >> 8) & 0xff), // - (byte) (data & 0xff), // - }; - } else if (value instanceof BigInteger) { - BigInteger data = (BigInteger) value; - bytes = data.toByteArray(); - } else { - throw new IllegalArgumentException("Value is not a supported Number"); + try (ByteArrayOutputStream output = new ByteArrayOutputStream(values.length)) { + for (Number value : values) { + if (value == null) + throw new IllegalArgumentException("Value contains null"); + byte[] bytes; + if (value instanceof Integer) { + int data = value.intValue(); + bytes = new byte[] { // + (byte) ((data >> 24) & 0xff), // + (byte) ((data >> 16) & 0xff), // + (byte) ((data >> 8) & 0xff), // + (byte) (data & 0xff), // + }; + } else if (value instanceof Long) { + long data = value.longValue(); + bytes = new byte[] { // + (byte) ((data >> 56) & 0xff), // + (byte) ((data >> 48) & 0xff), // + (byte) ((data >> 40) & 0xff), // + (byte) ((data >> 32) & 0xff), // + (byte) ((data >> 24) & 0xff), // + (byte) ((data >> 16) & 0xff), // + (byte) ((data >> 8) & 0xff), // + (byte) (data & 0xff), // + }; + } else if (value instanceof BigInteger) { + BigInteger data = (BigInteger) value; + bytes = data.toByteArray(); + } else { + throw new IllegalArgumentException("Value is not a supported Number"); + } + try (ByteArrayInputStream input = new ByteArrayInputStream(bytes)) { + azamEncodeStream(output, input); + } } - ByteArrayInputStream input = new ByteArrayInputStream(bytes); - azamEncodeStream(output, input); + return new String(output.toByteArray(), StandardCharsets.US_ASCII); + } catch (IOException io) { + // We should not get here because we are only using byte array streams, + // unless there are severe memory IO errors + throw new IllegalArgumentException("Unexpected IOException", io); } - return new String(output.toByteArray(), StandardCharsets.US_ASCII); } - public static String azamEncodeInts(int... values) throws IOException { + /** + * For each int array of `values`, generate Azam Codec encoded string section based on the + * number's byte representation in Big-Endian, concatenate all sections and returns the string. + * + * @param values Input numbers + * @return Azam Codec encoded string + */ + public static String azamEncodeInts(int... values) { if (values == null) throw new IllegalArgumentException("Values are null"); - ByteArrayOutputStream output = new ByteArrayOutputStream(values.length); - for (int value : values) { - byte[] bytes = new byte[] { // - (byte) ((value >> 24) & 0xff), // - (byte) ((value >> 16) & 0xff), // - (byte) ((value >> 8) & 0xff), // - (byte) (value >> 0 & 0xff), // - }; - ByteArrayInputStream input = new ByteArrayInputStream(bytes); - azamEncodeStream(output, input); + try (ByteArrayOutputStream output = new ByteArrayOutputStream(values.length)) { + for (int value : values) { + byte[] bytes = new byte[] { // + (byte) ((value >> 24) & 0xff), // + (byte) ((value >> 16) & 0xff), // + (byte) ((value >> 8) & 0xff), // + (byte) (value & 0xff), // + }; + try (ByteArrayInputStream input = new ByteArrayInputStream(bytes)) { + azamEncodeStream(output, input); + } + } + return new String(output.toByteArray(), StandardCharsets.US_ASCII); + } catch (IOException io) { + // We should not get here because we are only using byte array streams, + // unless there are severe memory IO errors + throw new IllegalArgumentException("Unexpected IOException", io); } - return new String(output.toByteArray(), StandardCharsets.US_ASCII); } - public static String azamEncodeLongs(long... values) throws IOException { + /** + * For each long array of `values`, generate Azam Codec encoded string section based on the + * number's byte representation in Big-Endian, concatenate all sections and returns the string. + * + * @param values Input numbers + * @return Azam Codec encoded string + */ + public static String azamEncodeLongs(long... values) { if (values == null) throw new IllegalArgumentException("Values are null"); - ByteArrayOutputStream output = new ByteArrayOutputStream(values.length); - for (long value : values) { - byte[] bytes = new byte[] { // - (byte) ((value >> 56) & 0xff), // - (byte) ((value >> 48) & 0xff), // - (byte) ((value >> 40) & 0xff), // - (byte) ((value >> 32) & 0xff), // - (byte) ((value >> 24) & 0xff), // - (byte) ((value >> 16) & 0xff), // - (byte) ((value >> 8) & 0xff), // - (byte) (value & 0xff), // - }; - ByteArrayInputStream input = new ByteArrayInputStream(bytes); - azamEncodeStream(output, input); + try (ByteArrayOutputStream output = new ByteArrayOutputStream(values.length)) { + for (long value : values) { + byte[] bytes = new byte[] { // + (byte) ((value >> 56) & 0xff), // + (byte) ((value >> 48) & 0xff), // + (byte) ((value >> 40) & 0xff), // + (byte) ((value >> 32) & 0xff), // + (byte) ((value >> 24) & 0xff), // + (byte) ((value >> 16) & 0xff), // + (byte) ((value >> 8) & 0xff), // + (byte) (value & 0xff), // + }; + try (ByteArrayInputStream input = new ByteArrayInputStream(bytes)) { + azamEncodeStream(output, input); + } + } + return new String(output.toByteArray(), StandardCharsets.US_ASCII); + } catch (IOException io) { + // We should not get here because we are only using byte array streams, + // unless there are severe memory IO errors + throw new IllegalArgumentException("Unexpected IOException", io); } - return new String(output.toByteArray(), StandardCharsets.US_ASCII); } final static byte getNybbleValue(final int symbol) { @@ -301,8 +383,18 @@ final static byte getNybbleValue(final int symbol) { } } - public static void azamDecodeStream(InputStream input, OutputStream output) - throws IOException, ParseException { + /** + * Consume a single section of an Azam Codec encoded stream from `input` and write decoded bytes + * to `output`. + * + * @param input Input stream + * @param output Output stream + * @throws EOFException On end of a stream + * @throws IOException On unexpected IO exceptions + * @throws ParseException On invalid Azam Codec characters and/or character orders + */ + public static void azamDecodeStreamSection(InputStream input, OutputStream output) + throws EOFException, IOException, ParseException { // Bitshift operators recasts to integer, so we have to // mask byte variables with 0xff before doing integer operations. @@ -384,62 +476,132 @@ public static void azamDecodeStream(InputStream input, OutputStream output) return; } - public static byte[][] azamDecodeAllBytes(String value) throws IOException, ParseException { + /** + * Decode all sections of an Azam Codec encoded string `value` as arrays of byte array. + * + * @param value Azam Codec encoded string + * @return Decoded value as array of byte array + * @throws ParseException On invalid Azam Codec characters and/or character orders + */ + public static byte[][] azamDecodeBytes(String value) throws ParseException { if (value == null) throw new IllegalArgumentException("Argument is null"); - InputStream input = new ByteArrayInputStream(value.getBytes()); - byte[][] values = new byte[0][]; - for (int i = 0;; i++) { - try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { - azamDecodeStream(input, output); - - // Extend return array - byte[][] extended = new byte[i + 1][]; - if (values.length > 0) { - System.arraycopy(values, 0, extended, 0, values.length); + try (InputStream input = new ByteArrayInputStream(value.getBytes())) { + byte[][] values = new byte[0][]; + for (int i = 0;; i++) { + try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { + azamDecodeStreamSection(input, output); + + // Extend return array + byte[][] extended = new byte[i + 1][]; + if (values.length > 0) { + System.arraycopy(values, 0, extended, 0, values.length); + } + extended[i] = output.toByteArray(); + values = extended; + } catch (EOFException eof) { + // azamDecodeStream throws EOFException only on empty streams, + // so when we receive this, it should be the last one to ignore + break; } - extended[i] = output.toByteArray(); - values = extended; - } catch (EOFException eof) { - // azamDecodeStream throws EOFException only on empty streams, - // so when we receive this, it should be the last one to ignore - break; } + return values; + } catch (IOException io) { + // We should not get here because we are only using byte array streams, + // unless there are severe memory IO errors + throw new ParseException("Unexpected IO Exception: " + io.getMessage(), -1); } - return values; } - public static int[] azamDecodeAllInts(String value) throws IOException, ParseException { + /** + * Decode all sections of an Azam Codec encoded string `value` as int array. + * + * @param value Azam Codec encoded string + * @return Decoded value as int array + * @throws ParseException On invalid Azam Codec characters and/or character orders + */ + public static int[] azamDecodeInts(String value) throws ParseException { if (value == null) throw new IllegalArgumentException("Argument is null"); - InputStream input = new ByteArrayInputStream(value.getBytes()); - int[] values = new int[0]; - for (int i = 0;; i++) { - try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { - azamDecodeStream(input, output); - byte[] bytes = output.toByteArray(); - - // BigEndian byte array to int - if (bytes == null || bytes.length == 0 || bytes.length > Integer.BYTES) - throw new ParseException("Encoded value is empty or too long to convert to int", -1); - int decoded = 0; - for (int j = 0; j < bytes.length; j++) { - decoded = decoded << 8 | (bytes[j] & 0xff); + try (InputStream input = new ByteArrayInputStream(value.getBytes())) { + int[] values = new int[0]; + for (int i = 0;; i++) { + try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { + azamDecodeStreamSection(input, output); + byte[] bytes = output.toByteArray(); + + // BigEndian byte array to int + if (bytes == null || bytes.length == 0 || bytes.length > Integer.BYTES) + throw new ParseException("Encoded value is empty or too long to convert to int", -1); + int decoded = 0; + for (int j = 0; j < bytes.length; j++) { + decoded = decoded << 8 | (bytes[j] & 0xff); + } + + // Extend return array + int[] extended = new int[i + 1]; + if (values.length > 0) { + System.arraycopy(values, 0, extended, 0, values.length); + } + extended[i] = decoded; + values = extended; + } catch (EOFException eof) { + // azamDecodeStream throws EOFException only on empty streams, + // so when we receive this, it should be the last one to ignore + break; } + } + return values; + } catch (IOException io) { + // We should not get here because we are only using byte array streams, + // unless there are severe memory IO errors + throw new ParseException("Unexpected IO Exception: " + io.getMessage(), -1); + } + } - // Extend return array - int[] extended = new int[i + 1]; - if (values.length > 0) { - System.arraycopy(values, 0, extended, 0, values.length); + /** + * Decode all sections of an Azam Codec encoded string `value` as long array. + * + * @param value Azam Codec encoded string + * @return Decoded value as long array + * @throws ParseException On invalid Azam Codec characters and/or character orders + */ + public static long[] azamDecodeLongs(String value) throws ParseException { + if (value == null) + throw new IllegalArgumentException("Argument is null"); + try (InputStream input = new ByteArrayInputStream(value.getBytes())) { + long[] values = new long[0]; + for (int i = 0;; i++) { + try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { + azamDecodeStreamSection(input, output); + byte[] bytes = output.toByteArray(); + + // BigEndian byte array to int + if (bytes == null || bytes.length == 0 || bytes.length > Long.BYTES) + throw new ParseException("Encoded value is empty or too long to convert to int", -1); + long decoded = 0; + for (int j = 0; j < bytes.length; j++) { + decoded = decoded << 8 | (bytes[j] & 0xff); + } + + // Extend return array + long[] extended = new long[i + 1]; + if (values.length > 0) { + System.arraycopy(values, 0, extended, 0, values.length); + } + extended[i] = decoded; + values = extended; + } catch (EOFException eof) { + // azamDecodeStream throws EOFException only on empty streams, + // so when we receive this, it should be the last one to ignore + break; } - extended[i] = decoded; - values = extended; - } catch (EOFException eof) { - // azamDecodeStream throws EOFException only on empty streams, - // so when we receive this, it should be the last one to ignore - break; } + return values; + } catch (IOException io) { + // We should not get here because we are only using byte array streams, + // unless there are severe memory IO errors + throw new ParseException("Unexpected IO Exception: " + io.getMessage(), -1); } - return values; } } diff --git a/src/test/java/io/azam/azamcodec/AzamCodecTest.java b/src/test/java/io/azam/azamcodec/AzamCodecTest.java index 0b992da..87c97b6 100755 --- a/src/test/java/io/azam/azamcodec/AzamCodecTest.java +++ b/src/test/java/io/azam/azamcodec/AzamCodecTest.java @@ -2,8 +2,9 @@ import java.io.IOException; import java.text.ParseException; -import java.util.HashMap; -import java.util.Map; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import org.junit.Assert; import org.junit.Test; @@ -12,39 +13,171 @@ import static io.azam.azamcodec.AzamCodec.*; /** - * Test class for {@link io.azam.azamcodec.AzamDecode} + * Test class for {@link io.azam.azamcodec.AzamCodecs} * * @author azam * @since 0.0.1 */ public class AzamCodecTest { - final static Map SAMPLES = new HashMap() { + static class Sample { + String encoded = null; + byte[][] bytes = null; + long[] longs = null; + + public Sample() {} + + public Sample(String encoded, byte[][] bytes, long[] longs) { + this.encoded = encoded; + this.bytes = bytes; + this.longs = longs; + } + + public int[] ints() { + // This is equivalent of the following (since we try to keep source compatible with 1.7) + // return Arrays.stream(this.longs).mapToInt(Math::toIntExact).toArray(); + int[] ints = new int[this.longs.length]; + for (int i = 0; i < ints.length; i++) { + // Converts long to int on byte level not value + // i.e. 0xffffffffL will convert to -1 , not 4294967295 (overflow) + // NOTE: to do a value conversion, use ints[i] = Math.toIntExact(this.longs[i]); instead + ints[i] = (int) this.longs[i] & 0xffffffff; + } + return ints; + } + + public Number[] nums() { + // This is equivalent of the following (since we try to keep source compatible with 1.7) + // return Arrays.stream(this.longs).mapToInt(Math::toIntExact).toArray(); + Number[] nums = new Number[this.longs.length]; + for (int i = 0; i < nums.length; i++) { + // Converts long to int on byte level not value + // i.e. 0xffffffffL will convert to -1 , not 4294967295 (overflow) + // NOTE: to do a value conversion, use ints[i] = Math.toIntExact(this.longs[i]); instead + nums[i] = Long.valueOf(this.longs[i]); + } + return nums; + } + + public int largestBytesLength() { + int max = 0; + for (int i = 0; i < this.bytes.length; i++) { + if (this.bytes[i].length > max) { + max = this.bytes[i].length; + } + } + return max; + } + + public Sample e(String encoded) { + this.encoded = encoded; + return this; + } + + public Sample b(byte[]... bytes) { + this.bytes = bytes; + return this; + } + + public Sample l(long... longs) { + this.longs = longs; + return this; + } + } + + static Sample S(String encoded) { + return new Sample(encoded, null, null); + } + + static List SAMPLES = Arrays.asList( + // Single sections + S("0").b(b(0x00)).l(0x00), // + S("1").b(b(0x01)).l(0x01), // + S("f").b(b(0x0f)).l(0x0f), // + S("h0").b(b(0x10)).l(0x10), // + S("h1").b(b(0x11)).l(0x11), // + S("hf").b(b(0x1f)).l(0x1f), // + S("z0").b(b(0xf0)).l(0xf0), // + S("z1").b(b(0xf1)).l(0xf1), // + S("zf").b(b(0xff)).l(0xffL), // + S("hg0").b(b(0x01, 0x00)).l(0x0100L), // + S("hg1").b(b(0x01, 0x01)).l(0x0101L), // + S("hgf").b(b(0x01, 0x0f)).l(0x010fL), // + S("hh0").b(b(0x01, 0x10)).l(0x0110L), // + S("zg0").b(b(0x0f, 0x00)).l(0x0f00L), // + S("zz0").b(b(0x0f, 0xf0)).l(0x0ff0L), // + S("zz1").b(b(0x0f, 0xf1)).l(0x0ff1L), // + S("zzf").b(b(0x0f, 0xff)).l(0x0fffL), // + S("hgg0").b(b(0x10, 0x00)).l(0x1000L), // + S("hgh0").b(b(0x10, 0x10)).l(0x1010L), // + S("zgg0").b(b(0xf0, 0x00)).l(0xf000L), // + S("zzz0").b(b(0xff, 0xf0)).l(0xfff0L), // + S("zzz1").b(b(0xff, 0xf1)).l(0xfff1L), // + S("zzzf").b(b(0xff, 0xff)).l(0xffffL), // + S("hggg0").b(b(0x01, 0x00, 0x00)).l(0x010000L), // + S("hggg1").b(b(0x01, 0x00, 0x01)).l(0x010001L), // + S("hghg1").b(b(0x01, 0x01, 0x01)).l(0x010101L), // + S("hggggg0").b(b(0x01, 0x00, 0x00, 0x00)).l(0x01000000L), // + S("hggggg1").b(b(0x01, 0x00, 0x00, 0x01)).l(0x01000001L), // + S("hgggggf").b(b(0x01, 0x00, 0x00, 0x0f)).l(0x0100000fL), // + S("zzzzzzf").b(b(0x0f, 0xff, 0xff, 0xff)).l(0x0fffffffL), // + S("hgggggg0").b(b(0x10, 0x00, 0x00, 0x00)).l(0x10000000L), // + S("hgggggg1").b(b(0x10, 0x00, 0x00, 0x01)).l(0x10000001L), // + S("hggggggf").b(b(0x10, 0x00, 0x00, 0x0f)).l(0x1000000fL), // + S("zzzzzzzf").b(b(0xff, 0xff, 0xff, 0xff)).l(0xffffffffL)); + + static List MULTI_SAMPLES = new ArrayList() { { - put("0", new byte[][] {bytes(0x00)}); - put("1", new byte[][] {bytes(0x01)}); - put("f", new byte[][] {bytes(0x0f)}); - put("h0", new byte[][] {bytes(0x10)}); - put("h1", new byte[][] {bytes(0x11)}); - put("hf", new byte[][] {bytes(0x1f)}); - put("z0", new byte[][] {bytes(0xf0)}); - put("z1", new byte[][] {bytes(0xf1)}); - put("zf", new byte[][] {bytes(0xff)}); - put("hg0", new byte[][] {bytes(0x01, 0x00)}); - put("hg1", new byte[][] {bytes(0x01, 0x01)}); - put("hgf", new byte[][] {bytes(0x01, 0x0f)}); - put("hh0", new byte[][] {bytes(0x01, 0x10)}); - put("zg0", new byte[][] {bytes(0x0f, 0x00)}); - put("zz0", new byte[][] {bytes(0x0f, 0xf0)}); - put("zz1", new byte[][] {bytes(0x0f, 0xf1)}); - put("zzf", new byte[][] {bytes(0x0f, 0xff)}); - put("hgg0", new byte[][] {bytes(0x10, 0x00)}); - put("zgg0", new byte[][] {bytes(0xf0, 0x00)}); - put("zzz1", new byte[][] {bytes(0xff, 0xf1)}); - put("zzzf", new byte[][] {bytes(0xff, 0xff)}); + // 2 sections + for (Sample s1 : SAMPLES) { + for (Sample s2 : SAMPLES) { + String encoded = s1.encoded + s2.encoded; + byte[][] bytes = new byte[s1.bytes.length + s2.bytes.length][]; + for (int i = 0; i < s1.bytes.length; i++) { + bytes[i] = new byte[s1.bytes[i].length]; + System.arraycopy(s1.bytes[i], 0, bytes[i], 0, s1.bytes[i].length); + } + for (int i = 0; i < s2.bytes.length; i++) { + bytes[i + s1.bytes.length] = new byte[s2.bytes[i].length]; + System.arraycopy(s2.bytes[i], 0, bytes[i + s1.bytes.length], 0, s2.bytes[i].length); + } + long[] longs = new long[s1.longs.length + s2.longs.length]; + System.arraycopy(s1.longs, 0, longs, 0, s1.longs.length); + System.arraycopy(s2.longs, 0, longs, s1.longs.length, s2.longs.length); + add(S(encoded).b(bytes).l(longs)); + } + } + // 3 sections + for (Sample s1 : SAMPLES) { + for (Sample s2 : SAMPLES) { + for (Sample s3 : SAMPLES) { + String encoded = s1.encoded + s2.encoded + s3.encoded; + byte[][] bytes = new byte[s1.bytes.length + s2.bytes.length + s3.bytes.length][]; + for (int i = 0; i < s1.bytes.length; i++) { + bytes[i] = new byte[s1.bytes[i].length]; + System.arraycopy(s1.bytes[i], 0, bytes[i], 0, s1.bytes[i].length); + } + for (int i = 0; i < s2.bytes.length; i++) { + bytes[i + s1.bytes.length] = new byte[s2.bytes[i].length]; + System.arraycopy(s2.bytes[i], 0, bytes[i + s1.bytes.length], 0, s2.bytes[i].length); + } + for (int i = 0; i < s3.bytes.length; i++) { + bytes[i + s1.bytes.length + s2.bytes.length] = new byte[s3.bytes[i].length]; + System.arraycopy(s3.bytes[i], 0, bytes[i + s1.bytes.length + s2.bytes.length], 0, + s3.bytes[i].length); + } + long[] longs = new long[s1.longs.length + s2.longs.length + s3.longs.length]; + System.arraycopy(s1.longs, 0, longs, 0, s1.longs.length); + System.arraycopy(s2.longs, 0, longs, s1.longs.length, s2.longs.length); + System.arraycopy(s3.longs, 0, longs, s1.longs.length + s2.longs.length, + s3.longs.length); + add(S(encoded).b(bytes).l(longs)); + } + } + } } }; - final static byte[] bytes(int... values) { + final static byte[] b(int... values) { byte[] value = new byte[values.length]; for (int i = 0; i < values.length; i++) { value[i] = (byte) (values[i] & 0xff); @@ -52,63 +185,129 @@ final static byte[] bytes(int... values) { return value; } - final static void assertAzamEncodeBytes(String expected, byte[]... values) { - try { - String actual = azamEncodeBytes(values); - Assert.assertEquals(expected, actual); - } catch (IOException e) { - Assert.fail("azamEncodeBytes throws IOException"); + final static byte[][] bb(byte[]... values) { + byte[][] value = new byte[values.length][]; + for (int i = 0; i < values.length; i++) { + value[i] = values[i]; } + return value; } @Test public void testAzamEncodeBytes() { - for (Map.Entry entry : SAMPLES.entrySet()) { - assertAzamEncodeBytes(entry.getKey(), entry.getValue()); + for (Sample sample : SAMPLES) { + Assert.assertEquals("azamEncodeBytes failed for value " + sample.encoded, sample.encoded, + azamEncodeBytes(sample.bytes)); + } + for (Sample sample : MULTI_SAMPLES) { + Assert.assertEquals("azamEncodeBytes failed for value " + sample.encoded, sample.encoded, + azamEncodeBytes(sample.bytes)); } } - void assertAzamDecodeAllBytes(String value, byte[]... expected) throws ParseException { - try { - byte[][] actual = azamDecodeAllBytes(value); - for (int i = 0; i < expected.length; i++) { - Assert.assertArrayEquals(expected[i], actual[i]); - } - } catch (IOException e) { - Assert.fail("azamDecodeAllBytes throws IOException"); + @Test + public void testAzamEncodeInts() { + for (Sample sample : SAMPLES) { + Assert.assertEquals("azamEncodeLongs failed for value " + sample.encoded, sample.encoded, + azamEncodeInts(sample.ints())); + } + for (Sample sample : MULTI_SAMPLES) { + Assert.assertEquals("azamEncodeLongs failed for value " + sample.encoded, sample.encoded, + azamEncodeInts(sample.ints())); + } + } + + @Test + public void testAzamEncodeLongs() { + for (Sample sample : SAMPLES) { + Assert.assertEquals("azamEncodeLongs failed for value " + sample.encoded, sample.encoded, + azamEncodeLongs(sample.longs)); + } + for (Sample sample : MULTI_SAMPLES) { + Assert.assertEquals("azamEncodeLongs failed for value " + sample.encoded, sample.encoded, + azamEncodeLongs(sample.longs)); + } + } + + @Test + public void testAzamEncode() { + for (Sample sample : SAMPLES) { + Assert.assertEquals("azamEncodeNumbers failed for value " + sample.encoded, sample.encoded, + azamEncodeNumbers(sample.nums())); + } + for (Sample sample : MULTI_SAMPLES) { + Assert.assertEquals("azamEncodeNumbers failed for value " + sample.encoded, sample.encoded, + azamEncodeNumbers(sample.nums())); } } @Test public void testAzamDecodeAllBytes() throws ParseException { - for (Map.Entry entry : SAMPLES.entrySet()) { - assertAzamDecodeAllBytes(entry.getKey(), entry.getValue()); + for (Sample sample : SAMPLES) { + byte[][] actual = azamDecodeBytes(sample.encoded); + for (int i = 0; i < sample.bytes.length; i++) { + Assert.assertArrayEquals("azamDecodeBytes failed for " + sample.encoded, sample.bytes[i], + actual[i]); + } + } + for (Sample sample : MULTI_SAMPLES) { + byte[][] actual = azamDecodeBytes(sample.encoded); + for (int i = 0; i < sample.bytes.length; i++) { + Assert.assertArrayEquals("azamDecodeBytes failed for " + sample.encoded, sample.bytes[i], + actual[i]); + } + } + } + + void assertAzamDecodeInts(String value, int... expected) { + try { + int[] actual = azamDecodeInts(value); + Assert.assertArrayEquals("azamDecodeInts failed for " + value, expected, actual); + } catch (ParseException e) { + Assert.fail("azamDecodeInts throws ParseException"); + } + } + + @Test + public void testAzamDecodeInts() throws ParseException { + for (Sample sample : SAMPLES) { + if (sample.largestBytesLength() <= Integer.BYTES) { + int[] actual = azamDecodeInts(sample.encoded); + Assert.assertArrayEquals("azamDecodeInts failed for " + sample.encoded, sample.ints(), + actual); + } + } + for (Sample sample : MULTI_SAMPLES) { + if (sample.largestBytesLength() <= Integer.BYTES) { + int[] actual = azamDecodeInts(sample.encoded); + Assert.assertArrayEquals("azamDecodeInts failed for " + sample.encoded, sample.ints(), + actual); + } } } @Test - public void testAzamDecodeAllInts() throws IOException, ParseException { - Assert.assertArrayEquals(new int[0], azamDecodeAllInts("")); - Assert.assertArrayEquals(new int[] {0x00}, azamDecodeAllInts("0")); - Assert.assertArrayEquals(new int[] {0x01}, azamDecodeAllInts("1")); - Assert.assertArrayEquals(new int[] {0x0f}, azamDecodeAllInts("f")); - Assert.assertArrayEquals(new int[] {0x10}, azamDecodeAllInts("h0")); - Assert.assertArrayEquals(new int[] {0x11}, azamDecodeAllInts("h1")); - Assert.assertArrayEquals(new int[] {0x1f}, azamDecodeAllInts("hf")); - Assert.assertArrayEquals(new int[] {0xf0}, azamDecodeAllInts("z0")); - Assert.assertArrayEquals(new int[] {0xf1}, azamDecodeAllInts("z1")); - Assert.assertArrayEquals(new int[] {0xff}, azamDecodeAllInts("zf")); - Assert.assertArrayEquals(new int[] {0x0100}, azamDecodeAllInts("hg0")); - Assert.assertArrayEquals(new int[] {0x0fff}, azamDecodeAllInts("zzf")); - Assert.assertArrayEquals(new int[] {0xffff}, azamDecodeAllInts("zzzf")); - Assert.assertArrayEquals(new int[] {0x0fffff}, azamDecodeAllInts("zzzzf")); - Assert.assertArrayEquals(new int[] {0xffffff}, azamDecodeAllInts("zzzzzf")); - Assert.assertArrayEquals(new int[] {0x0fffffff}, azamDecodeAllInts("zzzzzzf")); - Assert.assertArrayEquals(new int[] {0xffffffff}, azamDecodeAllInts("zzzzzzzf")); + public void testAzamDecodeLongs() throws ParseException { + for (Sample sample : SAMPLES) { + if (sample.largestBytesLength() <= Long.BYTES) { + long[] actual = azamDecodeLongs(sample.encoded); + Assert.assertArrayEquals("azamDecodeLongs failed for " + sample.encoded, sample.longs, + actual); + } + } + for (Sample sample : MULTI_SAMPLES) { + if (sample.largestBytesLength() <= Long.BYTES) { + long[] actual = azamDecodeLongs(sample.encoded); + Assert.assertArrayEquals("azamDecodeLongs failed for " + sample.encoded, sample.longs, + actual); + } + } } - void assertAcamDecodeAllIntsParseException(String... values) throws IOException { - for (String value : values) { + @Test + public void testAzamDecodeIntsParseException() { + String[] invalids = new String[] {"h", "hh", "hhh", "_0", "gf", "hggggggg0"}; + for (String value : invalids) { ThrowingRunnable runnable = new ThrowingRunnable() { String value = null; @@ -118,17 +317,35 @@ public ThrowingRunnable setValue(String value) { } @Override - public void run() throws IOException, ParseException { - int[] unexpected = azamDecodeAllInts(value); + public void run() throws ParseException { + int[] unexpected = azamDecodeInts(value); } }.setValue(value); - Assert.assertThrows(ParseException.class, runnable); + Assert.assertThrows("azamDecodeInts expects ParseException for " + value, + ParseException.class, runnable); } } @Test - public void testAzamDecodeAllIntsParseException() throws IOException { - assertAcamDecodeAllIntsParseException("h", "hh", "hhh", "_0", "gf"); + public void testAzamDecodeLongsParseException() { + String[] invalids = new String[] {"h", "hh", "hhh", "_0", "gf", "hggggggggggggggg0"}; + for (String value : invalids) { + ThrowingRunnable runnable = new ThrowingRunnable() { + String value = null; + + public ThrowingRunnable setValue(String value) { + this.value = value; + return this; + } + + @Override + public void run() throws ParseException { + long[] unexpected = azamDecodeLongs(value); + } + }.setValue(value); + Assert.assertThrows("azamDecodeLongs expects ParseException for " + value, + ParseException.class, runnable); + } } } From 471b2aa4e21d1fa28b438b2b3f49299b575e9367 Mon Sep 17 00:00:00 2001 From: azam Date: Sun, 14 May 2023 17:26:32 +0900 Subject: [PATCH 2/4] Revert benchmark iterations --- src/bench/java/io/azam/azamcodec/AzamCodecBench.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bench/java/io/azam/azamcodec/AzamCodecBench.java b/src/bench/java/io/azam/azamcodec/AzamCodecBench.java index 922420c..f8ccb87 100644 --- a/src/bench/java/io/azam/azamcodec/AzamCodecBench.java +++ b/src/bench/java/io/azam/azamcodec/AzamCodecBench.java @@ -16,8 +16,8 @@ @OutputTimeUnit(TimeUnit.NANOSECONDS) @State(Scope.Benchmark) @Fork(value = 1, jvmArgs = {"-Xms256M", "-Xmx256M"}) -@Warmup(iterations = 1) -@Measurement(iterations = 1) +@Warmup(iterations = 3) +@Measurement(iterations = 10) public class AzamCodecBench { public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder().include(AzamCodecBench.class.getSimpleName()).build(); From 6229353a1aa4bc58000dbf19b3d89df739c58d16 Mon Sep 17 00:00:00 2001 From: azam Date: Sun, 14 May 2023 17:27:41 +0900 Subject: [PATCH 3/4] Bump version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c3c32c1..cb9601f 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.azam.azamcodec azamcodec - 0.0.1 + 0.1.0 azamcodec An encoder and decoder implementation in Java for Azam Codec, a lexicographically sortable multi-section base16 encoding of byte array. https://github.com/azam/azamcodec-java From c067dd62e1b397cc2917d8088cc1ce653b460753 Mon Sep 17 00:00:00 2001 From: azam Date: Sun, 14 May 2023 17:40:32 +0900 Subject: [PATCH 4/4] Remove redundant declaration on GHA. Add VS Code settings. --- .github/workflows/bench.yml | 5 +---- .github/workflows/build.yml | 5 +---- .vscode/settings.json | 16 ++++++++++++++++ .vscode/tasks.json | 37 +++++++++++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 8 deletions(-) create mode 100755 .vscode/settings.json create mode 100644 .vscode/tasks.json diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 46301b2..8c8119a 100755 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -13,13 +13,10 @@ on: jobs: test: - name: bench-${{matrix.distribution}}-${{matrix.version}} + name: bench-${{matrix.name}} runs-on: ubuntu-latest strategy: matrix: - name: - - jdk8 - - jdk11 include: - name: jdk8 version: 8 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dcf66b7..0556190 100755 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,13 +18,10 @@ on: jobs: test: - name: test-${{matrix.distribution}}-${{matrix.version}} + name: test-${{matrix.name}} runs-on: ubuntu-latest strategy: matrix: - name: - - jdk8 - - jdk11 include: - name: jdk8 version: 8 diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100755 index 0000000..02525b0 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,16 @@ +{ + "java.format.settings.profile": "GoogleStyle", + "java.format.settings.url": "./eclipse-java-google-style.xml", + "java.configuration.updateBuildConfiguration": "automatic", + "[java]": { + "editor.formatOnSave": true, + "editor.detectIndentation": false, + "editor.insertSpaces": true, + "editor.tabSize": 2 + }, + "[xml]": { + "editor.detectIndentation": false, + "editor.insertSpaces": true, + "editor.tabSize": 2 + } +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..88cdcc3 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,37 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "clean", + "type": "shell", + "command": "mvn -B clean", + "group": "build" + }, + { + "label": "format", + "type": "shell", + "command": "mvn -B formatter:format xml-format:xml-format", + "group": "build" + }, + { + "label": "verify", + "type": "shell", + "command": "mvn -B verify", + "group": "build" + }, + { + "label": "test", + "type": "shell", + "command": "mvn -B test", + "group": "test" + }, + { + "label": "bench", + "type": "shell", + "command": "mvn -B -P benchmark package && java -jar ${workspaceFolder}/target/benchmark.jar AzamCodecBench -rf json", + "group": "test" + } + ] +} \ No newline at end of file