001: /*
002: * FindBugs - Find bugs in Java programs
003: * Copyright (C) 2003-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;
021:
022: import java.io.IOException;
023:
024: import org.apache.bcel.Constants;
025:
026: import edu.umd.cs.findbugs.ba.SignatureConverter;
027: import edu.umd.cs.findbugs.ba.XFactory;
028: import edu.umd.cs.findbugs.ba.XMethod;
029: import edu.umd.cs.findbugs.classfile.MethodDescriptor;
030: import edu.umd.cs.findbugs.internalAnnotations.DottedClassName;
031: import edu.umd.cs.findbugs.util.ClassName;
032: import edu.umd.cs.findbugs.visitclass.DismantleBytecode;
033: import edu.umd.cs.findbugs.visitclass.PreorderVisitor;
034: import edu.umd.cs.findbugs.xml.XMLAttributeList;
035: import edu.umd.cs.findbugs.xml.XMLOutput;
036:
037: /**
038: * A BugAnnotation specifying a particular method in a particular class.
039: * A MethodAnnotation may (optionally) have a SourceLineAnnotation directly
040: * embedded inside it to indicate the range of source lines where the
041: * method is defined.
042: *
043: * @author David Hovemeyer
044: * @see BugAnnotation
045: */
046: public class MethodAnnotation extends PackageMemberAnnotation {
047: private static final long serialVersionUID = 1L;
048:
049: private static final boolean UGLY_METHODS = SystemProperties
050: .getBoolean("ma.ugly");
051:
052: private static final String DEFAULT_ROLE = "METHOD_DEFAULT";
053:
054: private String methodName;
055: private String methodSig;
056: private String fullMethod;
057: private boolean isStatic;
058:
059: public static final String METHOD_DANGEROUS_TARGET_ACTUAL_GUARANTEED_NULL = "METHOD_DANGEROUS_TARGET_ACTUAL_GUARANTEED_NULL";
060:
061: public static final String METHOD_DANGEROUS_TARGET = "METHOD_DANGEROUS_TARGET";
062:
063: public static final String METHOD_SAFE_TARGET = "METHOD_SAFE_TARGET";
064:
065: /**
066: * Constructor.
067: *
068: * @param className the name of the class containing the method
069: * @param methodName the name of the method
070: * @param methodSig the Java type signature of the method
071: * @param isStatic true if the method is static, false if not
072: */
073: public MethodAnnotation(String className, String methodName,
074: String methodSig, boolean isStatic) {
075: super (className, DEFAULT_ROLE);
076: this .methodName = methodName;
077: if (methodSig.indexOf(".") >= 0) {
078: assert false : "signatures should not be dotted: "
079: + methodSig;
080: methodSig = methodSig.replace('.', '/');
081: }
082: this .methodSig = methodSig;
083: this .isStatic = isStatic;
084: fullMethod = null;
085: sourceLines = null;
086: }
087:
088: /**
089: * Factory method to create a MethodAnnotation from the method the
090: * given visitor is currently visiting.
091: *
092: * @param visitor the BetterVisitor currently visiting the method
093: */
094: public static MethodAnnotation fromVisitedMethod(
095: PreorderVisitor visitor) {
096: String className = visitor.getDottedClassName();
097: MethodAnnotation result = new MethodAnnotation(className,
098: visitor.getMethodName(), visitor.getMethodSig(),
099: visitor.getMethod().isStatic());
100:
101: // Try to find the source lines for the method
102: SourceLineAnnotation srcLines = SourceLineAnnotation
103: .fromVisitedMethod(visitor);
104: result.setSourceLines(srcLines);
105:
106: return result;
107: }
108:
109: /**
110: * Factory method to create a MethodAnnotation from a method
111: * called by the instruction the given visitor is currently visiting.
112: *
113: * @param visitor the visitor
114: * @return the MethodAnnotation representing the called method
115: */
116: public static MethodAnnotation fromCalledMethod(
117: DismantleBytecode visitor) {
118: String className = visitor.getDottedClassConstantOperand();
119: String methodName = visitor.getNameConstantOperand();
120: String methodSig = visitor.getSigConstantOperand();
121:
122: return fromCalledMethod(className, methodName, methodSig,
123: visitor.getOpcode() == Constants.INVOKESTATIC);
124: }
125:
126: /**
127: * Factory method to create the MethodAnnotation from
128: * the classname, method name, signature, etc.
129: * The method tries to look up source line information for
130: * the method.
131: *
132: * @param className name of the class containing the method
133: * @param methodName name of the method
134: * @param methodSig signature of the method
135: * @param isStatic true if the method is static, false otherwise
136: * @return the MethodAnnotation
137: */
138: public static MethodAnnotation fromForeignMethod(String className,
139: String methodName, String methodSig, int accessFlags) {
140:
141: // FIXME: would be nice to do this without using BCEL
142:
143: className = ClassName.toDottedClassName(className);
144:
145: // Create MethodAnnotation.
146: // It won't have source lines yet.
147: MethodAnnotation methodAnnotation = new MethodAnnotation(
148: className, methodName, methodSig,
149: (accessFlags & Constants.ACC_STATIC) != 0);
150:
151: SourceLineAnnotation sourceLines = SourceLineAnnotation
152: .getSourceAnnotationForMethod(className, methodName,
153: methodSig);
154:
155: methodAnnotation.setSourceLines(sourceLines);
156:
157: return methodAnnotation;
158: }
159:
160: /**
161: * Factory method to create the MethodAnnotation from
162: * the classname, method name, signature, etc.
163: * The method tries to look up source line information for
164: * the method.
165: *
166: * @param className name of the class containing the method
167: * @param methodName name of the method
168: * @param methodSig signature of the method
169: * @param isStatic true if the method is static, false otherwise
170: * @return the MethodAnnotation
171: */
172: public static MethodAnnotation fromForeignMethod(String className,
173: String methodName, String methodSig, boolean isStatic) {
174:
175: // FIXME: would be nice to do this without using BCEL
176:
177: className = ClassName.toDottedClassName(className);
178:
179: // Create MethodAnnotation.
180: // It won't have source lines yet.
181: MethodAnnotation methodAnnotation = new MethodAnnotation(
182: className, methodName, methodSig, isStatic);
183:
184: SourceLineAnnotation sourceLines = SourceLineAnnotation
185: .getSourceAnnotationForMethod(className, methodName,
186: methodSig);
187:
188: methodAnnotation.setSourceLines(sourceLines);
189:
190: return methodAnnotation;
191: }
192:
193: /**
194: * Create a MethodAnnotation from a method that is not
195: * directly accessible. We will use the repository to
196: * try to find its class in order to populate the information
197: * as fully as possible.
198: *
199: * @param className class containing called method
200: * @param methodName name of called method
201: * @param methodSig signature of called method
202: * @param isStatic true if called method is static
203: * @return the MethodAnnotation for the called method
204: */
205: public static MethodAnnotation fromCalledMethod(String className,
206: String methodName, String methodSig, boolean isStatic) {
207:
208: MethodAnnotation methodAnnotation = fromForeignMethod(
209: className, methodName, methodSig, isStatic);
210: methodAnnotation.setDescription("METHOD_CALLED");
211: return methodAnnotation;
212:
213: }
214:
215: /**
216: * Create a MethodAnnotation from an XMethod.
217: *
218: * @param xmethod the XMethod
219: * @return the MethodAnnotation
220: */
221: public static MethodAnnotation fromXMethod(XMethod xmethod) {
222: return fromForeignMethod(xmethod.getClassName(), xmethod
223: .getName(), xmethod.getSignature(), xmethod.isStatic());
224: }
225:
226: /**
227: * Create a MethodAnnotation from a MethodDescriptor.
228: *
229: * @param methodDescriptor the MethodDescriptor
230: * @return the MethodAnnotation
231: */
232: public static BugAnnotation fromMethodDescriptor(
233: MethodDescriptor methodDescriptor) {
234: return fromForeignMethod(
235: methodDescriptor.getSlashedClassName(),
236: methodDescriptor.getName(), methodDescriptor
237: .getSignature(), methodDescriptor.isStatic());
238: }
239:
240: /**
241: * Get the method name.
242: */
243: public String getMethodName() {
244: return methodName;
245: }
246:
247: public String getJavaSourceMethodName() {
248: if (methodName.equals("<clinit>"))
249: return "<static initializer>";
250: if (methodName.equals("<init>")) {
251: String result = getClassName();
252: int pos = Math.max(result.lastIndexOf('$'), result
253: .lastIndexOf('.'));
254: return className.substring(pos + 1);
255: }
256: return methodName;
257: }
258:
259: /**
260: * Get the method type signature.
261: */
262: public String getMethodSignature() {
263: return methodSig;
264: }
265:
266: /**
267: * Return whether or not the method is static.
268: *
269: * @return true if the method is static, false otherwise
270: */
271: public boolean isStatic() {
272: return isStatic;
273: }
274:
275: /**
276: * Convert to an XMethod.
277: *
278: * @return an XMethod specifying the same method as this MethodAnnotation
279: */
280: public XMethod toXMethod() {
281: return XFactory.createXMethod(className, methodName, methodSig,
282: isStatic);
283: }
284:
285: public void accept(BugAnnotationVisitor visitor) {
286: visitor.visitMethodAnnotation(this );
287: }
288:
289: @Override
290: protected String formatPackageMember(String key,
291: ClassAnnotation primaryClass) {
292: if (key.equals(""))
293: return UGLY_METHODS ? getUglyMethod()
294: : getFullMethod(primaryClass);
295: else if (key.equals("givenClass")) {
296: if (methodName.equals("<init>")) {
297: return "new "
298: + shorten(primaryClass.getPackageName(),
299: className)
300: + getSignatureInClass(primaryClass);
301: }
302: if (className.equals(primaryClass.getClassName()))
303: return getNameInClass(primaryClass);
304: else
305: return shorten(primaryClass.getPackageName(), className)
306: + "." + getNameInClass(primaryClass);
307: } else if (key.equals("nameAndSignature")) {
308: return getNameInClass(primaryClass);
309: } else if (key.equals("shortMethod"))
310: return className + "." + methodName + "(...)";
311: else if (key.equals("hash")) {
312: String tmp = getNameInClass(false, true, true);
313:
314: return className + "." + tmp;
315: } else if (key.equals("returnType")) {
316: int i = methodSig.indexOf(')');
317: String returnType = methodSig.substring(i + 1);
318: String pkgName = primaryClass == null ? "" : primaryClass
319: .getPackageName();
320: SignatureConverter converter = new SignatureConverter(
321: returnType);
322: return shorten(pkgName, converter.parseNext());
323: } else
324: throw new IllegalArgumentException("unknown key " + key);
325: }
326:
327: /**
328: * Get the "full" method name.
329: * This is a format which looks sort of like a method signature
330: * that would appear in Java source code.
331: * @param primaryClass TODO
332: */
333: public String getNameInClass(ClassAnnotation primaryClass) {
334: return getNameInClass(true, false, false, false);
335: }
336:
337: public String getSignatureInClass(ClassAnnotation primaryClass) {
338: return getNameInClass(true, false, false, true);
339: }
340:
341: public String getNameInClass(boolean shortenPackages,
342: boolean useJVMMethodName, boolean hash) {
343: return getNameInClass(shortenPackages, useJVMMethodName, hash,
344: false);
345: }
346:
347: /**
348: * Get the "full" method name.
349: * This is a format which looks sort of like a method signature
350: * that would appear in Java source code.
351: *
352: * note: If shortenPackeges==true, this will return the same
353: * value as getNameInClass(), except that method caches the
354: * result and this one does not. Calling this one may be slow.
355: *
356: * @param shortenPackages whether to shorten package names
357: * if they are in java or in the same package as this method.
358: * @param useJVMMethodName TODO
359: * @param hash TODO
360: */
361: public String getNameInClass(boolean shortenPackages,
362: boolean useJVMMethodName, boolean hash,
363: boolean omitMethodName) {
364: // Convert to "nice" representation
365: StringBuffer result = new StringBuffer();
366: if (!omitMethodName) {
367: if (useJVMMethodName)
368: result.append(getMethodName());
369: else
370: result.append(getJavaSourceMethodName());
371: }
372: result.append('(');
373:
374: // append args
375: SignatureConverter converter = new SignatureConverter(methodSig);
376:
377: if (converter.getFirst() != '(')
378: throw new IllegalStateException("bad method signature "
379: + methodSig);
380: converter.skip();
381:
382: boolean needsComma = false;
383: while (converter.getFirst() != ')') {
384: if (needsComma)
385: if (hash)
386: result.append(",");
387: else
388: result.append(", ");
389: if (shortenPackages)
390: result.append(removePackageName(converter.parseNext()));
391: else
392: result.append(converter.parseNext());
393: needsComma = true;
394: }
395: converter.skip();
396:
397: result.append(')');
398: return result.toString();
399: }
400:
401: /**
402: * Get the "full" method name.
403: * This is a format which looks sort of like a method signature
404: * that would appear in Java source code.
405: * @param primaryClass TODO
406: */
407: public String getFullMethod(ClassAnnotation primaryClass) {
408: if (fullMethod == null) {
409: if (methodName.equals("<init>"))
410: fullMethod = "new " + stripJavaLang(className)
411: + getSignatureInClass(primaryClass);
412: else
413: fullMethod = stripJavaLang(className) + "."
414: + getNameInClass(primaryClass);
415: }
416:
417: return fullMethod;
418: }
419:
420: public String stripJavaLang(@DottedClassName
421: String className) {
422: if (className.startsWith("java.lang."))
423: return className.substring(10);
424: return className;
425: }
426:
427: private String getUglyMethod() {
428: return className + "." + methodName + " : "
429: + methodSig.replace('/', '.');
430: }
431:
432: @Override
433: public int hashCode() {
434: return className.hashCode() + methodName.hashCode()
435: + methodSig.hashCode();
436: }
437:
438: @Override
439: public boolean equals(Object o) {
440: if (!(o instanceof MethodAnnotation))
441: return false;
442: MethodAnnotation other = (MethodAnnotation) o;
443: return className.equals(other.className)
444: && methodName.equals(other.methodName)
445: && methodSig.equals(other.methodSig);
446: }
447:
448: public int compareTo(BugAnnotation o) {
449: if (!(o instanceof MethodAnnotation)) // BugAnnotations must be Comparable with any type of BugAnnotation
450: return this .getClass().getName().compareTo(
451: o.getClass().getName());
452: MethodAnnotation other = (MethodAnnotation) o;
453: int cmp;
454: cmp = className.compareTo(other.className);
455: if (cmp != 0)
456: return cmp;
457: cmp = methodName.compareTo(other.methodName);
458: if (cmp != 0)
459: return cmp;
460: return methodSig.compareTo(other.methodSig);
461: }
462:
463: /* ----------------------------------------------------------------------
464: * XML Conversion support
465: * ---------------------------------------------------------------------- */
466:
467: private static final String ELEMENT_NAME = "Method";
468:
469: public void writeXML(XMLOutput xmlOutput) throws IOException {
470: }
471:
472: public void writeXML(XMLOutput xmlOutput, boolean addMessages)
473: throws IOException {
474: XMLAttributeList attributeList = new XMLAttributeList()
475: .addAttribute("classname", getClassName())
476: .addAttribute("name", getMethodName()).addAttribute(
477: "signature", getMethodSignature())
478: .addAttribute("isStatic", String.valueOf(isStatic()));
479:
480: String role = getDescription();
481: if (!role.equals(DEFAULT_ROLE))
482: attributeList.addAttribute("role", role);
483:
484: if (sourceLines == null && !addMessages) {
485: xmlOutput.openCloseTag(ELEMENT_NAME, attributeList);
486: } else {
487: xmlOutput.openTag(ELEMENT_NAME, attributeList);
488: if (sourceLines != null) {
489: sourceLines.writeXML(xmlOutput);
490: }
491: if (addMessages) {
492: xmlOutput.openTag(MESSAGE_TAG);
493: xmlOutput.writeText(this .toString());
494: xmlOutput.closeTag(MESSAGE_TAG);
495: }
496: xmlOutput.closeTag(ELEMENT_NAME);
497: }
498: }
499:
500: @Override
501: public boolean isSignificant() {
502: String role = getDescription();
503: if (METHOD_DANGEROUS_TARGET.equals(role)
504: || METHOD_DANGEROUS_TARGET_ACTUAL_GUARANTEED_NULL
505: .equals(role)
506: || METHOD_SAFE_TARGET.equals(role))
507: return false;
508: return true;
509: }
510: }
511:
512: // vim:ts=4
|