Skip to content
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
4 changes: 3 additions & 1 deletion core/src/main/java/org/incenp/linkml/core/ClassInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,9 @@ private ClassInfo(Class<?> klass) {
identifierIsLocal = identifierSlot.isLocalIdentifier();
} else if ( slot.isExtensionStore() ) {
extensionSlot = slot;
} else if ( slot.isTypeDesignator() ) {
}

if ( slot.isTypeDesignator() ) {
designatorSlot = slot;
}

Expand Down
26 changes: 16 additions & 10 deletions core/src/main/java/org/incenp/linkml/core/ObjectConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -309,14 +309,6 @@ protected List<String> getGlobalIdentifierList(Object raw, ConverterContext ctx)

@Override
public Object serialise(Object object, ConverterContext ctx) throws LinkMLRuntimeException {
// Search for a more specialised converter.
// FIXME: Looks a bit too much like a hack...
IConverter conv = ctx.getConverter(object.getClass());
if ( conv instanceof ObjectConverter ) {
if ( ((ObjectConverter) conv).klass.getParents().contains(klass) ) {
return ((ObjectConverter) conv).serialise(object, true, ctx);
}
}
return serialise(object, true, ctx);
}

Expand Down Expand Up @@ -346,6 +338,15 @@ public Map<String, Object> serialise(Object object, boolean withIdentifier, Conv
throw new LinkMLValueError(String.format(OBJECT_EXPECTED, getType().getName()));
}

// Search for a more specialised converter.
// FIXME: Looks a bit too much like a hack...
IConverter conv = ctx.getConverter(object.getClass());
if ( conv instanceof ObjectConverter && conv != this ) {
if ( ((ObjectConverter) conv).klass.getParents().contains(klass) ) {
return ((ObjectConverter) conv).serialise(object, withIdentifier, ctx);
}
}

Map<String, Object> raw = new HashMap<>();
for ( Slot slot : klass.getSlots() ) {
if ( (slot.isIdentifier()) && !withIdentifier ) {
Expand Down Expand Up @@ -390,9 +391,10 @@ public Object serialiseForSlot(Object object, Slot slot, ConverterContext ctx) t
}
return list;
} else if ( inlining == InliningMode.DICT ) {
boolean simpleDict = klass.isEligibleForSimpleDict(true);
// boolean simpleDict = klass.isEligibleForSimpleDict(true);
Map<Object, Object> map = new HashMap<>();
for ( Object item : items ) {
boolean simpleDict = ClassInfo.get(item.getClass()).isEligibleForSimpleDict(true);
Object rawItem = simpleDict ? klass.getPrimarySlot().getValue(item) : serialise(item, false, ctx);
map.put(toIdentifier(item, ctx), rawItem);
}
Expand Down Expand Up @@ -434,7 +436,11 @@ protected Object toIdentifier(Object object, ConverterContext ctx) throws LinkML
Slot identifierSlot = klass.getIdentifierSlot();
Object identifier = identifierSlot.getValue(object);
if ( identifier == null ) {
throw new LinkMLValueError(String.format(NO_IDENTIFIER, getType().getName()));
if ( identifierSlot.isTypeDesignator() ) {
identifier = new TypeDesignatorResolver().getDesignator(ClassInfo.get(object.getClass()));
} else {
throw new LinkMLValueError(String.format(NO_IDENTIFIER, getType().getName()));
}
}
return ctx.getConverter(identifierSlot).serialise(identifier, ctx);
}
Expand Down
129 changes: 129 additions & 0 deletions core/src/test/java/org/incenp/linkml/core/ObjectConverterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

