From cb225e485856415532ce89942916651fc7746d22 Mon Sep 17 00:00:00 2001 From: Damien Goutte-Gattat Date: Fri, 26 Jun 2026 15:22:39 +0100 Subject: [PATCH 1/7] Add test case for a "key type designator". A type designator slot may also be the key (or even the identifier) slot if its class. Combined with dict inlining, this can be used to do something like this: items: FooItem: # ... FooItem attributes BarItem: # ... BarItem attributes BazItem: # ... BazItem attributes where FooItem, BarItem, and BazItem are all subclasses of Item (which is the declared range of the `items` slot), and the keys of the `items` dictionary act both as identifiers to refer to one particular item, _and_ as type designators to indicate the precise type of each item (as a side-effect, this ensures that `items` can only contain one object of each type). This is a pattern that could be useful for NGMF and/or NGFF extensions, and that we should support. This commit adds an explicit test case for it. The test currently _fails_, because the ObjectConverter does not expect to find the type designator as the key in a "inlined-as-dict" object. --- .../linkml/core/ObjectConverterTest.java | 24 +++++++++++++++ core/src/test/linkml/samples.yaml | 29 +++++++++++++++++++ ...iner-of-keyed-self-designated-objects.yaml | 6 ++++ 3 files changed, 59 insertions(+) create mode 100644 core/src/test/resources/core/samples/container-of-keyed-self-designated-objects.yaml diff --git a/core/src/test/java/org/incenp/linkml/core/ObjectConverterTest.java b/core/src/test/java/org/incenp/linkml/core/ObjectConverterTest.java index 12dc534..67a0ed3 100644 --- a/core/src/test/java/org/incenp/linkml/core/ObjectConverterTest.java +++ b/core/src/test/java/org/incenp/linkml/core/ObjectConverterTest.java @@ -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; @@ -56,17 +57,20 @@ import org.incenp.linkml.core.samples.base.ContainerOfIRIIdentifiableObjects; 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.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.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; @@ -515,6 +519,26 @@ void testMultiLevelTypeDesignators() throws IOException { roundtrip(cosdo); } + @Test + void testKeyTypeDesignator() throws IOException { + ContainerOfKeyedSelfDesignatedObjects cksdo = parse("container-of-keyed-self-designated-objects.yaml", + ContainerOfKeyedSelfDesignatedObjects.class); + + HashMap 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()); + + ksdc = d.get("DerivedKeyedSelfDesignatedClass"); + Assertions.assertNotNull(ksdc); + Assertions.assertEquals("Bob", ksdc.getFrobnicator()); + Assertions.assertInstanceOf(DerivedKeyedSelfDesignatedClass.class, ksdc); + Assertions.assertEquals(123, ((DerivedKeyedSelfDesignatedClass) ksdc).getLength()); + } + @Test void testReferenceToIRIIdentifiers() throws IOException, LinkMLRuntimeException { ctx.addPrefix("PFX", "https://example.org/"); diff --git a/core/src/test/linkml/samples.yaml b/core/src/test/linkml/samples.yaml index 36c80e9..4cc4b16 100644 --- a/core/src/test/linkml/samples.yaml +++ b/core/src/test/linkml/samples.yaml @@ -387,6 +387,35 @@ classes: range: Anything multivalued: true + KeyedSelfDesignatedClass: + description: >- + A class with a slot that is both a type designator and a key slot. + attributes: + type: + designates_type: true + key: true + frobnicator: + + DerivedKeyedSelfDesignatedClass: + description: >- + A class that derives from a class with a slot that is both a type + designator and a key slot. + is_a: KeyedSelfDesignatedClass + attributes: + length: + range: integer + + ContainerOfKeyedSelfDesignatedObjects: + description: >- + A class with a slot whose range is set to a class with a slot that is + both a key slot and and a type designator slot. + attributes: + objects: + range: KeyedSelfDesignatedClass + multivalued: true + inlined: true + inlined_as_list: false + enums: diff --git a/core/src/test/resources/core/samples/container-of-keyed-self-designated-objects.yaml b/core/src/test/resources/core/samples/container-of-keyed-self-designated-objects.yaml new file mode 100644 index 0000000..5e964fb --- /dev/null +++ b/core/src/test/resources/core/samples/container-of-keyed-self-designated-objects.yaml @@ -0,0 +1,6 @@ +objects: + DerivedKeyedSelfDesignatedClass: + frobnicator: Bob + length: 123 + KeyedSelfDesignatedClass: + frobnicator: Alice \ No newline at end of file From 5e71f3a05b1b1fbdbfdee830285feb31c9c64e33 Mon Sep 17 00:00:00 2001 From: Damien Goutte-Gattat Date: Fri, 26 Jun 2026 15:29:36 +0100 Subject: [PATCH 2/7] Commit re-generated code. --- ...ContainerOfKeyedSelfDesignatedObjects.java | 82 ++++++++++++++++++ .../base/DerivedKeyedSelfDesignatedClass.java | 67 +++++++++++++++ .../base/KeyedSelfDesignatedClass.java | 84 +++++++++++++++++++ 3 files changed, 233 insertions(+) create mode 100644 core/src/test/java/org/incenp/linkml/core/samples/base/ContainerOfKeyedSelfDesignatedObjects.java create mode 100644 core/src/test/java/org/incenp/linkml/core/samples/base/DerivedKeyedSelfDesignatedClass.java create mode 100644 core/src/test/java/org/incenp/linkml/core/samples/base/KeyedSelfDesignatedClass.java diff --git a/core/src/test/java/org/incenp/linkml/core/samples/base/ContainerOfKeyedSelfDesignatedObjects.java b/core/src/test/java/org/incenp/linkml/core/samples/base/ContainerOfKeyedSelfDesignatedObjects.java new file mode 100644 index 0000000..ed92db3 --- /dev/null +++ b/core/src/test/java/org/incenp/linkml/core/samples/base/ContainerOfKeyedSelfDesignatedObjects.java @@ -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 objects; + + public void setObjects(List objects) { + this.objects = objects; + } + + public List getObjects() { + return this.objects; + } + + public List 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; + } +} \ No newline at end of file diff --git a/core/src/test/java/org/incenp/linkml/core/samples/base/DerivedKeyedSelfDesignatedClass.java b/core/src/test/java/org/incenp/linkml/core/samples/base/DerivedKeyedSelfDesignatedClass.java new file mode 100644 index 0000000..99ac744 --- /dev/null +++ b/core/src/test/java/org/incenp/linkml/core/samples/base/DerivedKeyedSelfDesignatedClass.java @@ -0,0 +1,67 @@ +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#DerivedKeyedSelfDesignatedClass") +public class DerivedKeyedSelfDesignatedClass extends KeyedSelfDesignatedClass { + + @LinkURI("https://incenp.org/dvlpt/linkml-java/tests/samples#length") + private Integer length; + + public void setLength(Integer length) { + this.length = length; + } + + public Integer getLength() { + return this.length; + } + + @Override + public String toString() { + return "DerivedKeyedSelfDesignatedClass(type=" + this.getType() + ")"; + } + + @Override + public boolean equals(final Object o) { + if ( o == this ) return true; + if ( !(o instanceof DerivedKeyedSelfDesignatedClass) ) return false; + final DerivedKeyedSelfDesignatedClass other = (DerivedKeyedSelfDesignatedClass) o; + if ( !other.canEqual((Object) this)) return false; + if ( !super.equals(o) ) return false; + + final Object this$length = this.getLength(); + final Object other$length = other.getLength(); + if ( this$length == null ? other$length != null : !this$length.equals(other$length)) return false; + return true; + } + + protected boolean canEqual(final Object other) { + return other instanceof DerivedKeyedSelfDesignatedClass; + } + + @Override + public int hashCode() { + final int PRIME = 59; + int result = super.hashCode(); + final Object $length = this.getLength(); + result = result * PRIME + ($length == null ? 43 : $length.hashCode()); + return result; + } +} \ No newline at end of file diff --git a/core/src/test/java/org/incenp/linkml/core/samples/base/KeyedSelfDesignatedClass.java b/core/src/test/java/org/incenp/linkml/core/samples/base/KeyedSelfDesignatedClass.java new file mode 100644 index 0000000..cf595b2 --- /dev/null +++ b/core/src/test/java/org/incenp/linkml/core/samples/base/KeyedSelfDesignatedClass.java @@ -0,0 +1,84 @@ +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#KeyedSelfDesignatedClass") +public class KeyedSelfDesignatedClass { + + @Identifier(isGlobal = false) + @TypeDesignator + @Required + @LinkURI("https://incenp.org/dvlpt/linkml-java/tests/samples#type") + private String type; + + @LinkURI("https://incenp.org/dvlpt/linkml-java/tests/samples#frobnicator") + private String frobnicator; + + public void setType(String type) { + this.type = type; + } + + public String getType() { + return this.type; + } + + public void setFrobnicator(String frobnicator) { + this.frobnicator = frobnicator; + } + + public String getFrobnicator() { + return this.frobnicator; + } + + @Override + public String toString() { + return "KeyedSelfDesignatedClass(type=" + this.getType() + ")"; + } + + @Override + public boolean equals(final Object o) { + if ( o == this ) return true; + if ( !(o instanceof KeyedSelfDesignatedClass) ) return false; + final KeyedSelfDesignatedClass other = (KeyedSelfDesignatedClass) o; + if ( !other.canEqual((Object) this)) return false; + final Object this$type = this.getType(); + final Object other$type = other.getType(); + if ( this$type == null ? other$type != null : !this$type.equals(other$type)) return false; + final Object this$frobnicator = this.getFrobnicator(); + final Object other$frobnicator = other.getFrobnicator(); + if ( this$frobnicator == null ? other$frobnicator != null : !this$frobnicator.equals(other$frobnicator)) return false; + return true; + } + + protected boolean canEqual(final Object other) { + return other instanceof KeyedSelfDesignatedClass; + } + + @Override + public int hashCode() { + final int PRIME = 59; + int result = 1; + final Object $type = this.getType(); + result = result * PRIME + ($type == null ? 43 : $type.hashCode()); + final Object $frobnicator = this.getFrobnicator(); + result = result * PRIME + ($frobnicator == null ? 43 : $frobnicator.hashCode()); + return result; + } +} \ No newline at end of file From 83f8dde641419cd939a629c92a87be2d5f8132a6 Mon Sep 17 00:00:00 2001 From: Damien Goutte-Gattat Date: Mon, 29 Jun 2026 12:40:26 +0100 Subject: [PATCH 3/7] Support key type designator slots. Support the case where a type designator slot is also the key slot of its class. The fix to support that case is 3-fold: (1) The constructor of ClassInfo must explicitly allow a slot to be both a key slot and a type designator slot (currently, once a slot has been recognised as a key slot, it can no longer be recognised as a type designator). This is the only needed fix for _deserialisation_. Support for _serialising_ an instance of a class that has a dual key+type designator slot further requires: (2) The existing logic in the ObjectConverter class to find the most precise converter to use, which is currently only applied in some cases, must be used systematically. For that, it is moved at the beginning of the main `serialise(Object, boolean, ConverterContext)` method. (3) We must (temporarily) forbid inlined as a "simple dict" for any class that has a type designator, even if it has only a key slot and a primary value slot. This is because we cannot easily know if all subclasses will also be eligible for "simple dict" inlining. --- .../org/incenp/linkml/core/ClassInfo.java | 13 +++++++++++-- .../incenp/linkml/core/ObjectConverter.java | 17 +++++++++-------- .../linkml/core/ObjectConverterTest.java | 19 ++++++++++++++----- 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/org/incenp/linkml/core/ClassInfo.java b/core/src/main/java/org/incenp/linkml/core/ClassInfo.java index 6f9346b..cf491ab 100644 --- a/core/src/main/java/org/incenp/linkml/core/ClassInfo.java +++ b/core/src/main/java/org/incenp/linkml/core/ClassInfo.java @@ -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; } @@ -228,7 +230,14 @@ public boolean isEligibleForSimpleDict(boolean write) { if ( identifierSlot == null || getPrimarySlot() == null ) { return false; } else if ( write ) { - return slots.size() == 2; + // FIXME: For now, we completely forbid serialisation as a + // simple dict if the class has a type designator, because + // we cannot be sure that all child classes are also + // eligible. The proper solution here is to allow simple + // dict serialisation to be mixed with compact or expanded + // dict serialisations (LinkML-Py allows that), but this is + // not something we explicitly support for now. + return slots.size() == 2 && !hasTypeDesignator(); } else { return true; } diff --git a/core/src/main/java/org/incenp/linkml/core/ObjectConverter.java b/core/src/main/java/org/incenp/linkml/core/ObjectConverter.java index f65c506..e564140 100644 --- a/core/src/main/java/org/incenp/linkml/core/ObjectConverter.java +++ b/core/src/main/java/org/incenp/linkml/core/ObjectConverter.java @@ -309,14 +309,6 @@ protected List 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); } @@ -346,6 +338,15 @@ public Map 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 raw = new HashMap<>(); for ( Slot slot : klass.getSlots() ) { if ( (slot.isIdentifier()) && !withIdentifier ) { diff --git a/core/src/test/java/org/incenp/linkml/core/ObjectConverterTest.java b/core/src/test/java/org/incenp/linkml/core/ObjectConverterTest.java index 67a0ed3..c1a1b8d 100644 --- a/core/src/test/java/org/incenp/linkml/core/ObjectConverterTest.java +++ b/core/src/test/java/org/incenp/linkml/core/ObjectConverterTest.java @@ -532,11 +532,20 @@ void testKeyTypeDesignator() throws IOException { Assertions.assertNotNull(ksdc); Assertions.assertEquals("Alice", ksdc.getFrobnicator()); - ksdc = d.get("DerivedKeyedSelfDesignatedClass"); - Assertions.assertNotNull(ksdc); - Assertions.assertEquals("Bob", ksdc.getFrobnicator()); - Assertions.assertInstanceOf(DerivedKeyedSelfDesignatedClass.class, ksdc); - Assertions.assertEquals(123, ((DerivedKeyedSelfDesignatedClass) ksdc).getLength()); + 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); } @Test From 07f4afb9ecb0bc0350d54f81e367dc2463942edc Mon Sep 17 00:00:00 2001 From: Damien Goutte-Gattat Date: Mon, 29 Jun 2026 14:58:28 +0100 Subject: [PATCH 4/7] Add another test for keyed type designator slots. Test that we can correctly handle the case of a unknown designated type (where the type referenced by the type designator, which also happens to be the key of the dictionary entry, does not correspond to a known class in the code). --- .../linkml/core/ObjectConverterTest.java | 16 ++++++++++++++++ .../samples/base/KeyedSelfDesignatedClass.java | 18 ++++++++++++++++++ core/src/test/linkml/samples.yaml | 2 ++ 3 files changed, 36 insertions(+) diff --git a/core/src/test/java/org/incenp/linkml/core/ObjectConverterTest.java b/core/src/test/java/org/incenp/linkml/core/ObjectConverterTest.java index c1a1b8d..2dcab67 100644 --- a/core/src/test/java/org/incenp/linkml/core/ObjectConverterTest.java +++ b/core/src/test/java/org/incenp/linkml/core/ObjectConverterTest.java @@ -548,6 +548,22 @@ void testKeyTypeDesignator() throws IOException { roundtrip(cksdo); } + @Test + void testKeyTypeDesignatorWithUnknownType() throws IOException { + 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); + } + @Test void testReferenceToIRIIdentifiers() throws IOException, LinkMLRuntimeException { ctx.addPrefix("PFX", "https://example.org/"); diff --git a/core/src/test/java/org/incenp/linkml/core/samples/base/KeyedSelfDesignatedClass.java b/core/src/test/java/org/incenp/linkml/core/samples/base/KeyedSelfDesignatedClass.java index cf595b2..fb8d93d 100644 --- a/core/src/test/java/org/incenp/linkml/core/samples/base/KeyedSelfDesignatedClass.java +++ b/core/src/test/java/org/incenp/linkml/core/samples/base/KeyedSelfDesignatedClass.java @@ -31,6 +31,9 @@ public class KeyedSelfDesignatedClass { @LinkURI("https://incenp.org/dvlpt/linkml-java/tests/samples#frobnicator") private String frobnicator; + @ExtensionHolder + private Map extraSlots; + public void setType(String type) { this.type = type; } @@ -47,6 +50,21 @@ public String getFrobnicator() { return this.frobnicator; } + public void setExtraSlots(Map extraSlots) { + this.extraSlots = extraSlots; + } + + public Map getExtraSlots() { + return this.extraSlots; + } + + public Map getExtraSlots(boolean set) { + if ( this.extraSlots == null && set ) { + this.extraSlots = new HashMap<>(); + } + return this.extraSlots; + } + @Override public String toString() { return "KeyedSelfDesignatedClass(type=" + this.getType() + ")"; diff --git a/core/src/test/linkml/samples.yaml b/core/src/test/linkml/samples.yaml index 4cc4b16..31c732f 100644 --- a/core/src/test/linkml/samples.yaml +++ b/core/src/test/linkml/samples.yaml @@ -395,6 +395,8 @@ classes: designates_type: true key: true frobnicator: + extra_slots: + allowed: true DerivedKeyedSelfDesignatedClass: description: >- From 041ab59f0538e94b6b1aa1fd5def1570112ae1c7 Mon Sep 17 00:00:00 2001 From: Damien Goutte-Gattat Date: Mon, 29 Jun 2026 15:24:02 +0100 Subject: [PATCH 5/7] Inject correct key type designator if needed. When serialising, we must be ready for the case where the object to serialise may not have its type designator slot set to a value. We already support that in the general case, but some additional support is needed for the special case where the type designator slot also happens to be the key slot of its class. --- .../incenp/linkml/core/ObjectConverter.java | 6 ++++- .../linkml/core/ObjectConverterTest.java | 22 +++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/incenp/linkml/core/ObjectConverter.java b/core/src/main/java/org/incenp/linkml/core/ObjectConverter.java index e564140..a7db16a 100644 --- a/core/src/main/java/org/incenp/linkml/core/ObjectConverter.java +++ b/core/src/main/java/org/incenp/linkml/core/ObjectConverter.java @@ -435,7 +435,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); } diff --git a/core/src/test/java/org/incenp/linkml/core/ObjectConverterTest.java b/core/src/test/java/org/incenp/linkml/core/ObjectConverterTest.java index 2dcab67..a9ec963 100644 --- a/core/src/test/java/org/incenp/linkml/core/ObjectConverterTest.java +++ b/core/src/test/java/org/incenp/linkml/core/ObjectConverterTest.java @@ -520,7 +520,7 @@ void testMultiLevelTypeDesignators() throws IOException { } @Test - void testKeyTypeDesignator() throws IOException { + void testKeyTypeDesignator() throws IOException, LinkMLRuntimeException { ContainerOfKeyedSelfDesignatedObjects cksdo = parse("container-of-keyed-self-designated-objects.yaml", ContainerOfKeyedSelfDesignatedObjects.class); @@ -546,10 +546,19 @@ void testKeyTypeDesignator() throws IOException { // 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 raw = conv.serialise(cksdo, true, ctx); + @SuppressWarnings("unchecked") + Map objects = (Map) raw.get("objects"); + Assertions.assertTrue(objects.containsKey("DerivedKeyedSelfDesignatedClass")); } @Test - void testKeyTypeDesignatorWithUnknownType() throws IOException { + void testKeyTypeDesignatorWithUnknownType() throws IOException, LinkMLRuntimeException { String test = "objects:\n" + " UnknownSelfDesignatedClass:\n" + " frobnicator: \"Charlie\"\n" + @@ -562,6 +571,15 @@ void testKeyTypeDesignatorWithUnknownType() throws IOException { 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 raw = conv.serialise(cksdo, true, ctx); + @SuppressWarnings("unchecked") + Map objects = (Map) raw.get("objects"); + Assertions.assertTrue(objects.containsKey("KeyedSelfDesignatedClass")); } @Test From a48ddd2b63af56d4061d7d98e2d797a61447d016 Mon Sep 17 00:00:00 2001 From: Damien Goutte-Gattat Date: Mon, 29 Jun 2026 16:04:30 +0100 Subject: [PATCH 6/7] Support mixing simple and non-simple dicts. When the range of a multi-valued slot is a class that has descendants, it can happen that some objects in the list are eligible for simple dict inlining whereas some others are not (if the base class has only two slots but some derived classes have some additional slots). We already support that case when deserialising (this is automatically taken care of by the ObjectConverter::normalistList method), this commit makes sure we also support that case when serialising. All that is needed is to check for simple dict eligibility separately for every single object in the list to serialise, instead of once and for all at the beginning of the serialisation. --- .../org/incenp/linkml/core/ClassInfo.java | 9 +------ .../incenp/linkml/core/ObjectConverter.java | 3 ++- .../linkml/core/ObjectConverterTest.java | 27 +++++++++++++++++++ 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/org/incenp/linkml/core/ClassInfo.java b/core/src/main/java/org/incenp/linkml/core/ClassInfo.java index cf491ab..642e6e7 100644 --- a/core/src/main/java/org/incenp/linkml/core/ClassInfo.java +++ b/core/src/main/java/org/incenp/linkml/core/ClassInfo.java @@ -230,14 +230,7 @@ public boolean isEligibleForSimpleDict(boolean write) { if ( identifierSlot == null || getPrimarySlot() == null ) { return false; } else if ( write ) { - // FIXME: For now, we completely forbid serialisation as a - // simple dict if the class has a type designator, because - // we cannot be sure that all child classes are also - // eligible. The proper solution here is to allow simple - // dict serialisation to be mixed with compact or expanded - // dict serialisations (LinkML-Py allows that), but this is - // not something we explicitly support for now. - return slots.size() == 2 && !hasTypeDesignator(); + return slots.size() == 2; } else { return true; } diff --git a/core/src/main/java/org/incenp/linkml/core/ObjectConverter.java b/core/src/main/java/org/incenp/linkml/core/ObjectConverter.java index a7db16a..a449b07 100644 --- a/core/src/main/java/org/incenp/linkml/core/ObjectConverter.java +++ b/core/src/main/java/org/incenp/linkml/core/ObjectConverter.java @@ -391,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 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); } diff --git a/core/src/test/java/org/incenp/linkml/core/ObjectConverterTest.java b/core/src/test/java/org/incenp/linkml/core/ObjectConverterTest.java index a9ec963..64da918 100644 --- a/core/src/test/java/org/incenp/linkml/core/ObjectConverterTest.java +++ b/core/src/test/java/org/incenp/linkml/core/ObjectConverterTest.java @@ -582,6 +582,33 @@ void testKeyTypeDesignatorWithUnknownType() throws IOException, LinkMLRuntimeExc 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 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 testReferenceToIRIIdentifiers() throws IOException, LinkMLRuntimeException { ctx.addPrefix("PFX", "https://example.org/"); From 3440e4d9f72ece4ff9d5975b1ff07b995ad8abbe Mon Sep 17 00:00:00 2001 From: Damien Goutte-Gattat Date: Mon, 29 Jun 2026 16:41:22 +0100 Subject: [PATCH 7/7] Add test for a slot that is both a TD and an identifier slot. A kind of "special case within a special case" is the case where the type designator slot also happens to be, not the _key_ slot, but the _identifier_ slot of its class. This is already fully supported by the existing code, but we add a test fixture to explicitly test this case. --- .../linkml/core/ObjectConverterTest.java | 35 ++++++++ ...inerOfIdentifiedSelfDesignatedObjects.java | 82 ++++++++++++++++++ .../DerivedIdentifiedSelfDesignatedClass.java | 67 +++++++++++++++ .../base/IdentifiedSelfDesignatedClass.java | 85 +++++++++++++++++++ core/src/test/linkml/samples.yaml | 31 +++++++ 5 files changed, 300 insertions(+) create mode 100644 core/src/test/java/org/incenp/linkml/core/samples/base/ContainerOfIdentifiedSelfDesignatedObjects.java create mode 100644 core/src/test/java/org/incenp/linkml/core/samples/base/DerivedIdentifiedSelfDesignatedClass.java create mode 100644 core/src/test/java/org/incenp/linkml/core/samples/base/IdentifiedSelfDesignatedClass.java diff --git a/core/src/test/java/org/incenp/linkml/core/ObjectConverterTest.java b/core/src/test/java/org/incenp/linkml/core/ObjectConverterTest.java index 64da918..f3cf32e 100644 --- a/core/src/test/java/org/incenp/linkml/core/ObjectConverterTest.java +++ b/core/src/test/java/org/incenp/linkml/core/ObjectConverterTest.java @@ -55,6 +55,7 @@ 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; @@ -63,6 +64,7 @@ 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; @@ -70,6 +72,7 @@ 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; @@ -609,6 +612,38 @@ void testKeyTypeDesignatorWithSimpleDictMix() throws IOException, LinkMLRuntimeE 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 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/"); diff --git a/core/src/test/java/org/incenp/linkml/core/samples/base/ContainerOfIdentifiedSelfDesignatedObjects.java b/core/src/test/java/org/incenp/linkml/core/samples/base/ContainerOfIdentifiedSelfDesignatedObjects.java new file mode 100644 index 0000000..fd02a31 --- /dev/null +++ b/core/src/test/java/org/incenp/linkml/core/samples/base/ContainerOfIdentifiedSelfDesignatedObjects.java @@ -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 objects; + + public void setObjects(List objects) { + this.objects = objects; + } + + public List getObjects() { + return this.objects; + } + + public List 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; + } +} \ No newline at end of file diff --git a/core/src/test/java/org/incenp/linkml/core/samples/base/DerivedIdentifiedSelfDesignatedClass.java b/core/src/test/java/org/incenp/linkml/core/samples/base/DerivedIdentifiedSelfDesignatedClass.java new file mode 100644 index 0000000..4ab81df --- /dev/null +++ b/core/src/test/java/org/incenp/linkml/core/samples/base/DerivedIdentifiedSelfDesignatedClass.java @@ -0,0 +1,67 @@ +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#DerivedIdentifiedSelfDesignatedClass") +public class DerivedIdentifiedSelfDesignatedClass extends IdentifiedSelfDesignatedClass { + + @LinkURI("https://incenp.org/dvlpt/linkml-java/tests/samples#length") + private Integer length; + + public void setLength(Integer length) { + this.length = length; + } + + public Integer getLength() { + return this.length; + } + + @Override + public String toString() { + return "DerivedIdentifiedSelfDesignatedClass(type=" + this.getType() + ")"; + } + + @Override + public boolean equals(final Object o) { + if ( o == this ) return true; + if ( !(o instanceof DerivedIdentifiedSelfDesignatedClass) ) return false; + final DerivedIdentifiedSelfDesignatedClass other = (DerivedIdentifiedSelfDesignatedClass) o; + if ( !other.canEqual((Object) this)) return false; + if ( !super.equals(o) ) return false; + + final Object this$length = this.getLength(); + final Object other$length = other.getLength(); + if ( this$length == null ? other$length != null : !this$length.equals(other$length)) return false; + return true; + } + + protected boolean canEqual(final Object other) { + return other instanceof DerivedIdentifiedSelfDesignatedClass; + } + + @Override + public int hashCode() { + final int PRIME = 59; + int result = super.hashCode(); + final Object $length = this.getLength(); + result = result * PRIME + ($length == null ? 43 : $length.hashCode()); + return result; + } +} \ No newline at end of file diff --git a/core/src/test/java/org/incenp/linkml/core/samples/base/IdentifiedSelfDesignatedClass.java b/core/src/test/java/org/incenp/linkml/core/samples/base/IdentifiedSelfDesignatedClass.java new file mode 100644 index 0000000..943f95f --- /dev/null +++ b/core/src/test/java/org/incenp/linkml/core/samples/base/IdentifiedSelfDesignatedClass.java @@ -0,0 +1,85 @@ +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#IdentifiedSelfDesignatedClass") +public class IdentifiedSelfDesignatedClass { + + @Identifier + @TypeDesignator + @Required + @Converter(CurieConverter.class) + @LinkURI("https://incenp.org/dvlpt/linkml-java/tests/samples#type") + private String type; + + @LinkURI("https://incenp.org/dvlpt/linkml-java/tests/samples#frobnicator") + private String frobnicator; + + public void setType(String type) { + this.type = type; + } + + public String getType() { + return this.type; + } + + public void setFrobnicator(String frobnicator) { + this.frobnicator = frobnicator; + } + + public String getFrobnicator() { + return this.frobnicator; + } + + @Override + public String toString() { + return "IdentifiedSelfDesignatedClass(type=" + this.getType() + ")"; + } + + @Override + public boolean equals(final Object o) { + if ( o == this ) return true; + if ( !(o instanceof IdentifiedSelfDesignatedClass) ) return false; + final IdentifiedSelfDesignatedClass other = (IdentifiedSelfDesignatedClass) o; + if ( !other.canEqual((Object) this)) return false; + final Object this$type = this.getType(); + final Object other$type = other.getType(); + if ( this$type == null ? other$type != null : !this$type.equals(other$type)) return false; + final Object this$frobnicator = this.getFrobnicator(); + final Object other$frobnicator = other.getFrobnicator(); + if ( this$frobnicator == null ? other$frobnicator != null : !this$frobnicator.equals(other$frobnicator)) return false; + return true; + } + + protected boolean canEqual(final Object other) { + return other instanceof IdentifiedSelfDesignatedClass; + } + + @Override + public int hashCode() { + final int PRIME = 59; + int result = 1; + final Object $type = this.getType(); + result = result * PRIME + ($type == null ? 43 : $type.hashCode()); + final Object $frobnicator = this.getFrobnicator(); + result = result * PRIME + ($frobnicator == null ? 43 : $frobnicator.hashCode()); + return result; + } +} \ No newline at end of file diff --git a/core/src/test/linkml/samples.yaml b/core/src/test/linkml/samples.yaml index 31c732f..6ec28fc 100644 --- a/core/src/test/linkml/samples.yaml +++ b/core/src/test/linkml/samples.yaml @@ -418,6 +418,37 @@ classes: inlined: true inlined_as_list: false + IdentifiedSelfDesignatedClass: + description: >- + A class with a slot that is both a type designator and an + identifier slot. + attributes: + type: + designates_type: true + identifier: true + range: uriorcurie + frobnicator: + + DerivedIdentifiedSelfDesignatedClass: + description: >- + A class that derives from a class with a slot that is both a type + designator and an identifier slot. + is_a: IdentifiedSelfDesignatedClass + attributes: + length: + range: integer + + ContainerOfIdentifiedSelfDesignatedObjects: + description: >- + A class with a slot whose range is set to a class with a slot that is + both an identifier slot and and a type designator slot. + attributes: + objects: + range: IdentifiedSelfDesignatedClass + multivalued: true + inlined: true + inlined_as_list: false + enums: