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

fix (core): Introduce List of Value in Thing #551

Merged
merged 2 commits into from
Feb 25, 2024
Merged
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
43 changes: 36 additions & 7 deletions common/rdf/src/main/java/dev/enola/rdf/RdfThingConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import org.eclipse.rdf4j.model.BNode;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Model;
import org.eclipse.rdf4j.model.Statement;
import org.eclipse.rdf4j.model.base.CoreDatatype;

import java.util.ArrayList;
Expand Down Expand Up @@ -81,7 +80,25 @@ public Stream<Thing.Builder> convert(Model input) {

} else throw new UnsupportedOperationException("TODO: " + subject);

convert(thing, statement, deferred);
// The goal of this is to turn an RDF Object List into a Thing List Value
var predicate = statement.getPredicate();
var statements = input.filter(subject, predicate, null);
if (statements.size() == 1) {
convertAndPut(thing, predicate, statement.getObject(), deferred);
var value = convert(thing, predicate, statement.getObject(), deferred);
thing.putFields(predicate.stringValue(), value.build());
} else {
var valueList = dev.enola.thing.Value.List.newBuilder();
for (var subStatement : statements) {
var object = subStatement.getObject();
var value = convert(thing, predicate, object, deferred);
valueList.addValues(value);
}
var value = Value.newBuilder().setList(valueList);
thing.putFields(statement.getPredicate().stringValue(), value.build());
}
// TODO It's surprising that this this really works (test pass) as-is?
// What causes it not to repeat (duplicate) values?
}