Expand All @@ -54,19 +55,25 @@
import org.incenp.linkml.core.samples.base.ContainerOfAny;
import org.incenp.linkml.core.samples.base.ContainerOfBooleanValues;
import org.incenp.linkml.core.samples.base.ContainerOfIRIIdentifiableObjects;
import org.incenp.linkml.core.samples.base.ContainerOfIdentifiedSelfDesignatedObjects;
import org.incenp.linkml.core.samples.base.ContainerOfInlinedObjects;
import org.incenp.linkml.core.samples.base.ContainerOfIntegerValues;
import org.incenp.linkml.core.samples.base.ContainerOfKeyedSelfDesignatedObjects;
import org.incenp.linkml.core.samples.base.ContainerOfReferences;
import org.incenp.linkml.core.samples.base.ContainerOfSelfDesignatedObjects;
import org.incenp.linkml.core.samples.base.ContainerOfSimpleDicts;
import org.incenp.linkml.core.samples.base.ContainerOfSimpleObjects;
import org.incenp.linkml.core.samples.base.DerivedCurieSelfDesignatedClass;
import org.incenp.linkml.core.samples.base.DerivedIdentifiedSelfDesignatedClass;
import org.incenp.linkml.core.samples.base.DerivedKeyedSelfDesignatedClass;
import org.incenp.linkml.core.samples.base.DerivedMultiSelfDesignatedClass;
import org.incenp.linkml.core.samples.base.DerivedSelfDesignatedClass;
import org.incenp.linkml.core.samples.base.DerivedURISelfDesignatedClass;
import org.incenp.linkml.core.samples.base.ExtensibleSimpleClass;
import org.incenp.linkml.core.samples.base.ExtraSimpleDict;
import org.incenp.linkml.core.samples.base.IRISimpleIdentifiableClass;
import org.incenp.linkml.core.samples.base.IdentifiedSelfDesignatedClass;
import org.incenp.linkml.core.samples.base.KeyedSelfDesignatedClass;
import org.incenp.linkml.core.samples.base.MultivaluedSimpleDict;
import org.incenp.linkml.core.samples.base.SampleEnum;
import org.incenp.linkml.core.samples.base.SecondDerivedSelfDesignatedClass;
Expand Down Expand Up @@ -515,6 +522,128 @@ void testMultiLevelTypeDesignators() throws IOException {
roundtrip(cosdo);
}

@Test
void testKeyTypeDesignator() throws IOException, LinkMLRuntimeException {
ContainerOfKeyedSelfDesignatedObjects cksdo = parse("container-of-keyed-self-designated-objects.yaml",
ContainerOfKeyedSelfDesignatedObjects.class);

HashMap<String, KeyedSelfDesignatedClass> d = new HashMap<>();
for ( KeyedSelfDesignatedClass o : cksdo.getObjects() ) {
d.put(o.getType(), o);
}
KeyedSelfDesignatedClass ksdc = d.get("KeyedSelfDesignatedClass");
Assertions.assertNotNull(ksdc);
Assertions.assertEquals("Alice", ksdc.getFrobnicator());

KeyedSelfDesignatedClass ksdc2 = d.get("DerivedKeyedSelfDesignatedClass");
Assertions.assertNotNull(ksdc2);
Assertions.assertEquals("Bob", ksdc2.getFrobnicator());
Assertions.assertInstanceOf(DerivedKeyedSelfDesignatedClass.class, ksdc2);
Assertions.assertEquals(123, ((DerivedKeyedSelfDesignatedClass) ksdc2).getLength());

// Remove the base object, because the roundtrip test is
// sensible to the order of the objects in the list, which is
// not guaranteed to be preserved since the list is serialised
// as a dictionary. Still, if we can read back the one-item
// list, this is enough to know that serialisation works as
// expected.
cksdo.getObjects().remove(ksdc);
roundtrip(cksdo);

// Try serialising again, but this time with the type designator unset; the
// correct type designator should still appear in the serialised object
ksdc2.setType(null);
ObjectConverter conv = (ObjectConverter) ctx.getConverter(cksdo.getClass());
Map<String, Object> raw = conv.serialise(cksdo, true, ctx);
@SuppressWarnings("unchecked")
Map<String, Object> objects = (Map<String, Object>) raw.get("objects");
Assertions.assertTrue(objects.containsKey("DerivedKeyedSelfDesignatedClass"));
}

@Test
void testKeyTypeDesignatorWithUnknownType() throws IOException, LinkMLRuntimeException {
String test = "objects:\n" +
" UnknownSelfDesignatedClass:\n" +
" frobnicator: \"Charlie\"\n" +
" width: 456\n";
ContainerOfKeyedSelfDesignatedObjects cksdo = parseString(test, ContainerOfKeyedSelfDesignatedObjects.class);
KeyedSelfDesignatedClass ksdc = cksdo.getObjects().get(0);
Assertions.assertNotNull(ksdc);
Assertions.assertEquals("UnknownSelfDesignatedClass", ksdc.getType());
Assertions.assertEquals("Charlie", ksdc.getFrobnicator());
Assertions.assertEquals(456, ksdc.getExtraSlots().get("width"));

roundtrip(cksdo);

// Try serialising again, but this time with the type designator slot unset; the
// base type designator should still appear in the serialised object
ksdc.setType(null);
ObjectConverter conv = (ObjectConverter) ctx.getConverter(cksdo.getClass());
Map<String, Object> raw = conv.serialise(cksdo, true, ctx);
@SuppressWarnings("unchecked")
Map<String, Object> objects = (Map<String, Object>) raw.get("objects");
Assertions.assertTrue(objects.containsKey("KeyedSelfDesignatedClass"));
}

