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.guava.NonNullFunction;
import de.is24.guava.SequentialLoadingCache;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.AttributeInfo;
import javassist.bytecode.annotation.Annotation;
import org.apache.commons.io.IOUtils;
import javax.annotation.Nonnull;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.util.Collections;
import java.util.List;
import static com.google.common.base.Optional.of;
import static com.google.common.collect.Lists.newArrayList;
import static de.is24.deadcode4j.analyzer.javassist.ClassPoolAccessor.classPoolAccessorFor;
import static de.is24.guava.NonNullFunctions.toFunction;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.util.Arrays.asList;
/**
* Serves as a base class with which to analyze byte code (classes).
*
* @see de.is24.deadcode4j.analyzer.javassist.ClassPoolAccessor
* @since 1.3
*/
public abstract class ByteCodeAnalyzer extends AnalyzerAdapter {
private static final NonNullFunction<AnalysisContext, LoadingCache<File, Optional<CtClass>>> SUPPLIER =
new NonNullFunction<AnalysisContext, LoadingCache<File, Optional<CtClass>>>() {
@Nonnull
@Override
public LoadingCache<File, Optional<CtClass>> apply(@Nonnull final AnalysisContext analysisContext) {
return SequentialLoadingCache.createSingleValueCache(toFunction(new NonNullFunction<File, Optional<CtClass>>() {
@Nonnull
@Override
public Optional<CtClass> apply(@Nonnull File file) {
FileInputStream in = null;
try {
in = new FileInputStream(file);
return of(classPoolAccessorFor(analysisContext).getClassPool().makeClass(in));
} catch (IOException e) {
throw new RuntimeException("Could not load class from [" + file + "]!", e);
} finally {
IOUtils.closeQuietly(in);
}
}
}));
}
};
/**
* Retrieves all annotations of a package/class and its members (if requested).
*
* @param clazz the <code>CtClass</code> to examine
* @param elementTypes indicates which annotations to retrieve
* @since 1.4
*/
@Nonnull
@SuppressWarnings("unchecked")
protected static Iterable<Annotation> getAnnotations(@Nonnull CtClass clazz, ElementType... elementTypes) {
List<ElementType> types = asList(elementTypes);
List<AttributeInfo> attributes = newArrayList();
if (clazz.getName().endsWith("package-info") && types.contains(ElementType.PACKAGE) ||
!clazz.getName().endsWith("package-info") && types.contains(ElementType.TYPE)) {
attributes.addAll(clazz.getClassFile2().getAttributes());
}
if (types.contains(METHOD)) {
for (CtMethod method : clazz.getDeclaredMethods()) {
attributes.addAll(method.getMethodInfo2().getAttributes());
}
}
if (types.contains(FIELD)) {
for (CtField field : clazz.getDeclaredFields()) {
attributes.addAll(field.getFieldInfo2().getAttributes());
}
}
List<Annotation> annotations = newArrayList();
for (AttributeInfo attribute : attributes) {
if (AnnotationsAttribute.class.isInstance(attribute)) {
Collections.addAll(annotations, AnnotationsAttribute.class.cast(attribute).getAnnotations());
}
}
return annotations;
}
private static LoadingCache<File, Optional<CtClass>> getClassLoader(AnalysisContext analysisContext) {
return analysisContext.getOrCreateCacheEntry(ByteCodeAnalyzer.class, SUPPLIER);
}
@Override
public final void doAnalysis(@Nonnull AnalysisContext analysisContext, @Nonnull File file) {
if (file.getName().endsWith(".class")) {
CtClass ctClass = getClassLoader(analysisContext).getUnchecked(file).get();
logger.debug("Analyzing class [{}]...", ctClass.getName());
analyzeClass(analysisContext, ctClass);
}
}
/**
* Perform an analysis for the specified class.
* Results must be reported via the capabilities of the {@link de.is24.deadcode4j.AnalysisContext}.
*
* @since 1.3
*/
protected abstract void analyzeClass(@Nonnull AnalysisContext analysisContext, @Nonnull CtClass clazz);
}