Skip to content

Commit 221d803

Browse files
committed
#26 - Create Function counterparts to some of the iteration methods
1 parent 7556060 commit 221d803

File tree

7 files changed

+270
-44
lines changed

7 files changed

+270
-44
lines changed

README.adoc

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,25 @@ library, which suffered from a number of shortcomings.
1515

1616
=== Annotations
1717

18-
Hibernate Models defines an actual descriptor for annotations called `AnnotationDescriptor` which provides access
19-
to information (whether it is repeatable, etc.) about the annotation class. These descriptors are available from the
20-
`AnnotationDescriptorRegistry`.
18+
The modeling of annotations in hibernate-models is defined by a few main actors:
19+
20+
AnnotationDescriptor:: Extended information about an annotation class
21+
AnnotationTarget:: Something (Class, Method, ...) where an annotation can be used
22+
AnnotationUsage:: Specific usage of an annotation on a target
23+
AttributeDescriptor:: Details about an annotation attribute, including it's `ValueTypeDescriptor`
24+
ValueTypeDescriptor:: Describes an allowable type for annotation attributes - ints, enums, etc. Provides the capability to manipulate these values (create them, wrap them, unwrap them, etc).
25+
AnnotationDescriptorRegistry:: registry of `AnnotationDescriptor` references
26+
27+
28+
=== Model
29+
30+
These mostly model Java constructs such as Class, Method, etc. but adds the capability
31+
for these to be dynamic models (no physical Class).
32+
33+
ClassDetails:: Think `java.lang.Class`
34+
TypeDetails:: Think `java.lang.reflect.Type`
35+
MemberDetails:: Think `java.lang.reflect.Member`
36+
FieldDetails:: Think `java.lang.reflect.Field`
37+
MethodDetails:: Think `java.lang.reflect.Method`
38+
RecordComponentDetails:: Think `java.lang.reflect.RecordComponent`
39+
ClassDetailsRegistry:: registry of `ClassDetails` references

src/main/java/org/hibernate/models/internal/AnnotationTargetSupport.java

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@
77
package org.hibernate.models.internal;
88

99
import java.lang.annotation.Annotation;
10+
import java.util.ArrayList;
1011
import java.util.Collection;
1112
import java.util.List;
1213
import java.util.Map;
1314
import java.util.function.Consumer;
1415

1516
import org.hibernate.models.spi.AnnotationDescriptor;
17+
import org.hibernate.models.spi.AnnotationDescriptorRegistry;
1618
import org.hibernate.models.spi.AnnotationUsage;
1719
import org.hibernate.models.spi.SourceModelBuildingContext;
1820

@@ -33,6 +35,23 @@ default <A extends Annotation> boolean hasAnnotationUsage(Class<A> type) {
3335
return getUsageMap().containsKey( type );
3436
}
3537

