blob: 38db0d51bbde37d194a755610b78d5faa61d1570 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.tools.ant.util;
import org.apache.tools.ant.AntClassLoader;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.ProjectComponent;
import org.apache.tools.ant.MagicNames;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.Reference;
// CheckStyle:HideUtilityClassConstructorCheck OFF - bc
/**
* Offers some helper methods on the Path structure in ant.
*
* <p>The basic idea behind this utility class is to use it from inside the
* different Ant objects (and user defined objects) that need classLoading
* for their operation.
* Normally those would have a setClasspathRef() {for the @classpathref}
* and/or a createClasspath() {for the nested &lt;classpath&gt;}
* Typically one would have in your Ant Task or DataType</p>
*
* <pre><code>
* ClasspathUtils.Delegate cpDelegate;
*
* public void init() {
* this.cpDelegate = ClasspathUtils.getDelegate(this);
* super.init();
* }
*
* public void setClasspathRef(Reference r) {
* this.cpDelegate.setClasspathRef(r);
* }
*
* public Path createClasspath() {
* return this.cpDelegate.createClasspath();
* }
*
* public void setClassname(String fqcn) {
* this.cpDelegate.setClassname(fqcn);
* }
* </code></pre>
*
* <p>At execution time, when you actually need the classloading
* you can just:</p>
*
* <pre><code>
* Object o = this.cpDelegate.newInstance();
* </code></pre>
*
* @since Ant 1.6
*/
public class ClasspathUtils {
/**
* Name of the magic property that controls classloader reuse in Ant 1.4.
*/
public static final String REUSE_LOADER_REF = MagicNames.REFID_CLASSPATH_REUSE_LOADER;
/**
* Convenience overloaded version of {@link
* #getClassLoaderForPath(Project, Reference, boolean)}.
*
* <p>Assumes the logical 'false' for the reverseLoader.</p>
*
* @param p the project
* @param ref the reference
* @return The class loader
*/
public static ClassLoader getClassLoaderForPath(Project p, Reference ref) {
return getClassLoaderForPath(p, ref, false);
}
/**
* Convenience overloaded version of {@link #getClassLoaderForPath(Project, Path,
* String, boolean)}.
*
* <p>Delegates to the other one after extracting the referenced
* Path from the Project. This checks also that the passed
* Reference is pointing to a Path all right.</p>
* @param p current Ant project
* @param ref Reference to Path structure
* @param reverseLoader if set to true this new loader will take
* precedence over its parent (which is contra the regular
* classloader behaviour)
* @return The class loader
*/
public static ClassLoader getClassLoaderForPath(
Project p, Reference ref, boolean reverseLoader) {
String pathId = ref.getRefId();
Object path = p.getReference(pathId);
if (!(path instanceof Path)) {
throw new BuildException("The specified classpathref " + pathId
+ " does not reference a Path.");
}
String loaderId = MagicNames.REFID_CLASSPATH_LOADER_PREFIX + pathId;
return getClassLoaderForPath(p, (Path) path, loaderId, reverseLoader);
}
/**
* Convenience overloaded version of {@link
* #getClassLoaderForPath(Project, Path, String, boolean)}.
*
* <p>Assumes the logical 'false' for the reverseLoader.</p>
*
* @param p current Ant project
* @param path the path
* @param loaderId the loader id string
* @return The class loader
*/
public static ClassLoader getClassLoaderForPath(Project p, Path path, String loaderId) {
return getClassLoaderForPath(p, path, loaderId, false);
}
/**
* Convenience overloaded version of {@link
* #getClassLoaderForPath(Project, Path, String, boolean, boolean)}.
*
* <p>Sets value for 'reuseLoader' to true if the magic property
* has been set.</p>
*
* @param p the project
* @param path the path
* @param loaderId the loader id string
* @param reverseLoader if set to true this new loader will take
* precedence over its parent (which is contra the regular
* classloader behaviour)
* @return The class loader
*/
public static ClassLoader getClassLoaderForPath(
Project p, Path path, String loaderId, boolean reverseLoader) {
return getClassLoaderForPath(p, path, loaderId, reverseLoader, isMagicPropertySet(p));
}
/**
* Gets a classloader that loads classes from the classpath
* defined in the path argument.
*
* <p>Based on the setting of the magic property
* 'ant.reuse.loader' this will try to reuse the previously
* created loader with that id, and of course store it there upon
* creation.</p>
* @param p Ant Project where the handled components are living in.
* @param path Path object to be used as classpath for this classloader
* @param loaderId identification for this Loader,
* @param reverseLoader if set to true this new loader will take
* precedence over its parent (which is contra the regular
* classloader behaviour)
* @param reuseLoader if true reuse the loader if it is found
* @return ClassLoader that uses the Path as its classpath.
*/
public static ClassLoader getClassLoaderForPath(
Project p, Path path, String loaderId, boolean reverseLoader, boolean reuseLoader) {
ClassLoader cl = null;
// magic property
if (loaderId != null && reuseLoader) {
Object reusedLoader = p.getReference(loaderId);
if (reusedLoader != null && !(reusedLoader instanceof ClassLoader)) {
throw new BuildException("The specified loader id " + loaderId
+ " does not reference a class loader");
}
cl = (ClassLoader) reusedLoader;
}
if (cl == null) {
cl = getUniqueClassLoaderForPath(p, path, reverseLoader);
if (loaderId != null && reuseLoader) {
p.addReference(loaderId, cl);
}
}
return cl;
}
/**
* Gets a fresh, different, previously unused classloader that uses the
* passed path as its classpath.
*
* <p>This method completely ignores the ant.reuse.loader magic
* property and should be used with caution.</p>
* @param p Ant Project where the handled components are living in.
* @param path the classpath for this loader
* @param reverseLoader if set to true this new loader will take
* precedence over its parent (which is contra the regular
* classloader behaviour)
* @return The fresh, different, previously unused class loader.
*/
public static ClassLoader getUniqueClassLoaderForPath(Project p, Path path,
boolean reverseLoader) {
AntClassLoader acl = p.createClassLoader(path);
if (reverseLoader) {
acl.setParentFirst(false);
acl.addJavaLibraries();
}
return acl;
}
/**
* Creates a fresh object instance of the specified classname.
*
* <p> This uses the userDefinedLoader to load the specified class,
* and then makes an instance using the default no-argument constructor.
* </p>
*
* @param className the full qualified class name to load.
* @param userDefinedLoader the classloader to use.
* @return The fresh object instance
* @throws BuildException when loading or instantiation failed.
*/
public static Object newInstance(String className, ClassLoader userDefinedLoader) {
return newInstance(className, userDefinedLoader, Object.class);
}
/**
* Creates a fresh object instance of the specified classname.
*
* <p> This uses the userDefinedLoader to load the specified class,
* and then makes an instance using the default no-argument constructor.
* </p>
*
* @param className the full qualified class name to load.
* @param userDefinedLoader the classloader to use.
* @param expectedType the Class that the result should be assignment
* compatible with. (No ClassCastException will be thrown in case
* the result of this method is casted to the expectedType)
* @return The fresh object instance
* @throws BuildException when loading or instantiation failed.
* @since Ant 1.7
*/
public static Object newInstance(String className, ClassLoader userDefinedLoader,
Class expectedType) {
try {
Class clazz = Class.forName(className, true, userDefinedLoader);
Object o = clazz.newInstance();
if (!expectedType.isInstance(o)) {
throw new BuildException("Class of unexpected Type: " + className + " expected :"
+ expectedType);
}
return o;
} catch (ClassNotFoundException e) {
throw new BuildException("Class not found: " + className, e);
} catch (InstantiationException e) {
throw new BuildException("Could not instantiate " + className
+ ". Specified class should have a no " + "argument constructor.", e);
} catch (IllegalAccessException e) {
throw new BuildException("Could not instantiate " + className
+ ". Specified class should have a " + "public constructor.", e);
} catch (LinkageError e) {
throw new BuildException("Class " + className
+ " could not be loaded because of an invalid dependency.", e);
}
}
/**
* Obtains a delegate that helps out with classic classpath configuration.
*
* @param component your projectComponent that needs the assistence
* @return the helper, delegate.
* @see ClasspathUtils.Delegate
*/
public static Delegate getDelegate(ProjectComponent component) {
return new Delegate(component);
}
/**
* Checks for the magic property that enables class loader reuse
* for <taskdef> and <typedef> in Ant 1.5 and earlier.
*/
private static boolean isMagicPropertySet(Project p) {
return p.getProperty(REUSE_LOADER_REF) != null;
}
/**
* Delegate that helps out any specific ProjectComponent that needs
* dynamic classloading.
*
* <p>Ant ProjectComponents that need a to be able to dynamically load
* Classes and instantiate them often expose the following ant syntax
* sugar: </p>
*
* <ul><li> nested &lt;classpath&gt; </li>
* <li> attribute @classpathref </li>
* <li> attribute @classname </li></ul>
*
* <p> This class functions as a delegate handling the configuration
* issues for this recurring pattern. Its usage pattern, as the name
* suggests, is delegation rather than inheritance. </p>
*
* @since Ant 1.6
*/
public static class Delegate {
private final ProjectComponent component;
private Path classpath;
private String classpathId;
private String className;
private String loaderId;
private boolean reverseLoader = false;
/**
* Construct a Delegate
* @param component the ProjectComponent this delegate is for.
*/
Delegate(ProjectComponent component) {
this.component = component;
}
/**
* This method is a Delegate method handling the @classpath attribute.
*
* <p>This attribute can set a path to add to the classpath.</p>
*
* @param classpath the path to use for the classpath.
*/
public void setClasspath(Path classpath) {
if (this.classpath == null) {
this.classpath = classpath;
} else {
this.classpath.append(classpath);
}
}
/**
* Delegate method handling the &lt;classpath&gt; tag.
*
* <p>This nested path-like structure can set a path to add to the
* classpath.</p>
*
* @return the created path.
*/
public Path createClasspath() {
if (this.classpath == null) {
this.classpath = new Path(component.getProject());
}
return this.classpath.createPath();
}
/**
* Delegate method handling the @classname attribute.
*
* <p>This attribute sets the full qualified class name of the class
* to load and instantiate.</p>
*
* @param fcqn the name of the class to load.
*/
public void setClassname(String fcqn) {
this.className = fcqn;
}
/**
* Delegate method handling the @classpathref attribute.
*
* <p>This attribute can add a referenced path-like structure to the
* classpath.</p>
*
* @param r the reference to the classpath.
*/
public void setClasspathref(Reference r) {
this.classpathId = r.getRefId();
createClasspath().setRefid(r);
}
/**
* Delegate method handling the @reverseLoader attribute.
*
* <p>This attribute can set a boolean indicating that the used
* classloader should NOT follow the classical parent-first scheme.
* </p>
*
* <p>By default this is supposed to be false.</p>
*
* <p>Caution: this behaviour is contradictory to the normal way
* classloaders work. Do not let your ProjectComponent use it if
* you are not really sure.</p>
*
* @param reverseLoader if true reverse the order of looking up a class.
*/
public void setReverseLoader(boolean reverseLoader) {
this.reverseLoader = reverseLoader;
}
/**
* Sets the loaderRef.
* @param r the reference to the loader.
*/
public void setLoaderRef(Reference r) {
this.loaderId = r.getRefId();
}
/**
* Finds or creates the classloader for this object.
* @return The class loader.
*/
public ClassLoader getClassLoader() {
return getClassLoaderForPath(getContextProject(), classpath, getClassLoadId(),
reverseLoader, loaderId != null || isMagicPropertySet(getContextProject()));
}
/**
* The project of the ProjectComponent we are working for.
*/
private Project getContextProject() {
return component.getProject();
}
/**
* Computes the loaderId based on the configuration of the component.
* @return a loader identifier.
*/
public String getClassLoadId() {
if (loaderId == null && classpathId != null) {
return MagicNames.REFID_CLASSPATH_LOADER_PREFIX + classpathId;
} else {
return loaderId;
}
}
/**
* Helper method obtaining a fresh instance of the class specified
* in the @classname and using the specified classpath.
*
* @return the fresh instantiated object.
*/
public Object newInstance() {
return ClasspathUtils.newInstance(this.className, getClassLoader());
}
/**
* The classpath.
* @return the classpath.
*/
public Path getClasspath() {
return classpath;
}
/**
* Get the reverseLoader setting.
* @return true if looking up in reverse order.
*/
public boolean isReverseLoader() {
return reverseLoader;
}
//TODO no methods yet for getClassname
//TODO no method for newInstance using a reverse-classloader
}
}