@Test
void testKeyTypeDesignatorWithSimpleDictMix() throws IOException, LinkMLRuntimeException {
// Same test case as "testKeyTypeDesignator" above, except that the base class
// instance is serialised as a "simple dict" entry, while the derived instance
// is serialised as a "compact dict".
String test = "objects:\n" +
" KeyedSelfDesignatedClass: Alice\n" +
" DerivedKeyedSelfDesignatedClass:\n" +
" frobnicator: Bob\n" +
" length: 123\n";
ContainerOfKeyedSelfDesignatedObjects cksdo = parseString(test, ContainerOfKeyedSelfDesignatedObjects.class);

HashMap<String, KeyedSelfDesignatedClass> d = new HashMap<>();
for ( KeyedSelfDesignatedClass o : cksdo.getObjects() ) {
d.put(o.getType(), o);
}
KeyedSelfDesignatedClass ksdc = d.get("KeyedSelfDesignatedClass");
Assertions.assertNotNull(ksdc);
Assertions.assertEquals("Alice", ksdc.getFrobnicator());

KeyedSelfDesignatedClass ksdc2 = d.get("DerivedKeyedSelfDesignatedClass");
Assertions.assertNotNull(ksdc2);
Assertions.assertEquals("Bob", ksdc2.getFrobnicator());
Assertions.assertInstanceOf(DerivedKeyedSelfDesignatedClass.class, ksdc2);
Assertions.assertEquals(123, ((DerivedKeyedSelfDesignatedClass) ksdc2).getLength());
}

@Test
void testIdentifierTypeDesignator() throws IOException {
String test = "objects:\n" +
" 'EX:IdentifiedSelfDesignatedClass': Alice\n" +
" 'EX:DerivedIdentifiedSelfDesignatedClass':\n" +
" frobnicator: Bob\n" +
" length: 123\n";
// Make sure the derived class and its IRI are known
ClassInfo.get(DerivedIdentifiedSelfDesignatedClass.class);
// Make sure the context knows about the EX prefix
ctx.addPrefix("EX", TEST_NS);
ContainerOfIdentifiedSelfDesignatedObjects cisdo = parseString(test,
ContainerOfIdentifiedSelfDesignatedObjects.class);

HashMap<String, IdentifiedSelfDesignatedClass> d = new HashMap<>();
for ( IdentifiedSelfDesignatedClass o : cisdo.getObjects() ) {
d.put(o.getType(), o);
}
IdentifiedSelfDesignatedClass isdc = d.get(TEST_NS + "IdentifiedSelfDesignatedClass");
Assertions.assertNotNull(isdc);
Assertions.assertEquals("Alice", isdc.getFrobnicator());

IdentifiedSelfDesignatedClass isdc2 = d.get(TEST_NS + "DerivedIdentifiedSelfDesignatedClass");
Assertions.assertNotNull(isdc2);
Assertions.assertEquals("Bob", isdc2.getFrobnicator());
Assertions.assertInstanceOf(DerivedIdentifiedSelfDesignatedClass.class, isdc2);
Assertions.assertEquals(123, ((DerivedIdentifiedSelfDesignatedClass) isdc2).getLength());

cisdo.getObjects().remove(isdc);
roundtrip(cisdo);
}

