From 53c81b101ff8aa6573e31b37404ea2e489c5e64e Mon Sep 17 00:00:00 2001 From: Filip Date: Tue, 30 Jul 2024 22:28:59 +0200 Subject: [PATCH] Add CBOR-LD custom dictionaries (#102) --- .gitignore | 2 + README.md | 5 ++ pom.xml | 22 +++--- .../com/apicatalog/cli/JsonCborConfig.java | 5 ++ .../apicatalog/cli/JsonCborDictionary.java | 77 +++++++++++++++++++ .../com/apicatalog/cli/JsonDictionary.java | 47 ----------- .../apicatalog/cli/command/CompressCmd.java | 19 +++-- .../apicatalog/cli/command/DecompressCmd.java | 44 ++++++----- .../com/apicatalog/cli/command/ToRdfCmd.java | 36 +++------ utopia-barcodes-dictionary-example.json | 3 +- 10 files changed, 154 insertions(+), 106 deletions(-) create mode 100644 src/main/java/com/apicatalog/cli/JsonCborConfig.java create mode 100644 src/main/java/com/apicatalog/cli/JsonCborDictionary.java delete mode 100644 src/main/java/com/apicatalog/cli/JsonDictionary.java diff --git a/.gitignore b/.gitignore index dee6f0d..ff8d9d9 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ buildNumber.properties *.swp /out.cborld +/o.cborld +/ex.jsonld diff --git a/README.md b/README.md index 513cc46..e8d3901 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,11 @@ Options: > ld-cli compress -i file:/home/filip/example.jsonld ``` +### Custom CBOR-LD dictionaries +```bash +> ld-cli decompress --pretty --dictionary=./utopia-barcodes-dictionary-example.json <<< 'd90664a60183198000198001198002189d82187618a418b8a3189c18a618ce18b218d01ae592208118baa2189c18a018a8447582002018be18aa18c0a5189c186c18d60418e018e618e258417ab7c2e56b49e2cce62184ce26818e15a8b173164401b5d3bb93ffd6d2b5eb8f6ac0971502ae3dd49d17ec66528164034c912685b8111bc04cdc9ec13dbadd91cc18e418ac' +``` + ## Contributing All PR's welcome! diff --git a/pom.xml b/pom.xml index 00cacd3..063f098 100644 --- a/pom.xml +++ b/pom.xml @@ -29,14 +29,13 @@ 1.4.1 0.2.2 - 2.0.1 + 2.0.1 24.0.2 4.7.6 1.2.0 - + 2.4.1 - 5.10.3 @@ -54,10 +53,15 @@ ${iridium.version} + + jakarta.json + jakarta.json-api + ${jakarta.json.version} + org.glassfish jakarta.json - ${jakarta.json-p.version} + ${jakarta.json.version} @@ -184,18 +188,18 @@ 0.10.2 true - - + + --no-server --no-fallback --report-unsupported-elements-at-runtime -H:+UnlockExperimentalVMOptions -H:+ReportExceptionStackTraces - -H:+AddAllCharsets + -H:+AddAllCharsets -H:ReflectionConfigurationFiles=${project.basedir}/graal.json -H:IncludeResourceBundles=org.glassfish.json.messages -H:IncludeResources=.*/.*jsonld$ - + build-native @@ -212,7 +216,7 @@ test - + diff --git a/src/main/java/com/apicatalog/cli/JsonCborConfig.java b/src/main/java/com/apicatalog/cli/JsonCborConfig.java new file mode 100644 index 0000000..3499411 --- /dev/null +++ b/src/main/java/com/apicatalog/cli/JsonCborConfig.java @@ -0,0 +1,5 @@ +package com.apicatalog.cli; + +public class JsonCborConfig { + +} diff --git a/src/main/java/com/apicatalog/cli/JsonCborDictionary.java b/src/main/java/com/apicatalog/cli/JsonCborDictionary.java new file mode 100644 index 0000000..2b05f98 --- /dev/null +++ b/src/main/java/com/apicatalog/cli/JsonCborDictionary.java @@ -0,0 +1,77 @@ +package com.apicatalog.cli; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; + +import com.apicatalog.cborld.document.DocumentDictionary; +import com.apicatalog.cborld.document.DocumentDictionaryBuilder; +import com.apicatalog.jsonld.JsonLdError; +import com.apicatalog.jsonld.document.Document; +import com.apicatalog.jsonld.loader.DocumentLoader; +import com.apicatalog.jsonld.loader.DocumentLoaderOptions; +import com.apicatalog.jsonld.loader.SchemeRouter; + +import jakarta.json.Json; +import jakarta.json.JsonNumber; +import jakarta.json.JsonObject; +import jakarta.json.stream.JsonParser; + +public class JsonCborDictionary { + + public static DocumentDictionary of(URI input) throws IOException, JsonLdError { + if (input.isAbsolute()) { + final DocumentLoader loader = SchemeRouter.defaultInstance(); + return of(loader.loadDocument(input, new DocumentLoaderOptions())); + } + return of(new ByteArrayInputStream(Files.readAllBytes(Path.of(input.toString())))); + } + + public static DocumentDictionary of(Document doc) { + return of(doc.getJsonContent().orElseThrow(() -> new IllegalArgumentException("Invalid dictionary")).asJsonObject()); + } + + public static DocumentDictionary of(InputStream is) { + return of(parse(is)); + } + + public static DocumentDictionary of(JsonObject json) { + var builder = DocumentDictionaryBuilder.create(json.getInt("code")); + + for (var item : json.entrySet()) { + switch (item.getKey()) { + case "code": + continue; + case "context": + item.getValue().asJsonObject().entrySet() + .forEach(e -> builder.context( + ((JsonNumber) e.getValue()).intValue(), + e.getKey())); + default: + item.getValue().asJsonObject().entrySet() + .forEach(e -> builder.type( + item.getKey(), + ((JsonNumber) e.getValue()).intValue(), + e.getKey())); + } + } + + return builder.build(); + } + + public static JsonObject parse(InputStream json) { + try (final JsonParser parser = Json.createParser(json)) { + + if (!parser.hasNext()) { + throw new IllegalArgumentException("Invalid dictionary definition"); + } + + parser.next(); + + return parser.getObject(); + } + } +} diff --git a/src/main/java/com/apicatalog/cli/JsonDictionary.java b/src/main/java/com/apicatalog/cli/JsonDictionary.java deleted file mode 100644 index a7dadb9..0000000 --- a/src/main/java/com/apicatalog/cli/JsonDictionary.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.apicatalog.cli; - -import java.io.InputStream; - -import com.apicatalog.cborld.document.DocumentDictionary; -import com.apicatalog.cborld.document.DocumentDictionaryBuilder; -import com.apicatalog.jsonld.json.JsonProvider; - -import jakarta.json.JsonNumber; -import jakarta.json.JsonObject; -import jakarta.json.stream.JsonParser; - -public class JsonDictionary { - - public static DocumentDictionary of(InputStream is) { - - var json = parse(is); - - var builder = DocumentDictionaryBuilder.create(json.getInt("code")); - - for (var item : json.entrySet()) { - switch (item.getKey()) { - case "code": - continue; - case "context": - item.getValue().asJsonObject().entrySet() - .forEach(e -> builder.context( - ((JsonNumber) e.getValue()).intValue(), - e.getKey())); - default: - item.getValue().asJsonObject().entrySet() - .forEach(e -> builder.type( - item.getKey(), - ((JsonNumber) e.getValue()).intValue(), - e.getKey())); - } - } - - return builder.build(); - } - - public static JsonObject parse(InputStream json) { - try (final JsonParser parser = JsonProvider.instance().createParser(json)) { - return parser.getObject(); - } - } -} diff --git a/src/main/java/com/apicatalog/cli/command/CompressCmd.java b/src/main/java/com/apicatalog/cli/command/CompressCmd.java index 0e5ac8d..f982048 100644 --- a/src/main/java/com/apicatalog/cli/command/CompressCmd.java +++ b/src/main/java/com/apicatalog/cli/command/CompressCmd.java @@ -10,9 +10,9 @@ import com.apicatalog.base.Base16; import com.apicatalog.cborld.CborLd; -import com.apicatalog.cborld.barcode.BarcodesConfig; import com.apicatalog.cborld.config.DefaultConfig; import com.apicatalog.cborld.config.V05Config; +import com.apicatalog.cli.JsonCborDictionary; import com.apicatalog.jsonld.document.Document; import com.apicatalog.jsonld.document.JsonDocument; import com.apicatalog.jsonld.json.JsonUtils; @@ -43,9 +43,12 @@ public final class CompressCmd implements Callable { @Option(names = { "-a", "--keep-arrays" }, description = "keep arrays with just one element") boolean keepArrays = false; - @Option(names = { "-m", "--mode" }, description = "processing mode", paramLabel = "default|barcodes|v05") + @Option(names = { "-m", "--mode" }, description = "processing mode", paramLabel = "default|v05") String mode = "default"; + @Option(names = { "-d", "--dictionary" }, description = "a custom dictionary (JSON) location") + URI dictionary = null; + @Option(names = { "-x", "--hex" }, description = "print encoded as hexadecimal bytes") boolean hex = false; @@ -80,14 +83,19 @@ public Integer call() throws Exception { } var config = switch (mode) { - case "barcodes" -> BarcodesConfig.INSTANCE; case "v05" -> V05Config.INSTANCE; default -> DefaultConfig.INSTANCE; }; - var encoded = CborLd.createEncoder(config) + var encoder = CborLd.createEncoder(config) .base(base) - .compactArray(!keepArrays) + .compactArray(!keepArrays); + + if (dictionary != null) { + encoder.dictionary(JsonCborDictionary.of(dictionary)); + } + + var encoded = encoder .build() .encode(json.asJsonObject()); @@ -113,5 +121,4 @@ static byte[] encode(byte[] encoded, boolean hex) throws IOException { static final String toString(byte value) { return String.format("%02x", value); } - } \ No newline at end of file diff --git a/src/main/java/com/apicatalog/cli/command/DecompressCmd.java b/src/main/java/com/apicatalog/cli/command/DecompressCmd.java index 543c612..4b2c0e2 100644 --- a/src/main/java/com/apicatalog/cli/command/DecompressCmd.java +++ b/src/main/java/com/apicatalog/cli/command/DecompressCmd.java @@ -1,5 +1,6 @@ package com.apicatalog.cli.command; +import java.io.InputStream; import java.net.URI; import java.net.http.HttpClient.Redirect; import java.net.http.HttpRequest; @@ -11,14 +12,12 @@ import com.apicatalog.base.Base16; import com.apicatalog.cborld.CborLd; -import com.apicatalog.cborld.barcode.BarcodesConfig; import com.apicatalog.cborld.config.DefaultConfig; import com.apicatalog.cborld.config.V05Config; -import com.apicatalog.cborld.decoder.DecoderConfig; +import com.apicatalog.cli.JsonCborDictionary; import com.apicatalog.cli.JsonOutput; import jakarta.json.JsonStructure; -import jakarta.json.JsonValue; import picocli.CommandLine.Command; import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.Option; @@ -42,12 +41,15 @@ public final class DecompressCmd implements Callable { @Option(names = { "-a", "--keep-arrays" }, description = "keep arrays with just one element") boolean keepArrays = false; - @Option(names = { "-m", "--mode" }, description = "processing mode", paramLabel = "default|barcodes|v05") - String mode = "default"; + @Option(names = { "-d", "--dictionary" }, description = "a custom dictionary (JSON) location") + URI dictionary = null; @Option(names = { "-x", "--hex" }, description = "input is encoded as hexadecimal bytes") boolean hex = false; + @Option(names = { "-m", "--mode" }, description = "processing mode", paramLabel = "default|v05") + String mode = "default"; + @Spec CommandSpec spec; @@ -59,18 +61,24 @@ private DecompressCmd() { @Override public Integer call() throws Exception { - var encoded = decode(fetch()); + hex = hex || input == null; - final DecoderConfig config = switch (mode) { - case "barcodes" -> BarcodesConfig.INSTANCE; + var encoded = decode(fetch(input)); + + var config = switch (mode) { case "v05" -> V05Config.INSTANCE; default -> DefaultConfig.INSTANCE; }; - final JsonValue output = CborLd.createDecoder(config) + var decoder = CborLd.createDecoder(config) .base(base) - .compactArray(!keepArrays) - .build() + .compactArray(!keepArrays); + + if (dictionary != null) { + decoder.dictionary(JsonCborDictionary.of(dictionary)); + } + + var output = decoder.build() .decode(encoded); JsonOutput.print((JsonStructure) output, pretty); @@ -78,9 +86,8 @@ public Integer call() throws Exception { return spec.exitCodeOnSuccess(); } - byte[] fetch() throws Exception { + static byte[] fetch(URI input) throws Exception { if (input == null) { - hex = true; return System.in.readAllBytes(); } @@ -88,12 +95,14 @@ byte[] fetch() throws Exception { if ("file".equalsIgnoreCase(input.getScheme())) { return Files.readAllBytes(Path.of(input)); } - return fetch(input); + try (var is = fetchHttp(input)) { + return is.readAllBytes(); + } } return Files.readAllBytes(Path.of(input.toString())); } - static byte[] fetch(URI uri) throws Exception { + static InputStream fetchHttp(URI uri) throws Exception { var request = HttpRequest.newBuilder() .GET() @@ -106,10 +115,7 @@ static byte[] fetch(URI uri) throws Exception { if (response.statusCode() != 200) { throw new IllegalArgumentException("The [" + uri + "] has returned code " + response.statusCode() + ", expected 200 OK"); } - - try (var is = response.body()) { - return is.readAllBytes(); - } + return response.body(); } byte[] decode(byte[] data) { diff --git a/src/main/java/com/apicatalog/cli/command/ToRdfCmd.java b/src/main/java/com/apicatalog/cli/command/ToRdfCmd.java index 9ba246e..2146f47 100644 --- a/src/main/java/com/apicatalog/cli/command/ToRdfCmd.java +++ b/src/main/java/com/apicatalog/cli/command/ToRdfCmd.java @@ -19,15 +19,7 @@ import picocli.CommandLine.Option; import picocli.CommandLine.Spec; -@Command( - name = "tordf", - mixinStandardHelpOptions = false, - description = "Transform JSON-LD document into N-Quads document", - sortOptions = true, - descriptionHeading = "%n", - parameterListHeading = "%nParameters:%n", - optionListHeading = "%nOptions:%n" - ) +@Command(name = "tordf", mixinStandardHelpOptions = false, description = "Transform JSON-LD document into N-Quads document", sortOptions = true, descriptionHeading = "%n", parameterListHeading = "%nParameters:%n", optionListHeading = "%nOptions:%n") public final class ToRdfCmd implements Callable { @Option(names = { "-h", "--help" }, hidden = true, usageHelp = true) @@ -36,7 +28,7 @@ public final class ToRdfCmd implements Callable { @Option(names = { "-i", "--input" }, description = "input document IRI") URI input = null; - @Option(names = { "-c", "--context" }, description = "context IRI") + @Option(names = { "-c", "--context" }, description = "expansion context IRI") URI context = null; @Option(names = { "-b", "--base" }, description = "input document base IRI") @@ -45,24 +37,20 @@ public final class ToRdfCmd implements Callable { @Option(names = { "-m", "--mode" }, description = "processing mode", paramLabel = "1.0|1.1") String mode = "1.1"; - @Option(names = { "-o", "--ordered" }, - description = "certain algorithm processing steps are ordered lexicographically") + @Option(names = { "-o", "--ordered" }, description = "certain algorithm processing steps are ordered lexicographically") boolean ordered = false; - @Option(names = { "-d", "--direction" }, - description = "determines how value objects containing a base direction are transformed", - paramLabel = "I18N_DATATYPE|COMPOUND_LITERAL") + @Option(names = { "-d", "--direction" }, description = "determines how value objects containing a base direction are transformed", paramLabel = "I18N_DATATYPE|COMPOUND_LITERAL") String rdfDirection; - @Option(names = { "-n", "--no-blanks" }, - description = "omit blank nodes for triple predicates" - ) - boolean generalizedRdf = true; + @Option(names = { "-n", "--no-blanks" }, description = "omit blank nodes for triple predicates") + boolean generalizedRdf = true; @Spec CommandSpec spec; - private ToRdfCmd() {} + private ToRdfCmd() { + } @Override public Integer call() throws Exception { @@ -84,16 +72,16 @@ public Integer call() throws Exception { api.base(base); api.ordered(ordered); api.produceGeneralizedRdf(generalizedRdf); - + if (rdfDirection != null) { api.rdfDirection(RdfDirection.valueOf(rdfDirection.toUpperCase())); } final RdfDataset output = api.get(); - + final StringWriter stringWriter = new StringWriter(); - - final RdfWriter writer = Rdf.createWriter(MediaType.N_QUADS, stringWriter); + + final RdfWriter writer = Rdf.createWriter(MediaType.N_QUADS, stringWriter); writer.write(output); System.out.println(stringWriter.toString()); diff --git a/utopia-barcodes-dictionary-example.json b/utopia-barcodes-dictionary-example.json index ddef44b..df23fe6 100644 --- a/utopia-barcodes-dictionary-example.json +++ b/utopia-barcodes-dictionary-example.json @@ -11,4 +11,5 @@ "eddsa-rdfc-2022": 3, "ecdsa-xi-2023": 4 } -} \ No newline at end of file +} +