001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018: package org.apache.tools.ant.util;
019:
020: import org.apache.tools.ant.AntClassLoader;
021: import org.apache.tools.ant.BuildException;
022: import org.apache.tools.ant.Project;
023: import org.apache.tools.ant.ProjectComponent;
024: import org.apache.tools.ant.MagicNames;
025: import org.apache.tools.ant.types.Path;
026: import org.apache.tools.ant.types.Reference;
027:
028: // CheckStyle:HideUtilityClassConstructorCheck OFF - bc
029:
030: /**
031: * Offers some helper methods on the Path structure in ant.
032: *
033: * <p>The basic idea behind this utility class is to use it from inside the
034: * different Ant objects (and user defined objects) that need classLoading
035: * for their operation.
036: * Normally those would have a setClasspathRef() {for the @classpathref}
037: * and/or a createClasspath() {for the nested <classpath>}
038: * Typically one would have in your Ant Task or DataType</p>
039: *
040: * <pre><code>
041: * ClasspathUtils.Delegate cpDelegate;
042: *
043: * public void init() {
044: * this.cpDelegate = ClasspathUtils.getDelegate(this);
045: * super.init();
046: * }
047: *
048: * public void setClasspathRef(Reference r) {
049: * this.cpDelegate.setClasspathRef(r);
050: * }
051: *
052: * public Path createClasspath() {
053: * return this.cpDelegate.createClasspath();
054: * }
055: *
056: * public void setClassname(String fqcn) {
057: * this.cpDelegate.setClassname(fqcn);
058: * }
059: * </code></pre>
060: *
061: * <p>At execution time, when you actually need the classloading
062: * you can just:</p>
063: *
064: * <pre><code>
065: * Object o = this.cpDelegate.newInstance();
066: * </code></pre>
067: *
068: * @since Ant 1.6
069: */
070: public class ClasspathUtils {
071:
072: /**
073: * Name of the magic property that controls classloader reuse in Ant 1.4.
074: */
075: public static final String REUSE_LOADER_REF = MagicNames.REFID_CLASSPATH_REUSE_LOADER;
076:
077: /**
078: * Convenience overloaded version of {@link
079: * #getClassLoaderForPath(Project, Reference, boolean)}.
080: *
081: * <p>Assumes the logical 'false' for the reverseLoader.</p>
082: *
083: * @param p the project
084: * @param ref the reference
085: * @return The class loader
086: */
087: public static ClassLoader getClassLoaderForPath(Project p,
088: Reference ref) {
089:
090: return getClassLoaderForPath(p, ref, false);
091: }
092:
093: /**
094: * Convenience overloaded version of {@link #getClassLoaderForPath(Project, Path,
095: * String, boolean)}.
096: *
097: * <p>Delegates to the other one after extracting the referenced
098: * Path from the Project. This checks also that the passed
099: * Reference is pointing to a Path all right.</p>
100: * @param p current Ant project
101: * @param ref Reference to Path structure
102: * @param reverseLoader if set to true this new loader will take
103: * precedence over its parent (which is contra the regular
104: * classloader behaviour)
105: * @return The class loader
106: */
107: public static ClassLoader getClassLoaderForPath(Project p,
108: Reference ref, boolean reverseLoader) {
109:
110: String pathId = ref.getRefId();
111: Object path = p.getReference(pathId);
112: if (!(path instanceof Path)) {
113: throw new BuildException("The specified classpathref "
114: + pathId + " does not reference a Path.");
115: }
116: String loaderId = MagicNames.REFID_CLASSPATH_LOADER_PREFIX
117: + pathId;
118: return getClassLoaderForPath(p, (Path) path, loaderId,
119: reverseLoader);
120: }
121:
122: /**
123: * Convenience overloaded version of {@link
124: * #getClassLoaderForPath(Project, Path, String, boolean)}.
125: *
126: * <p>Assumes the logical 'false' for the reverseLoader.</p>
127: *
128: * @param p current Ant project
129: * @param path the path
130: * @param loaderId the loader id string
131: * @return The class loader
132: */
133: public static ClassLoader getClassLoaderForPath(Project p,
134: Path path, String loaderId) {
135:
136: return getClassLoaderForPath(p, path, loaderId, false);
137: }
138:
139: /**
140: * Convenience overloaded version of {@link
141: * #getClassLoaderForPath(Project, Path, String, boolean, boolean)}.
142: *
143: * <p>Sets value for 'reuseLoader' to true if the magic property
144: * has been set.</p>
145: *
146: * @param p the project
147: * @param path the path
148: * @param loaderId the loader id string
149: * @param reverseLoader if set to true this new loader will take
150: * precedence over its parent (which is contra the regular
151: * classloader behaviour)
152: * @return The class loader
153: */
154: public static ClassLoader getClassLoaderForPath(Project p,
155: Path path, String loaderId, boolean reverseLoader) {
156: return getClassLoaderForPath(p, path, loaderId, reverseLoader,
157: isMagicPropertySet(p));
158: }
159:
160: /**
161: * Gets a classloader that loads classes from the classpath
162: * defined in the path argument.
163: *
164: * <p>Based on the setting of the magic property
165: * 'ant.reuse.loader' this will try to reuse the previously
166: * created loader with that id, and of course store it there upon
167: * creation.</p>
168: * @param p Ant Project where the handled components are living in.
169: * @param path Path object to be used as classpath for this classloader
170: * @param loaderId identification for this Loader,
171: * @param reverseLoader if set to true this new loader will take
172: * precedence over its parent (which is contra the regular
173: * classloader behaviour)
174: * @param reuseLoader if true reuse the loader if it is found
175: * @return ClassLoader that uses the Path as its classpath.
176: */
177: public static ClassLoader getClassLoaderForPath(Project p,
178: Path path, String loaderId, boolean reverseLoader,
179: boolean reuseLoader) {
180:
181: ClassLoader cl = null;
182:
183: // magic property
184: if (loaderId != null && reuseLoader) {
185: Object reusedLoader = p.getReference(loaderId);
186: if (reusedLoader != null
187: && !(reusedLoader instanceof ClassLoader)) {
188: throw new BuildException("The specified loader id "
189: + loaderId
190: + " does not reference a class loader");
191: }
192: cl = (ClassLoader) reusedLoader;
193: }
194: if (cl == null) {
195: cl = getUniqueClassLoaderForPath(p, path, reverseLoader);
196: if (loaderId != null && reuseLoader) {
197: p.addReference(loaderId, cl);
198: }
199: }
200: return cl;
201: }
202:
203: /**
204: * Gets a fresh, different, previously unused classloader that uses the
205: * passed path as its classpath.
206: *
207: * <p>This method completely ignores the ant.reuse.loader magic
208: * property and should be used with caution.</p>
209: * @param p Ant Project where the handled components are living in.
210: * @param path the classpath for this loader
211: * @param reverseLoader if set to true this new loader will take
212: * precedence over its parent (which is contra the regular
213: * classloader behaviour)
214: * @return The fresh, different, previously unused class loader.
215: */
216: public static ClassLoader getUniqueClassLoaderForPath(Project p,
217: Path path, boolean reverseLoader) {
218: AntClassLoader acl = p.createClassLoader(path);
219: if (reverseLoader) {
220: acl.setParentFirst(false);
221: acl.addJavaLibraries();
222: }
223: return acl;
224: }
225:
226: /**
227: * Creates a fresh object instance of the specified classname.
228: *
229: * <p> This uses the userDefinedLoader to load the specified class,
230: * and then makes an instance using the default no-argument constructor.
231: * </p>
232: *
233: * @param className the full qualified class name to load.
234: * @param userDefinedLoader the classloader to use.
235: * @return The fresh object instance
236: * @throws BuildException when loading or instantiation failed.
237: */
238: public static Object newInstance(String className,
239: ClassLoader userDefinedLoader) {
240: return newInstance(className, userDefinedLoader, Object.class);
241: }
242:
243: /**
244: * Creates a fresh object instance of the specified classname.
245: *
246: * <p> This uses the userDefinedLoader to load the specified class,
247: * and then makes an instance using the default no-argument constructor.
248: * </p>
249: *
250: * @param className the full qualified class name to load.
251: * @param userDefinedLoader the classloader to use.
252: * @param expectedType the Class that the result should be assignment
253: * compatible with. (No ClassCastException will be thrown in case
254: * the result of this method is casted to the expectedType)
255: * @return The fresh object instance
256: * @throws BuildException when loading or instantiation failed.
257: * @since Ant 1.7
258: */
259: public static Object newInstance(String className,
260: ClassLoader userDefinedLoader, Class expectedType) {
261: try {
262: Class clazz = Class.forName(className, true,
263: userDefinedLoader);
264: Object o = clazz.newInstance();
265: if (!expectedType.isInstance(o)) {
266: throw new BuildException("Class of unexpected Type: "
267: + className + " expected :" + expectedType);
268: }
269: return o;
270: } catch (ClassNotFoundException e) {
271: throw new BuildException("Class not found: " + className, e);
272: } catch (InstantiationException e) {
273: throw new BuildException("Could not instantiate "
274: + className + ". Specified class should have a no "
275: + "argument constructor.", e);
276: } catch (IllegalAccessException e) {
277: throw new BuildException("Could not instantiate "
278: + className + ". Specified class should have a "
279: + "public constructor.", e);
280: } catch (LinkageError e) {
281: throw new BuildException(
282: "Class "
283: + className
284: + " could not be loaded because of an invalid dependency.",
285: e);
286: }
287: }
288:
289: /**
290: * Obtains a delegate that helps out with classic classpath configuration.
291: *
292: * @param component your projectComponent that needs the assistence
293: * @return the helper, delegate.
294: * @see ClasspathUtils.Delegate
295: */
296: public static Delegate getDelegate(ProjectComponent component) {
297: return new Delegate(component);
298: }
299:
300: /**
301: * Checks for the magic property that enables class loader reuse
302: * for <taskdef> and <typedef> in Ant 1.5 and earlier.
303: */
304: private static boolean isMagicPropertySet(Project p) {
305: return p.getProperty(REUSE_LOADER_REF) != null;
306: }
307:
308: /**
309: * Delegate that helps out any specific ProjectComponent that needs
310: * dynamic classloading.
311: *
312: * <p>Ant ProjectComponents that need a to be able to dynamically load
313: * Classes and instantiate them often expose the following ant syntax
314: * sugar: </p>
315: *
316: * <ul><li> nested <classpath> </li>
317: * <li> attribute @classpathref </li>
318: * <li> attribute @classname </li></ul>
319: *
320: * <p> This class functions as a delegate handling the configuration
321: * issues for this recurring pattern. Its usage pattern, as the name
322: * suggests, is delegation rather than inheritance. </p>
323: *
324: * @since Ant 1.6
325: */
326: public static class Delegate {
327: private final ProjectComponent component;
328: private Path classpath;
329: private String classpathId;
330: private String className;
331: private String loaderId;
332: private boolean reverseLoader = false;
333:
334: /**
335: * Construct a Delegate
336: * @param component the ProjectComponent this delegate is for.
337: */
338: Delegate(ProjectComponent component) {
339: this .component = component;
340: }
341:
342: /**
343: * This method is a Delegate method handling the @classpath attribute.
344: *
345: * <p>This attribute can set a path to add to the classpath.</p>
346: *
347: * @param classpath the path to use for the classpath.
348: */
349: public void setClasspath(Path classpath) {
350: if (this .classpath == null) {
351: this .classpath = classpath;
352: } else {
353: this .classpath.append(classpath);
354: }
355: }
356:
357: /**
358: * Delegate method handling the <classpath> tag.
359: *
360: * <p>This nested path-like structure can set a path to add to the
361: * classpath.</p>
362: *
363: * @return the created path.
364: */
365: public Path createClasspath() {
366: if (this .classpath == null) {
367: this .classpath = new Path(component.getProject());
368: }
369: return this .classpath.createPath();
370: }
371:
372: /**
373: * Delegate method handling the @classname attribute.
374: *
375: * <p>This attribute sets the full qualified class name of the class
376: * to load and instantiate.</p>
377: *
378: * @param fcqn the name of the class to load.
379: */
380: public void setClassname(String fcqn) {
381: this .className = fcqn;
382: }
383:
384: /**
385: * Delegate method handling the @classpathref attribute.
386: *
387: * <p>This attribute can add a referenced path-like structure to the
388: * classpath.</p>
389: *
390: * @param r the reference to the classpath.
391: */
392: public void setClasspathref(Reference r) {
393: this .classpathId = r.getRefId();
394: createClasspath().setRefid(r);
395: }
396:
397: /**
398: * Delegate method handling the @reverseLoader attribute.
399: *
400: * <p>This attribute can set a boolean indicating that the used
401: * classloader should NOT follow the classical parent-first scheme.
402: * </p>
403: *
404: * <p>By default this is supposed to be false.</p>
405: *
406: * <p>Caution: this behaviour is contradictory to the normal way
407: * classloaders work. Do not let your ProjectComponent use it if
408: * you are not really sure.</p>
409: *
410: * @param reverseLoader if true reverse the order of looking up a class.
411: */
412: public void setReverseLoader(boolean reverseLoader) {
413: this .reverseLoader = reverseLoader;
414: }
415:
416: /**
417: * Sets the loaderRef.
418: * @param r the reference to the loader.
419: */
420: public void setLoaderRef(Reference r) {
421: this .loaderId = r.getRefId();
422: }
423:
424: /**
425: * Finds or creates the classloader for this object.
426: * @return The class loader.
427: */
428: public ClassLoader getClassLoader() {
429: return getClassLoaderForPath(getContextProject(),
430: this .classpath, getClassLoadId(),
431: this .reverseLoader, loaderId != null
432: || isMagicPropertySet(getContextProject()));
433: }
434:
435: /**
436: * The project of the ProjectComponent we are working for.
437: */
438: private Project getContextProject() {
439: return this .component.getProject();
440: }
441:
442: /**
443: * Computes the loaderId based on the configuration of the component.
444: * @return a loader identifier.
445: */
446: public String getClassLoadId() {
447: return this .loaderId == null && this .classpathId != null ? MagicNames.REFID_CLASSPATH_LOADER_PREFIX
448: + this .classpathId
449: : this .loaderId;
450: }
451:
452: /**
453: * Helper method obtaining a fresh instance of the class specified
454: * in the @classname and using the specified classpath.
455: *
456: * @return the fresh instantiated object.
457: */
458: public Object newInstance() {
459: return ClasspathUtils.newInstance(this .className,
460: getClassLoader());
461: }
462:
463: /**
464: * The classpath.
465: * @return the classpath.
466: */
467: public Path getClasspath() {
468: return classpath;
469: }
470:
471: /**
472: * Get the reverseLoader setting.
473: * @return true if looking up in reverse order.
474: */
475: public boolean isReverseLoader() {
476: return reverseLoader;
477: }
478:
479: //TODO no methods yet for getClassname
480: //TODO no method for newInstance using a reverse-classloader
481: }
482: }
|