diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/InjectTestProcessor.java b/inject-generator/src/main/java/io/avaje/inject/generator/InjectTestProcessor.java
new file mode 100644
index 00000000..70e86298
--- /dev/null
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/InjectTestProcessor.java
@@ -0,0 +1,73 @@
+package io.avaje.inject.generator;
+
+import static io.avaje.inject.generator.APContext.typeElement;
+
+import java.io.IOException;
+import java.util.Optional;
+import java.util.Set;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.TypeElement;
+
+@SupportedAnnotationTypes({"io.avaje.inject.test.InjectTest"})
+public final class InjectTestProcessor extends AbstractProcessor {
+
+ private boolean wroteLookup;
+
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latest();
+ }
+
+ @Override
+ public synchronized void init(ProcessingEnvironment processingEnv) {
+ super.init(processingEnv);
+ }
+
+ @Override
+ public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ if (!wroteLookup
+ && !Optional.ofNullable(typeElement("io.avaje.inject.test.InjectTest"))
+ .map(roundEnv::getElementsAnnotatedWith)
+ .orElse(Set.of())
+ .isEmpty()) {
+ wroteLookup = true;
+ writeLookup();
+ }
+ return false;
+ }
+
+ private void writeLookup() {
+ var template =
+ "package io.avaje.inject.test.lookup;\n"
+ + "\n"
+ + "import java.lang.invoke.MethodHandles;\n"
+ + "import java.lang.invoke.MethodHandles.Lookup;\n"
+ + "\n"
+ + "import io.avaje.inject.test.LookupProvider;\n"
+ + "\n"
+ + "public class TestLookup implements LookupProvider {\n"
+ + "\n"
+ + " @Override\n"
+ + " public Lookup provideLookup() {\n"
+ + " return MethodHandles.lookup();\n"
+ + " }\n"
+ + "}";
+
+ try (var writer =
+ APContext.createSourceFile("io.avaje.inject.test.lookup.TestLookup").openWriter();
+ var services =
+ ProcessingContext.createMetaInfWriterFor(
+ "META-INF/services/io.avaje.inject.test.LookupProvider")
+ .openWriter()) {
+ writer.append(template);
+ services.append("io.avaje.inject.test.lookup.TestLookup");
+ } catch (IOException e) {
+ APContext.logWarn("failed to write lookup");
+ }
+ }
+}
diff --git a/inject-generator/src/main/java/module-info.java b/inject-generator/src/main/java/module-info.java
index 8d0464df..02a487fc 100644
--- a/inject-generator/src/main/java/module-info.java
+++ b/inject-generator/src/main/java/module-info.java
@@ -1,3 +1,6 @@
+import io.avaje.inject.generator.InjectProcessor;
+import io.avaje.inject.generator.InjectTestProcessor;
+
module io.avaje.inject.generator {
requires java.compiler;
@@ -11,5 +14,5 @@
uses io.avaje.inject.spi.InjectExtension;
- provides javax.annotation.processing.Processor with io.avaje.inject.generator.InjectProcessor;
+ provides javax.annotation.processing.Processor with InjectProcessor, InjectTestProcessor;
}
diff --git a/inject-test/pom.xml b/inject-test/pom.xml
index 9486328f..69301acd 100644
--- a/inject-test/pom.xml
+++ b/inject-test/pom.xml
@@ -12,7 +12,7 @@
5.12.0
- 5.15.2
+ 5.16.0
@@ -45,12 +45,12 @@
net.bytebuddy
byte-buddy
- 1.17.1
+ 1.17.2
net.bytebuddy
byte-buddy-agent
- 1.17.1
+ 1.17.2
diff --git a/inject-test/src/main/java/io/avaje/inject/test/LookupProvider.java b/inject-test/src/main/java/io/avaje/inject/test/LookupProvider.java
new file mode 100644
index 00000000..e42acc7c
--- /dev/null
+++ b/inject-test/src/main/java/io/avaje/inject/test/LookupProvider.java
@@ -0,0 +1,10 @@
+package io.avaje.inject.test;
+
+import java.lang.invoke.MethodHandles.Lookup;
+
+/** Provides a Lookup instance for accessing test fields. */
+public interface LookupProvider {
+
+ /** Return the Lookup. */
+ Lookup provideLookup();
+}
diff --git a/inject-test/src/main/java/io/avaje/inject/test/Lookups.java b/inject-test/src/main/java/io/avaje/inject/test/Lookups.java
new file mode 100644
index 00000000..e3f5aaaf
--- /dev/null
+++ b/inject-test/src/main/java/io/avaje/inject/test/Lookups.java
@@ -0,0 +1,57 @@
+package io.avaje.inject.test;
+
+import static java.util.stream.Collectors.toMap;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.invoke.VarHandle;
+import java.lang.reflect.Field;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Map;
+import java.util.ServiceLoader;
+
+/** Provides Lookup instances using potentially module specific Lookups. */
+final class Lookups {
+
+ private static final Map MODULE_LOOKUP_MAP =
+ ServiceLoader.load(LookupProvider.class).stream()
+ .collect(toMap(p -> p.type().getModule().getName(), p -> p.get().provideLookup()));
+
+ private static final Lookup DEFAULT_LOOKUP = MethodHandles.publicLookup();
+
+ /** Return a Lookup ideally for the module associated with the given type. */
+ static Lookup getLookup(Class> type) {
+ return MODULE_LOOKUP_MAP.getOrDefault(type.getModule().getName(), DEFAULT_LOOKUP);
+ }
+
+ static VarHandle getVarhandle(Class> testClass, Field field) {
+ try {
+ var lookup = getLookup(testClass);
+ lookup =
+ lookup.hasPrivateAccess()
+ ? MethodHandles.privateLookupIn(testClass, getLookup(testClass))
+ : lookup;
+
+ return lookup.unreflectVarHandle(field);
+ } catch (Exception e) {
+ throw new IllegalStateException("Can't access field " + field, e);
+ }
+ }
+
+ static Class> getClassFromType(Type generic) {
+ if (generic instanceof Class) {
+ return (Class>) generic;
+ }
+ if (generic instanceof ParameterizedType) {
+ Type actual = ((ParameterizedType) generic).getActualTypeArguments()[0];
+ if (actual instanceof Class) {
+ return (Class>) actual;
+ }
+ if (actual instanceof ParameterizedType) {
+ return (Class>) ((ParameterizedType) actual).getRawType();
+ }
+ }
+ return Object.class;
+ }
+}
diff --git a/inject-test/src/main/java/io/avaje/inject/test/MetaReader.java b/inject-test/src/main/java/io/avaje/inject/test/MetaReader.java
index ac02315c..5ab35916 100644
--- a/inject-test/src/main/java/io/avaje/inject/test/MetaReader.java
+++ b/inject-test/src/main/java/io/avaje/inject/test/MetaReader.java
@@ -1,6 +1,7 @@
package io.avaje.inject.test;
import java.lang.annotation.Annotation;
+import java.lang.invoke.VarHandle;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
@@ -14,8 +15,6 @@
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Spy;
-import org.mockito.internal.configuration.plugins.Plugins;
-import org.mockito.internal.util.reflection.GenericMaster;
import io.avaje.inject.BeanScope;
import io.avaje.inject.BeanScopeBuilder;
@@ -26,6 +25,7 @@
final class MetaReader {
private final SetupMethods methodFinder;
+ final Class> testClass;
final List captors = new ArrayList<>();
final List mocks = new ArrayList<>();
final List spies = new ArrayList<>();
@@ -41,6 +41,7 @@ final class MetaReader {
boolean instancePlugin;
MetaReader(Class> testClass, Plugin plugin) {
+ this.testClass = testClass;
this.plugin = plugin;
final var hierarchy = typeHierarchy(testClass);
this.methodFinder = new SetupMethods(hierarchy);
@@ -54,9 +55,8 @@ final class MetaReader {
boolean hasMocksOrSpies(Object testInstance) {
if (testInstance == null) {
return hasStaticMocksOrSpies() || methodFinder.hasStaticMethods();
- } else {
- return hasInstanceMocksOrSpies(testInstance) || methodFinder.hasInstanceMethods();
}
+ return hasInstanceMocksOrSpies(testInstance) || methodFinder.hasInstanceMethods();
}
private boolean hasInstanceMocksOrSpies(Object testInstance) {
@@ -154,7 +154,7 @@ private void add(FieldTarget target, List instanceList, List type = field.getType();
if (!ArgumentCaptor.class.isAssignableFrom(type)) {
- throw new IllegalStateException("@Captor field must be of the type ArgumentCaptor.\n Field: '" + field.getName() + "' has wrong type");
+ throw new IllegalStateException(
+ "@Captor field must be of the type ArgumentCaptor.\n Field: '"
+ + field.getName()
+ + "' has wrong type");
}
- Class> cls = new GenericMaster().getGenericType(field);
+ Class> cls = Lookups.getClassFromType(field.getGenericType());
return ArgumentCaptor.forClass(cls);
}
@@ -308,8 +314,12 @@ private static void registerAsTestDouble(BeanScopeBuilder builder, FieldTarget t
builder.bean(target.name(), target.type(), value);
}
- void set(Field field, Object val, Object testInstance) throws IllegalAccessException {
- Plugins.getMemberAccessor().set(field, testInstance, val);
+ void set(boolean isStatic, VarHandle fieldHandle, Object val, Object testInstance) {
+ if (isStatic) {
+ fieldHandle.set(val);
+ } else {
+ fieldHandle.set(testInstance, val);
+ }
}
class FieldTarget {
@@ -319,11 +329,13 @@ class FieldTarget {
private final boolean isStatic;
private boolean pluginInjection;
private boolean valueAlreadyProvided;
+ private final VarHandle fieldHandle;
- FieldTarget(Field field, String name) {
+ FieldTarget(Field field, String name, VarHandle fieldHandle) {
this.field = field;
this.isStatic = Modifier.isStatic(field.getModifiers());
this.name = name;
+ this.fieldHandle = fieldHandle;
}
@Override
@@ -344,11 +356,7 @@ boolean isStatic() {
}
Object get(Object instance) {
- try {
- return Plugins.getMemberAccessor().get(field, instance);
- } catch (IllegalAccessException e) {
- throw new RuntimeException(e);
- }
+ return isStatic ? fieldHandle.get() : fieldHandle.get(instance);
}
void setFromScope(BeanScope beanScope, Object testInstance) throws IllegalAccessException {
@@ -356,28 +364,27 @@ void setFromScope(BeanScope beanScope, Object testInstance) throws IllegalAccess
return;
}
final var type = type();
-
if (type instanceof ParameterizedType) {
final var parameterizedType = (ParameterizedType) type;
final var rawType = parameterizedType.getRawType();
final var typeArguments = parameterizedType.getActualTypeArguments();
if (rawType.equals(List.class)) {
- set(field, beanScope.list(typeArguments[0]), testInstance);
+ set(isStatic, fieldHandle, beanScope.list(typeArguments[0]), testInstance);
return;
}
if (rawType.equals(Optional.class)) {
- set(field, beanScope.getOptional(typeArguments[0], name), testInstance);
+ set(isStatic, fieldHandle, beanScope.getOptional(typeArguments[0], name), testInstance);
return;
}
}
- set(field, beanScope.get(type, name), testInstance);
+ set(isStatic, fieldHandle, beanScope.get(type, name), testInstance);
}
void setFromPlugin(Object value, Object testInstance) throws IllegalAccessException {
- set(field, value, testInstance);
+ set(isStatic, fieldHandle, value, testInstance);
}
void markForPluginInjection() {
diff --git a/inject-test/src/main/java/module-info.java b/inject-test/src/main/java/module-info.java
index 956aef2c..6b4dbba6 100644
--- a/inject-test/src/main/java/module-info.java
+++ b/inject-test/src/main/java/module-info.java
@@ -16,4 +16,5 @@
uses io.avaje.inject.test.TestModule;
uses io.avaje.inject.test.Plugin;
+ uses io.avaje.inject.test.LookupProvider;
}
diff --git a/inject/pom.xml b/inject/pom.xml
index 32bfe740..a0a3e0ac 100644
--- a/inject/pom.xml
+++ b/inject/pom.xml
@@ -53,7 +53,7 @@
org.mockito
mockito-core
- 5.15.2
+ 5.16.0
true
diff --git a/pom.xml b/pom.xml
index 48c99a82..0476e854 100644
--- a/pom.xml
+++ b/pom.xml
@@ -61,19 +61,19 @@
net.bytebuddy
byte-buddy
- 1.17.1
+ 1.17.2
test
net.bytebuddy
byte-buddy-agent
- 1.17.1
+ 1.17.2
test
org.mockito
mockito-core
- 5.15.2
+ 5.16.0
test