mirror of https://github.com/FabricMC/yarn.git
Merge pull request #3869 from modmuss50/mapping-poet
Migrate Mapping poet to yarn repo
This commit is contained in:
commit
ada73d3b68
58
build.gradle
58
build.gradle
|
@ -60,14 +60,6 @@ configurations {
|
|||
}
|
||||
javadocClasspath
|
||||
decompileClasspath
|
||||
mappingPoetJar {
|
||||
transitive = false
|
||||
}
|
||||
mappingPoet {
|
||||
extendsFrom mappingPoetJar
|
||||
extendsFrom asm
|
||||
transitive = true
|
||||
}
|
||||
}
|
||||
|
||||
def unpickMetaFile = file("unpick-definitions/unpick.json")
|
||||
|
@ -81,7 +73,6 @@ dependencies {
|
|||
javadocClasspath "com.google.code.findbugs:jsr305:3.0.2" // for some other jsr annotations
|
||||
decompileClasspath "net.fabricmc:cfr:${project.cfr_version}"
|
||||
decompileClasspath "org.vineflower:vineflower:${project.vineflower_version}"
|
||||
mappingPoetJar "net.fabricmc:mappingpoet:${project.mappingpoet_version}"
|
||||
asm "org.ow2.asm:asm:${project.asm_version}"
|
||||
asm "org.ow2.asm:asm-tree:${project.asm_version}"
|
||||
asm "org.ow2.asm:asm-commons:${project.asm_version}"
|
||||
|
@ -102,6 +93,7 @@ import cuchaz.enigma.command.CheckMappingsCommand
|
|||
import net.fabricmc.filament.task.MapJarTask
|
||||
import net.fabricmc.filament.task.DownloadTask
|
||||
import net.fabricmc.filament.task.UnpickJarTask
|
||||
import net.fabricmc.filament.task.MappingPoetTask;
|
||||
import net.fabricmc.filament.task.base.WithFileInput
|
||||
import net.fabricmc.filament.task.base.WithFileOutput
|
||||
import net.fabricmc.filament.task.enigma.MapSpecializedMethodsTask
|
||||
|
@ -430,20 +422,12 @@ tasks.register('syncDependencies', Sync) {
|
|||
}
|
||||
|
||||
def fakeSourceDir = file(".gradle/temp/fakeSource")
|
||||
tasks.register('genFakeSource', JavaExec) {
|
||||
dependsOn mergeV2, mapNamedJar, syncDependencies
|
||||
tasks.register('genFakeSource', MappingPoetTask) {
|
||||
group = "javadoc generation"
|
||||
|
||||
inputs.file mergeV2.output
|
||||
inputs.file mapNamedJar.output
|
||||
inputs.dir mcLibsDir
|
||||
|
||||
outputs.dir fakeSourceDir
|
||||
|
||||
mainClass = "net.fabricmc.mappingpoet.Main"
|
||||
classpath configurations.mappingPoet
|
||||
// use merged v2 so we have all namespaces in jd
|
||||
args mergeV2.outputFile.getAbsolutePath(), mapNamedJar.outputFile.getAbsolutePath(), fakeSourceDir.getAbsolutePath(), mcLibsDir.getAbsolutePath()
|
||||
mappings = mergeV2.output
|
||||
minecraftJar = mapNamedJar.output
|
||||
libraries.from(minecraftLibraries)
|
||||
output = fakeSourceDir
|
||||
}
|
||||
|
||||
def decompileOutput = layout.buildDirectory.file("namedSrc")
|
||||
|
@ -480,8 +464,6 @@ javadoc {
|
|||
dependsOn genFakeSource
|
||||
group = "javadoc generation"
|
||||
|
||||
def mappingPoetJar = project.provider { zipTree configurations.mappingPoetJar.singleFile }
|
||||
|
||||
failOnError = false
|
||||
maxMemory = '2G'
|
||||
|
||||
|
@ -498,7 +480,7 @@ javadoc {
|
|||
'implSpec:a:Implementation Requirements:',
|
||||
'implNote:a:Implementation Note:'
|
||||
)
|
||||
taglets "net.fabricmc.mappingpoet.jd.MappingTaglet"
|
||||
taglets "net.fabricmc.filament.mappingpoet.jd.MappingTaglet"
|
||||
// taglet path, header, extra stylesheet settings deferred
|
||||
it.use()
|
||||
|
||||
|
@ -537,26 +519,13 @@ javadoc {
|
|||
source fileTree(fakeSourceDir) + sourceSets.constants.allJava + sourceSets.packageDocs.allJava
|
||||
classpath = configurations.javadocClasspath.plus minecraftLibraries
|
||||
|
||||
def fs = project.services.get(FileSystemOperations.class)
|
||||
def outputDir = javadoc.destinationDir
|
||||
def filamentCodeSource = MappingPoetTask.class.getProtectionDomain().getCodeSource()
|
||||
def filamentJarFile = new File(filamentCodeSource.getLocation().getFile())
|
||||
|
||||
doLast {
|
||||
fs.copy {
|
||||
from mappingPoetJar
|
||||
include "copy_on_click.js"
|
||||
into outputDir
|
||||
}
|
||||
}
|
||||
|
||||
def tagletClasspath = configurations.mappingPoet.files.toList()
|
||||
|
||||
doFirst {
|
||||
// lazy setting
|
||||
options {
|
||||
tagletPath tagletClasspath
|
||||
header mappingPoetJar.get().filter { it.name == 'javadoc_header.txt' }.singleFile.text.trim() // cannot include line breaks
|
||||
addFileOption "-add-stylesheet", mappingPoetJar.get().filter { it.name == 'forms.css' }.singleFile
|
||||
}
|
||||
options {
|
||||
tagletPath = [filamentJarFile]
|
||||
header file("gradle/javadoc/header.txt").text.trim() // cannot include line breaks
|
||||
addFileOption "-add-stylesheet", file("gradle/javadoc/forms.css")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -565,6 +534,7 @@ tasks.register('javadocJar', Jar) {
|
|||
group = "javadoc generation"
|
||||
|
||||
from javadoc.destinationDir
|
||||
from file("gradle/javadoc/copy_on_click.js")
|
||||
|
||||
archiveVersion.set yarnVersion
|
||||
archiveClassifier = 'javadoc'
|
||||
|
|
|
@ -36,9 +36,9 @@ dependencies {
|
|||
implementation "net.fabricmc.unpick:unpick-format-utils:$properties.unpick_version"
|
||||
implementation "net.fabricmc.unpick:unpick-cli:$properties.unpick_version"
|
||||
implementation "net.fabricmc:tiny-remapper:$properties.tiny_remapper_version"
|
||||
implementation "net.fabricmc:mappingpoet:$properties.mappingpoet_version"
|
||||
implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.4.2'
|
||||
implementation 'net.fabricmc:mapping-io:0.5.1'
|
||||
implementation 'net.fabricmc:javapoet:0.1.1'
|
||||
|
||||
// Contains a number of useful utilities we can re-use.
|
||||
implementation ("net.fabricmc:fabric-loom:1.5.7") {
|
||||
|
|
|
@ -0,0 +1,409 @@
|
|||
/*
|
||||
* Copyright (c) 2020 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.filament.mappingpoet;
|
||||
|
||||
import static net.fabricmc.filament.mappingpoet.FieldBuilder.parseAnnotation;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.squareup.javapoet.AnnotationSpec;
|
||||
import com.squareup.javapoet.ClassName;
|
||||
import com.squareup.javapoet.ParameterSpec;
|
||||
import com.squareup.javapoet.TypeSpec;
|
||||
import com.squareup.javapoet.TypeVariableName;
|
||||
import org.objectweb.asm.Handle;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.TypeReference;
|
||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||
import org.objectweb.asm.tree.AnnotationNode;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.objectweb.asm.tree.FieldNode;
|
||||
import org.objectweb.asm.tree.InnerClassNode;
|
||||
import org.objectweb.asm.tree.InvokeDynamicInsnNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
import net.fabricmc.filament.mappingpoet.signature.AnnotationAwareDescriptors;
|
||||
import net.fabricmc.filament.mappingpoet.signature.AnnotationAwareSignatures;
|
||||
import net.fabricmc.filament.mappingpoet.signature.ClassSignature;
|
||||
import net.fabricmc.filament.mappingpoet.signature.TypeAnnotationMapping;
|
||||
import net.fabricmc.filament.mappingpoet.signature.TypeAnnotationStorage;
|
||||
|
||||
public class ClassBuilder {
|
||||
static final Handle OBJ_MTH_BOOTSTRAP = new Handle(
|
||||
Opcodes.H_INVOKESTATIC,
|
||||
"java/lang/runtime/ObjectMethods",
|
||||
"bootstrap",
|
||||
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;",
|
||||
false);
|
||||
|
||||
private final MappingsStore mappings;
|
||||
private final ClassNode classNode;
|
||||
|
||||
private final TypeSpec.Builder builder;
|
||||
private final List<ClassBuilder> innerClasses = new ArrayList<>();
|
||||
private final Environment environment;
|
||||
|
||||
private final ClassSignature signature; // not really signature
|
||||
private final TypeAnnotationMapping typeAnnotations;
|
||||
private boolean annotationClass;
|
||||
private boolean enumClass;
|
||||
private boolean recordClass;
|
||||
private boolean instanceInner = false;
|
||||
// only nonnull if any class in the inner class chain creates a generic <T> decl
|
||||
// omits L and ;
|
||||
private String receiverSignature;
|
||||
|
||||
public ClassBuilder(MappingsStore mappings, ClassNode classNode, Environment environment) {
|
||||
this.mappings = mappings;
|
||||
this.classNode = classNode;
|
||||
this.environment = environment;
|
||||
this.typeAnnotations = setupAnnotations();
|
||||
this.signature = setupSignature();
|
||||
this.builder = setupBuilder();
|
||||
|
||||
addInterfaces();
|
||||
addAnnotations();
|
||||
addJavaDoc();
|
||||
}
|
||||
|
||||
public static ClassName parseInternalName(String internalName) {
|
||||
int classNameSeparator = -1;
|
||||
int index = 0;
|
||||
int nameStart = index;
|
||||
ClassName currentClassName = null;
|
||||
|
||||
char ch;
|
||||
|
||||
do {
|
||||
ch = index == internalName.length() ? ';' : internalName.charAt(index);
|
||||
|
||||
if (ch == '$' || ch == ';') {
|
||||
// collect class name
|
||||
if (currentClassName == null) {
|
||||
String packageName = nameStart < classNameSeparator ? internalName.substring(nameStart, classNameSeparator).replace('/', '.') : "";
|
||||
String simpleName = internalName.substring(classNameSeparator + 1, index);
|
||||
currentClassName = ClassName.get(packageName, simpleName);
|
||||
} else {
|
||||
String simpleName = internalName.substring(classNameSeparator + 1, index);
|
||||
currentClassName = currentClassName.nestedClass(simpleName);
|
||||
}
|
||||
}
|
||||
|
||||
if (ch == '/' || ch == '$') {
|
||||
// Start of simple name
|
||||
classNameSeparator = index;
|
||||
}
|
||||
|
||||
index++;
|
||||
} while (ch != ';');
|
||||
|
||||
if (currentClassName == null) {
|
||||
throw new IllegalArgumentException(String.format("Invalid internal name \"%s\"", internalName));
|
||||
}
|
||||
|
||||
return currentClassName;
|
||||
}
|
||||
|
||||
private TypeAnnotationMapping setupAnnotations() {
|
||||
return TypeAnnotationStorage.builder()
|
||||
.add(classNode.invisibleTypeAnnotations)
|
||||
.add(classNode.visibleTypeAnnotations)
|
||||
.build();
|
||||
}
|
||||
|
||||
public void addMembers() {
|
||||
addMethods();
|
||||
addFields();
|
||||
}
|
||||
|
||||
private ClassSignature setupSignature() {
|
||||
if (classNode.signature == null) {
|
||||
return AnnotationAwareDescriptors.parse(classNode.superName, classNode.interfaces, typeAnnotations, environment);
|
||||
} else {
|
||||
return AnnotationAwareSignatures.parseClassSignature(classNode.signature, typeAnnotations, environment);
|
||||
}
|
||||
}
|
||||
|
||||
private TypeSpec.Builder setupBuilder() {
|
||||
TypeSpec.Builder builder;
|
||||
ClassName name = parseInternalName(classNode.name); // no type anno here
|
||||
|
||||
if (Modifier.isInterface(classNode.access)) {
|
||||
if (classNode.interfaces.size() == 1 && classNode.interfaces.get(0).equals("java/lang/annotation/Annotation")) {
|
||||
builder = TypeSpec.annotationBuilder(name);
|
||||
this.annotationClass = true;
|
||||
} else {
|
||||
builder = TypeSpec.interfaceBuilder(name);
|
||||
}
|
||||
} else if (classNode.superName.equals("java/lang/Enum")) {
|
||||
enumClass = true;
|
||||
builder = TypeSpec.enumBuilder(name);
|
||||
} else if (classNode.superName.equals("java/lang/Record")) {
|
||||
recordClass = true;
|
||||
builder = TypeSpec.recordBuilder(name);
|
||||
} else {
|
||||
builder = TypeSpec.classBuilder(name)
|
||||
.superclass(signature.superclass());
|
||||
}
|
||||
|
||||
if (!signature.generics().isEmpty()) {
|
||||
builder.addTypeVariables(signature.generics());
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(classNode.name);
|
||||
sb.append("<");
|
||||
|
||||
for (TypeVariableName each : signature.generics()) {
|
||||
sb.append("T").append(each.name).append(";");
|
||||
}
|
||||
|
||||
sb.append(">");
|
||||
receiverSignature = sb.toString();
|
||||
}
|
||||
|
||||
return builder
|
||||
.addModifiers(new ModifierBuilder(classNode.access)
|
||||
.checkUnseal(classNode, environment)
|
||||
.getModifiers(ModifierBuilder.getType(enumClass, recordClass)));
|
||||
}
|
||||
|
||||
private void addInterfaces() {
|
||||
if (annotationClass) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (signature != null) {
|
||||
builder.addSuperinterfaces(signature.superinterfaces());
|
||||
return;
|
||||
}
|
||||
|
||||
if (classNode.interfaces.isEmpty()) return;
|
||||
|
||||
for (String iFace : classNode.interfaces) {
|
||||
builder.addSuperinterface(parseInternalName(iFace));
|
||||
}
|
||||
}
|
||||
|
||||
private void addAnnotations() {
|
||||
// type anno already done through class sig
|
||||
addDirectAnnotations(classNode.invisibleAnnotations);
|
||||
addDirectAnnotations(classNode.visibleAnnotations);
|
||||
}
|
||||
|
||||
private void addDirectAnnotations(List<AnnotationNode> regularAnnotations) {
|
||||
if (regularAnnotations == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (AnnotationNode annotation : regularAnnotations) {
|
||||
builder.addAnnotation(parseAnnotation(annotation));
|
||||
}
|
||||
}
|
||||
|
||||
private void addMethods() {
|
||||
if (classNode.methods == null) return;
|
||||
|
||||
methodsLoop: for (MethodNode method : classNode.methods) {
|
||||
if ((method.access & Opcodes.ACC_SYNTHETIC) != 0 || (method.access & Opcodes.ACC_MANDATED) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (method.name.equals("<clinit>")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int formalParamStartIndex = 0;
|
||||
|
||||
if (enumClass) {
|
||||
// Skip enum sugar methods
|
||||
if (method.name.equals("values") && method.desc.equals("()[L" + classNode.name + ";")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (method.name.equals("valueOf") && method.desc.equals("(Ljava/lang/String;)L" + classNode.name + ";")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (method.name.equals("<init>")) {
|
||||
formalParamStartIndex = 2; // 0 String 1 int
|
||||
}
|
||||
}
|
||||
|
||||
if (recordClass) {
|
||||
// skip record sugars
|
||||
if (method.name.equals("equals") && method.desc.equals("(Ljava/lang/Object;)Z")
|
||||
|| method.name.equals("toString") && method.desc.equals("()Ljava/lang/String;")
|
||||
|| method.name.equals("hashCode") && method.desc.equals("()I")) {
|
||||
for (AbstractInsnNode insn : method.instructions) {
|
||||
if (insn instanceof InvokeDynamicInsnNode indy
|
||||
&& indy.bsm.equals(OBJ_MTH_BOOTSTRAP)
|
||||
&& indy.name.equals(method.name)) {
|
||||
continue methodsLoop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// todo test component getters
|
||||
}
|
||||
|
||||
if (instanceInner) {
|
||||
if (method.name.equals("<init>")) {
|
||||
formalParamStartIndex = 1; // 0 this$0
|
||||
}
|
||||
}
|
||||
|
||||
builder.addMethod(new MethodBuilder(mappings, classNode, method, environment, receiverSignature, formalParamStartIndex).build());
|
||||
}
|
||||
}
|
||||
|
||||
private void addFields() {
|
||||
if (classNode.fields == null) return;
|
||||
|
||||
for (FieldNode field : classNode.fields) {
|
||||
if (recordClass && !Modifier.isStatic(field.access)) {
|
||||
// proguard elevates record field access for direct record field gets
|
||||
if (!Modifier.isFinal(field.access) || Modifier.isProtected(field.access) || Modifier.isPublic(field.access)) {
|
||||
System.out.println("abnormal instance field " + field.name + " in record " + getClassName() + ", skipping");
|
||||
} else {
|
||||
var fieldBuilder = new FieldBuilder(mappings, classNode, field, environment);
|
||||
var paramBuilder = ParameterSpec.builder(fieldBuilder.calculateType(), field.name);
|
||||
fieldBuilder.addJavaDoc(paramBuilder);
|
||||
fieldBuilder.addDirectAnnotations(paramBuilder);
|
||||
builder.addRecordComponent(paramBuilder.build());
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((field.access & Opcodes.ACC_SYNTHETIC) != 0 || (field.access & Opcodes.ACC_MANDATED) != 0) {
|
||||
continue; // hide synthetic stuff
|
||||
}
|
||||
|
||||
if ((field.access & Opcodes.ACC_ENUM) == 0) {
|
||||
builder.addField(new FieldBuilder(mappings, classNode, field, environment).build());
|
||||
} else {
|
||||
TypeSpec.Builder enumBuilder = TypeSpec.anonymousClassBuilder("");
|
||||
// jd
|
||||
FieldBuilder.addFieldJavaDoc(enumBuilder, mappings, classNode, field);
|
||||
|
||||
// annotations
|
||||
addDirectAnnotations(enumBuilder, field.invisibleAnnotations);
|
||||
addDirectAnnotations(enumBuilder, field.visibleAnnotations);
|
||||
List<AnnotationSpec> annotations = TypeAnnotationStorage.builder()
|
||||
.add(field.invisibleTypeAnnotations)
|
||||
.add(field.visibleTypeAnnotations)
|
||||
.build().getBank(TypeReference.newTypeReference(TypeReference.FIELD))
|
||||
.getCurrentAnnotations();
|
||||
|
||||
if (!annotations.isEmpty()) {
|
||||
enumBuilder.addAnnotations(annotations); // no custom paths for annotations rip
|
||||
}
|
||||
|
||||
builder.addEnumConstant(field.name, enumBuilder.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addDirectAnnotations(TypeSpec.Builder builder, List<AnnotationNode> regularAnnotations) {
|
||||
if (regularAnnotations == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (AnnotationNode annotation : regularAnnotations) {
|
||||
builder.addAnnotation(parseAnnotation(annotation));
|
||||
}
|
||||
}
|
||||
|
||||
private void addJavaDoc() {
|
||||
mappings.addClassDoc(builder::addJavadoc, classNode.name);
|
||||
}
|
||||
|
||||
public void addInnerClass(ClassBuilder classBuilder) {
|
||||
InnerClassNode innerClassNode = null;
|
||||
|
||||
if (classNode.innerClasses != null) {
|
||||
for (InnerClassNode node : classNode.innerClasses) {
|
||||
if (node.name.equals(classBuilder.classNode.name)) {
|
||||
innerClassNode = node;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (innerClassNode == null) {
|
||||
// fallback
|
||||
classBuilder.builder.addModifiers(javax.lang.model.element.Modifier.PUBLIC);
|
||||
classBuilder.builder.addModifiers(javax.lang.model.element.Modifier.STATIC);
|
||||
} else {
|
||||
if (innerClassNode.outerName == null) {
|
||||
// skip local classes and records, which have null outerName
|
||||
return;
|
||||
}
|
||||
|
||||
if (classBuilder.classNode.outerMethod != null) {
|
||||
// local class per EnclosingMethod attribute
|
||||
return;
|
||||
}
|
||||
|
||||
classBuilder.builder.modifiers.remove(javax.lang.model.element.Modifier.PUBLIC); // this modifier may come from class access
|
||||
classBuilder.builder.addModifiers(new ModifierBuilder(innerClassNode.access)
|
||||
.checkUnseal(classBuilder.classNode, environment)
|
||||
.getModifiers(ModifierBuilder.getType(classBuilder.enumClass, classBuilder.recordClass)));
|
||||
|
||||
if (!Modifier.isStatic(innerClassNode.access)) {
|
||||
classBuilder.instanceInner = true;
|
||||
}
|
||||
|
||||
// consider emit warning if this.instanceInner is true when classBuilder.instanceInner is false
|
||||
|
||||
if (this.receiverSignature != null && classBuilder.instanceInner) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(this.receiverSignature).append("."); // like O<TT;>. for O<T>
|
||||
sb.append(innerClassNode.innerName); // append simple name
|
||||
|
||||
List<TypeVariableName> innerClassGenerics = classBuilder.signature.generics();
|
||||
|
||||
if (!innerClassGenerics.isEmpty()) {
|
||||
sb.append("<");
|
||||
|
||||
for (TypeVariableName each : innerClassGenerics) {
|
||||
sb.append("T").append(each.name).append(";");
|
||||
}
|
||||
|
||||
sb.append(">");
|
||||
}
|
||||
|
||||
classBuilder.receiverSignature = sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
innerClasses.add(classBuilder);
|
||||
}
|
||||
|
||||
public String getClassName() {
|
||||
return classNode.name;
|
||||
}
|
||||
|
||||
public TypeSpec build() {
|
||||
for (ClassBuilder innerCb : innerClasses) {
|
||||
builder.addType(innerCb.build());
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright (c) 2020 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.filament.mappingpoet;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.squareup.javapoet.ClassName;
|
||||
|
||||
import net.fabricmc.filament.mappingpoet.signature.ClassStaticContext;
|
||||
|
||||
/**
|
||||
* Represents an overall runtime environment, knows all inner class,
|
||||
* super class, etc. information.
|
||||
*/
|
||||
public record Environment(
|
||||
Map<String, Collection<String>> superTypes,
|
||||
Set<String> sealedClasses,
|
||||
// declaring classes keep track of namable inner classes
|
||||
// and local/anon classes in whole codebase
|
||||
Map<String, NestedClassInfo> declaringClasses
|
||||
) implements ClassStaticContext {
|
||||
public record NestedClassInfo(String declaringClass, boolean instanceInner, String simpleName) {
|
||||
// two strings are nullable
|
||||
}
|
||||
|
||||
public record ClassNamePointer(String simple, String outerClass) {
|
||||
public ClassName toClassName(ClassName outerClassName) {
|
||||
if (simple == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return outerClassName.nestedClass(simple);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInstanceInner(String internalName) {
|
||||
var info = declaringClasses.get(internalName);
|
||||
return info != null && info.declaringClass != null && info.instanceInner;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,544 @@
|
|||
/*
|
||||
* Copyright (c) 2020 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.filament.mappingpoet;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.squareup.javapoet.AnnotationSpec;
|
||||
import com.squareup.javapoet.ArrayTypeName;
|
||||
import com.squareup.javapoet.ClassName;
|
||||
import com.squareup.javapoet.CodeBlock;
|
||||
import com.squareup.javapoet.FieldSpec;
|
||||
import com.squareup.javapoet.ParameterSpec;
|
||||
import com.squareup.javapoet.TypeName;
|
||||
import com.squareup.javapoet.TypeSpec;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.TypePath;
|
||||
import org.objectweb.asm.TypeReference;
|
||||
import org.objectweb.asm.tree.AnnotationNode;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.objectweb.asm.tree.FieldNode;
|
||||
|
||||
import net.fabricmc.filament.mappingpoet.signature.AnnotationAwareDescriptors;
|
||||
import net.fabricmc.filament.mappingpoet.signature.AnnotationAwareSignatures;
|
||||
import net.fabricmc.filament.mappingpoet.signature.ClassStaticContext;
|
||||
import net.fabricmc.filament.mappingpoet.signature.TypeAnnotationBank;
|
||||
import net.fabricmc.filament.mappingpoet.signature.TypeAnnotationMapping;
|
||||
import net.fabricmc.filament.mappingpoet.signature.TypeAnnotationStorage;
|
||||
|
||||
public class FieldBuilder {
|
||||
private final MappingsStore mappings;
|
||||
private final ClassNode classNode;
|
||||
private final FieldNode fieldNode;
|
||||
private final FieldSpec.Builder builder; // todo extract interface to build both record param/field easily
|
||||
private final TypeAnnotationMapping annotations;
|
||||
private final ClassStaticContext context;
|
||||
|
||||
public FieldBuilder(MappingsStore mappings, ClassNode classNode, FieldNode fieldNode, ClassStaticContext context) {
|
||||
this.mappings = mappings;
|
||||
this.classNode = classNode;
|
||||
this.fieldNode = fieldNode;
|
||||
this.context = context;
|
||||
this.annotations = TypeAnnotationStorage.builder()
|
||||
.add(fieldNode.invisibleTypeAnnotations)
|
||||
.add(fieldNode.visibleTypeAnnotations)
|
||||
.build();
|
||||
this.builder = createBuilder();
|
||||
addDirectAnnotations();
|
||||
addJavaDoc();
|
||||
}
|
||||
|
||||
static void addFieldJavaDoc(TypeSpec.Builder enumBuilder, MappingsStore mappings, ClassNode classNode, FieldNode fieldNode) {
|
||||
mappings.addFieldDoc(enumBuilder::addJavadoc, classNode.name, fieldNode.name, fieldNode.desc);
|
||||
}
|
||||
|
||||
public static AnnotationSpec parseAnnotation(AnnotationNode annotation) {
|
||||
ClassName annoClassName = (ClassName) typeFromDesc(annotation.desc);
|
||||
AnnotationSpec.Builder builder = AnnotationSpec.builder(annoClassName);
|
||||
List<Object> values = annotation.values;
|
||||
|
||||
if (values != null) {
|
||||
Iterator<?> itr = values.iterator();
|
||||
|
||||
while (itr.hasNext()) {
|
||||
String key = (String) itr.next();
|
||||
Object value = itr.next();
|
||||
|
||||
builder.addMember(key, codeFromAnnoValue(value));
|
||||
}
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static CodeBlock codeFromAnnoValue(Object value) {
|
||||
// BCDFIJSZ; String; String[] (for enum); asm type; anno node; list of any prev stuff (cannot nest)
|
||||
if (value instanceof List) {
|
||||
return ((List<?>) value).stream().map(FieldBuilder::codeFromAnnoValue).collect(CodeBlock.joining(",", "{", "}"));
|
||||
}
|
||||
|
||||
if (value instanceof Character || value instanceof Number || value instanceof Boolean) {
|
||||
return CodeBlock.builder().add("$L", value).build();
|
||||
}
|
||||
|
||||
if (value instanceof String) {
|
||||
return CodeBlock.builder().add("$S", value).build();
|
||||
}
|
||||
|
||||
if (value instanceof String[]) {
|
||||
String[] arr = (String[]) value;
|
||||
ClassName enumClassName = (ClassName) typeFromDesc(arr[0]);
|
||||
String valueName = arr[1];
|
||||
return CodeBlock.builder().add("$T.$L", enumClassName, valueName).build();
|
||||
}
|
||||
|
||||
if (value instanceof Type) {
|
||||
return CodeBlock.builder().add("$T.class", typeFromDesc(((Type) value).getDescriptor())).build();
|
||||
}
|
||||
|
||||
if (value instanceof AnnotationNode) {
|
||||
return CodeBlock.builder().add(parseAnnotation((AnnotationNode) value).toString()).build();
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(String.format("Don't know how to convert \"%s\" into annotation value", value));
|
||||
}
|
||||
|
||||
public static TypeName typeFromDesc(final String desc) {
|
||||
return parseType(desc, 0).getValue();
|
||||
}
|
||||
|
||||
public static Map.Entry<Integer, TypeName> parseType(final String desc, final int start) {
|
||||
int index = start;
|
||||
int arrayLevel = 0;
|
||||
|
||||
while (desc.charAt(index) == '[') {
|
||||
arrayLevel++;
|
||||
index++;
|
||||
}
|
||||
|
||||
TypeName current;
|
||||
switch (desc.charAt(index)) {
|
||||
case 'B': {
|
||||
current = TypeName.BYTE;
|
||||
index++;
|
||||
break;
|
||||
}
|
||||
case 'C': {
|
||||
current = TypeName.CHAR;
|
||||
index++;
|
||||
break;
|
||||
}
|
||||
case 'D': {
|
||||
current = TypeName.DOUBLE;
|
||||
index++;
|
||||
break;
|
||||
}
|
||||
case 'F': {
|
||||
current = TypeName.FLOAT;
|
||||
index++;
|
||||
break;
|
||||
}
|
||||
case 'I': {
|
||||
current = TypeName.INT;
|
||||
index++;
|
||||
break;
|
||||
}
|
||||
case 'J': {
|
||||
current = TypeName.LONG;
|
||||
index++;
|
||||
break;
|
||||
}
|
||||
case 'S': {
|
||||
current = TypeName.SHORT;
|
||||
index++;
|
||||
break;
|
||||
}
|
||||
case 'Z': {
|
||||
current = TypeName.BOOLEAN;
|
||||
index++;
|
||||
break;
|
||||
}
|
||||
case 'V': {
|
||||
current = TypeName.VOID;
|
||||
index++;
|
||||
break;
|
||||
}
|
||||
case 'L': {
|
||||
int classNameSeparator = index;
|
||||
index++;
|
||||
int nameStart = index;
|
||||
ClassName currentClassName = null;
|
||||
|
||||
char ch;
|
||||
|
||||
do {
|
||||
ch = desc.charAt(index);
|
||||
|
||||
if (ch == '$' || ch == ';') {
|
||||
// collect class name
|
||||
if (currentClassName == null) {
|
||||
String packageName = nameStart < classNameSeparator ? desc.substring(nameStart, classNameSeparator).replace('/', '.') : "";
|
||||
String simpleName = desc.substring(classNameSeparator + 1, index);
|
||||
currentClassName = ClassName.get(packageName, simpleName);
|
||||
} else {
|
||||
String simpleName = desc.substring(classNameSeparator + 1, index);
|
||||
currentClassName = currentClassName.nestedClass(simpleName);
|
||||
}
|
||||
}
|
||||
|
||||
if (ch == '/' || ch == '$') {
|
||||
// Start of simple name
|
||||
classNameSeparator = index;
|
||||
}
|
||||
|
||||
index++;
|
||||
} while (ch != ';');
|
||||
|
||||
if (currentClassName == null) {
|
||||
throw invalidDesc(desc, index);
|
||||
}
|
||||
|
||||
current = currentClassName;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw invalidDesc(desc, index);
|
||||
}
|
||||
|
||||
for (int i = 0; i < arrayLevel; i++) {
|
||||
current = ArrayTypeName.of(current);
|
||||
}
|
||||
|
||||
return new AbstractMap.SimpleImmutableEntry<>(index, current);
|
||||
}
|
||||
|
||||
public static Map.Entry<Integer, TypeName> parseAnnotatedType(final String desc, final int start, TypeAnnotationBank annotations, ClassStaticContext context) {
|
||||
int index = start;
|
||||
Deque<List<AnnotationSpec>> arrayAnnos = new ArrayDeque<>();
|
||||
|
||||
while (desc.charAt(index) == '[') {
|
||||
arrayAnnos.push(annotations.getCurrentAnnotations());
|
||||
annotations = annotations.advance(TypePath.ARRAY_ELEMENT, 0);
|
||||
index++;
|
||||
}
|
||||
|
||||
TypeName current;
|
||||
switch (desc.charAt(index)) {
|
||||
case 'B': {
|
||||
current = TypeName.BYTE;
|
||||
index++;
|
||||
break;
|
||||
}
|
||||
case 'C': {
|
||||
current = TypeName.CHAR;
|
||||
index++;
|
||||
break;
|
||||
}
|
||||
case 'D': {
|
||||
current = TypeName.DOUBLE;
|
||||
index++;
|
||||
break;
|
||||
}
|
||||
case 'F': {
|
||||
current = TypeName.FLOAT;
|
||||
index++;
|
||||
break;
|
||||
}
|
||||
case 'I': {
|
||||
current = TypeName.INT;
|
||||
index++;
|
||||
break;
|
||||
}
|
||||
case 'J': {
|
||||
current = TypeName.LONG;
|
||||
index++;
|
||||
break;
|
||||
}
|
||||
case 'S': {
|
||||
current = TypeName.SHORT;
|
||||
index++;
|
||||
break;
|
||||
}
|
||||
case 'Z': {
|
||||
current = TypeName.BOOLEAN;
|
||||
index++;
|
||||
break;
|
||||
}
|
||||
case 'V': {
|
||||
current = TypeName.VOID;
|
||||
index++;
|
||||
break;
|
||||
}
|
||||
case 'L': {
|
||||
int classNameSeparator = index;
|
||||
index++;
|
||||
int nameStart = index;
|
||||
ClassName currentClassName = null;
|
||||
boolean instanceInner = false;
|
||||
|
||||
char ch;
|
||||
|
||||
do {
|
||||
ch = desc.charAt(index);
|
||||
|
||||
if (ch == '$' || ch == ';') {
|
||||
// collect class name
|
||||
if (currentClassName == null) {
|
||||
String packageName = nameStart < classNameSeparator ? desc.substring(nameStart, classNameSeparator).replace('/', '.') : "";
|
||||
String simpleName = desc.substring(classNameSeparator + 1, index);
|
||||
currentClassName = ClassName.get(packageName, simpleName);
|
||||
} else {
|
||||
String simpleName = desc.substring(classNameSeparator + 1, index);
|
||||
|
||||
if (!instanceInner && context.isInstanceInner(desc.substring(nameStart, index))) {
|
||||
instanceInner = true;
|
||||
}
|
||||
|
||||
currentClassName = currentClassName.nestedClass(simpleName);
|
||||
|
||||
if (instanceInner) {
|
||||
currentClassName = AnnotationAwareDescriptors.annotate(currentClassName, annotations);
|
||||
annotations = annotations.advance(TypePath.INNER_TYPE, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ch == '/' || ch == '$') {
|
||||
// Start of simple name
|
||||
classNameSeparator = index;
|
||||
}
|
||||
|
||||
index++;
|
||||
} while (ch != ';');
|
||||
|
||||
if (currentClassName == null) {
|
||||
throw invalidDesc(desc, index);
|
||||
}
|
||||
|
||||
current = currentClassName;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw invalidDesc(desc, index);
|
||||
}
|
||||
|
||||
while (!arrayAnnos.isEmpty()) {
|
||||
current = ArrayTypeName.of(current);
|
||||
List<AnnotationSpec> currentAnnos = arrayAnnos.pop();
|
||||
|
||||
if (!currentAnnos.isEmpty()) {
|
||||
current = current.annotated(currentAnnos);
|
||||
}
|
||||
}
|
||||
|
||||
return new AbstractMap.SimpleImmutableEntry<>(index, current);
|
||||
}
|
||||
|
||||
private static IllegalArgumentException invalidDesc(String desc, int index) {
|
||||
return new IllegalArgumentException(String.format("Invalid descriptor at index %d for \"%s\"", index, desc));
|
||||
}
|
||||
|
||||
@Deprecated // use typeFromDesc, non-recursive
|
||||
public static TypeName getFieldType(String desc) {
|
||||
switch (desc) {
|
||||
case "B":
|
||||
return TypeName.BYTE;
|
||||
case "C":
|
||||
return TypeName.CHAR;
|
||||
case "S":
|
||||
return TypeName.SHORT;
|
||||
case "Z":
|
||||
return TypeName.BOOLEAN;
|
||||
case "I":
|
||||
return TypeName.INT;
|
||||
case "J":
|
||||
return TypeName.LONG;
|
||||
case "F":
|
||||
return TypeName.FLOAT;
|
||||
case "D":
|
||||
return TypeName.DOUBLE;
|
||||
case "V":
|
||||
return TypeName.VOID;
|
||||
}
|
||||
|
||||
if (desc.startsWith("[")) {
|
||||
return ArrayTypeName.of(getFieldType(desc.substring(1)));
|
||||
}
|
||||
|
||||
if (desc.startsWith("L")) {
|
||||
return ClassBuilder.parseInternalName(desc.substring(1).substring(0, desc.length() - 2));
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException("Unknown field type" + desc);
|
||||
}
|
||||
|
||||
private FieldSpec.Builder createBuilder() {
|
||||
FieldSpec.Builder ret = FieldSpec.builder(calculateType(), fieldNode.name)
|
||||
.addModifiers(new ModifierBuilder(fieldNode.access).getModifiers(ModifierBuilder.Type.FIELD));
|
||||
|
||||
if ((fieldNode.access & Opcodes.ACC_FINAL) != 0) {
|
||||
ret.initializer(makeInitializer(fieldNode.desc)); // so jd doesn't complain about type mismatch
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private CodeBlock makeInitializer(String desc) {
|
||||
// fake initializers exclude fields from constant values
|
||||
switch (desc.charAt(0)) {
|
||||
case 'B':
|
||||
if (fieldNode.value instanceof Integer) {
|
||||
return CodeBlock.builder().add("(byte) $L", fieldNode.value).build();
|
||||
}
|
||||
|
||||
// fake initializer falls through
|
||||
case 'C':
|
||||
if (fieldNode.value instanceof Integer) {
|
||||
int value = (int) fieldNode.value;
|
||||
char c = (char) value;
|
||||
return printChar(CodeBlock.builder(), c, value).build();
|
||||
}
|
||||
|
||||
// fake initializer falls through
|
||||
case 'D':
|
||||
if (fieldNode.value instanceof Double) {
|
||||
return CodeBlock.builder().add("$LD", fieldNode.value).build();
|
||||
}
|
||||
|
||||
// fake initializer falls through
|
||||
case 'I':
|
||||
if (fieldNode.value instanceof Integer) {
|
||||
return CodeBlock.builder().add("$L", fieldNode.value).build();
|
||||
}
|
||||
|
||||
// fake initializer falls through
|
||||
case 'J':
|
||||
if (fieldNode.value instanceof Long) {
|
||||
return CodeBlock.builder().add("$LL", fieldNode.value).build();
|
||||
}
|
||||
|
||||
// fake initializer falls through
|
||||
case 'S':
|
||||
if (fieldNode.value instanceof Integer) {
|
||||
return CodeBlock.builder().add("(short) $L", fieldNode.value).build();
|
||||
}
|
||||
|
||||
return CodeBlock.builder().add("java.lang.Byte.parseByte(\"dummy\")").build();
|
||||
case 'F':
|
||||
if (fieldNode.value instanceof Float) {
|
||||
return CodeBlock.builder().add("$LF", fieldNode.value).build();
|
||||
}
|
||||
|
||||
return CodeBlock.builder().add("java.lang.Float.parseFloat(\"dummy\")").build();
|
||||
case 'Z':
|
||||
if (fieldNode.value instanceof Integer) {
|
||||
return CodeBlock.builder().add("$L", ((int) fieldNode.value) != 0).build();
|
||||
}
|
||||
|
||||
return CodeBlock.builder().add("java.lang.Boolean.parseBoolean(\"dummy\")").build();
|
||||
}
|
||||
|
||||
if (desc.equals("Ljava/lang/String;") && fieldNode.value instanceof String) {
|
||||
return CodeBlock.builder().add("$S", fieldNode.value).build();
|
||||
}
|
||||
|
||||
return CodeBlock.builder().add(desc.equals("Ljava/lang/String;") ? "java.lang.String.valueOf(\"dummy\")" : "null").build();
|
||||
}
|
||||
|
||||
private static CodeBlock.Builder printChar(CodeBlock.Builder builder, char c, int value) {
|
||||
if (!Character.isValidCodePoint(value) || !Character.isDefined(value)) {
|
||||
return builder.add("(char) $L", value);
|
||||
}
|
||||
|
||||
// See https://docs.oracle.com/javase/specs/jls/se16/html/jls-3.html#jls-EscapeSequence
|
||||
// ignore space or ", just use direct in those cases
|
||||
switch (c) {
|
||||
case '\b':
|
||||
return builder.add("'\\b'");
|
||||
case '\t':
|
||||
return builder.add("'\\t'");
|
||||
case '\n':
|
||||
return builder.add("'\\n'");
|
||||
case '\f':
|
||||
return builder.add("'\\f'");
|
||||
case '\r':
|
||||
return builder.add("'\\r'");
|
||||
case '\'':
|
||||
return builder.add("'\\''");
|
||||
case '\\':
|
||||
return builder.add("'\\\\'");
|
||||
}
|
||||
|
||||
return builder.add("'$L'", c);
|
||||
}
|
||||
|
||||
private void addJavaDoc() {
|
||||
mappings.addFieldDoc(builder::addJavadoc, classNode.name, fieldNode.name, fieldNode.desc);
|
||||
}
|
||||
|
||||
void addJavaDoc(ParameterSpec.Builder paramBuilder) {
|
||||
mappings.addFieldDoc(paramBuilder::addJavadoc, classNode.name, fieldNode.name, fieldNode.desc);
|
||||
}
|
||||
|
||||
private void addDirectAnnotations() {
|
||||
addDirectAnnotations(fieldNode.invisibleAnnotations);
|
||||
addDirectAnnotations(fieldNode.visibleAnnotations);
|
||||
}
|
||||
|
||||
private void addDirectAnnotations(List<AnnotationNode> regularAnnotations) {
|
||||
if (regularAnnotations == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (AnnotationNode annotation : regularAnnotations) {
|
||||
builder.addAnnotation(parseAnnotation(annotation));
|
||||
}
|
||||
}
|
||||
|
||||
void addDirectAnnotations(ParameterSpec.Builder paramBuilder) {
|
||||
addDirectAnnotations(paramBuilder, fieldNode.invisibleAnnotations);
|
||||
addDirectAnnotations(paramBuilder, fieldNode.visibleAnnotations);
|
||||
}
|
||||
|
||||
private void addDirectAnnotations(ParameterSpec.Builder paramBuilder, List<AnnotationNode> regularAnnotations) {
|
||||
if (regularAnnotations == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (AnnotationNode annotation : regularAnnotations) {
|
||||
paramBuilder.addAnnotation(parseAnnotation(annotation));
|
||||
}
|
||||
}
|
||||
|
||||
TypeName calculateType() {
|
||||
if (fieldNode.signature != null) {
|
||||
return AnnotationAwareSignatures.parseFieldSignature(fieldNode.signature, annotations, context);
|
||||
}
|
||||
|
||||
return parseAnnotatedType(fieldNode.desc, 0, annotations.getBank(TypeReference.newTypeReference(TypeReference.FIELD)), context).getValue();
|
||||
}
|
||||
|
||||
public FieldSpec build() {
|
||||
return builder.build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,225 @@
|
|||
/*
|
||||
* Copyright (c) 2020 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.filament.mappingpoet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import com.squareup.javapoet.JavaFile;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.objectweb.asm.tree.InnerClassNode;
|
||||
|
||||
import net.fabricmc.filament.mappingpoet.Environment.ClassNamePointer;
|
||||
import net.fabricmc.filament.mappingpoet.Environment.NestedClassInfo;
|
||||
|
||||
public class MappingPoet {
|
||||
public static void generate(Path mappings, Path inputJar, Path outputDirectory, List<Path> libraries) {
|
||||
final MappingsStore mapping = new MappingsStore(mappings);
|
||||
Map<String, ClassBuilder> classes = new HashMap<>();
|
||||
forEachClass(inputJar, (classNode, environment) -> writeClass(mapping, classNode, classes, environment), libraries);
|
||||
|
||||
for (ClassBuilder classBuilder : classes.values()) {
|
||||
String name = classBuilder.getClassName();
|
||||
if (name.contains("$")) continue;
|
||||
|
||||
try {
|
||||
int packageEnd = classBuilder.getClassName().lastIndexOf("/");
|
||||
String pkgName = packageEnd < 0 ? "" : classBuilder.getClassName().substring(0, packageEnd).replaceAll("/", ".");
|
||||
JavaFile javaFile = JavaFile.builder(pkgName, classBuilder.build()).build();
|
||||
|
||||
javaFile.writeTo(outputDirectory);
|
||||
} catch (Throwable t) {
|
||||
throw new RuntimeException("Failed to process class "+name, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void forEachClass(Path jar, ClassNodeConsumer classNodeConsumer, List<Path> libraries) {
|
||||
List<ClassNode> classes = new ArrayList<>();
|
||||
Map<String, Collection<String>> supers = new HashMap<>();
|
||||
Set<String> sealedClasses = new HashSet<>(); // their subclsses/impls need non-sealed modifier
|
||||
|
||||
Map<String, Environment.NestedClassInfo> nestedClasses = new ConcurrentHashMap<>();
|
||||
Map<String, ClassNamePointer> classNames = new ConcurrentHashMap<>();
|
||||
|
||||
if (libraries.isEmpty()) {
|
||||
scanNestedClasses(classNames, nestedClasses, libraries);
|
||||
}
|
||||
|
||||
try (JarFile jarFile = new JarFile(jar.toFile())) {
|
||||
Enumeration<JarEntry> entryEnumerator = jarFile.entries();
|
||||
|
||||
while (entryEnumerator.hasMoreElements()) {
|
||||
JarEntry entry = entryEnumerator.nextElement();
|
||||
|
||||
if (entry.isDirectory() || !entry.getName().endsWith(".class")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try (InputStream is = jarFile.getInputStream(entry)) {
|
||||
ClassReader reader = new ClassReader(is);
|
||||
ClassNode classNode = new ClassNode();
|
||||
reader.accept(classNode, ClassReader.SKIP_CODE);
|
||||
List<String> superNames = new ArrayList<>();
|
||||
|
||||
if (classNode.superName != null && !classNode.superName.equals("java/lang/Object")) {
|
||||
superNames.add(classNode.superName);
|
||||
}
|
||||
|
||||
if (classNode.interfaces != null) {
|
||||
superNames.addAll(classNode.interfaces);
|
||||
}
|
||||
|
||||
if (!superNames.isEmpty()) {
|
||||
supers.put(classNode.name, superNames);
|
||||
}
|
||||
|
||||
if (classNode.innerClasses != null) {
|
||||
for (InnerClassNode e : classNode.innerClasses) {
|
||||
if (e.outerName != null) {
|
||||
// null -> declared in method/initializer
|
||||
nestedClasses.put(e.name, new NestedClassInfo(e.outerName, !Modifier.isStatic(e.access), e.innerName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (classNode.permittedSubclasses != null) {
|
||||
sealedClasses.add(classNode.name);
|
||||
}
|
||||
|
||||
classes.add(classNode);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
//Sort all the classes making sure that inner classes come after the parent classes
|
||||
classes.sort(Comparator.comparing(o -> o.name));
|
||||
|
||||
for (ClassNode node : classes) {
|
||||
classNodeConsumer.accept(node, new Environment(supers, sealedClasses, nestedClasses));
|
||||
}
|
||||
}
|
||||
|
||||
private static void scanNestedClasses(Map<String, ClassNamePointer> classNames, Map<String, Environment.NestedClassInfo> instanceInnerClasses, List<Path> libraries) {
|
||||
try {
|
||||
for (Path library : libraries) {
|
||||
try (JarFile jarFile = new JarFile(library.toFile())) {
|
||||
Enumeration<JarEntry> entryEnumerator = jarFile.entries();
|
||||
|
||||
while (entryEnumerator.hasMoreElements()) {
|
||||
JarEntry entry = entryEnumerator.nextElement();
|
||||
|
||||
if (entry.isDirectory() || !entry.getName().endsWith(".class")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try (InputStream is = jarFile.getInputStream(entry)) {
|
||||
ClassReader reader = new ClassReader(is);
|
||||
reader.accept(new ClassVisitor(Opcodes.ASM9) {
|
||||
@Override
|
||||
public void visitInnerClass(String name, String outerName, String simpleName, int access) {
|
||||
instanceInnerClasses.put(name, new NestedClassInfo(outerName, !Modifier.isStatic(access), simpleName));
|
||||
|
||||
if (outerName != null) {
|
||||
classNames.put(name, new ClassNamePointer(simpleName, outerName));
|
||||
}
|
||||
}
|
||||
}, ClassReader.SKIP_CODE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isInstanceInnerOnClasspath(String internalName) {
|
||||
String javaBinary = internalName.replace('/', '.');
|
||||
|
||||
try {
|
||||
Class<?> c = Class.forName(javaBinary, false, MappingPoet.class.getClassLoader());
|
||||
return !Modifier.isStatic(c.getModifiers()) && c.getDeclaringClass() != null;
|
||||
} catch (Throwable ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isDigit(char ch) {
|
||||
return ch >= '0' && ch <= '9';
|
||||
}
|
||||
|
||||
private static void writeClass(MappingsStore mappings, ClassNode classNode, Map<String, ClassBuilder> existingClasses, Environment environment) {
|
||||
// TODO make sure named jar has valid InnerClasses, use that info instead
|
||||
String name = classNode.name;
|
||||
|
||||
{
|
||||
//Block anonymous class and their nested classes
|
||||
int lastSearch = name.length();
|
||||
|
||||
while (lastSearch != -1) {
|
||||
lastSearch = name.lastIndexOf('$', lastSearch - 1);
|
||||
|
||||
// names starting with digit is illegal java
|
||||
if (isDigit(name.charAt(lastSearch + 1))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: ensure InnerClasses is remapped, and create ClassName from parent class name
|
||||
ClassBuilder classBuilder = new ClassBuilder(mappings, classNode, environment);
|
||||
|
||||
if (name.contains("$")) {
|
||||
String parentClass = name.substring(0, name.lastIndexOf("$"));
|
||||
|
||||
if (!existingClasses.containsKey(parentClass)) {
|
||||
throw new RuntimeException("Could not find parent class: " + parentClass + " for " + classNode.name);
|
||||
}
|
||||
|
||||
existingClasses.get(parentClass).addInnerClass(classBuilder);
|
||||
}
|
||||
|
||||
classBuilder.addMembers();
|
||||
existingClasses.put(name, classBuilder);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface ClassNodeConsumer {
|
||||
void accept(ClassNode node, Environment environment);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
/*
|
||||
* Copyright (c) 2020 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.filament.mappingpoet;
|
||||
|
||||
import static net.fabricmc.mappingio.tree.MappingTreeView.SRC_NAMESPACE_ID;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.AbstractMap.SimpleImmutableEntry;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import net.fabricmc.mappingio.MappingReader;
|
||||
import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch;
|
||||
import net.fabricmc.mappingio.format.MappingFormat;
|
||||
import net.fabricmc.mappingio.tree.MappingTreeView;
|
||||
import net.fabricmc.mappingio.tree.MappingTreeView.ClassMappingView;
|
||||
import net.fabricmc.mappingio.tree.MappingTreeView.ElementMappingView;
|
||||
import net.fabricmc.mappingio.tree.MappingTreeView.MethodMappingView;
|
||||
import net.fabricmc.mappingio.tree.MemoryMappingTree;
|
||||
|
||||
//Taken from loom
|
||||
public class MappingsStore {
|
||||
private final MappingTreeView tree;
|
||||
private final int maxNamespace;
|
||||
|
||||
public MappingsStore(Path tinyFile) {
|
||||
this.tree = readMappings(tinyFile);
|
||||
this.maxNamespace = tree.getMaxNamespaceId();
|
||||
}
|
||||
|
||||
private static MappingTreeView readMappings(Path input) {
|
||||
var tree = new MemoryMappingTree();
|
||||
|
||||
try {
|
||||
MappingReader.read(input, MappingFormat.TINY_2_FILE, new MappingSourceNsSwitch(tree, "named"));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to read mappings", e);
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
private void addDoc(ElementMappingView element, DocAdder adder) {
|
||||
String doc = element.getComment();
|
||||
|
||||
if (doc != null) {
|
||||
adder.addJavadoc("$L", doc);
|
||||
}
|
||||
}
|
||||
|
||||
public void addClassDoc(DocAdder adder, String className) {
|
||||
var classDef = tree.getClass(className);
|
||||
|
||||
if (classDef == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
addDoc(classDef, adder);
|
||||
adder.addJavadoc("\n");
|
||||
|
||||
for (int id = SRC_NAMESPACE_ID; id < maxNamespace; id++) {
|
||||
String transformedName = classDef.getName(id);
|
||||
adder.addJavadoc("@mapping {@literal $L:$L}\n", tree.getNamespaceName(id), transformedName);
|
||||
}
|
||||
}
|
||||
|
||||
public void addFieldDoc(DocAdder addJavadoc, String owner, String name, String desc) {
|
||||
var classDef = tree.getClass(owner);
|
||||
|
||||
if (classDef == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var fieldDef = classDef.getField(name, desc);
|
||||
|
||||
if (fieldDef == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
addDoc(fieldDef, addJavadoc);
|
||||
addJavadoc.addJavadoc("\n");
|
||||
|
||||
for (int id = SRC_NAMESPACE_ID; id < maxNamespace; id++) {
|
||||
String transformedName = fieldDef.getName(id);
|
||||
String mixinForm = "L" + classDef.getName(id) + ";" + transformedName + ":" + fieldDef.getDesc(id);
|
||||
addJavadoc.addJavadoc("@mapping {@literal $L:$L:$L}\n", tree.getNamespaceName(id), transformedName, mixinForm);
|
||||
}
|
||||
}
|
||||
|
||||
public Map.Entry<String, String> getParamNameAndDoc(Environment environment, String owner, String name, String desc, int index) {
|
||||
var found = searchMethod(environment, owner, name, desc);
|
||||
|
||||
if (found != null) {
|
||||
var methodDef = found.getValue();
|
||||
|
||||
if (methodDef.getArgs().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return methodDef.getArgs().stream()
|
||||
.filter(param -> param.getLvIndex() == index)
|
||||
// Map.entry() is null-hostile
|
||||
.map(param -> new SimpleImmutableEntry<>(param.getSrcName(), param.getComment()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void addMethodDoc(DocAdder adder, Environment environment, String owner, String name, String desc) {
|
||||
var found = searchMethod(environment, owner, name, desc);
|
||||
|
||||
if (found == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var methodDef = found.getValue();
|
||||
var ownerDef = found.getKey();
|
||||
|
||||
if (!ownerDef.equals(methodDef.getOwner())) {
|
||||
adder.addJavadoc("{@inheritDoc}");
|
||||
} else {
|
||||
addDoc(methodDef, adder);
|
||||
}
|
||||
|
||||
adder.addJavadoc("\n");
|
||||
|
||||
for (int id = SRC_NAMESPACE_ID; id < maxNamespace; id++) {
|
||||
String transformedName = methodDef.getName(id);
|
||||
String mixinForm = "L" + ownerDef.getName(id) + ";" + transformedName + methodDef.getDesc(id);
|
||||
adder.addJavadoc("@mapping {@literal $L:$L:$L}\n", tree.getNamespaceName(id), transformedName, mixinForm);
|
||||
}
|
||||
}
|
||||
|
||||
private Map.Entry<ClassMappingView, MethodMappingView> searchMethod(Environment environment, String owner, String name, String desc) {
|
||||
var classDef = tree.getClass(owner);
|
||||
|
||||
if (classDef == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var methodDef = classDef.getMethod(name, desc);
|
||||
|
||||
if (methodDef != null) {
|
||||
return Map.entry(methodDef.getOwner(), methodDef);
|
||||
}
|
||||
|
||||
for (String superName : environment.superTypes().getOrDefault(owner, List.of())) {
|
||||
var ret = searchMethod(environment, superName, name, desc);
|
||||
|
||||
if (ret != null) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public interface DocAdder {
|
||||
void addJavadoc(String format, Object... args);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,373 @@
|
|||
/*
|
||||
* Copyright (c) 2020 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.filament.mappingpoet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
import com.squareup.javapoet.MethodSpec;
|
||||
import com.squareup.javapoet.ParameterSpec;
|
||||
import com.squareup.javapoet.TypeName;
|
||||
import org.objectweb.asm.TypeReference;
|
||||
import org.objectweb.asm.tree.AnnotationNode;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
import net.fabricmc.filament.mappingpoet.signature.AnnotationAwareDescriptors;
|
||||
import net.fabricmc.filament.mappingpoet.signature.AnnotationAwareSignatures;
|
||||
import net.fabricmc.filament.mappingpoet.signature.MethodSignature;
|
||||
import net.fabricmc.filament.mappingpoet.signature.TypeAnnotationBank;
|
||||
import net.fabricmc.filament.mappingpoet.signature.TypeAnnotationMapping;
|
||||
import net.fabricmc.filament.mappingpoet.signature.TypeAnnotationStorage;
|
||||
|
||||
public class MethodBuilder {
|
||||
private static final Set<String> RESERVED_KEYWORDS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
|
||||
"abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue",
|
||||
"default", "do", "double", "else", "enum", "extends", "final", "finally", "float", "for", "goto", "if",
|
||||
"implements", "import", "instanceof", "int", "interface", "long", "native", "new", "package", "private",
|
||||
"protected", "public", "return", "short", "static", "strictfp", "super", "switch", "synchronized", "this",
|
||||
"throw", "throws", "transient", "try", "void", "volatile", "while"
|
||||
)));
|
||||
private final MappingsStore mappings;
|
||||
private final ClassNode classNode;
|
||||
private final MethodNode methodNode;
|
||||
private final MethodSpec.Builder builder;
|
||||
private final Environment environment;
|
||||
private final int formalParamStartIndex;
|
||||
private final String receiverSignature;
|
||||
private final TypeAnnotationMapping typeAnnotations;
|
||||
|
||||
private MethodSignature signature;
|
||||
|
||||
public MethodBuilder(MappingsStore mappings, ClassNode classNode, MethodNode methodNode, Environment environment, String receiverSignature, int formalParamStartIndex) {
|
||||
this.mappings = mappings;
|
||||
this.classNode = classNode;
|
||||
this.methodNode = methodNode;
|
||||
this.environment = environment;
|
||||
this.receiverSignature = receiverSignature;
|
||||
this.formalParamStartIndex = formalParamStartIndex;
|
||||
|
||||
typeAnnotations = TypeAnnotationStorage.builder()
|
||||
.add(methodNode.invisibleTypeAnnotations)
|
||||
.add(methodNode.visibleTypeAnnotations)
|
||||
.build();
|
||||
|
||||
this.builder = createBuilder();
|
||||
addJavaDoc();
|
||||
addAnnotations();
|
||||
setReturnType();
|
||||
addParameters();
|
||||
addExceptions();
|
||||
}
|
||||
|
||||
private static void addDirectAnnotations(ParameterSpec.Builder builder, List<AnnotationNode>[] regularAnnotations, int index) {
|
||||
if (regularAnnotations == null || regularAnnotations.length <= index) {
|
||||
return;
|
||||
}
|
||||
|
||||
addDirectAnnotations(builder, regularAnnotations[index]);
|
||||
}
|
||||
|
||||
private static void addDirectAnnotations(ParameterSpec.Builder builder, List<AnnotationNode> regularAnnotations) {
|
||||
if (regularAnnotations == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (AnnotationNode annotation : regularAnnotations) {
|
||||
builder.addAnnotation(FieldBuilder.parseAnnotation(annotation));
|
||||
}
|
||||
}
|
||||
|
||||
private static IllegalArgumentException invalidMethodDesc(String desc, int index) {
|
||||
return new IllegalArgumentException(String.format("Invalid method descriptor at %d: \"%s\"", index, desc));
|
||||
}
|
||||
|
||||
static String reserveValidName(String suggestedName, Set<String> usedNames) {
|
||||
if (!usedNames.contains(suggestedName)) {
|
||||
usedNames.add(suggestedName);
|
||||
return suggestedName;
|
||||
}
|
||||
|
||||
int t = 2;
|
||||
String currentSuggestion = suggestedName + t;
|
||||
|
||||
while (usedNames.contains(currentSuggestion)) {
|
||||
t++;
|
||||
currentSuggestion = suggestedName + t;
|
||||
}
|
||||
|
||||
usedNames.add(currentSuggestion);
|
||||
|
||||
return currentSuggestion;
|
||||
}
|
||||
|
||||
static String suggestName(TypeName type) {
|
||||
String str = type.withoutAnnotations().toString();
|
||||
int newStart = 0;
|
||||
int newEnd = str.length();
|
||||
int ltStart;
|
||||
ltStart = str.indexOf('<', newStart);
|
||||
|
||||
if (ltStart != -1 && ltStart < newEnd) {
|
||||
newEnd = ltStart;
|
||||
}
|
||||
|
||||
ltStart = str.indexOf('[', newStart);
|
||||
|
||||
if (ltStart != -1 && ltStart < newEnd) {
|
||||
newEnd = ltStart;
|
||||
}
|
||||
|
||||
int dotEnd;
|
||||
|
||||
if ((dotEnd = str.lastIndexOf(".", newEnd)) != -1) {
|
||||
newStart = dotEnd + 1;
|
||||
}
|
||||
|
||||
str = Character.toLowerCase(str.charAt(newStart)) + str.substring(newStart + 1, newEnd);
|
||||
|
||||
if (str.equals("boolean")) {
|
||||
str = "bool";
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
private MethodSpec.Builder createBuilder() {
|
||||
MethodSpec.Builder builder = MethodSpec.methodBuilder(methodNode.name)
|
||||
.addModifiers(new ModifierBuilder(methodNode.access).getModifiers(ModifierBuilder.Type.METHOD));
|
||||
if (methodNode.name.equals("<init>") || !java.lang.reflect.Modifier.isInterface(classNode.access) || java.lang.reflect.Modifier.isPrivate(methodNode.access)) {
|
||||
builder.modifiers.remove(Modifier.DEFAULT);
|
||||
}
|
||||
|
||||
if (methodNode.signature != null) {
|
||||
signature = AnnotationAwareSignatures.parseMethodSignature(methodNode.signature, typeAnnotations, environment);
|
||||
builder.addTypeVariables(signature.generics());
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private void addAnnotations() {
|
||||
addDirectAnnotations(methodNode.invisibleAnnotations);
|
||||
addDirectAnnotations(methodNode.visibleAnnotations);
|
||||
}
|
||||
|
||||
private void addDirectAnnotations(List<AnnotationNode> regularAnnotations) {
|
||||
if (regularAnnotations == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (AnnotationNode annotation : regularAnnotations) {
|
||||
builder.addAnnotation(FieldBuilder.parseAnnotation(annotation));
|
||||
}
|
||||
}
|
||||
|
||||
private void setReturnType() {
|
||||
//Skip constructors
|
||||
if (methodNode.name.equals("<init>")) {
|
||||
return;
|
||||
}
|
||||
|
||||
TypeName typeName;
|
||||
|
||||
if (signature != null) {
|
||||
typeName = signature.result();
|
||||
} else {
|
||||
String returnDesc = methodNode.desc.substring(methodNode.desc.lastIndexOf(")") + 1);
|
||||
typeName = AnnotationAwareDescriptors.parseDesc(returnDesc, typeAnnotations.getBank(TypeReference.newTypeReference(TypeReference.METHOD_RETURN)), environment);
|
||||
}
|
||||
|
||||
builder.returns(typeName);
|
||||
|
||||
if (typeName != TypeName.VOID && !builder.modifiers.contains(Modifier.ABSTRACT)) {
|
||||
builder.addStatement("throw new RuntimeException()");
|
||||
} else if (methodNode.annotationDefault != null) {
|
||||
builder.defaultValue(FieldBuilder.codeFromAnnoValue(methodNode.annotationDefault));
|
||||
}
|
||||
}
|
||||
|
||||
private void addParameters(MethodBuilder this) {
|
||||
// todo fix enum ctors
|
||||
List<ParamType> paramTypes = new ArrayList<>();
|
||||
boolean instanceMethod = !builder.modifiers.contains(Modifier.STATIC);
|
||||
Set<String> usedParamNames = new HashSet<>(RESERVED_KEYWORDS);
|
||||
getParams(paramTypes, instanceMethod, usedParamNames);
|
||||
|
||||
// generate receiver param for type annos
|
||||
|
||||
TypeAnnotationBank receiverAnnos = typeAnnotations.getBank(TypeReference.newTypeReference(TypeReference.METHOD_RECEIVER));
|
||||
|
||||
if (!receiverAnnos.isEmpty()) {
|
||||
ParameterSpec.Builder receiverBuilder;
|
||||
|
||||
// only instance inner class ctor can have receivers
|
||||
if (methodNode.name.equals("<init>")) {
|
||||
TypeName annotatedReceiver = AnnotationAwareSignatures.parseSignature("L" + receiverSignature.substring(0, receiverSignature.lastIndexOf('.')) + ";", receiverAnnos, environment);
|
||||
// vulnerable heuristics
|
||||
String simpleNameChain = classNode.name.substring(classNode.name.lastIndexOf('/') + 1);
|
||||
int part1 = simpleNameChain.lastIndexOf('$'); // def exists
|
||||
int part2 = simpleNameChain.lastIndexOf('$', part1 - 1); // may be -1
|
||||
String usedName = simpleNameChain.substring(part2 + 1, part1);
|
||||
receiverBuilder = ParameterSpec.builder(annotatedReceiver, usedName + ".this");
|
||||
} else {
|
||||
TypeName annotatedReceiver = AnnotationAwareSignatures.parseSignature("L" + receiverSignature + ";", receiverAnnos, environment);
|
||||
receiverBuilder = ParameterSpec.builder(annotatedReceiver, "this");
|
||||
}
|
||||
|
||||
// receiver param cannot have its jd/param anno except type use anno
|
||||
builder.addParameter(receiverBuilder.build());
|
||||
}
|
||||
|
||||
List<AnnotationNode>[] visibleParameterAnnotations = methodNode.visibleParameterAnnotations;
|
||||
List<AnnotationNode>[] invisibleParameterAnnotations = methodNode.invisibleParameterAnnotations;
|
||||
int index = 0;
|
||||
|
||||
for (ParamType paramType : paramTypes) {
|
||||
paramType.fillName(usedParamNames);
|
||||
ParameterSpec.Builder paramBuilder = ParameterSpec.builder(paramType.type, paramType.name, paramType.modifiers);
|
||||
|
||||
if (paramType.comment != null) {
|
||||
paramBuilder.addJavadoc(paramType.comment + "\n");
|
||||
}
|
||||
|
||||
addDirectAnnotations(paramBuilder, visibleParameterAnnotations, index);
|
||||
addDirectAnnotations(paramBuilder, invisibleParameterAnnotations, index);
|
||||
builder.addParameter(paramBuilder.build());
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
private void getParams(List<ParamType> paramTypes, boolean instance, Set<String> usedParamNames) {
|
||||
int slot = instance ? 1 : 0;
|
||||
final String desc = methodNode.desc;
|
||||
int paramIndex = 0;
|
||||
int index = 0;
|
||||
|
||||
if (desc.charAt(index) != '(') {
|
||||
throw invalidMethodDesc(desc, index);
|
||||
}
|
||||
|
||||
index++; // consume '('
|
||||
|
||||
Iterator<TypeName> signatureParamIterator = signature == null ? Collections.emptyIterator() : signature.parameters().iterator();
|
||||
|
||||
while (desc.charAt(index) != ')') {
|
||||
int oldIndex = index;
|
||||
Map.Entry<Integer, TypeName> parsedParam = FieldBuilder.parseType(desc, index);
|
||||
index = parsedParam.getKey();
|
||||
TypeName nonAnnotatedParsedType = parsedParam.getValue();
|
||||
|
||||
if (paramIndex >= formalParamStartIndex) { // skip guessed synthetic/implicit params
|
||||
TypeName parsedType;
|
||||
|
||||
if (signatureParamIterator.hasNext()) {
|
||||
parsedType = signatureParamIterator.next();
|
||||
} else {
|
||||
parsedType = AnnotationAwareDescriptors.parseDesc(desc.substring(oldIndex, index), typeAnnotations.getBank(TypeReference.newFormalParameterReference(paramIndex - formalParamStartIndex)), environment);
|
||||
}
|
||||
|
||||
paramTypes.add(new ParamType(mappings.getParamNameAndDoc(environment, classNode.name, methodNode.name, methodNode.desc, slot), parsedType, usedParamNames, slot));
|
||||
}
|
||||
|
||||
slot++;
|
||||
|
||||
if (nonAnnotatedParsedType.equals(TypeName.DOUBLE) || nonAnnotatedParsedType.equals(TypeName.LONG)) {
|
||||
slot++;
|
||||
}
|
||||
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
/* bruh, we don't care about return type
|
||||
index++; // consume ')'
|
||||
Map.Entry<Integer, TypeName> parsedReturn = FieldBuilder.parseType(desc, index);
|
||||
index = parsedReturn.getKey();
|
||||
TypeName returnType = parsedReturn.getValue();
|
||||
*/
|
||||
}
|
||||
|
||||
private void addExceptions() {
|
||||
if (signature != null && !signature.thrown().isEmpty()) {
|
||||
for (TypeName each : signature.thrown()) {
|
||||
builder.addException(each);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> exceptions = methodNode.exceptions;
|
||||
|
||||
if (exceptions != null) {
|
||||
int index = 0;
|
||||
|
||||
for (String internalName : exceptions) {
|
||||
builder.addException(AnnotationAwareDescriptors.parseType(internalName, typeAnnotations.getBank(TypeReference.newExceptionReference(index)), environment));
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addJavaDoc() {
|
||||
mappings.addMethodDoc(builder::addJavadoc, environment, classNode.name, methodNode.name, methodNode.desc);
|
||||
}
|
||||
|
||||
public MethodSpec build() {
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private class ParamType {
|
||||
final String comment;
|
||||
private final TypeName type;
|
||||
private final Modifier[] modifiers;
|
||||
private String name;
|
||||
|
||||
ParamType(Map.Entry<String, String> nameAndDoc, TypeName type, Set<String> usedNames, int slot) {
|
||||
this.name = nameAndDoc != null ? nameAndDoc.getKey() : null;
|
||||
|
||||
if (this.name != null) {
|
||||
if (usedNames.contains(this.name)) {
|
||||
System.err.printf("Overridden parameter name detected in %s %s %s slot %d, resetting%n", classNode.name, methodNode.name, methodNode.desc, slot);
|
||||
this.name = null;
|
||||
} else {
|
||||
usedNames.add(this.name);
|
||||
}
|
||||
}
|
||||
|
||||
this.comment = nameAndDoc == null ? null : nameAndDoc.getValue();
|
||||
this.type = type;
|
||||
this.modifiers = new ModifierBuilder(0)
|
||||
.getModifiers(ModifierBuilder.Type.PARAM);
|
||||
}
|
||||
|
||||
private void fillName(Set<String> usedNames) {
|
||||
if (name != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
name = reserveValidName(suggestName(type), usedNames);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Copyright (c) 2020 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.filament.mappingpoet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
|
||||
public class ModifierBuilder {
|
||||
private final int access;
|
||||
private boolean needsUnseal;
|
||||
|
||||
public ModifierBuilder(int access) {
|
||||
this.access = access;
|
||||
}
|
||||
|
||||
public ModifierBuilder checkUnseal(ClassNode node, Environment env) {
|
||||
if (java.lang.reflect.Modifier.isFinal(node.access)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
if (node.interfaces != null) {
|
||||
for (String itf : node.interfaces) {
|
||||
if (env.sealedClasses().contains(itf)) {
|
||||
needsUnseal = true;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (node.superName != null && env.sealedClasses().contains(node.superName)) {
|
||||
needsUnseal = true;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Modifier[] getModifiers(Type type) {
|
||||
List<Modifier> modifiers = new ArrayList<>();
|
||||
|
||||
if (type == Type.PARAM) {
|
||||
if (java.lang.reflect.Modifier.isFinal(access)) {
|
||||
modifiers.add(Modifier.FINAL);
|
||||
}
|
||||
|
||||
return modifiers.toArray(new Modifier[]{});
|
||||
}
|
||||
|
||||
if (java.lang.reflect.Modifier.isPublic(access)) {
|
||||
modifiers.add(Modifier.PUBLIC);
|
||||
} else if (java.lang.reflect.Modifier.isPrivate(access)) {
|
||||
modifiers.add(Modifier.PRIVATE);
|
||||
} else if (java.lang.reflect.Modifier.isProtected(access)) {
|
||||
modifiers.add(Modifier.PROTECTED);
|
||||
}
|
||||
|
||||
if (java.lang.reflect.Modifier.isAbstract(access) && type != Type.ENUM) {
|
||||
modifiers.add(Modifier.ABSTRACT);
|
||||
}
|
||||
|
||||
if (java.lang.reflect.Modifier.isStatic(access)) {
|
||||
modifiers.add(Modifier.STATIC);
|
||||
}
|
||||
|
||||
if (!java.lang.reflect.Modifier.isAbstract(access) && !java.lang.reflect.Modifier.isStatic(access) && type == Type.METHOD) {
|
||||
modifiers.add(Modifier.DEFAULT);
|
||||
}
|
||||
|
||||
if (java.lang.reflect.Modifier.isFinal(access) && type != Type.ENUM && type != Type.RECORD) {
|
||||
modifiers.add(Modifier.FINAL);
|
||||
}
|
||||
|
||||
if (java.lang.reflect.Modifier.isTransient(access) && type == Type.FIELD) {
|
||||
modifiers.add(Modifier.TRANSIENT);
|
||||
}
|
||||
|
||||
if (java.lang.reflect.Modifier.isVolatile(access) && type == Type.FIELD) {
|
||||
modifiers.add(Modifier.VOLATILE);
|
||||
}
|
||||
|
||||
if (java.lang.reflect.Modifier.isSynchronized(access) && type == Type.METHOD) {
|
||||
modifiers.add(Modifier.SYNCHRONIZED);
|
||||
}
|
||||
|
||||
if (java.lang.reflect.Modifier.isNative(access) && type == Type.METHOD) {
|
||||
modifiers.add(Modifier.NATIVE);
|
||||
}
|
||||
|
||||
if (java.lang.reflect.Modifier.isStrict(access)) {
|
||||
modifiers.add(Modifier.STRICTFP); // obsolete as of Java 17
|
||||
}
|
||||
|
||||
if (needsUnseal && type == Type.CLASS) {
|
||||
modifiers.add(Modifier.NON_SEALED);
|
||||
}
|
||||
|
||||
return modifiers.toArray(new Modifier[]{});
|
||||
}
|
||||
|
||||
public static Type getType(boolean enumType, boolean recordType) {
|
||||
return enumType ? Type.ENUM : recordType ? Type.RECORD : Type.CLASS;
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
CLASS,
|
||||
ENUM,
|
||||
RECORD,
|
||||
METHOD,
|
||||
FIELD,
|
||||
PARAM
|
||||
}
|
||||
}
|
|
@ -0,0 +1,523 @@
|
|||
/*
|
||||
* Copyright (c) 2020 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.filament.mappingpoet;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
import com.squareup.javapoet.ArrayTypeName;
|
||||
import com.squareup.javapoet.ClassName;
|
||||
import com.squareup.javapoet.ParameterizedTypeName;
|
||||
import com.squareup.javapoet.TypeName;
|
||||
import com.squareup.javapoet.TypeVariableName;
|
||||
import com.squareup.javapoet.WildcardTypeName;
|
||||
|
||||
import net.fabricmc.filament.mappingpoet.signature.ClassSignature;
|
||||
import net.fabricmc.filament.mappingpoet.signature.MethodSignature;
|
||||
|
||||
public final class Signatures {
|
||||
public static ClassSignature parseClassSignature(final String signature) {
|
||||
// <A:Labc.Def:Ljava.util.Iterable<Ljava/lang.Object;>;B:Ljava/lang/Object>Ljava/lang/Object; etc etc
|
||||
int index = 0;
|
||||
char ch;
|
||||
List<TypeVariableName> generics = Collections.emptyList();
|
||||
|
||||
if (signature.charAt(0) == '<') {
|
||||
// parse generic decl
|
||||
index++; // consume '<'
|
||||
|
||||
// parse type params e.g. <A, B>
|
||||
generics = new LinkedList<>();
|
||||
|
||||
while ((ch = signature.charAt(index)) != '>') {
|
||||
int genericNameStart = index;
|
||||
|
||||
if (ch == ':') {
|
||||
throw errorAt(signature, index);
|
||||
}
|
||||
|
||||
do {
|
||||
index++;
|
||||
} while (signature.charAt(index) != ':');
|
||||
|
||||
String genericName = signature.substring(genericNameStart, index);
|
||||
|
||||
List<TypeName> bounds = new LinkedList<>();
|
||||
boolean classBound = true;
|
||||
|
||||
while (signature.charAt(index) == ':') {
|
||||
// parse bounds
|
||||
index++; // consume ':'
|
||||
|
||||
if (classBound && signature.charAt(index) == ':') {
|
||||
// No class bound, only interface bounds, so '::'
|
||||
classBound = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
classBound = false;
|
||||
Map.Entry<Integer, TypeName> bound = parseParameterizedType(signature, index);
|
||||
index = bound.getKey();
|
||||
bounds.add(bound.getValue());
|
||||
}
|
||||
|
||||
generics.add(TypeVariableName.get(genericName, bounds.toArray(new TypeName[0])));
|
||||
}
|
||||
|
||||
index++; // consume '>'
|
||||
}
|
||||
|
||||
LinkedList<TypeName> supers = new LinkedList<>();
|
||||
|
||||
while (index < signature.length()) {
|
||||
Map.Entry<Integer, TypeName> bound = parseParameterizedType(signature, index);
|
||||
index = bound.getKey();
|
||||
supers.add(bound.getValue());
|
||||
}
|
||||
|
||||
return new ClassSignature(generics, supers.removeFirst(), supers);
|
||||
}
|
||||
|
||||
public static MethodSignature parseMethodSignature(String signature) {
|
||||
int index = 0;
|
||||
char ch;
|
||||
List<TypeVariableName> generics = Collections.emptyList();
|
||||
|
||||
if (signature.charAt(0) == '<') {
|
||||
// parse generic decl
|
||||
index++; // consume '<'
|
||||
|
||||
// parse type params e.g. <A, B>
|
||||
generics = new LinkedList<>();
|
||||
|
||||
while ((ch = signature.charAt(index)) != '>') {
|
||||
int genericNameStart = index;
|
||||
|
||||
if (ch == ':') {
|
||||
throw errorAt(signature, index);
|
||||
}
|
||||
|
||||
do {
|
||||
index++;
|
||||
} while (signature.charAt(index) != ':');
|
||||
|
||||
String genericName = signature.substring(genericNameStart, index);
|
||||
|
||||
List<TypeName> bounds = new LinkedList<>();
|
||||
boolean classBound = true;
|
||||
|
||||
while (signature.charAt(index) == ':') {
|
||||
// parse bounds
|
||||
index++; // consume ':'
|
||||
|
||||
if (classBound && signature.charAt(index) == ':') {
|
||||
// No class bound, only interface bounds, so '::'
|
||||
classBound = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
classBound = false;
|
||||
Map.Entry<Integer, TypeName> bound = parseParameterizedType(signature, index);
|
||||
index = bound.getKey();
|
||||
bounds.add(bound.getValue());
|
||||
}
|
||||
|
||||
generics.add(TypeVariableName.get(genericName, bounds.toArray(new TypeName[0])));
|
||||
}
|
||||
|
||||
index++; // consume '>'
|
||||
}
|
||||
|
||||
if (signature.charAt(index) != '(') {
|
||||
throw errorAt(signature, index);
|
||||
}
|
||||
|
||||
index++; // consume '('
|
||||
|
||||
LinkedList<TypeName> params = new LinkedList<>();
|
||||
|
||||
while (signature.charAt(index) != ')') {
|
||||
Map.Entry<Integer, TypeName> param = parseParameterizedType(signature, index);
|
||||
index = param.getKey();
|
||||
params.add(param.getValue());
|
||||
}
|
||||
|
||||
index++; // consume ')'
|
||||
|
||||
TypeName returnType;
|
||||
|
||||
if (signature.charAt(index) == 'V') {
|
||||
returnType = TypeName.VOID;
|
||||
index++;
|
||||
} else {
|
||||
Map.Entry<Integer, TypeName> parsedReturnType = parseParameterizedType(signature, index);
|
||||
index = parsedReturnType.getKey();
|
||||
returnType = parsedReturnType.getValue();
|
||||
}
|
||||
|
||||
LinkedList<TypeName> thrown = new LinkedList<>();
|
||||
|
||||
while (index < signature.length() && signature.charAt(index) == '^') {
|
||||
index++; // consume '^'
|
||||
Map.Entry<Integer, TypeName> parsedThrown = parseParameterizedType(signature, index);
|
||||
index = parsedThrown.getKey();
|
||||
thrown.addLast(parsedThrown.getValue());
|
||||
}
|
||||
|
||||
return new MethodSignature(generics, params, returnType, thrown);
|
||||
}
|
||||
|
||||
public static TypeName parseFieldSignature(String signature) {
|
||||
return parseParameterizedType(signature, 0).getValue();
|
||||
}
|
||||
|
||||
public static Map.Entry<Integer, TypeName> parseParameterizedType(final String signature, final int startOffset) {
|
||||
GenericStack stack = new GenericStack();
|
||||
|
||||
int index = startOffset;
|
||||
|
||||
// the loop parses a type and try to quit levels if possible
|
||||
do {
|
||||
char ch = signature.charAt(index);
|
||||
boolean parseExactType = true;
|
||||
boolean bounded = false;
|
||||
boolean extendsBound = false;
|
||||
|
||||
switch (ch) {
|
||||
case '*': {
|
||||
index++;
|
||||
parseExactType = false;
|
||||
stack.addWildcard();
|
||||
break;
|
||||
}
|
||||
case '+': {
|
||||
index++;
|
||||
bounded = true;
|
||||
extendsBound = true;
|
||||
break;
|
||||
}
|
||||
case '-': {
|
||||
index++;
|
||||
bounded = true;
|
||||
extendsBound = false;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
if (parseExactType) {
|
||||
int arrayLevel = 0;
|
||||
|
||||
while ((ch = signature.charAt(index)) == '[') {
|
||||
index++;
|
||||
arrayLevel++;
|
||||
}
|
||||
|
||||
index++; // whatever the prefix is it's consumed
|
||||
switch (ch) {
|
||||
case 'B':
|
||||
case 'C':
|
||||
case 'D':
|
||||
case 'F':
|
||||
case 'I':
|
||||
case 'J':
|
||||
case 'S':
|
||||
case 'Z': {
|
||||
// primitives
|
||||
stack.add(getPrimitive(ch), arrayLevel, bounded, extendsBound);
|
||||
break;
|
||||
}
|
||||
case 'T': {
|
||||
// "TE;" for <E>
|
||||
int nameStart = index;
|
||||
|
||||
while (signature.charAt(index) != ';') {
|
||||
index++;
|
||||
}
|
||||
|
||||
String typeVarName = signature.substring(nameStart, index);
|
||||
stack.add(TypeVariableName.get(typeVarName), arrayLevel, bounded, extendsBound);
|
||||
index++; // read ending ";"
|
||||
break;
|
||||
}
|
||||
case 'L': {
|
||||
// Lcom/example/Outer<TA;TB;>.Inner<TC;>;
|
||||
// Lcom/example/Outer$Inner<TA;>;
|
||||
// dot only appears after ">"!
|
||||
int nameStart = index;
|
||||
ClassName currentClass = null;
|
||||
int nextSimpleNamePrev = -1;
|
||||
|
||||
do {
|
||||
ch = signature.charAt(index);
|
||||
|
||||
if (ch == '/') {
|
||||
if (currentClass != null) {
|
||||
throw errorAt(signature, index);
|
||||
}
|
||||
|
||||
nextSimpleNamePrev = index;
|
||||
}
|
||||
|
||||
if (ch == '$' || ch == '<' || ch == ';') {
|
||||
if (currentClass == null) {
|
||||
String packageName = nextSimpleNamePrev == -1 ? "" : signature.substring(nameStart, nextSimpleNamePrev).replace('/', '.');
|
||||
String simpleName = signature.substring(nextSimpleNamePrev + 1, index);
|
||||
currentClass = ClassName.get(packageName, simpleName);
|
||||
} else {
|
||||
String simpleName = signature.substring(nextSimpleNamePrev + 1, index);
|
||||
currentClass = currentClass.nestedClass(simpleName);
|
||||
}
|
||||
|
||||
nextSimpleNamePrev = index;
|
||||
}
|
||||
|
||||
index++;
|
||||
} while (ch != '<' && ch != ';');
|
||||
|
||||
assert currentClass != null;
|
||||
|
||||
if (ch == ';') {
|
||||
stack.add(currentClass, arrayLevel, bounded, extendsBound);
|
||||
}
|
||||
|
||||
if (ch == '<') {
|
||||
stack.push(Frame.ofClass(currentClass), arrayLevel, bounded, extendsBound);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw errorAt(signature, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// quit generics
|
||||
quitLoop:
|
||||
|
||||
while (stack.canQuit() && signature.charAt(index) == '>') {
|
||||
// pop
|
||||
stack.popFrame();
|
||||
index++;
|
||||
|
||||
// followups like .B<E> in A<T>.B<E>
|
||||
if ((ch = signature.charAt(index)) != ';') {
|
||||
if (ch != '.') {
|
||||
throw errorAt(signature, index);
|
||||
}
|
||||
|
||||
index++;
|
||||
int innerNameStart = index;
|
||||
final int checkIndex = index;
|
||||
stack.checkHead(head -> {
|
||||
if (!(head instanceof ParameterizedTypeName)) {
|
||||
throw errorAt(signature, checkIndex);
|
||||
}
|
||||
});
|
||||
|
||||
while (true) {
|
||||
ch = signature.charAt(index);
|
||||
|
||||
if (ch == '.' || ch == ';' || ch == '<') {
|
||||
String simpleName = signature.substring(innerNameStart, index);
|
||||
|
||||
if (ch == '.' || ch == ';') {
|
||||
stack.tweakLast(name -> ((ParameterizedTypeName) name).nestedClass(simpleName));
|
||||
|
||||
if (ch == ';') {
|
||||
index++;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
stack.push(Frame.ofGenericInnerClass((ParameterizedTypeName) stack.deque.getLast().typeNames.removeLast(), simpleName));
|
||||
index++;
|
||||
break quitLoop;
|
||||
}
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
} else {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
} while (stack.canQuit());
|
||||
|
||||
assert stack.deque.size() == 1;
|
||||
assert stack.deque.getLast().typeNames.size() == 1;
|
||||
return new AbstractMap.SimpleImmutableEntry<>(index, stack.collectFrame());
|
||||
}
|
||||
|
||||
private static IllegalArgumentException errorAt(String signature, int index) {
|
||||
return new IllegalArgumentException(String.format("Signature format error at %d for \"%s\"", index, signature));
|
||||
}
|
||||
|
||||
public static TypeName wrap(TypeName component, int level, boolean bounded, boolean extendsBound) {
|
||||
TypeName ret = component;
|
||||
|
||||
for (int i = 0; i < level; i++) {
|
||||
ret = ArrayTypeName.of(ret);
|
||||
}
|
||||
|
||||
return bounded ? extendsBound ? WildcardTypeName.subtypeOf(ret) : WildcardTypeName.supertypeOf(ret) : ret;
|
||||
}
|
||||
|
||||
public static TypeName getPrimitive(char c) {
|
||||
switch (c) {
|
||||
case 'B':
|
||||
return TypeName.BYTE;
|
||||
case 'C':
|
||||
return TypeName.CHAR;
|
||||
case 'D':
|
||||
return TypeName.DOUBLE;
|
||||
case 'F':
|
||||
return TypeName.FLOAT;
|
||||
case 'I':
|
||||
return TypeName.INT;
|
||||
case 'J':
|
||||
return TypeName.LONG;
|
||||
case 'S':
|
||||
return TypeName.SHORT;
|
||||
case 'V':
|
||||
return TypeName.VOID;
|
||||
case 'Z':
|
||||
return TypeName.BOOLEAN;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Invalid primitive " + c);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface Frame<T extends TypeName> {
|
||||
static Frame<ParameterizedTypeName> ofClass(ClassName className) {
|
||||
return parameters -> ParameterizedTypeName.get(className, parameters.toArray(new TypeName[0]));
|
||||
}
|
||||
|
||||
static Frame<ParameterizedTypeName> ofGenericInnerClass(ParameterizedTypeName outerClass, String innerName) {
|
||||
return parameters -> outerClass.nestedClass(innerName, parameters);
|
||||
}
|
||||
|
||||
static Frame<TypeName> collecting() {
|
||||
return parameters -> {
|
||||
if (parameters.size() != 1) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
return parameters.get(0);
|
||||
};
|
||||
}
|
||||
|
||||
T acceptParameters(List<TypeName> parameters);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface HeadChecker<E extends Throwable> {
|
||||
void check(TypeName typeName) throws E;
|
||||
}
|
||||
|
||||
static final class GenericStack {
|
||||
private final Deque<Element> deque = new LinkedList<>();
|
||||
|
||||
GenericStack() {
|
||||
deque.addLast(new Element(Frame.collecting()));
|
||||
}
|
||||
|
||||
public boolean canQuit() {
|
||||
return deque.size() > 1;
|
||||
}
|
||||
|
||||
public void push(Frame<?> frame) {
|
||||
deque.addLast(new Element(frame));
|
||||
}
|
||||
|
||||
public void push(Frame<?> frame, int arrayLevel, boolean bounded, boolean extendsBound) {
|
||||
deque.getLast().pushAttributes(arrayLevel, bounded, extendsBound);
|
||||
push(frame);
|
||||
}
|
||||
|
||||
public void addWildcard() {
|
||||
add(WildcardTypeName.subtypeOf(ClassName.OBJECT), 0, false, false);
|
||||
}
|
||||
|
||||
public void add(TypeName typeName, int arrayLevel, boolean bounded, boolean extendsBound) {
|
||||
deque.getLast().add(typeName, arrayLevel, bounded, extendsBound);
|
||||
}
|
||||
|
||||
public void tweakLast(UnaryOperator<TypeName> modifier) {
|
||||
LinkedList<TypeName> typeNames = deque.getLast().typeNames;
|
||||
typeNames.addLast(modifier.apply(typeNames.removeLast()));
|
||||
}
|
||||
|
||||
public TypeName collectFrame() {
|
||||
return deque.removeLast().pop();
|
||||
}
|
||||
|
||||
public void popFrame() {
|
||||
TypeName name = collectFrame();
|
||||
deque.getLast().typeNames.addLast(name);
|
||||
}
|
||||
|
||||
public <E extends Throwable> void checkHead(HeadChecker<E> checker) throws E {
|
||||
checker.check(deque.getLast().typeNames.getLast());
|
||||
}
|
||||
|
||||
private static final class Element {
|
||||
final LinkedList<TypeName> typeNames;
|
||||
final Frame<?> frame;
|
||||
int arrayLevel = 0;
|
||||
boolean bounded = false;
|
||||
boolean extendsBound = false;
|
||||
|
||||
Element(Frame<?> frame) {
|
||||
this.typeNames = new LinkedList<>();
|
||||
this.frame = frame;
|
||||
}
|
||||
|
||||
void add(TypeName typeName, int arrayLevel, boolean bounded, boolean extendsBound) {
|
||||
pushAttributes(arrayLevel, bounded, extendsBound);
|
||||
typeNames.addLast(typeName);
|
||||
}
|
||||
|
||||
private void updateLast() {
|
||||
if (!typeNames.isEmpty()) {
|
||||
TypeName lastTypeName = typeNames.removeLast();
|
||||
typeNames.addLast(Signatures.wrap(lastTypeName, this.arrayLevel, this.bounded, this.extendsBound));
|
||||
}
|
||||
}
|
||||
|
||||
void pushAttributes(int arrayLevel, boolean bounded, boolean extendsBound) {
|
||||
updateLast();
|
||||
this.arrayLevel = arrayLevel;
|
||||
this.bounded = bounded;
|
||||
this.extendsBound = extendsBound;
|
||||
}
|
||||
|
||||
TypeName pop() {
|
||||
updateLast();
|
||||
return frame.acceptParameters(typeNames);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Copyright (c) 2020 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.filament.mappingpoet.jd;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
|
||||
import com.sun.source.doctree.DocTree;
|
||||
import com.sun.source.doctree.LiteralTree;
|
||||
import com.sun.source.doctree.UnknownBlockTagTree;
|
||||
import jdk.javadoc.doclet.Taglet;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class MappingTaglet implements Taglet {
|
||||
public MappingTaglet() {
|
||||
// Required by javadoc
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Location> getAllowedLocations() {
|
||||
return EnumSet.of(Location.TYPE, Location.CONSTRUCTOR, Location.METHOD, Location.FIELD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInlineTag() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "mapping";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(List<? extends DocTree> tags, Element element) {
|
||||
boolean typeDecl = element instanceof TypeElement; // means it's a class, itf, enum, etc.
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("<dt>Mappings:</dt>\n");
|
||||
builder.append("<dd><div class=\"fabric\"><table class=\"mapping\" summary=\"Mapping data\">\n");
|
||||
builder.append("<thead>\n");
|
||||
builder.append("<th>Namespace</th>\n");
|
||||
builder.append("<th>Name</th>\n");
|
||||
|
||||
if (!typeDecl) {
|
||||
builder.append("<th>Mixin selector</th>\n");
|
||||
}
|
||||
|
||||
builder.append("</thead>\n");
|
||||
builder.append("<tbody>\n");
|
||||
|
||||
for (DocTree each : tags) {
|
||||
String body = ((UnknownBlockTagTree) each).getContent().stream().map(t -> ((LiteralTree) t).getBody().getBody()).collect(Collectors.joining());
|
||||
String[] ans = body.split(":", 3);
|
||||
builder.append("<tr>\n");
|
||||
builder.append(String.format("<td>%s</td>\n", escaped(ans[0])));
|
||||
builder.append(String.format("<td><span class=\"copyable\"><code>%s</code></span></td>\n", escaped(ans[1])));
|
||||
|
||||
if (!typeDecl) {
|
||||
builder.append(String.format("<td><span class=\"copyable\"><code>%s</code></span></td>\n", escaped(ans[2])));
|
||||
}
|
||||
|
||||
builder.append("</tr>\n");
|
||||
}
|
||||
|
||||
builder.append("</tbody>\n");
|
||||
builder.append("</table></div></dd>\n");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
// I hate <init>
|
||||
private static String escaped(String original) {
|
||||
StringBuilder builder = new StringBuilder(original.length());
|
||||
final int len = original.length();
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
char c = original.charAt(i);
|
||||
|
||||
if (c > 127 || c == '"' || c == '\'' || c == '<' || c == '>' || c == '&') {
|
||||
builder.append("&#").append((int) c).append(";");
|
||||
} else {
|
||||
builder.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* Copyright (c) 2020 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.filament.mappingpoet.signature;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
|
||||
import com.squareup.javapoet.AnnotationSpec;
|
||||
import com.squareup.javapoet.ArrayTypeName;
|
||||
import com.squareup.javapoet.ClassName;
|
||||
import com.squareup.javapoet.TypeName;
|
||||
import org.objectweb.asm.TypePath;
|
||||
import org.objectweb.asm.TypeReference;
|
||||
|
||||
import net.fabricmc.filament.mappingpoet.Signatures;
|
||||
|
||||
public final class AnnotationAwareDescriptors {
|
||||
private AnnotationAwareDescriptors() {
|
||||
}
|
||||
|
||||
// not really signature, but annotated classes
|
||||
public static ClassSignature parse(String rawSuper, List<String> rawInterfaces, TypeAnnotationMapping mapping, ClassStaticContext context) {
|
||||
ClassName superName = parseType(rawSuper, mapping.getBank(TypeReference.newSuperTypeReference(-1)), context);
|
||||
|
||||
List<TypeName> interfaces = new ArrayList<>(rawInterfaces.size());
|
||||
|
||||
for (ListIterator<String> itr = rawInterfaces.listIterator(); itr.hasNext(); ) {
|
||||
int i = itr.nextIndex();
|
||||
String item = itr.next();
|
||||
|
||||
ClassName itfName = parseType(item, mapping.getBank(TypeReference.newSuperTypeReference(i)), context);
|
||||
interfaces.add(itfName);
|
||||
}
|
||||
|
||||
return new ClassSignature(Collections.emptyList(), superName, interfaces);
|
||||
}
|
||||
|
||||
// only for descriptor-based ones. Use signature visitor for signature-based ones!
|
||||
public static TypeName parseDesc(String desc, TypeAnnotationBank bank, ClassStaticContext context) {
|
||||
Deque<List<AnnotationSpec>> arrayAnnotations = new ArrayDeque<>();
|
||||
int len = desc.length();
|
||||
int index;
|
||||
|
||||
for (index = 0; (index < len) && (desc.charAt(index) == '['); index++) {
|
||||
arrayAnnotations.push(bank.getCurrentAnnotations());
|
||||
bank = bank.advance(TypePath.ARRAY_ELEMENT, 0);
|
||||
}
|
||||
|
||||
TypeName current;
|
||||
|
||||
if (len - index == 1) {
|
||||
current = annotate(Signatures.getPrimitive(desc.charAt(index)), bank);
|
||||
} else {
|
||||
// L ;
|
||||
assert desc.charAt(index) == 'L' && desc.charAt(len - 1) == ';';
|
||||
current = parseType(desc.substring(index + 1, len - 1), bank, context);
|
||||
}
|
||||
|
||||
while (!arrayAnnotations.isEmpty()) {
|
||||
current = ArrayTypeName.of(current);
|
||||
List<AnnotationSpec> specs = arrayAnnotations.pop();
|
||||
|
||||
if (!specs.isEmpty()) {
|
||||
current = current.annotated(specs);
|
||||
}
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
public static ClassName parseType(String internalName, TypeAnnotationBank bank, ClassStaticContext context) {
|
||||
Map.Entry<ClassName, TypeAnnotationBank> result = annotateUpTo(internalName, bank, context);
|
||||
return annotate(result.getKey(), result.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Annotate class name chains until the last element. Useful for making the last
|
||||
* element a parameterized type before annotating it.
|
||||
*
|
||||
* @param internalName the internal name chain
|
||||
* @param bank the annotation storage
|
||||
* @param context the context for testing instance inner class
|
||||
* @return the class name ready for parameterization/annotation and the current annotation state
|
||||
*/
|
||||
static Map.Entry<ClassName, TypeAnnotationBank> annotateUpTo(String internalName, TypeAnnotationBank bank, ClassStaticContext context) {
|
||||
if (internalName.startsWith("L") && internalName.endsWith(";")) {
|
||||
throw new AssertionError(internalName);
|
||||
}
|
||||
|
||||
int slice = internalName.lastIndexOf('/');
|
||||
String packageSt = slice < 0 ? "" : internalName.substring(0, slice).replace('/', '.');
|
||||
|
||||
int moneySign = internalName.indexOf('$', slice + 1);
|
||||
|
||||
if (moneySign == -1) {
|
||||
return new AbstractMap.SimpleImmutableEntry<>(ClassName.get(packageSt, internalName.substring(slice + 1)), bank);
|
||||
}
|
||||
|
||||
ClassName current = ClassName.get(packageSt, internalName.substring(slice + 1, moneySign));
|
||||
|
||||
final int len = internalName.length();
|
||||
boolean enteredInner = false;
|
||||
|
||||
for (int i = moneySign; i < len; ) {
|
||||
int t = internalName.indexOf('$', i + 1);
|
||||
|
||||
if (t < 0) {
|
||||
t = len;
|
||||
}
|
||||
|
||||
// do work
|
||||
if (!enteredInner && context.isInstanceInner(internalName.substring(0, t))) {
|
||||
enteredInner = true; // instance inner classes cannot nest static ones
|
||||
}
|
||||
|
||||
if (enteredInner) {
|
||||
// annotate parent before we advance
|
||||
current = annotate(current, bank);
|
||||
}
|
||||
|
||||
current = current.nestedClass(internalName.substring(i + 1, t));
|
||||
|
||||
if (enteredInner) {
|
||||
// advance on path as it's instance inner class
|
||||
bank = bank.advance(TypePath.INNER_TYPE, 0);
|
||||
}
|
||||
|
||||
i = t;
|
||||
}
|
||||
|
||||
return new AbstractMap.SimpleImmutableEntry<>(current, bank);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends TypeName> T annotate(T input, TypeAnnotationBank storage) {
|
||||
List<AnnotationSpec> annotations = storage.getCurrentAnnotations();
|
||||
return annotations.isEmpty() ? input : (T) input.annotated(annotations); // it's implemented so
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2020 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.filament.mappingpoet.signature;
|
||||
|
||||
import com.squareup.javapoet.TypeName;
|
||||
import org.objectweb.asm.TypeReference;
|
||||
import org.objectweb.asm.signature.SignatureReader;
|
||||
|
||||
public final class AnnotationAwareSignatures {
|
||||
private AnnotationAwareSignatures() {
|
||||
}
|
||||
|
||||
public static ClassSignature parseClassSignature(String signature, TypeAnnotationMapping annotationMapping, ClassStaticContext context) {
|
||||
PoetClassMethodSignatureVisitor visitor = new PoetClassMethodSignatureVisitor(annotationMapping, context, true);
|
||||
new SignatureReader(signature).accept(visitor);
|
||||
return visitor.collectClass();
|
||||
}
|
||||
|
||||
// Note: No receiver (self) parameter included!
|
||||
public static MethodSignature parseMethodSignature(String signature, TypeAnnotationMapping annotationMapping, ClassStaticContext context) {
|
||||
PoetClassMethodSignatureVisitor visitor = new PoetClassMethodSignatureVisitor(annotationMapping, context, false);
|
||||
new SignatureReader(signature).accept(visitor);
|
||||
return visitor.collectMethod();
|
||||
}
|
||||
|
||||
public static TypeName parseFieldSignature(String signature, TypeAnnotationMapping annotationMapping, ClassStaticContext context) {
|
||||
return parseSignature(signature, annotationMapping.getBank(TypeReference.newTypeReference(TypeReference.FIELD)), context);
|
||||
}
|
||||
|
||||
public static TypeName parseSignature(String signature, TypeAnnotationBank annotations, ClassStaticContext context) {
|
||||
PoetTypeSignatureWriter visitor = new PoetTypeSignatureWriter(annotations, context);
|
||||
new SignatureReader(signature).acceptType(visitor);
|
||||
return visitor.compute();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright (c) 2020 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.filament.mappingpoet.signature;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.squareup.javapoet.TypeName;
|
||||
import com.squareup.javapoet.TypeVariableName;
|
||||
|
||||
// no more a class signature but general super info about class
|
||||
public record ClassSignature(List<TypeVariableName> generics, TypeName superclass,
|
||||
List<TypeName> superinterfaces) {
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2020 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.filament.mappingpoet.signature;
|
||||
|
||||
/**
|
||||
* A context to retrieve if a class is an instance inner class. Useful for
|
||||
* placing type annotations correctly. See
|
||||
* <a href="https://docs.oracle.com/javase/specs/jvms/se15/html/jvms-4.html#d5e9440">
|
||||
* an example in JVM Specification 15.</a>
|
||||
*/
|
||||
public interface ClassStaticContext {
|
||||
/**
|
||||
* Returns if this class is an instance inner class.
|
||||
*
|
||||
* <p>For example, a top-level class is not so. A static nested
|
||||
* class, such as {@code Map.Entry}, is not as well.</p>
|
||||
*
|
||||
* @param internalName the JVM name of the class
|
||||
* @return whether this class is not an instance inner class.
|
||||
*/
|
||||
boolean isInstanceInner(String internalName);
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright (c) 2020 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.filament.mappingpoet.signature;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.squareup.javapoet.TypeName;
|
||||
import com.squareup.javapoet.TypeVariableName;
|
||||
|
||||
public record MethodSignature(List<TypeVariableName> generics,
|
||||
List<TypeName> parameters, TypeName result,
|
||||
List<TypeName> thrown) {
|
||||
}
|
|
@ -0,0 +1,196 @@
|
|||
/*
|
||||
* Copyright (c) 2020 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.filament.mappingpoet.signature;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import com.squareup.javapoet.TypeName;
|
||||
import com.squareup.javapoet.TypeVariableName;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.TypeReference;
|
||||
import org.objectweb.asm.signature.SignatureVisitor;
|
||||
|
||||
public final class PoetClassMethodSignatureVisitor extends SignatureVisitor {
|
||||
private final TypeAnnotationMapping mapping;
|
||||
private final ClassStaticContext context;
|
||||
private final boolean forClass;
|
||||
ArrayList<TypeVariableName> generics = new ArrayList<>();
|
||||
// collecting generic
|
||||
String currentGenericName;
|
||||
ArrayList<TypeName> currentGenericBounds = new ArrayList<>();
|
||||
// bound for each generic
|
||||
PoetTypeSignatureWriter pendingLowerBound;
|
||||
|
||||
// classes usage
|
||||
ArrayList<TypeName> superTypes = new ArrayList<>();
|
||||
PoetTypeSignatureWriter pendingSupertype;
|
||||
|
||||
// methods usage
|
||||
ArrayList<TypeName> params = new ArrayList<>();
|
||||
ArrayList<TypeName> throwables = new ArrayList<>();
|
||||
PoetTypeSignatureWriter pendingSlot;
|
||||
TypeName returnType;
|
||||
|
||||
public PoetClassMethodSignatureVisitor(TypeAnnotationMapping mapping, ClassStaticContext context, boolean forClass) {
|
||||
super(Opcodes.ASM9);
|
||||
this.mapping = mapping;
|
||||
this.context = context;
|
||||
this.forClass = forClass;
|
||||
}
|
||||
|
||||
private void collectGeneric() {
|
||||
collectLowerBound();
|
||||
|
||||
if (currentGenericName != null) {
|
||||
TypeVariableName generic = TypeVariableName.get(currentGenericName, currentGenericBounds.toArray(new TypeName[0]));
|
||||
TypeAnnotationBank bank = mapping.getBank(TypeReference.newTypeParameterReference(forClass ? TypeReference.CLASS_TYPE_PARAMETER : TypeReference.METHOD_TYPE_PARAMETER, generics.size()));
|
||||
generic = AnnotationAwareDescriptors.annotate(generic, bank);
|
||||
generics.add(generic);
|
||||
|
||||
currentGenericName = null;
|
||||
currentGenericBounds.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void collectGenerics() {
|
||||
// end all generics
|
||||
collectGeneric();
|
||||
}
|
||||
|
||||
// starts a new generic declaration, like <T> in "<T> T[] toArray(T[] input);"
|
||||
@Override
|
||||
public void visitFormalTypeParameter(String name) {
|
||||
collectGeneric();
|
||||
// collect existing type parameter
|
||||
// start type var name
|
||||
currentGenericName = name;
|
||||
currentGenericBounds.clear();
|
||||
}
|
||||
|
||||
private void collectLowerBound() {
|
||||
if (pendingLowerBound != null) {
|
||||
currentGenericBounds.add(pendingLowerBound.compute());
|
||||
pendingLowerBound = null;
|
||||
}
|
||||
}
|
||||
|
||||
private SignatureVisitor visitLowerBound() {
|
||||
collectLowerBound();
|
||||
|
||||
TypeAnnotationBank bank = mapping.getBank(TypeReference.newTypeParameterBoundReference(forClass
|
||||
? TypeReference.CLASS_TYPE_PARAMETER_BOUND : TypeReference.METHOD_TYPE_PARAMETER_BOUND, generics.size(),
|
||||
currentGenericBounds.size()));
|
||||
return pendingLowerBound = new PoetTypeSignatureWriter(bank, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SignatureVisitor visitClassBound() {
|
||||
return visitLowerBound();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SignatureVisitor visitInterfaceBound() {
|
||||
return visitLowerBound();
|
||||
}
|
||||
|
||||
// class exclusive
|
||||
|
||||
private void collectSupertype() {
|
||||
if (pendingSupertype != null) {
|
||||
TypeName simple = pendingSupertype.compute();
|
||||
superTypes.add(simple);
|
||||
|
||||
pendingSupertype = null;
|
||||
}
|
||||
}
|
||||
|
||||
// always called
|
||||
@Override
|
||||
public SignatureVisitor visitSuperclass() {
|
||||
collectGenerics();
|
||||
// don't need to collect other supertype
|
||||
|
||||
return pendingSupertype = new PoetTypeSignatureWriter(mapping.getBank(TypeReference.newSuperTypeReference(-1)), context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SignatureVisitor visitInterface() {
|
||||
// super class always visited, no generic check
|
||||
collectSupertype();
|
||||
|
||||
return pendingSupertype = new PoetTypeSignatureWriter(mapping.getBank(TypeReference.newSuperTypeReference(superTypes.size() - 1)), context);
|
||||
}
|
||||
|
||||
public ClassSignature collectClass() {
|
||||
collectSupertype();
|
||||
|
||||
TypeName superclass = superTypes.remove(0);
|
||||
return new ClassSignature(generics, superclass, superTypes);
|
||||
}
|
||||
|
||||
// method exclusive
|
||||
|
||||
private void collectParam() {
|
||||
if (pendingSlot != null) {
|
||||
TypeName slot = pendingSlot.compute();
|
||||
params.add(slot);
|
||||
|
||||
pendingSlot = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void collectReturnOrThrows() {
|
||||
if (pendingSlot != null) {
|
||||
if (returnType == null) {
|
||||
returnType = pendingSlot.compute();
|
||||
} else {
|
||||
throwables.add(pendingSlot.compute());
|
||||
}
|
||||
|
||||
pendingSlot = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SignatureVisitor visitParameterType() {
|
||||
collectGenerics();
|
||||
collectParam();
|
||||
|
||||
return pendingSlot = new PoetTypeSignatureWriter(mapping.getBank(TypeReference.newFormalParameterReference(params.size())), context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SignatureVisitor visitReturnType() {
|
||||
collectGenerics(); // they may skip visiting params, rip!
|
||||
collectParam();
|
||||
|
||||
return pendingSlot = new PoetTypeSignatureWriter(mapping.getBank(TypeReference.newTypeReference(TypeReference.METHOD_RETURN)), context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SignatureVisitor visitExceptionType() {
|
||||
collectReturnOrThrows();
|
||||
|
||||
return pendingSlot = new PoetTypeSignatureWriter(mapping.getBank(TypeReference.newExceptionReference(throwables.size())), context);
|
||||
}
|
||||
|
||||
public MethodSignature collectMethod() {
|
||||
collectReturnOrThrows();
|
||||
|
||||
return new MethodSignature(generics, params, returnType, throwables);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* Copyright (c) 2020 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.filament.mappingpoet.signature;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.squareup.javapoet.ArrayTypeName;
|
||||
import com.squareup.javapoet.ClassName;
|
||||
import com.squareup.javapoet.ParameterizedTypeName;
|
||||
import com.squareup.javapoet.TypeName;
|
||||
import com.squareup.javapoet.TypeVariableName;
|
||||
import com.squareup.javapoet.WildcardTypeName;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.TypePath;
|
||||
import org.objectweb.asm.signature.SignatureVisitor;
|
||||
|
||||
import net.fabricmc.filament.mappingpoet.Signatures;
|
||||
|
||||
/**
|
||||
* A type signature to javapoet visitor.
|
||||
*
|
||||
* <p>A type signature is one at the usage of type, such as field and local var type.
|
||||
* It does not include class or method signatures where new generics like {@code <T>}
|
||||
* can be defined.</p>
|
||||
*/
|
||||
public final class PoetTypeSignatureWriter extends SignatureVisitor {
|
||||
private final ClassStaticContext context;
|
||||
private final ArrayList<TypeName> params = new ArrayList<>();
|
||||
private /* NonNull */ TypeAnnotationBank storage; // mutable
|
||||
private TypeName result;
|
||||
// array
|
||||
private PoetTypeSignatureWriter arrayChild;
|
||||
// class type signature stuff
|
||||
private TypeName currentType; // ClassName or ParameterizedTypeName
|
||||
private String nestedClassName;
|
||||
// single type argument
|
||||
private char activeTypeArgumentKind;
|
||||
private PoetTypeSignatureWriter activeTypeArgument;
|
||||
|
||||
public PoetTypeSignatureWriter(TypeAnnotationBank storage, ClassStaticContext context) {
|
||||
super(Opcodes.ASM9);
|
||||
this.storage = storage;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public TypeName compute() {
|
||||
// array cleanup. doesn't have visit end TwT
|
||||
if (arrayChild != null) {
|
||||
result = ArrayTypeName.of(arrayChild.compute());
|
||||
|
||||
result = annotate(result);
|
||||
}
|
||||
|
||||
return Objects.requireNonNull(result, "writer did not visit");
|
||||
}
|
||||
|
||||
private <T extends TypeName> T annotate(T input) {
|
||||
return AnnotationAwareDescriptors.annotate(input, storage);
|
||||
}
|
||||
|
||||
private void annotateResult() {
|
||||
result = annotate(result);
|
||||
}
|
||||
|
||||
// primitives
|
||||
@Override
|
||||
public void visitBaseType(char descriptor) {
|
||||
result = Signatures.getPrimitive(descriptor);
|
||||
annotateResult();
|
||||
}
|
||||
|
||||
// T, E etc
|
||||
@Override
|
||||
public void visitTypeVariable(String name) {
|
||||
result = TypeVariableName.get(name);
|
||||
annotateResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SignatureVisitor visitArrayType() {
|
||||
return arrayChild = new PoetTypeSignatureWriter(this.storage.advance(TypePath.ARRAY_ELEMENT, 0), context);
|
||||
// post cleanup, annotate in #getResult()
|
||||
}
|
||||
|
||||
// outer class, may have instance inner class. ends with visitEnd
|
||||
@Override
|
||||
public void visitClassType(String internalName) {
|
||||
Map.Entry<ClassName, TypeAnnotationBank> entry = AnnotationAwareDescriptors.annotateUpTo(internalName, storage, context);
|
||||
|
||||
currentType = entry.getKey();
|
||||
storage = entry.getValue();
|
||||
// later collect annotations in #collectPreviousTypeArgumentsAndAnnotations
|
||||
}
|
||||
|
||||
// collect info onto this before we append inners
|
||||
private void collectPreviousTypeArgumentsAndAnnotations() {
|
||||
collectLastTypeArgument();
|
||||
|
||||
if (!params.isEmpty()) {
|
||||
if (currentType instanceof ParameterizedTypeName) {
|
||||
// top-level handled already
|
||||
currentType = ((ParameterizedTypeName) currentType).nestedClass(nestedClassName, params);
|
||||
} else { // assume ClassName
|
||||
if (nestedClassName == null) { // top-level
|
||||
currentType = ParameterizedTypeName.get((ClassName) currentType, params.toArray(new TypeName[0]));
|
||||
} else {
|
||||
currentType = ParameterizedTypeName.get(((ClassName) currentType).nestedClass(nestedClassName), params.toArray(new TypeName[0]));
|
||||
}
|
||||
}
|
||||
|
||||
params.clear();
|
||||
nestedClassName = null;
|
||||
} else if (nestedClassName != null) {
|
||||
if (currentType instanceof ParameterizedTypeName) {
|
||||
currentType = ((ParameterizedTypeName) currentType).nestedClass(nestedClassName);
|
||||
} else {
|
||||
currentType = ((ClassName) currentType).nestedClass(nestedClassName);
|
||||
}
|
||||
|
||||
nestedClassName = null;
|
||||
}
|
||||
|
||||
currentType = annotate(currentType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInnerClassType(String name) {
|
||||
collectPreviousTypeArgumentsAndAnnotations();
|
||||
// collect previous type arguments
|
||||
nestedClassName = name;
|
||||
storage = storage.advance(TypePath.INNER_TYPE, 0);
|
||||
}
|
||||
|
||||
private void collectLastTypeArgument() {
|
||||
if (activeTypeArgument != null) {
|
||||
TypeName hold = activeTypeArgument.compute();
|
||||
TypeName used = switch (activeTypeArgumentKind) {
|
||||
case SignatureVisitor.EXTENDS -> WildcardTypeName.subtypeOf(hold);
|
||||
case SignatureVisitor.SUPER -> WildcardTypeName.supertypeOf(hold);
|
||||
case SignatureVisitor.INSTANCEOF -> hold;
|
||||
default -> throw new IllegalStateException(String.format("Illegal type argument wildcard %s", activeTypeArgumentKind));
|
||||
};
|
||||
|
||||
used = AnnotationAwareDescriptors.annotate(used, storage.advance(TypePath.TYPE_ARGUMENT, params.size()));
|
||||
params.add(used);
|
||||
|
||||
activeTypeArgument = null;
|
||||
activeTypeArgumentKind = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// wildcard ? like in List<?>
|
||||
@Override
|
||||
public void visitTypeArgument() {
|
||||
collectLastTypeArgument();
|
||||
|
||||
TypeName used = WildcardTypeName.subtypeOf(TypeName.OBJECT);
|
||||
used = AnnotationAwareDescriptors.annotate(used, storage.advance(TypePath.TYPE_ARGUMENT, params.size()));
|
||||
params.add(used);
|
||||
}
|
||||
|
||||
// (? extends/ ? super)? ClassType like in Consumer<? super Integer>,
|
||||
// Supplier<? extends String>
|
||||
@Override
|
||||
public SignatureVisitor visitTypeArgument(char wildcard) {
|
||||
collectLastTypeArgument();
|
||||
|
||||
activeTypeArgumentKind = wildcard;
|
||||
return activeTypeArgument = new PoetTypeSignatureWriter(storage.advance(TypePath.WILDCARD_BOUND, 0), context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
collectPreviousTypeArgumentsAndAnnotations();
|
||||
// finalize result!
|
||||
result = currentType;
|
||||
currentType = null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright (c) 2020 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.filament.mappingpoet.signature;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.squareup.javapoet.AnnotationSpec;
|
||||
|
||||
// recommended storing with a sorted array + index slicing
|
||||
|
||||
/**
|
||||
* The collection of type annotations on a specific type. Can be narrowed down through type path.
|
||||
*/
|
||||
public interface TypeAnnotationBank {
|
||||
TypeAnnotationBank EMPTY = new TypeAnnotationBank() {
|
||||
@Override
|
||||
public TypeAnnotationBank advance(int step, int stepArgument) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AnnotationSpec> getCurrentAnnotations() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Make the scope of type annotations smaller.
|
||||
*
|
||||
* @param step see {@link org.objectweb.asm.TypePath#getStep(int)}
|
||||
* @param stepArgument see {@link org.objectweb.asm.TypePath#getStepArgument(int)}
|
||||
* @return the sliced type annotation storage
|
||||
*/
|
||||
TypeAnnotationBank advance(int step, int stepArgument);
|
||||
|
||||
/**
|
||||
* Accesses annotations applicable at current type location.
|
||||
*
|
||||
* <p>Do not modify the returned list!</p>
|
||||
*
|
||||
* @return the current annotations to apply
|
||||
*/
|
||||
List<AnnotationSpec> getCurrentAnnotations();
|
||||
|
||||
/**
|
||||
* Returns if there is no more annotations. Used to check for receiver
|
||||
* declarations.
|
||||
*
|
||||
* @return whether there's no more annotations
|
||||
*/
|
||||
boolean isEmpty();
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2020 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.filament.mappingpoet.signature;
|
||||
|
||||
import org.objectweb.asm.TypeReference;
|
||||
|
||||
/**
|
||||
* The collection of type annotations from a bytecode structure that stores type annotations.
|
||||
*/
|
||||
public interface TypeAnnotationMapping {
|
||||
TypeAnnotationMapping EMPTY = reference -> TypeAnnotationBank.EMPTY;
|
||||
|
||||
// implNote: TypeReference is not a pojo! No equals or hash!
|
||||
TypeAnnotationBank getBank(TypeReference reference);
|
||||
}
|
|
@ -0,0 +1,225 @@
|
|||
/*
|
||||
* Copyright (c) 2020 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.filament.mappingpoet.signature;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import com.squareup.javapoet.AnnotationSpec;
|
||||
import org.objectweb.asm.TypePath;
|
||||
import org.objectweb.asm.TypeReference;
|
||||
import org.objectweb.asm.tree.TypeAnnotationNode;
|
||||
|
||||
import net.fabricmc.filament.mappingpoet.FieldBuilder;
|
||||
|
||||
public final class TypeAnnotationStorage implements TypeAnnotationMapping, TypeAnnotationBank {
|
||||
private final int[] targets; // target type and info, only exist in mapping version
|
||||
private final String[] paths;
|
||||
private final AnnotationSpec[] contents;
|
||||
private final int startIndex;
|
||||
private final int endIndex;
|
||||
private final String currentPath;
|
||||
|
||||
TypeAnnotationStorage(int startIndex, int endIndex, String currentPath, int[] targets, String[] paths, AnnotationSpec[] contents) {
|
||||
this.targets = targets;
|
||||
this.paths = paths;
|
||||
this.contents = contents;
|
||||
this.startIndex = startIndex;
|
||||
this.endIndex = endIndex;
|
||||
this.currentPath = currentPath;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
static int comparePath(TypePath left, TypePath right) {
|
||||
int len = Math.min(left.getLength(), right.getLength());
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
int leftStep = left.getStep(i);
|
||||
int rightStep = right.getStep(i);
|
||||
|
||||
if (leftStep != rightStep) {
|
||||
return Integer.compare(leftStep, rightStep);
|
||||
}
|
||||
|
||||
int leftStepArg = left.getStepArgument(i);
|
||||
int rightStepArg = right.getStepArgument(i);
|
||||
|
||||
if (leftStepArg != rightStepArg) {
|
||||
return Integer.compare(leftStepArg, rightStepArg);
|
||||
}
|
||||
}
|
||||
|
||||
// shorter ones definitely go first!
|
||||
return Integer.compare(left.getLength(), right.getLength());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeAnnotationBank advance(int step, int stepArgument) {
|
||||
if (currentPath == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
String suffix;
|
||||
switch (step) {
|
||||
case TypePath.ARRAY_ELEMENT:
|
||||
suffix = "[";
|
||||
break;
|
||||
case TypePath.INNER_TYPE:
|
||||
suffix = ".";
|
||||
break;
|
||||
case TypePath.WILDCARD_BOUND:
|
||||
suffix = "*";
|
||||
break;
|
||||
case TypePath.TYPE_ARGUMENT:
|
||||
suffix = stepArgument + ";";
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
String check = currentPath.concat(suffix);
|
||||
|
||||
String hiCheck = check.substring(0, check.length() - 1).concat(Character.toString((char) (check.charAt(check.length() - 1) + 1)));
|
||||
|
||||
int low = Arrays.binarySearch(paths, startIndex, endIndex, check);
|
||||
|
||||
if (low < 0) {
|
||||
low = -(low + 1);
|
||||
}
|
||||
|
||||
// exclusive hi
|
||||
int hi = Arrays.binarySearch(paths, startIndex, endIndex, hiCheck);
|
||||
|
||||
if (hi < 0) {
|
||||
hi = -(hi + 1);
|
||||
}
|
||||
|
||||
return new TypeAnnotationStorage(low, hi, check, null, paths, contents);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AnnotationSpec> getCurrentAnnotations() {
|
||||
if (currentPath == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
int hi = Arrays.binarySearch(paths, startIndex, endIndex, currentPath + '\u0000');
|
||||
|
||||
if (hi < 0) {
|
||||
hi = -(hi + 1);
|
||||
}
|
||||
|
||||
return Arrays.asList(contents).subList(startIndex, hi);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return startIndex >= endIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeAnnotationBank getBank(TypeReference reference) {
|
||||
if (targets == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
int target = reference.getValue();
|
||||
// inclusive low
|
||||
int low = Arrays.binarySearch(targets, startIndex, endIndex, target);
|
||||
|
||||
if (low < 0) {
|
||||
low = -(low + 1);
|
||||
}
|
||||
|
||||
// exclusive hi
|
||||
int hi = Arrays.binarySearch(targets, startIndex, endIndex, target + 1);
|
||||
|
||||
if (hi < 0) {
|
||||
hi = -(hi + 1);
|
||||
}
|
||||
|
||||
return new TypeAnnotationStorage(low, hi, "", null, paths, contents);
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
final List<Entry> entries = new ArrayList<>();
|
||||
|
||||
Builder() {
|
||||
}
|
||||
|
||||
public Builder add(int typeReference, String typePath, AnnotationSpec spec) {
|
||||
entries.add(new Entry(typeReference, typePath, spec));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder add(Iterable<TypeAnnotationNode> nodes) {
|
||||
if (nodes == null) {
|
||||
return this; // thanks asm
|
||||
}
|
||||
|
||||
for (TypeAnnotationNode node : nodes) {
|
||||
entries.add(new Entry(node.typeRef, node.typePath == null ? "" : node.typePath.toString(), FieldBuilder.parseAnnotation(node)));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public TypeAnnotationMapping build() {
|
||||
this.entries.sort(null);
|
||||
int len = this.entries.size();
|
||||
|
||||
int[] targets = new int[len];
|
||||
String[] paths = new String[len];
|
||||
AnnotationSpec[] contents = new AnnotationSpec[len];
|
||||
|
||||
Iterator<Entry> itr = this.entries.iterator();
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
Entry entry = itr.next();
|
||||
targets[i] = entry.target;
|
||||
paths[i] = entry.path;
|
||||
contents[i] = entry.content;
|
||||
}
|
||||
|
||||
return new TypeAnnotationStorage(0, len, null, targets, paths, contents);
|
||||
}
|
||||
|
||||
private static final class Entry implements Comparable<Entry> {
|
||||
final int target;
|
||||
final String path;
|
||||
final AnnotationSpec content;
|
||||
|
||||
Entry(int target, String path, AnnotationSpec content) {
|
||||
this.target = target;
|
||||
this.path = path;
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Entry o) {
|
||||
int c0 = Integer.compare(target, o.target);
|
||||
if (c0 != 0) return c0;
|
||||
return path.compareTo(o.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package net.fabricmc.filament.task;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.gradle.api.DefaultTask;
|
||||
import org.gradle.api.file.ConfigurableFileCollection;
|
||||
import org.gradle.api.file.DirectoryProperty;
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.tasks.InputFile;
|
||||
import org.gradle.api.tasks.InputFiles;
|
||||
import org.gradle.api.tasks.OutputDirectory;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
|
||||
import net.fabricmc.filament.mappingpoet.MappingPoet;
|
||||
|
||||
public abstract class MappingPoetTask extends DefaultTask {
|
||||
@InputFile
|
||||
public abstract RegularFileProperty getMappings();
|
||||
@InputFile
|
||||
public abstract RegularFileProperty getMinecraftJar();
|
||||
@InputFiles
|
||||
public abstract ConfigurableFileCollection getLibraries();
|
||||
@OutputDirectory
|
||||
public abstract DirectoryProperty getOutput();
|
||||
|
||||
@TaskAction
|
||||
public void run() {
|
||||
MappingPoet.generate(
|
||||
getMappings().get().getAsFile().toPath(),
|
||||
getMinecraftJar().get().getAsFile().toPath(),
|
||||
getOutput().get().getAsFile().toPath(),
|
||||
getLibraries().getFiles().stream().map(File::toPath).toList()
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2020 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.filament.test.mappingpoet;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Documented
|
||||
@Target(ElementType.PARAMETER)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface BorkAnno {
|
||||
}
|
|
@ -0,0 +1,262 @@
|
|||
/*
|
||||
* Copyright (c) 2020 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.filament.test.mappingpoet;
|
||||
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
import com.squareup.javapoet.ArrayTypeName;
|
||||
import com.squareup.javapoet.ClassName;
|
||||
import com.squareup.javapoet.ParameterizedTypeName;
|
||||
import com.squareup.javapoet.TypeName;
|
||||
import com.squareup.javapoet.TypeVariableName;
|
||||
import com.squareup.javapoet.WildcardTypeName;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.objectweb.asm.signature.SignatureReader;
|
||||
|
||||
import net.fabricmc.filament.mappingpoet.signature.ClassSignature;
|
||||
import net.fabricmc.filament.mappingpoet.signature.MethodSignature;
|
||||
import net.fabricmc.filament.mappingpoet.signature.PoetClassMethodSignatureVisitor;
|
||||
import net.fabricmc.filament.mappingpoet.signature.PoetTypeSignatureWriter;
|
||||
import net.fabricmc.filament.mappingpoet.signature.TypeAnnotationBank;
|
||||
import net.fabricmc.filament.mappingpoet.signature.TypeAnnotationMapping;
|
||||
import net.fabricmc.filament.mappingpoet.Signatures;
|
||||
|
||||
public class SignaturesTest {
|
||||
@Test
|
||||
public void testRandomMapType() {
|
||||
//signature Ljava/util/Map<Ljava/util/Map$Entry<[[Ljava/lang/String;[Ljava/util/List<[I>;>;[[[D>;
|
||||
//Map<Map.Entry<String[][], List<int[]>[]>, double[][][]> map = new HashMap<>();
|
||||
String signature = "Ljava/util/Map<Ljava/util/Map$Entry<[[Ljava/lang/String;[Ljava/util/List<[I>;>;[[[D>;";
|
||||
Map.Entry<Integer, TypeName> result = Signatures.parseParameterizedType(signature, 0);
|
||||
|
||||
Assertions.assertEquals(85, result.getKey().intValue());
|
||||
Assertions.assertEquals("java.util.Map<java.util.Map.Entry<java.lang.String[][], java.util.List<int[]>[]>, double[][][]>", result.getValue().toString());
|
||||
|
||||
PoetTypeSignatureWriter writer = new PoetTypeSignatureWriter(TypeAnnotationBank.EMPTY, s -> false);
|
||||
new SignatureReader(signature).acceptType(writer);
|
||||
Assertions.assertEquals("java.util.Map<java.util.Map.Entry<java.lang.String[][], java.util.List<int[]>[]>, double[][][]>", writer.compute().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrazyOne() {
|
||||
String classSignature = "<B::Ljava/util/Comparator<-TA;>;C:Ljava/lang/ClassLoader;:Ljava/lang/Iterable<*>;>Ljava/lang/Object;";
|
||||
Map.Entry<Integer, TypeName> result = Signatures.parseParameterizedType(classSignature, 4);
|
||||
Assertions.assertEquals(32, result.getKey().intValue());
|
||||
Assertions.assertEquals("java.util.Comparator<? super A>", result.getValue().toString());
|
||||
|
||||
result = Signatures.parseParameterizedType(classSignature, 34);
|
||||
Assertions.assertEquals(57, result.getKey().intValue());
|
||||
Assertions.assertEquals("java.lang.ClassLoader", result.getValue().toString());
|
||||
|
||||
result = Signatures.parseParameterizedType(classSignature, 58);
|
||||
Assertions.assertEquals(81, result.getKey().intValue());
|
||||
Assertions.assertEquals("java.lang.Iterable<?>", result.getValue().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void soo() {
|
||||
TestOuter<Integer>.Inner<Comparator<Integer>, URLClassLoader>.ExtraInner<UnaryOperator<Map<int[][], BiFunction<Comparator<Integer>, Integer, URLClassLoader>>>> local = new TestOuter<Integer>().new Inner<Comparator<Integer>, URLClassLoader>().new ExtraInner<UnaryOperator<Map<int[][], BiFunction<Comparator<Integer>, Integer, URLClassLoader>>>>();
|
||||
local.hashCode();
|
||||
|
||||
// signature Lnet/fabricmc/mappingpoet/TestOuter<Ljava/lang/Integer;>.Inner<Ljava/util/Comparator<Ljava/lang/Integer;>;Ljava/net/URLClassLoader;>.ExtraInner<Ljava/util/function/UnaryOperator<Ljava/util/Map<[[ILjava/util/function/BiFunction<Ljava/util/Comparator<Ljava/lang/Integer;>;Ljava/lang/Integer;Ljava/net/URLClassLoader;>;>;>;>;
|
||||
String signature = "Lnet/fabricmc/mappingpoet/TestOuter<Ljava/lang/Integer;>.Inner<Ljava/util/Comparator<Ljava/lang/Integer;>;Ljava/net/URLClassLoader;>.ExtraInner<Ljava/util/function/UnaryOperator<Ljava/util/Map<[[ILjava/util/function/BiFunction<Ljava/util/Comparator<Ljava/lang/Integer;>;Ljava/lang/Integer;Ljava/net/URLClassLoader;>;>;>;>;";
|
||||
Map.Entry<Integer, TypeName> result = Signatures.parseParameterizedType(signature, 0);
|
||||
|
||||
Assertions.assertEquals(322, result.getKey().intValue());
|
||||
Assertions.assertEquals("net.fabricmc.mappingpoet.TestOuter<java.lang.Integer>.Inner<java.util.Comparator<java.lang.Integer>, java.net.URLClassLoader>.ExtraInner<java.util.function.UnaryOperator<java.util.Map<int[][], java.util.function.BiFunction<java.util.Comparator<java.lang.Integer>, java.lang.Integer, java.net.URLClassLoader>>>>", result.getValue().toString());
|
||||
|
||||
PoetTypeSignatureWriter writer = new PoetTypeSignatureWriter(TypeAnnotationBank.EMPTY, s -> false);
|
||||
new SignatureReader(signature).acceptType(writer);
|
||||
Assertions.assertEquals("net.fabricmc.mappingpoet.TestOuter<java.lang.Integer>.Inner<java.util.Comparator<java.lang.Integer>, java.net.URLClassLoader>.ExtraInner<java.util.function.UnaryOperator<java.util.Map<int[][], java.util.function.BiFunction<java.util.Comparator<java.lang.Integer>, java.lang.Integer, java.net.URLClassLoader>>>>", writer.compute().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void arrSoo() {
|
||||
@SuppressWarnings("unchecked")
|
||||
TestOuter<Integer>.Inner<Comparator<Integer>, URLClassLoader>.ExtraInner<UnaryOperator<Map<int[][], BiFunction<Comparator<Integer>, Integer, URLClassLoader>>>>[][] arr = (TestOuter<Integer>.Inner<Comparator<Integer>, URLClassLoader>.ExtraInner<UnaryOperator<Map<int[][], BiFunction<Comparator<Integer>, Integer, URLClassLoader>>>>[][]) new TestOuter<?>.Inner<?, ?>.ExtraInner<?>[0][];
|
||||
arr.toString();
|
||||
// signature [[Lnet/fabricmc/mappingpoet/TestOuter<Ljava/lang/Integer;>.Inner<Ljava/util/Comparator<Ljava/lang/Integer;>;Ljava/net/URLClassLoader;>.ExtraInner<Ljava/util/function/UnaryOperator<Ljava/util/Map<[[ILjava/util/function/BiFunction<Ljava/util/Comparator<Ljava/lang/Integer;>;Ljava/lang/Integer;Ljava/net/URLClassLoader;>;>;>;>;
|
||||
String arraySignature = "[[Lnet/fabricmc/mappingpoet/TestOuter<Ljava/lang/Integer;>.Inner<Ljava/util/Comparator<Ljava/lang/Integer;>;Ljava/net/URLClassLoader;>.ExtraInner<Ljava/util/function/UnaryOperator<Ljava/util/Map<[[ILjava/util/function/BiFunction<Ljava/util/Comparator<Ljava/lang/Integer;>;Ljava/lang/Integer;Ljava/net/URLClassLoader;>;>;>;>;";
|
||||
Map.Entry<Integer, TypeName> result = Signatures.parseParameterizedType(arraySignature, 0);
|
||||
|
||||
Assertions.assertEquals(324, result.getKey().intValue());
|
||||
Assertions.assertEquals("net.fabricmc.mappingpoet.TestOuter<java.lang.Integer>.Inner<java.util.Comparator<java.lang.Integer>, java.net.URLClassLoader>.ExtraInner<java.util.function.UnaryOperator<java.util.Map<int[][], java.util.function.BiFunction<java.util.Comparator<java.lang.Integer>, java.lang.Integer, java.net.URLClassLoader>>>>[][]", result.getValue().toString());
|
||||
|
||||
PoetTypeSignatureWriter writer = new PoetTypeSignatureWriter(TypeAnnotationBank.EMPTY, s -> false);
|
||||
new SignatureReader(arraySignature).acceptType(writer);
|
||||
Assertions.assertEquals("net.fabricmc.mappingpoet.TestOuter<java.lang.Integer>.Inner<java.util.Comparator<java.lang.Integer>, java.net.URLClassLoader>.ExtraInner<java.util.function.UnaryOperator<java.util.Map<int[][], java.util.function.BiFunction<java.util.Comparator<java.lang.Integer>, java.lang.Integer, java.net.URLClassLoader>>>>[][]", writer.compute().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClassDeeSignature() {
|
||||
// signature <D::Ljava/util/function/UnaryOperator<Ljava/util/Map<[[ILjava/util/function/BiFunction<TB;TA;TC;>;>;>;>Ljava/lang/Object;
|
||||
String classSig = "<D::Ljava/util/function/UnaryOperator<Ljava/util/Map<[[ILjava/util/function/BiFunction<TB;TA;TC;>;>;>;>Ljava/lang/Object;";
|
||||
Map.Entry<Integer, TypeName> dBound = Signatures.parseParameterizedType(classSig, 4);
|
||||
|
||||
Assertions.assertEquals(102, dBound.getKey().intValue());
|
||||
Assertions.assertEquals("java.util.function.UnaryOperator<java.util.Map<int[][], java.util.function.BiFunction<B, A, C>>>", dBound.getValue().toString());
|
||||
|
||||
ClassSignature parsed = Signatures.parseClassSignature(classSig);
|
||||
Assertions.assertIterableEquals(Collections.singleton(TypeVariableName.get("D")), parsed.generics());
|
||||
Assertions.assertEquals(ClassName.OBJECT, parsed.superclass());
|
||||
Assertions.assertIterableEquals(Collections.emptyList(), parsed.superinterfaces());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClassDeeSignatureVisitor() {
|
||||
// signature <D::Ljava/util/function/UnaryOperator<Ljava/util/Map<[[ILjava/util/function/BiFunction<TB;TA;TC;>;>;>;>Ljava/lang/Object;
|
||||
String classSig = "<D::Ljava/util/function/UnaryOperator<Ljava/util/Map<[[ILjava/util/function/BiFunction<TB;TA;TC;>;>;>;>Ljava/lang/Object;";
|
||||
|
||||
PoetClassMethodSignatureVisitor visitor = new PoetClassMethodSignatureVisitor(TypeAnnotationMapping.EMPTY, s -> false, true);
|
||||
new SignatureReader(classSig).accept(visitor);
|
||||
ClassSignature parsed = visitor.collectClass();
|
||||
Assertions.assertIterableEquals(Collections.singleton(TypeVariableName.get("D")), parsed.generics());
|
||||
Assertions.assertEquals(ClassName.OBJECT, parsed.superclass());
|
||||
Assertions.assertIterableEquals(Collections.emptyList(), parsed.superinterfaces());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCollectionIntFunctionToArraySignature() {
|
||||
// signature <T:Ljava/lang/Object;>(Ljava/util/function/IntFunction<[TT;>;)[TT;
|
||||
String methodSignature = "<T:Ljava/lang/Object;>(Ljava/util/function/IntFunction<[TT;>;)[TT;";
|
||||
MethodSignature parsed = Signatures.parseMethodSignature(methodSignature);
|
||||
Assertions.assertIterableEquals(Collections.singleton(TypeVariableName.get("T")), parsed.generics());
|
||||
Assertions.assertEquals(ArrayTypeName.of(TypeVariableName.get("T")), parsed.result());
|
||||
Assertions.assertIterableEquals(Collections.emptyList(), parsed.thrown());
|
||||
Assertions.assertIterableEquals(Collections.singleton(ParameterizedTypeName.get(ClassName.get(IntFunction.class), ArrayTypeName.of(TypeVariableName.get("T")))), parsed.parameters());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCollectionArrayToArraySignature() {
|
||||
// signature <T:Ljava/lang/Object;>([TT;)[TT;
|
||||
String methodSignature = "<T:Ljava/lang/Object;>([TT;)[TT;";
|
||||
MethodSignature parsed = Signatures.parseMethodSignature(methodSignature);
|
||||
Assertions.assertIterableEquals(Collections.singleton(TypeVariableName.get("T")), parsed.generics());
|
||||
Assertions.assertEquals(ArrayTypeName.of(TypeVariableName.get("T")), parsed.result());
|
||||
Assertions.assertIterableEquals(Collections.emptyList(), parsed.thrown());
|
||||
Assertions.assertIterableEquals(Collections.singleton(ArrayTypeName.of(TypeVariableName.get("T"))), parsed.parameters());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCollectionArrayToArraySignatureVisitor() {
|
||||
// signature <T:Ljava/lang/Object;>([TT;)[TT;
|
||||
String methodSignature = "<T:Ljava/lang/Object;>([TT;)[TT;";
|
||||
PoetClassMethodSignatureVisitor visitor = new PoetClassMethodSignatureVisitor(TypeAnnotationMapping.EMPTY, s -> false, false);
|
||||
new SignatureReader(methodSignature).accept(visitor);
|
||||
MethodSignature parsed = visitor.collectMethod();
|
||||
Assertions.assertIterableEquals(Collections.singleton(TypeVariableName.get("T")), parsed.generics());
|
||||
Assertions.assertEquals(ArrayTypeName.of(TypeVariableName.get("T")), parsed.result());
|
||||
Assertions.assertIterableEquals(Collections.emptyList(), parsed.thrown());
|
||||
Assertions.assertIterableEquals(Collections.singleton(ArrayTypeName.of(TypeVariableName.get("T"))), parsed.parameters());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTweakLastSignature() {
|
||||
// signature (Ljava/util/function/UnaryOperator<Lcom/squareup/javapoet/TypeName;>;)V
|
||||
String methodSignature = "(Ljava/util/function/UnaryOperator<Lcom/squareup/javapoet/TypeName;>;)V";
|
||||
MethodSignature parsed = Signatures.parseMethodSignature(methodSignature);
|
||||
Assertions.assertTrue(parsed.generics().isEmpty());
|
||||
Assertions.assertEquals(TypeName.VOID, parsed.result());
|
||||
Assertions.assertIterableEquals(Collections.emptyList(), parsed.thrown());
|
||||
ClassName unaryOperatorClass = ClassName.get(UnaryOperator.class);
|
||||
ClassName typeNameClass = ClassName.get(TypeName.class);
|
||||
Assertions.assertIterableEquals(Collections.singleton(ParameterizedTypeName.get(unaryOperatorClass, typeNameClass)), parsed.parameters());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTweakLastSignatureVisitor() {
|
||||
// signature (Ljava/util/function/UnaryOperator<Lcom/squareup/javapoet/TypeName;>;)V
|
||||
String methodSignature = "(Ljava/util/function/UnaryOperator<Lcom/squareup/javapoet/TypeName;>;)V";
|
||||
PoetClassMethodSignatureVisitor visitor = new PoetClassMethodSignatureVisitor(TypeAnnotationMapping.EMPTY, s -> false, false);
|
||||
new SignatureReader(methodSignature).accept(visitor);
|
||||
MethodSignature parsed = visitor.collectMethod();
|
||||
Assertions.assertTrue(parsed.generics().isEmpty());
|
||||
Assertions.assertEquals(TypeName.VOID, parsed.result());
|
||||
Assertions.assertIterableEquals(Collections.emptyList(), parsed.thrown());
|
||||
ClassName unaryOperatorClass = ClassName.get(UnaryOperator.class);
|
||||
ClassName typeNameClass = ClassName.get(TypeName.class);
|
||||
Assertions.assertIterableEquals(Collections.singleton(ParameterizedTypeName.get(unaryOperatorClass, typeNameClass)), parsed.parameters());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckHeadSignature() {
|
||||
// signature <E:Ljava/lang/Throwable;>(Lnet/fabricmc/filament/mappingpoet/Signatures$HeadChecker<TE;>;)V^TE;
|
||||
String raw = "<E:Ljava/lang/Throwable;>(Lnet/fabricmc/filament/mappingpoet/Signatures$HeadChecker<TE;>;)V^TE;";
|
||||
MethodSignature parsed = Signatures.parseMethodSignature(raw);
|
||||
Assertions.assertIterableEquals(Collections.singleton(TypeVariableName.get("E", ClassName.get(Throwable.class))), parsed.generics());
|
||||
ClassName headCheckerClass = ClassName.get(Signatures.class).nestedClass("HeadChecker");
|
||||
Assertions.assertIterableEquals(Collections.singleton(ParameterizedTypeName.get(headCheckerClass, TypeVariableName.get("E"))), parsed.parameters());
|
||||
Assertions.assertEquals(TypeName.VOID, parsed.result());
|
||||
Assertions.assertIterableEquals(Collections.singleton(TypeVariableName.get("E")), parsed.thrown());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckHeadSignatureVisitor() {
|
||||
// signature <E:Ljava/lang/Throwable;>(Lnet/fabricmc/filament/mappingpoet/Signatures$HeadChecker<TE;>;)V^TE;
|
||||
String raw = "<E:Ljava/lang/Throwable;>(Lnet/fabricmc/filament/mappingpoet/Signatures$HeadChecker<TE;>;)V^TE;";
|
||||
PoetClassMethodSignatureVisitor visitor = new PoetClassMethodSignatureVisitor(TypeAnnotationMapping.EMPTY, s -> false, false);
|
||||
new SignatureReader(raw).accept(visitor);
|
||||
MethodSignature parsed = visitor.collectMethod();
|
||||
Assertions.assertIterableEquals(Collections.singleton(TypeVariableName.get("E", ClassName.get(Throwable.class))), parsed.generics());
|
||||
ClassName headCheckerClass = ClassName.get(Signatures.class).nestedClass("HeadChecker");
|
||||
Assertions.assertIterableEquals(Collections.singleton(ParameterizedTypeName.get(headCheckerClass, TypeVariableName.get("E"))), parsed.parameters());
|
||||
Assertions.assertEquals(TypeName.VOID, parsed.result());
|
||||
Assertions.assertIterableEquals(Collections.singleton(TypeVariableName.get("E")), parsed.thrown());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBrokenVariantSettingSignature() {
|
||||
// Ljava/util/Map<Lnet/minecraft/data/client/model/VariantSetting<*>;Lnet/minecraft/data/client/model/VariantSetting<*>.Value;>;
|
||||
String signature = "Ljava/util/Map<Lnet/minecraft/data/client/model/VariantSetting<*>;Lnet/minecraft/data/client/model/VariantSetting<*>.Value;>;";
|
||||
Map.Entry<Integer, TypeName> parsed = Signatures.parseParameterizedType(signature, 0);
|
||||
Assertions.assertEquals(125, parsed.getKey().intValue());
|
||||
ClassName variantSettingClass = ClassName.get("net.minecraft.data.client.model", "VariantSetting");
|
||||
ParameterizedTypeName wildcardVariantSetting = ParameterizedTypeName.get(variantSettingClass, WildcardTypeName.subtypeOf(TypeName.OBJECT));
|
||||
ParameterizedTypeName valueClass = wildcardVariantSetting.nestedClass("Value");
|
||||
ClassName mapClass = ClassName.get(Map.class);
|
||||
ParameterizedTypeName genericMap = ParameterizedTypeName.get(mapClass, wildcardVariantSetting, valueClass);
|
||||
Assertions.assertEquals(genericMap, parsed.getValue());
|
||||
|
||||
PoetTypeSignatureWriter writer = new PoetTypeSignatureWriter(TypeAnnotationBank.EMPTY, s -> false);
|
||||
new SignatureReader(signature).acceptType(writer);
|
||||
Assertions.assertEquals(genericMap, writer.compute());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStaticOuters() {
|
||||
Outer.MiddleStatic.@TestAnno InnerStatic instance = null;
|
||||
|
||||
// https://docs.oracle.com/javase/specs/jvms/se15/html/jvms-4.html#jvms-4.7.20.2-220-B-A.1
|
||||
Outer.@TestAnno("a") MiddleStatic<@TestAnno("b") Object>.@TestAnno("c") Inner<@TestAnno("d") Integer> instance2 = new Outer.MiddleStatic<>().new Inner<Integer>();
|
||||
|
||||
String input = "Lnet/fabricmc/mappingpoet/Outer$MiddleStatic<Ljava/lang/Object;>.Inner<Ljava/lang/Integer;>;";
|
||||
|
||||
TypeName name = Signatures.parseFieldSignature(input);
|
||||
Assertions.assertEquals("net.fabricmc.mappingpoet.Outer.MiddleStatic<java.lang.Object>.Inner<java.lang.Integer>", name.toString());
|
||||
|
||||
PoetTypeSignatureWriter writer = new PoetTypeSignatureWriter(TypeAnnotationBank.EMPTY, s -> false);
|
||||
new SignatureReader(input).acceptType(writer);
|
||||
Assertions.assertEquals(name, writer.compute());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2020 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.filament.test.mappingpoet;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.TYPE_USE, ElementType.FIELD})
|
||||
public @interface TestAnno {
|
||||
String value() default "";
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Copyright (c) 2020 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.filament.test.mappingpoet;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
enum StupidEnum {
|
||||
FIRST,
|
||||
@TestAnno
|
||||
SECOND;
|
||||
}
|
||||
|
||||
// signature <A::Ljava/lang/Comparable<-TA;>;>Ljava/lang/Object;
|
||||
public class TestOuter<A extends Comparable<? super A>> {
|
||||
// signature <B::Ljava/util/Comparator<-TA;>;C:Ljava/lang/ClassLoader;:Ljava/lang/Iterable<*>;>Ljava/lang/Object;
|
||||
class Inner<B extends Comparator<? super A>, C extends ClassLoader & AutoCloseable> {
|
||||
// signature <D::Ljava/util/function/UnaryOperator<Ljava/util/Map<[[ILjava/util/function/BiFunction<TB;TA;TC;>;>;>;>Ljava/lang/Object;
|
||||
class ExtraInner<D extends UnaryOperator<Map<int[][], BiFunction<B, A, C>>>> {
|
||||
ExtraInner(Inner<B, C> Inner.this) {
|
||||
// constructor receiver example. Notice 'this' cannot receive parameter annos
|
||||
}
|
||||
|
||||
void work(@TestAnno("on extra inner")ExtraInner<D> this, Inner<@TestAnno("pig") B, C> @TestAnno("lion") [][] @TestAnno("rat") [] arr) {
|
||||
}
|
||||
|
||||
void work2(ExtraInner<D> this) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Outer {
|
||||
void eat(@BorkAnno @TestAnno MiddleTwo.InnerThree<Integer> apple) {
|
||||
}
|
||||
|
||||
void eat(@BorkAnno MiddleStatic.@TestAnno InnerStatic apple) {
|
||||
}
|
||||
|
||||
static class MiddleStatic<T> {
|
||||
static class InnerStatic {
|
||||
}
|
||||
|
||||
class Inner<U> {
|
||||
}
|
||||
}
|
||||
|
||||
class MiddleTwo {
|
||||
class InnerThree<E> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class OuterTwo<T> {
|
||||
static class InnerOne<G> {
|
||||
class InnerTwo {
|
||||
InnerTwo(InnerOne<G> InnerOne.this) {
|
||||
}
|
||||
|
||||
void called(InnerTwo this, InnerTwo other) {
|
||||
}
|
||||
|
||||
class NestedThree<W> {
|
||||
void work(NestedThree<W> this, NestedThree<W> other) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class InnerTwo {
|
||||
class NestedThree<W> {
|
||||
void workSelf(@TestAnno("on myself!")NestedThree<@TestAnno("on my W") W> this) {
|
||||
}
|
||||
|
||||
void workSelf1(NestedThree<@TestAnno("on my W only") W> this) {
|
||||
}
|
||||
|
||||
void workSelf2(@TestAnno("on myself only")NestedThree<W> this) {
|
||||
}
|
||||
|
||||
void work(@TestAnno("on myself!")NestedThree<@TestAnno("on my W") W> this, NestedThree<@TestAnno("on their W") W> other) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
tiny 2 0 intermediary official named
|
||||
notice This is an example to test functionalities!
|
||||
c net/fabricmc/mappingpoet/Outer a net/fabricmc/mappingpoet/Outer
|
||||
m (Lnet/fabricmc/mappingpoet/Outer$MiddleTwo$InnerThree;)V eaten ate eat
|
||||
c net/hackingmc/fightingpoet/ZombieTest b net/fabricmc/mappingpoet/SignaturesTest
|
|
@ -13,7 +13,6 @@ asm_version=9.6
|
|||
# Javadoc generation/linking
|
||||
fabric_loader_version=0.15.10
|
||||
jetbrains_annotations_version=24.1.0
|
||||
mappingpoet_version=0.4.2
|
||||
|
||||
# Build logic
|
||||
tiny_remapper_version=0.10.3
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2020 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
document.onreadystatechange = function() {
|
||||
if(document.readyState == "complete") {
|
||||
const items = document.querySelectorAll(".copyable");
|
||||
items.forEach(item => {
|
||||
item.title = "Click to copy";
|
||||
item.style["cursor"] = "pointer";
|
||||
item.onclick = function() {
|
||||
var range = document.createRange();
|
||||
range.selectNode(item);
|
||||
window.getSelection().addRange(range);
|
||||
document.execCommand("copy");
|
||||
window.getSelection().removeRange(range);
|
||||
console.log("Copied to clipboard");
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
|
@ -0,0 +1,39 @@
|
|||
.fabric {
|
||||
font: 400 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
color: #111;
|
||||
background-color: #fdfdfd;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-font-feature-settings: "kern" 1;
|
||||
-moz-font-feature-settings: "kern" 1;
|
||||
-o-font-feature-settings: "kern" 1;
|
||||
font-feature-settings: "kern" 1;
|
||||
font-kerning: normal;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.fabric table {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
color: #3f3f3f;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.fabric table tr:nth-child(even) {
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.fabric table th, table td {
|
||||
padding: 1px 10px;
|
||||
}
|
||||
|
||||
.fabric table th {
|
||||
background-color: #f0f0f0;
|
||||
border: 1px solid #dedede;
|
||||
border-bottom-color: #c9c9c9;
|
||||
}
|
||||
|
||||
.fabric table td {
|
||||
border: 1px solid #e8e8e8;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
<script src="{@docRoot}/copy_on_click.js"></script>
|
Loading…
Reference in New Issue