/*
* FindBugs - Find Bugs in Java programs
* Copyright (C) 2005, University of Maryland
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package edu.umd.cs.findbugs.ba;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import javax.annotation.CheckForNull;
import org.apache.bcel.Repository;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
import edu.umd.cs.findbugs.classfile.ClassDescriptor;
import edu.umd.cs.findbugs.classfile.DescriptorFactory;
import edu.umd.cs.findbugs.classfile.Global;
import edu.umd.cs.findbugs.internalAnnotations.DottedClassName;
import edu.umd.cs.findbugs.util.MapCache;
/**
* Database to keep track of annotated fields/methods/classes/etc. for a
* particular kind of annotation.
*
* @author William Pugh
*/
public class AnnotationDatabase<AnnotationEnum extends AnnotationEnumeration<AnnotationEnum>> {
static final boolean DEBUG = SystemProperties.getBoolean("annotations.debug");
public static final boolean IGNORE_BUILTIN_ANNOTATIONS = SystemProperties.getBoolean("findbugs.ignoreBuiltinAnnotations");
public static enum Target {
FIELD, METHOD, PARAMETER, @Deprecated
CLASS, ANY
}
private static final String DEFAULT_ANNOTATION_ANNOTATION_CLASS = "DefaultAnnotation";
private final Map<Object, AnnotationEnum> directAnnotations = new HashMap<Object, AnnotationEnum>();
private final Map<AnnotationDatabase.Target, Map<String, AnnotationEnum>> defaultAnnotation = new EnumMap<AnnotationDatabase.Target, Map<String, AnnotationEnum>>(AnnotationDatabase.Target.class);
// private Subtypes subtypes;
public AnnotationDatabase() {
defaultAnnotation.put(Target.ANY, new HashMap<String, AnnotationEnum>());
defaultAnnotation.put(Target.PARAMETER, new HashMap<String, AnnotationEnum>());
defaultAnnotation.put(Target.METHOD, new HashMap<String, AnnotationEnum>());
defaultAnnotation.put(Target.FIELD, new HashMap<String, AnnotationEnum>());
// if (!Subtypes.DO_NOT_USE) {
// subtypes = AnalysisContext.currentAnalysisContext().getSubtypes();
// }
}
public void loadAuxiliaryAnnotations() {
}
private final Set<AnnotationEnum> seen = new HashSet<AnnotationEnum>();
public void addDirectAnnotation(Object o, AnnotationEnum n) {
directAnnotations.put(o, n);
seen.add(n);
}
public void addDefaultAnnotation(Target target, String c, AnnotationEnum n) {
if (!defaultAnnotation.containsKey(target))
return;
if (DEBUG)
System.out.println("Default annotation " + target + " " + c + " " + n);
defaultAnnotation.get(target).put(c, n);
seen.add(n);
}
public boolean anyAnnotations(AnnotationEnum n) {
return seen.contains(n);
}
// TODO: Parameterize these values?
Map<Object, AnnotationEnum> cachedMinimal = new MapCache<Object, AnnotationEnum>(20000);
Map<Object, AnnotationEnum> cachedMaximal = new MapCache<Object, AnnotationEnum>(20000);
@CheckForNull
public AnnotationEnum getResolvedAnnotation(Object o, boolean getMinimal) {
if (o instanceof XMethod) {
XMethod m = (XMethod) o;
if (m.getName().startsWith("access$")) {
InnerClassAccessMap icam = AnalysisContext.currentAnalysisContext().getInnerClassAccessMap();
try {
InnerClassAccess ica = icam.getInnerClassAccess(m.getClassName(), m.getName());
if (ica != null && ica.isLoad()) {
o = ica.getField();
}
} catch (ClassNotFoundException e) {
AnalysisContext.reportMissingClass(e);
return null;
}
}
}
Map<Object, AnnotationEnum> cache;
if (getMinimal)
cache = cachedMinimal;
else
cache = cachedMaximal;
if (cache.containsKey(o)) {
return cache.get(o);
}
AnnotationEnum n = getUncachedResolvedAnnotation(o, getMinimal);
if (DEBUG)
System.out.println("TTT: " + o + " " + n);
cache.put(o, n);
return n;
}
public boolean annotationIsDirect(Object o) {
return directAnnotations.containsKey(o);
}
@CheckForNull
public AnnotationEnum getUncachedResolvedAnnotation(final Object o, boolean getMinimal) {
AnnotationEnum n = getDirectAnnotation(o);
if (n != null)
return n;
try {
String className;
Target kind;
boolean isParameterToInitMethodofAnonymousInnerClass = false;
boolean isSyntheticMethod = false;
if (o instanceof XMethod || o instanceof XMethodParameter) {
XMethod m;
if (o instanceof XMethod) {
m = (XMethod) o;
isSyntheticMethod = m.isSynthetic();
kind = Target.METHOD;
className = m.getClassName();
} else if (o instanceof XMethodParameter) {
m = ((XMethodParameter) o).getMethod();
// Don't
isSyntheticMethod = m.isSynthetic();
className = m.getClassName();
kind = Target.PARAMETER;
if (m.getName().equals("<init>")) {
int i = className.lastIndexOf("$");
if (i + 1 < className.length() && Character.isDigit(className.charAt(i + 1)))
isParameterToInitMethodofAnonymousInnerClass = true;
}
} else
throw new IllegalStateException("impossible");
if (!m.isStatic() && !m.getName().equals("<init>")) {
JavaClass c = Repository.lookupClass(className);
// get inherited annotation
TreeSet<AnnotationEnum> inheritedAnnotations = new TreeSet<AnnotationEnum>();
if (c.getSuperclassNameIndex() > 0) {
n = lookInOverriddenMethod(o, c.getSuperclassName(), m, getMinimal);
if (n != null)
inheritedAnnotations.add(n);
}
for (String implementedInterface : c.getInterfaceNames()) {
n = lookInOverriddenMethod(o, implementedInterface, m, getMinimal);
if (n != null)
inheritedAnnotations.add(n);
}
if (DEBUG)
System.out.println("# of inherited annotations : " + inheritedAnnotations.size());
if (!inheritedAnnotations.isEmpty()) {
if (inheritedAnnotations.size() == 1)
return inheritedAnnotations.first();
if (!getMinimal)
return inheritedAnnotations.last();
AnnotationEnum min = inheritedAnnotations.first();
if (min.getIndex() == 0) {
inheritedAnnotations.remove(min);
min = inheritedAnnotations.first();
}
return min;
}
// check to see if method is defined in this class;
// if not, on't consider default annotations
if (!classDefinesMethod(c, m))
return null;
if (DEBUG)
System.out.println("looking for default annotations: " + c.getClassName() + " defines " + m);
} // if not static
} // associated with method
else if (o instanceof XField) {
className = ((XField) o).getClassName();
kind = Target.FIELD;
} else if (o instanceof String) {
assert false;
className = (String) o;
kind = Target.CLASS;
} else
throw new IllegalArgumentException("Can't look up annotation for " + o.getClass().getName());
// <init> method parameters for inner classes don't inherit default
// annotations
// since some of them are synthetic
if (isParameterToInitMethodofAnonymousInnerClass)
return null;
// synthetic elements should not inherit default annotations
if (isSyntheticMethod)
return null;
try {
XClass c = Global.getAnalysisCache().getClassAnalysis(XClass.class,
DescriptorFactory.createClassDescriptorFromDottedClassName(className));
if (c != null && c.isSynthetic())
return null;
} catch (CheckedAnalysisException e) {
assert true;
}
// look for default annotation
n = defaultAnnotation.get(kind).get(className);
if (DEBUG)
System.out.println("Default annotation for " + kind + " is " + n);
if (n != null)
return n;
n = defaultAnnotation.get(Target.ANY).get(className);
if (DEBUG)
System.out.println("Default annotation for any is " + n);
if (n != null)
return n;
int p = className.lastIndexOf('.');
className = className.substring(0, p + 1) + "package-info";
n = defaultAnnotation.get(kind).get(className);
if (DEBUG)
System.out.println("Default annotation for " + kind + " is " + n);
if (n != null)
return n;
n = defaultAnnotation.get(Target.ANY).get(className);
if (DEBUG)
System.out.println("Default annotation for any is " + n);
if (n != null)
return n;
return n;
} catch (ClassNotFoundException e) {
AnalysisContext.reportMissingClass(e);
return null;
}
}
/**
* @param o
* @return
*/
public AnnotationEnum getDirectAnnotation(final Object o) {
return directAnnotations.get(o);
}
private boolean classDefinesMethod(JavaClass c, XMethod m) {
for (Method definedMethod : c.getMethods())
if (definedMethod.getName().equals(m.getName()) && definedMethod.getSignature().equals(m.getSignature())
&& definedMethod.isStatic() == m.isStatic())
return true;
return false;
}
private AnnotationEnum lookInOverriddenMethod(final Object originalQuery, String classToLookIn, XMethod originalMethod,
boolean getMinimal) {
try {
AnnotationEnum n;
// Look in supermethod
XMethod superMethod = XFactory.createXMethod(classToLookIn, originalMethod.getName(), originalMethod.getSignature(),
originalMethod.isStatic());
if (!superMethod.isResolved())
return null;
if (DEBUG)
System.out.println("Looking for overridden method " + superMethod);
Object probe;
if (originalQuery instanceof XMethod)
probe = superMethod;
else if (originalQuery instanceof XMethodParameter)
probe = new XMethodParameter(superMethod, ((XMethodParameter) originalQuery).getParameterNumber());
else
throw new IllegalStateException("impossible");
n = getResolvedAnnotation(probe, getMinimal);
return n;
} catch (RuntimeException e) {
AnalysisContext.logError("Exception while looking for annotation of " + originalMethod + "in " + classToLookIn, e);
return null;
}
}
boolean addClassOnly = false;
public boolean setAddClassOnly(boolean newValue) {
boolean oldValue = addClassOnly;
addClassOnly = newValue;
return oldValue;
}
protected void addDefaultMethodAnnotation(String cName, AnnotationEnum annotation) {
// if (!Subtypes.DO_NOT_USE) {
// subtypes.addNamedClass(cName);
// }
if (addClassOnly)
return;
addDefaultAnnotation(AnnotationDatabase.Target.METHOD, cName, annotation);
}
protected void addFieldAnnotation(String cName, String mName, String mSig, boolean isStatic, AnnotationEnum annotation) {
// if (!Subtypes.DO_NOT_USE) {
// subtypes.addNamedClass(cName);
// }
if (addClassOnly)
return;
XField m = XFactory.createXField(cName, mName, mSig, isStatic);
addDirectAnnotation(m, annotation);
}
protected void addMethodAnnotation(Class<?> clazz, String mName, String mSig, boolean isStatic,
AnnotationEnum annotation) {
addMethodAnnotation(clazz.getName(), mName, mSig, isStatic, annotation);
}
protected void addMethodAnnotation(@DottedClassName String cName, String mName, String mSig, boolean isStatic,
AnnotationEnum annotation) {
if (addClassOnly)
return;
XMethod m = XFactory.createXMethod(cName, mName, mSig, isStatic);
if (!m.getClassName().equals(cName))
return;
if (false && !m.isResolved()) {
System.out.println("Unable to add annotation " + annotation + " to " + m);
ClassDescriptor c = DescriptorFactory.createClassDescriptorFromDottedClassName(cName);
if (true)
try {
XClass xClass = Global.getAnalysisCache().getClassAnalysis(XClass.class, c);
if (xClass != null) {
System.out.println("class has methods: ");
for (XMethod m2 : xClass.getXMethods())
System.out.println(" " + m2);
}
} catch (CheckedAnalysisException e) {
e.printStackTrace();
}
}
addDirectAnnotation(m, annotation);
}
private boolean onlyAppliesToReferenceParameters(AnnotationEnum annotation) {
// return annotation instanceof NullnessAnnotation; work around JDK bug
return true;
}
protected void addMethodParameterAnnotation(String cName, String mName, String mSig, boolean isStatic, int param,
AnnotationEnum annotation) {
// if (!Subtypes.DO_NOT_USE) {
// subtypes.addNamedClass(cName);
// }
if (addClassOnly)
return;
SignatureParser parser = new SignatureParser(mSig);
if (param < 0 || param >= parser.getNumParameters())
throw new IllegalArgumentException("can't annotation parameter #" + param + " of " + cName + "." + mName + mSig);
String signature = parser.getParameter(param);
char firstChar = signature.charAt(0);
boolean isReference = firstChar == 'L' || firstChar == '[';
if (onlyAppliesToReferenceParameters(annotation) && !isReference) {
AnalysisContext.logError("Can't apply " + annotation + " to parameter " + param + " with signature " + signature
+ " of " + cName + "." + mName + " : " + mSig);
return;
}
XMethod m = XFactory.createXMethod(cName, mName, mSig, isStatic);
addDirectAnnotation(new XMethodParameter(m, param), annotation);
}
}