diff options
Diffstat (limited to 'src/main/java/com/replaymod/gradle/remap/Transformer.java')
-rw-r--r-- | src/main/java/com/replaymod/gradle/remap/Transformer.java | 524 |
1 files changed, 61 insertions, 463 deletions
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 { |