aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com')
-rw-r--r--src/main/java/com/replaymod/gradle/remap/PsiMapper.java396
-rw-r--r--src/main/java/com/replaymod/gradle/remap/Transformer.java524
2 files changed, 457 insertions, 463 deletions
diff --git a/src/main/java/com/replaymod/gradle/remap/PsiMapper.java b/src/main/java/com/replaymod/gradle/remap/PsiMapper.java
new file mode 100644
index 0000000..17d0783
--- /dev/null
+++ b/src/main/java/com/replaymod/gradle/remap/PsiMapper.java
@@ -0,0 +1,396 @@
+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 com.replaymod.gradle.remap.Transformer.Mapping;
+
+import java.util.ArrayDeque;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
+
+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 Map<String, Mapping> map;
+ private final Map<String, Mapping> mixinMappings = new HashMap<>();
+ private final PsiFile file;
+ private boolean error;
+ private TreeMap<TextRange, String> changes = new TreeMap<>(Comparator.comparing(TextRange::getStartOffset));
+
+ PsiMapper(Map<String, Mapping> map, PsiFile file) {
+ this.map = map;
+ this.file = file;
+ }
+
+ 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;
+ Mapping mapping = this.mixinMappings.get(name);
+ if (mapping == null) {
+ mapping = map.get(name);
+ }
+ if (mapping == null) return;
+ String mapped = mapping.fields.get(field.getName());
+ 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
+ ) {
+ int line = StringUtil.offsetToLineNumber(file.getText(), expr.getTextOffset());
+ System.err.println(file.getName() + ":" + line + ": 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.");
+ error = true;
+ }
+ }
+
+ private void map(PsiElement expr, PsiMethod method) {
+ PsiClass declaringClass = method.getContainingClass();
+ if (declaringClass == null) return;
+ ArrayDeque<PsiClass> parentQueue = new ArrayDeque<>();
+ parentQueue.offer(declaringClass);
+ Mapping mapping = null;
+
+ String name = declaringClass.getQualifiedName();
+ if (name != null) {
+ mapping = mixinMappings.get(name);
+ }
+ while (true) {
+ if (mapping != null) {
+ String mapped = mapping.methods.get(method.getName());
+ 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.get(name);
+ }
+ }
+ }
+
+ private void map(PsiElement expr, PsiQualifiedNamedElement resolved) {
+ String name = resolved.getQualifiedName();
+ if (name == null) return;
+ Mapping mapping = map.get(name);
+ if (mapping == null) return;
+ String mapped = mapping.newName;
+ if (mapped.equals(name)) return;
+
+ 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(Mapping 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.fields.get(target);
+ 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(Mapping 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 = mapping.methods.get(literalValue);
+ if (mapped != null && !mapped.equals(literalValue)) {
+ PsiAnnotationMemberValue value = attribute.getValue();
+ assert value != null;
+ replace(value, '"' + mapped + '"');
+ }
+ }
+ }
+ });
+ }
+
+ private Mapping remapInternalType(String internalType, StringBuilder result) {
+ if (internalType.charAt(0) == 'L') {
+ String type = internalType.substring(1, internalType.length() - 1).replace('/', '.');
+ Mapping mapping = map.get(type);
+ if (mapping != null) {
+ result.append('L').append(mapping.newName.replace('.', '/')).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);
+ Mapping mapping = remapInternalType(owner, builder);
+ String mapped = null;
+ if (mapping != null) {
+ mapped = (method ? mapping.methods : mapping.fields).get(name);
+ }
+ 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;
+
+ Mapping mapping = map.get(target.getQualifiedName());
+ if (mapping == null) return;
+
+ mixinMappings.put(psiClass.getQualifiedName(), mapping);
+
+ if (!mapping.fields.isEmpty()) {
+ remapAccessors(mapping);
+ }
+ if (!mapping.methods.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/Transformer.java b/src/main/java/com/replaymod/gradle/remap/Transformer.java
index e41867f..1cbd425 100644
--- a/src/main/java/com/replaymod/gradle/remap/Transformer.java
+++ b/src/main/java/com/replaymod/gradle/remap/Transformer.java
@@ -1,11 +1,28 @@
package com.replaymod.gradle.remap;
-import org.eclipse.jdt.core.JavaCore;
-import org.eclipse.jdt.core.compiler.IProblem;
-import org.eclipse.jdt.core.dom.*;
-import org.eclipse.jface.text.BadLocationException;
-import org.eclipse.jface.text.Document;
-import org.eclipse.text.edits.TextEdit;
+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.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 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;
@@ -15,14 +32,11 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
-import java.util.ArrayDeque;
-import java.util.Arrays;
+import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -32,7 +46,7 @@ class Transformer {
private String[] classpath;
private boolean fail;
- public static void main(String[] args) throws IOException, BadLocationException {
+ public static void main(String[] args) throws IOException {
Map<String, Mapping> mappings;
if (args[0].isEmpty()) {
mappings = new HashMap<>();
@@ -93,21 +107,9 @@ class Transformer {
this.classpath = classpath;
}
- public Map<String, String> remap(Map<String, String> sources) throws BadLocationException, IOException {
- ASTParser parser = ASTParser.newParser(AST.JLS8);
- Map<String, String> options = JavaCore.getDefaultOptions();
- JavaCore.setComplianceOptions("1.8", options);
- parser.setCompilerOptions(options);
- parser.setEnvironment(classpath, null, null, true);
- parser.setResolveBindings(true);
- parser.setBindingsRecovery(true);
-
+ public Map<String, String> remap(Map<String, String> sources) throws IOException {
Path tmpDir = Files.createTempDirectory("remap");
try {
- Map<String, String> filePathToName = new HashMap<>();
- String[] compilationUnits = new String[sources.size()];
- String[] encodings = new String[compilationUnits.length];
- int i = 0;
for (Entry<String, String> entry : sources.entrySet()) {
String unitName = entry.getKey();
String source = entry.getValue();
@@ -115,456 +117,52 @@ class Transformer {
Path path = tmpDir.resolve(unitName);
Files.createDirectories(path.getParent());
Files.write(path, source.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE);
- String filePath = path.toString();
- filePathToName.put(filePath, unitName);
- compilationUnits[i] = filePath;
- encodings[i] = "UTF-8";
-
- i++;
}
- Map<String, CompilationUnit> cus = new HashMap<>();
- parser.createASTs(compilationUnits, encodings, new String[0], new FileASTRequestor() {
- @Override
- public void acceptAST(String sourceFilePath, CompilationUnit cu) {
- String unitName = filePathToName.get(sourceFilePath);
- for (IProblem problem : cu.getProblems()) {
- if (problem.isError()) {
- System.err.println(unitName + ":" + problem.getSourceLineNumber() + ": " + problem.getMessage());
- }
- }
- cus.put(unitName, cu);
- }
- }, null);
- Map<String, String> results = new HashMap<>();
- for (Entry<String, CompilationUnit> entry : cus.entrySet()) {
- String unitName = entry.getKey();
- CompilationUnit cu = entry.getValue();
+ 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));
- cu.recordModifications();
- if (remapClass(unitName, cu)) {
- Document document = new Document(sources.get(unitName));
- TextEdit edit = cu.rewrite(document, JavaCore.getDefaultOptions());
- edit.apply(document);
- results.put(unitName, document.get());
- } else {
- results.put(unitName, sources.get(unitName));
- }
- }
- return results;
- } finally {
- Files.walk(tmpDir).map(Path::toFile).sorted(Comparator.reverseOrder()).forEach(File::delete);
- }
- }
+ KotlinCoreEnvironment environment = KotlinCoreEnvironment.createForProduction(
+ Disposer.newDisposable(),
+ config,
+ EnvironmentConfigFiles.JVM_CONFIG_FILES
+ );
+ Extensions.getRootArea().registerExtensionPoint(CustomExceptionHandler.KEY.getName(), CustomExceptionHandler.class.getName(), ExtensionPoint.Kind.INTERFACE);
- 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";
+ MockProject project = (MockProject) environment.getProject();
- // Note: Supports only Mixins with a single target (ignores others) and only ones specified via class literals
- private ITypeBinding getMixinTarget(IAnnotationBinding annotation) {
- for (IMemberValuePairBinding pair : annotation.getDeclaredMemberValuePairs()) {
- if (pair.getName().equals("value")) {
- return (ITypeBinding) ((Object[]) pair.getValue())[0];
- }
- }
- return null;
- }
-
- private boolean remapAccessors(CompilationUnit cu, Mapping mapping) {
- AtomicBoolean changed = new AtomicBoolean(false);
- AST ast = cu.getAST();
+ environment.updateClasspath(Stream.of(getClasspath()).map(it -> new JvmClasspathRoot(new File(it))).collect(Collectors.toList()));
- cu.accept(new ASTVisitor() {
- @Override
- public boolean visit(MethodDeclaration node) {
- int annotationIndex = -1;
- Annotation annotationNode = null;
- IAnnotationBinding annotation = null;
- for (int i = 0; i < node.modifiers().size(); i++) {
- Object obj = node.modifiers().get(i);
- if (!(obj instanceof Annotation)) {
- continue;
- }
- annotationNode = (Annotation) obj;
- annotation = annotationNode.resolveAnnotationBinding();
- if (annotation != null && annotation.getAnnotationType().getQualifiedName().equals(CLASS_ACCESSOR)) {
- annotationIndex = i;
- break;
- }
- }
- if (annotationIndex == -1) return false;
+ TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(
+ project,
+ Collections.emptyList(),
+ new NoScopeRecordCliBindingTrace(),
+ environment.getConfiguration(),
+ environment::createPackagePartProvider
+ );
- String targetByName = node.getName().getIdentifier();
- 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.getDeclaredMemberValuePairs())
- .filter(it -> it.getName().equals("value"))
- .map(it -> (String) it.getValue())
- .findAny()
- .orElse(targetByName);
-
- if (target == null) {
- throw new IllegalArgumentException("Cannot determine accessor target for " + node);
- }
-
- String mapped = mapping.fields.get(target);
- if (mapped != null && !mapped.equals(target)) {
-
- Annotation newAnnotation;
-
- // Update accessor target
- if (mapped.equals(targetByName)) {
- // Mapped name matches implied target, can just remove the explict target
- newAnnotation = ast.newMarkerAnnotation();
- } else {
- // Mapped name does not match implied target, need to set the target as annotation value
- SingleMemberAnnotation singleMemberAnnotation = ast.newSingleMemberAnnotation();
- StringLiteral value = ast.newStringLiteral();
- value.setLiteralValue(mapped);
- singleMemberAnnotation.setValue(value);
- newAnnotation = singleMemberAnnotation;
- }
-
- newAnnotation.setTypeName(ast.newName(annotationNode.getTypeName().getFullyQualifiedName()));
- //noinspection unchecked
- node.modifiers().set(annotationIndex, newAnnotation);
-
- changed.set(true);
- }
-
- return false;
- }
- });
-
- return changed.get();
- }
-
- private boolean remapInjectsAndRedirects(CompilationUnit cu, Mapping mapping) {
- AtomicBoolean changed = new AtomicBoolean(false);
-
- cu.accept(new ASTVisitor() {
- @Override
- public boolean visit(MethodDeclaration node) {
- NormalAnnotation annotationNode = null;
- for (Object obj : node.modifiers()) {
- if (!(obj instanceof NormalAnnotation)) {
- continue;
- }
- annotationNode = (NormalAnnotation) obj;
- IAnnotationBinding annotation = annotationNode.resolveAnnotationBinding();
- if (annotation != null) {
- String qualifiedName = annotation.getAnnotationType().getQualifiedName();
- if (qualifiedName.equals(CLASS_INJECT) || qualifiedName.equals(CLASS_REDIRECT)) {
- break;
- }
- }
- annotationNode = null;
- }
- if (annotationNode == null) return false;
-
- //noinspection unchecked
- for (MemberValuePair pair : (List<MemberValuePair>) annotationNode.values()) {
- if (!pair.getName().getIdentifier().equals("method")) continue;
-
- Object expr = pair.getValue();
- // Note: mixin supports multiple targets, we do not (yet)
- if (!(expr instanceof StringLiteral)) continue;
- StringLiteral methodNode = (StringLiteral) expr;
- String method = methodNode.getLiteralValue();
- String mapped = mapping.methods.get(method);
- if (mapped != null && !mapped.equals(method)) {
- methodNode.setLiteralValue(mapped);
- changed.set(true);
- }
- }
-
- return false;
- }
- });
-
- return changed.get();
- }
-
- private Mapping remapInternalType(String internalType, StringBuilder result) {
- if (internalType.charAt(0) == 'L') {
- String type = internalType.substring(1, internalType.length() - 1).replace('/', '.');
- Mapping mapping = map.get(type);
- if (mapping != null) {
- result.append('L').append(mapping.newName.replace('.', '/')).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);
- Mapping mapping = remapInternalType(owner, builder);
- String mapped = null;
- if (mapping != null) {
- mapped = (method ? mapping.methods : mapping.fields).get(name);
- }
- 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);
+ 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;
}
- int end = args.indexOf(';', i);
- String arg = args.substring(i, end + 1);
- remapInternalType(arg, builder);
- i = end;
+ results.put(name, mapped);
}
- builder.append(')');
- } else {
- builder.append(':');
+ return results;
+ } finally {
+ Files.walk(tmpDir).map(Path::toFile).sorted(Comparator.reverseOrder()).forEach(File::delete);
}
- remapInternalType(returnType, builder);
- return builder.toString();
- }
-
- private boolean remapAtTargets(CompilationUnit cu) {
- AtomicBoolean changed = new AtomicBoolean(false);
-
- cu.accept(new ASTVisitor() {
- @Override
- public boolean visit(NormalAnnotation node) {
- IAnnotationBinding annotation = node.resolveAnnotationBinding();
- if (annotation == null) return true;
- String qualifiedName = annotation.getAnnotationType().getQualifiedName();
- if (!qualifiedName.equals(CLASS_AT)) return true;
-
- //noinspection unchecked
- for (MemberValuePair pair : (List<MemberValuePair>) node.values()) {
- if (!pair.getName().getIdentifier().equals("target")) continue;
-
- StringLiteral value = (StringLiteral) pair.getValue();
- String signature = value.getLiteralValue();
- String newSignature = remapFullyQualifiedMethodOrField(signature);
- if (!newSignature.equals(signature)) {
- value.setLiteralValue(newSignature);
- changed.set(true);
- }
- }
-
- return false;
- }
- });
-
- return changed.get();
- }
-
- private static String stripGenerics(String name) {
- int paramIndex = name.indexOf('<');
- return paramIndex != -1 ? name.substring(0, paramIndex) : name;
- }
-
- private boolean remapClass(String unitName, CompilationUnit cu) {
- AtomicBoolean changed = new AtomicBoolean(false);
- Map<String, Mapping> mixinMappings = new HashMap<>();
-
- cu.accept(new ASTVisitor() {
- @Override
- public boolean visit(TypeDeclaration node) {
- ITypeBinding type = node.resolveBinding();
- if (type == null) return false;
-
- IAnnotationBinding binding = null;
- for (Object modifier : node.modifiers()) {
- if (modifier instanceof Annotation) {
- binding = ((Annotation) modifier).resolveAnnotationBinding();
- if (binding != null && !binding.getAnnotationType().getQualifiedName().equals(CLASS_MIXIN)) {
- binding = null;
- }
- }
- }
- if (binding == null) return false;
-
- if (remapAtTargets(cu)) {
- changed.set(true);
- }
-
- ITypeBinding target = getMixinTarget(binding);
- if (target == null) return false;
-
- Mapping mapping = map.get(target.getQualifiedName());
- if (mapping == null) return false;
-
- mixinMappings.put(type.getQualifiedName(), mapping);
-
- if (!mapping.fields.isEmpty()) {
- if (remapAccessors(cu, mapping)) {
- changed.set(true);
- }
- }
- if (!mapping.methods.isEmpty()) {
- if (remapInjectsAndRedirects(cu, mapping)) {
- changed.set(true);
- }
- }
-
- return false;
- }
- });
-
- cu.accept(new ASTVisitor() {
- @Override
- public boolean visit(ImportDeclaration node) {
- String name = node.getName().getFullyQualifiedName();
- Mapping mapping = map.get(name);
- String mapped = mapping == null ? null : mapping.newName;
- if (mapped != null && !mapped.equals(name)) {
- node.setName(node.getAST().newName(mapped));
- changed.set(true);
- }
- return false;
- }
- });
-
- cu.accept(new ASTVisitor() {
- @Override
- public boolean visit(ImportDeclaration node) {
- return false;
- }
-
- @Override
- public boolean visit(QualifiedName node) {
- String name = node.getFullyQualifiedName();
- Mapping mapping = map.get(name);
- String mapped = mapping == null ? null : mapping.newName;
- if (mapped != null && !mapped.equals(name)) {
- node.setQualifier(node.getAST().newName(mapped.substring(0, mapped.lastIndexOf('.'))));
- node.setName(node.getAST().newSimpleName(mapped.substring(mapped.lastIndexOf('.') + 1)));
- changed.set(true);
- return false;
- } else {
- return true;
- }
- }
-
- @Override
- public boolean visit(SimpleName node) {
- return visitName(node.resolveBinding(), node);
- }
-
- private boolean visitName(IBinding binding, SimpleName node) {
- String mapped;
- if (binding instanceof IVariableBinding) {
- ITypeBinding declaringClass = ((IVariableBinding) binding).getDeclaringClass();
- if (declaringClass == null) return true;
- String name = stripGenerics(declaringClass.getQualifiedName());
- if (name.isEmpty()) return true;
- Mapping mapping = mixinMappings.get(name);
- if (mapping == null) {
- mapping = map.get(name);
- }
- if (mapping == null) return true;
- mapped = mapping.fields.get(node.getIdentifier());
- if (mapped != null) {
- ASTNode parent = node.getParent();
- if (!(parent instanceof FieldAccess // qualified access is fine
- || parent instanceof QualifiedName // qualified access is fine
- || parent instanceof VariableDeclarationFragment // shadow member declarations are fine
- || parent instanceof SwitchCase) // referencing constants in case statements is fine
- ) {
- System.err.println(unitName + ": Implicit member reference to remapped field \"" + node.getIdentifier() + "\". " +
- "This can cause issues if the remapped reference becomes shadowed by a local variable and is therefore forbidden. " +
- "Use \"this." + node.getIdentifier() + "\" instead.");
- fail = true;
- }
- }
- } else if (binding instanceof IMethodBinding) {
- ITypeBinding declaringClass = ((IMethodBinding) binding).getDeclaringClass();
- if (declaringClass == null) return true;
- ArrayDeque<ITypeBinding> parentQueue = new ArrayDeque<>();
- parentQueue.offer(declaringClass);
- Mapping mapping = null;
-
- String name = stripGenerics(declaringClass.getQualifiedName());
- if (!name.isEmpty()) {
- mapping = mixinMappings.get(name);
- }
- while (true) {
- if (mapping != null) {
- mapped = mapping.methods.get(node.getIdentifier());
- if (mapped != null) {
- break;
- }
- mapping = null;
- }
- while (mapping == null) {
- declaringClass = parentQueue.poll();
- if (declaringClass == null) return true;
-
- ITypeBinding superClass = declaringClass.getSuperclass();
- if (superClass != null) {
- parentQueue.offer(superClass);
- }
- for (ITypeBinding anInterface : declaringClass.getInterfaces()) {
- parentQueue.offer(anInterface);
- }
-
- name = stripGenerics(declaringClass.getQualifiedName());
- if (name.isEmpty()) continue;
- mapping = map.get(name);
- }
- }
- } else if (binding instanceof ITypeBinding) {
- String name = stripGenerics(((ITypeBinding) binding).getQualifiedName());
- if (name.isEmpty()) return true;
- Mapping mapping = map.get(name);
- if (mapping == null) return true;
- mapped = mapping.newName;
- mapped = mapped.substring(mapped.lastIndexOf('.') + 1);
- } else {
- return true;
- }
-
- if (mapped != null && !mapped.equals(node.getIdentifier())) {
- node.setIdentifier(mapped);
- changed.set(true);
- }
- return true;
- }
-
- @Override
- public boolean visit(MethodDeclaration node) {
- if (node.getBody() != null && node.getLength() == node.getBody().getLength()) {
- // Body exists but is same length as overall definition? -> method was probably generated by lombok
- return false;
- }
- return super.visit(node);
- }
- });
- return changed.get();
}
public static class Mapping {