KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > edu > umd > cs > findbugs > SourceLineAnnotation


1 /*
2  * FindBugs - Find bugs in Java programs
3  * Copyright (C) 2003-2005, University of Maryland
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18  */

19
20 package edu.umd.cs.findbugs;
21
22 import java.io.IOException JavaDoc;
23
24 import org.apache.bcel.classfile.Code;
25 import org.apache.bcel.classfile.JavaClass;
26 import org.apache.bcel.classfile.LineNumber;
27 import org.apache.bcel.classfile.LineNumberTable;
28 import org.apache.bcel.classfile.Method;
29 import org.apache.bcel.generic.InstructionHandle;
30 import org.apache.bcel.generic.MethodGen;
31
32 import edu.umd.cs.findbugs.annotations.NonNull;
33 import edu.umd.cs.findbugs.ba.AnalysisContext;
34 import edu.umd.cs.findbugs.ba.ClassContext;
35 import edu.umd.cs.findbugs.ba.Hierarchy;
36 import edu.umd.cs.findbugs.ba.JavaClassAndMethod;
37 import edu.umd.cs.findbugs.ba.SourceInfoMap;
38 import edu.umd.cs.findbugs.ba.XMethod;
39 import edu.umd.cs.findbugs.visitclass.PreorderVisitor;
40 import edu.umd.cs.findbugs.xml.XMLAttributeList;
41 import edu.umd.cs.findbugs.xml.XMLOutput;
42
43 /**
44  * A BugAnnotation that records a range of source lines
45  * in a class.
46  *
47  * @author David Hovemeyer
48  * @see BugAnnotation
49  */

