diff options
Diffstat (limited to 'src/main/java/com/replaymod')
-rw-r--r-- | src/main/java/com/replaymod/gradle/remap/PsiMapper.java | 396 | ||||
-rw-r--r-- | src/main/java/com/replaymod/gradle/remap/Transformer.java | 524 |
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 { |