Skip to content

Commit 2c4e968

Browse files
authored
Generate computer methods at compile time with an annotation processor (#7829)
1 parent 31314be commit 2c4e968

File tree

147 files changed

+3662
-4201
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

147 files changed

+3662
-4201
lines changed

.gitignore

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,5 @@ MDK.zip
2222
*.blend2
2323
/bin
2424
/logs
25-
/src/datagen/*/resources
26-
/src/datagen/*/annotation_generated
27-
!/src/datagen/main/resources/PersistingMekanismDatagenCache.js
28-
!/src/datagen/main/resources/META-INF/coremods.json
2925
/annotation-processor/bin
3026
libs/

annotation-processor/build.gradle

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ plugins {
33
}
44

55
group 'mekanism.annotation-processor'
6-
version '1.1.1'
6+
version '2.0.0'
77
java.toolchain.languageVersion = JavaLanguageVersion.of("${java_version}")
88

99
repositories {
@@ -24,5 +24,6 @@ repositories {
2424
dependencies {
2525
implementation "com.blamejared.crafttweaker:CraftTweaker-forge-${minecraft_version}:${crafttweaker_version}"
2626
//Version of GSON used by vanilla (and thus packed and already downloaded)
27-
implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.9'
28-
}
27+
implementation group: 'com.google.code.gson', name: 'gson', version: '2.10'
28+
implementation "com.squareup:javapoet:1.13.0"
29+
}
Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,33 @@
11
package mekanism;
22

3+
import javax.lang.model.element.Element;
34
import java.lang.annotation.Annotation;
5+
import java.util.ArrayList;
6+
import java.util.List;
47
import java.util.Set;
58

6-
public record AnnotationParamScanner(String targetFile, Set<Class<? extends Annotation>> supportedAnnotations) {
9+
public record AnnotationParamScanner(String targetFile, Set<Class<? extends Annotation>> supportedAnnotations, List<Element> originatingElements) {
710

811
@SafeVarargs
912
public AnnotationParamScanner(String targetFile, Class<? extends Annotation>... supportedAnnotations) {
10-
this(targetFile, Set.of(supportedAnnotations));
13+
this(targetFile, Set.of(supportedAnnotations), new ArrayList<>());
1114
}
12-
}
15+
16+
@Override
17+
public boolean equals(Object o) {
18+
if (this == o) {
19+
return true;
20+
}
21+
22+
return (o instanceof AnnotationParamScanner that) &&
23+
targetFile.equals(that.targetFile) &&
24+
supportedAnnotations.equals(that.supportedAnnotations);
25+
}
26+
27+
@Override
28+
public int hashCode() {
29+
int result = targetFile.hashCode();
30+
result = 31 * result + supportedAnnotations.hashCode();
31+
return result;
32+
}
33+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package mekanism;
2+
3+
import com.squareup.javapoet.JavaFile;
4+
import mekanism.builder.ComputerHandlerBuilder;
5+
6+
import javax.annotation.processing.AbstractProcessor;
7+
import javax.annotation.processing.ProcessingEnvironment;
8+
import javax.annotation.processing.RoundEnvironment;
9+
import javax.annotation.processing.SupportedAnnotationTypes;
10+
import javax.annotation.processing.SupportedSourceVersion;
11+
import javax.lang.model.SourceVersion;
12+
import javax.lang.model.element.Element;
13+
import javax.lang.model.element.TypeElement;
14+
import java.io.IOException;
15+
import java.util.ArrayList;
16+
import java.util.HashMap;
17+
import java.util.List;
18+
import java.util.Map;
19+
import java.util.Set;
20+
21+
/**
22+
* Annotation processor to generate ComputerMethodFactory subclasses for computer methods.
23+
* Must only produce files directly related to the annotated elements, and not based on other elements.
24+
* This is due to being marked as an Isolating processor in Gradle.
25+
* <p>
26+
* Technically we violate this with the Wrapping methods, but if new methods are added to a wrapper,
27+
* a manually triggered full rebuild should catch it.
28+
*/
29+
@SupportedSourceVersion(SourceVersion.RELEASE_17)
30+
@SupportedAnnotationTypes({
31+
MekAnnotationProcessors.ANNOTATION_COMPUTER_METHOD,
32+
MekAnnotationProcessors.ANNOTATION_SYNTHETIC_COMPUTER_METHOD,
33+
MekAnnotationProcessors.ANNOTATION_WRAPPING_COMPUTER_METHOD
34+
})
35+
public class ComputerMethodProcessor extends AbstractProcessor {
36+
@Override
37+
public synchronized void init(ProcessingEnvironment processingEnv) {
38+
super.init(processingEnv);
39+
ComputerHandlerBuilder.init(processingEnv.getElementUtils(), processingEnv.getTypeUtils());
40+
}
41+
42+
@Override
43+
public boolean process(Set<? extends TypeElement> annotatedTypes, RoundEnvironment roundEnvironment) {
44+
//map annotated elements to multimap by enclosing class
45+
Map<TypeElement, List<Element>> annotatedElementsByParent = new HashMap<>();
46+
for (Element element : roundEnvironment.getElementsAnnotatedWithAny(annotatedTypes.toArray(new TypeElement[0]))) {
47+
annotatedElementsByParent.computeIfAbsent((TypeElement) element.getEnclosingElement(), i -> new ArrayList<>()).add(element);
48+
}
49+
50+
annotatedElementsByParent.forEach(this::processTypeWithAnnotations);
51+
52+
return true;
53+
}
54+
55+
private void processTypeWithAnnotations(TypeElement containingType, List<Element> annotatedElements) {
56+
JavaFile factoryFile = new ComputerHandlerBuilder(containingType, processingEnv).build(annotatedElements);
57+
try {
58+
factoryFile.writeTo(processingEnv.getFiler());
59+
} catch (IOException e) {
60+
throw new RuntimeException(e);
61+
}
62+
}
63+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package mekanism;
2+
3+
import com.squareup.javapoet.ClassName;
4+
5+
public class MekAnnotationProcessors {
6+
public static final String MODULE_OPTION = "mekanismModule";
7+
8+
//Packages
9+
public static final String COMPUTER_INTEGRATION_PACKAGE = "mekanism.common.integration.computer";
10+
public static final String ANNOTATION_PACKAGE = COMPUTER_INTEGRATION_PACKAGE + ".annotation";
11+
12+
//annotations
13+
public static final String COMPUTER_METHOD_FACTORY_ANNOTATION_CLASSNAME = ANNOTATION_PACKAGE + ".MethodFactory";
14+
public static final String ANNOTATION_COMPUTER_METHOD = ANNOTATION_PACKAGE + ".ComputerMethod";
15+
public static final String ANNOTATION_SYNTHETIC_COMPUTER_METHOD = ANNOTATION_PACKAGE + ".SyntheticComputerMethod";
16+
public static final String ANNOTATION_WRAPPING_COMPUTER_METHOD = ANNOTATION_PACKAGE + ".WrappingComputerMethod";
17+
18+
//class names
19+
public static final ClassName COMPUTER_METHOD_FACTORY_ANNOTATION = ClassName.bestGuess(COMPUTER_METHOD_FACTORY_ANNOTATION_CLASSNAME);
20+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package mekanism;
2+
3+
import com.squareup.javapoet.ClassName;
4+
import com.squareup.javapoet.CodeBlock;
5+
import com.squareup.javapoet.JavaFile;
6+
import com.squareup.javapoet.MethodSpec;
7+
import com.squareup.javapoet.TypeSpec;
8+
import java.io.Writer;
9+
import java.util.ArrayList;
10+
import java.util.List;
11+
import java.util.Set;
12+
import javax.annotation.processing.AbstractProcessor;
13+
import javax.annotation.processing.ProcessingEnvironment;
14+
import javax.annotation.processing.RoundEnvironment;
15+
import javax.annotation.processing.SupportedAnnotationTypes;
16+
import javax.annotation.processing.SupportedOptions;
17+
import javax.annotation.processing.SupportedSourceVersion;
18+
import javax.lang.model.element.AnnotationMirror;
19+
import javax.lang.model.element.Element;
20+
import javax.lang.model.element.ElementKind;
21+
import javax.lang.model.element.Modifier;
22+
import javax.lang.model.element.TypeElement;
23+
import javax.tools.StandardLocation;
24+
import mekanism.visitors.AnnotationHelper;
25+
26+
import javax.lang.model.SourceVersion;
27+
import javax.lang.model.type.TypeKind;
28+
import javax.lang.model.type.TypeMirror;
29+
import javax.lang.model.util.Types;
30+
import javax.tools.Diagnostic;
31+
import java.io.IOException;
32+
33+
/**
34+
* Gathering (Gradle) annotation processor which generates a ComputerMethodRegistry for the Factories generated
35+
* by the {@link ComputerMethodProcessor} processor.
36+
*/
37+
@SupportedSourceVersion(SourceVersion.RELEASE_17)
38+
@SupportedAnnotationTypes(MekAnnotationProcessors.COMPUTER_METHOD_FACTORY_ANNOTATION_CLASSNAME)
39+
@SupportedOptions(MekAnnotationProcessors.MODULE_OPTION)
40+
public class MethodFactoryProcessor extends AbstractProcessor {
41+
private String mekModule;
42+
private final ClassName factoryRegistry = ClassName.get(MekAnnotationProcessors.COMPUTER_INTEGRATION_PACKAGE, "FactoryRegistry");
43+
private final ClassName methodRegistryInterface = ClassName.get(MekAnnotationProcessors.COMPUTER_INTEGRATION_PACKAGE, "IComputerMethodRegistry");
44+
private final MethodSpec.Builder registryInit = MethodSpec.methodBuilder("register")
45+
.addModifiers(Modifier.PUBLIC)
46+
.addAnnotation(Override.class);
47+
48+
@Override
49+
public synchronized void init(ProcessingEnvironment processingEnv) {
50+
super.init(processingEnv);
51+
mekModule = processingEnv.getOptions().getOrDefault(MekAnnotationProcessors.MODULE_OPTION, "value_not_supplied");
52+
}
53+
54+
@Override
55+
public boolean process(Set<? extends TypeElement> annotatedTypes, RoundEnvironment roundEnvironment) {
56+
TypeMirror methodFactoryType = processingEnv.getElementUtils().getTypeElement(MekAnnotationProcessors.COMPUTER_METHOD_FACTORY_ANNOTATION_CLASSNAME).asType();
57+
TypeSpec.Builder registryType = TypeSpec.classBuilder("ComputerMethodRegistry_" + mekModule)
58+
.addModifiers(Modifier.PUBLIC)
59+
.addSuperinterface(methodRegistryInterface);
60+
61+
//this should only ever be 1 annotation
62+
for (Element element : roundEnvironment.getElementsAnnotatedWithAny(annotatedTypes.toArray(new TypeElement[0]))) {
63+
if (element instanceof TypeElement factoryTypeEl) {
64+
//get the annotation mirror for @MethodFactory
65+
AnnotationMirror annotationMirror = null;
66+
for (AnnotationMirror am : factoryTypeEl.getAnnotationMirrors()) {
67+
if (typeUtils().isSameType(am.getAnnotationType(), methodFactoryType)) {
68+
annotationMirror = am;
69+
break;
70+
}
71+
}
72+
if (annotationMirror == null) {
73+
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Couldn't find annotation mirror", factoryTypeEl);
74+
continue;
75+
}
76+
registryType.addOriginatingElement(factoryTypeEl);
77+
AnnotationHelper helper = new AnnotationHelper(processingEnv.getElementUtils(), annotationMirror);
78+
addHandlerToRegistry((TypeElement) typeUtils().asElement(helper.getClassValue("target")), ClassName.get(factoryTypeEl));
79+
}
80+
}
81+
82+
if (!registryType.originatingElements.isEmpty()) {
83+
registryType.addMethod(registryInit.build());
84+
TypeSpec registrySpec = registryType.build();
85+
String packageName = "mekanism.generated." + mekModule;
86+
try {
87+
JavaFile.builder(packageName, registrySpec).build().writeTo(processingEnv.getFiler());
88+
} catch (IOException e) {
89+
throw new RuntimeException(e);
90+
}
91+
try(Writer serviceWriter = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "","META-INF/services/"+methodRegistryInterface.canonicalName()).openWriter()) {
92+
serviceWriter.write(packageName+"."+registrySpec.name);
93+
} catch (IOException e) {
94+
throw new RuntimeException(e);
95+
}
96+
}
97+
return false;
98+
}
99+
100+
/**
101+
* Gather superclasses for handledType and add a register call
102+
* @param handledType the subject type of the handler
103+
* @param factoryClassName the factory's class name
104+
*/
105+
private void addHandlerToRegistry(TypeElement handledType, ClassName factoryClassName) {
106+
//gather all superclasses (in mekanism package)
107+
List<ClassName> superClasses = new ArrayList<>();
108+
TypeMirror superClass = handledType.getSuperclass();
109+
TypeElement superTypeElement;
110+
do {
111+
superTypeElement = (TypeElement) typeUtils().asElement(superClass);
112+
if (superTypeElement == null) {
113+
break;
114+
}
115+
ClassName clazz = ClassName.get(superTypeElement);
116+
if (clazz.canonicalName().startsWith("mekanism")) {
117+
superClasses.add(0, clazz);
118+
}
119+
} while ((superClass = superTypeElement.getSuperclass()).getKind() != TypeKind.NONE);
120+
121+
//add register call to the factory
122+
String registerName = handledType.getKind() == ElementKind.INTERFACE ? "registerInterface" : "register";
123+
CodeBlock.Builder registerStatement = CodeBlock.builder()
124+
.add("$T.$L($T.class, $T::new", factoryRegistry, registerName, typeUtils().erasure(handledType.asType()), factoryClassName);
125+
//add all super classes, so we don't have to calculate at runtime
126+
for (ClassName cls : superClasses) {
127+
registerStatement.add(", $T.class", cls);
128+
}
129+
registerStatement.add(")");
130+
registryInit.addStatement(registerStatement.build());
131+
}
132+
133+
private Types typeUtils() {
134+
return processingEnv.getTypeUtils();
135+
}
136+
}

0 commit comments

Comments
 (0)