50 public class SourceLineAnnotation implements BugAnnotation {
51     private static final long serialVersionUID = 1L;
52
53     private static final String JavaDoc DEFAULT_ROLE = "SOURCE_LINE_DEFAULT";
54     private static final String JavaDoc DEFAULT_ROLE_UNKNOWN_LINE = "SOURCE_LINE_DEFAULT_UNKNOWN_LINE";
55
56     /**
57      * String returned if the source file is unknown.
58      * This must match what BCEL uses when the source file is unknown.
59      */

60     public static final String JavaDoc UNKNOWN_SOURCE_FILE = "<Unknown>";
61
62     private String JavaDoc description;
63     final private String JavaDoc className;
64     private String JavaDoc sourceFile;
65     final private int startLine;
66     final private int endLine;
67     final private int startBytecode;
68     final private int endBytecode;
69     private boolean synthetic;
70
71     /**
72      * Constructor.
73      *
74      * @param className the class to which the line number(s) refer
75      * @param sourceFile the name of the source file
76      * @param startLine the first line (inclusive)
77      * @param endLine the ending line (inclusive)
78      * @param startBytecode the first bytecode offset (inclusive)
79      * @param endBytecode the end bytecode offset (inclusive)
80      */

81     public SourceLineAnnotation(@NonNull String JavaDoc className, @NonNull String JavaDoc sourceFile, int startLine, int endLine,
82                                 int startBytecode, int endBytecode) {
83         if (className == null) throw new IllegalArgumentException JavaDoc("class name is null");
84         if (sourceFile == null) throw new IllegalArgumentException JavaDoc("source file is null");
85         this.description = DEFAULT_ROLE;
86         this.className = className;
87         this.sourceFile = sourceFile;
88         this.startLine = startLine;
89         this.endLine = endLine;
90         this.startBytecode = startBytecode;
91         this.endBytecode = endBytecode;
92     }
93     
94     //@Override
95
@Override JavaDoc
96     public Object JavaDoc clone() {
97         try {
98             return super.clone();
99         } catch (CloneNotSupportedException JavaDoc e) {
100             throw new AssertionError JavaDoc(e);
101         }
102     }
103
104     /**
105      * Factory method to create an unknown source line annotation.
106      *
107      * @param className the class name
108      * @param sourceFile the source file name
109      * @return the SourceLineAnnotation
110      */

111     public static SourceLineAnnotation createUnknown(String JavaDoc className, String JavaDoc sourceFile) {
112         return createUnknown(className, sourceFile, -1, -1);
113     }
114
115     /**
116      * Factory method to create an unknown source line annotation.
117      * This variant looks up the source filename automatically
118      * based on the class using best effort.
119      *
120      * @param className the class name
121      * @return the SourceLineAnnotation
122      */

123     public static SourceLineAnnotation createUnknown(String JavaDoc className) {
124         return createUnknown(
125                 className,
126                 AnalysisContext.currentAnalysisContext().lookupSourceFile(className),
127                 -1,
128                 -1);
129     }
130
131     /**
132      * Factory method to create an unknown source line annotation.
133      * This variant is used when bytecode offsets are known,
134      * but not source lines.
135      *
136      * @param className the class name
137      * @param sourceFile the source file name
138      * @return the SourceLineAnnotation
139      */

140     public static SourceLineAnnotation createUnknown(String JavaDoc className, String JavaDoc sourceFile, int startBytecode, int endBytecode) {
141         SourceLineAnnotation result = new SourceLineAnnotation(className, sourceFile, -1, -1, startBytecode, endBytecode);
142         // result.setDescription("SOURCE_LINE_UNKNOWN");
143
return result;
144     }
145
146     /**
147      * Factory method for creating a source line annotation describing
148      * an entire method.
149      *
150      * @param visitor a BetterVisitor which is visiting the method
151      * @return the SourceLineAnnotation
152      */

153     public static SourceLineAnnotation fromVisitedMethod(PreorderVisitor visitor) {
154         LineNumberTable lineNumberTable = getLineNumberTable(visitor);
155         String JavaDoc className = visitor.getDottedClassName();
156         String JavaDoc sourceFile = visitor.getSourceFile();
157         Code code = visitor.getMethod().getCode();
158         int codeSize = (code != null) ? code.getCode().length : 0;
159         if (lineNumberTable == null) {
160             // Try SourceInfoMap
161
SourceInfoMap.SourceLineRange range = AnalysisContext.currentAnalysisContext()
162                 .getSourceInfoMap()
163                 .getMethodLine(className, visitor.getMethodName(), visitor.getMethodSig());
164             if (range != null) {
165                 return new SourceLineAnnotation(
166                         className,
167                         visitor.getSourceFile(),
168                         range.getStart().intValue(),
169                         range.getEnd().intValue(),
170                         0,
171                         codeSize - 1);
172             } else {
173                 return createUnknown(className, sourceFile, 0, codeSize - 1);
174             }
175         }
176         return forEntireMethod(className, sourceFile, lineNumberTable, codeSize);
177     }
178
179     /**
180      * Factory method for creating a source line annotation describing
181      * an entire method.
182      *
183      * @param methodGen the method being visited
184      * @return the SourceLineAnnotation, or null if we do not have line number information
185      * for the method
186      */

187     public static SourceLineAnnotation fromVisitedMethod(MethodGen methodGen, String JavaDoc sourceFile) {
188         LineNumberTable lineNumberTable = methodGen.getLineNumberTable(methodGen.getConstantPool());
189         String JavaDoc className = methodGen.getClassName();
190         int codeSize = methodGen.getInstructionList().getLength();
191         if (lineNumberTable == null)
192             return createUnknown(className, sourceFile, 0, codeSize - 1);
193         return forEntireMethod(className, sourceFile, lineNumberTable, codeSize);
194     }
195
196     /**
197      * Create a SourceLineAnnotation covering an entire method.
198      *
199      * @param className name of the class the method is in
200      * @param sourceFile source file containing the method
201      * @param lineNumberTable the method's LineNumberTable
202      * @param codeSize size in bytes of the method's code
203      * @return a SourceLineAnnotation covering the entire method
204      */

205     public static SourceLineAnnotation forEntireMethod(String JavaDoc className, String JavaDoc sourceFile,
206                                                         LineNumberTable lineNumberTable, int codeSize) {
207         LineNumber[] table = lineNumberTable.getLineNumberTable();
208         if (table != null && table.length > 0) {
209             LineNumber first = table[0];
210             LineNumber last = table[table.length - 1];
211             return new SourceLineAnnotation(className, sourceFile, first.getLineNumber(), last.getLineNumber(),
212                     0, codeSize - 1);
213         } else {
214             return createUnknown(className, sourceFile, 0, codeSize - 1);
215         }
216     }
217     
218     /**
219      * Create a SourceLineAnnotation covering an entire method.
220      *
221      * @param javaClass JavaClass containing the method
222      * @param method the method
223      * @return a SourceLineAnnotation for the entire method
224      */

225     public static SourceLineAnnotation forEntireMethod(JavaClass javaClass, Method method) {
226         String JavaDoc sourceFile = javaClass.getSourceFileName();
227         Code code = method.getCode();
228         LineNumberTable lineNumberTable = method.getLineNumberTable();
229         if (code == null || lineNumberTable == null) {
230             return createUnknown(javaClass.getClassName(), sourceFile);
231         }
232         
233         return forEntireMethod(javaClass.getClassName(), sourceFile, lineNumberTable, code.getLength());
234     }
235
236     /**
237      * Create a SourceLineAnnotation covering an entire method.
238      *
239      * @param javaClass JavaClass containing the method
240      * @param xmethod the method
241      * @return a SourceLineAnnotation for the entire method
242      */

243     public static SourceLineAnnotation forEntireMethod(JavaClass javaClass, XMethod xmethod) {
244         JavaClassAndMethod m = Hierarchy.findMethod(javaClass, xmethod.getName(), xmethod.getSignature());
245         if (m == null) {
246             return createUnknown(javaClass.getClassName(), javaClass.getSourceFileName());
247         } else {
248             return forEntireMethod(javaClass, m.getMethod());
249         }
250     }
251
252     /**
253      * Factory method for creating a source line annotation describing the
254      * source line number for the instruction being visited by given visitor.
255      *
256      * @param visitor a BetterVisitor which is visiting the method
257      * @param pc the bytecode offset of the instruction in the method
258      * @return the SourceLineAnnotation, or null if we do not have line number information
259      * for the instruction
260      */

261     public static SourceLineAnnotation fromVisitedInstruction(BytecodeScanningDetector visitor, int pc) {
262         return fromVisitedInstructionRange(visitor.getClassContext(), visitor, pc, pc);
263     }
264
265     /**
266      * Factory method for creating a source line annotation describing the
267      * source line number for the instruction being visited by given visitor.
268      *
269      * @param classContext the ClassContext
270      * @param visitor a BetterVisitor which is visiting the method
271      * @param pc the bytecode offset of the instruction in the method
272      * @return the SourceLineAnnotation, or null if we do not have line number information
273      * for the instruction
274      */

275     public static SourceLineAnnotation fromVisitedInstruction(ClassContext classContext, PreorderVisitor visitor, int pc) {
276         return fromVisitedInstructionRange(classContext, visitor, pc, pc);
277     }
278
279     /**
280      * Factory method for creating a source line annotation describing the
281      * source line numbers for a range of instructions in the method being
282      * visited by the given visitor.
283      *
284      * @param visitor a BetterVisitor which is visiting the method
285      * @param startPC the bytecode offset of the start instruction in the range
286      * @param endPC the bytecode offset of the end instruction in the range
287      * @return the SourceLineAnnotation, or null if we do not have line number information
288      * for the instruction
289      */

290     public static SourceLineAnnotation fromVisitedInstructionRange(
291             BytecodeScanningDetector visitor, int startPC, int endPC) {
292         LineNumberTable lineNumberTable = getLineNumberTable(visitor);
293         String JavaDoc className = visitor.getDottedClassName();
294         String JavaDoc sourceFile = visitor.getSourceFile();
295
296         if (lineNumberTable == null)
297             return createUnknown(className, sourceFile, startPC, endPC);
298
299         int startLine = lineNumberTable.getSourceLine(startPC);
300         int endLine = lineNumberTable.getSourceLine(endPC);
301         return new SourceLineAnnotation(className, sourceFile, startLine, endLine, startPC, endPC)
302                 .addInstructionContext(visitor.getClassContext(), visitor.getMethod());
303     }
304
305     /**
306      * Factory method for creating a source line annotation describing the
307      * source line numbers for a range of instructions in the method being
308      * visited by the given visitor.
309      *
310      * @param classContext the ClassContext
311      * @param visitor a BetterVisitor which is visiting the method
312      * @param startPC the bytecode offset of the start instruction in the range
313      * @param endPC the bytecode offset of the end instruction in the range
314      * @return the SourceLineAnnotation, or null if we do not have line number information
315      * for the instruction
316      */

317     public static SourceLineAnnotation fromVisitedInstructionRange(
318             ClassContext classContext, PreorderVisitor visitor, int startPC, int endPC) {
319         LineNumberTable lineNumberTable = getLineNumberTable(visitor);
320         String JavaDoc className = visitor.getDottedClassName();
321         String JavaDoc sourceFile = visitor.getSourceFile();
322
323         if (lineNumberTable == null)
324             return createUnknown(className, sourceFile, startPC, endPC);
325
326         int startLine = lineNumberTable.getSourceLine(startPC);
327         int endLine = lineNumberTable.getSourceLine(endPC);
328         return new SourceLineAnnotation(className, sourceFile, startLine, endLine, startPC, endPC)
329                 .addInstructionContext(classContext, visitor.getMethod());
330     }
331
332     /**
333      * Factory method for creating a source line annotation describing the
334      * source line number for the instruction being visited by given visitor.
335      *
336      * @param visitor a DismantleBytecode visitor which is visiting the method
337      * @return the SourceLineAnnotation, or null if we do not have line number information
338      * for the instruction
339      */

340     public static SourceLineAnnotation fromVisitedInstruction(BytecodeScanningDetector visitor) {
341         return fromVisitedInstruction(visitor.getClassContext(), visitor, visitor.getPC());
342     }
343
344     /**
345      * Factory method for creating a source line annotation describing the
346      * source line number for a visited instruction.
347      *
348      * @param classContext the ClassContext
349      * @param methodGen the MethodGen object representing the method
350      * @param handle the InstructionHandle containing the visited instruction
351      * @return the SourceLineAnnotation, or null if we do not have line number information
352      * for the instruction
353      */

354     public static SourceLineAnnotation fromVisitedInstruction(ClassContext classContext, MethodGen methodGen, String JavaDoc sourceFile, @NonNull InstructionHandle handle) {
355         LineNumberTable table = methodGen.getLineNumberTable(methodGen.getConstantPool());
356         String JavaDoc className = methodGen.getClassName();
357
358         int bytecodeOffset = handle.getPosition();
359
360         if (table == null)
361             return createUnknown(className, sourceFile, bytecodeOffset, bytecodeOffset);
362
363         int lineNumber = table.getSourceLine(handle.getPosition());
364         return new SourceLineAnnotation(
365                 className, sourceFile, lineNumber, lineNumber, bytecodeOffset, bytecodeOffset)
366                 .addInstructionContext(classContext, classContext.getMethod(methodGen));
367     }
368
369     /**
370      * Factory method for creating a source line annotation describing
371      * the source line numbers for a range of instruction in a method.
372      *
373      * @param classContext theClassContext
374      * @param methodGen the method
375      * @param start the start instruction
376      * @param end the end instruction (inclusive)
377      */

378     public static SourceLineAnnotation fromVisitedInstructionRange(
379             ClassContext classContext, MethodGen methodGen, String JavaDoc sourceFile, InstructionHandle start, InstructionHandle end) {
380         LineNumberTable lineNumberTable = methodGen.getLineNumberTable(methodGen.getConstantPool());
381         String JavaDoc className = methodGen.getClassName();
382
383         if (lineNumberTable == null)
384             return createUnknown(className, sourceFile, start.getPosition(), end.getPosition());
385
386         int startLine = lineNumberTable.getSourceLine(start.getPosition());
387         int endLine = lineNumberTable.getSourceLine(end.getPosition());
388         return new SourceLineAnnotation(
389                 className, sourceFile, startLine, endLine, start.getPosition(), end.getPosition())
390                 .addInstructionContext(classContext, classContext.getMethod(methodGen));
391     }
392
393     private static LineNumberTable getLineNumberTable(PreorderVisitor visitor) {
394         Code code = visitor.getMethod().getCode();
395         if (code == null)
396             return null;
397         return code.getLineNumberTable();
398     }
399     
400     private static final int NUM_CONTEXT_OPCODES = 5;
401     
402     /**
403      * Fill in context information about surrounding opcodes.
404      *
405      * @param classContext the ClassContext
406      * @param method the method
407      */

408     private SourceLineAnnotation addInstructionContext(ClassContext classContext, Method method) {
409         // For now, do nothing.
410
return this;
411     }
412
413     /**
414      * Get the class name.
415      */

416     public String JavaDoc getClassName() {
417         return className;
418     }
419
420     /**
421      * Get the source file name.
422      */

423     public String JavaDoc getSourceFile() {
424         return sourceFile;
425     }
426
427     /**
428      * Is the source file known?
429      */

430     public boolean isSourceFileKnown() {
431         return !sourceFile.equals(UNKNOWN_SOURCE_FILE);
432     }
433
434     /**
435      * Set the source file name.
436      *
437      * @param sourceFile the source file name
438      */

439     public void setSourceFile(String JavaDoc sourceFile) {
440         this.sourceFile = sourceFile;
441     }
442
443     /**
444      * Get the package name.
445      */

446     public String JavaDoc getPackageName() {
447         int lastDot = className.lastIndexOf('.');
448         if (lastDot < 0)
449             return "";
450         else
451             return className.substring(0, lastDot);
452     }
453
454     /**
455      * Get the start line (inclusive).
456      */

457     public int getStartLine() {
458         return startLine;
459     }
460
461     /**
462      * Get the ending line (inclusive).
463      */

464     public int getEndLine() {
465         return endLine;
466     }
467
468     /**
469      * Get start bytecode (inclusive).
470      */

471     public int getStartBytecode() {
472         return startBytecode;
473     }
474
475     /**
476      * Get end bytecode (inclusive).
477      */

478     public int getEndBytecode() {
479         return endBytecode;
480     }
481
482     /**
483      * Is this an unknown source line annotation?
484      */

485     public boolean isUnknown() {
486         return startLine < 0 || endLine < 0;
487     }
488
489     public void accept(BugAnnotationVisitor visitor) {
490         visitor.visitSourceLineAnnotation(this);
491     }
492
493     public String JavaDoc format(String JavaDoc key, ClassAnnotation primaryClass) {
494         if (key.equals("hash")) return "";
495         if (key.equals("")) {
496             StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
497             buf.append(sourceFile);
498             appendLines(buf);
499             return buf.toString();
500         } else if (key.equals("lineNumber")) {
501             StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
502             appendLinesRaw(buf);
503             return buf.toString();
504         } else if (key.equals("full")) {
505             StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
506             String JavaDoc pkgName = getPackageName();
507             if (!pkgName.equals("")) {
508                 buf.append(pkgName.replace('.', '/'));
509                 buf.append('/');
510             }
511             buf.append(sourceFile);
512             appendLines(buf);
513             return buf.toString();
514         } else
515             throw new IllegalArgumentException JavaDoc("Unknown format key " + key);
516     }
517
518     private void appendLines(StringBuffer JavaDoc buf) {
519         if (isUnknown()) return;
520         buf.append(":[");
521         appendLinesRaw(buf);
522         buf.append(']');
523     }
524
525     private void appendLinesRaw(StringBuffer JavaDoc buf) {
526         if (isUnknown()) return;
527         if (startLine == endLine) {
528             buf.append("line ");
529             buf.append(startLine);
530         } else {
531             buf.append("lines ");
532             buf.append(startLine);
533             buf.append('-');
534             buf.append(endLine);
535         }
536
537     }
538
539     public String JavaDoc getDescription() {
540         return description;
541     }
542
543     public void setDescription(String JavaDoc description) {
544         this.description = description;
545     }
546
547     @Override JavaDoc
548     public String JavaDoc toString() {
549         String JavaDoc desc = description;
550         if (desc.equals(DEFAULT_ROLE) && isUnknown())
551             desc = DEFAULT_ROLE_UNKNOWN_LINE;
552         String JavaDoc pattern = I18N.instance().getAnnotationDescription(desc);
553         FindBugsMessageFormat format = new FindBugsMessageFormat(pattern);
554         return format.format(new BugAnnotation[]{this}, null);
555     }
556
557     public int compareTo(BugAnnotation o) {
558         if (!(o instanceof SourceLineAnnotation)) // All BugAnnotations must be comparable
559
return this.getClass().getName().compareTo(o.getClass().getName());
560
561         SourceLineAnnotation other = (SourceLineAnnotation) o;
562
563         int cmp = className.compareTo(other.className);
564         if (cmp != 0)
565             return cmp;
566         cmp = startLine - other.startLine;
567         if (cmp != 0)
568             return cmp;
569         return endLine - other.endLine;
570     }
571
572     @Override JavaDoc
573     public int hashCode() {
574         return className.hashCode() + startLine + 3 * endLine;
575     }
576
577     @Override JavaDoc
578     public boolean equals(Object JavaDoc o) {
579         if (!(o instanceof SourceLineAnnotation))
580             return false;
581         SourceLineAnnotation other = (SourceLineAnnotation) o;
582         return className.equals(other.className)
583                 && startLine == other.startLine
584                 && endLine == other.endLine;
585     }
586
587     /* ----------------------------------------------------------------------
588      * XML Conversion support
589      * ---------------------------------------------------------------------- */

590
591     private static final String JavaDoc ELEMENT_NAME = "SourceLine";
592
593     public void writeXML(XMLOutput xmlOutput) throws IOException JavaDoc {
594         writeXML(xmlOutput, false);
595     }
596
597     public void writeXML(XMLOutput xmlOutput, boolean addMessages) throws IOException JavaDoc {
598         String JavaDoc classname = getClassName();
599         String JavaDoc packageName = "";
600         if (classname.indexOf('.') > 0)
601             packageName = classname.substring(0,1+classname.lastIndexOf('.'));
602         XMLAttributeList attributeList = new XMLAttributeList()
603             .addAttribute("classname", classname);
604         int n = getStartLine(); // start/end are now optional (were too many "-1"s in the xml)
605
if (n >= 0) attributeList.addAttribute("start", String.valueOf(n));
606         n = getEndLine();
607         if (n >= 0) attributeList.addAttribute("end", String.valueOf(n));
608         n = getStartBytecode(); // startBytecode/endBytecode haven't been set for a while now
609
if (n >= 0) attributeList.addAttribute("startBytecode", String.valueOf(n));
610         n = getEndBytecode();
611         if (n >= 0) attributeList.addAttribute("endBytecode", String.valueOf(n));
612         
613         if (isSourceFileKnown()) {
614             attributeList.addAttribute("sourcefile", sourceFile);
615             attributeList.addAttribute("sourcepath", packageName.replace('.', '/')+sourceFile);
616         }
617         
618         String JavaDoc role = getDescription();
619         if (!role.equals(DEFAULT_ROLE))
620             attributeList.addAttribute("role", getDescription());
621         if (synthetic)
622             attributeList.addAttribute("synthetic", "true");
623         if (addMessages) {
624             xmlOutput.openTag(ELEMENT_NAME, attributeList);
625             xmlOutput.openTag("Message");
626             xmlOutput.writeText(this.toString());
627             xmlOutput.closeTag("Message");
628             xmlOutput.closeTag(ELEMENT_NAME);
629         } else {
630             xmlOutput.openCloseTag(ELEMENT_NAME, attributeList);
631         }
632     }
633
634     /**
635      * @param synthetic The synthetic to set.
636      */

637     public void setSynthetic(boolean synthetic) {
638         this.synthetic = synthetic;
639     }
640
641     /**
642      * @return Returns the synthetic.
643      */

644     public boolean isSynthetic() {
645         return synthetic;
646     }
647
648
649     public boolean isSignificant() {
650         return false;
651     }
652 }
653
654 // vim:ts=4
655
Popular Tags