tools/javac/CompatibilityPlugin.java (84 lines of code) (raw):
/*
* Copyright 2000-2024 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import com.sun.source.tree.*;
import com.sun.source.util.*;
import com.sun.source.util.TreeScanner;
import com.sun.tools.javac.api.*;
import com.sun.tools.javac.tree.*;
import com.sun.tools.javac.util.*;
import java.io.IOException;
import java.nio.file.*;
import java.util.Arrays;
import java.util.stream.Stream;
/**
* Magically makes JBR API written in Java 9 compile for Java 8.
* Also removes Java 9 classes which are duplicating existing Java 8 classes.
*/
public class CompatibilityPlugin implements Plugin {
private TreeMaker treeMaker;
@Override
public String getName() {
return "CompatibilityPlugin";
}
@Override
public void init(JavacTask task, String... args) {
Context context = ((BasicJavacTask) task).getContext();
treeMaker = TreeMaker.instance(context);
task.addTaskListener(new TaskListener() {
public void finished(TaskEvent e) {
if (e.getKind() == TaskEvent.Kind.PARSE) {
e.getCompilationUnit().accept(scanner, null);
} else if (e.getKind() == TaskEvent.Kind.COMPILATION) {
if (args.length >= 2) removeDuplicates(args[0], args[1]);
}
}
});
}
private final TreeScanner<Void, String> scanner = new TreeScanner<>() {
@Override
public Void visitAnnotation(AnnotationTree node, String qualifiedName) {
switch (node.getAnnotationType().toString()) {
case "Deprecated", "java.lang.Deprecated" -> {
// @Deprecated didn't have any members in Java 8, so clear it.
var jca = (JCTree.JCAnnotation) node;
jca.args = List.nil();
}
}
return super.visitAnnotation(node, qualifiedName);
}
@Override
public Void visitCompilationUnit(CompilationUnitTree node, String qualifiedName) {
ExpressionTree pkg = node.getPackageName();
return super.visitCompilationUnit(node, pkg == null ? null : pkg.toString());
}
@Override
public Void visitClass(ClassTree node, String qualifiedName) {
return super.visitClass(node, qualifiedName == null ? node.getSimpleName().toString() :
qualifiedName + "." + node.getSimpleName().toString());
}
@Override
public Void visitMethod(MethodTree node, String qualifiedName) {
// getApiVersionFromModule works, well, by asking version from module,
// which is not available in Java 8, so for that case we return UNKNOWN.
if (qualifiedName.equals("com.jetbrains.JBR") &&
node.getName().toString().equals("getApiVersionFromModule")) {
var body = (JCTree.JCBlock) node.getBody();
body.stats = List.of(treeMaker.Return(treeMaker.Literal("UNKNOWN")));
}
return super.visitMethod(node, qualifiedName);
}
};
// Can we really simply compare class files byte by byte?
// I doubt it, but jar utility does exactly that, well...
private static void removeDuplicates(String out8, String out9) {
byte[] cafeBabe = {(byte) 0xca, (byte) 0xfe, (byte) 0xba, (byte) 0xbe};
Path path8 = Path.of(out8), path9 = Path.of(out9);
try (Stream<Path> stream8 = Files.walk(path8)) {
stream8.filter(Files::isRegularFile).forEach(f8 -> {
Path f9 = path9.resolve(path8.relativize(f8));
try {
byte[] b9 = Files.readAllBytes(f9);
byte[] b8 = Files.readAllBytes(f8);
// First 4 bytes are a magic number, next 4 bytes are version which we are ignoring.
if (Arrays.equals(b8, 0, 4, cafeBabe, 0, 4) && Arrays.equals(b9, 0, 4, cafeBabe, 0, 4) &&
Arrays.equals(b8, 8, b8.length, b9, 8, b9.length)) {
// Duplicate! Remove it.
Files.delete(f9);
}
} catch (NoSuchFileException | IllegalArgumentException | ArrayIndexOutOfBoundsException ignore) {
} catch (IOException e) {
throw new RuntimeException(e);
}
});
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}