Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Graph Commons JSON Converter #929

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/use/rosetta/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,15 @@ $ ./enola rosetta --no-file-loader --in test/picasso.ttl --out "docs/BUILT/picas

![Smaller Graph of Painters](../../BUILT/picasso-small.gv.svg)

### Graph Commons

```bash cd ../.././..
$ ./enola rosetta --in enola:TikaMediaTypes --out /tmp/TikaMediaTypes.graphcommons.json
...
```

produces a JSON which can be imported into [GraphCommons.com](https://graphcommons.com/).

### GEXF

```bash cd ../.././..
Expand Down
7 changes: 7 additions & 0 deletions java/dev/enola/cli/CommandWithIRI.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import dev.enola.core.view.EnolaMessages;
import dev.enola.rdf.io.RdfWriterConverter;
import dev.enola.rdf.proto.ProtoThingRdfConverter;
import dev.enola.thing.gen.graphcommons.GraphCommonsJsonGenerator;
import dev.enola.thing.gen.graphviz.GraphvizGenerator;
import dev.enola.thing.message.ProtoThings;
import dev.enola.thing.metadata.ThingMetadataProvider;
Expand Down Expand Up @@ -108,6 +109,12 @@ protected void write(Message thing) throws IOException {
return;
}

if (Format.GraphCommons.equals(format) && thing instanceof Things protoThings) {
var javaThings = ProtoThings.proto2java(protoThings.getThingsList());
new GraphCommonsJsonGenerator().convertIntoOrThrow(javaThings, resource);
return;
}

// Otherwise
new ProtoIO(typeRegistryWrapper.get()).write(thing, resource);
}
Expand Down
2 changes: 2 additions & 0 deletions java/dev/enola/cli/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import dev.enola.rdf.io.RdfMediaTypeYamlLd;
import dev.enola.rdf.io.RdfMediaTypes;
import dev.enola.thing.gen.gexf.GexfMediaType;
import dev.enola.thing.gen.graphcommons.GraphCommonsMediaType;
import dev.enola.thing.gen.graphviz.GraphvizMediaType;
import dev.enola.thing.io.ThingMediaTypes;

Expand All @@ -46,6 +47,7 @@ class Configuration {
new MarkdownMediaTypes(),
new GraphvizMediaType(),
new GexfMediaType(),
new GraphCommonsMediaType(),
new DatalogMediaTypes(),
new StandardMediaTypes(),
new YamlMediaType(),
Expand Down
4 changes: 4 additions & 0 deletions java/dev/enola/cli/Format.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import dev.enola.common.protobuf.ProtobufMediaTypes;
import dev.enola.rdf.io.RdfMediaTypes;
import dev.enola.thing.gen.graphcommons.GraphCommonsMediaType;
import dev.enola.thing.gen.graphviz.GraphvizMediaType;

public enum Format {
Expand All @@ -30,6 +31,8 @@ public enum Format {

Graphviz,

GraphCommons,

TextProto,

ProtoYAML,
Expand All @@ -43,6 +46,7 @@ MediaType toMediaType() {
case Turtle -> RdfMediaTypes.TURTLE;
case JSONLD -> RdfMediaTypes.JSON_LD;
case Graphviz -> GraphvizMediaType.GV;
case GraphCommons -> GraphCommonsMediaType.GCJSON;

case TextProto -> ProtobufMediaTypes.PROTOBUF_TEXTPROTO_UTF_8;
case ProtoYAML -> ProtobufMediaTypes.PROTOBUF_YAML_UTF_8;
Expand Down
4 changes: 3 additions & 1 deletion java/dev/enola/common/context/Singleton.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ public Singleton<T> set(T value) {

@Override
public T get() {
if (value == null) throw new IllegalStateException();
if (value == null)
throw new IllegalStateException(
getClass() + " was never set(); use SingletonRule in tests");
else return value;
}

Expand Down
3 changes: 1 addition & 2 deletions java/dev/enola/common/io/mediatype/MediaTypeProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ default MediaType detect(String uri, ByteSource byteSource, MediaType original)
// TODO It's kinda wrong that this uses MediaTypeProviders.SINGLETON; it would be clearer if
// it only ever used itself. But that requires moving normalize() from MediaTypeProviders
// to... where? Another ABC?! Urgh.
var normalized = MediaTypeProviders.SINGLETON.get().normalize(original);
if (!normalized.equals(original)) return normalized;
original = MediaTypeProviders.SINGLETON.get().normalize(original);

// NB: This looks inefficient, and you could be tempted to do this "the other way around"
// (instead of checking EACH map entry with uri.endsWith(), the URI extension should be
Expand Down
10 changes: 7 additions & 3 deletions java/dev/enola/common/io/resource/MediaTypeDetector.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,12 @@ class MediaTypeDetector {
private static final Set<MediaType> TRY_FIXING =
ImmutableSet.of(
// raw.githubusercontent.com returns "text/plain" e.g. for *.yaml
MediaType.parse("text/plain"));
MediaType.parse("text/plain"),
// URLConnection assumes JSON for all *.json but we want "longest
// match" e.g. for ".graphcommons.json"
MediaType.parse("application/json"),
// URLConnection assumes XML for .gexf instead of GexfMediaType (with +xml)
MediaType.parse("application/xml"));

private static boolean isSpecial(MediaType mediaType) {
var mediaTypeWithoutParameters = mediaType.withoutParameters();
Expand Down Expand Up @@ -167,8 +172,7 @@ private MediaType detect(@Nullable String contentType, @Nullable String contentE
MediaType mediaType = null;
if (contentType != null) {
mediaType = MediaTypes.parse(contentType);
if (TRY_FIXING.contains(mediaType.withoutParameters())
|| IGNORE.contains(mediaType.withoutParameters())) {
if (isSpecial(mediaType)) {
mediaType = null;
}
}
Expand Down
21 changes: 15 additions & 6 deletions java/dev/enola/common/io/resource/MemoryResourceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

import dev.enola.common.context.testlib.SingletonRule;
import dev.enola.common.io.mediatype.MediaTypeProviders;
import dev.enola.common.io.mediatype.YamlMediaType;

import org.junit.Rule;
import org.junit.Test;
Expand All @@ -35,14 +36,15 @@

public class MemoryResourceTest {

public @Rule SingletonRule r = $(MediaTypeProviders.set());
// The new YamlMediaType() is required for non-regression of the funkyYamlURL below
public @Rule SingletonRule r = $(MediaTypeProviders.set(new YamlMediaType()));

private static final byte[] BYTES = new byte[] {1, 2, 3};
private static final String TEXT = "hello, world";

@Test
public void testBinaryMemoryResource() throws IOException {
MemoryResource resource = new MemoryResource(OCTET_STREAM);
var resource = new MemoryResource(OCTET_STREAM);
resource.byteSink().write(BYTES);
assertThat(resource.byteSource().read()).isEqualTo(BYTES);

Expand All @@ -52,16 +54,23 @@ public void testBinaryMemoryResource() throws IOException {

@Test
public void testTextMemoryResource() throws IOException {
MemoryResource resource = new MemoryResource(PLAIN_TEXT_UTF_8);
var resource = new MemoryResource(PLAIN_TEXT_UTF_8);
resource.charSink().write(TEXT);
assertThat(resource.charSource().read()).isEqualTo(TEXT);
}

@Test
public void testMediaTypePrecedence() throws IOException {
// This does not work for PLAIN_TEXT_UTF_8, because that's "special"
public void testMediaTypePrecedenceHTML_GZIP() {
// TODO Fix to also make this work for PLAIN_TEXT_UTF_8, which is "special"
// (It's one of a few MediaTypes which MediaTypeDetector always overrides)
MemoryResource resource = new MemoryResource(URI.create("test.html"), GZIP);
var resource = new MemoryResource(URI.create("test.html"), GZIP);
assertThat(resource.mediaType()).isEqualTo(GZIP);
}

@Test
public void testMediaTypePrecedenceYAML_JSON() {
var funkyYamlURL = "classpath:/picasso.yaml?context=classpath:/picasso-context.jsonld";
var resource = new MemoryResource(URI.create(funkyYamlURL), JSON_UTF_8);
assertThat(resource.mediaType()).isEqualTo(JSON_UTF_8);
}
}
2 changes: 2 additions & 0 deletions java/dev/enola/common/io/resource/ResourceProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
*/
public interface ResourceProvider extends ProviderFromIRI<Resource> {

// TODO Rename all parameters from iri or uri to url - because that's what these are!

// TODO Change all @Nullable Resource to Optional<Resource>... or, better, throw exception for
// unknown schema

Expand Down
2 changes: 2 additions & 0 deletions java/dev/enola/core/rosetta/Rosetta.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import dev.enola.rdf.io.RdfResourceConverter;
import dev.enola.thing.gen.gexf.GexfGenerator;
import dev.enola.thing.gen.gexf.GexfResourceConverter;
import dev.enola.thing.gen.graphcommons.GraphCommonsResourceConverter;
import dev.enola.thing.gen.graphviz.GraphvizGenerator;
import dev.enola.thing.gen.graphviz.GraphvizResourceConverter;
import dev.enola.thing.io.Loader;
Expand Down Expand Up @@ -103,6 +104,7 @@ public Rosetta(ResourceProvider rp, Loader loader) {
new YamlJsonResourceConverter(),
new GraphvizResourceConverter(loader, new GraphvizGenerator(tmp)),
new GexfResourceConverter(loader, new GexfGenerator(tmp)),
new GraphCommonsResourceConverter(loader),
new XmlResourceConverter(rp),
new CharResourceConverter()));
// NOT new IdempotentCopyingResourceNonConverter()
Expand Down
24 changes: 19 additions & 5 deletions java/dev/enola/core/rosetta/RosettaTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,15 @@
import dev.enola.common.io.iri.namespace.NamespaceRepositoryEnolaDefaults;
import dev.enola.common.io.mediatype.MediaTypeProviders;
import dev.enola.common.io.mediatype.YamlMediaType;
import dev.enola.common.io.resource.ClasspathResource;
import dev.enola.common.io.resource.MemoryResource;
import dev.enola.common.io.resource.ResourceProvider;
import dev.enola.common.io.resource.StringResource;
import dev.enola.common.io.resource.*;
import dev.enola.common.xml.XmlMediaType;
import dev.enola.common.yamljson.JSON;
import dev.enola.common.yamljson.YAML;
import dev.enola.rdf.io.RdfLoader;
import dev.enola.rdf.io.RdfMediaTypes;
import dev.enola.thing.Thing;
import dev.enola.thing.gen.gexf.GexfMediaType;
import dev.enola.thing.gen.graphcommons.GraphCommonsMediaType;
import dev.enola.thing.gen.graphviz.GraphvizMediaType;
import dev.enola.thing.impl.ImmutableThing;
import dev.enola.thing.io.ThingMediaTypes;
Expand All @@ -57,6 +55,7 @@
import org.junit.Test;
import org.junit.rules.TestRule;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class RosettaTest {
Expand All @@ -72,6 +71,7 @@ public class RosettaTest {
MediaTypeProviders.set(
new RdfMediaTypes(),
new GraphvizMediaType(),
new GraphCommonsMediaType(),
new GexfMediaType(),
new YamlMediaType(),
new XmlMediaType()));
Expand Down Expand Up @@ -186,7 +186,7 @@ public void testXMLToTurtle() throws Exception {
}

@Test
public void testGraphvizAndGexf() throws Exception {
public void testGexfAndGraphvizAndGraphCommons() throws Exception {
var in = rp.get("classpath:/graph.ttl");
try (var ctx = TLC.open()) {
// This tests that StackedThingProvider in GraphvizGenerator works;
Expand All @@ -200,18 +200,32 @@ public void testGraphvizAndGexf() throws Exception {
var namespaceConverter = new NamespaceConverterWithRepository(namespaceRepo);
ctx.push(NamespaceConverter.class, namespaceConverter);

// TODO GexfMediaTypeTest: checkRosettaConvert(in, "classpath:/graph.expected.gexf");
var gexf = new MemoryResource(GexfMediaType.GEXF);
rosetta.convertInto(in, gexf);
assertThat(gexf)
.hasCharsEqualTo(rp.get("classpath:/graph.expected.gexf?charset=UTF-8"));

// TODO checkRosettaConvert(in, "classpath:/graph.expected-full.gv?" +
// OUT_URI_QUERY_PARAMETER_FULL + "=true");
var gv = new MemoryResource(GV, OUT_URI_QUERY_PARAMETER_FULL + "=true");
rosetta.convertInto(in, gv);
assertThat(gv).hasCharsEqualTo(rp.get("classpath:/graph.expected-full.gv"));

// TODO checkRosettaConvert(in, "classpath:/graph.expected-full.gv?"
// + OUT_URI_QUERY_PARAMETER_FULL + "=false");
gv = new MemoryResource(GV, OUT_URI_QUERY_PARAMETER_FULL + "=false");
rosetta.convertInto(in, gv);
assertThat(gv).hasCharsEqualTo(rp.get("classpath:/graph.expected-short.gv"));

checkRosettaConvert(in, "classpath:/graph.expected.graphcommons.json");
}
}

void checkRosettaConvert(ReadableResource in, String expectedURL) throws IOException {
var expected = rp.get(expectedURL);
var actual = new MemoryResource(expected.mediaType());
rosetta.convertInto(in, actual);
assertThat(actual).hasCharsEqualTo(expected);
}
}
2 changes: 2 additions & 0 deletions java/dev/enola/thing/gen/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ java_library(
"@maven//:com_google_auto_service_auto_service_annotations",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_errorprone_error_prone_type_annotations",
"@maven//:com_google_code_gson_gson",
"@maven//:com_google_guava_guava",
"@maven//:org_jspecify_jspecify",
"@maven//:org_slf4j_slf4j_api",
Expand All @@ -70,6 +71,7 @@ junit_tests(
"//java/dev/enola/common/context/testlib",
"//java/dev/enola/common/io",
"//java/dev/enola/common/io/testlib",
"//java/dev/enola/common/xml",
"//java/dev/enola/datatype",
"//java/dev/enola/model",
"//java/dev/enola/rdf/io",
Expand Down
44 changes: 44 additions & 0 deletions java/dev/enola/thing/gen/gexf/GexfMediaTypeTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Copyright 2024 The Enola <https://enola.dev> Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.enola.thing.gen.gexf;

import static com.google.common.truth.Truth.assertThat;

import static dev.enola.common.context.testlib.SingletonRule.$;

import dev.enola.common.context.testlib.SingletonRule;
import dev.enola.common.io.mediatype.MediaTypeProviders;
import dev.enola.common.io.resource.ClasspathResource;
import dev.enola.common.xml.XmlMediaType;

import org.junit.Rule;
import org.junit.Test;

public class GexfMediaTypeTest {

// XmlMediaType is required because this tests non-regression for a past bug

@Rule
public SingletonRule r = $(MediaTypeProviders.set(new GexfMediaType(), new XmlMediaType()));

@Test
public void gexfMediaType() {
var r = new ClasspathResource.Provider().get("classpath:/graph.expected.gexf");
assertThat(r.mediaType()).isEqualTo(GexfMediaType.GEXF);
}
}
Loading
Loading