Skip to content

Commit

Permalink
fix (core): Merge lists in ThingMerger
Browse files Browse the repository at this point in the history
  • Loading branch information
vorburger committed Jun 25, 2024
1 parent c01a4b2 commit 4ac7769
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 33 deletions.
31 changes: 31 additions & 0 deletions java/dev/enola/common/collect/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# SPDX-License-Identifier: Apache-2.0
#
# Copyright 2023 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.

load("@rules_java//java:defs.bzl", "java_library")

java_library(
name = "collect",
srcs = glob(
["*.java"],
exclude = ["*Test.java"],
),
visibility = ["//:__subpackages__"],
deps = [
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
"@maven//:org_jspecify_jspecify",
],
)
32 changes: 32 additions & 0 deletions java/dev/enola/common/collect/Immutables.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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.common.collect;

import com.google.common.collect.ImmutableList;

import java.util.List;

public final class Immutables {

public static <T> ImmutableList<T> join(List<T> a, List<T> b) {
var builder = ImmutableList.<T>builderWithExpectedSize(a.size() + b.size());
return builder.addAll(a).addAll(b).build();
}

private Immutables() {}
}
1 change: 1 addition & 0 deletions java/dev/enola/thing/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ java_library(
deps = [
":thing_java_proto",
"//java/dev/enola/common",
"//java/dev/enola/common/collect",
"//java/dev/enola/common/convert",
"//java/dev/enola/common/function",
"//java/dev/enola/common/io",
Expand Down
5 changes: 4 additions & 1 deletion java/dev/enola/thing/PredicatesObjects.java
Original file line number Diff line number Diff line change
Expand Up @@ -120,16 +120,19 @@ default <T> Optional<T> getOptional(String predicateIRI, Class<T> klass) {

// TODO get... other types.

// TODO Remove un-used copy()
Builder<? extends PredicatesObjects> copy();

Builder<? extends PredicatesObjects> newBuilder();

@SuppressFBWarnings("NM_SAME_SIMPLE_NAME_AS_INTERFACE")
interface Builder<B extends PredicatesObjects> // skipcq: JAVA-E0169
extends dev.enola.common.Builder<PredicatesObjects> {

<@ImmutableTypeParameter T> PredicatesObjects.Builder<B> set(String predicateIRI, T value);

<@ImmutableTypeParameter T> PredicatesObjects.Builder<B> set(
String predicateIRI, T value, String datatypeIRI);
String predicateIRI, T value, @Nullable String datatypeIRI);

@Override
B build();
Expand Down
14 changes: 12 additions & 2 deletions java/dev/enola/thing/Thing.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

import org.jspecify.annotations.Nullable;

/**
* Thing is the central data structure of Enola.
*
Expand All @@ -34,17 +36,25 @@ public interface Thing extends PredicatesObjects {

String iri();

// TODO @Override Builder<? extends Thing> newBuilder();

@Override
Builder<? extends Thing> copy();
Builder<? extends Thing> copy(); // TODO Remove un-used copy()

@SuppressFBWarnings("NM_SAME_SIMPLE_NAME_AS_INTERFACE")
interface Builder<B extends Thing> extends PredicatesObjects.Builder<B> { // skipcq: JAVA-E0169

default Builder<B> copy(Thing thing) {
iri(thing.iri());
thing.properties().forEach((p, v) -> set(p, v, thing.datatype(p)));
return this;
}

Builder<B> iri(String iri);

Builder<B> set(String predicateIRI, Object value);

Builder<B> set(String predicateIRI, Object value, String datatypeIRI);
Builder<B> set(String predicateIRI, Object value, @Nullable String datatypeIRI);

@Override
B build();
Expand Down
12 changes: 11 additions & 1 deletion java/dev/enola/thing/impl/ImmutableThing.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ protected ImmutableThing(
this.iri = Objects.requireNonNull(iri, "iri");
}

public static ImmutableThing copyOf(Thing thing) {
if (thing instanceof ImmutableThing immutableThing) return immutableThing;

return new ImmutableThing(
thing.iri(),
ImmutableMap.copyOf(thing.properties()),
ImmutableMap.copyOf(thing.datatypes()));
}

public static Thing.Builder<? extends ImmutableThing> builder() {
return new Builder<>();
}
Expand Down Expand Up @@ -124,7 +133,8 @@ public Thing.Builder<B> set(String predicateIRI, Object value) {
}

@Override
public Thing.Builder<B> set(String predicateIRI, Object value, String datatypeIRI) {
public Thing.Builder<B> set(
String predicateIRI, Object value, @Nullable String datatypeIRI) {
properties.put(predicateIRI, value);
if (datatypeIRI != null) datatypes.put(predicateIRI, datatypeIRI);
return this;
Expand Down
3 changes: 2 additions & 1 deletion java/dev/enola/thing/io/Loader.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.List;
import java.util.stream.Stream;

public class Loader implements ConverterInto<Stream<ReadableResource>, Store<?, Thing>> {
Expand Down Expand Up @@ -58,7 +59,7 @@ private void load(ReadableResource resource, Store<?, Thing> store) {
things.get()
.forEach(
thingBuilder -> {
thingBuilder.set(KIRI.E.ORIGIN, resource.uri());
thingBuilder.set(KIRI.E.ORIGIN, List.of(resource.uri()));
var thing = thingBuilder.build();
store.merge(thing);
});
Expand Down
76 changes: 48 additions & 28 deletions java/dev/enola/thing/repo/ThingMerger.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,39 +17,59 @@
*/
package dev.enola.thing.repo;

import static dev.enola.common.collect.Immutables.join;

import dev.enola.thing.KIRI;
import dev.enola.thing.Thing;
import dev.enola.thing.impl.ImmutableThing;
import dev.enola.thing.impl.MutableThing;

import java.util.List;

class ThingMerger {
// TODO Implement missing ThingMergerTest coverage!

public static Thing merge(Thing existing, Thing update) {
if (!existing.iri().equals(update.iri())) throw new IllegalArgumentException();

if (existing.predicateIRIs().isEmpty()) return update;
if (update.predicateIRIs().isEmpty()) return existing;

var merged = existing.copy();
var properties = update.properties();
properties.forEach(
(predicate, value) -> {
var old = existing.get(predicate);
if (old == null) merged.set(predicate, value, update.datatype(predicate));
else if (old.equals(value)) {
// That's fine!
} else if (predicate.equals(KIRI.E.ORIGIN)) {
// TODO Implement merging both into a List, with test coverage!
} else
throw new IllegalStateException(
"Cannot merge "
+ predicate
+ " of an "
+ existing.iri()
+ " from "
+ existing.getString(KIRI.E.ORIGIN)
+ " and "
+ update.getString(KIRI.E.ORIGIN));
});
return merged.build();
public static Thing merge(Thing t1, Thing t2) {
if (!t1.iri().equals(t2.iri())) throw new IllegalArgumentException();

var t1predis = t1.predicateIRIs();
var t2predis = t2.predicateIRIs();
if (t1predis.isEmpty()) return t2;
if (t2predis.isEmpty()) return t1;

// TODO Use copy() instead of new MutableThing + ImmutableThing.copyOf()

var merged = new MutableThing<>(t2predis.size());
t2.properties()
.forEach(
(predicate, t2obj) -> {
var t1obj = t1.get(predicate);
var t1dt = t1.datatype(predicate);
var t2dt = t2.datatype(predicate);
var sameDt = t1dt != null && t1dt.equals(t2dt);

if (t1obj == null) merged.set(predicate, t2obj, t2dt);
else if (t1obj.equals(t2obj) && sameDt)
merged.set(predicate, t2obj, t2dt);
// skipcq: JAVA-C1003
else if (t1obj instanceof List t1list && t2obj instanceof List t2list) {
merged.set(predicate, join(t1list, t2list));
} else
throw new IllegalStateException(
"Cannot merge "
+ predicate
+ " of an "
+ t1.iri()
+ " from "
+ t1.getString(KIRI.E.ORIGIN)
+ " and "
+ t2.getString(KIRI.E.ORIGIN));
});

try {
return ImmutableThing.copyOf(merged.build());
} catch (IllegalArgumentException | NullPointerException e) {
throw new IllegalArgumentException("Cannot merge " + t1.iri(), e);
}
}
}

0 comments on commit 4ac7769

Please sign in to comment.