Preliminary work on getting records and sealed classes right

Signed-off-by: liach <liach@users.noreply.github.com>
This commit is contained in:
liach 2021-09-18 13:31:17 -05:00
parent defbde19d8
commit 099ddebbd0
4 changed files with 405 additions and 294 deletions

View File

@ -16,34 +16,45 @@
package net.fabricmc.mappingpoet;
import static net.fabricmc.mappingpoet.FieldBuilder.parseAnnotation;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
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.Opcodes;
import org.objectweb.asm.TypeReference;
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.MethodNode;
import net.fabricmc.mappingpoet.signature.AnnotationAwareDescriptors;
import net.fabricmc.mappingpoet.signature.AnnotationAwareSignatures;
import net.fabricmc.mappingpoet.signature.ClassSignature;
import net.fabricmc.mappingpoet.signature.ClassStaticContext;
import net.fabricmc.mappingpoet.signature.TypeAnnotationMapping;
import net.fabricmc.mappingpoet.signature.TypeAnnotationStorage;
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 java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import static net.fabricmc.mappingpoet.FieldBuilder.parseAnnotation;
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;
@ -51,21 +62,24 @@ public class ClassBuilder {
private final TypeSpec.Builder builder;
private final List<ClassBuilder> innerClasses = new ArrayList<>();
private final Function<String, Collection<String>> superGetter;
private final Predicate<String> sealChecker;
private final ClassStaticContext context;
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, Function<String, Collection<String>> superGetter, ClassStaticContext context) {
public ClassBuilder(MappingsStore mappings, ClassNode classNode, Function<String, Collection<String>> superGetter, Predicate<String> sealChecker, ClassStaticContext context) {
this.mappings = mappings;
this.classNode = classNode;
this.superGetter = superGetter;
this.sealChecker = sealChecker;
this.context = context;
this.typeAnnotations = setupAnnotations();
this.signature = setupSignature();
@ -144,6 +158,9 @@ public class ClassBuilder {
} 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());
@ -162,7 +179,10 @@ public class ClassBuilder {
}
return builder
.addModifiers(new ModifierBuilder(classNode.access).getModifiers(enumClass ? ModifierBuilder.Type.ENUM : ModifierBuilder.Type.CLASS));
.addModifiers(new ModifierBuilder(classNode.access)
.checkUnseal(classNode, sealChecker)
.getModifiers(ModifierBuilder.getType(enumClass, recordClass))
);
}
private void addInterfaces() {
@ -197,6 +217,7 @@ public class ClassBuilder {
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;
@ -217,6 +238,21 @@ public class ClassBuilder {
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
@ -229,6 +265,19 @@ public class ClassBuilder {
private void addFields() {
if (classNode.fields == null) return;
for (FieldNode field : classNode.fields) {
if (recordClass && !Modifier.isStatic(field.access)) {
if (!Modifier.isFinal(field.access) || !Modifier.isPrivate(field.access)) {
System.out.println("abnormal instance field " + field.name + " in record " + getClassName() + ", skipping");
} else {
var fieldBuilder = new FieldBuilder(mappings, classNode, field, context);
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
}
@ -285,7 +334,10 @@ public class ClassBuilder {
classBuilder.builder.addModifiers(javax.lang.model.element.Modifier.STATIC);
} else {
classBuilder.builder.modifiers.remove(javax.lang.model.element.Modifier.PUBLIC); // this modifier may come from class access
classBuilder.builder.addModifiers(new ModifierBuilder(innerClassNode.access).getModifiers(classBuilder.enumClass ? ModifierBuilder.Type.ENUM : ModifierBuilder.Type.CLASS));
classBuilder.builder.addModifiers(new ModifierBuilder(innerClassNode.access)
.checkUnseal(classBuilder.classNode, sealChecker)
.getModifiers(ModifierBuilder.getType(classBuilder.enumClass, classBuilder.recordClass))
);
if (!Modifier.isStatic(innerClassNode.access)) {
classBuilder.instanceInner = true;
}

View File

@ -16,20 +16,21 @@
package net.fabricmc.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 net.fabricmc.mapping.util.EntryTriple;
import net.fabricmc.mappingpoet.signature.AnnotationAwareDescriptors;
import net.fabricmc.mappingpoet.signature.AnnotationAwareSignatures;
import net.fabricmc.mappingpoet.signature.ClassStaticContext;
import net.fabricmc.mappingpoet.signature.TypeAnnotationBank;
import net.fabricmc.mappingpoet.signature.TypeAnnotationMapping;
import net.fabricmc.mappingpoet.signature.TypeAnnotationStorage;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.TypePath;
@ -38,19 +39,18 @@ import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import net.fabricmc.mapping.util.EntryTriple;
import net.fabricmc.mappingpoet.signature.AnnotationAwareDescriptors;
import net.fabricmc.mappingpoet.signature.AnnotationAwareSignatures;
import net.fabricmc.mappingpoet.signature.ClassStaticContext;
import net.fabricmc.mappingpoet.signature.TypeAnnotationBank;
import net.fabricmc.mappingpoet.signature.TypeAnnotationMapping;
import net.fabricmc.mappingpoet.signature.TypeAnnotationStorage;
import java.util.AbstractMap;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public class FieldBuilder {
private final MappingsStore mappings;
private final ClassNode classNode;
private final FieldNode fieldNode;
private final FieldSpec.Builder builder;
private final FieldSpec.Builder builder; // todo extract interface to build both record param/field easily
private final TypeAnnotationMapping annotations;
private final ClassStaticContext context;
@ -129,90 +129,90 @@ public class FieldBuilder {
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;
}
case 'B': {
current = TypeName.BYTE;
index++;
} while (ch != ';');
if (currentClassName == null) {
throw invalidDesc(desc, 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;
current = currentClassName;
break;
}
default:
throw invalidDesc(desc, index);
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++) {
@ -233,101 +233,101 @@ public class FieldBuilder {
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;
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);
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 (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;
}
if (!instanceInner && context.isInstanceInner(desc.substring(nameStart, index))) {
instanceInner = true;
}
currentClassName = currentClassName.nestedClass(simpleName);
currentClassName = currentClassName.nestedClass(simpleName);
if (instanceInner) {
currentClassName = AnnotationAwareDescriptors.annotate(currentClassName, annotations);
annotations = annotations.advance(TypePath.INNER_TYPE, 0);
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);
}
if (ch == '/' || ch == '$') {
// Start of simple name
classNameSeparator = index;
}
index++;
} while (ch != ';');
if (currentClassName == null) {
throw invalidDesc(desc, index);
current = currentClassName;
break;
}
current = currentClassName;
break;
}
default:
throw invalidDesc(desc, index);
default:
throw invalidDesc(desc, index);
}
while (!arrayAnnos.isEmpty()) {
@ -348,24 +348,24 @@ public class FieldBuilder {
@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;
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)));
@ -390,48 +390,48 @@ public class FieldBuilder {
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();
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();
@ -448,20 +448,20 @@ public class FieldBuilder {
// 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("'\\\\'");
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);
@ -471,6 +471,10 @@ public class FieldBuilder {
mappings.addFieldDoc(builder::addJavadoc, new EntryTriple(classNode.name, fieldNode.name, fieldNode.desc));
}
void addJavaDoc(ParameterSpec.Builder paramBuilder) {
mappings.addFieldDoc(paramBuilder::addJavadoc, new EntryTriple(classNode.name, fieldNode.name, fieldNode.desc));
}
private void addDirectAnnotations() {
addDirectAnnotations(fieldNode.invisibleAnnotations);
addDirectAnnotations(fieldNode.visibleAnnotations);
@ -485,7 +489,21 @@ public class FieldBuilder {
}
}
private TypeName calculateType() {
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);
}

View File

@ -16,6 +16,14 @@
package net.fabricmc.mappingpoet;
import com.squareup.javapoet.JavaFile;
import net.fabricmc.mappingpoet.signature.ClassStaticContext;
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 java.io.File;
import java.io.IOException;
import java.io.InputStream;
@ -33,22 +41,16 @@ import java.util.Collections;
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.function.Function;
import java.util.function.Predicate;
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.mappingpoet.signature.ClassStaticContext;
public class Main {
public static void main(String[] args) {
@ -90,7 +92,7 @@ public class Main {
public static void generate(Path mappings, Path inputJar, Path outputDirectory, Path librariesDir) {
final MappingsStore mapping = new MappingsStore(mappings);
Map<String, ClassBuilder> classes = new HashMap<>();
forEachClass(inputJar, (superGetter, classNode, context) -> writeClass(mapping, classNode, classes, superGetter, context), librariesDir);
forEachClass(inputJar, (superGetter, classNode, context, sealChecker) -> writeClass(mapping, classNode, classes, superGetter, sealChecker, context), librariesDir);
classes.values().stream()
.filter(classBuilder -> !classBuilder.getClassName().contains("$"))
@ -111,6 +113,7 @@ public class Main {
private static void forEachClass(Path jar, ClassNodeConsumer classNodeConsumer, Path librariesDir) {
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, Boolean> instanceInnerClasses = new ConcurrentHashMap<>();
@ -149,6 +152,10 @@ public class Main {
}
}
if (classNode.permittedSubclasses != null) {
sealedClasses.add(classNode.name);
}
classes.add(classNode);
}
}
@ -161,7 +168,7 @@ public class Main {
ClassStaticContext innerClassContext = new InnerClassStats(instanceInnerClasses);
Function<String, Collection<String>> superGetter = k -> supers.getOrDefault(k, Collections.emptyList());
classes.forEach(node -> classNodeConsumer.accept(superGetter, node, innerClassContext));
classes.forEach(node -> classNodeConsumer.accept(superGetter, node, innerClassContext, sealedClasses::contains));
}
private static void scanInnerClasses(Map<String, Boolean> instanceInnerClasses, Path librariesDir) {
@ -218,7 +225,7 @@ public class Main {
return ch >= '0' && ch <= '9';
}
private static void writeClass(MappingsStore mappings, ClassNode classNode, Map<String, ClassBuilder> existingClasses, Function<String, Collection<String>> superGetter, ClassStaticContext context) {
private static void writeClass(MappingsStore mappings, ClassNode classNode, Map<String, ClassBuilder> existingClasses, Function<String, Collection<String>> superGetter, Predicate<String> sealChecker, ClassStaticContext context) {
String name = classNode.name;
{
//Block anonymous class and their nested classes
@ -232,7 +239,7 @@ public class Main {
}
}
ClassBuilder classBuilder = new ClassBuilder(mappings, classNode, superGetter, context);
ClassBuilder classBuilder = new ClassBuilder(mappings, classNode, superGetter, sealChecker, context);
if (name.contains("$")) {
String parentClass = name.substring(0, name.lastIndexOf("$"));
@ -249,7 +256,7 @@ public class Main {
@FunctionalInterface
private interface ClassNodeConsumer {
void accept(Function<String, Collection<String>> superGetter, ClassNode node, ClassStaticContext staticContext);
void accept(Function<String, Collection<String>> superGetter, ClassNode node, ClassStaticContext staticContext, Predicate<String> sealedChecker);
}
private static final class InnerClassStats implements ClassStaticContext {

View File

@ -16,19 +16,44 @@
package net.fabricmc.mappingpoet;
import java.util.ArrayList;
import java.util.List;
import org.objectweb.asm.tree.ClassNode;
import javax.lang.model.element.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public class ModifierBuilder {
private final int access;
private boolean needsUnseal;
public ModifierBuilder(int access) {
this.access = access;
}
public ModifierBuilder checkUnseal(ClassNode node, Predicate<String> sealChecker) {
if (java.lang.reflect.Modifier.isFinal(node.access)) {
return this;
}
if (node.interfaces != null) {
for (String itf : node.interfaces) {
if (sealChecker.test(itf)) {
needsUnseal = true;
return this;
}
}
}
if (node.superName != null && sealChecker.test(node.superName)) {
needsUnseal = true;
}
return this;
}
public Modifier[] getModifiers(Type type) {
List<Modifier> modifiers = new ArrayList<>();
@ -36,7 +61,7 @@ public class ModifierBuilder {
if (java.lang.reflect.Modifier.isFinal(access)) {
modifiers.add(Modifier.FINAL);
}
return modifiers.toArray(new Modifier[] {});
return modifiers.toArray(new Modifier[]{});
}
if (java.lang.reflect.Modifier.isPublic(access)) {
@ -57,7 +82,7 @@ public class ModifierBuilder {
modifiers.add(Modifier.DEFAULT);
}
if (java.lang.reflect.Modifier.isFinal(access) && type != Type.ENUM) {
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) {
@ -76,12 +101,21 @@ public class ModifierBuilder {
modifiers.add(Modifier.STRICTFP);
}
return modifiers.toArray(new Modifier[] {});
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