KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > csdl > jblanket > modifier > MethodModifier


1 package csdl.jblanket.modifier;
2
3 import java.io.File JavaDoc;
4 import java.io.FileInputStream JavaDoc;
5 import java.util.ArrayList JavaDoc;
6 import java.util.Date JavaDoc;
7
8 import org.apache.bcel.Constants;
9 import org.apache.bcel.classfile.Method;
10 import org.apache.bcel.generic.ALOAD;
11 import org.apache.bcel.generic.ASTORE;
12 import org.apache.bcel.generic.ConstantPoolGen;
13 import org.apache.bcel.generic.InstructionConstants;
14 import org.apache.bcel.generic.InstructionFactory;
15 import org.apache.bcel.generic.InstructionList;
16 import org.apache.bcel.generic.LineNumberGen;
17 import org.apache.bcel.generic.LocalVariableGen;
18 import org.apache.bcel.generic.MethodGen;
19 import org.apache.bcel.generic.ObjectType;
20 import org.apache.bcel.generic.POP;
21 import org.apache.bcel.generic.PUSH;
22 import org.apache.bcel.generic.Type;
23
24 import csdl.jblanket.methodset.MethodInfo;
25 import csdl.jblanket.methodset.MethodSet;
26 import csdl.jblanket.methodset.MethodSetManager;
27 import csdl.jblanket.util.MethodCategories;
28
29 /**
30  * Provides a Modifier for methods in .class files. Methods are either instrumented or recorded
31  * for exclusion from coverage. Modifying a method here is performed unconditionally. Therefore,
32  * without first checking at the ClassModifier level to see if a class as a whole has been
33  * previously modified can result in numerous modifications to a method.
34  * <p>
35  * Recording a method for exclusion from coverage leaves its byte code unchanged.
36  *
37  * @author Joy M. Agustin
38  * @version $Id: MethodModifier.java,v 1.5 2005/03/08 08:02:01 timshadel Exp $
39  */

