Skip to content

Commit 48ff4ce

Browse files
authored
Inheritance support (#10)
1 parent aadaa92 commit 48ff4ce

File tree

3 files changed

+131
-14
lines changed

3 files changed

+131
-14
lines changed

src/main/java/io/papermc/restamp/recipe/MethodATMutator.java

+62-11
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@
1212
import org.cadixdev.bombe.type.VoidType;
1313
import org.cadixdev.bombe.type.signature.MethodSignature;
1414
import org.jspecify.annotations.NullMarked;
15+
import org.jspecify.annotations.Nullable;
1516
import org.openrewrite.ExecutionContext;
1617
import org.openrewrite.Recipe;
1718
import org.openrewrite.TreeVisitor;
1819
import org.openrewrite.java.JavaIsoVisitor;
1920
import org.openrewrite.java.tree.J;
2021
import org.openrewrite.java.tree.JavaType;
22+
import org.openrewrite.java.tree.JavaType.FullyQualified;
2123
import org.openrewrite.java.tree.TypeTree;
2224
import org.slf4j.Logger;
2325
import org.slf4j.LoggerFactory;
@@ -34,6 +36,7 @@ public class MethodATMutator extends Recipe {
3436
private static final Logger LOGGER = LoggerFactory.getLogger(MethodATMutator.class);
3537

3638
private final AccessTransformSet atDictionary;
39+
private final AccessTransformSet inheritanceAccessTransformAtDirectory;
3740
private final ModifierTransformer modifierTransformer;
3841
private final AccessTransformerTypeConverter atTypeConverter;
3942

@@ -43,6 +46,12 @@ public MethodATMutator(final AccessTransformSet atDictionary,
4346
this.atDictionary = atDictionary;
4447
this.modifierTransformer = modifierTransformer;
4548
this.atTypeConverter = atTypeConverter;
49+
50+
// Create a copy of the atDirectory for inherited at lookups.
51+
// Needed as the parent type may be processed first, removing its access transformer for tracking purposes.
52+
// Child types hence lookup using this.
53+
this.inheritanceAccessTransformAtDirectory = AccessTransformSet.create();
54+
this.inheritanceAccessTransformAtDirectory.merge(this.atDictionary);
4655
}
4756

4857
@Override
@@ -67,20 +76,14 @@ public J.MethodDeclaration visitMethodDeclaration(final J.MethodDeclaration unre
6776
if (parentClassDeclaration == null || parentClassDeclaration.getType() == null)
6877
return methodDeclaration;
6978

70-
// Find access transformers for class
71-
final AccessTransformSet.Class transformerClass = atDictionary.getClass(
72-
parentClassDeclaration.getType().getFullyQualifiedName()
73-
).orElse(null);
74-
if (transformerClass == null) return methodDeclaration;
75-
7679
final String methodIdentifier = parentClassDeclaration.getType().getFullyQualifiedName() + "#" + methodDeclaration.getName();
7780

7881
if (methodDeclaration.getMethodType() == null) {
7982
LOGGER.warn("Method {} did not have a method type!", methodIdentifier);
8083
return methodDeclaration;
8184
}
8285

83-
// Fetch access transformer to apply to specific field.
86+
// Fetch access transformer to apply to specific method.
8487
String atMethodName = methodDeclaration.getMethodType().getName();
8588
Type returnType = atTypeConverter.convert(methodDeclaration.getMethodType().getReturnType(),
8689
() -> "Parsing return type " + methodDeclaration.getReturnTypeExpression().toString() + " of method " + methodIdentifier);
@@ -101,10 +104,14 @@ public J.MethodDeclaration visitMethodDeclaration(final J.MethodDeclaration unre
101104
returnType = VoidType.INSTANCE;
102105
}
103106

104-
final AccessTransform accessTransform = transformerClass.replaceMethod(new MethodSignature(
105-
atMethodName, new MethodDescriptor(parameterTypes, returnType)
106-
), AccessTransform.EMPTY);
107-
if (accessTransform == null || accessTransform.isEmpty()) return methodDeclaration;
107+
// Find access transformers for method
108+
final AccessTransform accessTransform = findApplicableAccessTransformer(
109+
parentClassDeclaration.getType(),
110+
atMethodName,
111+
returnType,
112+
parameterTypes
113+
);
114+
if (accessTransform == null) return methodDeclaration;
108115

109116
final TypeTree returnTypeExpression = methodDeclaration.getReturnTypeExpression();
110117
final ModifierTransformationResult transformationResult = modifierTransformer.transformModifiers(
@@ -125,4 +132,48 @@ atMethodName, new MethodDescriptor(parameterTypes, returnType)
125132
};
126133
}
127134

135+
/**
136+
* Finds the applicable access transformer for a method and *optionally* removes it from the atDirectory.
137+
*
138+
* @param owningType the owning type of the method, e.g. the type it is defined in.
139+
* @param atMethodName the method name.
140+
* @param returnType the return type.
141+
* @param parameterTypes the method parameters.
142+
*
143+
* @return the access transformer or null.
144+
*/
145+
@Nullable
146+
private AccessTransform findApplicableAccessTransformer(
147+
final FullyQualified owningType,
148+
final String atMethodName,
149+
final Type returnType,
150+
final List<FieldType> parameterTypes
151+
) {
152+
final MethodSignature methodSignature = new MethodSignature(
153+
atMethodName,
154+
new MethodDescriptor(parameterTypes, returnType)
155+
);
156+
157+
for (FullyQualified currentCheckedType = owningType; currentCheckedType != null; currentCheckedType = currentCheckedType.getSupertype()) {
158+
// The class at data from the copy of the at dir.
159+
// Removal of these happens later but we need the original state to ensure overrides are updated.
160+
final AccessTransformSet.Class transformerClass = inheritanceAccessTransformAtDirectory
161+
.getClass(currentCheckedType.getFullyQualifiedName())
162+
.orElse(null);
163+
if (transformerClass == null) continue;
164+
165+
// Only get the method here.
166+
final AccessTransform accessTransform = transformerClass.getMethod(methodSignature);
167+
if (accessTransform == null || accessTransform.isEmpty()) continue;
168+
169+
// If we *did* find an AT here and this *is* the direct owning type, remove it from the original atDirectory.
170+
if (currentCheckedType == owningType) {
171+
atDictionary.getClass(transformerClass.getName()).ifPresent(c -> c.replaceMethod(methodSignature, AccessTransform.EMPTY));
172+
}
173+
return accessTransform;
174+
}
175+
176+
return null; // We did not find anything applicable.
177+
}
178+
128179
}

src/test/java/io/papermc/restamp/RestampFunctionTestHelper.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.openrewrite.java.tree.Space;
2020
import org.openrewrite.marker.Markers;
2121

22+
import java.util.Arrays;
2223
import java.util.Collections;
2324
import java.util.List;
2425
import java.util.stream.Stream;
@@ -33,16 +34,16 @@ public class RestampFunctionTestHelper {
3334
* Constructs a new restamp input object from a single java class' source in a string.
3435
*
3536
* @param accessTransformSet the access transformers to apply.
36-
* @param javaClassSource the source code of a java class.
37+
* @param javaClassesSource the source code of a java class.
3738
*
3839
* @return the constructed restamp input.
3940
*/
4041
public static RestampInput inputFromSourceString(final AccessTransformSet accessTransformSet,
41-
final String javaClassSource) {
42+
final String... javaClassesSource) {
4243
final Java21Parser javaParser = Java21Parser.builder().build();
4344
final InMemoryExecutionContext executionContext = new InMemoryExecutionContext(t -> Assertions.fail("Failed to parse inputs", t));
4445
final List<SourceFile> sourceFiles = javaParser.parseInputs(
45-
List.of(Parser.Input.fromString(javaClassSource)),
46+
Arrays.stream(javaClassesSource).map(Parser.Input::fromString).toList(),
4647
null,
4748
executionContext
4849
).toList();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package io.papermc.restamp.at;
2+
3+
import io.papermc.restamp.Restamp;
4+
import io.papermc.restamp.RestampFunctionTestHelper;
5+
import io.papermc.restamp.RestampInput;
6+
import org.cadixdev.at.AccessTransform;
7+
import org.cadixdev.at.AccessTransformSet;
8+
import org.cadixdev.bombe.type.signature.MethodSignature;
9+
import org.jspecify.annotations.NullMarked;
10+
import org.junit.jupiter.api.Assertions;
11+
import org.junit.jupiter.api.Test;
12+
import org.openrewrite.Result;
13+
14+
import java.util.List;
15+
16+
@NullMarked
17+
public class InheritanceMethodATTest {
18+
19+
@Test
20+
public void testInheritedATs() {
21+
final AccessTransformSet accessTransformSet = AccessTransformSet.create();
22+
accessTransformSet.getOrCreateClass("io.papermc.test.Test").replaceMethod(
23+
MethodSignature.of("test", "(Ljava.lang.Object;)Ljava.lang.String;"), AccessTransform.PUBLIC
24+
);
25+
26+
final RestampInput input = RestampFunctionTestHelper.inputFromSourceString(
27+
accessTransformSet,
28+
"""
29+
package io.papermc.test;
30+
31+
public class Test {
32+
protected String test(final Object parameter) {
33+
return "hi there";
34+
}
35+
}
36+
""",
37+
"""
38+
package io.papermc.test;
39+
40+
public class SuperTest extends Test {
41+
@Override
42+
protected String test(final Object parameter) {
43+
return "hi there but better";
44+
}
45+
}
46+
"""
47+
);
48+
49+
final List<Result> results = Restamp.run(input).getAllResults();
50+
Assertions.assertEquals(
51+
"""
52+
package io.papermc.test;
53+
54+
public class SuperTest extends Test {
55+
@Override
56+
public String test(final Object parameter) {
57+
return "hi there but better";
58+
}
59+
}
60+
""",
61+
results.get(1).getAfter().printAll()
62+
);
63+
}
64+
65+
}

0 commit comments

Comments
 (0)