Skip to content

Commit 1f43dec

Browse files
committed
#932 Support executable elements in reference conditions
1 parent 4f87aec commit 1f43dec

File tree

26 files changed

+232
-108
lines changed

26 files changed

+232
-108
lines changed

hartshorn-inject-configurations/src/main/java/org/dockbox/hartshorn/inject/condition/Condition.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
*
2828
* @author Guus Lieben
2929
*/
30-
public sealed interface Condition permits IntrospectionCondition, TypeReferenceCondition {
30+
public sealed interface Condition permits IntrospectionCondition, ReferenceCondition {
3131

3232
/**
3333
* Returns a {@link ConditionResult} that describes whether the condition is matched.

hartshorn-inject-configurations/src/main/java/org/dockbox/hartshorn/inject/condition/ConditionContext.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
* @author Guus Lieben
3232
*/
3333
public sealed class ConditionContext extends DefaultContext
34-
permits IntrospectedConditionContext, TypeReferenceConditionContext {
34+
permits IntrospectedConditionContext, ReferenceConditionContext {
3535

3636
private final ConditionDeclaration condition;
3737

hartshorn-inject-configurations/src/main/java/org/dockbox/hartshorn/inject/condition/ConditionMatcher.java

Lines changed: 19 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,13 @@
2323
import org.dockbox.hartshorn.inject.InjectionCapableApplication;
2424
import org.dockbox.hartshorn.inject.ObjectFactory;
2525
import org.dockbox.hartshorn.inject.ReflectionObjectFactory;
26-
import org.dockbox.hartshorn.util.introspect.scan.ClassReference;
27-
import org.dockbox.hartshorn.util.introspect.scan.TypeReference;
2826
import org.dockbox.hartshorn.util.introspect.view.AnnotatedElementView;
2927
import org.dockbox.hartshorn.util.introspect.view.EnclosableView;
30-
import org.dockbox.hartshorn.util.introspect.view.TypeView;
3128
import org.dockbox.hartshorn.util.option.Option;
3229
import org.dockbox.hartshorn.util.types.ClassFileUtilities;
3330

3431
import java.lang.classfile.Annotation;
35-
import java.lang.classfile.ClassModel;
32+
import java.lang.classfile.AttributedElement;
3633
import java.util.ArrayDeque;
3734
import java.util.LinkedList;
3835
import java.util.List;
@@ -117,10 +114,10 @@ public ConditionMatcher includeEnclosingConditions(boolean includeEnclosingCondi
117114
* enclosed element to the most enclosed element (the given element). This means that
118115
* class-level conditions are evaluated before method-level conditions.
119116
*
120-
* <p>{@link TypeReferenceCondition reference conditions} are also matched for any type views
121-
* encountered during the matching process. Note that this may cause issues with class loading
122-
* if the type references point to classes that are not available at runtime. In such cases,
123-
* {@link #match(TypeReference, ContextView...)} should be used directly to avoid class
117+
* <p>{@link ReferenceCondition reference conditions} are also matched for any reference
118+
* views encountered during the matching process. Note that this may cause issues with class
119+
* loading if the references point to classes that are not available at runtime. In such cases,
120+
* {@link #match(AttributedElement, ContextView...)} should be used directly to avoid class
124121
* loading.
125122
*
126123
* @param annotatedElementContext the annotated element to match against
@@ -149,8 +146,9 @@ public boolean match(AnnotatedElementView annotatedElementContext, ContextView..
149146
return false;
150147
}
151148

152-
if (elementView instanceof TypeView<?> typeView
153-
&& !this.match(new ClassReference(typeView.type()), contexts)) {
149+
if (!elementView.classFileElement()
150+
.ofType(AttributedElement.class)
151+
.test(element -> this.match(element, contexts), true)) {
154152
return false;
155153
}
156154
}
@@ -159,35 +157,28 @@ public boolean match(AnnotatedElementView annotatedElementContext, ContextView..
159157

160158
/**
161159
* Matches the {@link RequiresReferenceCondition} annotations of the given
162-
* {@link TypeReference}, providing any additional {@link ContextView} instances to the
160+
* {@link AttributedElement}, providing any additional {@link ContextView} instances to the
163161
* {@link ConditionContext} that is used to match the
164162
* {@link Condition condition implementations}. If any of the conditions do not match, this
165163
* method will return {@code false}. If all conditions match, this method will return
166164
* {@code true}.
167165
*
168-
* @param typeReference the type reference to match against
166+
* @param element the attributed element to match against
169167
* @param contexts the additional contexts to provide to the condition context
170-
*
171168
* @return {@code true} if all conditions match, {@code false} otherwise
172169
*/
173-
public boolean match(TypeReference typeReference, ContextView... contexts) {
174-
ClassModel classModel = ClassFileUtilities.getClassModel(
175-
typeReference.qualifiedName()
176-
).orElseThrow(() -> new IllegalStateException(
177-
"Could not load class model for type reference: " + typeReference
178-
));
170+
public boolean match(AttributedElement element, ContextView... contexts) {
179171
List<Annotation> annotations = ClassFileUtilities.getMetaAnnotations(
180-
classModel,
181-
RequiresReferenceCondition.class
172+
element,
173+
RequiresReferenceCondition.class
182174
);
183175
for (Annotation annotation : annotations) {
184176
var declaration = ReferenceConditionDeclaration.createFromMetaAnnotation(annotation);
185-
TypeReferenceConditionContext conditionContext =
186-
new TypeReferenceConditionContext(
187-
declaration,
188-
typeReference,
189-
classModel
190-
);
177+
ReferenceConditionContext conditionContext =
178+
new ReferenceConditionContext(
179+
declaration,
180+
element
181+
);
191182
if (!this.matchConditionContext(conditionContext, declaration, contexts)) {
192183
return false;
193184
}
@@ -251,7 +242,7 @@ private boolean matchConditionContext(
251242
// Application may still be starting, thus fallback should be used.
252243
final ObjectFactory objectFactory;
253244
if (application == null) {
254-
assert context instanceof TypeReferenceConditionContext : """
245+
assert context instanceof ReferenceConditionContext : """
255246
Expected type reference condition context when application is starting.
256247
Introspection-capable condition contexts require at least a partially
257248
initialized application.

hartshorn-inject-configurations/src/main/java/org/dockbox/hartshorn/inject/condition/TypeReferenceCondition.java renamed to hartshorn-inject-configurations/src/main/java/org/dockbox/hartshorn/inject/condition/ReferenceCondition.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,18 @@
1717
package org.dockbox.hartshorn.inject.condition;
1818

1919
/**
20-
* A {@link Condition} that matches based on a type reference, without requiring the type to be
20+
* A {@link Condition} that matches based on a reference, without requiring the element to be
2121
* loaded. This has the benefit of being able to evaluate conditions without causing class loading
2222
* early in the application lifecycle. The drawback is that the condition implementation must work
23-
* with type references with limited information (compared to loaded classes, which support full
23+
* with references with limited information (compared to loaded elements, which support full
2424
* introspection).
2525
*
2626
* @since 0.7.0
2727
*
2828
* @author Guus Lieben
2929
*/
3030
@FunctionalInterface
31-
public non-sealed interface TypeReferenceCondition extends Condition {
31+
public non-sealed interface ReferenceCondition extends Condition {
3232

3333
/**
3434
* Returns a {@link ConditionResult} that describes whether the condition is matched.
@@ -39,11 +39,11 @@ public non-sealed interface TypeReferenceCondition extends Condition {
3939
*/
4040
@Override
4141
default ConditionResult matches(ConditionContext context) {
42-
if (context instanceof TypeReferenceConditionContext referenceConditionContext) {
42+
if (context instanceof ReferenceConditionContext referenceConditionContext) {
4343
return this.matches(referenceConditionContext);
4444
}
4545
return ConditionResult.notMatched(
46-
"ConditionContext is not an instance of TypeReferenceConditionContext"
46+
"ConditionContext is not an instance of ReferenceConditionContext"
4747
);
4848
}
4949

@@ -53,5 +53,5 @@ default ConditionResult matches(ConditionContext context) {
5353
* @param context the type reference condition context
5454
* @return the condition result
5555
*/
56-
ConditionResult matches(TypeReferenceConditionContext context);
56+
ConditionResult matches(ReferenceConditionContext context);
5757
}

hartshorn-inject-configurations/src/main/java/org/dockbox/hartshorn/inject/condition/TypeReferenceConditionContext.java renamed to hartshorn-inject-configurations/src/main/java/org/dockbox/hartshorn/inject/condition/ReferenceConditionContext.java

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,45 +17,31 @@
1717
package org.dockbox.hartshorn.inject.condition;
1818

1919
import java.lang.classfile.AttributedElement;
20-
import org.dockbox.hartshorn.util.introspect.scan.TypeReference;
2120

2221
/**
23-
* A context that is used during the evaluation of a condition. This includes the type reference
24-
* that is being evaluated, and the {@link RequiresReferenceCondition} annotation that is used to
25-
* evaluate the condition.
22+
* A context that is used during the evaluation of a condition. This context provides access to the
23+
* {@link AttributedElement class file element} being evaluated. Unlike the
24+
* {@link IntrospectedConditionContext}, this context does not ensure that the element is fully
25+
* loaded by the JVM, and may therefore be used in earlier stages of the injection process.
2626
*
2727
* @see RequiresReferenceCondition
2828
*
2929
* @since 0.7.0
3030
*
3131
* @author Guus Lieben
3232
*/
33-
public non-sealed class TypeReferenceConditionContext extends ConditionContext {
33+
public non-sealed class ReferenceConditionContext extends ConditionContext {
3434

35-
private final TypeReference typeReference;
3635
private final AttributedElement element;
3736

38-
public TypeReferenceConditionContext(
37+
public ReferenceConditionContext(
3938
ConditionDeclaration condition,
40-
TypeReference typeReference,
4139
AttributedElement element
4240
) {
4341
super(condition);
44-
this.typeReference = typeReference;
4542
this.element = element;
4643
}
4744

48-
/**
49-
* Returns the type reference in which the condition is being evaluated. This may match the
50-
* {@link #element()} if the element is a type, but may also represent a member or other
51-
* annotated element within the type.
52-
*
53-
* @return the type reference being evaluated
54-
*/
55-
public TypeReference typeReference() {
56-
return this.typeReference;
57-
}
58-
5945
/**
6046
* Returns the element being evaluated. This may be the type itself, but may also be a member or
6147
* other annotated element within the type.

hartshorn-inject-configurations/src/main/java/org/dockbox/hartshorn/inject/condition/RequiresReferenceCondition.java

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,16 @@
2424
import java.lang.annotation.Target;
2525

2626
/**
27-
* A condition that requires conditions to be met based on type references. Unlike
28-
* {@link RequiresCondition}, this annotation is specifically designed to work with conditions that
29-
* evaluate type references, such as classes or interfaces, without needing to load the classes into
30-
* the JVM.
27+
* A condition that requires conditions to be met based on
28+
* {@link java.lang.classfile.AttributedElement class file elements}.
3129
*
32-
* <p>This approach has the benefit of avoiding class loading issues, such as
33-
* {@link ClassNotFoundException ClassNotFoundExceptions}, when checking for the presence of
34-
* classes that may not be available at runtime. By using type references, conditions can be
35-
* evaluated based on metadata alone, allowing for safer and more flexible condition checks. The
36-
* drawback is that the condition implementations cannot rely on loaded classes, and must
37-
* instead work with type metadata.
30+
* <p>By using type references, conditions can be evaluated based on metadata alone, allowing for
31+
* safer and more flexible condition checks. The drawback is that the condition implementations
32+
* cannot rely on loaded classes, and must instead work with type metadata.
3833
*
39-
* <p>Due to the nature of type references, meta annotations of this annotation are supported, but
40-
* lack hierarchy support (i.e. meta-meta annotations and attribute aliases are not supported).
34+
* <p>Due to the nature of type references, only meta annotations of this annotation are supported,
35+
* but these lack hierarchy support (i.e. meta-meta annotations and attribute aliases are not
36+
* supported).
4137
*
4238
* <pre>{@code
4339
* @RequiresReferenceCondition(condition = SampleCondition.class)
@@ -62,7 +58,7 @@
6258
*
6359
* @return the condition that is required to be met
6460
*/
65-
Class<? extends TypeReferenceCondition> condition();
61+
Class<? extends ReferenceCondition> condition();
6662

6763
/**
6864
* Whether to fail on no match. If set to {@code true}, the operation will fail if the condition

hartshorn-inject-configurations/src/main/java/org/dockbox/hartshorn/inject/condition/support/ClassCondition.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,16 @@
1616

1717
package org.dockbox.hartshorn.inject.condition.support;
1818

19+
import org.dockbox.hartshorn.inject.condition.ReferenceConditionContext;
20+
import org.dockbox.hartshorn.inject.condition.ConditionResult;
21+
import org.dockbox.hartshorn.inject.condition.ReferenceCondition;
22+
import org.dockbox.hartshorn.util.types.ClassFileUtilities;
23+
import org.dockbox.hartshorn.util.types.TypeUtils;
24+
1925
import java.lang.classfile.Annotation;
2026
import java.lang.classfile.AnnotationValue;
2127
import java.lang.classfile.AttributedElement;
2228
import java.util.List;
23-
import org.dockbox.hartshorn.inject.condition.ConditionResult;
24-
import org.dockbox.hartshorn.inject.condition.TypeReferenceCondition;
25-
import org.dockbox.hartshorn.inject.condition.TypeReferenceConditionContext;
26-
import org.dockbox.hartshorn.util.types.ClassFileUtilities;
27-
import org.dockbox.hartshorn.util.types.TypeUtils;
2829

2930
/**
3031
* A condition that matches when a class is present on the classpath.
@@ -35,10 +36,10 @@
3536
*
3637
* @author Guus Lieben
3738
*/
38-
public class ClassCondition implements TypeReferenceCondition {
39+
public class ClassCondition implements ReferenceCondition {
3940

4041
@Override
41-
public ConditionResult matches(TypeReferenceConditionContext context) {
42+
public ConditionResult matches(ReferenceConditionContext context) {
4243
AttributedElement element = context.element();
4344
Annotation annotation = ClassFileUtilities.getAnnotation(
4445
element,

hartshorn-inject-configurations/src/main/java/org/dockbox/hartshorn/inject/condition/support/RequiresClass.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,23 @@
1616

1717
package org.dockbox.hartshorn.inject.condition.support;
1818

19+
import org.dockbox.hartshorn.inject.condition.RequiresReferenceCondition;
20+
1921
import java.lang.annotation.ElementType;
2022
import java.lang.annotation.Retention;
2123
import java.lang.annotation.RetentionPolicy;
2224
import java.lang.annotation.Target;
23-
import org.dockbox.hartshorn.inject.condition.RequiresReferenceCondition;
2425

2526
/**
26-
* A condition that requires classes to be present in the classpath.
27+
* A condition that requires classes to be present in the classpath. As this condition is evaluated
28+
* early during the injection process, it does not require the classes to be loaded by the JVM, and
29+
* can therefore be used to conditionally load configurations based on optional dependencies.
30+
*
31+
* <p><b>Note, for binding methods</b>, take into account that the JVM will have loaded the
32+
* enclosing class, as well as the return type and any method references, before evaluating this
33+
* condition. As such, this condition is best applied to configuration classes. If used on binding
34+
* methods, ensure the return type and references do not depend on the checked classes. For such
35+
* cases, use separate conditionally loaded configuration classes.
2736
*
2837
* @see ClassCondition
2938
*

hartshorn-integration-tests/src/integration-test/java/test/org/dockbox/hartshorn/inject/conditions/ConditionTests.java

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@
1616

1717
package test.org.dockbox.hartshorn.inject.conditions;
1818

19-
import java.lang.classfile.Annotation;
20-
import java.lang.classfile.ClassModel;
21-
import java.lang.classfile.MethodModel;
22-
import java.util.stream.Stream;
2319
import org.dockbox.hartshorn.inject.ComponentKey;
2420
import org.dockbox.hartshorn.inject.annotations.Inject;
2521
import org.dockbox.hartshorn.inject.binding.BindingHierarchy;
@@ -31,7 +27,7 @@
3127
import org.dockbox.hartshorn.inject.condition.IntrospectedConditionContext;
3228
import org.dockbox.hartshorn.inject.condition.ReferenceConditionDeclaration;
3329
import org.dockbox.hartshorn.inject.condition.RequiresCondition;
34-
import org.dockbox.hartshorn.inject.condition.TypeReferenceConditionContext;
30+
import org.dockbox.hartshorn.inject.condition.ReferenceConditionContext;
3531
import org.dockbox.hartshorn.inject.condition.support.ClassCondition;
3632
import org.dockbox.hartshorn.inject.condition.support.RequiresClass;
3733
import org.dockbox.hartshorn.launchpad.ApplicationContext;
@@ -40,7 +36,6 @@
4036
import org.dockbox.hartshorn.test.annotations.TestComponents;
4137
import org.dockbox.hartshorn.test.annotations.TestProperties;
4238
import org.dockbox.hartshorn.test.junit.HartshornIntegrationTest;
43-
import org.dockbox.hartshorn.util.introspect.scan.ClassReference;
4439
import org.dockbox.hartshorn.util.introspect.view.MethodView;
4540
import org.dockbox.hartshorn.util.introspect.view.TypeView;
4641
import org.dockbox.hartshorn.util.types.ClassFileUtilities;
@@ -49,6 +44,11 @@
4944
import org.junit.jupiter.params.provider.Arguments;
5045
import org.junit.jupiter.params.provider.MethodSource;
5146

47+
import java.lang.classfile.Annotation;
48+
import java.lang.classfile.ClassModel;
49+
import java.lang.classfile.MethodModel;
50+
import java.util.stream.Stream;
51+
5252
import static org.assertj.core.api.Assertions.assertThat;
5353

5454
@SuppressWarnings("unused")
@@ -130,10 +130,9 @@ void classConditionOnPresentClass() {
130130
).get();
131131
ReferenceConditionDeclaration declarationForPresent =
132132
ReferenceConditionDeclaration.createFromMetaAnnotation(annotationForPresent);
133-
ConditionContext contextForPresent = new TypeReferenceConditionContext(
133+
ConditionContext contextForPresent = new ReferenceConditionContext(
134134
declarationForPresent,
135-
new ClassReference(ConditionTests.class),
136-
requiresClass
135+
requiresClass
137136
);
138137
assertThat(condition.matches(contextForPresent).matches()).isTrue();
139138
}
@@ -153,10 +152,9 @@ void classConditionOnAbsentClass() {
153152
).get();
154153
ReferenceConditionDeclaration declarationForAbsent =
155154
ReferenceConditionDeclaration.createFromMetaAnnotation(annotationForAbsent);
156-
ConditionContext contextForAbsent = new TypeReferenceConditionContext(
155+
ConditionContext contextForAbsent = new ReferenceConditionContext(
157156
declarationForAbsent,
158-
new ClassReference(ConditionTests.class),
159-
requiresAbsentClass
157+
requiresAbsentClass
160158
);
161159
assertThat(condition.matches(contextForAbsent).matches()).isFalse();
162160
}

hartshorn-introspect-reflection/src/main/java/org/dockbox/hartshorn/util/introspect/reflect/ReflectionExecutableParametersIntrospector.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019-2025 the original author or authors.
2+
* Copyright 2019-2026 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -147,4 +147,9 @@ private boolean matches(
147147
}
148148
return true;
149149
}
150+
151+
@Override
152+
public int indexOf(ParameterView<?> parameter) {
153+
return this.all().indexOf(parameter);
154+
}
150155
}

0 commit comments

Comments
 (0)