Skip to content

Commit 17402b1

Browse files
committed
#128 - Track interface implementors
1 parent bbda259 commit 17402b1

File tree

11 files changed

+497
-69
lines changed

11 files changed

+497
-69
lines changed

hibernate-models/src/main/java/org/hibernate/models/internal/AbstractClassDetailsRegistry.java

Lines changed: 74 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@
66

77
import java.util.ArrayList;
88
import java.util.Collections;
9+
import java.util.LinkedHashSet;
910
import java.util.List;
1011
import java.util.Map;
12+
import java.util.Set;
1113
import java.util.concurrent.ConcurrentHashMap;
14+
import java.util.function.Predicate;
1215

1316
import org.hibernate.models.UnknownClassException;
17+
import org.hibernate.models.internal.util.CollectionHelper;
1418
import org.hibernate.models.spi.ClassDetails;
1519
import org.hibernate.models.spi.ModelsContext;
1620
import org.hibernate.models.spi.TypeDetails;
@@ -31,18 +35,18 @@ public abstract class AbstractClassDetailsRegistry implements MutableClassDetail
3135
protected final Map<String, ClassDetails> classDetailsMap;
3236

3337
// subtype per type
34-
protected final Map<String, List<ClassDetails>> directSubTypeMap;
38+
protected final Map<String, Set<ClassDetails>> directSubTypeMap;
3539
// implementor by interface
36-
protected final Map<String, List<ClassDetails>> directImplementorMap;
40+
protected final Map<String, Set<ClassDetails>> directImplementorMap;
3741

3842
protected AbstractClassDetailsRegistry(ModelsContext context) {
3943
this( new ConcurrentHashMap<>(), new ConcurrentHashMap<>(), new ConcurrentHashMap<>(), context );
4044
}
4145

