KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sleepycat > persist > model > ClassEnhancer


1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 2002,2006 Oracle. All rights reserved.
5  *
6  * $Id: ClassEnhancer.java,v 1.14 2006/10/30 21:14:33 bostic Exp $
7  */

8
9 package com.sleepycat.persist.model;
10
11 import java.io.File JavaDoc;
12 import java.io.FileInputStream JavaDoc;
13 import java.io.FileOutputStream JavaDoc;
14 import java.io.IOException JavaDoc;
15 import java.lang.instrument.ClassFileTransformer JavaDoc;
16 import java.lang.instrument.Instrumentation JavaDoc;
17 import java.security.ProtectionDomain JavaDoc;
18 import java.util.ArrayList JavaDoc;
19 import java.util.HashSet JavaDoc;
20 import java.util.List JavaDoc;
21 import java.util.Set JavaDoc;
22 import java.util.StringTokenizer JavaDoc;
23
24 import com.sleepycat.asm.ClassReader;
25 import com.sleepycat.asm.ClassVisitor;
26 import com.sleepycat.asm.ClassWriter;
27
28 /**
29  * Enhances the bytecode of persistent classes to provide efficient access to
30  * fields and constructors, and to avoid special security policy settings for
31  * accessing non-public members. Classes are enhanced if they are annotated
32  * with {@link Entity} or {@link Persistent}.
33  *
34  * <p>{@code ClassEnhancer} objects are thread-safe. Multiple threads may
35  * safely call the methods of a shared {@code ClassEnhancer} object.</p>
36  *
37  * <p>As described in the {@link <a
38  * HREF="../package-summary.html#bytecode">package summary</a>}, bytecode
39  * enhancement may be used either at runtime or offline (at build time).</p>
40  *
41  * <p>To use enhancement offline, this class may be used as a {@link #main main
42  * program} or via an {@link ClassEnhancerTask ant task}.</p>
43  *
44  * <p>For enhancement at runtime, this class provides the low level support
45  * needed to transform class bytes during class loading. To configure runtime
46  * enhancement you may use one of the following approaches:</p>
47  * <ol>
48  * <li>For Java 1.5, the {@code je-<version>.jar} file may be used as an instrumentation
49  * agent as follows:
50  * <pre class="code">{@literal java -javaagent:lib/je-<version>.jar=enhance:packageNames ...}</pre>
51  * {@code packageNames} is a comma separated list of packages containing
52  * persistent classes. Sub-packages of these packages are also searched. If
53  * {@code packageNames} is omitted then all packages known to the current
54  * classloader are searched.
55  * <p>The "-v" option may be included in the comma separated list to print the
56  * name of each class that is enhanced.</p></li>
57  * <br>
58  * <li>The {@link #enhance} method may be called to implement a class loader
59  * that performs enhancement. Using this approach, it is the developer's
60  * responsibility to implement and configure the class loader.</li>
61  * </ol>
62  *
63  * @author Mark Hayes
64  */

