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