Skip to content

#128 - Track interface implementors #129

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 18, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -30,18 +35,22 @@ public abstract class AbstractClassDetailsRegistry implements MutableClassDetail
protected final Map<String, ClassDetails> classDetailsMap;

// subtype per type
protected final Map<String, List<ClassDetails>> subTypeClassDetailsMap;
protected final Map<String, Set<ClassDetails>> directSubTypeMap;
// implementor by interface
protected final Map<String, Set<ClassDetails>> directImplementorMap;

protected AbstractClassDetailsRegistry(ModelsContext context) {
this( new ConcurrentHashMap<>(), new ConcurrentHashMap<>(), context );
this( new ConcurrentHashMap<>(), new ConcurrentHashMap<>(), new ConcurrentHashMap<>(), context );
}

protected AbstractClassDetailsRegistry(
Map<String, ClassDetails> classDetailsMap,
Map<String, List<ClassDetails>> subTypeClassDetailsMap,
Map<String, Set<ClassDetails>> directSubTypeMap,
Map<String, Set<ClassDetails>> 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 );
Expand All @@ -51,13 +60,22 @@ protected AbstractClassDetailsRegistry(
}

@Override
public List<ClassDetails> getDirectSubTypes(String superTypeName) {
return subTypeClassDetailsMap.get( superTypeName );
public List<ClassDetails> getDirectSubTypes(String typeName) {
final Set<ClassDetails> directSubtypes = getDirectSubtypes( typeName );
return CollectionHelper.isNotEmpty( directSubtypes )
? new ArrayList<>( directSubtypes )
: List.of();
}

@Override
public void forEachDirectSubType(String superTypeName, ClassDetailsConsumer consumer) {
final List<ClassDetails> directSubTypes = getDirectSubTypes( superTypeName );
public Set<ClassDetails> getDirectSubtypes(String typeName) {
final Set<ClassDetails> directSubtypes = directSubTypeMap.get( typeName );
return directSubtypes != null ? directSubtypes : Set.of();
}

@Override
public void forEachDirectSubtype(String typeName, ClassDetailsConsumer consumer) {
final List<ClassDetails> directSubTypes = getDirectSubTypes( typeName );
if ( directSubTypes == null ) {
return;
}
Expand All @@ -66,6 +84,85 @@ public void forEachDirectSubType(String superTypeName, ClassDetailsConsumer cons
}
}

@Override
public Set<ClassDetails> getDirectImplementors(String interfaceName) {
final Set<ClassDetails> implementors = directImplementorMap.get( interfaceName );
return implementors != null ? implementors : Set.of();
}

@Override
public void forEachDirectImplementor(String interfaceName, ClassDetailsConsumer consumer) {
final Set<ClassDetails> directImplementors = getDirectImplementors( interfaceName );
if ( directImplementors != null ) {
directImplementors.forEach( consumer::consume );
}
}

@Override
public Set<ClassDetails> findConcreteTypes(String base, boolean includeBase) {
final Set<ClassDetails> result = new LinkedHashSet<>();
walkImplementors( base, includeBase, classDetails -> {
if ( !classDetails.isAbstract() && !classDetails.isInterface() ) {
result.add( classDetails );
}

});
return result;
}

@Override
public Set<ClassDetails> collectImplementors(String base, boolean includeBase, Predicate<ClassDetails> exclusions) {
final Set<ClassDetails> 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 );
Expand Down Expand Up @@ -119,14 +216,25 @@ public void addClassDetails(String name, ClassDetails classDetails) {
classDetailsMap.put( name, classDetails );

if ( classDetails.getSuperClass() != null ) {
List<ClassDetails> subTypes = subTypeClassDetailsMap.get( classDetails.getSuperClass().getName() );
Set<ClassDetails> 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<TypeDetails> implementedInterfaces = classDetails.getImplementedInterfaces();
if ( implementedInterfaces != null ) {
implementedInterfaces.forEach( (implementedInterface) -> {
final Set<ClassDetails> directImplementors = directImplementorMap.computeIfAbsent(
implementedInterface.getName(),
(interfaceName) -> new LinkedHashSet<>()
);
directImplementors.add( classDetails );
} );
}
}

@Override
Expand Down Expand Up @@ -172,7 +280,11 @@ public Map<String, ClassDetails> getClassDetailsMap() {
return Collections.unmodifiableMap( classDetailsMap );
}

public Map<String, List<ClassDetails>> getSubTypeClassDetailsMap() {
return Collections.unmodifiableMap( subTypeClassDetailsMap );
public Map<String, Set<ClassDetails>> getDirectSubTypeMap() {
return Collections.unmodifiableMap( directSubTypeMap );
}

public Map<String, Set<ClassDetails>> getDirectImplementorMap() {
return Collections.unmodifiableMap( directImplementorMap );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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.
Expand Down Expand Up @@ -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<ClassDetails> getDirectSubTypes(String superTypeName);

/**
* Get the list of all direct subtypes for the named managed-class.
*/
Set<ClassDetails> 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> S as(Class<S> 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<ClassDetails> 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<ClassDetails> 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<ClassDetails> 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<ClassDetails> 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<ClassDetails> collectImplementors(String base, boolean includeBase, Predicate<ClassDetails> exclusions);

/**
* Access to the ClassDetailsBuilder used in this registry
*/
ClassDetailsBuilder getClassDetailsBuilder();

@SuppressWarnings("unchecked")
default <S> S as(Class<S> 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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
}
Original file line number Diff line number Diff line change
@@ -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 {
}
Original file line number Diff line number Diff line change
@@ -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 {
}
Loading