
de.is24.deadcode4j.analyzer.JavaFileAnalyzer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of deadcode4j-maven-plugin Show documentation
Show all versions of deadcode4j-maven-plugin Show documentation
Finds unused classes of a project
package de.is24.deadcode4j.analyzer;
import com.google.common.base.Optional;
import com.google.common.cache.LoadingCache;
import de.is24.deadcode4j.AnalysisContext;
import de.is24.deadcode4j.analyzer.javassist.ClassPoolAccessor;
import de.is24.guava.NonNullFunction;
import de.is24.guava.SequentialLoadingCache;
import de.is24.javaparser.Nodes;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import japa.parser.JavaParser;
import japa.parser.TokenMgrError;
import japa.parser.ast.CompilationUnit;
import japa.parser.ast.ImportDeclaration;
import japa.parser.ast.Node;
import japa.parser.ast.PackageDeclaration;
import japa.parser.ast.body.BodyDeclaration;
import japa.parser.ast.body.TypeDeclaration;
import javassist.CtClass;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static com.google.common.base.Optional.absent;
import static com.google.common.base.Optional.of;
import static com.google.common.base.Predicates.and;
import static com.google.common.base.Predicates.not;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.collect.Lists.newArrayList;
import static de.is24.deadcode4j.Utils.emptyIfNull;
import static de.is24.guava.NonNullFunctions.or;
import static de.is24.guava.NonNullFunctions.toFunction;
import static de.is24.javaparser.ImportDeclarations.isAsterisk;
import static de.is24.javaparser.ImportDeclarations.refersTo;
import static de.is24.javaparser.Nodes.getTypeName;
import static de.is24.javaparser.Nodes.prepend;
import static de.is24.javassist.CtClasses.*;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonList;
import static org.apache.commons.io.IOUtils.closeQuietly;
/**
* Serves as a base class with which to analyze java files.
*
* @since 2.0.0
*/
@SuppressWarnings("PMD.TooManyStaticImports")
public abstract class JavaFileAnalyzer extends AnalyzerAdapter {
private static final String JAVA_PARSER_KEY = JavaFileAnalyzer.class.getName() + ":JavaParser";
private static final NonNullFunction>>
JAVA_PARSER_SUPPLIER =
new NonNullFunction>>() {
@Nonnull
@Override
public LoadingCache> apply(@Nonnull final AnalysisContext analysisContext) {
return SequentialLoadingCache.createSingleValueCache(toFunction(new NonNullFunction>() {
@Nonnull
@Override
@SuppressFBWarnings(value = "DM_DEFAULT_ENCODING", justification = "The MavenProject does not provide the proper encoding")
public Optional apply(@Nonnull File file) {
Reader reader = null;
try {
reader = analysisContext.getModule().getEncoding() != null
? new InputStreamReader(new FileInputStream(file),
analysisContext.getModule().getEncoding())
: new FileReader(file);
return of(JavaParser.parse(reader, false));
} catch (TokenMgrError e) {
throw new RuntimeException("Failed to parse [" + file + "]!", e);
} catch (Exception e) {
throw new RuntimeException("Failed to parse [" + file + "]!", e);
} finally {
closeQuietly(reader);
}
}
}));
}
};
private static final String TYPE_RESOLVER_KEY = JavaFileAnalyzer.class.getName() + ":TypeResolver";
private static final NonNullFunction, Optional>>
TYPE_RESOLVER_SUPPLIER =
new NonNullFunction, Optional>>() {
@Nonnull
@Override
public NonNullFunction, Optional> apply(@Nonnull AnalysisContext analysisContext) {
final ClassPoolAccessor classPoolAccessor = ClassPoolAccessor.classPoolAccessorFor(analysisContext);
return new NonNullFunction, Optional>() {
@Nonnull
@Override
@SuppressWarnings("unchecked")
public Optional apply(@Nonnull Qualifier> input) {
return or(
new FullyQualifiedTypeResolver(classPoolAccessor),
new InnerTypeResolver(),
new InheritedTypeResolver(classPoolAccessor),
new ImportedTypeResolver(classPoolAccessor),
new PackageTypeResolver(classPoolAccessor),
new AsteriskImportedTypeResolver(classPoolAccessor),
new JavaLangTypeResolver(classPoolAccessor)
).apply(input);
}
};
}
};
private static LoadingCache> getJavaFileParser(AnalysisContext analysisContext) {
return analysisContext.getOrCreateCacheEntry(JAVA_PARSER_KEY, JAVA_PARSER_SUPPLIER);
}
private static NonNullFunction, Optional> getTypeResolver(AnalysisContext analysisContext) {
return analysisContext.getOrCreateCacheEntry(TYPE_RESOLVER_KEY, TYPE_RESOLVER_SUPPLIER);
}
/**
* Resolves a type reference by means of the given {@code Qualifier}.
*
* @see de.is24.deadcode4j.analyzer.JavaFileAnalyzer.Qualifier
* @since 2.0.0
*/
@Nonnull
protected static Optional resolveType(@Nonnull final AnalysisContext analysisContext, @Nonnull Qualifier qualifier) {
Optional resolvedClass = getTypeResolver(analysisContext).apply(qualifier);
if (!qualifier.allowsPartialResolving()
&& resolvedClass.isPresent()
&& !isFullyResolved(resolvedClass.get(), qualifier)) {
return absent();
}
return resolvedClass;
}
protected static boolean isFullyResolved(@Nonnull String resolvedClass, @Nonnull Qualifier qualifier) {
return resolvedClass.replace('$', '.').endsWith(qualifier.getFullQualifier().replace('$', '.'));
}
@Override
public final void doAnalysis(@Nonnull AnalysisContext analysisContext, @Nonnull File file) {
if (file.getName().endsWith(".java")) {
CompilationUnit compilationUnit = getJavaFileParser(analysisContext).getUnchecked(file).get();
logger.debug("Analyzing Java file [{}]...", file);
analyzeCompilationUnit(analysisContext, compilationUnit);
}
}
/**
* Perform an analysis for the specified java file.
* Results must be reported via the capabilities of the {@link AnalysisContext}.
*
* @since 2.0.0
*/
protected abstract void analyzeCompilationUnit(@Nonnull AnalysisContext analysisContext, @Nonnull CompilationUnit compilationUnit);
/**
* Subclasses of {@code Qualifier} are used to resolve types by providing an environment to analyze.
*
* @see #resolveType(de.is24.deadcode4j.AnalysisContext, de.is24.deadcode4j.analyzer.JavaFileAnalyzer.Qualifier)
* @since 2.0.0
*/
protected static abstract class Qualifier {
@Nonnull
private final T reference;
@Nonnull
private final String name;
@Nonnull
private final String fullQualifier;
@Nullable
private final Qualifier> parentQualifier;
@Nullable
private final Qualifier> scopeQualifier;
protected Qualifier(@Nonnull T reference, @Nullable Qualifier> parent) {
this.reference = reference;
this.parentQualifier = parent;
this.scopeQualifier = getScopeQualifier(reference);
this.name = getName(reference);
this.fullQualifier = getFullQualifier(reference);
}
protected Qualifier(@Nonnull T reference) {
this(reference, null);
}
/**
* Must return the name of the level/scope this qualifier represents.
*
* @since 2.0.0
*/
@Nonnull
protected abstract String getName(@Nonnull T reference);
/**
* Must return the full qualifier name of this level/scope and below.
*
* @since 2.0.0
*/
@Nonnull
protected abstract String getFullQualifier(@Nonnull T reference);
/**
* Must return the qualifier of the level/scope below.
*
* @since 2.0.0
*/
@Nullable
protected abstract Qualifier> getScopeQualifier(@Nonnull T reference);
/**
* Indicates if this qualifier can be resolved partially or must be resolved completely.
*
* @since 2.0.0
*/
protected abstract boolean allowsPartialResolving();
@Nonnull
protected final T getNode() {
return this.reference;
}
@Nonnull
protected final String getName() {
return this.name;
}
@Nonnull
protected final String getFullQualifier() {
return fullQualifier;
}
@Nullable
protected final Qualifier> getParentQualifier() {
return this.parentQualifier;
}
@Nullable
protected final Qualifier> getScopeQualifier() {
return this.scopeQualifier;
}
@Nonnull
protected final Qualifier> getFirstQualifier() {
for (Qualifier> currentScope = this, nextScope; ; ) {
nextScope = currentScope.getScopeQualifier();
if (nextScope == null) {
return currentScope;
}
currentScope = nextScope;
}
}
protected final boolean isSingleQualifier() {
return this == getFirstQualifier();
}
@Nonnull
protected final Iterable extends Qualifier> getTypeCandidates() {
if (!allowsPartialResolving()) {
return Collections.>singleton(this);
}
List> candidates = newArrayList();
for (Qualifier> loopQualifier = this; ; ) {
candidates.add(loopQualifier);
loopQualifier = loopQualifier.getScopeQualifier();
if (loopQualifier == null) {
return candidates;
}
}
}
/**
* This hook allows to further analyze an inherited type.
*
* @return the name of the class this qualifier refers to
* @since 2.0.0
*/
@Nonnull
protected Optional examineInheritedType(@Nonnull CtClass referencingClazz,
@Nonnull CtClass inheritedClazz) {
return absent();
}
}
private static abstract class RequiresClassPoolAccessor {
@Nonnull
protected final ClassPoolAccessor classPoolAccessor;
protected RequiresClassPoolAccessor(@Nonnull ClassPoolAccessor classPoolAccessor) {
this.classPoolAccessor = classPoolAccessor;
}
}
private static abstract class CandidatesResolver extends RequiresClassPoolAccessor
implements NonNullFunction, Optional> {
protected CandidatesResolver(@Nonnull ClassPoolAccessor classPoolAccessor) {
super(classPoolAccessor);
}
@Nonnull
protected Iterable calculatePrefixes(@Nonnull Qualifier> topQualifier) {
String prefix = calculatePrefix(topQualifier);
return prefix != null ? singletonList(prefix) : Collections.emptyList();
}
@Nullable
protected String calculatePrefix(@Nonnull Qualifier> topQualifier) {
return null;
}
protected boolean skipResolvingFor(@Nonnull Qualifier> candidate) {
return false;
}
@Nonnull
@Override
public final Optional apply(@Nonnull Qualifier> input) {
for (CharSequence prefix : calculatePrefixes(input)) {
for (Qualifier candidate : input.getTypeCandidates()) {
if (skipResolvingFor(candidate)) {
continue;
}
Optional resolvedClass = classPoolAccessor.resolveClass(prefix + candidate.getFullQualifier());
if (resolvedClass.isPresent()) {
return resolvedClass;
}
}
}
return absent();
}
}
private static class FullyQualifiedTypeResolver extends CandidatesResolver {
public FullyQualifiedTypeResolver(ClassPoolAccessor classPoolAccessor) {
super(classPoolAccessor);
}
@Nonnull
@Override
protected String calculatePrefix(@Nonnull Qualifier> topQualifier) {
return "";
}
@Override
protected boolean skipResolvingFor(@Nonnull Qualifier> candidate) {
return candidate.isSingleQualifier();
}
}
private static class InnerTypeResolver implements NonNullFunction, Optional> {
@Nonnull
@Override
public Optional apply(@Nonnull Qualifier> typeReference) {
Qualifier firstQualifier = typeReference.getFirstQualifier();
for (Node loopNode = typeReference.getNode(); ; ) {
Optional reference;
if (TypeDeclaration.class.isInstance(loopNode)) {
TypeDeclaration typeDeclaration = TypeDeclaration.class.cast(loopNode);
reference = resolveInnerReference(firstQualifier, singleton(typeDeclaration));
if (reference.isPresent()) {
return reference;
}
reference = resolveInnerReference(firstQualifier, typeDeclaration.getMembers());
if (reference.isPresent()) {
return reference;
}
} else if (CompilationUnit.class.isInstance(loopNode)) {
reference = resolveInnerReference(firstQualifier, CompilationUnit.class.cast(loopNode).getTypes());
if (reference.isPresent())
return reference;
}
loopNode = loopNode.getParentNode();
if (loopNode == null) {
return absent();
}
}
}
@Nonnull
private Optional resolveInnerReference(
@Nonnull Qualifier firstQualifier,
@Nullable Iterable extends BodyDeclaration> bodyDeclarations) {
for (TypeDeclaration typeDeclaration : emptyIfNull(bodyDeclarations).filter(TypeDeclaration.class)) {
if (firstQualifier.getName().equals(typeDeclaration.getName())) {
return of(resolveReferencedType(firstQualifier, typeDeclaration));
}
}
return absent();
}
@Nonnull
private String resolveReferencedType(@Nonnull Qualifier qualifier, @Nonnull TypeDeclaration type) {
Qualifier parentQualifier = qualifier.getParentQualifier();
if (parentQualifier != null) {
for (TypeDeclaration innerType : emptyIfNull(type.getMembers()).filter(TypeDeclaration.class)) {
if (parentQualifier.getName().equals(innerType.getName())) {
return resolveReferencedType(parentQualifier, innerType);
}
}
}
return getTypeName(type);
}
}
private static class InheritedTypeResolver extends RequiresClassPoolAccessor
implements NonNullFunction, Optional> {
public InheritedTypeResolver(@Nonnull ClassPoolAccessor classPoolAccessor) {
super(classPoolAccessor);
}
@Nonnull
@Override
public Optional apply(@Nonnull Qualifier> typeReference) {
String typeName = getTypeName(typeReference.getNode());
CtClass clazz = getCtClass(classPoolAccessor.getClassPool(), typeName);
if (clazz == null) {
return absent();
}
Qualifier firstQualifier = typeReference.getFirstQualifier();
for (CtClass declaringClazz : getDeclaringClassesOf(clazz)) {
Optional inheritedType = resolveInheritedType(clazz, declaringClazz, firstQualifier);
if (inheritedType.isPresent()) {
return inheritedType;
}
}
return absent();
}
@Nonnull
private Optional resolveInheritedType(@Nonnull CtClass referencingClazz,
@Nonnull CtClass clazz,
@Nonnull Qualifier firstQualifier) {
@SuppressWarnings("unchecked")
Optional result = firstQualifier.examineInheritedType(referencingClazz, clazz);
if (result.isPresent()) {
return result;
}
result = checkNestedClasses(referencingClazz, getSuperclassOf(clazz), firstQualifier);
if (result.isPresent()) {
return result;
}
for (CtClass interfaceClazz : getInterfacesOf(clazz)) {
result = checkNestedClasses(referencingClazz, interfaceClazz, firstQualifier);
if (result.isPresent()) {
return result;
}
}
return absent();
}
@Nonnull
private Optional checkNestedClasses(@Nonnull CtClass referencingClazz,
@Nullable CtClass clazz,
@Nonnull Qualifier firstQualifier) {
if (clazz == null || isJavaLangObject(clazz)) {
return absent();
}
for (CtClass nestedClass : getNestedClassesOf(clazz)) {
if (nestedClass.getName().substring(clazz.getName().length() + 1).equals(firstQualifier.getName())) {
return resolveNestedType(firstQualifier, nestedClass);
}
}
return resolveInheritedType(referencingClazz, clazz, firstQualifier);
}
private Optional resolveNestedType(Qualifier qualifier, CtClass clazz) {
Qualifier parentQualifier = qualifier.getParentQualifier();
if (parentQualifier != null) {
for (CtClass nestedClass : getNestedClassesOf(clazz)) {
if (nestedClass.getName().substring(clazz.getName().length() + 1)
.equals(parentQualifier.getName())) {
return resolveNestedType(parentQualifier, nestedClass);
}
}
}
return of(clazz.getName());
}
}
private static class ImportedTypeResolver extends CandidatesResolver {
public ImportedTypeResolver(ClassPoolAccessor classPoolAccessor) {
super(classPoolAccessor);
}
@Nullable
@Override
protected String calculatePrefix(@Nonnull Qualifier> topQualifier) {
String firstQualifier = topQualifier.getFirstQualifier().getName();
CompilationUnit compilationUnit = Nodes.getCompilationUnit(topQualifier.getNode());
ImportDeclaration importDeclaration = getOnlyElement(emptyIfNull(compilationUnit.getImports()).filter(
and(not(isAsterisk()), refersTo(firstQualifier))), null);
if (importDeclaration == null) {
return null;
}
StringBuilder buffy = prepend(importDeclaration.getName(), new StringBuilder());
int beginIndex = buffy.length() - firstQualifier.length();
return beginIndex == 0 ? "" :
buffy.replace(beginIndex - 1, buffy.length(), importDeclaration.isStatic() ? "$" : ".").toString();
}
}
private static class PackageTypeResolver extends CandidatesResolver {
public PackageTypeResolver(@Nonnull ClassPoolAccessor classPoolAccessor) {
super(classPoolAccessor);
}
@Nonnull
@Override
protected String calculatePrefix(@Nonnull Qualifier> topQualifier) {
PackageDeclaration aPackage = Nodes.getCompilationUnit(topQualifier.getNode()).getPackage();
if (aPackage == null) {
return "";
}
return prepend(aPackage.getName(), new StringBuilder("")).append(".").toString();
}
}
private static class AsteriskImportedTypeResolver extends CandidatesResolver {
public AsteriskImportedTypeResolver(@Nonnull ClassPoolAccessor classPoolAccessor) {
super(classPoolAccessor);
}
@Nonnull
@Override
protected Iterable calculatePrefixes(@Nonnull Qualifier> topQualifier) {
ArrayList asteriskImports = newArrayList();
CompilationUnit compilationUnit = Nodes.getCompilationUnit(topQualifier.getNode());
for (ImportDeclaration importDeclaration :
emptyIfNull(compilationUnit.getImports()).filter(isAsterisk())) {
StringBuilder buffy = prepend(importDeclaration.getName(), new StringBuilder());
buffy.append(importDeclaration.isStatic() ? '$' : '.');
asteriskImports.add(buffy.toString());
}
return asteriskImports;
}
}
private static class JavaLangTypeResolver extends CandidatesResolver {
public JavaLangTypeResolver(@Nonnull ClassPoolAccessor classPoolAccessor) {
super(classPoolAccessor);
}
@Nonnull
@Override
protected String calculatePrefix(@Nonnull Qualifier> topQualifier) {
return "java.lang.";
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy