KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > net > sourceforge > groboutils > codecoverage > v2 > compiler > ModifiedClass


1 /*
2  * @(#)ModifiedClass.java
3  *
4  * Copyright (C) 2002-2004 Matt Albrecht
5  * groboclown@users.sourceforge.net
6  * http://groboutils.sourceforge.net
7  *
8  * Permission is hereby granted, free of charge, to any person obtaining a
9  * copy of this software and associated documentation files (the "Software"),
10  * to deal in the Software without restriction, including without limitation
11  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12  * and/or sell copies of the Software, and to permit persons to whom the
13  * Software is furnished to do so, subject to the following conditions:
14  *
15  * The above copyright notice and this permission notice shall be included in
16  * all copies or substantial portions of the Software.
17  *
18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24  * DEALINGS IN THE SOFTWARE.
25  */

26
27 package net.sourceforge.groboutils.codecoverage.v2.compiler;
28
29 import java.io.ByteArrayInputStream JavaDoc;
30 import java.io.ByteArrayOutputStream JavaDoc;
31 import java.io.IOException JavaDoc;
32 import java.util.ArrayList JavaDoc;
33 import java.util.HashMap JavaDoc;
34 import java.util.List JavaDoc;
35 import java.util.Map JavaDoc;
36
37 import net.sourceforge.groboutils.codecoverage.v2.datastore.AnalysisModuleSet;
38 import net.sourceforge.groboutils.codecoverage.v2.datastore.ClassRecord;
39 import net.sourceforge.groboutils.codecoverage.v2.util.ChecksumUtil;
40 import net.sourceforge.groboutils.codecoverage.v2.util.ClassSignatureUtil;
41
42 import org.apache.bcel.classfile.Attribute;
43 import org.apache.bcel.classfile.ClassParser;
44 import org.apache.bcel.classfile.JavaClass;
45 import org.apache.bcel.classfile.Method;
46 import org.apache.bcel.classfile.SourceFile;
47 import org.apache.bcel.generic.ConstantPoolGen;
48 import org.apache.bcel.generic.MethodGen;
49
50 /**
51  * Refers to a class file that has been modified with additional logging
52  * statements.
53  *
54  * @author Matt Albrecht <a HREF="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
55  * @version $Date: 2004/04/15 05:48:25 $
56  * @since December 17, 2002
57  */

58 public class ModifiedClass
59 {
60     private static final org.apache.log4j.Logger LOG =
61         org.apache.log4j.Logger.getLogger( ModifiedClass.class );
62     
63     // split these two strings apart so that this class itself can
64
// be post-compiled.
65
private static final String JavaDoc ALREADY_POST_COMPILED_FLAG_1 =
66         "--..AlreadyPostCompiled";
67     private static final String JavaDoc ALREADY_POST_COMPILED_FLAG_2 =
68         // had to switch this from a char 0 due to parsing errors in
69
// java - it doesn't like finding a '0' in a class file string.
70
"..--" + (char)1;
71     private static final String JavaDoc ALREADY_POST_COMPILED_FLAG;
72     static {
73         // don't let a compiler optimization create this string in this
74
// class, so that this class can be instrumented.
75
StringBuffer JavaDoc sb = new StringBuffer JavaDoc( ALREADY_POST_COMPILED_FLAG_1 );
76         sb.append( ALREADY_POST_COMPILED_FLAG_2 );
77         ALREADY_POST_COMPILED_FLAG = new String JavaDoc( sb );
78     }
79         
80     private String JavaDoc className;
81     private JavaClass origClass;
82     //private ClassGen modClass;
83
private ConstantPoolGen constantPool;
84     private ModifiedMethod[] modMethods;
85     private byte[] finalModifiedClass;
86     
87     /**
88      * constant pool index for the name of the class's signature.
89      */

90     private int classSigPoolIndex;
91     
92     /**
93      * constant pool index for the method-ref for invoking the logger.
94      */

95     private int staticMethodPoolIndex;
96     
97     /**
98      * the original class file's checksum.
99      */

100     private long checksum;
101     
102     /**
103      * We can't allow coverage of every method: some shouldn't be allowed,
104      * such as javac-generated methods like "class$".
105      */

106     private static final String JavaDoc[] IGNORE_METHODS = {
107             "class$(Ljava/lang/String;)Ljava/lang/Class;",
108         };
109     
110     
111     /**
112      * Uses the default settings for the <tt>ParseCoverageLogger</tt>
113      * during the creation of the modified class.
114      *
115      * @param filename the name of the file the class file was pulled from.
116      * @throws IllegalStateException if the class file has already been
117      * modified (identified by a class name field).
118      */

119     public ModifiedClass( String JavaDoc filename, byte[] originalClassFile )
120             throws IOException JavaDoc
121     {
122         this( new ParseCoverageLogger(), filename, originalClassFile );
123     }
124     
125     
126     /**
127      * @param pcl the definition for the logger to create method references
128      * for. This allows for easier testing and customizations of the
129      * logger to compile for.
130      * @param filename the name of the file the class file was pulled from.
131      * @throws IllegalStateException if the class file has already been
132      * modified (identified by a class name field).
133      */

134     public ModifiedClass( ParseCoverageLogger pcl, String JavaDoc filename,
135             byte[] originalClassFile )
136             throws IOException JavaDoc
137     {
138         if (originalClassFile == null || filename == null || pcl == null)
139         {
140             throw new IllegalArgumentException JavaDoc( "No null args." );
141         }
142         
143         updateChecksum( originalClassFile );
144         updateClassGen( pcl, originalClassFile, filename );
145     }
146     
147     
148     /**
149      *
150      */

151     public String JavaDoc getClassName()
152     {
153         return this.className;
154     }
155     
156     
157     /**
158      *
159      */

160     public long getClassCRC()
161     {
162         return this.checksum;
163     }
164     
165     
166     /**
167      *
168      */

169     public String JavaDoc getClassSignature()
170     {
171         return ClassSignatureUtil.getInstance().
172             createClassSignature( getClassName(), getClassCRC() );
173     }
174     
175     
176     /**
177      *
178      */

179     public String JavaDoc getSourceFileName()
180     {
181         String JavaDoc ret = "";
182         Attribute attr[] = this.origClass.getAttributes();
183         for (int i = 0; i < attr.length; ++i)
184         {
185             if (attr[i] instanceof SourceFile)
186             {
187                 ret = ((SourceFile)attr[i]).getSourceFileName();
188                 break;
189             }
190         }
191         return ret;
192     }
193     
194     
195     /**
196      *
197      */

198     public ClassRecord createClassRecord( AnalysisModuleSet ams )
199     {
200         ModifiedMethod mmL[] = getMethods();
201         String JavaDoc methodSigs[] = new String JavaDoc[ mmL.length ];
202         for (int i = 0; i < mmL.length; ++i)
203         {
204             methodSigs[ mmL[i].getMethodIndex() ] = mmL[i].getMethodName();
205         }
206         return new ClassRecord( getClassName(), getClassCRC(),
207             getSourceFileName(), methodSigs, ams );
208     }
209     
210     
211     /**
212      * Return all modifiable methods owned by the class. This will not return
213      * methods deemed non-modifiable, such as abstract or native methods, or
214      * javac-generated methods.
215      */

216     public ModifiedMethod[] getMethods()
217     {
218         if (this.modMethods == null)
219         {
220             checkClose();
221             
222             Method mL[] = this.origClass.getMethods();
223             List JavaDoc methods = new ArrayList JavaDoc();
224             short methodIndex = 0;
225             for (int i = 0; i < mL.length; ++i)
226             {
227                 if (allowModification( mL[i] ))
228                 {
229                     ModifiedMethod mm = new ModifiedMethod(
230                         methodIndex,
231                         this.classSigPoolIndex,
232                         this.staticMethodPoolIndex,
233                         this.origClass,
234                         mL[i],
235                         new MethodGen( mL[i], this.className, this.constantPool )
236                     );
237                     methods.add( mm );
238                     ++methodIndex;
239                 }
240             }
241             this.modMethods = (ModifiedMethod[])methods.toArray(
242                 new ModifiedMethod[ methods.size() ] );
243         }
244         return this.modMethods;
245     }
246     
247     
248     /**
249      * Returns the class in its current state, and closes off the modified
250      * class from further modifications.
251      */

252     public byte[] getModifiedClass()
253     {
254         if (this.finalModifiedClass == null)
255         {
256             checkClose();
257             
258             // commit the current changes to the generated class
259
updateClass();
260             
261             ByteArrayOutputStream JavaDoc baos = new ByteArrayOutputStream JavaDoc();
262             try
263             {
264                 this.origClass.dump( baos );
265             }
266             catch (IOException JavaDoc e)
267             {
268                 throw new IllegalStateException JavaDoc("This should never be reached.");
269             }
270             
271             // close off the modified class from further modification
272
this.origClass = null;
273             
274             this.finalModifiedClass = baos.toByteArray();
275         }
276         return this.finalModifiedClass;
277     }
278     
279     
280     /**
281      * String that gets placed within class files to flag that they've
282      * been post-compiled.
283      */

284     public static final String JavaDoc getPostCompiledSignature()
285     {
286         return ALREADY_POST_COMPILED_FLAG;
287     }
288     
289     
290     //-------------------------------------------------------------------------
291
// private members
292

293     
294     /**
295      * This method commits any changes to the class file, and closes the
296      * class off to any more changes. It can be called multiple times
297      * without error.
298      */

299     private void updateClass()
300     {
301         // update the methods
302
ModifiedMethod mL[] = this.modMethods;
303         if (mL == null)
304         {
305             // we've already committed the methods, or no modifications
306
// were done to the methods.
307
return;
308         }
309         
310         // we're committing the methods, so close off our list
311
this.modMethods = null;
312         
313         // create a map of all our modified methods.
314
Map JavaDoc mLmap = new HashMap JavaDoc();
315         for (int i = 0; i < mL.length; ++i)
316         {
317             // commit the methods and remove them from our list.
318
ModifiedMethod m = mL[i];
319             mL[i] = null;
320             m.close();
321             
322             mLmap.put( m.getMethodName(), m.getNewMethod() );
323         }
324         
325         // Replace the original methods with the modified methods.
326
Method origMethods[] = this.origClass.getMethods();
327         for (int i = 0; i < origMethods.length; ++i)
328         {
329             Method m = (Method)mLmap.get( createSignature( origMethods[i] ) );
330             if (m != null)
331             {
332                 LOG.debug( "replacing method ["+origMethods[i]+"]." );
333                 origMethods[i] = m;
334             }
335         }
336         this.origClass.setMethods( origMethods );
337         
338         this.origClass.setConstantPool(
339             this.constantPool.getFinalConstantPool() );
340     }
341     
342     
343     /**
344      *
345      */

346     private void updateClassGen( ParseCoverageLogger pcl, byte[] code,
347             String JavaDoc filename )
348             throws IOException JavaDoc
349     {
350         ByteArrayInputStream JavaDoc bais = new ByteArrayInputStream JavaDoc( code );
351         ClassParser cp = new ClassParser( bais, filename );
352         this.origClass = cp.parse();
353         this.className = this.origClass.getClassName();
354         //this.modClass = new ClassGen( this.origClass );
355
//this.modClass.setMajor( this.origClass.getMajor() );
356
//this.modClass.setMinor( this.origClass.getMinor() );
357
this.constantPool = new ConstantPoolGen(
358             this.origClass.getConstantPool() );
359         
360         addClassSignature( this.constantPool );
361         addMethodRef( pcl, this.constantPool );
362     }
363     
364     
365     
366     /**
367      * Adds the class signature to the constant pool of the class.
368      *
369      * @throws IllegalStateException if the class file has already been
370      * modified (identified by a class name field).
371      */

372     private void addClassSignature( ConstantPoolGen pool )
373     {
374         if (pool == null)
375         {
376             throw new IllegalArgumentException JavaDoc( "No null args." );
377         }
378         
379         // check if the class signature has already been set
380
// see bug 903837
381
if (pool.lookupUtf8( getPostCompiledSignature() ) != -1)
382         {
383             throw new AlreadyPostCompiledException(
384                 "Class '"+this.className+"' has already been modified." );
385         }
386         
387         // add constant pool name reference
388
this.classSigPoolIndex = pool.addString( getClassSignature() );
389         LOG.debug( "Added pool index "+this.classSigPoolIndex+" pointing to '"+
390             getClassSignature() );
391         
392         // and add the signature that the class has been post-compiled
393
pool.addString( getPostCompiledSignature() );
394     }
395     
396     
397     
398     /**
399      *
400      */

401     private void addMethodRef( ParseCoverageLogger pcl, ConstantPoolGen pool )
402     {
403         if (pool == null || pcl == null)
404         {
405             throw new IllegalArgumentException JavaDoc( "No null args." );
406         }
407         
408         LOG.debug( "Adding methodref to pool for method: "+
409             pcl.getClassName()+" # "+pcl.getMethodName() + ": "+
410             pcl.getMethodSignature() );
411         this.staticMethodPoolIndex = pool.addMethodref(
412             pcl.getClassName(),
413             pcl.getMethodName(),
414             pcl.getMethodSignature() );
415         LOG.debug( "Methodref pool index = "+this.staticMethodPoolIndex );
416     }
417     
418     
419     /**
420      *
421      */

422     private void updateChecksum( byte[] code )
423     {
424         if (code == null)
425         {
426             throw new IllegalArgumentException JavaDoc("no null args");
427         }
428         this.checksum = ChecksumUtil.getInstance().checksum( code );
429     }
430     
431     
432     /**
433      *
434      */

435     private void checkClose()
436     {
437         if (this.origClass == null)
438         {
439             throw new IllegalStateException JavaDoc(
440                 "Class has been closed from further modifications." );
441         }
442     }
443     
444     
445     /**
446      * Returns <tt>true</tt> if the method can be modified, otherwise returns
447      * <tt>false</tt>. A method can be modified only if it is not native,
448      * not abstract, it has a code attribute, and is not one of the "ignored
449      * methods" defined at the top of this class.
450      */

451     private boolean allowModification( Method m )
452     {
453         if (!isMarkable( m ))
454         {
455             return false;
456         }
457         
458         String JavaDoc sig = createSignature( m );
459         for (int i = 0; i < IGNORE_METHODS.length; ++i)
460         {
461             if (IGNORE_METHODS[i].equals( sig ))
462             {
463                 return false;
464             }
465         }
466         return true;
467     }
468     
469     
470     /**
471      * Checks if the given method is markable only from the context of the
472      * method contents, not by looking at the signature.
473      */

474     static boolean isMarkable( Method m )
475     {
476         return (!m.isNative() && !m.isAbstract() && m.getCode() != null);
477     }
478     
479     
480     /**
481      *
482      */

483     private String JavaDoc createSignature( Method m )
484     {
485         String JavaDoc sig = m.getName() + m.getSignature();
486         return sig;
487     }
488 }
489
490
Popular Tags