for (var action : deferred) {
Expand All @@ -91,9 +108,21 @@ public Stream<Thing.Builder> convert(Model input) {
return roots.values().stream();
}

private static void convert(
Builder thing, Statement statement, List<Consumer<Map<String, Builder>>> deferred) {
org.eclipse.rdf4j.model.Value rdfValue = statement.getObject();
// TODO Remove this again?
private static void convertAndPut(
Builder thing,
IRI predicate,
org.eclipse.rdf4j.model.Value object,
List<Consumer<Map<String, Builder>>> deferred) {
var value = convert(thing, predicate, object, deferred);
thing.putFields(predicate.stringValue(), value.build());
}

private static dev.enola.thing.Value.Builder convert(
Builder thing,
IRI predicate,
org.eclipse.rdf4j.model.Value rdfValue,
List<Consumer<Map<String, Builder>>> deferred) {
var value = Value.newBuilder();
if (rdfValue.isIRI()) {
value.setLink(Link.newBuilder().setIri(rdfValue.stringValue()));
Expand Down Expand Up @@ -127,7 +156,7 @@ private static void convert(
throw new IllegalStateException(
bNodeID + " not found: " + map.keySet());
value.setStruct(containedThing);
thing.putFields(statement.getPredicate().stringValue(), value.build());
thing.putFields(predicate.stringValue(), value.build());
});

} else if (rdfValue.isTriple()) {
Expand All @@ -137,6 +166,6 @@ private static void convert(
throw new UnsupportedOperationException("TODO: Resource");
}

thing.putFields(statement.getPredicate().stringValue(), value.build());
return value;
}
}
37 changes: 27 additions & 10 deletions common/rdf/src/main/java/dev/enola/rdf/ThingRdfConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
*/
package dev.enola.rdf;

import static java.util.Collections.singleton;

import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;

import dev.enola.common.convert.ConversionException;
import dev.enola.common.convert.Converter;
Expand Down Expand Up @@ -91,9 +94,10 @@ private void convertInto(
}
for (var field : from.getFieldsMap().entrySet()) {
IRI predicate = vf.createIRI(field.getKey());
var object = convert(field.getValue(), containedThings);
Statement statement = vf.createStatement(subject, predicate, object);
into.handleStatement(statement);
for (var object : convert(field.getValue(), containedThings)) {
Statement statement = vf.createStatement(subject, predicate, object);
into.handleStatement(statement);
}
}

for (var containedThing : containedThings.entrySet()) {
Expand All @@ -106,21 +110,22 @@ private void convertInto(
}
}

private org.eclipse.rdf4j.model.Value convert(
private Iterable<org.eclipse.rdf4j.model.Value> convert(
dev.enola.thing.Value value, Map<BNode, Thing> containedThings) {
return switch (value.getKindCase()) {
case LINK -> vf.createIRI(value.getLink().getIri());
case LINK -> singleton(vf.createIRI(value.getLink().getIri()));

case STRING -> vf.createLiteral(value.getString());
case STRING -> singleton(vf.createLiteral(value.getString()));

case LITERAL -> {
var literal = value.getLiteral();
yield vf.createLiteral(literal.getValue(), vf.createIRI(literal.getDatatype()));
yield singleton(
vf.createLiteral(literal.getValue(), vf.createIRI(literal.getDatatype())));
}

case LANG_STRING -> {
LangString langString = value.getLangString();
yield vf.createLiteral(langString.getText(), langString.getLang());
yield singleton(vf.createLiteral(langString.getText(), langString.getLang()));
}

case STRUCT -> {
Expand All @@ -133,10 +138,22 @@ private org.eclipse.rdf4j.model.Value convert(
bNode = vf.createBNode();
}
containedThings.put(bNode, containedThing);
yield bNode;
yield singleton(bNode);
}

// case LIST -> throw new UnsupportedOperationException("TODO");
case LIST -> {
// TODO value.getList().getValuesList().stream().map(eValue -> convert(eValue,?
var enolaValues = value.getList().getValuesList();
var rdfValues =
ImmutableList.<org.eclipse.rdf4j.model.Value>builderWithExpectedSize(
enolaValues.size());
for (var enolaValue : enolaValues) {
var rdfValue = convert(enolaValue, containedThings);
// Not 100% sure if addAll() is "correct" here...
rdfValues.addAll(rdfValue);
}
yield rdfValues.build();
}

case KIND_NOT_SET -> throw new IllegalArgumentException(value.toString());
};
Expand Down
8 changes: 8 additions & 0 deletions common/rdf/src/test/java/dev/enola/rdf/LearnRdf4jTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,11 @@ Model picasso1() {

IRI dali = Values.iri(ex, "Dalí");
model.add(dali, RDF.TYPE, artist);
// The spanish really do make sure that their names are UUIDs... ;-)
model.add(dali, FOAF.FIRST_NAME, Values.literal("Salvador"));
model.add(dali, FOAF.FIRST_NAME, Values.literal("Domingo"));
model.add(dali, FOAF.FIRST_NAME, Values.literal("Felipe"));
model.add(dali, FOAF.FIRST_NAME, Values.literal("Jacinto"));

return model;
}
Expand All @@ -86,7 +90,11 @@ Model picasso2() {
.add("ex:city", "Barcelona")
.subject("ex:Dalí")
.add(RDF.TYPE, "ex:Artist")
// The spanish really do make sure that their names are UUIDs... ;-)
.add(FOAF.FIRST_NAME, "Salvador")
.add(FOAF.FIRST_NAME, "Domingo")
.add(FOAF.FIRST_NAME, "Felipe")
.add(FOAF.FIRST_NAME, "Jacinto")
.build();
}

Expand Down
39 changes: 31 additions & 8 deletions common/rdf/src/test/java/dev/enola/rdf/RdfThingConverterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import static dev.enola.common.io.mediatype.YamlMediaType.YAML_UTF_8;

import com.google.common.truth.Truth;
import com.google.common.truth.extensions.proto.ProtoTruth;

import dev.enola.common.convert.ConversionException;
Expand All @@ -38,37 +39,59 @@ public class RdfThingConverterTest {

private final ReadableResource turtle =
new ClasspathResource("picasso.turtle", RdfMediaType.TURTLE);
private final ReadableResource yaml = new ClasspathResource("picasso.thing.yaml", YAML_UTF_8);

private final ReadableResource picassoYaml =
new ClasspathResource("picasso.thing.yaml", YAML_UTF_8);

private final ReadableResource daliYaml = //
new ClasspathResource("dali.thing.yaml", YAML_UTF_8);

private final ProtoIO protoReader = new ProtoIO();
private final RdfReaderConverter rdfReader = new RdfReaderConverter();
private final RdfThingConverter rdfToThingConverter = new RdfThingConverter();
private final ThingRdfConverter thingToRdfConverter = new ThingRdfConverter();

private Model rdf;
private Thing thing;
private Thing picassoThing;
private Thing daliThing;

@Before
public void before() throws ConversionException, IOException {
rdf = rdfReader.convert(turtle);
thing = protoReader.read(yaml, Thing.newBuilder(), Thing.class);
picassoThing = protoReader.read(picassoYaml, Thing.newBuilder(), Thing.class);
daliThing = protoReader.read(daliYaml, Thing.newBuilder(), Thing.class);
}

@Test
public void rdfToThing() throws ConversionException, IOException {
public void rdfToPicassoThing() throws ConversionException, IOException {
var actualThings = rdfToThingConverter.convertToList(rdf);
var expectedThing = thing;
var expectedThing = picassoThing;
ProtoTruth.assertThat(actualThings.get(1).build()).isEqualTo(expectedThing);
}

@Test
public void thingToRDF() throws ConversionException {
var actualRDF = thingToRdfConverter.convert(thing);
rdf.remove(Values.iri("http://example.enola.dev/Dalí"), null, null);
public void rdfToDaliThing() throws ConversionException, IOException {
var actualThings = rdfToThingConverter.convertToList(rdf);
var expectedThing = daliThing;
ProtoTruth.assertThat(actualThings.get(0).build()).isEqualTo(expectedThing);
}

@Test
public void picassoThingToRDF() throws ConversionException {
var actualRDF = thingToRdfConverter.convert(picassoThing);
Truth.assertThat(rdf.remove(Values.iri("http://example.enola.dev/Dalí"), null, null))
.isTrue();
var expectedRDF = rdf;
ModelSubject.assertThat(actualRDF).isEqualTo(expectedRDF);
}

@Test
public void daliThingToRDF() throws ConversionException {
var actualRDF = thingToRdfConverter.convert(daliThing);
var expectedRDF = rdf.filter(Values.iri("http://example.enola.dev/Dalí"), null, null);
ModelSubject.assertThat(actualRDF).isEqualTo(expectedRDF);
}

@Test
public void messageToRDF() {
// TODO Implement messageToRDF(), via MessageToThingConverter
Expand Down
21 changes: 21 additions & 0 deletions common/rdf/src/test/resources/dali.thing.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# 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.

iri: http://example.enola.dev/Dalí
fields:
http://xmlns.com/foaf/0.1/firstName:
{ list: { values: [{ string: "Salvador" }, { string: "Domingo" }, { string: "Felipe" }, { string: "Jacinto" }] } }
http://www.w3.org/1999/02/22-rdf-syntax-ns#type: { link: { iri: "http://example.enola.dev/Artist" } }
9 changes: 9 additions & 0 deletions common/rdf/src/test/resources/picasso.jsonld
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@
"http://xmlns.com/foaf/0.1/firstName": [
{
"@value": "Salvador"
},
{
"@value": "Domingo"
},
{
"@value": "Felipe"
},
{
"@value": "Jacinto"
}
]
},
Expand Down
2 changes: 1 addition & 1 deletion common/rdf/src/test/resources/picasso.turtle
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ _:f034741e5da3451ead5b5972d6cf75311 ex:street "31 Art Gallery";
ex:city "Barcelona" .

ex:Dalí a ex:Artist;
foaf:firstName "Salvador" .
foaf:firstName "Salvador", "Domingo", "Felipe", "Jacinto" .
23 changes: 13 additions & 10 deletions common/thing/thing.proto
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,16 @@ message Value {

Literal literal = 3;

// TODO Keep or remove this? See
// https://github.com/enola-dev/enola/pull/540...
LangString lang_string = 4;

// Sub-structure (contained) Thing.
Thing struct = 18;

// List of Values.
List list = 19;

// https://protobuf.dev/programming-guides/proto3/#scalar
// TODO Reconsider if this is really needed?! By who, for what?
// bytes bytes = 4;
Expand All @@ -70,13 +78,6 @@ message Value {
// double double = 15;
// float float = 16;
// bool bool = 17;

Thing struct = 18;

// TODO Do we actually need List? That's just a stream of Thing, no?
// TODO Inline? repeated Thing things = 18;
// TODO Never has an IRI? Maybe just repeated Value values = 18;
// List list = 19;
}

message Link {
Expand All @@ -102,6 +103,8 @@ message Value {
string datatype = 2;
}

// TODO Keep or remove this? See
// https://github.com/enola-dev/enola/pull/540...
message LangString {
// Text, for humans.
string text = 1;
Expand All @@ -110,9 +113,9 @@ message Value {
string lang = 2;
}

// message List {
// repeated Thing entries = 1;
// }
message List {
repeated Value values = 1;
}
}

// TODO Allow "uint64 id" instatead string IRIs (or all Value?), similar to
Expand Down