Skip to content

Commit c2317ad

Browse files
[inject Test] Generate Method Handle Lookups (#775)
* Bump the dependencies group with 4 updates Bumps the dependencies group with 4 updates: [net.bytebuddy:byte-buddy](https://github.com/raphw/byte-buddy), [net.bytebuddy:byte-buddy-agent](https://github.com/raphw/byte-buddy), [org.mockito:mockito-core](https://github.com/mockito/mockito) and [org.mockito:mockito-junit-jupiter](https://github.com/mockito/mockito). Updates `net.bytebuddy:byte-buddy` from 1.17.1 to 1.17.2 - [Release notes](https://github.com/raphw/byte-buddy/releases) - [Changelog](https://github.com/raphw/byte-buddy/blob/master/release-notes.md) - [Commits](raphw/byte-buddy@byte-buddy-1.17.1...byte-buddy-1.17.2) Updates `net.bytebuddy:byte-buddy-agent` from 1.17.1 to 1.17.2 - [Release notes](https://github.com/raphw/byte-buddy/releases) - [Changelog](https://github.com/raphw/byte-buddy/blob/master/release-notes.md) - [Commits](raphw/byte-buddy@byte-buddy-1.17.1...byte-buddy-1.17.2) Updates `org.mockito:mockito-core` from 5.15.2 to 5.16.0 - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](mockito/mockito@v5.15.2...v5.16.0) Updates `org.mockito:mockito-junit-jupiter` from 5.15.2 to 5.16.0 - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](mockito/mockito@v5.15.2...v5.16.0) Updates `org.mockito:mockito-junit-jupiter` from 5.15.2 to 5.16.0 - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](mockito/mockito@v5.15.2...v5.16.0) --- updated-dependencies: - dependency-name: net.bytebuddy:byte-buddy dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: net.bytebuddy:byte-buddy-agent dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: org.mockito:mockito-core dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: org.mockito:mockito-junit-jupiter dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: org.mockito:mockito-junit-jupiter dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies ... Signed-off-by: dependabot[bot] <[email protected]> * start Revert "start" This reverts commit 4bab305. structure * add processor * Update Lookups.java --------- Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1 parent 6d67e03 commit c2317ad

File tree

9 files changed

+182
-31
lines changed

9 files changed

+182
-31
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package io.avaje.inject.generator;
2+
3+
import static io.avaje.inject.generator.APContext.typeElement;
4+
5+
import java.io.IOException;
6+
import java.util.Optional;
7+
import java.util.Set;
8+
9+
import javax.annotation.processing.AbstractProcessor;
10+
import javax.annotation.processing.ProcessingEnvironment;
11+
import javax.annotation.processing.RoundEnvironment;
12+
import javax.annotation.processing.SupportedAnnotationTypes;
13+
import javax.lang.model.SourceVersion;
14+
import javax.lang.model.element.TypeElement;
15+
16+
@SupportedAnnotationTypes({"io.avaje.inject.test.InjectTest"})
17+
public final class InjectTestProcessor extends AbstractProcessor {
18+
19+
private boolean wroteLookup;
20+
21+
@Override
22+
public SourceVersion getSupportedSourceVersion() {
23+
return SourceVersion.latest();
24+
}
25+
26+
@Override
27+
public synchronized void init(ProcessingEnvironment processingEnv) {
28+
super.init(processingEnv);
29+
}
30+
31+
@Override
32+
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
33+
if (!wroteLookup
34+
&& !Optional.ofNullable(typeElement("io.avaje.inject.test.InjectTest"))
35+
.map(roundEnv::getElementsAnnotatedWith)
36+
.orElse(Set.of())
37+
.isEmpty()) {
38+
wroteLookup = true;
39+
writeLookup();
40+
}
41+
return false;
42+
}
43+
44+
private void writeLookup() {
45+
var template =
46+
"package io.avaje.inject.test.lookup;\n"
47+
+ "\n"
48+
+ "import java.lang.invoke.MethodHandles;\n"
49+
+ "import java.lang.invoke.MethodHandles.Lookup;\n"
50+
+ "\n"
51+
+ "import io.avaje.inject.test.LookupProvider;\n"
52+
+ "\n"
53+
+ "public class TestLookup implements LookupProvider {\n"
54+
+ "\n"
55+
+ " @Override\n"
56+
+ " public Lookup provideLookup() {\n"
57+
+ " return MethodHandles.lookup();\n"
58+
+ " }\n"
59+
+ "}";
60+
61+
try (var writer =
62+
APContext.createSourceFile("io.avaje.inject.test.lookup.TestLookup").openWriter();
63+
var services =
64+
ProcessingContext.createMetaInfWriterFor(
65+
"META-INF/services/io.avaje.inject.test.LookupProvider")
66+
.openWriter()) {
67+
writer.append(template);
68+
services.append("io.avaje.inject.test.lookup.TestLookup");
69+
} catch (IOException e) {
70+
APContext.logWarn("failed to write lookup");
71+
}
72+
}
73+
}

inject-generator/src/main/java/module-info.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import io.avaje.inject.generator.InjectProcessor;
2+
import io.avaje.inject.generator.InjectTestProcessor;
3+
14
module io.avaje.inject.generator {
25

36
requires java.compiler;
@@ -11,5 +14,5 @@
1114

1215
uses io.avaje.inject.spi.InjectExtension;
1316

14-
provides javax.annotation.processing.Processor with io.avaje.inject.generator.InjectProcessor;
17+
provides javax.annotation.processing.Processor with InjectProcessor, InjectTestProcessor;
1518
}

inject-test/pom.xml

+3-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
<properties>
1414
<jupiter.version>5.12.0</jupiter.version>
15-
<mockito.version>5.15.2</mockito.version>
15+
<mockito.version>5.16.0</mockito.version>
1616
</properties>
1717

1818
<dependencies>
@@ -45,12 +45,12 @@
4545
<dependency>
4646
<groupId>net.bytebuddy</groupId>
4747
<artifactId>byte-buddy</artifactId>
48-
<version>1.17.1</version>
48+
<version>1.17.2</version>
4949
</dependency>
5050
<dependency>
5151
<groupId>net.bytebuddy</groupId>
5252
<artifactId>byte-buddy-agent</artifactId>
53-
<version>1.17.1</version>
53+
<version>1.17.2</version>
5454
</dependency>
5555

5656
<dependency>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package io.avaje.inject.test;
2+
3+
import java.lang.invoke.MethodHandles.Lookup;
4+
5+
/** Provides a Lookup instance for accessing test fields. */
6+
public interface LookupProvider {
7+
8+
/** Return the Lookup. */
9+
Lookup provideLookup();
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package io.avaje.inject.test;
2+
3+
import static java.util.stream.Collectors.toMap;
4+
5+
import java.lang.invoke.MethodHandles;
6+
import java.lang.invoke.MethodHandles.Lookup;
7+
import java.lang.invoke.VarHandle;
8+
import java.lang.reflect.Field;
9+
import java.lang.reflect.ParameterizedType;
10+
import java.lang.reflect.Type;
11+
import java.util.Map;
12+
import java.util.ServiceLoader;
13+
14+
/** Provides Lookup instances using potentially module specific Lookups. */
15+
final class Lookups {
16+
17+
private static final Map<String, Lookup> MODULE_LOOKUP_MAP =
18+
ServiceLoader.load(LookupProvider.class).stream()
19+
.collect(toMap(p -> p.type().getModule().getName(), p -> p.get().provideLookup()));
20+
21+
private static final Lookup DEFAULT_LOOKUP = MethodHandles.publicLookup();
22+
23+
/** Return a Lookup ideally for the module associated with the given type. */
24+
static Lookup getLookup(Class<?> type) {
25+
return MODULE_LOOKUP_MAP.getOrDefault(type.getModule().getName(), DEFAULT_LOOKUP);
26+
}
27+
28+
static VarHandle getVarhandle(Class<?> testClass, Field field) {
29+
try {
30+
var lookup = getLookup(testClass);
31+
lookup =
32+
lookup.hasPrivateAccess()
33+
? MethodHandles.privateLookupIn(testClass, getLookup(testClass))
34+
: lookup;
35+
36+
return lookup.unreflectVarHandle(field);
37+
} catch (Exception e) {
38+
throw new IllegalStateException("Can't access field " + field, e);
39+
}
40+
}
41+
42+
static Class<?> getClassFromType(Type generic) {
43+
if (generic instanceof Class) {
44+
return (Class<?>) generic;
45+
}
46+
if (generic instanceof ParameterizedType) {
47+
Type actual = ((ParameterizedType) generic).getActualTypeArguments()[0];
48+
if (actual instanceof Class) {
49+
return (Class<?>) actual;
50+
}
51+
if (actual instanceof ParameterizedType) {
52+
return (Class<?>) ((ParameterizedType) actual).getRawType();
53+
}
54+
}
55+
return Object.class;
56+
}
57+
}

inject-test/src/main/java/io/avaje/inject/test/MetaReader.java

+30-23
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.avaje.inject.test;
22

33
import java.lang.annotation.Annotation;
4+
import java.lang.invoke.VarHandle;
45
import java.lang.reflect.Field;
56
import java.lang.reflect.Modifier;
67
import java.lang.reflect.ParameterizedType;
@@ -14,8 +15,6 @@
1415
import org.mockito.Captor;
1516
import org.mockito.Mock;
1617
import org.mockito.Spy;
17-
import org.mockito.internal.configuration.plugins.Plugins;
18-
import org.mockito.internal.util.reflection.GenericMaster;
1918

2019
import io.avaje.inject.BeanScope;
2120
import io.avaje.inject.BeanScopeBuilder;
@@ -26,6 +25,7 @@
2625
final class MetaReader {
2726

2827
private final SetupMethods methodFinder;
28+
final Class<?> testClass;
2929
final List<Field> captors = new ArrayList<>();
3030
final List<FieldTarget> mocks = new ArrayList<>();
3131
final List<FieldTarget> spies = new ArrayList<>();
@@ -41,6 +41,7 @@ final class MetaReader {
4141
boolean instancePlugin;
4242

4343
MetaReader(Class<?> testClass, Plugin plugin) {
44+
this.testClass = testClass;
4445
this.plugin = plugin;
4546
final var hierarchy = typeHierarchy(testClass);
4647
this.methodFinder = new SetupMethods(hierarchy);
@@ -54,9 +55,8 @@ final class MetaReader {
5455
boolean hasMocksOrSpies(Object testInstance) {
5556
if (testInstance == null) {
5657
return hasStaticMocksOrSpies() || methodFinder.hasStaticMethods();
57-
} else {
58-
return hasInstanceMocksOrSpies(testInstance) || methodFinder.hasInstanceMethods();
5958
}
59+
return hasInstanceMocksOrSpies(testInstance) || methodFinder.hasInstanceMethods();
6060
}
6161

6262
private boolean hasInstanceMocksOrSpies(Object testInstance) {
@@ -154,7 +154,7 @@ private void add(FieldTarget target, List<FieldTarget> instanceList, List<FieldT
154154
}
155155

156156
private FieldTarget newTarget(Field field) {
157-
return new FieldTarget(field, name(field));
157+
return new FieldTarget(field, name(field), Lookups.getVarhandle(testClass, field));
158158
}
159159

160160
private String name(Field field) {
@@ -178,9 +178,8 @@ private String name(Field field) {
178178
TestBeans setFromScope(TestBeans metaScope, Object testInstance) {
179179
if (testInstance != null) {
180180
return setForInstance(metaScope, testInstance);
181-
} else {
182-
return setForStatics(metaScope);
183181
}
182+
return setForStatics(metaScope);
184183
}
185184

186185
private TestBeans setForInstance(TestBeans metaScope, Object testInstance) {
@@ -189,7 +188,11 @@ private TestBeans setForInstance(TestBeans metaScope, Object testInstance) {
189188
BeanScope beanScope = metaScope.beanScope();
190189

191190
for (Field field : captors) {
192-
set(field, captorFor(field), testInstance);
191+
set(
192+
Modifier.isStatic(field.getModifiers()),
193+
Lookups.getVarhandle(testClass, field),
194+
captorFor(field),
195+
testInstance);
193196
}
194197
for (FieldTarget target : mocks) {
195198
target.setFromScope(beanScope, testInstance);
@@ -239,9 +242,12 @@ private TestBeans setForStatics(TestBeans metaScope) {
239242
private Object captorFor(Field field) {
240243
Class<?> type = field.getType();
241244
if (!ArgumentCaptor.class.isAssignableFrom(type)) {
242-
throw new IllegalStateException("@Captor field must be of the type ArgumentCaptor.\n Field: '" + field.getName() + "' has wrong type");
245+
throw new IllegalStateException(
246+
"@Captor field must be of the type ArgumentCaptor.\n Field: '"
247+
+ field.getName()
248+
+ "' has wrong type");
243249
}
244-
Class<?> cls = new GenericMaster().getGenericType(field);
250+
Class<?> cls = Lookups.getClassFromType(field.getGenericType());
245251
return ArgumentCaptor.forClass(cls);
246252
}
247253

@@ -308,8 +314,12 @@ private static void registerAsTestDouble(BeanScopeBuilder builder, FieldTarget t
308314
builder.bean(target.name(), target.type(), value);
309315
}
310316

311-
void set(Field field, Object val, Object testInstance) throws IllegalAccessException {
312-
Plugins.getMemberAccessor().set(field, testInstance, val);
317+
void set(boolean isStatic, VarHandle fieldHandle, Object val, Object testInstance) {
318+
if (isStatic) {
319+
fieldHandle.set(val);
320+
} else {
321+
fieldHandle.set(testInstance, val);
322+
}
313323
}
314324

315325
class FieldTarget {
@@ -319,11 +329,13 @@ class FieldTarget {
319329
private final boolean isStatic;
320330
private boolean pluginInjection;
321331
private boolean valueAlreadyProvided;
332+
private final VarHandle fieldHandle;
322333

323-
FieldTarget(Field field, String name) {
334+
FieldTarget(Field field, String name, VarHandle fieldHandle) {
324335
this.field = field;
325336
this.isStatic = Modifier.isStatic(field.getModifiers());
326337
this.name = name;
338+
this.fieldHandle = fieldHandle;
327339
}
328340

329341
@Override
@@ -344,40 +356,35 @@ boolean isStatic() {
344356
}
345357

346358
Object get(Object instance) {
347-
try {
348-
return Plugins.getMemberAccessor().get(field, instance);
349-
} catch (IllegalAccessException e) {
350-
throw new RuntimeException(e);
351-
}
359+
return isStatic ? fieldHandle.get() : fieldHandle.get(instance);
352360
}
353361

354362
void setFromScope(BeanScope beanScope, Object testInstance) throws IllegalAccessException {
355363
if (valueAlreadyProvided) {
356364
return;
357365
}
358366
final var type = type();
359-
360367
if (type instanceof ParameterizedType) {
361368
final var parameterizedType = (ParameterizedType) type;
362369
final var rawType = parameterizedType.getRawType();
363370
final var typeArguments = parameterizedType.getActualTypeArguments();
364371

365372
if (rawType.equals(List.class)) {
366-
set(field, beanScope.list(typeArguments[0]), testInstance);
373+
set(isStatic, fieldHandle, beanScope.list(typeArguments[0]), testInstance);
367374
return;
368375
}
369376

370377
if (rawType.equals(Optional.class)) {
371-
set(field, beanScope.getOptional(typeArguments[0], name), testInstance);
378+
set(isStatic, fieldHandle, beanScope.getOptional(typeArguments[0], name), testInstance);
372379
return;
373380
}
374381
}
375382

376-
set(field, beanScope.get(type, name), testInstance);
383+
set(isStatic, fieldHandle, beanScope.get(type, name), testInstance);
377384
}
378385

379386
void setFromPlugin(Object value, Object testInstance) throws IllegalAccessException {
380-
set(field, value, testInstance);
387+
set(isStatic, fieldHandle, value, testInstance);
381388
}
382389

383390
void markForPluginInjection() {

inject-test/src/main/java/module-info.java

+1
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@
1616

1717
uses io.avaje.inject.test.TestModule;
1818
uses io.avaje.inject.test.Plugin;
19+
uses io.avaje.inject.test.LookupProvider;
1920
}

inject/pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
<dependency>
5454
<groupId>org.mockito</groupId>
5555
<artifactId>mockito-core</artifactId>
56-
<version>5.15.2</version>
56+
<version>5.16.0</version>
5757
<optional>true</optional>
5858
</dependency>
5959

pom.xml

+3-3
Original file line numberDiff line numberDiff line change
@@ -61,19 +61,19 @@
6161
<dependency>
6262
<groupId>net.bytebuddy</groupId>
6363
<artifactId>byte-buddy</artifactId>
64-
<version>1.17.1</version>
64+
<version>1.17.2</version>
6565
<scope>test</scope>
6666
</dependency>
6767
<dependency>
6868
<groupId>net.bytebuddy</groupId>
6969
<artifactId>byte-buddy-agent</artifactId>
70-
<version>1.17.1</version>
70+
<version>1.17.2</version>
7171
<scope>test</scope>
7272
</dependency>
7373
<dependency>
7474
<groupId>org.mockito</groupId>
7575
<artifactId>mockito-core</artifactId>
76-
<version>5.15.2</version>
76+
<version>5.16.0</version>
7777
<scope>test</scope>
7878
</dependency>
7979
</dependencies>

0 commit comments

Comments
 (0)