/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