aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonas Herzig <me@johni0702.de>2019-08-30 16:48:00 +0200
committerJonas Herzig <me@johni0702.de>2019-08-30 16:48:00 +0200
commitcfdc125366b756a7d164502ebcde22e2976c9319 (patch)
tree03559547dd1cc52ec9a3f2a13c490da3d02182e6
parent06279d658496a3fb0a1909bad1ebdb9a60a37aed (diff)
downloadRemap-cfdc125366b756a7d164502ebcde22e2976c9319.tar.gz
Remap-cfdc125366b756a7d164502ebcde22e2976c9319.tar.bz2
Remap-cfdc125366b756a7d164502ebcde22e2976c9319.zip
Convert implementation and build script to Kotlin
-rw-r--r--build.gradle.kts27
-rw-r--r--src/main/java/com/replaymod/gradle/remap/PsiMapper.java429
-rw-r--r--src/main/java/com/replaymod/gradle/remap/PsiUtils.java61
-rw-r--r--src/main/java/com/replaymod/gradle/remap/Transformer.java173
-rw-r--r--src/main/java/com/replaymod/gradle/remap/legacy/LegacyMapping.java118
-rw-r--r--src/main/java/com/replaymod/gradle/remap/legacy/LegacyMappingSetModelFactory.java36
-rw-r--r--src/main/java/com/replaymod/gradle/remap/legacy/LegacyMappingsReader.java44
-rw-r--r--src/main/kotlin/com/replaymod/gradle/remap/LorenzExtensions.kt11
-rw-r--r--src/main/kotlin/com/replaymod/gradle/remap/PsiMapper.kt365
-rw-r--r--src/main/kotlin/com/replaymod/gradle/remap/PsiUtils.kt41
-rw-r--r--src/main/kotlin/com/replaymod/gradle/remap/Transformer.kt149
-rw-r--r--src/main/kotlin/com/replaymod/gradle/remap/legacy/LegacyMapping.kt109
-rw-r--r--src/main/kotlin/com/replaymod/gradle/remap/legacy/LegacyMappingSetModelFactory.kt33
-rw-r--r--src/main/kotlin/com/replaymod/gradle/remap/legacy/LegacyMappingsReader.kt28
14 files changed, 751 insertions, 873 deletions
diff --git a/build.gradle.kts b/build.gradle.kts
index c00ca4c..12b502b 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,31 +1,34 @@
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
plugins {
- id 'java'
- id 'maven-publish'
+ kotlin("jvm") version "1.3.40"
+ `maven-publish`
}
-sourceCompatibility = '1.8'
-targetCompatibility = '1.8'
+tasks.withType<KotlinCompile>().configureEach {
+ kotlinOptions.jvmTarget = "1.8"
+}
-group = 'com.github.replaymod'
-version = 'SNAPSHOT'
+group = "com.github.replaymod"
+version = "SNAPSHOT"
repositories {
mavenCentral()
}
dependencies {
- compile 'org.jetbrains.kotlin:kotlin-compiler:1.3.40'
- compile 'org.cadixdev:lorenz:0.5.0'
+ compile("org.jetbrains.kotlin:kotlin-compiler:1.3.40")
+ compile("org.cadixdev:lorenz:0.5.0")
}
-jar {
- archiveBaseName.set('remap')
+tasks.named<Jar>("jar") {
+ archiveBaseName.set("remap")
}
publishing {
publications {
- maven(MavenPublication) {
- from components.java
+ create("maven", MavenPublication::class) {
+ from(components["java"])
}
}
}
diff --git a/src/main/java/com/replaymod/gradle/remap/PsiMapper.java b/src/main/java/com/replaymod/gradle/remap/PsiMapper.java
deleted file mode 100644
index c1b2d41..0000000
--- a/src/main/java/com/replaymod/gradle/remap/PsiMapper.java
+++ /dev/null
@@ -1,429 +0,0 @@
-package com.replaymod.gradle.remap;
-
-import com.intellij.openapi.util.TextRange;
-import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.psi.JavaRecursiveElementVisitor;
-import com.intellij.psi.PsiAnnotation;
-import com.intellij.psi.PsiAnnotationMemberValue;
-import com.intellij.psi.PsiClass;
-import com.intellij.psi.PsiClassObjectAccessExpression;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiField;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.PsiIdentifier;
-import com.intellij.psi.PsiJavaCodeReferenceElement;
-import com.intellij.psi.PsiMethod;
-import com.intellij.psi.PsiNameValuePair;
-import com.intellij.psi.PsiPackage;
-import com.intellij.psi.PsiQualifiedNamedElement;
-import com.intellij.psi.PsiSwitchLabelStatement;
-import com.intellij.psi.PsiTypeElement;
-import org.cadixdev.bombe.type.signature.MethodSignature;
-import org.cadixdev.lorenz.MappingSet;
-import org.cadixdev.lorenz.model.ClassMapping;
-import org.cadixdev.lorenz.model.Mapping;
-import org.cadixdev.lorenz.model.MethodMapping;
-
-import java.util.ArrayDeque;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.TreeMap;
-
-import static com.replaymod.gradle.remap.PsiUtils.getSignature;
-
-class PsiMapper {
- private static final String CLASS_MIXIN = "org.spongepowered.asm.mixin.Mixin";
- private static final String CLASS_ACCESSOR = "org.spongepowered.asm.mixin.gen.Accessor";
- private static final String CLASS_AT = "org.spongepowered.asm.mixin.injection.At";
- private static final String CLASS_INJECT = "org.spongepowered.asm.mixin.injection.Inject";
- private static final String CLASS_REDIRECT = "org.spongepowered.asm.mixin.injection.Redirect";
-
- private final MappingSet map;
- private final Map<String, ClassMapping<?, ?>> mixinMappings = new HashMap<>();
- private final PsiFile file;
- private boolean error;
- private TreeMap<TextRange, String> changes = new TreeMap<>(Comparator.comparing(TextRange::getStartOffset));
-
- PsiMapper(MappingSet map, PsiFile file) {
- this.map = map;
- this.file = file;
- }
-
- private void error(PsiElement at, String message) {
- int line = StringUtil.offsetToLineNumber(file.getText(), at.getTextOffset());
- System.err.println(file.getName() + ":" + line + ": " + message);
- error = true;
- }
-
- private void replace(PsiElement e, String with) {
- changes.put(e.getTextRange(), with);
- }
-
- private void replaceIdentifier(PsiElement parent, String with) {
- for (PsiElement child : parent.getChildren()) {
- if (child instanceof PsiIdentifier) {
- replace(child, with);
- return;
- }
- }
- }
-
- private boolean valid(PsiElement e) {
- TextRange range = e.getTextRange();
- TextRange before = changes.ceilingKey(range);
- return before == null || !before.intersects(range);
- }
-
- private String getResult(String text) {
- if (error) {
- return null;
- }
- for (Map.Entry<TextRange, String> change : changes.descendingMap().entrySet()) {
- text = change.getKey().replace(text, change.getValue());
- }
- return text;
- }
-
- private static boolean isSwitchCase(PsiElement e) {
- if (e instanceof PsiSwitchLabelStatement) {
- return true;
- }
- PsiElement parent = e.getParent();
- return parent != null && isSwitchCase(parent);
- }
-
- private void map(PsiElement expr, PsiField field) {
- PsiClass declaringClass = field.getContainingClass();
- if (declaringClass == null) return;
- String name = declaringClass.getQualifiedName();
- if (name == null) return;
- ClassMapping<?, ?> mapping = this.mixinMappings.get(name);
- if (mapping == null) {
- mapping = map.getClassMapping(name).orElse(null);
- }
- if (mapping == null) return;
- String mapped = mapping.getFieldMapping(field.getName()).map(Mapping::getDeobfuscatedName).orElse(null);
- if (mapped == null || mapped.equals(field.getName())) return;
- replaceIdentifier(expr, mapped);
-
- if (expr instanceof PsiJavaCodeReferenceElement
- && !((PsiJavaCodeReferenceElement) expr).isQualified() // qualified access is fine
- && !isSwitchCase(expr) // referencing constants in case statements is fine
- ) {
- error(expr, "Implicit member reference to remapped field \"" + field.getName() + "\". " +
- "This can cause issues if the remapped reference becomes shadowed by a local variable and is therefore forbidden. " +
- "Use \"this." + field.getName() + "\" instead.");
- }
- }
-
- private void map(PsiElement expr, PsiMethod method) {
- if (method.isConstructor()) return;
-
- PsiClass declaringClass = method.getContainingClass();
- if (declaringClass == null) return;
- ArrayDeque<PsiClass> parentQueue = new ArrayDeque<>();
- parentQueue.offer(declaringClass);
- ClassMapping<?, ?> mapping = null;
-
- String name = declaringClass.getQualifiedName();
- if (name != null) {
- mapping = mixinMappings.get(name);
- }
- while (true) {
- if (mapping != null) {
- String mapped = mapping.getMethodMapping(getSignature(method)).map(Mapping::getDeobfuscatedName).orElse(null);
- if (mapped != null) {
- if (!mapped.equals(method.getName())) {
- replaceIdentifier(expr, mapped);
- }
- return;
- }
- mapping = null;
- }
- while (mapping == null) {
- declaringClass = parentQueue.poll();
- if (declaringClass == null) return;
-
- PsiClass superClass = declaringClass.getSuperClass();
- if (superClass != null) {
- parentQueue.offer(superClass);
- }
- for (PsiClass anInterface : declaringClass.getInterfaces()) {
- parentQueue.offer(anInterface);
- }
-
- name = declaringClass.getQualifiedName();
- if (name == null) continue;
- mapping = map.getClassMapping(name).orElse(null);
- }
- }
- }
-
- private void map(PsiElement expr, PsiQualifiedNamedElement resolved) {
- String name = resolved.getQualifiedName();
- if (name == null) return;
- ClassMapping<?, ?> mapping = map.getClassMapping(name).orElse(null);
- if (mapping == null) return;
- String mapped = mapping.getDeobfuscatedName();
- if (mapped.equals(name)) return;
- mapped = mapped.replace('/', '.');
-
- if (expr.getText().equals(name)) {
- replace(expr, mapped);
- return;
- }
- replaceIdentifier(expr, mapped.substring(mapped.lastIndexOf('.') + 1));
- }
-
- private void map(PsiElement expr, PsiElement resolved) {
- if (resolved instanceof PsiField) {
- map(expr, (PsiField) resolved);
- } else if (resolved instanceof PsiMethod) {
- map(expr, (PsiMethod) resolved);
- } else if (resolved instanceof PsiClass || resolved instanceof PsiPackage) {
- map(expr, (PsiQualifiedNamedElement) resolved);
- }
- }
-
- // Note: Supports only Mixins with a single target (ignores others) and only ones specified via class literals
- private PsiClass getMixinTarget(PsiAnnotation annotation) {
- for (PsiNameValuePair pair : annotation.getParameterList().getAttributes()) {
- String name = pair.getName();
- if (name != null && !"value".equals(name)) continue;
- PsiAnnotationMemberValue value = pair.getValue();
- if (!(value instanceof PsiClassObjectAccessExpression)) continue;
- PsiTypeElement type = ((PsiClassObjectAccessExpression) value).getOperand();
- PsiJavaCodeReferenceElement reference = type.getInnermostComponentReferenceElement();
- if (reference == null) continue;
- return (PsiClass) reference.resolve();
- }
- return null;
- }
-
- private void remapAccessors(ClassMapping<?, ?> mapping) {
- file.accept(new JavaRecursiveElementVisitor() {
- @Override
- public void visitMethod(PsiMethod method) {
- PsiAnnotation annotation = method.getAnnotation(CLASS_ACCESSOR);
- if (annotation == null) return;
-
- String targetByName = method.getName();
- if (targetByName.startsWith("is")) {
- targetByName = targetByName.substring(2);
- } else if (targetByName.startsWith("get") || targetByName.startsWith("set")) {
- targetByName = targetByName.substring(3);
- } else {
- targetByName = null;
- }
- if (targetByName != null) {
- targetByName = targetByName.substring(0, 1).toLowerCase() + targetByName.substring(1);
- }
-
- String target = Arrays.stream(annotation.getParameterList().getAttributes())
- .filter(it -> it.getName() == null || it.getName().equals("value"))
- .map(PsiNameValuePair::getLiteralValue)
- .findAny()
- .orElse(targetByName);
-
- if (target == null) {
- throw new IllegalArgumentException("Cannot determine accessor target for " + method);
- }
-
- String mapped = mapping.getFieldMapping(target).map(Mapping::getDeobfuscatedName).orElse(null);
- if (mapped != null && !mapped.equals(target)) {
- // Update accessor target
- String parameterList;
- if (mapped.equals(targetByName)) {
- // Mapped name matches implied target, can just remove the explict target
- parameterList = "";
- } else {
- // Mapped name does not match implied target, need to set the target as annotation value
- parameterList = "(\"" + StringUtil.escapeStringCharacters(mapped) + "\")";
- }
- replace(annotation.getParameterList(), parameterList);
- }
- }
- });
- }
-
- private void remapInjectsAndRedirects(ClassMapping<?, ?> mapping) {
- file.accept(new JavaRecursiveElementVisitor() {
- @Override
- public void visitMethod(PsiMethod method) {
- PsiAnnotation annotation = method.getAnnotation(CLASS_INJECT);
- if (annotation == null) {
- annotation = method.getAnnotation(CLASS_REDIRECT);
- }
- if (annotation == null) return;
-
- for (PsiNameValuePair attribute : annotation.getParameterList().getAttributes()) {
- if (!"method".equals(attribute.getName())) continue;
- // Note: mixin supports multiple targets, we do not (yet)
- String literalValue = attribute.getLiteralValue();
- if (literalValue == null) continue;
- String mapped;
- if (literalValue.contains("(")) {
- mapped = mapping.getMethodMapping(MethodSignature.of(literalValue)).map(Mapping::getDeobfuscatedName).orElse(null);
- } else {
- mapped = null;
- for (MethodMapping methodMapping : mapping.getMethodMappings()) {
- if (methodMapping.getObfuscatedName().equals(literalValue)) {
- String name = methodMapping.getDeobfuscatedName();
- if (mapped != null && !mapped.equals(name)) {
- error(attribute, "Ambiguous mixin method \"" + literalValue + "\" maps to \"" + mapped + "\" and \"" + name + "\"");
- }
- mapped = name;
- }
- }
- }
- if (mapped != null && !mapped.equals(literalValue)) {
- PsiAnnotationMemberValue value = attribute.getValue();
- assert value != null;
- replace(value, '"' + mapped + '"');
- }
- }
- }
- });
- }
-
- private ClassMapping<?, ?> remapInternalType(String internalType, StringBuilder result) {
- if (internalType.charAt(0) == 'L') {
- String type = internalType.substring(1, internalType.length() - 1).replace('/', '.');
- ClassMapping<?, ?> mapping = map.getClassMapping(type).orElse(null);
- if (mapping != null) {
- result.append('L').append(mapping.getFullDeobfuscatedName()).append(';');
- return mapping;
- }
- }
- result.append(internalType);
- return null;
- }
-
- private String remapFullyQualifiedMethodOrField(String signature) {
- int ownerEnd = signature.indexOf(';');
- int argsBegin = signature.indexOf('(');
- int argsEnd = signature.indexOf(')');
- boolean method = argsBegin != -1;
- if (!method) {
- argsBegin = argsEnd = signature.indexOf(':');
- }
- String owner = signature.substring(0, ownerEnd + 1);
- String name = signature.substring(ownerEnd + 1, argsBegin);
- String returnType = signature.substring(argsEnd + 1);
-
- StringBuilder builder = new StringBuilder(signature.length() + 32);
- ClassMapping<?, ?> mapping = remapInternalType(owner, builder);
- String mapped = null;
- if (mapping != null) {
- mapped = (method
- ? mapping.getMethodMapping(MethodSignature.of(signature.substring(ownerEnd + 1)))
- : mapping.getFieldMapping(name))
- .map(Mapping::getDeobfuscatedName)
- .orElse(null);
- }
- builder.append(mapped != null ? mapped : name);
- if (method) {
- builder.append('(');
- String args = signature.substring(argsBegin + 1, argsEnd);
- for (int i = 0; i < args.length(); i++) {
- char c = args.charAt(i);
- if (c != 'L') {
- builder.append(c);
- continue;
- }
- int end = args.indexOf(';', i);
- String arg = args.substring(i, end + 1);
- remapInternalType(arg, builder);
- i = end;
- }
- builder.append(')');
- } else {
- builder.append(':');
- }
- remapInternalType(returnType, builder);
- return builder.toString();
- }
-
- private void remapAtTargets() {
- file.accept(new JavaRecursiveElementVisitor() {
- @Override
- public void visitAnnotation(PsiAnnotation annotation) {
- if (!CLASS_AT.equals(annotation.getQualifiedName())) {
- super.visitAnnotation(annotation);
- return;
- }
-
- for (PsiNameValuePair attribute : annotation.getParameterList().getAttributes()) {
- if (!"target".equals(attribute.getName())) continue;
- String signature = attribute.getLiteralValue();
- if (signature == null) continue;
- String newSignature = remapFullyQualifiedMethodOrField(signature);
- if (!newSignature.equals(signature)) {
- PsiAnnotationMemberValue value = attribute.getValue();
- assert value != null;
- replace(value, '"' + newSignature + '"');
- }
- }
- }
- });
- }
-
- String remapFile() {
- file.accept(new JavaRecursiveElementVisitor() {
- @Override
- public void visitClass(PsiClass psiClass) {
- PsiAnnotation annotation = psiClass.getAnnotation(CLASS_MIXIN);
- if (annotation == null) return;
-
- remapAtTargets();
-
- PsiClass target = getMixinTarget(annotation);
- if (target == null) return;
- String qualifiedName = target.getQualifiedName();
- if (qualifiedName == null) return;
-
- ClassMapping<?, ?> mapping = map.getClassMapping(qualifiedName).orElse(null);
- if (mapping == null) return;
-
- mixinMappings.put(psiClass.getQualifiedName(), mapping);
-
- if (!mapping.getFieldMappings().isEmpty()) {
- remapAccessors(mapping);
- }
- if (!mapping.getMethodMappings().isEmpty()) {
- remapInjectsAndRedirects(mapping);
- }
- }
- });
-
- file.accept(new JavaRecursiveElementVisitor() {
- @Override
- public void visitField(PsiField field) {
- if (valid(field)) {
- map(field, field);
- }
- super.visitField(field);
- }
-
- @Override
- public void visitMethod(PsiMethod method) {
- if (valid(method)) {
- map(method, method);
- }
- super.visitMethod(method);
- }
-
- @Override
- public void visitReferenceElement(PsiJavaCodeReferenceElement reference) {
- if (valid(reference)) {
- map(reference, reference.resolve());
- }
- super.visitReferenceElement(reference);
- }
- });
-
- return getResult(file.getText());
- }
-}
diff --git a/src/main/java/com/replaymod/gradle/remap/PsiUtils.java b/src/main/java/com/replaymod/gradle/remap/PsiUtils.java
deleted file mode 100644
index ec4d1c8..0000000
--- a/src/main/java/com/replaymod/gradle/remap/PsiUtils.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package com.replaymod.gradle.remap;
-
-import com.intellij.psi.PsiArrayType;
-import com.intellij.psi.PsiClass;
-import com.intellij.psi.PsiClassType;
-import com.intellij.psi.PsiMethod;
-import com.intellij.psi.PsiPrimitiveType;
-import com.intellij.psi.PsiType;
-import com.intellij.psi.util.TypeConversionUtil;
-import org.cadixdev.bombe.type.ArrayType;
-import org.cadixdev.bombe.type.FieldType;
-import org.cadixdev.bombe.type.MethodDescriptor;
-import org.cadixdev.bombe.type.ObjectType;
-import org.cadixdev.bombe.type.Type;
-import org.cadixdev.bombe.type.VoidType;
-import org.cadixdev.bombe.type.signature.MethodSignature;
-
-import java.util.Arrays;
-import java.util.stream.Collectors;
-
-class PsiUtils {
- static MethodSignature getSignature(PsiMethod method) {
- return new MethodSignature(method.getName(), getDescriptor(method));
- }
-
- private static MethodDescriptor getDescriptor(PsiMethod method) {
- return new MethodDescriptor(
- Arrays.stream(method.getParameterList().getParameters())
- .map(it -> getFieldType(it.getType()))
- .collect(Collectors.toList()),
- getType(method.getReturnType())
- );
- }
-
- private static FieldType getFieldType(PsiType type) {
- type = TypeConversionUtil.erasure(type);
- if (type instanceof PsiPrimitiveType) {
- return FieldType.of(((PsiPrimitiveType) type).getKind().getBinaryName());
- } else if (type instanceof PsiArrayType) {
- PsiArrayType array = (PsiArrayType) type;
- return new ArrayType(array.getArrayDimensions(), getFieldType(array.getDeepComponentType()));
- } else if (type instanceof PsiClassType) {
- PsiClass resolved = ((PsiClassType) type).resolve();
- if (resolved == null) throw new NullPointerException("Failed to resolve type " + type);
- String qualifiedName = resolved.getQualifiedName();
- if (qualifiedName == null) throw new NullPointerException("Type " + type + " has no qualified name.");
- return new ObjectType(qualifiedName);
- } else {
- throw new IllegalArgumentException("Cannot translate type " + type);
- }
- }
-
- private static Type getType(PsiType type) {
- if (TypeConversionUtil.isVoidType(type)) {
- return VoidType.INSTANCE;
- } else {
- return getFieldType(type);
- }
- }
-
-}
diff --git a/src/main/java/com/replaymod/gradle/remap/Transformer.java b/src/main/java/com/replaymod/gradle/remap/Transformer.java
deleted file mode 100644
index 5b25856..0000000
--- a/src/main/java/com/replaymod/gradle/remap/Transformer.java
+++ /dev/null
@@ -1,173 +0,0 @@
-package com.replaymod.gradle.remap;
-
-import com.intellij.codeInsight.CustomExceptionHandler;
-import com.intellij.mock.MockProject;
-import com.intellij.openapi.extensions.ExtensionPoint;
-import com.intellij.openapi.extensions.Extensions;
-import com.intellij.openapi.extensions.ExtensionsArea;
-import com.intellij.openapi.util.Disposer;
-import com.intellij.openapi.vfs.StandardFileSystems;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.openapi.vfs.VirtualFileManager;
-import com.intellij.openapi.vfs.local.CoreLocalFileSystem;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.PsiManager;
-import com.replaymod.gradle.remap.legacy.LegacyMapping;
-import org.cadixdev.lorenz.MappingSet;
-import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys;
-import org.jetbrains.kotlin.cli.common.config.KotlinSourceRoot;
-import org.jetbrains.kotlin.cli.common.messages.MessageRenderer;
-import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector;
-import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles;
-import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
-import org.jetbrains.kotlin.cli.jvm.compiler.NoScopeRecordCliBindingTrace;
-import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM;
-import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot;
-import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot;
-import org.jetbrains.kotlin.config.CommonConfigurationKeys;
-import org.jetbrains.kotlin.config.CompilerConfiguration;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-public class Transformer {
- private MappingSet map;
- private String[] classpath;
- private boolean fail;
-
- public static void main(String[] args) throws IOException {
- MappingSet mappings;
- if (args[0].isEmpty()) {
- mappings = MappingSet.create();
- } else {
- mappings = LegacyMapping.readMappingSet(new File(args[0]).toPath(), args[1].equals("true"));
- }
- Transformer transformer = new Transformer(mappings);
-
- BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
-
- String[] classpath = new String[Integer.parseInt(args[2])];
- for (int i = 0; i < classpath.length; i++) {
- classpath[i] = reader.readLine();
- }
- transformer.setClasspath(classpath);
-
- Map<String, String> sources = new HashMap<>();
- while (true) {
- String name = reader.readLine();
- if (name == null || name.isEmpty()) {
- break;
- }
-
- String[] lines = new String[Integer.parseInt(reader.readLine())];
- for (int i = 0; i < lines.length; i++) {
- lines[i] = reader.readLine();
- }
- String source = String.join("\n", lines);
-
- sources.put(name, source);
- }
-
- Map<String, String> results = transformer.remap(sources);
-
- for (String name : sources.keySet()) {
- System.out.println(name);
- String[] lines = results.get(name).split("\n");
- System.out.println(lines.length);
- for (String line : lines) {
- System.out.println(line);
- }
- }
-
- if (transformer.fail) {
- System.exit(1);
- }
- }
-
- public Transformer(MappingSet mappings) {
- this.map = mappings;
- }
-
- public String[] getClasspath() {
- return classpath;
- }
-
- public void setClasspath(String[] classpath) {
- this.classpath = classpath;
- }
-
- public Map<String, String> remap(Map<String, String> sources) throws IOException {
- Path tmpDir = Files.createTempDirectory("remap");
- try {
- for (Entry<String, String> entry : sources.entrySet()) {
- String unitName = entry.getKey();
- String source = entry.getValue();
-
- Path path = tmpDir.resolve(unitName);
- Files.createDirectories(path.getParent());
- Files.write(path, source.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE);
- }
-
- CompilerConfiguration config = new CompilerConfiguration();
- config.put(CommonConfigurationKeys.MODULE_NAME, "main");
- config.add(CLIConfigurationKeys.CONTENT_ROOTS, new JavaSourceRoot(tmpDir.toFile(), ""));
- config.add(CLIConfigurationKeys.CONTENT_ROOTS, new KotlinSourceRoot(tmpDir.toAbsolutePath().toString(), false));
- config.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, new PrintingMessageCollector(System.err, MessageRenderer.GRADLE_STYLE, true));
-
- KotlinCoreEnvironment environment = KotlinCoreEnvironment.createForProduction(
- Disposer.newDisposable(),
- config,
- EnvironmentConfigFiles.JVM_CONFIG_FILES
- );
- ExtensionsArea rootArea = Extensions.getRootArea();
- if (!rootArea.hasExtensionPoint(CustomExceptionHandler.KEY)) {
- rootArea.registerExtensionPoint(CustomExceptionHandler.KEY.getName(), CustomExceptionHandler.class.getName(), ExtensionPoint.Kind.INTERFACE);
- }
-
- MockProject project = (MockProject) environment.getProject();
-
- environment.updateClasspath(Stream.of(getClasspath()).map(it -> new JvmClasspathRoot(new File(it))).collect(Collectors.toList()));
-
- TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(
- project,
- Collections.emptyList(),
- new NoScopeRecordCliBindingTrace(),
- environment.getConfiguration(),
- environment::createPackagePartProvider
- );
-
- CoreLocalFileSystem vfs = (CoreLocalFileSystem) VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL);
- Map<String, String> results = new HashMap<>();
- for (String name : sources.keySet()) {
- VirtualFile file = vfs.findFileByIoFile(tmpDir.resolve(name).toFile());
- assert file != null;
- PsiFile psiFile = PsiManager.getInstance(project).findFile(file);
- assert psiFile != null;
-
- String mapped = new PsiMapper(map, psiFile).remapFile();
- if (mapped == null) {
- fail = true;
- continue;
- }
- results.put(name, mapped);
- }
- return results;
- } finally {
- Files.walk(tmpDir).map(Path::toFile).sorted(Comparator.reverseOrder()).forEach(File::delete);
- }
- }
-
-}
diff --git a/src/main/java/com/replaymod/gradle/remap/legacy/LegacyMapping.java b/src/main/java/com/replaymod/gradle/remap/legacy/LegacyMapping.java
deleted file mode 100644
index 05a85d9..0000000
--- a/src/main/java/com/replaymod/gradle/remap/legacy/LegacyMapping.java
+++ /dev/null
@@ -1,118 +0,0 @@
-package com.replaymod.gradle.remap.legacy;
-
-import org.cadixdev.lorenz.MappingSet;
-
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-public class LegacyMapping {
- public static MappingSet readMappingSet(Path mappingFile, boolean invert) throws IOException {
- return new LegacyMappingsReader(readMappings(mappingFile, invert)).read();
- }
-
- public static Map<String, LegacyMapping> readMappings(Path mappingFile, boolean invert) throws IOException {
- Map<String, LegacyMapping> mappings = new HashMap<>();
- Map<String, LegacyMapping> revMappings = new HashMap<>();
- int lineNumber = 0;
- for (String line : Files.readAllLines(mappingFile, StandardCharsets.UTF_8)) {
- lineNumber++;
- if (line.trim().startsWith("#") || line.trim().isEmpty()) continue;
-
- String[] parts = line.split(" ");
- if (parts.length < 2 || line.contains(";")) {
- throw new IllegalArgumentException("Failed to parse line " + lineNumber + " in " + mappingFile + ".");
- }
-
- LegacyMapping mapping = mappings.get(parts[0]);
- if (mapping == null) {
- mapping = new LegacyMapping();
- mapping.oldName = mapping.newName = parts[0];
- mappings.put(mapping.oldName, mapping);
- }
-
- if (parts.length == 2) {
- // Class mapping
- mapping.newName = parts[1];
- // Possibly merge with reverse mapping
- LegacyMapping revMapping = revMappings.remove(mapping.newName);
- if (revMapping != null) {
- mapping.fields.putAll(revMapping.fields);
- mapping.methods.putAll(revMapping.methods);
- }
- revMappings.put(mapping.newName, mapping);
- } else if (parts.length == 3 || parts.length == 4) {
- String fromName = parts[1];
- String toName;
- LegacyMapping revMapping;
- if (parts.length == 4) {
- toName = parts[3];
- revMapping = revMappings.get(parts[2]);
- if (revMapping == null) {
- revMapping = new LegacyMapping();
- revMapping.oldName = revMapping.newName = parts[2];
- revMappings.put(revMapping.newName, revMapping);
- }
- } else {
- toName = parts[2];
- revMapping = mapping;
- }
- if (fromName.endsWith("()")) {
- // Method mapping
- fromName = fromName.substring(0, fromName.length() - 2);
- toName = toName.substring(0, toName.length() - 2);
- mapping.methods.put(fromName, toName);
- revMapping.methods.put(fromName, toName);
- } else {
- // Field mapping
- mapping.fields.put(fromName, toName);
- revMapping.fields.put(fromName, toName);
- }
- } else {
- throw new IllegalArgumentException("Failed to parse line " + lineNumber + " in " + mappingFile + ".");
- }
- }
- if (invert) {
- Stream.concat(
- mappings.values().stream(),
- revMappings.values().stream()
- ).distinct().forEach(it -> {
- String oldName = it.oldName;
- it.oldName = it.newName;
- it.newName = oldName;
- it.fields = it.fields.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
- it.methods = it.methods.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
- });
- }
- return Stream.concat(
- mappings.values().stream(),
- revMappings.values().stream()
- ).collect(Collectors.toMap(mapping -> mapping.oldName, Function.identity(), (mapping, other) -> {
- if (!other.oldName.equals(other.newName)) {
- if (!mapping.oldName.equals(mapping.newName)
- && !other.oldName.equals(mapping.oldName)
- && !other.newName.equals(mapping.newName)) {
- throw new IllegalArgumentException("Conflicting mappings: "
- + mapping.oldName + " -> " + mapping.newName
- + " and " + other.oldName + " -> " + other.newName);
- }
- mapping.oldName = other.oldName;
- mapping.newName = other.newName;
- }
- mapping.fields.putAll(other.fields);
- mapping.methods.putAll(other.methods);
- return mapping;
- }));
- }
-
- public String oldName;
- public String newName;
- public Map<String, String> fields = new HashMap<>();
- public Map<String, String> methods = new HashMap<>();
-}
diff --git a/src/main/java/com/replaymod/gradle/remap/legacy/LegacyMappingSetModelFactory.java b/src/main/java/com/replaymod/gradle/remap/legacy/LegacyMappingSetModelFactory.java
deleted file mode 100644
index eb72788..0000000
--- a/src/main/java/com/replaymod/gradle/remap/legacy/LegacyMappingSetModelFactory.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.replaymod.gradle.remap.legacy;
-
-import org.cadixdev.bombe.type.signature.MethodSignature;
-import org.cadixdev.lorenz.MappingSet;
-import org.cadixdev.lorenz.impl.MappingSetModelFactoryImpl;
-import org.cadixdev.lorenz.impl.model.TopLevelClassMappingImpl;
-import org.cadixdev.lorenz.model.MethodMapping;
-import org.cadixdev.lorenz.model.TopLevelClassMapping;
-
-import java.util.Optional;
-
-public class LegacyMappingSetModelFactory extends MappingSetModelFactoryImpl {
- @Override
- public TopLevelClassMapping createTopLevelClassMapping(MappingSet parent, String obfuscatedName, String deobfuscatedName) {
- return new TopLevelClassMappingImpl(parent, obfuscatedName, deobfuscatedName) {
- private MethodSignature stripDesc(MethodSignature signature) {
- // actual descriptor isn't included in legacy format
- return MethodSignature.of(signature.getName(), "()V");
- }
-
- @Override
- public boolean hasMethodMapping(MethodSignature signature) {
- return super.hasMethodMapping(signature) || super.hasMethodMapping(stripDesc(signature));
- }
-
- @Override
- public Optional<MethodMapping> getMethodMapping(MethodSignature signature) {
- Optional<MethodMapping> maybeMapping = super.getMethodMapping(signature);
- if (!maybeMapping.isPresent() || !maybeMapping.get().hasMappings()) {
- maybeMapping = super.getMethodMapping(stripDesc(signature));
- }
- return maybeMapping;
- }
- };
- }
-}
diff --git a/src/main/java/com/replaymod/gradle/remap/legacy/LegacyMappingsReader.java b/src/main/java/com/replaymod/gradle/remap/legacy/LegacyMappingsReader.java
deleted file mode 100644
index 502f7ba..0000000
--- a/src/main/java/com/replaymod/gradle/remap/legacy/LegacyMappingsReader.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package com.replaymod.gradle.remap.legacy;
-
-import org.cadixdev.lorenz.MappingSet;
-import org.cadixdev.lorenz.io.MappingsReader;
-import org.cadixdev.lorenz.model.ClassMapping;
-
-import java.util.Map;
-
-public class LegacyMappingsReader extends MappingsReader {
- private final Map<String, LegacyMapping> map;
-
- public LegacyMappingsReader(Map<String, LegacyMapping> map) {
- this.map = map;
- }
-
- @Override
- public MappingSet read() {
- return read(MappingSet.create(new LegacyMappingSetModelFactory()));
- }
-
- @Override
- public MappingSet read(MappingSet mappings) {
- if (!(mappings.getModelFactory() instanceof LegacyMappingSetModelFactory)) {
- throw new IllegalArgumentException("legacy mappings must use legacy model factory, use read() instead");
- }
- for (LegacyMapping legacyMapping : map.values()) {
- ClassMapping classMapping = mappings.getOrCreateClassMapping(legacyMapping.oldName)
- .setDeobfuscatedName(legacyMapping.newName);
- for (Map.Entry<String, String> entry : legacyMapping.fields.entrySet()) {
- classMapping.getOrCreateFieldMapping(entry.getKey())
- .setDeobfuscatedName(entry.getValue());
- }
- for (Map.Entry<String, String> entry : legacyMapping.methods.entrySet()) {
- classMapping.getOrCreateMethodMapping(entry.getKey(), "()V")
- .setDeobfuscatedName(entry.getValue());
- }
- }
- return mappings;
- }
-
- @Override
- public void close() {
- }
-}
diff --git a/src/main/kotlin/com/replaymod/gradle/remap/LorenzExtensions.kt b/src/main/kotlin/com/replaymod/gradle/remap/LorenzExtensions.kt
new file mode 100644
index 0000000..cd04918
--- /dev/null
+++ b/src/main/kotlin/com/replaymod/gradle/remap/LorenzExtensions.kt
@@ -0,0 +1,11 @@
+package com.replaymod.gradle.remap
+
+import org.cadixdev.bombe.type.signature.MethodSignature
+import org.cadixdev.lorenz.MappingSet
+import org.cadixdev.lorenz.model.ClassMapping
+import org.cadixdev.lorenz.model.FieldMapping
+import org.cadixdev.lorenz.model.MethodMapping
+
+fun MappingSet.findClassMapping(obfuscatedName: String): ClassMapping<*, *>? = getClassMapping(obfuscatedName).orElse(null)
+fun ClassMapping<*, *>.findFieldMapping(obfuscatedName: String): FieldMapping? = getFieldMapping(obfuscatedName).orElse(null)
+fun ClassMapping<*, *>.findMethodMapping(signature: MethodSignature): MethodMapping? = getMethodMapping(signature).orElse(null)
diff --git a/src/main/kotlin/com/replaymod/gradle/remap/PsiMapper.kt b/src/main/kotlin/com/replaymod/gradle/remap/PsiMapper.kt
new file mode 100644
index 0000000..95ee858
--- /dev/null
+++ b/src/main/kotlin/com/replaymod/gradle/remap/PsiMapper.kt
@@ -0,0 +1,365 @@
+package com.replaymod.gradle.remap
+
+import com.intellij.openapi.util.TextRange
+import com.intellij.openapi.util.text.StringUtil
+import com.intellij.psi.*
+import com.replaymod.gradle.remap.PsiUtils.getSignature
+import org.cadixdev.bombe.type.signature.MethodSignature
+import org.cadixdev.lorenz.MappingSet
+import org.cadixdev.lorenz.model.ClassMapping
+import java.util.*
+
+internal class PsiMapper(private val map: MappingSet, private val file: PsiFile) {
+ private val mixinMappings = mutableMapOf<String, ClassMapping<*, *>>()
+ private var error: Boolean = false
+ private val changes = TreeMap<TextRange, String>(Comparator.comparing<TextRange, Int> { it.startOffset })
+
+ private fun error(at: PsiElement, message: String) {
+ val line = StringUtil.offsetToLineNumber(file.text, at.textOffset)
+ System.err.println(file.name + ":" + line + ": " + message)
+ error = true
+ }
+
+ private fun replace(e: PsiElement, with: String) {
+ changes[e.textRange] = with
+ }
+
+ private fun replaceIdentifier(parent: PsiElement, with: String) {
+ for (child in parent.children) {
+ if (child is PsiIdentifier) {
+ replace(child, with)
+ return
+ }
+ }
+ }
+
+ private fun valid(e: PsiElement): Boolean {
+ val range = e.textRange
+ val before = changes.ceilingKey(range)
+ return before == null || !before.intersects(range)
+ }
+
+ private fun getResult(text: String): String? {
+ if (error) {
+ return null
+ }
+ var result = text
+ for ((key, value) in changes.descendingMap()) {
+ result = key.replace(result, value)
+ }
+ return result
+ }
+
+ private fun map(expr: PsiElement, field: PsiField) {
+ val fieldName = field.name ?: return
+ val declaringClass = field.containingClass ?: return
+ val name = declaringClass.qualifiedName ?: return
+ var mapping: ClassMapping<*, *>? = this.mixinMappings[name]
+ if (mapping == null) {
+ mapping = map.findClassMapping(name)
+ }
+ if (mapping == null) return
+ val mapped = mapping.findFieldMapping(fieldName)?.deobfuscatedName
+ if (mapped == null || mapped == fieldName) return
+ replaceIdentifier(expr, mapped)
+
+ if (expr is PsiJavaCodeReferenceElement
+ && !expr.isQualified // qualified access is fine
+ && !isSwitchCase(expr) // referencing constants in case statements is fine
+ ) {
+ error(expr, "Implicit member reference to remapped field \"$fieldName\". " +
+ "This can cause issues if the remapped reference becomes shadowed by a local variable and is therefore forbidden. " +
+ "Use \"this.$fieldName\" instead.")
+ }
+ }
+
+ private fun map(expr: PsiElement, method: PsiMethod) {
+ if (method.isConstructor) return
+
+ var declaringClass: PsiClass? = method.containingClass ?: return
+ val parentQueue = ArrayDeque<PsiClass>()
+ parentQueue.offer(declaringClass)
+ var mapping: ClassMapping<*, *>? = null
+
+ var name = declaringClass!!.qualifiedName
+ if (name != null) {
+ mapping = mixinMappings[name]
+ }
+ while (true) {
+ if (mapping != null) {
+ val mapped = mapping.findMethodMapping(getSignature(method))?.deobfuscatedName
+ if (mapped != null) {
+ if (mapped != method.name) {
+ replaceIdentifier(expr, mapped)
+ }
+ return
+ }
+ mapping = null
+ }
+ while (mapping == null) {
+ declaringClass = parentQueue.poll()
+ if (declaringClass == null) return
+
+ val superClass = declaringClass.superClass
+ if (superClass != null) {
+ parentQueue.offer(superClass)
+ }
+ for (anInterface in declaringClass.interfaces) {
+ parentQueue.offer(anInterface)
+ }
+
+ name = declaringClass.qualifiedName
+ if (name == null) continue
+ mapping = map.findClassMapping(name)
+ }
+ }
+ }
+
+ private fun map(expr: PsiElement, resolved: PsiQualifiedNamedElement) {
+ val name = resolved.qualifiedName ?: return
+ val mapping = map.findClassMapping(name) ?: return
+ var mapped = mapping.deobfuscatedName
+ if (mapped == name) return
+ mapped = mapped.replace('/', '.')
+
+ if (expr.text == name) {
+ replace(expr, mapped)
+ return
+ }
+ replaceIdentifier(expr, mapped.substring(mapped.lastIndexOf('.') + 1))
+ }
+
+ private fun map(expr: PsiElement, resolved: PsiElement?) {
+ when (resolved) {
+ is PsiField -> map(expr, resolved)
+ is PsiMethod -> map(expr, resolved)
+ is PsiClass, is PsiPackage -> map(expr, resolved as PsiQualifiedNamedElement)
+ }
+ }
+
+ // Note: Supports only Mixins with a single target (ignores others) and only ones specified via class literals
+ private fun getMixinTarget(annotation: PsiAnnotation): PsiClass? {
+ for (pair in annotation.parameterList.attributes) {
+ val name = pair.name
+ if (name != null && "value" != name) continue
+ val value = pair.value
+ if (value !is PsiClassObjectAccessExpression) continue
+ val type = value.operand
+ val reference = type.innermostComponentReferenceElement ?: continue
+ return reference.resolve() as PsiClass?
+ }
+ return null
+ }
+
+ private fun remapAccessors(mapping: ClassMapping<*, *>) {
+ file.accept(object : JavaRecursiveElementVisitor() {
+ override fun visitMethod(method: PsiMethod) {
+ val annotation = method.getAnnotation(CLASS_ACCESSOR) ?: return
+
+ val methodName = method.name
+ val targetByName = when {
+ methodName.startsWith("is") -> methodName.substring(2)
+ methodName.startsWith("get") || methodName.startsWith("set") -> methodName.substring(3)
+ else -> null
+ }?.decapitalize()
+
+ val target = annotation.parameterList.attributes.find {
+ it.name == null || it.name == "value"
+ }?.literalValue ?: targetByName ?: throw IllegalArgumentException("Cannot determine accessor target for $method")
+
+ val mapped = mapping.findFieldMapping(target)?.deobfuscatedName
+ if (mapped != null && mapped != target) {
+ // Update accessor target
+ replace(annotation.parameterList, if (mapped == targetByName) {
+ // Mapped name matches implied target, can just remove the explict target
+ ""
+ } else {
+ // Mapped name does not match implied target, need to set the target as annotation value
+ "(\"" + StringUtil.escapeStringCharacters(mapped) + "\")"
+ })
+ }
+ }
+ })
+ }
+
+ private fun remapInjectsAndRedirects(mapping: ClassMapping<*, *>) {
+ file.accept(object : JavaRecursiveElementVisitor() {
+ override fun visitMethod(method: PsiMethod) {
+ val annotation = method.getAnnotation(CLASS_INJECT) ?: method.getAnnotation(CLASS_REDIRECT) ?: return
+
+ for (attribute in annotation.parameterList.attributes) {
+ if ("method" != attribute.name) continue
+ // Note: mixin supports multiple targets, we do not (yet)
+ val literalValue = attribute.literalValue ?: continue
+ var mapped: String?
+ if (literalValue.contains("(")) {
+ mapped = mapping.findMethodMapping(MethodSignature.of(literalValue))?.deobfuscatedName
+ } else {
+ mapped = null
+ for (methodMapping in mapping.methodMappings) {
+ if (methodMapping.obfuscatedName == literalValue) {
+ val name = methodMapping.deobfuscatedName
+ if (mapped != null && mapped != name) {
+ error(attribute, "Ambiguous mixin method \"$literalValue\" maps to \"$mapped\" and \"$name\"")
+ }
+ mapped = name
+ }
+ }
+ }
+ if (mapped != null && mapped != literalValue) {
+ val value = attribute.value!!
+ replace(value, '"'.toString() + mapped + '"'.toString())
+ }
+ }
+ }
+ })
+ }
+
+ private fun remapInternalType(internalType: String, result: StringBuilder): ClassMapping<*, *>? {
+ if (internalType[0] == 'L') {
+ val type = internalType.substring(1, internalType.length - 1).replace('/', '.')
+ val mapping = map.findClassMapping(type)
+ if (mapping != null) {
+ result.append('L').append(mapping.fullDeobfuscatedName).append(';')
+ return mapping
+ }
+ }
+ result.append(internalType)
+ return null
+ }
+
+ private fun remapFullyQualifiedMethodOrField(signature: String): String {
+ val ownerEnd = signature.indexOf(';')
+ var argsBegin = signature.indexOf('(')
+ var argsEnd = signature.indexOf(')')
+ val method = argsBegin != -1
+ if (!method) {
+ argsEnd = signature.indexOf(':')
+ argsBegin = argsEnd
+ }
+ val owner = signature.substring(0, ownerEnd + 1)
+ val name = signature.substring(ownerEnd + 1, argsBegin)
+ val returnType = signature.substring(argsEnd + 1)
+
+ val builder = StringBuilder(signature.length + 32)
+ val mapping = remapInternalType(owner, builder)
+ var mapped: String? = null
+ if (mapping != null) {
+ mapped = (if (method) {
+ mapping.findMethodMapping(MethodSignature.of(signature.substring(ownerEnd + 1)))
+ } else {
+ mapping.findFieldMapping(name)
+ })?.deobfuscatedName
+ }
+ builder.append(mapped ?: name)
+ if (method) {
+ builder.append('(')
+ val args = signature.substring(argsBegin + 1, argsEnd)
+ var i = 0
+ while (i < args.length) {
+ val c = args[i]
+ if (c != 'L') {
+ builder.append(c)
+ i++
+ continue
+ }
+ val end = args.indexOf(';', i)
+ val arg = args.substring(i, end + 1)
+ remapInternalType(arg, builder)
+ i = end
+ i++
+ }
+ builder.append(')')
+ } else {
+ builder.append(':')
+ }
+ remapInternalType(returnType, builder)
+ return builder.toString()
+ }
+
+ private fun remapAtTargets() {
+ file.accept(object : JavaRecursiveElementVisitor() {
+ override fun visitAnnotation(annotation: PsiAnnotation) {
+ if (CLASS_AT != annotation.qualifiedName) {
+ super.visitAnnotation(annotation)
+ return
+ }
+
+ for (attribute in annotation.parameterList.attributes) {
+ if ("target" != attribute.name) continue
+ val signature = attribute.literalValue ?: continue
+ val newSignature = remapFullyQualifiedMethodOrField(signature)
+ if (newSignature != signature) {
+ val value = attribute.value!!
+ replace(value, "\"$newSignature\"")
+ }
+ }
+ }
+ })
+ }
+
+ fun remapFile(): String? {
+ file.accept(object : JavaRecursiveElementVisitor() {
+ override fun visitClass(psiClass: PsiClass) {
+ val annotation = psiClass.getAnnotation(CLASS_MIXIN) ?: return
+
+ remapAtTargets()
+
+ val target = getMixinTarget(annotation) ?: return
+ val qualifiedName = target.qualifiedName ?: return
+
+ val mapping = map.findClassMapping(qualifiedName) ?: return
+
+ mixinMappings[psiClass.qualifiedName!!] = mapping
+
+ if (!mapping.fieldMappings.isEmpty()) {
+ remapAccessors(mapping)
+ }
+ if (!mapping.methodMappings.isEmpty()) {
+ remapInjectsAndRedirects(mapping)
+ }
+ }
+ })
+
+ file.accept(object : JavaRecursiveElementVisitor() {
+ override fun visitField(field: PsiField) {
+ if (valid(field)) {
+ map(field, field)
+ }
+ super.visitField(field)
+ }
+
+ override fun visitMethod(method: PsiMethod) {
+ if (valid(method)) {
+ map(method, method)
+ }
+ super.visitMethod(method)
+ }
+
+ override fun visitReferenceElement(reference: PsiJavaCodeReferenceElement) {
+ if (valid(reference)) {
+ map(reference, reference.resolve())
+ }
+ super.visitReferenceElement(reference)
+ }
+ })
+
+ return getResult(file.text)
+ }
+
+ companion object {
+ private const val CLASS_MIXIN = "org.spongepowered.asm.mixin.Mixin"
+ private const val CLASS_ACCESSOR = "org.spongepowered.asm.mixin.gen.Accessor"
+ private const val CLASS_AT = "org.spongepowered.asm.mixin.injection.At"
+ private const val CLASS_INJECT = "org.spongepowered.asm.mixin.injection.Inject"
+ private const val CLASS_REDIRECT = "org.spongepowered.asm.mixin.injection.Redirect"
+
+ private fun isSwitchCase(e: PsiElement): Boolean {
+ if (e is PsiSwitchLabelStatement) {
+ return true
+ }
+ val parent = e.parent
+ return parent != null && isSwitchCase(parent)
+ }
+ }
+}
diff --git a/src/main/kotlin/com/replaymod/gradle/remap/PsiUtils.kt b/src/main/kotlin/com/replaymod/gradle/remap/PsiUtils.kt
new file mode 100644
index 0000000..de29956
--- /dev/null
+++ b/src/main/kotlin/com/replaymod/gradle/remap/PsiUtils.kt
@@ -0,0 +1,41 @@
+package com.replaymod.gradle.remap
+
+import com.intellij.psi.*
+import com.intellij.psi.util.TypeConversionUtil
+import org.cadixdev.bombe.type.ArrayType
+import org.cadixdev.bombe.type.FieldType
+import org.cadixdev.bombe.type.MethodDescriptor
+import org.cadixdev.bombe.type.ObjectType
+import org.cadixdev.bombe.type.Type
+import org.cadixdev.bombe.type.VoidType
+import org.cadixdev.bombe.type.signature.MethodSignature
+
+internal object PsiUtils {
+ fun getSignature(method: PsiMethod): MethodSignature = MethodSignature(method.name, getDescriptor(method))
+
+ private fun getDescriptor(method: PsiMethod): MethodDescriptor = MethodDescriptor(
+ method.parameterList.parameters.map { getFieldType(it.type) },
+ getType(method.returnType)
+ )
+
+ private fun getFieldType(type: PsiType?): FieldType = when (val erasedType = TypeConversionUtil.erasure(type)) {
+ is PsiPrimitiveType -> FieldType.of(erasedType.kind.binaryName)
+ is PsiArrayType -> {
+ val array = erasedType as PsiArrayType?
+ ArrayType(array!!.arrayDimensions, getFieldType(array.deepComponentType))
+ }
+ is PsiClassType -> {
+ val resolved = erasedType.resolve() ?: throw NullPointerException("Failed to resolve type $erasedType")
+ val qualifiedName = resolved.qualifiedName
+ ?: throw NullPointerException("Type $erasedType has no qualified name.")
+ ObjectType(qualifiedName)
+ }
+ else -> throw IllegalArgumentException("Cannot translate type " + erasedType!!)
+ }
+
+ private fun getType(type: PsiType?): Type = if (TypeConversionUtil.isVoidType(type)) {
+ VoidType.INSTANCE
+ } else {
+ getFieldType(type)
+ }
+}
diff --git a/src/main/kotlin/com/replaymod/gradle/remap/Transformer.kt b/src/main/kotlin/com/replaymod/gradle/remap/Transformer.kt
new file mode 100644
index 0000000..303ee42
--- /dev/null
+++ b/src/main/kotlin/com/replaymod/gradle/remap/Transformer.kt
@@ -0,0 +1,149 @@
+package com.replaymod.gradle.remap
+
+import com.intellij.codeInsight.CustomExceptionHandler
+import com.intellij.mock.MockProject
+import com.intellij.openapi.extensions.ExtensionPoint
+import com.intellij.openapi.extensions.Extensions
+import com.intellij.openapi.util.Disposer
+import com.intellij.openapi.vfs.StandardFileSystems
+import com.intellij.openapi.vfs.VirtualFileManager
+import com.intellij.openapi.vfs.local.CoreLocalFileSystem
+import com.intellij.psi.PsiManager
+import com.intellij.psi.search.GlobalSearchScope
+import com.replaymod.gradle.remap.legacy.LegacyMapping
+import org.cadixdev.lorenz.MappingSet
+import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
+import org.jetbrains.kotlin.cli.common.config.ContentRoot
+import org.jetbrains.kotlin.cli.common.config.KotlinSourceRoot
+import org.jetbrains.kotlin.cli.common.messages.MessageCollector
+import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
+import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
+import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
+import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
+import org.jetbrains.kotlin.cli.jvm.compiler.NoScopeRecordCliBindingTrace
+import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM
+import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot
+import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
+import org.jetbrains.kotlin.config.CommonConfigurationKeys
+import org.jetbrains.kotlin.config.CompilerConfiguration
+import java.io.BufferedReader
+import java.io.File
+import java.io.IOException
+import java.io.InputStreamReader
+import java.nio.charset.StandardCharsets
+import java.nio.file.Files
+import java.nio.file.StandardOpenOption
+import java.util.*
+import kotlin.system.exitProcess
+
+class Transformer(private val map: MappingSet) {
+ var classpath: Array<String>? = null
+ private var fail: Boolean = false
+
+ @Throws(IOException::class)
+ fun remap(sources: Map<String, String>): Map<String, String> {
+ val tmpDir = Files.createTempDirectory("remap")
+ try {
+ for ((unitName, source) in sources) {
+ val path = tmpDir.resolve(unitName)
+ Files.createDirectories(path.parent)
+ Files.write(path, source.toByteArray(StandardCharsets.UTF_8), StandardOpenOption.CREATE)
+ }
+
+ val config = CompilerConfiguration()
+ config.put(CommonConfigurationKeys.MODULE_NAME, "main")
+ config.add<ContentRoot>(CLIConfigurationKeys.CONTENT_ROOTS, JavaSourceRoot(tmpDir.toFile(), ""))
+ config.add<ContentRoot>(CLIConfigurationKeys.CONTENT_ROOTS, KotlinSourceRoot(tmpDir.toAbsolutePath().toString(), false))
+ config.put<MessageCollector>(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, PrintingMessageCollector(System.err, MessageRenderer.GRADLE_STYLE, true))
+
+ val environment = KotlinCoreEnvironment.createForProduction(
+ Disposer.newDisposable(),
+ config,
+ EnvironmentConfigFiles.JVM_CONFIG_FILES
+ )
+ val rootArea = Extensions.getRootArea()
+ if (!rootArea.hasExtensionPoint(CustomExceptionHandler.KEY)) {
+ rootArea.registerExtensionPoint(CustomExceptionHandler.KEY.name, CustomExceptionHandler::class.java.name, ExtensionPoint.Kind.INTERFACE)
+ }
+
+ val project = environment.project as MockProject
+
+ environment.updateClasspath(classpath!!.map { JvmClasspathRoot(File(it)) })
+
+ TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(
+ project,
+ emptyList(),
+ NoScopeRecordCliBindingTrace(),
+ environment.configuration,
+ { scope: GlobalSearchScope -> environment.createPackagePartProvider(scope) }
+ )
+
+ val vfs = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL) as CoreLocalFileSystem
+ val results = HashMap<String, String>()
+ for (name in sources.keys) {
+ val file = vfs.findFileByIoFile(tmpDir.resolve(name).toFile())!!
+ val psiFile = PsiManager.getInstance(project).findFile(file)!!
+
+ val mapped = PsiMapper(map, psiFile).remapFile()
+ if (mapped == null) {
+ fail = true
+ continue
+ }
+ results[name] = mapped
+ }
+ return results
+ } finally {
+ Files.walk(tmpDir).map<File> { it.toFile() }.sorted(Comparator.reverseOrder()).forEach { it.delete() }
+ }
+ }
+
+ companion object {
+
+ @Throws(IOException::class)
+ @JvmStatic
+ fun main(args: Array<String>) {
+ val mappings: MappingSet = if (args[0].isEmpty()) {
+ MappingSet.create()
+ } else {
+ LegacyMapping.readMappingSet(File(args[0]).toPath(), args[1] == "true")
+ }
+ val transformer = Transformer(mappings)
+
+ val reader = BufferedReader(InputStreamReader(System.`in`))
+
+ transformer.classpath = (1..Integer.parseInt(args[2])).map { reader.readLine() }.toTypedArray()
+
+ val sources = mutableMapOf<String, String>()
+ while (true) {
+ val name = reader.readLine()
+ if (name == null || name.isEmpty()) {
+ break
+ }
+
+ val lines = arrayOfNulls<String>(Integer.parseInt(reader.readLine()))
+ for (i in lines.indices) {
+ lines[i] = reader.readLine()
+ }
+ val source = lines.joinToString("\n")
+
+ sources[name] = source
+ }
+
+ val results = transformer.remap(sources)
+
+ for (name in sources.keys) {
+ println(name)
+ val lines = results.getValue(name).split("\n").dropLastWhile { it.isEmpty() }.toTypedArray()
+ println(lines.size)
+ for (line in lines) {
+ println(line)
+ }
+ }
+
+ if (transformer.fail) {
+ exitProcess(1)
+ }
+ }
+ }
+
+}
diff --git a/src/main/kotlin/com/replaymod/gradle/remap/legacy/LegacyMapping.kt b/src/main/kotlin/com/replaymod/gradle/remap/legacy/LegacyMapping.kt
new file mode 100644
index 0000000..389b1c9
--- /dev/null
+++ b/src/main/kotlin/com/replaymod/gradle/remap/legacy/LegacyMapping.kt
@@ -0,0 +1,109 @@
+package com.replaymod.gradle.remap.legacy
+
+import org.cadixdev.lorenz.MappingSet
+
+import java.io.IOException
+import java.nio.charset.StandardCharsets
+import java.nio.file.Files
+import java.nio.file.Path
+import java.util.HashMap
+
+class LegacyMapping(var oldName: String, var newName: String) {
+ var fields: MutableMap<String, String> = mutableMapOf()
+ var methods: MutableMap<String, String> = mutableMapOf()
+
+ companion object {
+ @Throws(IOException::class)
+ fun readMappingSet(mappingFile: Path, invert: Boolean): MappingSet {
+ return LegacyMappingsReader(readMappings(mappingFile, invert)).read()
+ }
+
+ @Throws(IOException::class)
+ fun readMappings(mappingFile: Path, invert: Boolean): Map<String, LegacyMapping> {
+ val mappings = HashMap<String, LegacyMapping>()
+ val revMappings = HashMap<String, LegacyMapping>()
+ var lineNumber = 0
+ for (line in Files.readAllLines(mappingFile, StandardCharsets.UTF_8)) {
+ lineNumber++
+ if (line.trim { it <= ' ' }.startsWith("#") || line.trim { it <= ' ' }.isEmpty()) continue
+
+ val parts = line.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
+ require(!(parts.size < 2 || line.contains(";"))) { "Failed to parse line $lineNumber in $mappingFile." }
+
+ var mapping: LegacyMapping? = mappings[parts[0]]
+ if (mapping == null) {
+ mapping = LegacyMapping(parts[0], parts[0])
+ mappings[mapping.oldName] = mapping
+ }
+
+ if (parts.size == 2) {
+ // Class mapping
+ mapping.newName = parts[1]
+ // Possibly merge with reverse mapping
+ val revMapping = revMappings.remove(mapping.newName)
+ if (revMapping != null) {
+ mapping.fields.putAll(revMapping.fields)
+ mapping.methods.putAll(revMapping.methods)
+ }
+ revMappings[mapping.newName] = mapping
+ } else if (parts.size == 3 || parts.size == 4) {
+ var fromName = parts[1]
+ var toName: String
+ var revMapping: LegacyMapping?
+ if (parts.size == 4) {
+ toName = parts[3]
+ revMapping = revMappings[parts[2]]
+ if (revMapping == null) {
+ revMapping = LegacyMapping(parts[2], parts[2])
+ revMappings[revMapping.newName] = revMapping
+ }
+ } else {
+ toName = parts[2]
+ revMapping = mapping
+ }
+ if (fromName.endsWith("()")) {
+ // Method mapping
+ fromName = fromName.substring(0, fromName.length - 2)
+ toName = toName.substring(0, toName.length - 2)
+ mapping.methods[fromName] = toName
+ revMapping.methods[fromName] = toName
+ } else {
+ // Field mapping
+ mapping.fields[fromName] = toName
+ revMapping.fields[fromName] = toName
+ }
+ } else {
+ throw IllegalArgumentException("Failed to parse line $lineNumber in $mappingFile.")
+ }
+ }
+ if (invert) {
+
+ (mappings.values + revMappings.values).distinct().forEach { mapping ->
+ mapping.oldName = mapping.newName.also { mapping.newName = mapping.oldName }
+ mapping.fields = mapping.fields.map { it.value to it.key }.toMap(mutableMapOf())
+ mapping.methods = mapping.methods.map { it.value to it.key }.toMap(mutableMapOf())
+ }
+ }
+ val result = mutableMapOf<String, LegacyMapping>()
+ for (mapping in (mappings.values + revMappings.values)) {
+ val key = mapping.oldName
+ val other = result[key]
+ result[key] = if (other != null) {
+ if (other.oldName != other.newName) {
+ require(mapping.oldName == mapping.newName || other.oldName == mapping.oldName || other.newName == mapping.newName) {
+ "Conflicting mappings: ${mapping.oldName} -> ${mapping.newName} and ${other.oldName} -> ${other.newName}"
+ }
+ mapping.oldName = other.oldName
+ mapping.newName = other.newName
+ }
+ mapping.fields.putAll(other.fields)
+ mapping.methods.putAll(other.methods)
+ mapping
+ } else {
+ mapping
+ }
+ }
+ return result
+ }
+ }
+}
diff --git a/src/main/kotlin/com/replaymod/gradle/remap/legacy/LegacyMappingSetModelFactory.kt b/src/main/kotlin/com/replaymod/gradle/remap/legacy/LegacyMappingSetModelFactory.kt
new file mode 100644
index 0000000..7737200
--- /dev/null
+++ b/src/main/kotlin/com/replaymod/gradle/remap/legacy/LegacyMappingSetModelFactory.kt
@@ -0,0 +1,33 @@
+package com.replaymod.gradle.remap.legacy
+
+import org.cadixdev.bombe.type.signature.MethodSignature
+import org.cadixdev.lorenz.MappingSet
+import org.cadixdev.lorenz.impl.MappingSetModelFactoryImpl
+import org.cadixdev.lorenz.impl.model.TopLevelClassMappingImpl
+import org.cadixdev.lorenz.model.MethodMapping
+import org.cadixdev.lorenz.model.TopLevelClassMapping
+
+import java.util.Optional
+
+class LegacyMappingSetModelFactory : MappingSetModelFactoryImpl() {
+ override fun createTopLevelClassMapping(parent: MappingSet, obfuscatedName: String, deobfuscatedName: String): TopLevelClassMapping {
+ return object : TopLevelClassMappingImpl(parent, obfuscatedName, deobfuscatedName) {
+ private fun stripDesc(signature: MethodSignature): MethodSignature {
+ // actual descriptor isn't included in legacy format
+ return MethodSignature.of(signature.name, "()V")
+ }
+
+ override fun hasMethodMapping(signature: MethodSignature): Boolean {
+ return super.hasMethodMapping(signature) || super.hasMethodMapping(stripDesc(signature))
+ }
+
+ override fun getMethodMapping(signature: MethodSignature): Optional<MethodMapping> {
+ var maybeMapping = super.getMethodMapping(signature)
+ if (!maybeMapping.isPresent || !maybeMapping.get().hasMappings()) {
+ maybeMapping = super.getMethodMapping(stripDesc(signature))
+ }
+ return maybeMapping
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/com/replaymod/gradle/remap/legacy/LegacyMappingsReader.kt b/src/main/kotlin/com/replaymod/gradle/remap/legacy/LegacyMappingsReader.kt
new file mode 100644
index 0000000..8a6144e
--- /dev/null
+++ b/src/main/kotlin/com/replaymod/gradle/remap/legacy/LegacyMappingsReader.kt
@@ -0,0 +1,28 @@
+package com.replaymod.gradle.remap.legacy
+
+import org.cadixdev.lorenz.MappingSet
+import org.cadixdev.lorenz.io.MappingsReader
+
+class LegacyMappingsReader(private val map: Map<String, LegacyMapping>) : MappingsReader() {
+
+ override fun read(): MappingSet {
+ return read(MappingSet.create(LegacyMappingSetModelFactory()))
+ }
+
+ override fun read(mappings: MappingSet): MappingSet {
+ require(mappings.modelFactory is LegacyMappingSetModelFactory) { "legacy mappings must use legacy model factory, use read() instead" }
+ for (legacyMapping in map.values) {
+ val classMapping = mappings.getOrCreateClassMapping(legacyMapping.oldName)
+ .setDeobfuscatedName(legacyMapping.newName)
+ for ((key, value) in legacyMapping.fields) {
+ classMapping.getOrCreateFieldMapping(key).deobfuscatedName = value
+ }
+ for ((key, value) in legacyMapping.methods) {
+ classMapping.getOrCreateMethodMapping(key, "()V").deobfuscatedName = value
+ }
+ }
+ return mappings
+ }
+
+ override fun close() {}
+}