65 public class ClassEnhancer implements ClassFileTransformer JavaDoc {
66
67     private static final String JavaDoc AGENT_PREFIX = "enhance:";
68
69     private Set JavaDoc<String JavaDoc> packagePrefixes;
70     private boolean verbose;
71
72     /**
73      * Enhances classes in the directories specified. The class files are
74      * replaced when they are enhanced, without changing the file modification
75      * date. For example:
76      *
77      * <pre class="code">java -cp je-&lt;version&gt;.jar com.sleepycat.persist.model.ClassEnhancer ./classes</pre>
78      *
79      * <p>The "-v" argument may be specified to print the name of each class
80      * file that is enhanced. The total number of class files enhanced will
81      * always be printed.</p>
82      *
83      * @param args one or more directories containing classes to be enhanced.
84      * Subdirectories of these directories will also be searched. Optionally,
85      * -v may be included to print the name of every class file enhanced.
86      */

87     public static void main(String JavaDoc[] args) throws Exception JavaDoc {
88         try {
89             boolean verbose = false;
90             List JavaDoc<File JavaDoc> fileList = new ArrayList JavaDoc<File JavaDoc>();
91             for (int i = 0; i < args.length; i += 1) {
92                 String JavaDoc arg = args[i];
93                 if (arg.startsWith("-")) {
94                     if ("-v".equals(args[i])) {
95                         verbose = true;
96                     } else {
97                         throw new IllegalArgumentException JavaDoc
98                             ("Unknown arg: " + arg);
99                     }
100                 } else {
101                     fileList.add(new File JavaDoc(arg));
102                 }
103             }
104             ClassEnhancer enhancer = new ClassEnhancer();
105             enhancer.setVerbose(verbose);
106             int nFiles = 0;
107             for (File JavaDoc file : fileList) {
108                 nFiles += enhancer.enhanceFile(file);
109             }
110             if (nFiles > 0) {
111                 System.out.println("Enhanced: " + nFiles + " files");
112             }
113         } catch (Exception JavaDoc e) {
114             e.printStackTrace();
115             throw e;
116         }
117     }
118
119     /**
120      * Enhances classes as specified by a JVM -javaagent argument.
121      *
122      * @see java.lang.instrument
123      */

124     public static void premain(String JavaDoc args, Instrumentation JavaDoc inst) {
125         if (!args.startsWith(AGENT_PREFIX)) {
126             throw new IllegalArgumentException JavaDoc
127                 ("Unknown javaagent args: " + args +
128                  " Args must start with: \"" + AGENT_PREFIX + '"');
129         }
130         args = args.substring(AGENT_PREFIX.length());
131         Set JavaDoc<String JavaDoc> packageNames = null;
132         boolean verbose = false;
133         if (args.length() > 0) {
134             packageNames = new HashSet JavaDoc<String JavaDoc>();
135             StringTokenizer JavaDoc tokens = new StringTokenizer JavaDoc(args, ",");
136             while (tokens.hasMoreTokens()) {
137                 String JavaDoc token = tokens.nextToken();
138                 if (token.startsWith("-")) {
139                     if (token.equals("-v")) {
140                         verbose = true;
141                     } else {
142                         throw new IllegalArgumentException JavaDoc
143                             ("Unknown javaagent arg: " + token);
144                     }
145                 } else {
146                     packageNames.add(token);
147                 }
148             }
149         }
150         ClassEnhancer enhancer = new ClassEnhancer(packageNames);
151         enhancer.setVerbose(verbose);
152         inst.addTransformer(enhancer);
153     }
154
155     /**
156      * Creates a class enhancer that searches all packages.
157      */

158     public ClassEnhancer() {
159     }
160
161     /**
162      * Sets verbose mode.
163      *
164      * <p>True may be specified to print the name of each class file that is
165      * enhanced. This property is false by default.</p>
166      */

167     public void setVerbose(boolean verbose) {
168         this.verbose = verbose;
169     }
170
171     /**
172      * Gets verbose mode.
173      *
174      * @see #setVerbose
175      */

176     public boolean getVerbose() {
177         return verbose;
178     }
179
180     /**
181      * Creates a class enhancer that searches a given set of packages.
182      *
183      * @param packageNames a set of packages to search for persistent
184      * classes. Sub-packages of these packages are also searched. If empty or
185      * null, all packages known to the current classloader are searched.
186      */

187     public ClassEnhancer(Set JavaDoc<String JavaDoc> packageNames) {
188         if (packageNames != null) {
189             packagePrefixes = new HashSet JavaDoc<String JavaDoc>();
190             for (String JavaDoc name : packageNames) {
191                 packagePrefixes.add(name + '.');
192             }
193         }
194     }
195
196     public byte[] transform(ClassLoader JavaDoc loader,
197                             String JavaDoc className,
198                             Class JavaDoc<?> classBeingRedefined,
199                             ProtectionDomain JavaDoc protectionDomain,
200                             byte[] classfileBuffer) {
201         className = className.replace('/', '.');
202         byte[] bytes = enhance(className, classfileBuffer);
203         if (verbose && bytes != null) {
204             System.out.println("Enhanced: " + className);
205         }
206         return bytes;
207     }
208
209     /**
210      * Enhances the given class bytes if the class is annotated with {@link
211      * Entity} or {@link Persistent}.
212      *
213      * @param className the class name in binary format; for example,
214      * "my.package.MyClass$Name", or null if no filtering by class name
215      * should be performed.
216      *
217      * @param classBytes are the class file bytes to be enhanced.
218      *
219      * @return the enhanced bytes, or null if no enhancement was performed.
220      */

221     public byte[] enhance(String JavaDoc className, byte[] classBytes) {
222         if (className != null && packagePrefixes != null) {
223             for (String JavaDoc prefix : packagePrefixes) {
224                 if (className.startsWith(prefix)) {
225                     return enhanceBytes(classBytes);
226                 }
227             }
228             return null;
229         } else {
230             return enhanceBytes(classBytes);
231         }
232     }
233
234     int enhanceFile(File JavaDoc file)
235         throws IOException JavaDoc {
236
237         int nFiles = 0;
238         if (file.isDirectory()) {
239             String JavaDoc[] names = file.list();
240             if (names != null) {
241                 for (int i = 0; i < names.length; i += 1) {
242                     nFiles += enhanceFile(new File JavaDoc(file, names[i]));
243                 }
244             }
245         } else if (file.getName().endsWith(".class")) {
246             byte[] newBytes = enhanceBytes(readFile(file));
247             if (newBytes != null) {
248                 long modified = file.lastModified();
249                 writeFile(file, newBytes);
250                 file.setLastModified(modified);
251                 nFiles += 1;
252                 if (verbose) {
253                     System.out.println("Enhanced: " + file);
254                 }
255             }
256         }
257         return nFiles;
258     }
259
260     private byte[] readFile(File JavaDoc file)
261         throws IOException JavaDoc {
262
263         byte[] bytes = new byte[(int) file.length()];
264         FileInputStream JavaDoc in = new FileInputStream JavaDoc(file);
265         try {
266             in.read(bytes);
267         } finally {
268             in.close();
269         }
270         return bytes;
271     }
272
273     private void writeFile(File JavaDoc file, byte[] bytes)
274         throws IOException JavaDoc {
275
276         FileOutputStream JavaDoc out = new FileOutputStream JavaDoc(file);
277         try {
278             out.write(bytes);
279         } finally {
280             out.close();
281         }
282     }
283
284     private byte[] enhanceBytes(byte[] bytes) {
285         
286         /*
287          * The writer is at the end of the visitor chain. Pass true to
288          * calculate stack size, for safety.
289          */

290         ClassWriter writer = new ClassWriter(true);
291         ClassVisitor visitor = writer;
292
293         /* The enhancer is at the beginning of the visitor chain. */
294         visitor = new BytecodeEnhancer(visitor);
295
296         /* The reader processes the class and invokes the visitors. */
297         ClassReader reader = new ClassReader(bytes);
298         try {
299
300             /*
301              * Pass false for skipDebug since we are rewriting the class and
302              * should include all information.
303              */

304             reader.accept(visitor, false);
305             return writer.toByteArray();
306         } catch (BytecodeEnhancer.NotPersistentException e) {
307             /* The class is not persistent and should not be enhanced. */
308             return null;
309         }
310     }
311 }
312
Popular Tags