001: /*
002: * FindBugs - Find Bugs in Java programs
003: * Copyright (C) 2005, University of Maryland
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation; either
008: * version 2.1 of the License, or (at your option) any later version.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: *
015: * You should have received a copy of the GNU Lesser General Public
016: * License along with this library; if not, write to the Free Software
017: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: */
019:
020: package edu.umd.cs.findbugs.ba;
021:
022: import java.util.HashMap;
023: import java.util.HashSet;
024: import java.util.Map;
025: import java.util.Set;
026: import java.util.TreeSet;
027:
028: import org.apache.bcel.Repository;
029: import org.apache.bcel.classfile.JavaClass;
030: import org.apache.bcel.classfile.Method;
031:
032: import edu.umd.cs.findbugs.SystemProperties;
033: import edu.umd.cs.findbugs.annotations.CheckForNull;
034: import edu.umd.cs.findbugs.ba.ch.Subtypes;
035: import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
036: import edu.umd.cs.findbugs.classfile.DescriptorFactory;
037: import edu.umd.cs.findbugs.classfile.Global;
038: import edu.umd.cs.findbugs.util.MapCache;
039:
040: /**
041: * Database to keep track of annotated fields/methods/classes/etc.
042: * for a particular kind of annotation.
043: *
044: * @author William Pugh
045: */
046: public class AnnotationDatabase<AnnotationEnum extends AnnotationEnumeration<AnnotationEnum>> {
047: static final boolean DEBUG = SystemProperties
048: .getBoolean("annotations.debug");
049: public static final boolean IGNORE_BUILTIN_ANNOTATIONS = SystemProperties
050: .getBoolean("findbugs.ignoreBuiltinAnnotations");
051:
052: public static enum Target {
053: FIELD, METHOD, PARAMETER, @Deprecated
054: CLASS, ANY
055: };
056:
057: private static final String DEFAULT_ANNOTATION_ANNOTATION_CLASS = "DefaultAnnotation";
058:
059: private Map<Object, AnnotationEnum> directAnnotations = new HashMap<Object, AnnotationEnum>();
060:
061: private final Map<AnnotationDatabase.Target, Map<String, AnnotationEnum>> defaultAnnotation = new HashMap<AnnotationDatabase.Target, Map<String, AnnotationEnum>>();
062:
063: private Subtypes subtypes;
064:
065: public AnnotationDatabase() {
066: defaultAnnotation.put(Target.ANY,
067: new HashMap<String, AnnotationEnum>());
068: defaultAnnotation.put(Target.PARAMETER,
069: new HashMap<String, AnnotationEnum>());
070: defaultAnnotation.put(Target.METHOD,
071: new HashMap<String, AnnotationEnum>());
072: defaultAnnotation.put(Target.FIELD,
073: new HashMap<String, AnnotationEnum>());
074: subtypes = AnalysisContext.currentAnalysisContext()
075: .getSubtypes();
076:
077: }
078:
079: public void loadAuxiliaryAnnotations() {
080:
081: }
082:
083: private final Set<AnnotationEnum> seen = new HashSet<AnnotationEnum>();
084:
085: public void addDirectAnnotation(Object o, AnnotationEnum n) {
086: directAnnotations.put(o, n);
087: seen.add(n);
088: }
089:
090: public void addDefaultAnnotation(Target target, String c,
091: AnnotationEnum n) {
092: if (!defaultAnnotation.containsKey(target))
093: return;
094: if (DEBUG)
095: System.out.println("Default annotation " + target + " " + c
096: + " " + n);
097: defaultAnnotation.get(target).put(c, n);
098: seen.add(n);
099: }
100:
101: public boolean anyAnnotations(AnnotationEnum n) {
102: return seen.contains(n);
103: }
104:
105: // TODO: Parameterize these values?
106: Map<Object, AnnotationEnum> cachedMinimal = new MapCache<Object, AnnotationEnum>(
107: 20000);
108: Map<Object, AnnotationEnum> cachedMaximal = new MapCache<Object, AnnotationEnum>(
109: 20000);
110:
111: @CheckForNull
112: public AnnotationEnum getResolvedAnnotation(Object o,
113: boolean getMinimal) {
114: Map<Object, AnnotationEnum> cache;
115: if (getMinimal)
116: cache = cachedMinimal;
117: else
118: cache = cachedMaximal;
119:
120: if (cache.containsKey(o)) {
121: return cache.get(o);
122: }
123: AnnotationEnum n = getUncachedResolvedAnnotation(o, getMinimal);
124: if (DEBUG)
125: System.out.println("TTT: " + o + " " + n);
126: cache.put(o, n);
127: return n;
128: }
129:
130: public boolean annotationIsDirect(Object o) {
131: return directAnnotations.containsKey(o);
132: }
133:
134: @CheckForNull
135: public AnnotationEnum getUncachedResolvedAnnotation(final Object o,
136: boolean getMinimal) {
137:
138: AnnotationEnum n = directAnnotations.get(o);
139: if (n != null)
140: return n;
141:
142: try {
143:
144: String className;
145: Target kind;
146: boolean isParameterToInitMethodofAnonymousInnerClass = false;
147: boolean isSyntheticMethod = false;
148: if (o instanceof XMethod || o instanceof XMethodParameter) {
149:
150: XMethod m;
151: if (o instanceof XMethod) {
152: m = (XMethod) o;
153: isSyntheticMethod = m.isSynthetic();
154: kind = Target.METHOD;
155: className = m.getClassName();
156: } else if (o instanceof XMethodParameter) {
157: m = ((XMethodParameter) o).getMethod();
158: // Don't
159: isSyntheticMethod = m.isSynthetic();
160: className = m.getClassName();
161: kind = Target.PARAMETER;
162: if (m.getName().equals("<init>")) {
163: int i = className.lastIndexOf("$");
164: if (i + 1 < className.length()
165: && Character.isDigit(className
166: .charAt(i + 1)))
167: isParameterToInitMethodofAnonymousInnerClass = true;
168: }
169: } else
170: throw new IllegalStateException("impossible");
171:
172: if (!m.isStatic() && !m.getName().equals("<init>")) {
173: JavaClass c = Repository.lookupClass(className);
174: // get inherited annotation
175: TreeSet<AnnotationEnum> inheritedAnnotations = new TreeSet<AnnotationEnum>();
176: if (c.getSuperclassNameIndex() > 0) {
177:
178: n = lookInOverriddenMethod(o, c
179: .getSuperclassName(), m, getMinimal);
180: if (n != null)
181: inheritedAnnotations.add(n);
182: }
183: for (String implementedInterface : c
184: .getInterfaceNames()) {
185: n = lookInOverriddenMethod(o,
186: implementedInterface, m, getMinimal);
187: if (n != null)
188: inheritedAnnotations.add(n);
189: }
190: if (DEBUG)
191: System.out
192: .println("# of inherited annotations : "
193: + inheritedAnnotations.size());
194: if (!inheritedAnnotations.isEmpty()) {
195: if (inheritedAnnotations.size() == 1)
196: return inheritedAnnotations.first();
197: if (!getMinimal)
198: return inheritedAnnotations.last();
199:
200: AnnotationEnum min = inheritedAnnotations
201: .first();
202: if (min.getIndex() == 0) {
203: inheritedAnnotations.remove(min);
204: min = inheritedAnnotations.first();
205: }
206: return min;
207: }
208: // check to see if method is defined in this class;
209: // if not, on't consider default annotations
210: if (!classDefinesMethod(c, m))
211: return null;
212: if (DEBUG)
213: System.out
214: .println("looking for default annotations: "
215: + c.getClassName()
216: + " defines " + m);
217: } // if not static
218: } // associated with method
219: else if (o instanceof XField) {
220:
221: className = ((XField) o).getClassName();
222: kind = Target.FIELD;
223: } else if (o instanceof String) {
224: assert false;
225: className = (String) o;
226: kind = Target.CLASS;
227: } else
228: throw new IllegalArgumentException(
229: "Can't look up annotation for "
230: + o.getClass().getName());
231:
232: // <init> method parameters for inner classes don't inherit default annotations
233: // since some of them are synthetic
234: if (isParameterToInitMethodofAnonymousInnerClass)
235: return null;
236:
237: // synthetic elements should not inherit default annotations
238: if (isSyntheticMethod)
239: return null;
240: try {
241: XClass c = Global
242: .getAnalysisCache()
243: .getClassAnalysis(
244: XClass.class,
245: DescriptorFactory
246: .createClassDescriptorFromDottedClassName(className));
247:
248: if (c != null && c.isSynthetic())
249: return null;
250: } catch (CheckedAnalysisException e) {
251: assert true;
252: }
253:
254: // look for default annotation
255: n = defaultAnnotation.get(kind).get(className);
256: if (DEBUG)
257: System.out.println("Default annotation for " + kind
258: + " is " + n);
259: if (n != null)
260: return n;
261:
262: n = defaultAnnotation.get(Target.ANY).get(className);
263: if (DEBUG)
264: System.out
265: .println("Default annotation for any is " + n);
266: if (n != null)
267: return n;
268:
269: int p = className.lastIndexOf('.');
270: className = className.substring(0, p + 1) + "package-info";
271: n = defaultAnnotation.get(kind).get(className);
272: if (DEBUG)
273: System.out.println("Default annotation for " + kind
274: + " is " + n);
275: if (n != null)
276: return n;
277:
278: n = defaultAnnotation.get(Target.ANY).get(className);
279: if (DEBUG)
280: System.out
281: .println("Default annotation for any is " + n);
282: if (n != null)
283: return n;
284:
285: return n;
286: } catch (ClassNotFoundException e) {
287: AnalysisContext.reportMissingClass(e);
288: return null;
289: }
290:
291: }
292:
293: private boolean classDefinesMethod(JavaClass c, XMethod m) {
294: for (Method definedMethod : c.getMethods())
295: if (definedMethod.getName().equals(m.getName())
296: && definedMethod.getSignature().equals(
297: m.getSignature())
298: && definedMethod.isStatic() == m.isStatic())
299: return true;
300: return false;
301: }
302:
303: private AnnotationEnum lookInOverriddenMethod(
304: final Object originalQuery, String classToLookIn,
305: XMethod originalMethod, boolean getMinimal) {
306: try {
307: AnnotationEnum n;
308: // Look in supermethod
309: XMethod super Method = XFactory.createXMethod(classToLookIn,
310: originalMethod.getName(), originalMethod
311: .getSignature(), originalMethod.isStatic());
312: if (!super Method.isResolved())
313: return null;
314: if (DEBUG)
315: System.out.println("Looking for overridden method "
316: + super Method);
317:
318: Object probe;
319: if (originalQuery instanceof XMethod)
320: probe = super Method;
321: else if (originalQuery instanceof XMethodParameter)
322: probe = new XMethodParameter(super Method,
323: ((XMethodParameter) originalQuery)
324: .getParameterNumber());
325: else
326: throw new IllegalStateException("impossible");
327:
328: n = getResolvedAnnotation(probe, getMinimal);
329: return n;
330: } catch (RuntimeException e) {
331: AnalysisContext
332: .logError(
333: "Exception while looking for annotation of "
334: + originalMethod + "in "
335: + classToLookIn, e);
336: return null;
337: }
338: }
339:
340: boolean addClassOnly = false;
341:
342: public boolean setAddClassOnly(boolean newValue) {
343: boolean oldValue = addClassOnly;
344: addClassOnly = newValue;
345: return oldValue;
346: }
347:
348: protected void addDefaultMethodAnnotation(String cName,
349: AnnotationEnum annotation) {
350: subtypes.addNamedClass(cName);
351:
352: if (addClassOnly)
353: return;
354:
355: addDefaultAnnotation(AnnotationDatabase.Target.METHOD, cName,
356: annotation);
357:
358: }
359:
360: protected void addFieldAnnotation(String cName, String mName,
361: String mSig, boolean isStatic, AnnotationEnum annotation) {
362: subtypes.addNamedClass(cName);
363: if (addClassOnly)
364: return;
365: XField m = XFactory.createXField(cName, mName, mSig, isStatic);
366: addDirectAnnotation(m, annotation);
367: }
368:
369: protected void addMethodAnnotation(String cName, String mName,
370: String mSig, boolean isStatic, AnnotationEnum annotation) {
371: subtypes.addNamedClass(cName);
372: if (addClassOnly)
373: return;
374: XMethod m = XFactory
375: .createXMethod(cName, mName, mSig, isStatic);
376: addDirectAnnotation(m, annotation);
377: }
378:
379: private boolean onlyAppliesToReferenceParameters(
380: AnnotationEnum annotation) {
381: // return annotation instanceof NullnessAnnotation; work around JDK bug
382: return true;
383: }
384:
385: protected void addMethodParameterAnnotation(String cName,
386: String mName, String mSig, boolean isStatic, int param,
387: AnnotationEnum annotation) {
388: subtypes.addNamedClass(cName);
389: if (addClassOnly)
390: return;
391: SignatureParser parser = new SignatureParser(mSig);
392: if (param < 0 || param >= parser.getNumParameters())
393: throw new IllegalArgumentException(
394: "can't annotation parameter #" + param + " of "
395: + cName + "." + mName + mSig);
396: String signature = parser.getParameter(param);
397: char firstChar = signature.charAt(0);
398: boolean isReference = firstChar == 'L' || firstChar == '[';
399: if (onlyAppliesToReferenceParameters(annotation)
400: && !isReference) {
401: AnalysisContext.logError("Can't apply " + annotation
402: + " to parameter " + param + " with signature "
403: + signature + " of " + cName + "." + mName + " : "
404: + mSig);
405: return;
406: }
407: XMethod m = XFactory
408: .createXMethod(cName, mName, mSig, isStatic);
409: addDirectAnnotation(new XMethodParameter(m, param), annotation);
410: }
411: }
|