40 public class MethodModifier {
41
42   /** Method to modify */
43   private MethodGen method;
44
45   /** Grammar for names of test classes */
46   private String JavaDoc testGrammar;
47
48   /** Describes if one-line methods should be excluded from the coverage measurement */
49   private boolean excludeOneLineMethods;
50   /** Describes if constructors should be excluded from the coverage measurement */
51   private boolean excludeConstructors;
52
53   /** Container for all MethodSets */
54   private MethodSetManager manager;
55   /** Container for all method categories */
56   private MethodCategories categories;
57
58 private MethodSet excludedIndividualSet;
59
60 private boolean excludeIndividualMethods;
61
62   /**
63    * Constructs a new MethodModifier object.
64  * @param verbose describes if JBlanket should execute in verbose mode.
65  * @param testGrammar the grammar describing test class names.
66  * @param excludeOneLineMethods describes exclusion of one-line methods.
67  * @param excludeIndividualMethods describes exclusion of individual methods.
68  * @param excludeConstructors describes exclusion of constructors.
69  * @param method the method to modify.
70    */

71   public MethodModifier(boolean verbose, String JavaDoc testGrammar, boolean excludeOneLineMethods,
72                          boolean excludeIndividualMethods, boolean excludeConstructors,
73                          MethodGen method) {
74
75     this.method = method;
76
77     this.testGrammar = testGrammar;
78
79     this.excludeOneLineMethods = excludeOneLineMethods;
80     this.excludeConstructors = excludeConstructors;
81
82     this.manager = MethodSetManager.getInstance();
83     this.categories = MethodCategories.getInstance();
84
85     this.excludeIndividualMethods = excludeIndividualMethods;
86     this.excludedIndividualSet = new MethodSet();
87     if (this.excludeIndividualMethods) {
88       File JavaDoc excludeIndividualFile =
89           new File JavaDoc(this.categories.getFileName("excludedIndividualFile"));
90       // file will not exist if this is the first time excluding individual files
91
if (excludeIndividualFile.exists()) {
92         loadMethods(this.excludedIndividualSet, excludeIndividualFile);
93       }
94       else {
95         this.excludedIndividualSet = new MethodSet();
96       }
97     }
98   }
99
100   /**
101    * Processes a method. The method represented by this MethodModifier is not modified if it is
102    * abstract or native. Furthermore, it is not modified if <code>excludeConstructors</code> or
103    * <code>excludeOneLineMethods</code> is set. Otherwise, the this method is modified.
104    *
105    * @param pool the ConstantPoolGen for this method.
106    * @param isModified describes if this method was previously modified.
107    * @return modified <code>Method</code> version of this MethodModified object.
108    */

109   public Method processMethod(ConstantPoolGen pool, boolean isModified) {
110
111     if (isExcluded()) {
112       return this.method.getMethod();
113     }
114
115     if (isConstructor() || !isMethodUntestable()) {
116
117       if (!hasLineNumbers()) {
118         String JavaDoc methodName = this.method.getClassName() + "." + this.method.getName();
119         String JavaDoc message = "No line numbers detected in " + methodName + ". "
120                        + "Either remove the 'oneLineFile' tag or turn debug on when compiling.";
121         throw new UnsupportedOperationException JavaDoc(message);
122       }
123
124       // exclude constructors
125
if (this.excludeConstructors && isConstructor()) {
126         excludeMethod(manager.getMethodSet(categories.getFileName("constructorFile")));
127       }
128       // exclude one-line methods, include all constructors since not considered as methods
129
else if (this.excludeOneLineMethods && isOneLine() && !isConstructor()) {
130         excludeMethod(manager.getMethodSet(categories.getFileName("oneLineFile")));
131       }
132       // modify all other methods if they were not previously modified
133
else if (!isModified) {
134         return modifyMethod(pool);
135       }
136     }
137     // exclude untestable methods
138
else {
139       excludeMethod(manager.getMethodSet(categories.getFileName("untestableFile")));
140     }
141
142     return this.method.getMethod();
143   }
144
145   /**
146    * @return true if the current method is excluded.
147    */

148   private boolean isExcluded() {
149     return (excludeIndividualMethods && excludedIndividualSet.contains(getMethodInfo()));
150   }
151
152 /**
153    * @return true if method is abstract, native, or an empty void method.
154    */

155   private boolean isMethodUntestable() {
156     if (this.method.isAbstract()) {
157       return true;
158     }
159     if (this.method.isNative()) {
160       return true;
161     }
162     // get line numbers to check size of method
163
LineNumberGen[] lineNumbers = this.method.getLineNumbers();
164
165     // method body - blank
166
// one line - compiler inserted return statement (?)
167
if (this.method.getReturnType() == Type.VOID && lineNumbers.length == 1) {
168       return true;
169     }
170
171     return false;
172   }
173
174   /**
175    * Checks if a method contains line numbers. If so, then it is valid. If not, then the method
176    * is invalid. Line numbers are added by enabling debugging when compiling.
177    *
178    * @return true if <code>method</code> contains line numbers, false otherwise.
179    */

180   public boolean hasLineNumbers() {
181     
182     LineNumberGen[] lineNumbers = this.method.getLineNumbers();
183     if (lineNumbers.length == 0) {
184       return false;
185     }
186     
187     return true;
188   }
189
190   /**
191    * Checks if <code>method</code> is a constructor. It does not matter if it's implemented by
192    * the programmer or automatically generated by the compiler. If it is a constructor,
193    * <code>method</code> is stored. Valid constructor method name is &lt;init&gt;.
194    *
195    * @return true if <code>method</code> is a constructor, false otherwise.
196    */

197   public boolean isConstructor() {
198     
199     if ("<init>".equals(this.method.getName())) {
200       return true;
201     }
202     
203     return false;
204   }
205
206   /**
207    * Checks if <code>method</code> contains one line of source code. If so, <code>method</code>
208    * is stored.
209    *
210    * @return true if <code>method</code> contains one line, false otherwise.
211    */

212   public boolean isOneLine() {
213     
214     // get line numbers to check size of method
215
LineNumberGen[] lineNumbers = this.method.getLineNumbers();
216
217     // one line - method body
218
// one line - compiler inserted return statement (?)
219
if (this.method.getReturnType() == Type.VOID && lineNumbers.length == 2) {
220       return true;
221     }
222     // one line - return statement
223
else if (this.method.getReturnType() != Type.VOID && lineNumbers.length == 1) {
224       return true;
225     }
226
227     return false;
228   }
229   
230   /**
231    * Modifies <code>method</code> to store its type signature when invoked during testing.
232    * The modification inserts a call to <code>MethodCollector.storeMethodTypeSignature</code>
233    * at the beginning of <code>method</code>.
234    *
235    * @param pool the ConstantPool generator of class containing <code>method</code>.
236    * @return the modified method.
237    */

238   private Method modifyMethod(ConstantPoolGen pool) {
239
240     InstructionList oldList = method.getInstructionList();
241
242     // create newList to add to oldList
243
InstructionList newList = null;
244     newList = addStoreMethodTypeSignature(pool, method);
245
246     // insert new list before old list
247
oldList.append(oldList.getStart(), newList);
248     this.method.setInstructionList(oldList);
249     this.method.setMaxStack();
250     Method m = this.method.getMethod();
251
252     // REMINDER: must do method.getMethod() before can free memory
253
oldList.dispose();
254     newList.dispose();
255
256     return m;
257   }
258
259   /**
260    * Creates an InstructionList for inserting the static method call to
261    * <code>MethodCollector.storeMethodTypeSignature</code>. From the byte code, this additional
262    * method call records the name of the class <code>method</code> belongs to, the name of
263    * <code>method</code>, and the list of <code>method</code>'s parameter types.
264    *
265    * @param pool the ContantPool generator of the class containing <code>method</code>.
266    * @param method the method to alter.
267    * @return list of instructions for modification.
268    */

269   private InstructionList addStoreMethodTypeSignature(ConstantPoolGen pool, MethodGen method) {
270
271     // get elements of method's type signature
272
String JavaDoc methodName = method.getName();
273     String JavaDoc className = method.getClassName();
274     Type[] types = method.getArgumentTypes();
275
276     // common objects used below
277
String JavaDoc arrayList = "java.util.ArrayList";
278     String JavaDoc reportClass = "csdl.jblanket.modifier.MethodCollector";
279     ObjectType arrayObject = new ObjectType(arrayList);
280
281     // objects used for adding new instructions to byte code
282
InstructionFactory factory = new InstructionFactory(pool);
283     InstructionList newList = new InstructionList();
284     LocalVariableGen variable;
285
286     // create new "java.util.ArrayList" instance "params" in method to store method type signatures
287
variable = method.addLocalVariable("params", new ObjectType(arrayList), null, null);
288     int params = variable.getIndex();
289     newList.append(factory.createNew(arrayList));
290     newList.append(InstructionConstants.DUP);
291     newList.append(factory.createInvoke(arrayList, "<init>", Type.VOID, Type.NO_ARGS,
292                                         Constants.INVOKESPECIAL));
293     variable.setStart(newList.append(new ASTORE(params)));
294
295     // add instructions to add array elements when byte code is executed
296
for (int i = 0; i < types.length; i++) {
297       newList.append(new ALOAD(params));
298       newList.append(new PUSH(pool, types[i].getSignature()));
299       newList.append(factory.createInvoke(arrayList, "add", Type.BOOLEAN, new Type[]{Type.OBJECT},
300                                           Constants.INVOKEVIRTUAL));
301       // pop off the boolean return type from "add"
302
newList.append(new POP());
303     }
304
305     // using MethodCollector, call static method that stores the method type signature
306
Type[] invokeMethodParams = new Type[]{Type.STRING, Type.STRING, arrayObject, Type.STRING};
307     newList.append(new PUSH(pool, className));
308     newList.append(new PUSH(pool, methodName));
309     newList.append(new ALOAD(params));
310     newList.append(new PUSH(pool, this.testGrammar));
311     newList.append(factory.createInvoke(reportClass, "storeMethodTypeSignature", Type.VOID,
312                                         invokeMethodParams, Constants.INVOKESTATIC));
313
314     return newList;
315   }
316
317   /**
318    * Records this method to exclude from coverage and stores it in <code>excludeSet</code>.
319    *
320    * @param excludeSet the container storing methods to exclude from coverage.
321    */

322   public void excludeMethod(MethodSet excludeSet) {
323     MethodInfo methodInfo = getMethodInfo();
324     excludeSet.add(methodInfo);
325   }
326
327   /**
328    * @return Construct a MethodInfo object for the current method.
329    */

330   private MethodInfo getMethodInfo() {
331     String JavaDoc className = this.method.getClassName();
332     String JavaDoc methodName = this.method.getName();
333     Type[] paramTypes = this.method.getArgumentTypes();
334     ArrayList JavaDoc paramList = new ArrayList JavaDoc();
335     for (int i = 0; i < paramTypes.length; i++) {
336       paramList.add(MethodCollector.reconstructType(paramTypes[i].getSignature()));
337     }
338     // reconstruct names of constructors from default names
339
if ("<init>".equals(methodName)) {
340       methodName = MethodCollector.removePackagePrefix(className);
341     }
342     if ("<clinit>".equals(methodName)) {
343         methodName = MethodCollector.removePackagePrefix(className) + "[static initializer]";
344     }
345     methodName = methodName.replaceAll("<", "_").replaceAll(">", "_");
346     MethodInfo methodInfo = new MethodInfo(className, methodName, paramList);
347     return methodInfo;
348 }
349
350   /**
351    * Loads all the method information into <code>jblanketSet</code> from <code>file</code>
352    * in the jblanket.dir system property.
353    *
354    * @param methodSet the collection of method information to load.
355    * @param file the input file.
356    * @return the Date found in <code>file</code>.
357    */

358   private Date JavaDoc loadMethods(MethodSet methodSet, File JavaDoc file) {
359     
360     Date JavaDoc timeStamp = null;
361     try {
362       // throws FileNotFoundException
363
FileInputStream JavaDoc in = new FileInputStream JavaDoc(file);
364       // throws PareseException
365
timeStamp = methodSet.load(in);
366       // throws IOException
367
in.close();
368     }
369     catch (Exception JavaDoc e) {
370       // TODO: Figure out how to log this
371
//System.out.println("Unable to read file " + file.getAbsolutePath() + ". Exception: " + e);
372
e.printStackTrace();
373     }
374     
375     return timeStamp;
376   }
377 }
378
Popular Tags