@Test
void testReferenceToIRIIdentifiers() throws IOException, LinkMLRuntimeException {
ctx.addPrefix("PFX", "https://example.org/");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package org.incenp.linkml.core.samples.base;

import java.net.URI;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.incenp.linkml.core.annotations.Converter;
import org.incenp.linkml.core.annotations.ExtensionHolder;
import org.incenp.linkml.core.annotations.Identifier;
import org.incenp.linkml.core.annotations.Inlined;
import org.incenp.linkml.core.annotations.LinkURI;
import org.incenp.linkml.core.annotations.Required;
import org.incenp.linkml.core.annotations.SlotName;
import org.incenp.linkml.core.annotations.TypeDesignator;
import org.incenp.linkml.core.CurieConverter;

@LinkURI("https://incenp.org/dvlpt/linkml-java/tests/samples#ContainerOfIdentifiedSelfDesignatedObjects")
public class ContainerOfIdentifiedSelfDesignatedObjects {

@Inlined
@LinkURI("https://incenp.org/dvlpt/linkml-java/tests/samples#objects")
private List<IdentifiedSelfDesignatedClass> objects;

public void setObjects(List<IdentifiedSelfDesignatedClass> objects) {
this.objects = objects;
}

public List<IdentifiedSelfDesignatedClass> getObjects() {
return this.objects;
}

public List<IdentifiedSelfDesignatedClass> getObjects(boolean set) {
if ( this.objects == null && set ) {
this.objects = new ArrayList<>();
}
return this.objects;
}

@Override
public String toString() {
StringBuilder sb = new StringBuilder();
Object o;
sb.append("ContainerOfIdentifiedSelfDesignatedObjects(");
if ( (o = this.getObjects()) != null ) {
sb.append("objects=");
sb.append(o);
sb.append(",");
}
sb.append(")");
return sb.toString();
}

@Override
public boolean equals(final Object o) {
if ( o == this ) return true;
if ( !(o instanceof ContainerOfIdentifiedSelfDesignatedObjects) ) return false;
final ContainerOfIdentifiedSelfDesignatedObjects other = (ContainerOfIdentifiedSelfDesignatedObjects) o;
if ( !other.canEqual((Object) this)) return false;
final Object this$objects = this.getObjects();
final Object other$objects = other.getObjects();
if ( this$objects == null ? other$objects != null : !this$objects.equals(other$objects)) return false;
return true;
}

protected boolean canEqual(final Object other) {
return other instanceof ContainerOfIdentifiedSelfDesignatedObjects;
}

@Override
public int hashCode() {
final int PRIME = 59;
int result = 1;
final Object $objects = this.getObjects();
result = result * PRIME + ($objects == null ? 43 : $objects.hashCode());
return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package org.incenp.linkml.core.samples.base;

import java.net.URI;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.incenp.linkml.core.annotations.Converter;
import org.incenp.linkml.core.annotations.ExtensionHolder;
import org.incenp.linkml.core.annotations.Identifier;
import org.incenp.linkml.core.annotations.Inlined;
import org.incenp.linkml.core.annotations.LinkURI;
import org.incenp.linkml.core.annotations.Required;
import org.incenp.linkml.core.annotations.SlotName;
import org.incenp.linkml.core.annotations.TypeDesignator;
import org.incenp.linkml.core.CurieConverter;

@LinkURI("https://incenp.org/dvlpt/linkml-java/tests/samples#ContainerOfKeyedSelfDesignatedObjects")
public class ContainerOfKeyedSelfDesignatedObjects {

@Inlined
@LinkURI("https://incenp.org/dvlpt/linkml-java/tests/samples#objects")
private List<KeyedSelfDesignatedClass> objects;

public void setObjects(List<KeyedSelfDesignatedClass> objects) {
this.objects = objects;
}

public List<KeyedSelfDesignatedClass> getObjects() {
return this.objects;
}

public List<KeyedSelfDesignatedClass> getObjects(boolean set) {
if ( this.objects == null && set ) {
this.objects = new ArrayList<>();
}
return this.objects;
}

@Override
public String toString() {
StringBuilder sb = new StringBuilder();
Object o;
sb.append("ContainerOfKeyedSelfDesignatedObjects(");
if ( (o = this.getObjects()) != null ) {
sb.append("objects=");
sb.append(o);
sb.append(",");
}
sb.append(")");
return sb.toString();
}

@Override
public boolean equals(final Object o) {
if ( o == this ) return true;
if ( !(o instanceof ContainerOfKeyedSelfDesignatedObjects) ) return false;
final ContainerOfKeyedSelfDesignatedObjects other = (ContainerOfKeyedSelfDesignatedObjects) o;
if ( !other.canEqual((Object) this)) return false;
final Object this$objects = this.getObjects();
final Object other$objects = other.getObjects();
if ( this$objects == null ? other$objects != null : !this$objects.equals(other$objects)) return false;
return true;
}

protected boolean canEqual(final Object other) {
return other instanceof ContainerOfKeyedSelfDesignatedObjects;
}

@Override
public int hashCode() {
final int PRIME = 59;
int result = 1;
final Object $objects = this.getObjects();
result = result * PRIME + ($objects == null ? 43 : $objects.hashCode());
return result;
}
}
Loading
Loading