Quelle Depend.java
Sprache: JAVA
/*
* Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package build.tools.depend;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.ImportTree;
import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ModifiersTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Documented;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.HexFormat;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.AnnotationValueVisitor;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementVisitor;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.ModuleElement;
import javax.lang.model.element.ModuleElement.DirectiveVisitor;
import javax.lang.model.element.ModuleElement.ExportsDirective;
import javax.lang.model.element.ModuleElement.OpensDirective;
import javax.lang.model.element.ModuleElement.ProvidesDirective;
import javax.lang.model.element.ModuleElement.RequiresDirective;
import javax.lang.model.element.ModuleElement.UsesDirective;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.QualifiedNameable;
import javax.lang.model.element.RecordComponentElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ErrorType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.IntersectionType;
import javax.lang.model.type.NoType;
import javax.lang.model.type.NullType;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.type.TypeVisitor;
import javax.lang.model.type.UnionType;
import javax.lang.model.type.WildcardType;
import javax.lang.model.util.ElementFilter;
import javax.tools.JavaFileObject;
import com.sun.source.util.JavacTask;
import com.sun.source.util.Plugin;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TaskEvent.Kind;
import com.sun.source.util.TaskListener;
import com.sun.source.util.TreePath;
import com.sun.source.util.TreeScanner;
import com.sun.source.util.Trees;
import com.sun.tools.javac.api.BasicJavacTask;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.main.JavaCompiler;
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Context.Key;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Options;
import com.sun.tools.javac.util.StringUtils;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.StreamSupport;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Name;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
public class Depend implements Plugin {
@Override
public String getName() {
return "depend" ;
}
@Override
public void init(JavacTask jt, String... args) {
addExports();
AtomicBoolean noApiChange = new AtomicBoolean();
try {
Context context = ((BasicJavacTask) jt).getContext();
Options options = Options.instance(context);
String modifiedInputs = options.get("modifiedInputs" );
if (modifiedInputs == null ) {
throw new IllegalStateException("Expected modifiedInputs to be set using -XDmodifiedInputs=" );
}
String logLevel = options.get("LOG_LEVEL" );
boolean debug = "trace" .equals(logLevel) || "debug" .equals(logLevel);
String internalAPIPath = options.get("internalAPIPath" );
if (internalAPIPath == null ) {
throw new IllegalStateException("Expected internalAPIPath to be set using -XDinternalAPIPath=" );
}
Set<Path> modified = Files.readAllLines(Paths.get(modifiedInputs)).stream()
.map(Paths::get)
.collect(Collectors.toSet());
Path internalAPIDigestFile = Paths.get(internalAPIPath);
JavaCompiler compiler = JavaCompiler.instance(context);
Class <?> initialFileParserIntf = Class .forName("com.sun.tools.javac.main.JavaCompiler$InitialFileParserIntf" );
Class <?> initialFileParser = Class .forName("com.sun.tools.javac.main.JavaCompiler$InitialFileParser" );
Field initialParserKeyField = initialFileParser.getDeclaredField("initialParserKey" );
@SuppressWarnings("unchecked" )
Key<Object> key = (Key<Object>) initialParserKeyField.get(null );
Object initialParserInstance =
Proxy.newProxyInstance(Depend.class .getClassLoader(),
new Class <?>[] {initialFileParserIntf},
new FilteredInitialFileParser(compiler,
modified,
internalAPIDigestFile,
noApiChange,
debug));
context.<Object>put(key, initialParserInstance);
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
jt.addTaskListener(new TaskListener() {
private final Map<ModuleElement, Set<PackageElement>> apiPackages = new HashMap<>();
private final MessageDigest apiHash;
{
try {
apiHash = MessageDigest.getInstance("SHA-256" );
} catch (NoSuchAlgorithmException ex) {
throw new IllegalStateException(ex);
}
}
@Override
public void started(TaskEvent te) {
}
@Override
public void finished(TaskEvent te) {
if (te.getKind() == Kind.ANALYZE) {
if (te.getSourceFile().isNameCompatible("module-info" , JavaFileObject.Kind.SOURCE)) {
ModuleElement mod = (ModuleElement) Trees.instance(jt).getElement(new TreePath(te.getCompilationUnit()));
new APIVisitor(apiHash).visit(mod);
} else if (te.getSourceFile().isNameCompatible("package-info" , JavaFileObject.Kind.SOURCE)) {
//ignore - cannot contain important changes (?)
} else {
TypeElement clazz = te.getTypeElement();
ModuleElement mod = jt.getElements().getModuleOf(clazz);
Set<PackageElement> thisModulePackages = apiPackages.computeIfAbsent(mod, m -> {
return ElementFilter.exportsIn(mod.getDirectives())
.stream()
.map(ed -> ed.getPackage())
.collect(Collectors.toSet());
});
if (thisModulePackages.contains(jt.getElements().getPackageOf(clazz))) {
new APIVisitor(apiHash).visit(clazz);
}
}
}
if (te.getKind() == Kind.COMPILATION && !noApiChange.get()) {
String previousSignature = null ;
File digestFile = new File(args[0]);
try (InputStream in = new FileInputStream(digestFile)) {
previousSignature = new String(in.readAllBytes(), "UTF-8" );
} catch (IOException ex) {
//ignore
}
String currentSignature = Depend.this .toString(apiHash.digest());
if (!Objects.equals(previousSignature, currentSignature)) {
digestFile.getParentFile().mkdirs();
try (OutputStream out = new FileOutputStream(digestFile)) {
out.write(currentSignature.getBytes("UTF-8" ));
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
}
}
});
}
private void addExports() {
var systemCompiler = ToolProvider.getSystemJavaCompiler();
try (JavaFileManager jfm = systemCompiler.getStandardFileManager(null , null , null )) {
JavaFileManager fm = new ForwardingJavaFileManager<JavaFileManager>(jfm) {
@Override
public ClassLoader getClassLoader(JavaFileManager.Location location) {
if (location == StandardLocation.CLASS_PATH) {
return Depend.class .getClassLoader();
}
return super .getClassLoader(location);
}
};
((JavacTask) systemCompiler.getTask(null , fm, null ,
List.of("-proc:only" , "-XDaccessInternalAPI=true" ),
List.of("java.lang.Object" ), null ))
.analyze();
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
private com.sun.tools.javac.util.List<JCCompilationUnit> doFilteredParse(
JavaCompiler compiler, Iterable<JavaFileObject> fileObjects, Set<Path> modified,
Path internalAPIDigestFile, AtomicBoolean noApiChange,
boolean debug) {
Map<String, String> internalAPI = new LinkedHashMap<>();
if (Files.isReadable(internalAPIDigestFile)) {
try {
Files.readAllLines(internalAPIDigestFile, StandardCharsets.UTF_8)
.forEach(line -> {
String[] keyAndValue = line.split("=" );
internalAPI.put(keyAndValue[0], keyAndValue[1]);
});
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
Map<JavaFileObject, JCCompilationUnit> files2CUT = new IdentityHashMap<>();
boolean fullRecompile = modified.stream()
.map(Path::toString)
.anyMatch(f -> !StringUtils.toLowerCase(f).endsWith(".java" ));
ListBuffer<JCCompilationUnit> result = new ListBuffer<>();
for (JavaFileObject jfo : fileObjects) {
if (modified.contains(Path.of(jfo.getName()))) {
JCCompilationUnit parsed = compiler.parse(jfo);
files2CUT.put(jfo, parsed);
String currentSignature = treeDigest(parsed);
if (!currentSignature.equals(internalAPI.get(jfo.getName()))) {
fullRecompile |= true ;
internalAPI.put(jfo.getName(), currentSignature);
}
result.add(parsed);
}
}
if (fullRecompile) {
for (JavaFileObject jfo : fileObjects) {
if (!modified.contains(Path.of(jfo.getName()))) {
JCCompilationUnit parsed = files2CUT.get(jfo);
if (parsed == null ) {
parsed = compiler.parse(jfo);
internalAPI.put(jfo.getName(), treeDigest(parsed));
}
result.add(parsed);
}
}
try (OutputStream out = Files.newOutputStream(internalAPIDigestFile)) {
String hashes = internalAPI.entrySet()
.stream()
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining("\n" ));
out.write(hashes.getBytes(StandardCharsets.UTF_8));
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
} else {
noApiChange.set(true );
}
if (debug) {
long allJavaInputs = StreamSupport.stream(fileObjects.spliterator(), false ).count();
String module = StreamSupport.stream(fileObjects.spliterator(), false )
.map(fo -> fo.toUri().toString())
.filter(path -> path.contains("/share/classes/" ))
.map(path -> path.substring(0, path.indexOf("/share/classes/" )))
.map(path -> path.substring(path.lastIndexOf("/" ) + 1))
.findAny()
.orElseGet(() -> "unknown" );
String nonJavaModifiedFiles = modified.stream()
.map(Path::toString)
.filter(f -> !StringUtils.toLowerCase(f)
.endsWith(".java" ))
.collect(Collectors.joining(", " ));
System.err.println("compiling module: " + module +
", all Java inputs: " + allJavaInputs +
", modified files (Java or non-Java): " + modified.size() +
", full recompile: " + fullRecompile +
", non-Java modified files: " + nonJavaModifiedFiles);
}
return result.toList();
}
private String treeDigest(JCCompilationUnit cu) {
try {
TreeVisitor v = new TreeVisitor(MessageDigest.getInstance("MD5" ));
v.scan(cu, null );
return Depend.this .toString(v.apiHash.digest());
} catch (NoSuchAlgorithmException ex) {
throw new IllegalStateException(ex);
}
}
private String toString(byte [] digest) {
return HexFormat.of().withUpperCase().formatHex(digest);
}
private static final class APIVisitor implements ElementVisitor<Void , Void >,
TypeVisitor<Void , Void >,
AnnotationValueVisitor<Void , Void >,
DirectiveVisitor<Void , Void > {
private final MessageDigest apiHash;
private final Charset utf8;
public APIVisitor(MessageDigest apiHash) {
this .apiHash = apiHash;
utf8 = Charset.forName("UTF-8" );
}
public Void visit(Iterable<? extends Element> list, Void p) {
list.forEach(e -> visit(e, p));
return null ;
}
private void update(CharSequence data) {
apiHash.update(data.toString().getBytes(utf8));
}
private void visit(Iterable<? extends TypeMirror> types) {
for (TypeMirror type : types) {
visit(type);
}
}
private void updateAnnotation(AnnotationMirror am) {
update("@" );
visit(am.getAnnotationType());
am.getElementValues()
.keySet()
.stream()
.sorted((ee1, ee2) -> ee1.getSimpleName().toString().compareTo(ee2.getSimpleName().toString()))
.forEach(ee -> {
visit(ee);
visit(am.getElementValues().get(ee));
});
}
private void updateAnnotations(Iterable<? extends AnnotationMirror> annotations) {
for (AnnotationMirror am : annotations) {
if (am.getAnnotationType().asElement().getAnnotation(Documented.class ) == null )
continue ;
updateAnnotation(am);
}
}
@Override
public Void visit(Element e, Void p) {
if (e.getKind() != ElementKind.MODULE &&
!e.getModifiers().contains(Modifier.PUBLIC ) &&
!e.getModifiers().contains(Modifier.PROTECTED )) {
return null ;
}
update(e.getKind().name());
update(e.getModifiers().stream()
.map(mod -> mod.name())
.collect(Collectors.joining("," , "[" , "]" )));
update(e.getSimpleName());
updateAnnotations(e.getAnnotationMirrors());
return e.accept(this , p);
}
@Override
public Void visit(Element e) {
return visit(e, null );
}
@Override
public Void visitModule(ModuleElement e, Void p) {
update(String.valueOf(e.isOpen()));
update(e.getQualifiedName());
e.getDirectives()
.stream()
.forEach(d -> d.accept(this , null ));
return null ;
}
@Override
public Void visitPackage(PackageElement e, Void p) {
throw new UnsupportedOperationException("Should not get here." );
}
@Override
public Void visitType(TypeElement e, Void p) {
visit(e.getTypeParameters(), p);
visit(e.getSuperclass());
visit(e.getInterfaces());
visit(e.getEnclosedElements(), p);
return null ;
}
@Override
public Void visitRecordComponent(@SuppressWarnings("preview" )RecordComponentElement e, Void p) {
update(e.getSimpleName());
visit(e.asType());
return null ;
}
@Override
public Void visitVariable(VariableElement e, Void p) {
visit(e.asType());
update(String.valueOf(e.getConstantValue()));
return null ;
}
@Override
public Void visitExecutable(ExecutableElement e, Void p) {
update("<" );
visit(e.getTypeParameters(), p);
update(">" );
visit(e.getReturnType());
update("(" );
visit(e.getParameters(), p);
update(")" );
visit(e.getThrownTypes());
update(String.valueOf(e.getDefaultValue()));
update(String.valueOf(e.isVarArgs()));
return null ;
}
@Override
public Void visitTypeParameter(TypeParameterElement e, Void p) {
visit(e.getBounds());
return null ;
}
@Override
public Void visitUnknown(Element e, Void p) {
throw new UnsupportedOperationException("Not supported." );
}
@Override
public Void visit(TypeMirror t, Void p) {
if (t == null ) {
update("null" );
return null ;
}
update(t.getKind().name());
updateAnnotations(t.getAnnotationMirrors());
t.accept(this , p);
return null ;
}
@Override
public Void visitPrimitive(PrimitiveType t, Void p) {
return null ; //done
}
@Override
public Void visitNull(NullType t, Void p) {
return null ; //done
}
@Override
public Void visitArray(ArrayType t, Void p) {
visit(t.getComponentType());
update("[]" );
return null ;
}
@Override
public Void visitDeclared(DeclaredType t, Void p) {
update(((QualifiedNameable) t.asElement()).getQualifiedName());
update("<" );
visit(t.getTypeArguments());
update(">" );
return null ;
}
@Override
public Void visitError(ErrorType t, Void p) {
return visitDeclared(t, p);
}
private final Set<Element> seenVariables = new HashSet<>();
@Override
public Void visitTypeVariable(TypeVariable t, Void p) {
Element decl = t.asElement();
if (!seenVariables.add(decl)) {
return null ;
}
visit(decl, null );
visit(t.getLowerBound(), null );
visit(t.getUpperBound(), null );
seenVariables.remove(decl);
return null ;
}
@Override
public Void visitWildcard(WildcardType t, Void p) {
visit(t.getSuperBound());
visit(t.getExtendsBound());
return null ;
}
@Override
public Void visitExecutable(ExecutableType t, Void p) {
throw new UnsupportedOperationException("Not supported." );
}
@Override
public Void visitNoType(NoType t, Void p) {
return null ;//done
}
@Override
public Void visitUnknown(TypeMirror t, Void p) {
throw new UnsupportedOperationException("Not supported." );
}
@Override
public Void visitUnion(UnionType t, Void p) {
update("(" );
visit(t.getAlternatives());
update(")" );
return null ;
}
@Override
public Void visitIntersection(IntersectionType t, Void p) {
update("(" );
visit(t.getBounds());
update(")" );
return null ;
}
@Override
public Void visit(AnnotationValue av, Void p) {
return av.accept(this , p);
}
@Override
public Void visitBoolean(boolean b, Void p) {
update(String.valueOf(b));
return null ;
}
@Override
public Void visitByte(byte b, Void p) {
update(String.valueOf(b));
return null ;
}
@Override
public Void visitChar(char c, Void p) {
update(String.valueOf(c));
return null ;
}
@Override
public Void visitDouble(double d, Void p) {
update(String.valueOf(d));
return null ;
}
@Override
public Void visitFloat(float f, Void p) {
update(String.valueOf(f));
return null ;
}
@Override
public Void visitInt(int i, Void p) {
update(String.valueOf(i));
return null ;
}
@Override
public Void visitLong(long i, Void p) {
update(String.valueOf(i));
return null ;
}
@Override
public Void visitShort(short s, Void p) {
update(String.valueOf(s));
return null ;
}
@Override
public Void visitString(String s, Void p) {
update(s);
return null ;
}
@Override
public Void visitType(TypeMirror t, Void p) {
return visit(t);
}
@Override
public Void visitEnumConstant(VariableElement c, Void p) {
return visit(c);
}
@Override
public Void visitAnnotation(AnnotationMirror a, Void p) {
updateAnnotation(a);
return null ;
}
@Override
public Void visitArray(List<? extends AnnotationValue> vals, Void p) {
update("(" );
for (AnnotationValue av : vals) {
visit(av);
}
update(")" );
return null ;
}
@Override
public Void visitUnknown(AnnotationValue av, Void p) {
throw new UnsupportedOperationException("Not supported." );
}
@Override
public Void visitRequires(RequiresDirective d, Void p) {
update("RequiresDirective" );
update(String.valueOf(d.isStatic()));
update(String.valueOf(d.isTransitive()));
update(d.getDependency().getQualifiedName());
return null ;
}
@Override
public Void visitExports(ExportsDirective d, Void p) {
update("ExportsDirective" );
update(d.getPackage().getQualifiedName());
if (d.getTargetModules() != null ) {
for (ModuleElement me : d.getTargetModules()) {
update(me.getQualifiedName());
}
} else {
update("" );
}
return null ;
}
@Override
public Void visitOpens(OpensDirective d, Void p) {
update("OpensDirective" );
update(d.getPackage().getQualifiedName());
if (d.getTargetModules() != null ) {
for (ModuleElement me : d.getTargetModules()) {
update(me.getQualifiedName());
}
} else {
update("" );
}
return null ;
}
@Override
public Void visitUses(UsesDirective d, Void p) {
update("UsesDirective" );
update(d.getService().getQualifiedName());
return null ;
}
@Override
public Void visitProvides(ProvidesDirective d, Void p) {
update("ProvidesDirective" );
update(d.getService().getQualifiedName());
update("(" );
for (TypeElement impl : d.getImplementations()) {
update(impl.getQualifiedName());
}
update(")" );
return null ;
}
}
private static final class TreeVisitor extends TreeScanner<Void , Void > {
private final Set<Name> seenIdentifiers = new HashSet<>();
private final MessageDigest apiHash;
private final Charset utf8;
public TreeVisitor(MessageDigest apiHash) {
this .apiHash = apiHash;
utf8 = Charset.forName("UTF-8" );
}
private void update(CharSequence data) {
apiHash.update(data.toString().getBytes(utf8));
}
@Override
public Void scan(Tree tree, Void p) {
update("(" );
if (tree != null ) {
update(tree.getKind().name());
};
super .scan(tree, p);
update(")" );
return null ;
}
@Override
public Void visitCompilationUnit(CompilationUnitTree node, Void p) {
seenIdentifiers.clear();
scan(node.getPackage(), p);
scan(node.getTypeDecls(), p);
scan(((JCCompilationUnit) node).getModuleDecl(), p);
List<ImportTree> importantImports = new ArrayList<>();
for (ImportTree imp : node.getImports()) {
Tree t = imp.getQualifiedIdentifier();
if (t.getKind() == Tree.Kind.MEMBER_SELECT) {
Name member = ((MemberSelectTree) t).getIdentifier();
if (member.contentEquals("*" ) || seenIdentifiers.contains(member)) {
importantImports.add(imp);
}
} else {
//should not happen, possibly erroneous source?
importantImports.add(imp);
}
}
importantImports.sort((imp1, imp2) -> {
if (imp1.isStatic() ^ imp2.isStatic()) {
return imp1.isStatic() ? -1 : 1;
} else {
return imp1.getQualifiedIdentifier().toString().compareTo(imp2.getQualifiedIdentifier().toString());
}
});
scan(importantImports, p);
return null ;
}
@Override
public Void visitIdentifier(IdentifierTree node, Void p) {
update(node.getName());
seenIdentifiers.add(node.getName());
return super .visitIdentifier(node, p);
}
@Override
public Void visitMemberSelect(MemberSelectTree node, Void p) {
update(node.getIdentifier());
return super .visitMemberSelect(node, p);
}
@Override
public Void visitMemberReference(MemberReferenceTree node, Void p) {
update(node.getName());
return super .visitMemberReference(node, p);
}
@Override
public Void scan(Iterable<? extends Tree> nodes, Void p) {
update("(" );
super .scan(nodes, p);
update(")" );
return null ;
}
@Override
public Void visitClass(ClassTree node, Void p) {
update(node.getSimpleName());
scan(node.getModifiers(), p);
scan(node.getTypeParameters(), p);
scan(node.getExtendsClause(), p);
scan(node.getImplementsClause(), p);
scan(node.getMembers()
.stream()
.filter(this ::importantMember)
.collect(Collectors.toList()),
p);
return null ;
}
private boolean importantMember(Tree m) {
return switch (m.getKind()) {
case ANNOTATION_TYPE, CLASS , ENUM , INTERFACE , RECORD ->
!isPrivate(((ClassTree) m).getModifiers());
case METHOD ->
!isPrivate(((MethodTree) m).getModifiers());
case VARIABLE ->
!isPrivate(((VariableTree) m).getModifiers()) ||
isRecordComponent((VariableTree) m);
case BLOCK -> false ;
default -> throw new IllegalStateException("Unexpected tree kind: " + m.getKind());
};
}
private boolean isPrivate(ModifiersTree mt) {
return mt.getFlags().contains(Modifier.PRIVATE );
}
private boolean isRecordComponent(VariableTree vt) {
return (((JCVariableDecl) vt).mods.flags & Flags.RECORD) != 0;
}
@Override
public Void visitVariable(VariableTree node, Void p) {
update(node.getName());
return super .visitVariable(node, p);
}
@Override
public Void visitMethod(MethodTree node, Void p) {
update(node.getName());
scan(node.getModifiers(), p);
scan(node.getReturnType(), p);
scan(node.getTypeParameters(), p);
scan(node.getParameters(), p);
scan(node.getReceiverParameter(), p);
scan(node.getThrows(), p);
scan(node.getDefaultValue(), p);
return null ;
}
@Override
public Void visitLiteral(LiteralTree node, Void p) {
update(String.valueOf(node.getValue()));
return super .visitLiteral(node, p);
}
@Override
public Void visitModifiers(ModifiersTree node, Void p) {
update(node.getFlags().toString());
return super .visitModifiers(node, p);
}
}
private class FilteredInitialFileParser implements InvocationHandler {
private final JavaCompiler compiler;
private final Set<Path> modified;
private final Path internalAPIDigestFile;
private final AtomicBoolean noApiChange;
private final boolean debug;
public FilteredInitialFileParser(JavaCompiler compiler,
Set<Path> modified,
Path internalAPIDigestFile,
AtomicBoolean noApiChange,
boolean debug) {
this .compiler = compiler;
this .modified = modified;
this .internalAPIDigestFile = internalAPIDigestFile;
this .noApiChange = noApiChange;
this .debug = debug;
}
@Override
@SuppressWarnings("unchecked" )
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return switch (method.getName()) {
case "parse" -> doFilteredParse(compiler,
(Iterable<JavaFileObject>) args[0],
modified,
internalAPIDigestFile,
noApiChange,
debug);
default -> throw new UnsupportedOperationException();
};
}
}
}
Messung V0.5 C=94 H=87 G=90
¤ Dauer der Verarbeitung: 0.11 Sekunden
¤
*© Formatika GbR, Deutschland
2026-04-02