4246
protected AbstractClassDetailsRegistry(
4347
Map<String, ClassDetails> classDetailsMap,
44-
Map<String, List<ClassDetails>> directSubTypeMap,
45-
Map<String, List<ClassDetails>> directImplementorMap,
48+
Map<String, Set<ClassDetails>> directSubTypeMap,
49+
Map<String, Set<ClassDetails>> directImplementorMap,
4650
ModelsContext context) {
4751
this.classDetailsMap = classDetailsMap;
4852
this.directSubTypeMap = directSubTypeMap;
@@ -56,8 +60,17 @@ protected AbstractClassDetailsRegistry(
5660
}
5761

5862
@Override
59-
public List<ClassDetails> getDirectSubTypes(String superTypeName) {
60-
return directSubTypeMap.get( superTypeName );
63+
public List<ClassDetails> getDirectSubTypes(String typeName) {
64+
final Set<ClassDetails> directSubtypes = getDirectSubtypes( typeName );
65+
return CollectionHelper.isNotEmpty( directSubtypes )
66+
? new ArrayList<>( directSubtypes )
67+
: List.of();
68+
}
69+
70+
@Override
71+
public Set<ClassDetails> getDirectSubtypes(String typeName) {
72+
final Set<ClassDetails> directSubtypes = directSubTypeMap.get( typeName );
73+
return directSubtypes != null ? directSubtypes : Set.of();
6174
}
6275

6376
@Override
@@ -72,58 +85,81 @@ public void forEachDirectSubType(String superTypeName, ClassDetailsConsumer cons
7285
}
7386

7487
@Override
75-
public List<ClassDetails> getDirectImplementors(String interfaceName) {
76-
return directImplementorMap.get( interfaceName );
88+
public Set<ClassDetails> getDirectImplementors(String interfaceName) {
89+
final Set<ClassDetails> implementors = directImplementorMap.get( interfaceName );
90+
return implementors != null ? implementors : Set.of();
7791
}
7892

7993
@Override
8094
public void forEachDirectImplementor(String interfaceName, ClassDetailsConsumer consumer) {
81-
final List<ClassDetails> directImplementors = getDirectImplementors( interfaceName );
95+
final Set<ClassDetails> directImplementors = getDirectImplementors( interfaceName );
8296
if ( directImplementors != null ) {
8397
directImplementors.forEach( consumer::consume );
8498
}
8599
}
86100

87101
@Override
88-
public List<ClassDetails> findConcreteTypes(String base) {
89-
final List<ClassDetails> result = new ArrayList<>();
102+
public Set<ClassDetails> findConcreteTypes(String base, boolean includeBase) {
103+
final Set<ClassDetails> result = new LinkedHashSet<>();
104+
walkImplementors( base, includeBase, classDetails -> {
105+
if ( !classDetails.isAbstract() && !classDetails.isInterface() ) {
106+
result.add( classDetails );
107+
}
90108

91-
forEachDirectSubType( base, (subType) -> {
92-
if ( !subType.isAbstract() ) {
93-
result.add( subType );
109+
});
110+
return result;
111+
}
112+
113+
@Override
114+
public Set<ClassDetails> collectImplementors(String base, boolean includeBase, Predicate<ClassDetails> exclusions) {
115+
final Set<ClassDetails> result = new LinkedHashSet<>();
116+
walkImplementors( base, includeBase, classDetails -> {
117+
if ( exclusions == null || !exclusions.test( classDetails ) ) {
118+
result.add( classDetails );
94119
}
95-
collectConcreteTypesFromClass( subType, result::add );
96120
} );
121+
return result;
122+
}
97123

98-
forEachDirectImplementor( base, (implementor) -> {
99-
collectConcreteTypesFromInterfaceImplementor( implementor, result::add );
124+
@Override
125+
public void walkImplementors(String base, boolean includeBase, ClassDetailsConsumer consumer) {
126+
if ( includeBase ) {
127+
final ClassDetails baseDetails = resolveClassDetails( base );
128+
consumer.consume( baseDetails );
129+
}
130+
131+
forEachDirectSubType( base, (subType) -> {
132+
consumer.consume( subType );
133+
walkSubtypes( subType, consumer );
100134
} );
101135

102-
return result;
136+
forEachDirectImplementor( base, (implementor) -> {
137+
consumer.consume( implementor );
138+
walkInterfaceImplementors( implementor, consumer );
139+
} );
103140
}
104141

105-
private void collectConcreteTypesFromClass(ClassDetails base, ClassDetailsConsumer collector) {
142+
private void walkSubtypes(ClassDetails base, ClassDetailsConsumer consumer) {
106143
forEachDirectSubType( base.getName(), (subType) -> {
107-
if ( !subType.isAbstract() ) {
108-
collector.consume( subType );
109-
}
110-
collectConcreteTypesFromClass( subType, collector );
144+
consumer.consume( subType );
145+
walkSubtypes( subType, consumer );
111146
} );
112147
}
113148

114-
private void collectConcreteTypesFromInterfaceImplementor(ClassDetails implementor, ClassDetailsConsumer collector) {
149+
private void walkInterfaceImplementors(ClassDetails implementor, ClassDetailsConsumer consumer) {
115150
if ( implementor.isInterface() ) {
116151
// the direct interface implementor is itself an interface...
117152
forEachDirectImplementor( implementor.getName(), (implementorImplementor) -> {
118-
collectConcreteTypesFromInterfaceImplementor( implementor, collector );
153+
consumer.consume( implementorImplementor );
154+
walkInterfaceImplementors( implementorImplementor, consumer );
119155
} );
120156
}
121157
else {
122158
// the direct interface implementor is itself a class...
123-
if ( !implementor.isAbstract() ) {
124-
collector.consume( implementor );
125-
}
126-
collectConcreteTypesFromClass( implementor, collector );
159+
forEachDirectSubType( implementor.getName(), (subtype) -> {
160+
consumer.consume( subtype );
161+
walkSubtypes( subtype, consumer );
162+
} );
127163
}
128164
}
129165

@@ -180,10 +216,10 @@ public void addClassDetails(String name, ClassDetails classDetails) {
180216
classDetailsMap.put( name, classDetails );
181217

182218
if ( classDetails.getSuperClass() != null ) {
183-
List<ClassDetails> subTypes = directSubTypeMap.get( classDetails.getSuperClass().getName() );
219+
Set<ClassDetails> subTypes = directSubTypeMap.get( classDetails.getSuperClass().getName() );
184220
//noinspection Java8MapApi
185221
if ( subTypes == null ) {
186-
subTypes = new ArrayList<>();
222+
subTypes = new LinkedHashSet<>();
187223
directSubTypeMap.put( classDetails.getSuperClass().getName(), subTypes );
188224
}
189225
subTypes.add( classDetails );
@@ -192,9 +228,9 @@ public void addClassDetails(String name, ClassDetails classDetails) {
192228
final List<TypeDetails> implementedInterfaces = classDetails.getImplementedInterfaces();
193229
if ( implementedInterfaces != null ) {
194230
implementedInterfaces.forEach( (implementedInterface) -> {
195-
final List<ClassDetails> directImplementors = directImplementorMap.computeIfAbsent(
231+
final Set<ClassDetails> directImplementors = directImplementorMap.computeIfAbsent(
196232
implementedInterface.getName(),
197-
(interfaceName) -> new ArrayList<>()
233+
(interfaceName) -> new LinkedHashSet<>()
198234
);
199235
directImplementors.add( classDetails );
200236
} );
@@ -244,7 +280,11 @@ public Map<String, ClassDetails> getClassDetailsMap() {
244280
return Collections.unmodifiableMap( classDetailsMap );
245281
}
246282

247-
public Map<String, List<ClassDetails>> getDirectSubTypeMap() {
283+
public Map<String, Set<ClassDetails>> getDirectSubTypeMap() {
248284
return Collections.unmodifiableMap( directSubTypeMap );
249285
}
286+
287+
public Map<String, Set<ClassDetails>> getDirectImplementorMap() {
288+
return Collections.unmodifiableMap( directImplementorMap );
289+
}
250290
}

hibernate-models/src/main/java/org/hibernate/models/spi/ClassDetailsRegistry.java

Lines changed: 76 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
package org.hibernate.models.spi;
66

77
import java.util.List;
8+
import java.util.Set;
9+
import java.util.function.Predicate;
810

911
import org.hibernate.models.UnknownClassException;
1012

@@ -18,6 +20,12 @@
1820
* @author Steve Ebersole
1921
*/
2022
public interface ClassDetailsRegistry {
23+
/**
24+
* Resolves a managed-class by name. If there is currently no such registration,
25+
* one is created.
26+
*/
27+
ClassDetails resolveClassDetails(String name);
28+
2129
/**
2230
* Find the managed-class with the given {@code name}, if there is one.
2331
* Returns {@code null} if there are none registered with that name.
@@ -48,11 +56,18 @@ default ClassDetails getClassDetails(String name) {
4856
void forEachClassDetails(ClassDetailsConsumer consumer);
4957

5058
/**
51-
* Get the list of all direct subtypes for the named managed-class. Returns
52-
* {@code null} if there are none
59+
* Get the list of all direct subtypes for the named managed-class.
60+
*
61+
* @deprecated Use {@linkplain #getDirectSubtypes(String)} instead.
5362
*/
63+
@Deprecated
5464
List<ClassDetails> getDirectSubTypes(String superTypeName);
5565

66+
/**
67+
* Get the list of all direct subtypes for the named managed-class.
68+
*/
69+
Set<ClassDetails> getDirectSubtypes(String superTypeName);
70+
5671
/**
5772
* Visit each direct subtype of the named managed-class
5873
*/
@@ -61,12 +76,18 @@ default ClassDetails getClassDetails(String name) {
6176

6277
/**
6378
* Get the list of all direct implementors for the named interface.
64-
* Returns {@code null} if there are none.
79+
*
80+
* @return The direct implementors of the named interface.
81+
*
82+
* @apiNote Does not verify that {@code interfaceName} actually names an interface.
6583
*/
66-
List<ClassDetails> getDirectImplementors(String interfaceName);
84+
Set<ClassDetails> getDirectImplementors(String interfaceName);
6785

6886
/**
6987
* Visit each direct implementor of the named interface
88+
*
89+
* @apiNote Does not verify that {@code interfaceName} actually names an
90+
* interface. If it does not, no callbacks will happen.
7091
*/
7192
@SuppressWarnings("unused")
7293
void forEachDirectImplementor(String interfaceName, ClassDetailsConsumer consumer);
@@ -75,27 +96,71 @@ default ClassDetails getClassDetails(String name) {
7596
* Find ClassDetails for all non-abstract subtypes / implementors
7697
* of the given base (which might be a class or interface).
7798
*
99+
* @param base The name of the class/interface from which to start walking.
100+
*
101+
* @apiNote If {@code base} is a concrete type, it will also be returned.
102+
* @see #findConcreteTypes(String, boolean)
103+
*/
104+
default Set<ClassDetails> findConcreteTypes(String base) {
105+
return findConcreteTypes( base, true );
106+
}
107+
108+
/**
109+
* Find ClassDetails for all non-abstract subtypes / implementors
110+
* of the given base (which might be a class or interface).
111+
*
112+
* @param base The name of the class/interface from which to start walking.
113+
* @param includeBase Whether to include {@code base} if it is concrete type.
114+
*
78115
* @see #getDirectSubTypes
79116
* @see #getDirectImplementors
80117
*/
81-
List<ClassDetails> findConcreteTypes(String base);
118+
Set<ClassDetails> findConcreteTypes(String base, boolean includeBase);
82119

83120
/**
84-
* Resolves a managed-class by name. If there is currently no such registration,
85-
* one is created.
121+
* Walks the inheritance tree downward, starting from {@code base},
122+
* calling the consumer for each subclass and interface which is directly
123+
* an implementor.
124+
*
125+
* @param base The type from which to start.
126+
* @param includeBase Whether to include {@code base} if it is concrete type.
127+
* @param consumer The callback.
86128
*/
87-
ClassDetails resolveClassDetails(String name);
129+
void walkImplementors(String base, boolean includeBase, ClassDetailsConsumer consumer);
88130

89-
default <S> S as(Class<S> type) {
90-
//noinspection unchecked
91-
return (S) this;
131+
/**
132+
* Walks the inheritance tree, starting from {@code base}, "downward"
133+
* calling the consumer for each subclass and interface which is directly
134+
* an implementor.
135+
*
136+
* @param base The type from which to start.
137+
* @param includeBase Whether to include {@code base} if it is concrete type.
138+
*/
139+
default Set<ClassDetails> collectImplementors(String base, boolean includeBase) {
140+
return collectImplementors( base, includeBase, null );
92141
}
93142

143+
/**
144+
* Walks the inheritance tree, starting from {@code base}, "downward"
145+
* calling the consumer for each subclass and interface which is directly
146+
* an implementor.
147+
*
148+
* @param base The type from which to start.
149+
* @param includeBase Whether to include {@code base} if it is concrete type.
150+
* @param exclusions Exclusive check to filter ClassDetails out of the result.
151+
*/
152+
Set<ClassDetails> collectImplementors(String base, boolean includeBase, Predicate<ClassDetails> exclusions);
153+
94154
/**
95155
* Access to the ClassDetailsBuilder used in this registry
96156
*/
97157
ClassDetailsBuilder getClassDetailsBuilder();
98158

159+
default <S> S as(Class<S> type) {
160+
//noinspection unchecked
161+
return (S) this;
162+
}
163+
99164
@FunctionalInterface
100165
interface ClassDetailsConsumer {
101166
void consume(ClassDetails classDetails);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright: Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.models.testing.tests.classes;
6+
7+
/**
8+
* @author Steve Ebersole
9+
*/
10+
public interface Customer extends Person {
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright: Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.models.testing.tests.classes;
6+
7+
/**
8+
* @author Steve Ebersole
9+
*/
10+
public class CustomerImpl extends PersonImpl implements Customer {
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright: Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.models.testing.tests.classes;
6+
7+
/**
8+
* @author Steve Ebersole
9+
*/
10+
public interface Employee extends Person {
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright: Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.models.testing.tests.classes;
6+
7+
/**
8+
* @author Steve Ebersole
9+
*/
10+
public class EmployeeImpl extends PersonImpl implements Employee {
11+
}

0 commit comments

Comments
 (0)