38+
@Override
39+
default <A extends Annotation> boolean hasRepeatableAnnotationUsage(Class<A> type) {
40+
final boolean containsDirectly = getUsageMap().containsKey( type );
41+
if ( containsDirectly ) {
42+
return true;
43+
}
44+
45+
final AnnotationDescriptorRegistry descriptorRegistry = getBuildingContext().getAnnotationDescriptorRegistry();
46+
final AnnotationDescriptor<A> descriptor = descriptorRegistry.getDescriptor( type );
47+
if ( descriptor.isRepeatable() ) {
48+
// e.g. caller asks about NamedQuery... let's also check for NamedQueries (which implies NamedQuery)
49+
return getUsageMap().containsKey( descriptor.getRepeatableContainer().getAnnotationType() );
50+
}
51+
52+
return false;
53+
}
54+
3655
@Override
3756
default <A extends Annotation> AnnotationUsage<A> getAnnotationUsage(AnnotationDescriptor<A> descriptor) {
3857
return AnnotationUsageHelper.getUsage( descriptor, getUsageMap() );
@@ -45,21 +64,19 @@ default <A extends Annotation> AnnotationUsage<A> getAnnotationUsage(Class<A> an
4564

4665
@Override
4766
default <A extends Annotation> AnnotationUsage<A> locateAnnotationUsage(Class<A> annotationType) {
48-
final Map<Class<? extends Annotation>, AnnotationUsage<? extends Annotation>> localUsageMap = getUsageMap();
49-
5067
// e.g., locate `@Nationalized`
5168
// 1. direct - look for `Nationalized.class` in the usage map (direct local use on the target)
5269
// 2. "meta annotations" - for each local usage, check that annotation's annotations for `Nationalized.class` (one level deep)
5370

5471

5572
// first, see if we can find it directly...
56-
//noinspection unchecked
57-
final AnnotationUsage<A> direct = (AnnotationUsage<A>) localUsageMap.get( annotationType );
58-
if ( direct != null ) {
59-
return direct;
73+
final AnnotationUsage<A> localUsage = getAnnotationUsage( annotationType );
74+
if ( localUsage != null ) {
75+
return null;
6076
}
6177

6278
// check as "meta annotations" (annotations on our annotations)...
79+
final Map<Class<? extends Annotation>, AnnotationUsage<? extends Annotation>> localUsageMap = getUsageMap();
6380
for ( Map.Entry<Class<? extends Annotation>, AnnotationUsage<? extends Annotation>> usageEntry : localUsageMap.entrySet() ) {
6481
final AnnotationUsage<? extends Annotation> usage = usageEntry.getValue();
6582
if ( annotationType.equals( usage.getAnnotationType() ) ) {
@@ -97,6 +114,19 @@ default <X extends Annotation> void forEachAnnotationUsage(Class<X> type, Consum
97114
);
98115
}
99116

117+
@Override
118+
default <A extends Annotation> List<AnnotationUsage<? extends Annotation>> getMetaAnnotated(Class<A> metaAnnotationType) {
119+
final AnnotationDescriptorRegistry descriptorRegistry = getBuildingContext().getAnnotationDescriptorRegistry();
120+
final List<AnnotationUsage<?>> usages = new ArrayList<>();
121+
forAllAnnotationUsages( (usage) -> {
122+
final AnnotationUsage<? extends Annotation> metaUsage = usage.getAnnotationDescriptor().getAnnotationUsage( metaAnnotationType );
123+
if ( metaUsage != null ) {
124+
usages.add( usage );
125+
}
126+
} );
127+
return usages;
128+
}
129+
100130
@Override
101131
default <X extends Annotation> AnnotationUsage<X> getNamedAnnotationUsage(Class<X> type, String matchName) {
102132
return getNamedAnnotationUsage( getBuildingContext().getAnnotationDescriptorRegistry().getDescriptor( type ), matchName );

src/main/java/org/hibernate/models/internal/AnnotationUsageHelper.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,9 @@ public static <A extends Annotation> List<AnnotationUsage<A>> getRepeatedUsages(
7676
final List<AnnotationUsage<A>> repetitions = containerUsage.getAttributeValue( "value" );
7777
if ( CollectionHelper.isNotEmpty( repetitions ) ) {
7878
if ( usage != null ) {
79-
// we can have both when repeatable + inherited are mixed
8079
final ArrayList<AnnotationUsage<A>> combined = new ArrayList<>( repetitions );
81-
combined.add( usage );
80+
// prepend the singular usage
81+
combined.add( 0, usage );
8282
return combined;
8383
}
8484
return repetitions;

src/main/java/org/hibernate/models/internal/SimpleClassDetails.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,11 @@ public <A extends Annotation> boolean hasAnnotationUsage(Class<A> type) {
145145
return false;
146146
}
147147

148+
@Override
149+
public <A extends Annotation> boolean hasRepeatableAnnotationUsage(Class<A> type) {
150+
return false;
151+
}
152+
148153
@Override
149154
public <A extends Annotation> AnnotationUsage<A> getAnnotationUsage(AnnotationDescriptor<A> descriptor) {
150155
return null;
@@ -174,6 +179,11 @@ public <A extends Annotation> List<AnnotationUsage<A>> getRepeatedAnnotationUsag
174179
public <X extends Annotation> void forEachAnnotationUsage(Class<X> type, Consumer<AnnotationUsage<X>> consumer) {
175180
}
176181

182+
@Override
183+
public <A extends Annotation> List<AnnotationUsage<? extends Annotation>> getMetaAnnotated(Class<A> metaAnnotationType) {
184+
return Collections.emptyList();
185+
}
186+
177187
@Override
178188
public <X extends Annotation> AnnotationUsage<X> getNamedAnnotationUsage(
179189
AnnotationDescriptor<X> type,

src/main/java/org/hibernate/models/internal/jdk/AnnotationDescriptorOrmImpl.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ public <X extends Annotation> boolean hasAnnotationUsage(Class<X> type) {
9898
return false;
9999
}
100100

101+
@Override
102+
public <A extends Annotation> boolean hasRepeatableAnnotationUsage(Class<A> type) {
103+
return false;
104+
}
105+
101106
@Override
102107
public <X extends Annotation> AnnotationUsage<X> getAnnotationUsage(AnnotationDescriptor<X> descriptor) {
103108
// there are none
@@ -140,6 +145,11 @@ public <X extends Annotation> void forEachAnnotationUsage(Class<X> type, Consume
140145
// there are none
141146
}
142147

148+
public <X extends Annotation> List<AnnotationUsage<? extends Annotation>> getMetaAnnotated(Class<X> metaAnnotationType) {
149+
// there are none
150+
return Collections.emptyList();
151+
}
152+
143153
@Override
144154
public <X extends Annotation> AnnotationUsage<X> getNamedAnnotationUsage(
145155
AnnotationDescriptor<X> type,

src/main/java/org/hibernate/models/spi/AnnotationTarget.java

Lines changed: 81 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import java.lang.annotation.ElementType;
1111
import java.lang.annotation.Repeatable;
1212
import java.lang.annotation.Target;
13-
import java.util.ArrayList;
1413
import java.util.Collection;
1514
import java.util.EnumSet;
1615
import java.util.List;
@@ -19,7 +18,9 @@
1918
import org.hibernate.models.AnnotationAccessException;
2019

2120
/**
22-
* Abstraction of {@linkplain java.lang.reflect.AnnotatedElement}
21+
* Abstract for something where an annotation can be {@linkplain AnnotationUsage used}.
22+
*
23+
* @see java.lang.reflect.AnnotatedElement
2324
*
2425
* @author Steve Ebersole
2526
*/
@@ -36,24 +37,50 @@ public interface AnnotationTarget {
3637

3738
/**
3839
* Access to all the annotations used on this target.
40+
*
41+
* @apiNote This returns the usages directly available on the target; it does not
42+
* expand repeatable containers (e.g. NamedQueries -> *NamedQuery).
3943
*/
4044
Collection<AnnotationUsage<?>> getAllAnnotationUsages();
4145

46+
/**
47+
* Allows to visit every annotation on the target.
48+
*
49+
* @apiNote Only visits the usages directly available on the target; it does not
50+
* visit across repeatable containers (e.g. NamedQueries -> *NamedQuery).
51+
*/
4252
default void forAllAnnotationUsages(Consumer<AnnotationUsage<?>> consumer) {
4353
getAllAnnotationUsages().forEach( consumer );
4454
}
4555

4656
/**
47-
* Whether the given annotation is used on this target
57+
* Whether the given annotation is used on this target.
58+
*
59+
* @see #hasRepeatableAnnotationUsage
60+
*
61+
* @apiNote This form does not check across repeatable containers. E.g., calling this
62+
* method with {@code NamedQuery} will return {@code false} when the target directly
63+
* has a NamedQueries.
4864
*/
4965
<A extends Annotation> boolean hasAnnotationUsage(Class<A> type);
5066

67+
/**
68+
* Whether the given annotation is used on this target.
69+
*
70+
* @see #hasAnnotationUsage
71+
*
72+
* @apiNote This forms does check across repeatable containers. E.g., calling this
73+
* method with {@code NamedQuery} will return {@code true} when the target directly
74+
* has a NamedQueries.
75+
*/
76+
<A extends Annotation> boolean hasRepeatableAnnotationUsage(Class<A> type);
77+
5178
/**
5279
* Get the usage of the given annotation on this target.
5380
* <p/>
5481
* For {@linkplain Repeatable repeatable} annotation types (e.g. {@code @NamedQuery}), this method will either-<ul>
5582
* <li>
56-
* if the repeatable annotation itself is present, it is returned.
83+
* if a single repeatable annotation itself is present, it is returned.
5784
* </li>
5885
* <li>
5986
* if the {@linkplain Repeatable#value() "containing annotation"} is present (e.g. {@code @NamedQueries}), <ul>
@@ -67,15 +94,14 @@ default void forAllAnnotationUsages(Consumer<AnnotationUsage<?>> consumer) {
6794
* </li>
6895
* </ul>
6996
* <p/>
70-
* For annotations which can {@linkplain ElementType#ANNOTATION_TYPE target annotations},
71-
* all annotations on this target will be checked as well.
97+
* For also checking across meta-annotations, see {@linkplain #locateAnnotationUsage(Class)}.
7298
*
7399
* @return The usage or {@code null}
74100
*/
75101
<A extends Annotation> AnnotationUsage<A> getAnnotationUsage(AnnotationDescriptor<A> descriptor);
76102

77103
/**
78-
* Helper form of {@link #getAnnotationUsage(AnnotationDescriptor)}
104+
* Form of {@link #getAnnotationUsage(AnnotationDescriptor)} accepting the annotation {@linkplain Class}
79105
*/
80106
<A extends Annotation> AnnotationUsage<A> getAnnotationUsage(Class<A> type);
81107

@@ -87,26 +113,17 @@ default void forAllAnnotationUsages(Consumer<AnnotationUsage<?>> consumer) {
87113

88114
/**
89115
* Get all usages of the specified {@code annotationType} in this scope.
90-
* <p/>
91-
* For {@linkplain Repeatable repeatable} annotation types (e.g. {@code @NamedQuery}) -<ul>
92-
* <li>
93-
* if the repeatable annotation itself is present, a singleton list containing that single usage is returned
94-
* </li>
95-
* <li>
96-
* if the {@linkplain Repeatable#value() "containing annotation"} (e.g. {@code @NamedQueries}) is present,
97-
* the contained repeatable usages are extracted from the container and returned as a list
98-
* </li>
99-
* <li>
100-
* Otherwise, an empty list is returned.
101-
* </li>
102-
* </ul>
103116
*
104-
* @apiNote If the passed annotation type is not repeatable, an empty list is returned.
117+
* @apiNote For {@linkplain Repeatable repeatable} annotation types (e.g. {@code @NamedQuery}),
118+
* the returned list will contain the union of <ol>
119+
* <li>the singular {@code @NamedQuery} usage</li>
120+
* <li>the nested {@code @NamedQuery} usages from the {@code @NamedQueries} usage</li>
121+
* </ol>
105122
*/
106123
<A extends Annotation> List<AnnotationUsage<A>> getRepeatedAnnotationUsages(AnnotationDescriptor<A> type);
107124

108125
/**
109-
* Helper form of {@linkplain #getRepeatedAnnotationUsages(AnnotationDescriptor)}
126+
* Form of {@linkplain #getRepeatedAnnotationUsages(AnnotationDescriptor)} accepting the annotation {@linkplain Class}
110127
*/
111128
<A extends Annotation> List<AnnotationUsage<A>> getRepeatedAnnotationUsages(Class<A> type);
112129

@@ -128,7 +145,7 @@ default <X extends Annotation> void forEachAnnotationUsage(
128145
}
129146

130147
/**
131-
* Helper form of {@link #forEachAnnotationUsage(AnnotationDescriptor, Consumer)}
148+
* Form of {@link #forEachAnnotationUsage(AnnotationDescriptor, Consumer)} accepting the annotation {@linkplain Class}
132149
*/
133150
<X extends Annotation> void forEachAnnotationUsage(Class<X> type, Consumer<AnnotationUsage<X>> consumer);
134151

@@ -155,17 +172,11 @@ default <X extends Annotation> void forEachAnnotationUsage(
155172
* </pre>
156173
* a call to this method passing {@code TheMeta} on {@code ClassDetails(TheClass)} will return
157174
* the usage of {@code @TheAnnotation} on {@code TheClass}.
175+
*
176+
* @apiNote This method does not check across repeatable containers. Although the return is a List, we
177+
* are functionally wanting just the unique ones.
158178
*/
159-
default <A extends Annotation> List<AnnotationUsage<? extends Annotation>> getMetaAnnotated(Class<A> metaAnnotationType) {
160-
final List<AnnotationUsage<?>> usages = new ArrayList<>();
161-
forAllAnnotationUsages( (usage) -> {
162-
final AnnotationUsage<? extends Annotation> metaUsage = usage.getAnnotationDescriptor().getAnnotationUsage( metaAnnotationType );
163-
if ( metaUsage != null ) {
164-
usages.add( usage );
165-
}
166-
} );
167-
return usages;
168-
}
179+
<A extends Annotation> List<AnnotationUsage<? extends Annotation>> getMetaAnnotated(Class<A> metaAnnotationType);
169180

170181
/**
171182
* Get a usage of the given annotation {@code type} whose {@code attributeToMatch} attribute value
@@ -208,6 +219,43 @@ <X extends Annotation> AnnotationUsage<X> getNamedAnnotationUsage(
208219
String matchName,
209220
String attributeToMatch);
210221

222+
/**
223+
* Functional contract to process an annotation and return a value.
224+
*
225+
* @param <T> The type of the value being returned.
226+
*/
227+
@FunctionalInterface
228+
interface AnnotationUsageProcessor<T> {
229+
/**
230+
* The processed value. May be {@code null} to indicate a "no match"
231+
*/
232+
T process(AnnotationUsage<? extends Annotation> annotationUsage);
233+
}
234+
235+
/**
236+
* Returns a "matching value" using the passed {@code processor} from the
237+
* annotations, of the passed {@code annotationType}, used on the target.
238+
*
239+
* @apiNote In the case of repeatable annotations, the first usage for which
240+
* the passed {@code processor} does not return {@code null} will be returned.
241+
*
242+
* @return The matching value or {@code null}
243+
*
244+
* @param <T> The type of the value being returned.
245+
* @param <A> The type of annotations to check
246+
*/
247+
default <T, A extends Annotation> T fromAnnotations(
248+
Class<A> annotationType,
249+
AnnotationUsageProcessor<T> processor) {
250+
final List<AnnotationUsage<A>> annotationUsages = getRepeatedAnnotationUsages( annotationType );
251+
for ( AnnotationUsage<A> annotationUsage : annotationUsages ) {
252+
final T result = processor.process( annotationUsage );
253+
if ( result != null ) {
254+
return result;
255+
}
256+
}
257+
return null;
258+
}
211259

212260
/**
213261
* Subset of {@linkplain ElementType annotation targets} supported for mapping annotations

0 commit comments

Comments
 (0)