diff --git a/hibernate-models/src/main/java/org/hibernate/models/internal/AbstractClassDetailsRegistry.java b/hibernate-models/src/main/java/org/hibernate/models/internal/AbstractClassDetailsRegistry.java index 286816a..bbcf10f 100644 --- a/hibernate-models/src/main/java/org/hibernate/models/internal/AbstractClassDetailsRegistry.java +++ b/hibernate-models/src/main/java/org/hibernate/models/internal/AbstractClassDetailsRegistry.java @@ -6,13 +6,18 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Predicate; import org.hibernate.models.UnknownClassException; +import org.hibernate.models.internal.util.CollectionHelper; import org.hibernate.models.spi.ClassDetails; import org.hibernate.models.spi.ModelsContext; +import org.hibernate.models.spi.TypeDetails; import static org.hibernate.models.spi.ClassDetails.CLASS_CLASS_DETAILS; import static org.hibernate.models.spi.ClassDetails.OBJECT_CLASS_DETAILS; @@ -30,18 +35,22 @@ public abstract class AbstractClassDetailsRegistry implements MutableClassDetail protected final Map classDetailsMap; // subtype per type - protected final Map> subTypeClassDetailsMap; + protected final Map> directSubTypeMap; + // implementor by interface + protected final Map> directImplementorMap; protected AbstractClassDetailsRegistry(ModelsContext context) { - this( new ConcurrentHashMap<>(), new ConcurrentHashMap<>(), context ); + this( new ConcurrentHashMap<>(), new ConcurrentHashMap<>(), new ConcurrentHashMap<>(), context ); } protected AbstractClassDetailsRegistry( Map classDetailsMap, - Map> subTypeClassDetailsMap, + Map> directSubTypeMap, + Map> directImplementorMap, ModelsContext context) { this.classDetailsMap = classDetailsMap; - this.subTypeClassDetailsMap = subTypeClassDetailsMap; + this.directSubTypeMap = directSubTypeMap; + this.directImplementorMap = directImplementorMap; this.context = context; classDetailsMap.put( CLASS_CLASS_DETAILS.getName(), CLASS_CLASS_DETAILS ); @@ -51,13 +60,22 @@ protected AbstractClassDetailsRegistry( } @Override - public List getDirectSubTypes(String superTypeName) { - return subTypeClassDetailsMap.get( superTypeName ); + public List getDirectSubTypes(String typeName) { + final Set directSubtypes = getDirectSubtypes( typeName ); + return CollectionHelper.isNotEmpty( directSubtypes ) + ? new ArrayList<>( directSubtypes ) + : List.of(); } @Override - public void forEachDirectSubType(String superTypeName, ClassDetailsConsumer consumer) { - final List directSubTypes = getDirectSubTypes( superTypeName ); + public Set getDirectSubtypes(String typeName) { + final Set directSubtypes = directSubTypeMap.get( typeName ); + return directSubtypes != null ? directSubtypes : Set.of(); + } + + @Override + public void forEachDirectSubtype(String typeName, ClassDetailsConsumer consumer) { + final List directSubTypes = getDirectSubTypes( typeName ); if ( directSubTypes == null ) { return; } @@ -66,6 +84,85 @@ public void forEachDirectSubType(String superTypeName, ClassDetailsConsumer cons } } + @Override + public Set getDirectImplementors(String interfaceName) { + final Set implementors = directImplementorMap.get( interfaceName ); + return implementors != null ? implementors : Set.of(); + } + + @Override + public void forEachDirectImplementor(String interfaceName, ClassDetailsConsumer consumer) { + final Set directImplementors = getDirectImplementors( interfaceName ); + if ( directImplementors != null ) { + directImplementors.forEach( consumer::consume ); + } + } + + @Override + public Set findConcreteTypes(String base, boolean includeBase) { + final Set result = new LinkedHashSet<>(); + walkImplementors( base, includeBase, classDetails -> { + if ( !classDetails.isAbstract() && !classDetails.isInterface() ) { + result.add( classDetails ); + } + + }); + return result; + } + + @Override + public Set collectImplementors(String base, boolean includeBase, Predicate exclusions) { + final Set result = new LinkedHashSet<>(); + walkImplementors( base, includeBase, classDetails -> { + if ( exclusions == null || !exclusions.test( classDetails ) ) { + result.add( classDetails ); + } + } ); + return result; + } + + @Override + public void walkImplementors(String base, boolean includeBase, ClassDetailsConsumer consumer) { + if ( includeBase ) { + final ClassDetails baseDetails = resolveClassDetails( base ); + consumer.consume( baseDetails ); + } + + forEachDirectSubtype( base, (subType) -> { + consumer.consume( subType ); + walkSubtypes( subType, consumer ); + } ); + + forEachDirectImplementor( base, (implementor) -> { + consumer.consume( implementor ); + walkInterfaceImplementors( implementor, consumer ); + } ); + } + + private void walkSubtypes(ClassDetails base, ClassDetailsConsumer consumer) { + forEachDirectSubtype( base.getName(), (subType) -> { + consumer.consume( subType ); + walkSubtypes( subType, consumer ); + } ); + } + + private void walkInterfaceImplementors(ClassDetails implementor, ClassDetailsConsumer consumer) { + if ( implementor.isInterface() ) { + // the direct interface implementor is itself an interface... + forEachDirectImplementor( implementor.getName(), (implementorImplementor) -> { + consumer.consume( implementorImplementor ); + walkInterfaceImplementors( implementorImplementor, consumer ); + } ); + } + else { + // the direct interface implementor is itself a class... + forEachDirectSubtype( implementor.getName(), (subtype) -> { + consumer.consume( subtype ); + walkSubtypes( subtype, consumer ); + } ); + } + } + @Override public ClassDetails findClassDetails(String name) { return classDetailsMap.get( name ); @@ -119,14 +216,25 @@ public void addClassDetails(String name, ClassDetails classDetails) { classDetailsMap.put( name, classDetails ); if ( classDetails.getSuperClass() != null ) { - List subTypes = subTypeClassDetailsMap.get( classDetails.getSuperClass().getName() ); + Set subTypes = directSubTypeMap.get( classDetails.getSuperClass().getName() ); //noinspection Java8MapApi if ( subTypes == null ) { - subTypes = new ArrayList<>(); - subTypeClassDetailsMap.put( classDetails.getSuperClass().getName(), subTypes ); + subTypes = new LinkedHashSet<>(); + directSubTypeMap.put( classDetails.getSuperClass().getName(), subTypes ); } subTypes.add( classDetails ); } + + final List implementedInterfaces = classDetails.getImplementedInterfaces(); + if ( implementedInterfaces != null ) { + implementedInterfaces.forEach( (implementedInterface) -> { + final Set directImplementors = directImplementorMap.computeIfAbsent( + implementedInterface.getName(), + (interfaceName) -> new LinkedHashSet<>() + ); + directImplementors.add( classDetails ); + } ); + } } @Override @@ -172,7 +280,11 @@ public Map getClassDetailsMap() { return Collections.unmodifiableMap( classDetailsMap ); } - public Map> getSubTypeClassDetailsMap() { - return Collections.unmodifiableMap( subTypeClassDetailsMap ); + public Map> getDirectSubTypeMap() { + return Collections.unmodifiableMap( directSubTypeMap ); + } + + public Map> getDirectImplementorMap() { + return Collections.unmodifiableMap( directImplementorMap ); } } diff --git a/hibernate-models/src/main/java/org/hibernate/models/spi/ClassDetailsRegistry.java b/hibernate-models/src/main/java/org/hibernate/models/spi/ClassDetailsRegistry.java index b3ca6af..0df5c52 100644 --- a/hibernate-models/src/main/java/org/hibernate/models/spi/ClassDetailsRegistry.java +++ b/hibernate-models/src/main/java/org/hibernate/models/spi/ClassDetailsRegistry.java @@ -5,6 +5,8 @@ package org.hibernate.models.spi; import java.util.List; +import java.util.Set; +import java.util.function.Predicate; import org.hibernate.models.UnknownClassException; @@ -18,6 +20,12 @@ * @author Steve Ebersole */ public interface ClassDetailsRegistry { + /** + * Resolves a managed-class by name. If there is currently no such registration, + * one is created. + */ + ClassDetails resolveClassDetails(String name); + /** * Find the managed-class with the given {@code name}, if there is one. * Returns {@code null} if there are none registered with that name. @@ -48,33 +56,122 @@ default ClassDetails getClassDetails(String name) { void forEachClassDetails(ClassDetailsConsumer consumer); /** - * Get the list of all direct subtypes for the named managed-class. Returns - * {@code null} if there are none + * Get the list of all direct subtypes for the named managed-class. + * + * @deprecated Use {@linkplain #getDirectSubtypes(String)} instead. */ + @Deprecated List getDirectSubTypes(String superTypeName); + /** + * Get the list of all direct subtypes for the named managed-class. + */ + Set getDirectSubtypes(String superTypeName); + /** * Visit each direct subtype of the named managed-class + * + * @deprecated Use {@linkplain #forEachDirectSubtype} instead. */ - @SuppressWarnings("unused") - void forEachDirectSubType(String superTypeName, ClassDetailsConsumer consumer); + @Deprecated + default void forEachDirectSubType(String typeName, ClassDetailsConsumer consumer) { + forEachDirectSubtype( typeName, consumer ); + } /** - * Resolves a managed-class by name. If there is currently no such registration, - * one is created. + * Visit each direct subtype of the named managed-class */ - ClassDetails resolveClassDetails(String name); + void forEachDirectSubtype(String typeName, ClassDetailsConsumer consumer); - default S as(Class type) { - //noinspection unchecked - return (S) this; + /** + * Get the list of all direct implementors for the named interface. + * + * @return The direct implementors of the named interface. + * + * @apiNote Does not verify that {@code interfaceName} actually names an interface. + */ + Set getDirectImplementors(String interfaceName); + + /** + * Visit each direct implementor of the named interface + * + * @apiNote Does not verify that {@code interfaceName} actually names an + * interface. If it does not, no callbacks will happen. + */ + void forEachDirectImplementor(String interfaceName, ClassDetailsConsumer consumer); + + /** + * Find ClassDetails for all non-abstract subtypes / implementors + * of the given base (which might be a class or interface). + * + * @param base The name of the class/interface from which to start walking. + * + * @apiNote If {@code base} is a concrete type, it will also be returned. + * @see #findConcreteTypes(String, boolean) + */ + default Set findConcreteTypes(String base) { + return findConcreteTypes( base, true ); + } + + /** + * Find ClassDetails for all non-abstract subtypes / implementors + * of the given base (which might be a class or interface). + * + * @param base The name of the class/interface from which to start walking. + * @param includeBase Whether to include {@code base} if it is concrete type. + * + * @see #getDirectSubTypes + * @see #getDirectImplementors + */ + Set findConcreteTypes(String base, boolean includeBase); + + /** + * Walks the inheritance tree downward, starting from {@code base}, + * calling the consumer for each subclass and interface which is directly + * an implementor. + * + * @param base The type from which to start. + * @param includeBase Whether to include {@code base} if it is concrete type. + * @param consumer The callback. + */ + void walkImplementors(String base, boolean includeBase, ClassDetailsConsumer consumer); + + /** + * Walks the inheritance tree, starting from {@code base}, "downward" + * calling the consumer for each subclass and interface which is directly + * an implementor. + * + * @param base The type from which to start. + * @param includeBase Whether to include {@code base} if it is concrete type. + */ + default Set collectImplementors(String base, boolean includeBase) { + return collectImplementors( base, includeBase, null ); } + /** + * Walks the inheritance tree, starting from {@code base}, "downward" + * calling the consumer for each subclass and interface which is directly + * an implementor. + * + * @param base The type from which to start. + * @param includeBase Whether to include {@code base} if it is concrete type. + * @param exclusions Exclusive check to filter ClassDetails out of the result. + */ + Set collectImplementors(String base, boolean includeBase, Predicate exclusions); + /** * Access to the ClassDetailsBuilder used in this registry */ ClassDetailsBuilder getClassDetailsBuilder(); + @SuppressWarnings("unchecked") + default S as(Class type) { + if ( type.isInstance( this ) ) { + return (S) this; + } + throw new UnsupportedOperationException( "Unsure how to cast " + this + " to " + type.getName() ); + } + @FunctionalInterface interface ClassDetailsConsumer { void consume(ClassDetails classDetails); diff --git a/hibernate-models/src/test/java/org/hibernate/models/testing/tests/classes/Customer.java b/hibernate-models/src/test/java/org/hibernate/models/testing/tests/classes/Customer.java new file mode 100644 index 0000000..7eb38a5 --- /dev/null +++ b/hibernate-models/src/test/java/org/hibernate/models/testing/tests/classes/Customer.java @@ -0,0 +1,11 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.models.testing.tests.classes; + +/** + * @author Steve Ebersole + */ +public interface Customer extends Person { +} diff --git a/hibernate-models/src/test/java/org/hibernate/models/testing/tests/classes/CustomerImpl.java b/hibernate-models/src/test/java/org/hibernate/models/testing/tests/classes/CustomerImpl.java new file mode 100644 index 0000000..715409b --- /dev/null +++ b/hibernate-models/src/test/java/org/hibernate/models/testing/tests/classes/CustomerImpl.java @@ -0,0 +1,11 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.models.testing.tests.classes; + +/** + * @author Steve Ebersole + */ +public class CustomerImpl extends PersonImpl implements Customer { +} diff --git a/hibernate-models/src/test/java/org/hibernate/models/testing/tests/classes/Employee.java b/hibernate-models/src/test/java/org/hibernate/models/testing/tests/classes/Employee.java new file mode 100644 index 0000000..3e515b1 --- /dev/null +++ b/hibernate-models/src/test/java/org/hibernate/models/testing/tests/classes/Employee.java @@ -0,0 +1,11 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.models.testing.tests.classes; + +/** + * @author Steve Ebersole + */ +public interface Employee extends Person { +} diff --git a/hibernate-models/src/test/java/org/hibernate/models/testing/tests/classes/EmployeeImpl.java b/hibernate-models/src/test/java/org/hibernate/models/testing/tests/classes/EmployeeImpl.java new file mode 100644 index 0000000..7f83e4d --- /dev/null +++ b/hibernate-models/src/test/java/org/hibernate/models/testing/tests/classes/EmployeeImpl.java @@ -0,0 +1,11 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.models.testing.tests.classes; + +/** + * @author Steve Ebersole + */ +public class EmployeeImpl extends PersonImpl implements Employee { +} diff --git a/hibernate-models/src/test/java/org/hibernate/models/testing/tests/classes/ImplementorTests.java b/hibernate-models/src/test/java/org/hibernate/models/testing/tests/classes/ImplementorTests.java new file mode 100644 index 0000000..4b64d95 --- /dev/null +++ b/hibernate-models/src/test/java/org/hibernate/models/testing/tests/classes/ImplementorTests.java @@ -0,0 +1,270 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.models.testing.tests.classes; + +import java.util.Set; + +import org.hibernate.models.spi.ClassDetails; +import org.hibernate.models.spi.ClassDetailsRegistry; +import org.hibernate.models.spi.ModelsContext; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.models.testing.TestHelper.buildModelContext; + +/** + * @author Steve Ebersole + */ +public class ImplementorTests { + @Test + void testDirectImplementors() { + final ModelsContext modelsContext = buildModelContext( + PersonImpl.class, + CustomerImpl.class, + EmployeeImpl.class + ); + final ClassDetailsRegistry classDetailsRegistry = modelsContext.getClassDetailsRegistry(); + + { + // Person + final Set directImplementors = classDetailsRegistry + .getDirectImplementors( Person.class.getName() ); + assertThat( directImplementors ).hasSize( 3 ); + assertThat( directImplementors.stream().map( ClassDetails::getName ) ).contains( + PersonImpl.class.getName(), + Customer.class.getName(), + Employee.class.getName() + ); + } + + { + // PersonImpl + final Set directImplementors = classDetailsRegistry + .getDirectImplementors( PersonImpl.class.getName() ); + assertThat( directImplementors ).isEmpty(); + } + + { + // Customer + final Set directImplementors = classDetailsRegistry + .getDirectImplementors( Customer.class.getName() ); + assertThat( directImplementors ).hasSize( 1 ); + assertThat( directImplementors.stream().map( ClassDetails::getName ) ).contains( + CustomerImpl.class.getName() + ); + } + + { + // CustomerImpl + final Set directImplementors = classDetailsRegistry + .getDirectImplementors( CustomerImpl.class.getName() ); + assertThat( directImplementors ).isEmpty(); + } + } + + @Test + void testConcreteTypesFromPerson() { + final ModelsContext modelsContext = buildModelContext( + PersonImpl.class, + CustomerImpl.class, + EmployeeImpl.class + ); + final ClassDetailsRegistry classDetailsRegistry = modelsContext.getClassDetailsRegistry(); + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Since Person is an interface, we will get all concrete impls, + // regardless of whether we say to include base or not (because + // PersonImpl is abstract): + // * CustomerImpl + // * EmployeeImpl + + final Set concreteTypesWithBase = classDetailsRegistry.findConcreteTypes( Person.class.getName(), true ); + assertThat( concreteTypesWithBase ).hasSize( 2 ); + assertThat( concreteTypesWithBase.stream().map( ClassDetails::getName ) ).contains( + CustomerImpl.class.getName(), + EmployeeImpl.class.getName() + ); + + final Set concreteTypesWithoutBase = classDetailsRegistry.findConcreteTypes( Person.class.getName(), false ); + assertThat( concreteTypesWithoutBase ).hasSize( 2 ); + assertThat( concreteTypesWithoutBase.stream().map( ClassDetails::getName ) ).contains( + CustomerImpl.class.getName(), + EmployeeImpl.class.getName() + ); + } + + @Test + void testImplementorsFromPerson() { + final ModelsContext modelsContext = buildModelContext( + PersonImpl.class, + CustomerImpl.class, + EmployeeImpl.class + ); + final ClassDetailsRegistry classDetailsRegistry = modelsContext.getClassDetailsRegistry(); + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // For implementors, it will matter whether we say to include base or not. + // Without the base (Person) we have: + // * PersonImpl + // * Customer + // * CustomerImpl + // * Employee + // * EmployeeImpl + + final Set implementorsWithoutBase = classDetailsRegistry.collectImplementors( + Person.class.getName(), + false + ); + assertThat( implementorsWithoutBase ).hasSize( 5 ); + assertThat( implementorsWithoutBase.stream().map( ClassDetails::getName ) ).contains( + Customer.class.getName(), + Employee.class.getName(), + PersonImpl.class.getName(), + CustomerImpl.class.getName(), + EmployeeImpl.class.getName() + ); + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // including the base adds Person + + final Set implementorsWithBase = classDetailsRegistry.collectImplementors( + Person.class.getName(), + true + ); + assertThat( implementorsWithBase ).hasSize( 6 ); + assertThat( implementorsWithBase.stream().map( ClassDetails::getName ) ).contains( + Person.class.getName(), + Customer.class.getName(), + Employee.class.getName(), + PersonImpl.class.getName(), + CustomerImpl.class.getName(), + EmployeeImpl.class.getName() + ); + } + + @Test + void testConcreteTypesFromPersonImpl() { + final ModelsContext modelsContext = buildModelContext( + PersonImpl.class, + CustomerImpl.class, + EmployeeImpl.class + ); + final ClassDetailsRegistry classDetailsRegistry = modelsContext.getClassDetailsRegistry(); + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Since PersonImpl is a class and abstract, we will get all concrete impls, + // regardless of whether we say to include base or not: + // * CustomerImpl + // * EmployeeImpl + + final Set concreteTypesWithoutBase = classDetailsRegistry.findConcreteTypes( PersonImpl.class.getName(), false ); + assertThat( concreteTypesWithoutBase ).hasSize( 2 ); + assertThat( concreteTypesWithoutBase.stream().map( ClassDetails::getName ) ).contains( + CustomerImpl.class.getName(), + EmployeeImpl.class.getName() + ); + + final Set concreteTypesWithBase = classDetailsRegistry.findConcreteTypes( PersonImpl.class.getName(), true ); + assertThat( concreteTypesWithBase ).hasSize( 2 ); + assertThat( concreteTypesWithBase.stream().map( ClassDetails::getName ) ).contains( + CustomerImpl.class.getName(), + EmployeeImpl.class.getName() + ); + } + + @Test + void testImplementorsFromPersonImpl() { + final ModelsContext modelsContext = buildModelContext( + PersonImpl.class, + CustomerImpl.class, + EmployeeImpl.class + ); + final ClassDetailsRegistry classDetailsRegistry = modelsContext.getClassDetailsRegistry(); + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // For implementors, it will matter whether we say to include base or not. + // Without the base (PersonImpl) we have: + // * CustomerImpl + // * EmployeeImpl + + final Set implementorsWithoutBase = classDetailsRegistry.collectImplementors( + PersonImpl.class.getName(), + false + ); + assertThat( implementorsWithoutBase ).hasSize( 2 ); + assertThat( implementorsWithoutBase.stream().map( ClassDetails::getName ) ).contains( + CustomerImpl.class.getName(), + EmployeeImpl.class.getName() + ); + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Including the base, we now have all 3: + // * PersonImpl + // * CustomerImpl + // * EmployeeImpl + + final Set implementorsWithBase = classDetailsRegistry.collectImplementors( + PersonImpl.class.getName(), + true + ); + assertThat( implementorsWithBase ).hasSize( 3 ); + assertThat( implementorsWithBase.stream().map( ClassDetails::getName ) ).contains( + PersonImpl.class.getName(), + CustomerImpl.class.getName(), + EmployeeImpl.class.getName() + ); + } + + @Test + void testConcreteTypesFromCustomer() { + final ModelsContext modelsContext = buildModelContext( + PersonImpl.class, + CustomerImpl.class, + EmployeeImpl.class + ); + final ClassDetailsRegistry classDetailsRegistry = modelsContext.getClassDetailsRegistry(); + + final Set concreteTypesWithoutBase = classDetailsRegistry + .findConcreteTypes( Customer.class.getName(), false ); + assertThat( concreteTypesWithoutBase ).hasSize( 1 ); + assertThat( concreteTypesWithoutBase.stream().map( ClassDetails::getName ) ).contains( + CustomerImpl.class.getName() + ); + + final Set concreteTypesWithBase = classDetailsRegistry.findConcreteTypes( + Customer.class.getName(), + true + ); + assertThat( concreteTypesWithBase ).hasSize( 1 ); + assertThat( concreteTypesWithoutBase.stream().map( ClassDetails::getName ) ).contains( + CustomerImpl.class.getName() + ); + } + + @Test + void testImplementorsFromCustomer() { + final ModelsContext modelsContext = buildModelContext( + PersonImpl.class, + CustomerImpl.class, + EmployeeImpl.class + ); + final ClassDetailsRegistry classDetailsRegistry = modelsContext.getClassDetailsRegistry(); + + final Set implementorsWithoutBase = classDetailsRegistry + .collectImplementors( Customer.class.getName(), false ); + assertThat( implementorsWithoutBase ).hasSize( 1 ); + assertThat( implementorsWithoutBase.stream().map( ClassDetails::getName ) ).contains( + CustomerImpl.class.getName() + ); + + final Set implementorsWithBase = classDetailsRegistry + .collectImplementors( Customer.class.getName(), false ); + assertThat( implementorsWithBase ).hasSize( 1 ); + assertThat( implementorsWithBase.stream().map( ClassDetails::getName ) ).contains( + CustomerImpl.class.getName() + ); + } +} diff --git a/hibernate-models/src/test/java/org/hibernate/models/testing/tests/classes/InheritanceTests.java b/hibernate-models/src/test/java/org/hibernate/models/testing/tests/classes/InheritanceTests.java index f6d414e..3b1e5ba 100644 --- a/hibernate-models/src/test/java/org/hibernate/models/testing/tests/classes/InheritanceTests.java +++ b/hibernate-models/src/test/java/org/hibernate/models/testing/tests/classes/InheritanceTests.java @@ -148,20 +148,19 @@ void testForEachDirectSubType() { final ClassDetailsRegistry classDetailsRegistry = modelsContext.getClassDetailsRegistry(); final List subTypes = new ArrayList<>(); - classDetailsRegistry.forEachDirectSubType( RootClass.class.getName(), subTypes::add ); + classDetailsRegistry.forEachDirectSubtype( RootClass.class.getName(), subTypes::add ); assertThat( subTypes ).hasSize( 1 ); subTypes.clear(); - classDetailsRegistry.forEachDirectSubType( TrunkClass.class.getName(), subTypes::add ); + classDetailsRegistry.forEachDirectSubtype( TrunkClass.class.getName(), subTypes::add ); assertThat( subTypes ).hasSize( 1 ); subTypes.clear(); - classDetailsRegistry.forEachDirectSubType( BranchClass.class.getName(), subTypes::add ); + classDetailsRegistry.forEachDirectSubtype( BranchClass.class.getName(), subTypes::add ); assertThat( subTypes ).hasSize( 1 ); subTypes.clear(); - classDetailsRegistry.forEachDirectSubType( LeafClass.class.getName(), subTypes::add ); + classDetailsRegistry.forEachDirectSubtype( LeafClass.class.getName(), subTypes::add ); assertThat( subTypes ).hasSize( 0 ); } - } diff --git a/hibernate-models/src/test/java/org/hibernate/models/testing/tests/classes/Person.java b/hibernate-models/src/test/java/org/hibernate/models/testing/tests/classes/Person.java new file mode 100644 index 0000000..413aba0 --- /dev/null +++ b/hibernate-models/src/test/java/org/hibernate/models/testing/tests/classes/Person.java @@ -0,0 +1,11 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.models.testing.tests.classes; + +/** + * @author Steve Ebersole + */ +public interface Person extends Thing { +} diff --git a/hibernate-models/src/test/java/org/hibernate/models/testing/tests/classes/PersonImpl.java b/hibernate-models/src/test/java/org/hibernate/models/testing/tests/classes/PersonImpl.java new file mode 100644 index 0000000..ac5a05a --- /dev/null +++ b/hibernate-models/src/test/java/org/hibernate/models/testing/tests/classes/PersonImpl.java @@ -0,0 +1,11 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.models.testing.tests.classes; + +/** + * @author Steve Ebersole + */ +public abstract class PersonImpl implements Person { +} diff --git a/hibernate-models/src/test/java/org/hibernate/models/testing/tests/classes/Thing.java b/hibernate-models/src/test/java/org/hibernate/models/testing/tests/classes/Thing.java new file mode 100644 index 0000000..b81c0a7 --- /dev/null +++ b/hibernate-models/src/test/java/org/hibernate/models/testing/tests/classes/Thing.java @@ -0,0 +1,11 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.models.testing.tests.classes; + +/** + * @author Steve Ebersole + */ +public interface Thing { +}