1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
19 package org.apache.tools.ant;
20
21 import java.io.ByteArrayOutputStream;
22 import java.io.File;
23 import java.io.FileInputStream;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.InputStreamReader;
27 import java.io.Reader;
28 import java.lang.reflect.Constructor;
29 import java.net.MalformedURLException;
30 import java.net.URL;
31 import java.util.Collections;
32 import java.util.Enumeration;
33 import java.util.HashMap;
34 import java.util.Hashtable;
35 import java.util.Map;
36 import java.util.StringTokenizer;
37 import java.util.Vector;
38 import java.util.Locale;
39 import java.util.jar.Attributes;
40 import java.util.jar.Attributes.Name;
41 import java.util.jar.JarFile;
42 import java.util.jar.Manifest;
43 import java.util.zip.ZipEntry;
44 import java.util.zip.ZipFile;
45 import org.apache.tools.ant.types.Path;
46 import org.apache.tools.ant.util.CollectionUtils;
47 import org.apache.tools.ant.util.FileUtils;
48 import org.apache.tools.ant.util.JavaEnvUtils;
49 import org.apache.tools.ant.util.LoaderUtils;
50 import org.apache.tools.ant.launch.Locator;
51
52 /**
53 * Used to load classes within ant with a different classpath from
54 * that used to start ant. Note that it is possible to force a class
55 * into this loader even when that class is on the system classpath by
56 * using the forceLoadClass method. Any subsequent classes loaded by that
57 * class will then use this loader rather than the system class loader.
58 *
59 * <p>
60 * Note that this classloader has a feature to allow loading
61 * in reverse order and for "isolation".
62 * Due to the fact that a number of
63 * methods in java.lang.ClassLoader are final (at least
64 * in java 1.4 getResources) this means that the
65 * class has to fake the given parent.
66 * </p>
67 *
68 */
69 public class AntClassLoader extends ClassLoader implements SubBuildListener {
70
71 private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
72
73 /**
74 * An enumeration of all resources of a given name found within the
75 * classpath of this class loader. This enumeration is used by the
76 * ClassLoader.findResources method, which is in
77 * turn used by the ClassLoader.getResources method.
78 *
79 * @see AntClassLoader#findResources(String)
80 * @see java.lang.ClassLoader#getResources(String)
81 */
82 private class ResourceEnumeration implements Enumeration {
83 /**
84 * The name of the resource being searched for.
85 */
86 private String resourceName;
87
88 /**
89 * The index of the next classpath element to search.
90 */
91 private int pathElementsIndex;
92
93 /**
94 * The URL of the next resource to return in the enumeration. If this
95 * field is <code>null</code> then the enumeration has been completed,
96 * i.e., there are no more elements to return.
97 */
98 private URL nextResource;
99
100 /**
101 * Constructs a new enumeration of resources of the given name found
102 * within this class loader's classpath.
103 *
104 * @param name the name of the resource to search for.
105 */
106 ResourceEnumeration(String name) {
107 this.resourceName = name;
108 this.pathElementsIndex = 0;
109 findNextResource();
110 }
111
112 /**
113 * Indicates whether there are more elements in the enumeration to
114 * return.
115 *
116 * @return <code>true</code> if there are more elements in the
117 * enumeration; <code>false</code> otherwise.
118 */
119 public boolean hasMoreElements() {
120 return (this.nextResource != null);
121 }
122
123 /**
124 * Returns the next resource in the enumeration.
125 *
126 * @return the next resource in the enumeration
127 */
128 public Object nextElement() {
129 URL ret = this.nextResource;
130 findNextResource();
131 return ret;
132 }
133
134 /**
135 * Locates the next resource of the correct name in the classpath and
136 * sets <code>nextResource</code> to the URL of that resource. If no
137 * more resources can be found, <code>nextResource</code> is set to
138 * <code>null</code>.
139 */
140 private void findNextResource() {
141 URL url = null;
142 while ((pathElementsIndex < pathComponents.size())
143 && (url == null)) {
144 try {
145 File pathComponent
146 = (File) pathComponents.elementAt(pathElementsIndex);
147 url = getResourceURL(pathComponent, this.resourceName);
148 pathElementsIndex++;
149 } catch (BuildException e) {
150 // ignore path elements which are not valid relative to the
151 // project
152 }
153 }
154 this.nextResource = url;
155 }
156 }
157
158 /**
159 * The size of buffers to be used in this classloader.
160 */
161 private static final int BUFFER_SIZE = 8192;
162 /**
163 * Number of array elements in a test array of strings
164 */
165 private static final int NUMBER_OF_STRINGS = 256;
166
167 /**
168 * The components of the classpath that the classloader searches
169 * for classes.
170 */
171 private Vector pathComponents = new Vector();
172
173 /**
174 * The project to which this class loader belongs.
175 */
176 private Project project;
177
178 /**
179 * Indicates whether the parent class loader should be
180 * consulted before trying to load with this class loader.
181 */
182 private boolean parentFirst = true;
183
184 /**
185 * These are the package roots that are to be loaded by the parent class
186 * loader regardless of whether the parent class loader is being searched
187 * first or not.
188 */
189 private Vector systemPackages = new Vector();
190
191 /**
192 * These are the package roots that are to be loaded by this class loader
193 * regardless of whether the parent class loader is being searched first
194 * or not.
195 */
196 private Vector loaderPackages = new Vector();
197
198 /**
199 * Whether or not this classloader will ignore the base
200 * classloader if it can't find a class.
201 *
202 * @see #setIsolated(boolean)
203 */
204 private boolean ignoreBase = false;
205
206 /**
207 * The parent class loader, if one is given or can be determined.
208 */
209 private ClassLoader parent = null;
210
211 /**
212 * A hashtable of zip files opened by the classloader (File to ZipFile).
213 */
214 private Hashtable zipFiles = new Hashtable();
215
216 /** Static map of jar file/time to manifest class-path entries */
217 private static Map/*<String,String>*/ pathMap = Collections.synchronizedMap(new HashMap());
218
219 /**
220 * The context loader saved when setting the thread's current
221 * context loader.
222 */
223 private ClassLoader savedContextLoader = null;
224 /**
225 * Whether or not the context loader is currently saved.
226 */
227 private boolean isContextLoaderSaved = false;
228
229 /**
230 * Create an Ant ClassLoader for a given project, with
231 * a parent classloader and an initial classpath.
232 * @since Ant 1.7.
233 * @param parent the parent for this classloader.
234 * @param project The project to which this classloader is to
235 * belong.
236 * @param classpath The classpath to use to load classes.
237 */
238 public AntClassLoader(
239 ClassLoader parent, Project project, Path classpath) {
240 setParent(parent);
241 setClassPath(classpath);
242 setProject(project);
243 }
244
245 /**
246 * Create an Ant Class Loader
247 */
248 public AntClassLoader() {
249 setParent(null);
250 }
251
252 /**
253 * Creates a classloader for the given project using the classpath given.
254 *
255 * @param project The project to which this classloader is to belong.
256 * Must not be <code>null</code>.
257 * @param classpath The classpath to use to load the classes. This
258 * is combined with the system classpath in a manner
259 * determined by the value of ${build.sysclasspath}.
260 * May be <code>null</code>, in which case no path
261 * elements are set up to start with.
262 */
263 public AntClassLoader(Project project, Path classpath) {
264 setParent(null);
265 setProject(project);
266 setClassPath(classpath);
267 }
268
269 /**
270 * Creates a classloader for the given project using the classpath given.
271 *
272 * @param parent The parent classloader to which unsatisfied loading
273 * attempts are delegated. May be <code>null</code>,
274 * in which case the classloader which loaded this
275 * class is used as the parent.
276 * @param project The project to which this classloader is to belong.
277 * Must not be <code>null</code>.
278 * @param classpath the classpath to use to load the classes.
279 * May be <code>null</code>, in which case no path
280 * elements are set up to start with.
281 * @param parentFirst If <code>true</code>, indicates that the parent
282 * classloader should be consulted before trying to
283 * load the a class through this loader.
284 */
285 public AntClassLoader(ClassLoader parent, Project project, Path classpath,
286 boolean parentFirst) {
287 this(project, classpath);
288 if (parent != null) {
289 setParent(parent);
290 }
291 setParentFirst(parentFirst);
292 addJavaLibraries();
293 }
294
295
296 /**
297 * Creates a classloader for the given project using the classpath given.
298 *
299 * @param project The project to which this classloader is to belong.
300 * Must not be <code>null</code>.
301 * @param classpath The classpath to use to load the classes. May be
302 * <code>null</code>, in which case no path
303 * elements are set up to start with.
304 * @param parentFirst If <code>true</code>, indicates that the parent
305 * classloader should be consulted before trying to
306 * load the a class through this loader.
307 */
308 public AntClassLoader(Project project, Path classpath,
309 boolean parentFirst) {
310 this(null, project, classpath, parentFirst);
311 }
312
313 /**
314 * Creates an empty class loader. The classloader should be configured
315 * with path elements to specify where the loader is to look for
316 * classes.
317 *
318 * @param parent The parent classloader to which unsatisfied loading
319 * attempts are delegated. May be <code>null</code>,
320 * in which case the classloader which loaded this
321 * class is used as the parent.
322 * @param parentFirst If <code>true</code>, indicates that the parent
323 * classloader should be consulted before trying to
324 * load the a class through this loader.
325 */
326 public AntClassLoader(ClassLoader parent, boolean parentFirst) {
327 setParent(parent);
328 project = null;
329 this.parentFirst = parentFirst;
330 }
331
332 /**
333 * Set the project associated with this class loader
334 *
335 * @param project the project instance
336 */
337 public void setProject(Project project) {
338 this.project = project;
339 if (project != null) {
340 project.addBuildListener(this);
341 }
342 }
343
344 /**
345 * Set the classpath to search for classes to load. This should not be
346 * changed once the classloader starts to server classes
347 *
348 * @param classpath the search classpath consisting of directories and
349 * jar/zip files.
350 */
351 public void setClassPath(Path classpath) {
352 pathComponents.removeAllElements();
353 if (classpath != null) {
354 Path actualClasspath = classpath.concatSystemClasspath("ignore");
355 String[] pathElements = actualClasspath.list();
356 for (int i = 0; i < pathElements.length; ++i) {
357 try {
358 addPathElement(pathElements[i]);
359 } catch (BuildException e) {
360 // ignore path elements which are invalid
361 // relative to the project
362 }
363 }
364 }
365 }
366
367 /**
368 * Set the parent for this class loader. This is the class loader to which
369 * this class loader will delegate to load classes
370 *
371 * @param parent the parent class loader.
372 */
373 public void setParent(ClassLoader parent) {
374 if (parent == null) {
375 this.parent = AntClassLoader.class.getClassLoader();
376 } else {
377 this.parent = parent;
378 }
379 }
380
381 /**
382 * Control whether class lookup is delegated to the parent loader first
383 * or after this loader. Use with extreme caution. Setting this to
384 * false violates the class loader hierarchy and can lead to Linkage errors
385 *
386 * @param parentFirst if true, delegate initial class search to the parent
387 * classloader.
388 */
389 public void setParentFirst(boolean parentFirst) {
390 this.parentFirst = parentFirst;
391 }
392
393
394 /**
395 * Logs a message through the project object if one has been provided.
396 *
397 * @param message The message to log.
398 * Should not be <code>null</code>.
399 *
400 * @param priority The logging priority of the message.
401 */
402 protected void log(String message, int priority) {
403 if (project != null) {
404 project.log(message, priority);
405 }
406 // else {
407 // System.out.println(message);
408 // }
409 }
410
411 /**
412 * Sets the current thread's context loader to this classloader, storing
413 * the current loader value for later resetting.
414 */
415 public void setThreadContextLoader() {
416 if (isContextLoaderSaved) {
417 throw new BuildException("Context loader has not been reset");
418 }
419 if (LoaderUtils.isContextLoaderAvailable()) {
420 savedContextLoader = LoaderUtils.getContextClassLoader();
421 ClassLoader loader = this;
422 if (project != null
423 && "only".equals(project.getProperty("build.sysclasspath"))) {
424 loader = this.getClass().getClassLoader();
425 }
426 LoaderUtils.setContextClassLoader(loader);
427 isContextLoaderSaved = true;
428 }
429 }
430
431 /**
432 * Resets the current thread's context loader to its original value.
433 */
434 public void resetThreadContextLoader() {
435 if (LoaderUtils.isContextLoaderAvailable()
436 && isContextLoaderSaved) {
437 LoaderUtils.setContextClassLoader(savedContextLoader);
438 savedContextLoader = null;
439 isContextLoaderSaved = false;
440 }
441 }
442
443
444 /**
445 * Adds an element to the classpath to be searched.
446 *
447 * @param pathElement The path element to add. Must not be
448 * <code>null</code>.
449 *
450 * @exception BuildException if the given path element cannot be resolved
451 * against the project.
452 */
453 public void addPathElement(String pathElement) throws BuildException {
454 File pathComponent
455 = project != null ? project.resolveFile(pathElement)
456 : new File(pathElement);
457 try {
458 addPathFile(pathComponent);
459 } catch (IOException e) {
460 throw new BuildException(e);
461 }
462 }
463
464 /**
465 * Add a path component.
466 * This simply adds the file, unlike addPathElement
467 * it does not open jar files and load files from
468 * their CLASSPATH entry in the manifest file.
469 * @param file the jar file or directory to add.
470 */
471 public void addPathComponent(File file) {
472 if (pathComponents.contains(file)) {
473 return;
474 }
475 pathComponents.addElement(file);
476 }
477
478 /**
479 * Add a file to the path.
480 * Reads the manifest, if available, and adds any additional class path jars
481 * specified in the manifest.
482 *
483 * @param pathComponent the file which is to be added to the path for
484 * this class loader
485 *
486 * @throws IOException if data needed from the file cannot be read.
487 */
488 protected void addPathFile(File pathComponent) throws IOException {
489 pathComponents.addElement(pathComponent);
490 if (pathComponent.isDirectory()) {
491 return;
492 }
493
494 String absPathPlusTimeAndLength =
495 pathComponent.getAbsolutePath() + pathComponent.lastModified() + "-"
496 + pathComponent.length();
497 String classpath = (String) pathMap.get(absPathPlusTimeAndLength);
498 if (classpath == null) {
499 ZipFile jarFile = null;
500 InputStream manifestStream = null;
501 try {
502 jarFile = new ZipFile(pathComponent);
503 manifestStream
504 = jarFile.getInputStream(new ZipEntry("META-INF/MANIFEST.MF"));
505
506 if (manifestStream == null) {
507 return;
508 }
509 Reader manifestReader
510 = new InputStreamReader(manifestStream, "UTF-8");
511 org.apache.tools.ant.taskdefs.Manifest manifest
512 = new org.apache.tools.ant.taskdefs.Manifest(manifestReader);
513 classpath
514 = manifest.getMainSection().getAttributeValue("Class-Path");
515
516 } catch (org.apache.tools.ant.taskdefs.ManifestException e) {
517 // ignore
518 } finally {
519 FileUtils.close(manifestStream);
520 if (jarFile != null) {
521 jarFile.close();
522 }
523 }
524 if (classpath == null) {
525 classpath = "";
526 }
527 pathMap.put(absPathPlusTimeAndLength, classpath);
528 }
529
530 if (!"".equals(classpath)) {
531 URL baseURL = FILE_UTILS.getFileURL(pathComponent);
532 StringTokenizer st = new StringTokenizer(classpath);
533 while (st.hasMoreTokens()) {
534 String classpathElement = st.nextToken();
535 URL libraryURL = new URL(baseURL, classpathElement);
536 if (!libraryURL.getProtocol().equals("file")) {
537 log("Skipping jar library " + classpathElement
538 + " since only relative URLs are supported by this"
539 + " loader", Project.MSG_VERBOSE);
540 continue;
541 }
542 String decodedPath = Locator.decodeUri(libraryURL.getFile());
543 File libraryFile = new File(decodedPath);
544 if (libraryFile.exists() && !isInPath(libraryFile)) {
545 addPathFile(libraryFile);
546 }
547 }
548 }
549 }
550
551 /**
552 * Returns the classpath this classloader will consult.
553 *
554 * @return the classpath used for this classloader, with elements
555 * separated by the path separator for the system.
556 */
557 public String getClasspath() {
558 StringBuffer sb = new StringBuffer();
559 boolean firstPass = true;
560 Enumeration componentEnum = pathComponents.elements();
561 while (componentEnum.hasMoreElements()) {
562 if (!firstPass) {
563 sb.append(System.getProperty("path.separator"));
564 } else {
565 firstPass = false;
566 }
567 sb.append(((File) componentEnum.nextElement()).getAbsolutePath());
568 }
569 return sb.toString();
570 }
571
572 /**
573 * Sets whether this classloader should run in isolated mode. In
574 * isolated mode, classes not found on the given classpath will
575 * not be referred to the parent class loader but will cause a
576 * ClassNotFoundException.
577 *
578 * @param isolated Whether or not this classloader should run in
579 * isolated mode.
580 */
581 public synchronized void setIsolated(boolean isolated) {
582 ignoreBase = isolated;
583 }
584
585 /**
586 * Forces initialization of a class in a JDK 1.1 compatible, albeit hacky
587 * way.
588 *
589 * @param theClass The class to initialize.
590 * Must not be <code>null</code>.
591 *
592 * @deprecated since 1.6.x.
593 * Use Class.forName with initialize=true instead.
594 */
595 public static void initializeClass(Class theClass) {
596 // ***HACK*** We ask the VM to create an instance
597 // by voluntarily providing illegal arguments to force
598 // the VM to run the class' static initializer, while
599 // at the same time not running a valid constructor.
600
601 final Constructor[] cons = theClass.getDeclaredConstructors();
602 //At least one constructor is guaranteed to be there, but check anyway.
603 if (cons != null) {
604 if (cons.length > 0 && cons[0] != null) {
605 final String[] strs = new String[NUMBER_OF_STRINGS];
606 try {
607 cons[0].newInstance((Object[]) strs);
608 // Expecting an exception to be thrown by this call:
609 // IllegalArgumentException: wrong number of Arguments
610 } catch (Exception e) {
611 // Ignore - we are interested only in the side
612 // effect - that of getting the static initializers
613 // invoked. As we do not want to call a valid
614 // constructor to get this side effect, an
615 // attempt is made to call a hopefully
616 // invalid constructor - come on, nobody
617 // would have a constructor that takes in
618 // 256 String arguments ;-)
619 // (In fact, they can't - according to JVM spec
620 // section 4.10, the number of method parameters is limited
621 // to 255 by the definition of a method descriptor.
622 // Constructors count as methods here.)
623 }
624 }
625 }
626 }
627
628 /**
629 * Adds a package root to the list of packages which must be loaded on the
630 * parent loader.
631 *
632 * All subpackages are also included.
633 *
634 * @param packageRoot The root of all packages to be included.
635 * Should not be <code>null</code>.
636 */
637 public void addSystemPackageRoot(String packageRoot) {
638 systemPackages.addElement(packageRoot
639 + (packageRoot.endsWith(".") ? "" : "."));
640 }
641
642 /**
643 * Adds a package root to the list of packages which must be loaded using
644 * this loader.
645 *
646 * All subpackages are also included.
647 *
648 * @param packageRoot The root of all packages to be included.
649 * Should not be <code>null</code>.
650 */
651 public void addLoaderPackageRoot(String packageRoot) {
652 loaderPackages.addElement(packageRoot
653 + (packageRoot.endsWith(".") ? "" : "."));
654 }
655
656 /**
657 * Loads a class through this class loader even if that class is available
658 * on the parent classpath.
659 *
660 * This ensures that any classes which are loaded by the returned class
661 * will use this classloader.
662 *
663 * @param classname The name of the class to be loaded.
664 * Must not be <code>null</code>.
665 *
666 * @return the required Class object
667 *
668 * @exception ClassNotFoundException if the requested class does not exist
669 * on this loader's classpath.
670 */
671 public Class forceLoadClass(String classname)
672 throws ClassNotFoundException {
673 log("force loading " + classname, Project.MSG_DEBUG);
674
675 Class theClass = findLoadedClass(classname);
676
677 if (theClass == null) {
678 theClass = findClass(classname);
679 }
680
681 return theClass;
682 }
683
684 /**
685 * Loads a class through this class loader but defer to the parent class
686 * loader.
687 *
688 * This ensures that instances of the returned class will be compatible
689 * with instances which have already been loaded on the parent
690 * loader.
691 *
692 * @param classname The name of the class to be loaded.
693 * Must not be <code>null</code>.
694 *
695 * @return the required Class object
696 *
697 * @exception ClassNotFoundException if the requested class does not exist
698 * on this loader's classpath.
699 */
700 public Class forceLoadSystemClass(String classname)
701 throws ClassNotFoundException {
702 log("force system loading " + classname, Project.MSG_DEBUG);
703
704 Class theClass = findLoadedClass(classname);
705
706 if (theClass == null) {
707 theClass = findBaseClass(classname);
708 }
709
710 return theClass;
711 }
712
713 /**
714 * Returns a stream to read the requested resource name.
715 *
716 * @param name The name of the resource for which a stream is required.
717 * Must not be <code>null</code>.
718 *
719 * @return a stream to the required resource or <code>null</code> if the
720 * resource cannot be found on the loader's classpath.
721 */
722 public InputStream getResourceAsStream(String name) {
723
724 InputStream resourceStream = null;
725 if (isParentFirst(name)) {
726 resourceStream = loadBaseResource(name);
727 if (resourceStream != null) {
728 log("ResourceStream for " + name
729 + " loaded from parent loader", Project.MSG_DEBUG);
730
731 } else {
732 resourceStream = loadResource(name);
733 if (resourceStream != null) {
734 log("ResourceStream for " + name
735 + " loaded from ant loader", Project.MSG_DEBUG);
736 }
737 }
738 } else {
739 resourceStream = loadResource(name);
740 if (resourceStream != null) {
741 log("ResourceStream for " + name
742 + " loaded from ant loader", Project.MSG_DEBUG);
743
744 } else {
745 resourceStream = loadBaseResource(name);
746 if (resourceStream != null) {
747 log("ResourceStream for " + name
748 + " loaded from parent loader", Project.MSG_DEBUG);
749 }
750 }
751 }
752
753 if (resourceStream == null) {
754 log("Couldn't load ResourceStream for " + name,
755 Project.MSG_DEBUG);
756 }
757
758 return resourceStream;
759 }
760
761 /**
762 * Returns a stream to read the requested resource name from this loader.
763 *
764 * @param name The name of the resource for which a stream is required.
765 * Must not be <code>null</code>.
766 *
767 * @return a stream to the required resource or <code>null</code> if
768 * the resource cannot be found on the loader's classpath.
769 */
770 private InputStream loadResource(String name) {
771 // we need to search the components of the path to see if we can
772 // find the class we want.
773 InputStream stream = null;
774
775 Enumeration e = pathComponents.elements();
776 while (e.hasMoreElements() && stream == null) {
777 File pathComponent = (File) e.nextElement();
778 stream = getResourceStream(pathComponent, name);
779 }
780 return stream;
781 }
782
783 /**
784 * Finds a system resource (which should be loaded from the parent
785 * classloader).
786 *
787 * @param name The name of the system resource to load.
788 * Must not be <code>null</code>.
789 *
790 * @return a stream to the named resource, or <code>null</code> if
791 * the resource cannot be found.
792 */
793 private InputStream loadBaseResource(String name) {
794 if (parent == null) {
795 return getSystemResourceAsStream(name);
796 } else {
797 return parent.getResourceAsStream(name);
798 }
799 }
800
801 /**
802 * Returns an inputstream to a given resource in the given file which may
803 * either be a directory or a zip file.
804 *
805 * @param file the file (directory or jar) in which to search for the
806 * resource. Must not be <code>null</code>.
807 * @param resourceName The name of the resource for which a stream is
808 * required. Must not be <code>null</code>.
809 *
810 * @return a stream to the required resource or <code>null</code> if
811 * the resource cannot be found in the given file.
812 */
813 private InputStream getResourceStream(File file, String resourceName) {
814 try {
815 ZipFile zipFile = (ZipFile) zipFiles.get(file);
816 if (zipFile == null && file.isDirectory()) {
817 File resource = new File(file, resourceName);
818
819 if (resource.exists()) {
820 return new FileInputStream(resource);
821 }
822 } else {
823 if (zipFile == null) {
824 if (file.exists()) {
825
826 zipFile = new ZipFile(file);
827 zipFiles.put(file, zipFile);
828 } else {
829 return null;
830 }
831 //to eliminate a race condition, retrieve the entry
832 //that is in the hash table under that filename
833 zipFile = (ZipFile) zipFiles.get(file);
834 }
835 ZipEntry entry = zipFile.getEntry(resourceName);
836 if (entry != null) {
837 return zipFile.getInputStream(entry);
838 }
839 }
840 } catch (Exception e) {
841 log("Ignoring Exception " + e.getClass().getName() + ": "
842 + e.getMessage() + " reading resource " + resourceName
843 + " from " + file, Project.MSG_VERBOSE);
844 }
845
846 return null;
847 }
848
849 /**
850 * Tests whether or not the parent classloader should be checked for a
851 * resource before this one. If the resource matches both the "use parent
852 * classloader first" and the "use this classloader first" lists, the latter
853 * takes priority.
854 *
855 * @param resourceName
856 * The name of the resource to check. Must not be
857 * <code>null</code>.
858 *
859 * @return whether or not the parent classloader should be checked for a
860 * resource before this one is.
861 */
862 private boolean isParentFirst(String resourceName) {
863 // default to the global setting and then see
864 // if this class belongs to a package which has been
865 // designated to use a specific loader first
866 // (this one or the parent one)
867
868 // XXX - shouldn't this always return false in isolated mode?
869
870 boolean useParentFirst = parentFirst;
871
872 for (Enumeration e = systemPackages.elements(); e.hasMoreElements();) {
873 String packageName = (String) e.nextElement();
874 if (resourceName.startsWith(packageName)) {
875 useParentFirst = true;
876 break;
877 }
878 }
879
880 for (Enumeration e = loaderPackages.elements(); e.hasMoreElements();) {
881 String packageName = (String) e.nextElement();
882 if (resourceName.startsWith(packageName)) {
883 useParentFirst = false;
884 break;
885 }
886 }
887
888 return useParentFirst;
889 }
890
891 /**
892 * Used for isolated resource seaching.
893 * @return the root classloader of AntClassLoader.
894 */
895 private ClassLoader getRootLoader() {
896 ClassLoader ret = getClass().getClassLoader();
897 while (ret != null && ret.getParent() != null) {
898 ret = ret.getParent();
899 }
900 return ret;
901 }
902
903 /**
904 * Finds the resource with the given name. A resource is
905 * some data (images, audio, text, etc) that can be accessed by class
906 * code in a way that is independent of the location of the code.
907 *
908 * @param name The name of the resource for which a stream is required.
909 * Must not be <code>null</code>.
910 *
911 * @return a URL for reading the resource, or <code>null</code> if the
912 * resource could not be found or the caller doesn't have
913 * adequate privileges to get the resource.
914 */
915 public URL getResource(String name) {
916 // we need to search the components of the path to see if
917 // we can find the class we want.
918 URL url = null;
919 if (isParentFirst(name)) {
920 url = (parent == null) ? super.getResource(name)
921 : parent.getResource(name);
922 }
923
924 if (url != null) {
925 log("Resource " + name + " loaded from parent loader",
926 Project.MSG_DEBUG);
927
928 } else {
929 // try and load from this loader if the parent either didn't find
930 // it or wasn't consulted.
931 Enumeration e = pathComponents.elements();
932 while (e.hasMoreElements() && url == null) {
933 File pathComponent = (File) e.nextElement();
934 url = getResourceURL(pathComponent, name);
935 if (url != null) {
936 log("Resource " + name
937 + " loaded from ant loader",
938 Project.MSG_DEBUG);
939 }
940 }
941 }
942
943 if (url == null && !isParentFirst(name)) {
944 // this loader was first but it didn't find it - try the parent
945 if (ignoreBase) {
946 url = (getRootLoader() == null) ? null
947 : getRootLoader().getResource(name);
948 } else {
949 url = (parent == null) ? super.getResource(name)
950 : parent.getResource(name);
951 }
952 if (url != null) {
953 log("Resource " + name + " loaded from parent loader",
954 Project.MSG_DEBUG);
955 }
956 }
957
958 if (url == null) {
959 log("Couldn't load Resource " + name, Project.MSG_DEBUG);
960 }
961
962 return url;
963 }
964
965 /**
966 * Returns an enumeration of URLs representing all the resources with the
967 * given name by searching the class loader's classpath.
968 *
969 * @param name The resource name to search for.
970 * Must not be <code>null</code>.
971 * @return an enumeration of URLs for the resources
972 * @exception IOException if I/O errors occurs (can't happen)
973 */
974 protected Enumeration/*<URL>*/ findResources(String name) throws IOException {
975 Enumeration/*<URL>*/ mine = new ResourceEnumeration(name);
976 Enumeration/*<URL>*/ base;
977 if (parent != null && parent != getParent()) {
978 // Delegate to the parent:
979 base = parent.getResources(name);
980 // Note: could cause overlaps in case ClassLoader.this.parent has matches.
981 } else {
982 // ClassLoader.this.parent is already delegated to from
983 // ClassLoader.getResources, no need:
984 base = new CollectionUtils.EmptyEnumeration();
985 }
986 if (isParentFirst(name)) {
987 // Normal case.
988 return CollectionUtils.append(base, mine);
989 } else if (ignoreBase) {
990 return getRootLoader() == null
991 ? mine
992 : CollectionUtils.append(
993 mine, getRootLoader().getResources(name));
994 } else {
995 // Inverted.
996 return CollectionUtils.append(mine, base);
997 }
998 }
999
1000 /**
1001 * Returns the URL of a given resource in the given file which may
1002 * either be a directory or a zip file.
1003 *
1004 * @param file The file (directory or jar) in which to search for
1005 * the resource. Must not be <code>null</code>.
1006 * @param resourceName The name of the resource for which a stream
1007 * is required. Must not be <code>null</code>.
1008 *
1009 * @return a stream to the required resource or <code>null</code> if the
1010 * resource cannot be found in the given file object.
1011 */
1012 protected URL getResourceURL(File file, String resourceName) {
1013 try {
1014 ZipFile zipFile = (ZipFile) zipFiles.get(file);
1015 if (zipFile == null && file.isDirectory()) {
1016 File resource = new File(file, resourceName);
1017
1018 if (resource.exists()) {
1019 try {
1020 return FILE_UTILS.getFileURL(resource);
1021 } catch (MalformedURLException ex) {
1022 return null;
1023 }
1024 }
1025 } else {
1026 if (zipFile == null) {
1027 if (file.exists()) {
1028 zipFile = new ZipFile(file);
1029 zipFiles.put(file, zipFile);
1030 } else {
1031 return null;
1032 }
1033 }
1034 ZipEntry entry = zipFile.getEntry(resourceName);
1035 if (entry != null) {
1036 try {
1037 return new URL("jar:" + FILE_UTILS.getFileURL(file)
1038 + "!/" + entry);
1039 } catch (MalformedURLException ex) {
1040 return null;
1041 }
1042 }
1043 }
1044 } catch (Exception e) {
1045 e.printStackTrace();
1046 }
1047
1048 return null;
1049 }
1050
1051 /**
1052 * Loads a class with this class loader.
1053 *
1054 * This class attempts to load the class in an order determined by whether
1055 * or not the class matches the system/loader package lists, with the
1056 * loader package list taking priority. If the classloader is in isolated
1057 * mode, failure to load the class in this loader will result in a
1058 * ClassNotFoundException.
1059 *
1060 * @param classname The name of the class to be loaded.
1061 * Must not be <code>null</code>.
1062 * @param resolve <code>true</code> if all classes upon which this class
1063 * depends are to be loaded.
1064 *
1065 * @return the required Class object
1066 *
1067 * @exception ClassNotFoundException if the requested class does not exist
1068 * on the system classpath (when not in isolated mode) or this loader's
1069 * classpath.
1070 */
1071 protected synchronized Class loadClass(String classname, boolean resolve)
1072 throws ClassNotFoundException {
1073 // 'sync' is needed - otherwise 2 threads can load the same class
1074 // twice, resulting in LinkageError: duplicated class definition.
1075 // findLoadedClass avoids that, but without sync it won't work.
1076
1077 Class theClass = findLoadedClass(classname);
1078 if (theClass != null) {
1079 return theClass;
1080 }
1081
1082 if (isParentFirst(classname)) {
1083 try {
1084 theClass = findBaseClass(classname);
1085 log("Class " + classname + " loaded from parent loader "
1086 + "(parentFirst)", Project.MSG_DEBUG);
1087 } catch (ClassNotFoundException cnfe) {
1088 theClass = findClass(classname);
1089 log("Class " + classname + " loaded from ant loader "
1090 + "(parentFirst)", Project.MSG_DEBUG);
1091 }
1092 } else {
1093 try {
1094 theClass = findClass(classname);
1095 log("Class " + classname + " loaded from ant loader",
1096 Project.MSG_DEBUG);
1097 } catch (ClassNotFoundException cnfe) {
1098 if (ignoreBase) {
1099 throw cnfe;
1100 }
1101 theClass = findBaseClass(classname);
1102 log("Class " + classname + " loaded from parent loader",
1103 Project.MSG_DEBUG);
1104 }
1105 }
1106
1107 if (resolve) {
1108 resolveClass(theClass);
1109 }
1110
1111 return theClass;
1112 }
1113
1114 /**
1115 * Converts the class dot notation to a filesystem equivalent for
1116 * searching purposes.
1117 *
1118 * @param classname The class name in dot format (eg java.lang.Integer).
1119 * Must not be <code>null</code>.
1120 *
1121 * @return the classname in filesystem format (eg java/lang/Integer.class)
1122 */
1123 private String getClassFilename(String classname) {
1124 return classname.replace('.', '/') + ".class";
1125 }
1126
1127 /**
1128 * Define a class given its bytes
1129 *
1130 * @param container the container from which the class data has been read
1131 * may be a directory or a jar/zip file.
1132 *
1133 * @param classData the bytecode data for the class
1134 * @param classname the name of the class
1135 *
1136 * @return the Class instance created from the given data
1137 *
1138 * @throws IOException if the class data cannot be read.
1139 */
1140 protected Class defineClassFromData(File container, byte[] classData,
1141 String classname) throws IOException {
1142 definePackage(container, classname);
1143 // XXX should instead make a new ProtectionDomain with a CodeSource
1144 // corresponding to container.toURI().toURL() and the same
1145 // PermissionCollection as Project.class.protectionDomain had
1146 return defineClass(classname, classData, 0, classData.length,
1147 Project.class.getProtectionDomain());
1148 }
1149
1150 /**
1151 * Define the package information associated with a class.
1152 *
1153 * @param container the file containing the class definition.
1154 * @param className the class name of for which the package information
1155 * is to be determined.
1156 *
1157 * @exception IOException if the package information cannot be read from the
1158 * container.
1159 */
1160 protected void definePackage(File container, String className)
1161 throws IOException {
1162 int classIndex = className.lastIndexOf('.');
1163 if (classIndex == -1) {
1164 return;
1165 }
1166
1167 String packageName = className.substring(0, classIndex);
1168 if (getPackage(packageName) != null) {
1169 // already defined
1170 return;
1171 }
1172
1173 // define the package now
1174 Manifest manifest = getJarManifest(container);
1175
1176 if (manifest == null) {
1177 definePackage(packageName, null, null, null, null, null,
1178 null, null);
1179 } else {
1180 definePackage(container, packageName, manifest);
1181 }
1182 }
1183
1184 /**
1185 * Get the manifest from the given jar, if it is indeed a jar and it has a
1186 * manifest
1187 *
1188 * @param container the File from which a manifest is required.
1189 *
1190 * @return the jar's manifest or null is the container is not a jar or it
1191 * has no manifest.
1192 *
1193 * @exception IOException if the manifest cannot be read.
1194 */
1195 private Manifest getJarManifest(File container) throws IOException {
1196 if (container.isDirectory()) {
1197 return null;
1198 }
1199 JarFile jarFile = null;
1200 try {
1201 jarFile = new JarFile(container);
1202 return jarFile.getManifest();
1203 } finally {
1204 if (jarFile != null) {
1205 jarFile.close();
1206 }
1207 }
1208 }
1209
1210 /**
1211 * Define the package information when the class comes from a
1212 * jar with a manifest
1213 *
1214 * @param container the jar file containing the manifest
1215 * @param packageName the name of the package being defined.
1216 * @param manifest the jar's manifest
1217 */
1218 protected void definePackage(File container, String packageName,
1219 Manifest manifest) {
1220 String sectionName = packageName.replace('.', '/') + "/";
1221
1222 String specificationTitle = null;
1223 String specificationVendor = null;
1224 String specificationVersion = null;
1225 String implementationTitle = null;
1226 String implementationVendor = null;
1227 String implementationVersion = null;
1228 String sealedString = null;
1229 URL sealBase = null;
1230
1231 Attributes sectionAttributes = manifest.getAttributes(sectionName);
1232 if (sectionAttributes != null) {
1233 specificationTitle
1234 = sectionAttributes.getValue(Name.SPECIFICATION_TITLE);
1235 specificationVendor
1236 = sectionAttributes.getValue(Name.SPECIFICATION_VENDOR);
1237 specificationVersion
1238 = sectionAttributes.getValue(Name.SPECIFICATION_VERSION);
1239 implementationTitle
1240 = sectionAttributes.getValue(Name.IMPLEMENTATION_TITLE);
1241 implementationVendor
1242 = sectionAttributes.getValue(Name.IMPLEMENTATION_VENDOR);
1243 implementationVersion
1244 = sectionAttributes.getValue(Name.IMPLEMENTATION_VERSION);
1245 sealedString
1246 = sectionAttributes.getValue(Name.SEALED);
1247 }
1248
1249 Attributes mainAttributes = manifest.getMainAttributes();
1250 if (mainAttributes != null) {
1251 if (specificationTitle == null) {
1252 specificationTitle
1253 = mainAttributes.getValue(Name.SPECIFICATION_TITLE);
1254 }
1255 if (specificationVendor == null) {
1256 specificationVendor
1257 = mainAttributes.getValue(Name.SPECIFICATION_VENDOR);
1258 }
1259 if (specificationVersion == null) {
1260 specificationVersion
1261 = mainAttributes.getValue(Name.SPECIFICATION_VERSION);
1262 }
1263 if (implementationTitle == null) {
1264 implementationTitle
1265 = mainAttributes.getValue(Name.IMPLEMENTATION_TITLE);
1266 }
1267 if (implementationVendor == null) {
1268 implementationVendor
1269 = mainAttributes.getValue(Name.IMPLEMENTATION_VENDOR);
1270 }
1271 if (implementationVersion == null) {
1272 implementationVersion
1273 = mainAttributes.getValue(Name.IMPLEMENTATION_VERSION);
1274 }
1275 if (sealedString == null) {
1276 sealedString
1277 = mainAttributes.getValue(Name.SEALED);
1278 }
1279 }
1280
1281 if (sealedString != null
1282 && sealedString.toLowerCase(Locale.ENGLISH).equals("true")) {
1283 try {
1284 sealBase = new URL(FileUtils.getFileUtils().toURI(container.getAbsolutePath()));
1285 } catch (MalformedURLException e) {
1286 // ignore
1287 }
1288 }
1289
1290 definePackage(packageName, specificationTitle, specificationVersion,
1291 specificationVendor, implementationTitle,
1292 implementationVersion, implementationVendor, sealBase);
1293 }
1294
1295
1296 /**
1297 * Reads a class definition from a stream.
1298 *
1299 * @param stream The stream from which the class is to be read.
1300 * Must not be <code>null</code>.
1301 * @param classname The name of the class in the stream.
1302 * Must not be <code>null</code>.
1303 * @param container the file or directory containing the class.
1304 *
1305 * @return the Class object read from the stream.
1306 *
1307 * @exception IOException if there is a problem reading the class from the
1308 * stream.
1309 * @exception SecurityException if there is a security problem while
1310 * reading the class from the stream.
1311 */
1312 private Class getClassFromStream(InputStream stream, String classname,
1313 File container)
1314 throws IOException, SecurityException {
1315 ByteArrayOutputStream baos = new ByteArrayOutputStream();
1316 int bytesRead = -1;
1317 byte[] buffer = new byte[BUFFER_SIZE];
1318
1319 while ((bytesRead = stream.read(buffer, 0, BUFFER_SIZE)) != -1) {
1320 baos.write(buffer, 0, bytesRead);
1321 }
1322
1323 byte[] classData = baos.toByteArray();
1324 return defineClassFromData(container, classData, classname);
1325 }
1326
1327 /**
1328 * Searches for and load a class on the classpath of this class loader.
1329 *
1330 * @param name The name of the class to be loaded. Must not be
1331 * <code>null</code>.
1332 *
1333 * @return the required Class object
1334 *
1335 * @exception ClassNotFoundException if the requested class does not exist
1336 * on this loader's classpath.
1337 */
1338 public Class findClass(String name) throws ClassNotFoundException {
1339 log("Finding class " + name, Project.MSG_DEBUG);
1340
1341 return findClassInComponents(name);
1342 }
1343
1344 /**
1345 * Indicate if the given file is in this loader's path
1346 *
1347 * @param component the file which is to be checked
1348 *
1349 * @return true if the file is in the class path
1350 */
1351 protected boolean isInPath(File component) {
1352 for (Enumeration e = pathComponents.elements(); e.hasMoreElements();) {
1353 File pathComponent = (File) e.nextElement();
1354 if (pathComponent.equals(component)) {
1355 return true;
1356 }
1357 }
1358 return false;
1359 }
1360
1361
1362 /**
1363 * Finds a class on the given classpath.
1364 *
1365 * @param name The name of the class to be loaded. Must not be
1366 * <code>null</code>.
1367 *
1368 * @return the required Class object
1369 *
1370 * @exception ClassNotFoundException if the requested class does not exist
1371 * on this loader's classpath.
1372 */
1373 private Class findClassInComponents(String name)
1374 throws ClassNotFoundException {
1375 // we need to search the components of the path to see if
1376 // we can find the class we want.
1377 InputStream stream = null;
1378 String classFilename = getClassFilename(name);
1379 try {
1380 Enumeration e = pathComponents.elements();
1381 while (e.hasMoreElements()) {
1382 File pathComponent = (File) e.nextElement();
1383 try {
1384 stream = getResourceStream(pathComponent, classFilename);
1385 if (stream != null) {
1386 log("Loaded from " + pathComponent + " "
1387 + classFilename, Project.MSG_DEBUG);
1388 return getClassFromStream(stream, name, pathComponent);
1389 }
1390 } catch (SecurityException se) {
1391 throw se;
1392 } catch (IOException ioe) {
1393 // ioe.printStackTrace();
1394 log("Exception reading component " + pathComponent
1395 + " (reason: " + ioe.getMessage() + ")",
1396 Project.MSG_VERBOSE);
1397 }
1398 }
1399
1400 throw new ClassNotFoundException(name);
1401 } finally {
1402 FileUtils.close(stream);
1403 }
1404 }
1405
1406 /**
1407 * Finds a system class (which should be loaded from the same classloader
1408 * as the Ant core).
1409 *
1410 * For JDK 1.1 compatibility, this uses the findSystemClass method if
1411 * no parent classloader has been specified.
1412 *
1413 * @param name The name of the class to be loaded.
1414 * Must not be <code>null</code>.
1415 *
1416 * @return the required Class object
1417 *
1418 * @exception ClassNotFoundException if the requested class does not exist
1419 * on this loader's classpath.
1420 */
1421 private Class findBaseClass(String name) throws ClassNotFoundException {
1422 if (parent == null) {
1423 return findSystemClass(name);
1424 } else {
1425 return parent.loadClass(name);
1426 }
1427 }
1428
1429 /**
1430 * Cleans up any resources held by this classloader. Any open archive
1431 * files are closed.
1432 */
1433 public synchronized void cleanup() {
1434 for (Enumeration e = zipFiles.elements(); e.hasMoreElements();) {
1435 ZipFile zipFile = (ZipFile) e.nextElement();
1436 try {
1437 zipFile.close();
1438 } catch (IOException ioe) {
1439 // ignore
1440 }
1441 }
1442 zipFiles = new Hashtable();
1443 if (project != null) {
1444 project.removeBuildListener(this);
1445 }
1446 project = null;
1447 }
1448
1449 /**
1450 * Empty implementation to satisfy the BuildListener interface.
1451 *
1452 * @param event the buildStarted event
1453 */
1454 public void buildStarted(BuildEvent event) {
1455 // Not significant for the class loader.
1456 }
1457
1458 /**
1459 * Cleans up any resources held by this classloader at the end
1460 * of a build.
1461 *
1462 * @param event the buildFinished event
1463 */
1464 public void buildFinished(BuildEvent event) {
1465 cleanup();
1466 }
1467
1468 /**
1469 * Cleans up any resources held by this classloader at the end of
1470 * a subbuild if it has been created for the subbuild's project
1471 * instance.
1472 *
1473 * @param event the buildFinished event
1474 *
1475 * @since Ant 1.6.2
1476 */
1477 public void subBuildFinished(BuildEvent event) {
1478 if (event.getProject() == project) {
1479 cleanup();
1480 }
1481 }
1482
1483 /**
1484 * Empty implementation to satisfy the BuildListener interface.
1485 *
1486 * @param event the buildStarted event
1487 *
1488 * @since Ant 1.6.2
1489 */
1490 public void subBuildStarted(BuildEvent event) {
1491 // Not significant for the class loader.
1492 }
1493
1494 /**
1495 * Empty implementation to satisfy the BuildListener interface.
1496 *
1497 * @param event the targetStarted event
1498 */
1499 public void targetStarted(BuildEvent event) {
1500 // Not significant for the class loader.
1501 }
1502
1503 /**
1504 * Empty implementation to satisfy the BuildListener interface.
1505 *
1506 * @param event the targetFinished event
1507 */
1508 public void targetFinished(BuildEvent event) {
1509 // Not significant for the class loader.
1510 }
1511
1512 /**
1513 * Empty implementation to satisfy the BuildListener interface.
1514 *
1515 * @param event the taskStarted event
1516 */
1517 public void taskStarted(BuildEvent event) {
1518 // Not significant for the class loader.
1519 }
1520
1521 /**
1522 * Empty implementation to satisfy the BuildListener interface.
1523 *
1524 * @param event the taskFinished event
1525 */
1526 public void taskFinished(BuildEvent event) {
1527 // Not significant for the class loader.
1528 }
1529
1530 /**
1531 * Empty implementation to satisfy the BuildListener interface.
1532 *
1533 * @param event the messageLogged event
1534 */
1535 public void messageLogged(BuildEvent event) {
1536 // Not significant for the class loader.
1537 }
1538
1539 /**
1540 * add any libraries that come with different java versions
1541 * here
1542 */
1543 public void addJavaLibraries() {
1544 Vector packages = JavaEnvUtils.getJrePackages();
1545 Enumeration e = packages.elements();
1546 while (e.hasMoreElements()) {
1547 String packageName = (String) e.nextElement();
1548 addSystemPackageRoot(packageName);
1549 }
1550 }
1551
1552 /**
1553 * Returns a <code>String</code> representing this loader.
1554 * @return the path that this classloader has.
1555 */
1556 public String toString() {
1557 return "AntClassLoader[" + getClasspath() + "]";
1558 }
1559
1560 }