/Users/lyon/j4p/src/javassist/ClassPool.java
|
1 /*
2 * Javassist, a Java-bytecode translator toolkit.
3 * Copyright (C) 1999-2003 Shigeru Chiba. All Rights Reserved.
4 *
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. Alternatively, the contents of this file may be used under
8 * the terms of the GNU Lesser General Public License Version 2.1 or later.
9 *
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
14 */
15
16 package javassist;
17
18 import java.io.*;
19 import java.util.Hashtable;
20
21 /**
22 * A driver class for controlling bytecode editing with Javassist.
23 * It manages where a class file is obtained and how it is modified.
24 *
25 * <p>A <code>ClassPool</code> object can be regarded as a container
26 * of <code>CtClass</code> objects. It reads class files on demand
27 * from various
28 * sources represented by <code>ClassPath</code> and create
29 * <code>CtClass</code> objects representing those class files.
30 * The source may be another <code>ClassPool</code>. If so,
31 * <code>write()</code> is called on the source <code>ClassPool</code>
32 * for obtaining a class file.
33 *
34 * <p>A <code>CtClass</code>
35 * object contained in a <code>ClassPool</code> is written to an
36 * output stream (or a file) if <code>write()</code>
37 * (or <code>writeFile()</code>) is called on the
38 * <code>ClassPool</code>.
39 * <code>write()</code> is typically called by a class loader,
40 * which obtains the bytecode image to be loaded.
41 *
42 * <p>The users can modify <code>CtClass</code> objects
43 * before those objects are written out.
44 * To obtain a reference
45 * to a <code>CtClass</code> object contained in a
46 * <code>ClassPool</code>, <code>get()</code> should be
47 * called on the <code>ClassPool</code>. If a <code>CtClass</code>
48 * object is modified, then the modification is reflected on the resulting
49 * class file returned by <code>write()</code> in <code>ClassPool</code>.
50 *
51 * <p>In summary,
52 *
53 * <ul>
54 * <li><code>get()</code> returns a reference to a <code>CtClass</code>
55 * object contained in a <code>ClassPool</code>.
56 *
57 * <li><code>write()</code> translates a <code>CtClass</code>
58 * object contained in a <code>ClassPool</code> into a class file
59 * and writes it to an output stream.
60 * </ul>
61 *
62 * <p>The users can add a listener object receiving an event from a
63 * <code>ClassPool</code>. An event occurs when a listener is
64 * added to a <code>ClassPool</code> and when <code>write()</code>
65 * is called on a <code>ClassPool</code>. The listener class
66 * must implement <code>Translator</code>. A typical listener object
67 * is used for modifying a <code>CtClass</code> object <i>on demand</i>
68 * when it is written to an output stream.
69 *
70 * <p>The implementation of this class is thread-safe.
71 *
72 * @see javassist.CtClass
73 * @see javassist.ClassPath
74 * @see javassist.Translator
75 */
76 public class ClassPool {
77 /* If this field is null, then the object must be an instance of
78 * ClassPoolTail.
79 */
80 protected ClassPool source;
81
82 protected Translator translator;
83
84 protected Hashtable classes; // should be synchronous
85
86 /**
87 * Provide a hook so that subclasses can do their own
88 * caching of classes
89 *
90 * @see #removeCached(String)
91 */
92 protected CtClass getCached(String classname) {
93 return (CtClass) classes.get(classname);
94 }
95
96 /**
97 * Provide a hook so that subclasses can do their own
98 * caching of classes
99 *
100 * @see #getCached(String)
101 */
102 protected void removeCached(String classname) {
103 classes.remove(classname);
104 }
105
106 /**
107 * Creates a class pool.
108 *
109 * @param src the source of class files. If it is null,
110 * the class search path is initially null.
111 * @see javassist.ClassPool#getDefault()
112 */
113 public ClassPool(ClassPool src) {
114 this(src, null);
115 }
116
117 /**
118 * Creates a class pool.
119 *
120 * @param src the source of class files. If it is null,
121 * the class search path is initially null.
122 * @param trans the translator linked to this class pool.
123 * It may be null.
124 * @see javassist.ClassPool#getDefault()
125 */
126 public ClassPool(ClassPool src, Translator trans)
127 throws RuntimeException {
128 classes = new Hashtable();
129 CtClass[] pt = CtClass.primitiveTypes;
130 for (int i = 0; i < pt.length; ++i)
131 classes.put(pt[i].getName(), pt[i]);
132
133 if (src != null)
134 source = src;
135 else
136 source = new ClassPoolTail();
137
138 translator = trans;
139 if (trans != null)
140 try {
141 trans.start(this);
142 } catch (Exception e) {
143 throw new RuntimeException(
144 "Translator.start() throws an exception: "
145 + e.toString());
146 }
147 }
148
149 protected ClassPool() {
150 source = null;
151 classes = null;
152 translator = null;
153 }
154
155 /**
156 * Returns the default class pool.
157 * The returned object is always identical.
158 *
159 * <p>The default class pool searches the system search path,
160 * which usually includes the platform library, extension
161 * libraries, and the search path specified by the
162 * <code>-classpath</code> option or the <code>CLASSPATH</code>
163 * environment variable.
164 *
165 * @param t null or the translator linked to the class pool.
166 */
167 public static synchronized ClassPool getDefault(Translator t) {
168 if (defaultPool == null) {
169 ClassPoolTail tail = new ClassPoolTail();
170 tail.appendSystemPath();
171 defaultPool = new ClassPool(tail, t);
172 }
173
174 return defaultPool;
175 }
176
177 private static ClassPool defaultPool = null;
178
179 /**
180 * Returns the default class pool.
181 * The returned object is always identical.
182 *
183 * <p>This returns the result of <code>getDefault(null)</code>.
184 *
185 * @see #getDefault(Translator)
186 */
187 public static ClassPool getDefault() {
188 return getDefault(null);
189 }
190
191 /**
192 * Returns the class search path.
193 */
194 public String toString() {
195 return source.toString();
196 }
197
198 /**
199 * Returns the <code>Translator</code> object associated with
200 * this <code>ClassPool</code>.
201 */
202 public Translator getTranslator() {
203 return translator;
204 }
205
206 /**
207 * Table of registered cflow variables.
208 */
209 private Hashtable cflow = null; // should be synchronous.
210
211 /**
212 * Records the <code>$cflow</code> variable for the field specified
213 * by <code>cname</code> and <code>fname</code>.
214 *
215 * @param name variable name
216 * @param cname class name
217 * @param fname field name
218 */
219 void recordCflow(String name, String cname, String fname) {
220 if (cflow == null)
221 cflow = new Hashtable();
222
223 cflow.put(name, new Object[]{cname, fname});
224 }
225
226 /**
227 * Undocumented method. Do not use; internal-use only.
228 *
229 * @param name the name of <code>$cflow</code> variable
230 */
231 public Object[] lookupCflow(String name) {
232 if (cflow == null)
233 cflow = new Hashtable();
234
235 return (Object[]) cflow.get(name);
236 }
237
238 /**
239 * Writes a class file specified with <code>classname</code>
240 * in the current directory.
241 * It never calls <code>onWrite()</code> on a translator.
242 * It is provided for debugging.
243 *
244 * @param classname the name of the class written on a local disk.
245 */
246 public void debugWriteFile(String classname)
247 throws NotFoundException, CannotCompileException, IOException {
248 debugWriteFile(classname, ".");
249 }
250
251 /**
252 * Writes a class file specified with <code>classname</code>.
253 * It never calls <code>onWrite()</code> on a translator.
254 * It is provided for debugging.
255 *
256 * @param classname the name of the class written on a local disk.
257 * @param directoryName it must end without a directory separator.
258 */
259 public void debugWriteFile(String classname, String directoryName)
260 throws NotFoundException, CannotCompileException, IOException {
261 writeFile(classname, directoryName, false);
262 }
263
264 /* void writeFile(CtClass) should not be defined since writeFile()
265 * may be called on the class pool that does not contain the given
266 * CtClass object.
267 */
268
269 /**
270 * Writes a class file specified with <code>classname</code>
271 * in the current directory.
272 * It calls <code>onWrite()</code> on a translator.
273 *
274 * @param classname the name of the class written on a local disk.
275 */
276 public void writeFile(String classname)
277 throws NotFoundException, CannotCompileException, IOException {
278 writeFile(classname, ".");
279 }
280
281 /**
282 * Writes a class file specified with <code>classname</code>
283 * on a local disk.
284 * It calls <code>onWrite()</code> on a translator.
285 *
286 * @param classname the name of the class written on a local disk.
287 * @param directoryName it must end without a directory separator.
288 */
289 public void writeFile(String classname, String directoryName)
290 throws NotFoundException, CannotCompileException, IOException {
291 writeFile(classname, directoryName, true);
292 }
293
294 private void writeFile(String classname, String directoryName,
295 boolean callback)
296 throws NotFoundException, CannotCompileException, IOException {
297 String filename = directoryName + File.separatorChar
298 + classname.replace('.', File.separatorChar) + ".class";
299 int pos = filename.lastIndexOf(File.separatorChar);
300 if (pos > 0) {
301 String dir = filename.substring(0, pos);
302 if (!dir.equals("."))
303 new File(dir).mkdirs();
304 }
305
306 DataOutputStream out
307 = new DataOutputStream(new BufferedOutputStream(
308 new DelayedFileOutputStream(filename)));
309 write(classname, out, callback);
310 out.close();
311 }
312
313 static class DelayedFileOutputStream extends OutputStream {
314 private FileOutputStream file;
315 private String filename;
316
317 DelayedFileOutputStream(String name) {
318 file = null;
319 filename = name;
320 }
321
322 private void init() throws IOException {
323 if (file == null)
324 file = new FileOutputStream(filename);
325 }
326
327 public void write(int b) throws IOException {
328 init();
329 file.write(b);
330 }
331
332 public void write(byte[] b) throws IOException {
333 init();
334 file.write(b);
335 }
336
337 public void write(byte[] b, int off, int len) throws IOException {
338 init();
339 file.write(b, off, len);
340
341 }
342
343 public void flush() throws IOException {
344 init();
345 file.flush();
346 }
347
348 public void close() throws IOException {
349 init();
350 file.close();
351 }
352 }
353
354 static class LocalClassLoader extends ClassLoader {
355 public Class loadClass(String name, byte[] classfile)
356 throws ClassFormatError {
357 Class c = defineClass(name, classfile, 0, classfile.length);
358 resolveClass(c);
359 return c;
360 }
361 };
362
363 private static LocalClassLoader classLoader = new LocalClassLoader();
364
365 /**
366 * Returns a <code>java.lang.Class</code> object that has been loaded
367 * by <code>writeAsClass()</code>. That object cannot be
368 * obtained by <code>java.lang.Class.forName()</code> because it has
369 * been loaded by an internal class loader of Javassist.
370 *
371 * @see #writeAsClass(String)
372 * @see javassist.CtClass#toClass()
373 */
374 public static Class forName(String name) throws ClassNotFoundException {
375 return classLoader.loadClass(name);
376 }
377
378 /**
379 * Returns a <code>java.lang.Class</code> object.
380 * It calls <code>write()</code> to obtain a class file and then
381 * loads the obtained class file into the JVM. The returned
382 * <code>Class</code> object represents the loaded class.
383 *
384 * <p>This method is provided for convenience. If you need more
385 * complex functionality, you should write your own class loader.
386 *
387 * <p>To load a class file, this method uses an internal class loader.
388 * Thus, that class file is not loaded by the system class loader,
389 * which should have loaded this <code>ClassPool</code> class.
390 * The internal class loader
391 * loads only the classes explicitly specified by this method
392 * <code>writeAsClass()</code>. The other classes are loaded
393 * by the parent class loader (the sytem class loader) by delegation.
394 *
395 * <p>For example,
396 *
397 * <ul><pre>class Line { Point p1, p2; }</pre></ul>
398 *
399 * <p>If the class <code>Line</code> is loaded by the internal class
400 * loader and the class <code>Point</code> has not been loaded yet,
401 * then the class <code>Point</code> that the class <code>Line</code>
402 * refers to is loaded by the parent class loader. There is no
403 * chance of modifying the definition of <code>Point</code> with
404 * Javassist.
405 *
406 * <p>The internal class loader is shared among all the instances
407 * of <code>ClassPool</code>.
408 *
409 * @param classname a fully-qualified class name.
410 *
411 * @see #forName(String)
412 * @see javassist.CtClass#toClass()
413 * @see javassist.Loader
414 */
415 public Class writeAsClass(String classname)
416 throws NotFoundException, IOException, CannotCompileException {
417 try {
418 return classLoader.loadClass(classname, write(classname));
419 } catch (ClassFormatError e) {
420 throw new CannotCompileException(e, classname);
421 }
422 }
423
424 /**
425 * Returns a byte array representing the class file.
426 * It calls <code>onWrite()</code> on a translator.
427 *
428 * @param classname a fully-qualified class name.
429 */
430 public byte[] write(String classname)
431 throws NotFoundException, IOException, CannotCompileException {
432 ByteArrayOutputStream barray = new ByteArrayOutputStream();
433 DataOutputStream out = new DataOutputStream(barray);
434 try {
435 write(classname, out, true);
436 } finally {
437 out.close();
438 }
439
440 return barray.toByteArray();
441 }
442
443 /**
444 * Writes a class file specified by <code>classname</code>
445 * to a given output stream.
446 * It calls <code>onWrite()</code> on a translator.
447 *
448 * <p>This method does not close the output stream in the end.
449 *
450 * @param classname a fully-qualified class name.
451 * @param out an output stream
452 */
453 public void write(String classname, DataOutputStream out)
454 throws NotFoundException, CannotCompileException, IOException {
455 write(classname, out, true);
456 }
457
458 private void write(String classname, DataOutputStream out,
459 boolean callback)
460 throws NotFoundException, CannotCompileException, IOException {
461 CtClass clazz = (CtClass) getCached(classname);
462 if (callback && translator != null
463 && (clazz == null || !clazz.isFrozen())) {
464 translator.onWrite(this, classname);
465 // The CtClass object might be overwritten.
466 clazz = (CtClass) getCached(classname);
467 }
468
469 if (clazz == null || !clazz.isModified()) {
470 if (clazz != null)
471 clazz.freeze();
472
473 source.write(classname, out);
474 } else
475 clazz.toBytecode(out);
476 }
477
478 /* for CtClassType.getClassFile2()
479 */
480 byte[] readSource(String classname)
481 throws NotFoundException, IOException, CannotCompileException {
482 return source.write(classname);
483 }
484
485 /*
486 * Is invoked by CtClassType.setName().
487 */
488 synchronized void classNameChanged(String oldname, CtClass clazz) {
489 CtClass c = (CtClass) getCached(oldname);
490 if (c == clazz) // must check this equation
491 removeCached(oldname);
492
493 String newName = clazz.getName();
494 checkNotFrozen(newName, "the class with the new name is frozen.");
495 classes.put(newName, clazz);
496 }
497
498 /*
499 * Is invoked by CtClassType.setName() and methods in this class.
500 */
501 void checkNotFrozen(String classname, String errmsg)
502 throws RuntimeException {
503 CtClass c = (CtClass) classes.get(classname);
504 if (c != null && c.isFrozen())
505 throw new RuntimeException(errmsg);
506 }
507
508 /**
509 * Reads a class file and constructs a <code>CtClass</code>
510 * object with a new name.
511 * This method is useful if that class file has been already
512 * loaded and the resulting class is frozen.
513 *
514 * @param orgName the original (fully-qualified) class name
515 * @param newName the new class name
516 */
517 public CtClass getAndRename(String orgName, String newName)
518 throws NotFoundException {
519 CtClass clazz = get0(orgName);
520 clazz.setName(newName); // indirectly calls
521 // classNameChanged() in this class
522 return clazz;
523 }
524
525 /**
526 * Reads a class file from the source and returns a reference
527 * to the <code>CtClass</code>
528 * object representing that class file. If that class file has been
529 * already read, this method returns a reference to the
530 * <code>CtClass</code> created when that class file was read at the
531 * first time.
532 *
533 * <p>If <code>classname</code> ends with "[]", then this method
534 * returns a <code>CtClass</code> object for that array type.
535 *
536 * @param classname a fully-qualified class name.
537 */
538 public synchronized CtClass get(String classname)
539 throws NotFoundException {
540 CtClass clazz = (CtClass) classes.get(classname);
541 if (clazz == null) {
542 clazz = get0(classname);
543 classes.put(classname, clazz);
544 }
545
546 return clazz;
547 }
548
549 protected CtClass get0(String classname) throws NotFoundException {
550 if (classname.endsWith("[]"))
551 return new CtArray(classname, this);
552 else {
553 checkClassName(classname);
554 return new CtClassType(classname, this);
555 }
556 }
557
558 /**
559 * Reads class files from the source and returns an array of
560 * <code>CtClass</code>
561 * objects representing those class files.
562 *
563 * <p>If an element of <code>classnames</code> ends with "[]",
564 * then this method
565 * returns a <code>CtClass</code> object for that array type.
566 *
567 * @param classnames an array of fully-qualified class name.
568 */
569 public CtClass[] get(String[] classnames) throws NotFoundException {
570 if (classnames == null)
571 return new CtClass[0];
572
573 int num = classnames.length;
574 CtClass[] result = new CtClass[num];
575 for (int i = 0; i < num; ++i)
576 result[i] = get(classnames[i]);
577
578 return result;
579 }
580
581 /**
582 * Reads a class file and obtains a compile-time method.
583 *
584 * @param classname the class name
585 * @param methodname the method name
586 *
587 * @see CtClass#getDeclaredMethod(String)
588 */
589 public CtMethod getMethod(String classname, String methodname)
590 throws NotFoundException {
591 CtClass c = get(classname);
592 return c.getDeclaredMethod(methodname);
593 }
594
595 /**
596 * Creates a new class from the given class file.
597 * If there already exists a class with the same name, the new class
598 * overwrites that previous class.
599 *
600 * <p>This method is used for creating a <code>CtClass</code> object
601 * directly from a class file. The qualified class name is obtained
602 * from the class file; you do not have to explicitly give the name.
603 *
604 * @param classfile class file.
605 * @exception RuntimeException if there is a frozen class with the
606 * the same name.
607 */
608 public CtClass makeClass(InputStream classfile)
609 throws IOException, RuntimeException {
610 CtClass clazz = new CtClassType(classfile, this);
611 clazz.checkModify();
612 String classname = clazz.getName();
613 checkNotFrozen(classname,
614 "there is a frozen class with the same name.");
615 classes.put(classname, clazz);
616 return clazz;
617 }
618
619 /**
620 * Creates a new public class.
621 * If there already exists a class with the same name, the new class
622 * overwrites that previous class.
623 *
624 * @param classname a fully-qualified class name.
625 * @exception RuntimeException if the existing class is frozen.
626 */
627 public CtClass makeClass(String classname) throws RuntimeException {
628 return makeClass(classname, null);
629 }
630
631 /**
632 * Creates a new public class.
633 * If there already exists a class/interface with the same name,
634 * the new class overwrites that previous class.
635 *
636 * @param classname a fully-qualified class name.
637 * @param superclass the super class.
638 * @exception RuntimeException if the existing class is frozen.
639 */
640 public synchronized CtClass makeClass(String classname, CtClass superclass)
641 throws RuntimeException {
642 checkNotFrozen(classname,
643 "the class with the given name is frozen.");
644 CtClass clazz = new CtNewClass(classname, this, false, superclass);
645 classes.put(classname, clazz);
646 return clazz;
647 }
648
649 /**
650 * Creates a new public interface.
651 * If there already exists a class/interface with the same name,
652 * the new interface overwrites that previous one.
653 *
654 * @param name a fully-qualified interface name.
655 * @exception RuntimeException if the existing interface is frozen.
656 */
657 public CtClass makeInterface(String name) throws RuntimeException {
658 return makeInterface(name, null);
659 }
660
661 /**
662 * Creates a new public interface.
663 * If there already exists a class/interface with the same name,
664 * the new interface overwrites that previous one.
665 *
666 * @param name a fully-qualified interface name.
667 * @param superclass the super interface.
668 * @exception RuntimeException if the existing interface is frozen.
669 */
670 public synchronized CtClass makeInterface(String name, CtClass superclass)
671 throws RuntimeException {
672 checkNotFrozen(name,
673 "the interface with the given name is frozen.");
674 CtClass clazz = new CtNewClass(name, this, true, superclass);
675 classes.put(name, clazz);
676 return clazz;
677 }
678
679 /**
680 * Throws an exception if the class with the specified name does not
681 * exist.
682 */
683 void checkClassName(String classname)
684 throws NotFoundException {
685 source.checkClassName(classname);
686 }
687
688 /**
689 * Appends the system search path to the end of the
690 * search path. The system search path
691 * usually includes the platform library, extension
692 * libraries, and the search path specified by the
693 * <code>-classpath</code> option or the <code>CLASSPATH</code>
694 * environment variable.
695 *
696 * @return the appended class path.
697 */
698 public ClassPath appendSystemPath() {
699 return source.appendSystemPath();
700 }
701
702 /**
703 * Insert a <code>ClassPath</code> object at the head of the
704 * search path.
705 *
706 * @return the inserted class path.
707 *
708 * @see javassist.ClassPath
709 * @see javassist.URLClassPath
710 * @see javassist.ByteArrayClassPath
711 */
712 public ClassPath insertClassPath(ClassPath cp) {
713 return source.insertClassPath(cp);
714 }
715
716 /**
717 * Appends a <code>ClassPath</code> object to the end of the
718 * search path.
719 *
720 * @return the appended class path.
721 *
722 * @see javassist.ClassPath
723 * @see javassist.URLClassPath
724 * @see javassist.ByteArrayClassPath
725 */
726 public ClassPath appendClassPath(ClassPath cp) {
727 return source.appendClassPath(cp);
728 }
729
730 /**
731 * Inserts a directory or a jar (or zip) file at the head of the
732 * search path.
733 *
734 * @param pathname the path name of the directory or jar file.
735 * It must not end with a path separator ("/").
736 * @return the inserted class path.
737 * @exception NotFoundException if the jar file is not found.
738 */
739 public ClassPath insertClassPath(String pathname)
740 throws NotFoundException {
741 return source.insertClassPath(pathname);
742 }
743
744 /**
745 * Appends a directory or a jar (or zip) file to the end of the
746 * search path.
747 *
748 * @param pathname the path name of the directory or jar file.
749 * It must not end with a path separator ("/").
750 * @return the appended class path.
751 * @exception NotFoundException if the jar file is not found.
752 */
753 public ClassPath appendClassPath(String pathname)
754 throws NotFoundException {
755 return source.appendClassPath(pathname);
756 }
757
758 /**
759 * Detatches the <code>ClassPath</code> object from the search path.
760 * The detached <code>ClassPath</code> object cannot be added
761 * to the pathagain.
762 */
763 public synchronized void removeClassPath(ClassPath cp) {
764 source.removeClassPath(cp);
765 }
766
767 /**
768 * Appends directories and jar files for search.
769 *
770 * <p>The elements of the given path list must be separated by colons
771 * in Unix or semi-colons in Windows.
772 *
773 * @param pathlist a (semi)colon-separated list of
774 * the path names of directories and jar files.
775 * The directory name must not end with a path
776 * separator ("/").
777 *
778 * @exception NotFoundException if a jar file is not found.
779 */
780 public void appendPathList(String pathlist) throws NotFoundException {
781 char sep = File.pathSeparatorChar;
782 int i = 0;
783 for (; ;) {
784 int j = pathlist.indexOf(sep, i);
785 if (j < 0) {
786 appendClassPath(pathlist.substring(i));
787 break;
788 } else {
789 appendClassPath(pathlist.substring(i, j));
790 i = j + 1;
791 }
792 }
793 }
794 }
795
796