blob: a51482fd78846945c85f60861a3e4af273c9bc81 [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;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Constructor;
/**
* This class contains all the information
* on a particular ant type,
* the classname, adaptor and the class
* it should be assignable from.
* This type replaces the task/datatype split
* of pre ant 1.6.
*
*/
public class AntTypeDefinition {
private String name;
private Class clazz;
private Class adapterClass;
private Class adaptToClass;
private String className;
private ClassLoader classLoader;
private boolean restrict = false;
/**
* Set the restrict attribute.
* @param restrict the value to set.
*/
public void setRestrict(boolean restrict) {
this.restrict = restrict;
}
/**
* Get the restrict attribute.
* @return the restrict attribute.
*/
public boolean isRestrict() {
return restrict;
}
/**
* Set the definition's name.
* @param name the name of the definition.
*/
public void setName(String name) {
this.name = name;
}
/**
* Return the definition's name.
* @return the name of the definition.
*/
public String getName() {
return name;
}
/**
* Set the class of the definition.
* As a side-effect may set the classloader and classname.
* @param clazz the class of this definition.
*/
public void setClass(Class clazz) {
this.clazz = clazz;
if (clazz == null) {
return;
}
this.classLoader = (classLoader == null)
? clazz.getClassLoader() : classLoader;
this.className = (className == null) ? clazz.getName() : className;
}
/**
* Set the classname of the definition.
* @param className the classname of this definition.
*/
public void setClassName(String className) {
this.className = className;
}
/**
* Get the classname of the definition.
* @return the name of the class of this definition.
*/
public String getClassName() {
return className;
}
/**
* Set the adapter class for this definition.
* This class is used to adapt the definitions class if
* required.
* @param adapterClass the adapterClass.
*/
public void setAdapterClass(Class adapterClass) {
this.adapterClass = adapterClass;
}
/**
* Set the assignable class for this definition.
* @param adaptToClass the assignable class.
*/
public void setAdaptToClass(Class adaptToClass) {
this.adaptToClass = adaptToClass;
}
/**
* Set the classloader to use to create an instance
* of the definition.
* @param classLoader the ClassLoader.
*/
public void setClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
/**
* Get the classloader for this definition.
* @return the classloader for this definition.
*/
public ClassLoader getClassLoader() {
return classLoader;
}
/**
* Get the exposed class for this
* definition. This will be a proxy class
* (adapted class) if there is an adapter
* class and the definition class is not
* assignable from the assignable class.
* @param project the current project.
* @return the exposed class - may return null if upable to load the class
*/
public Class getExposedClass(Project project) {
if (adaptToClass != null) {
Class z = getTypeClass(project);
if (z == null || adaptToClass.isAssignableFrom(z)) {
return z;
}
}
return (adapterClass == null) ? getTypeClass(project) : adapterClass;
}
/**
* Get the definition class.
* @param project the current project.
* @return the type of the definition.
*/
public Class getTypeClass(Project project) {
try {
return innerGetTypeClass();
} catch (NoClassDefFoundError ncdfe) {
project.log("Could not load a dependent class ("
+ ncdfe.getMessage() + ") for type "
+ name, Project.MSG_DEBUG);
} catch (ClassNotFoundException cnfe) {
project.log("Could not load class (" + className
+ ") for type " + name, Project.MSG_DEBUG);
}
return null;
}
/**
* Try and load a class, with no attempt to catch any fault.
* @return the class that implements this component
* @throws ClassNotFoundException if the class cannot be found.
* @throws NoClassDefFoundError if the there is an error
* finding the class.
*/
public Class innerGetTypeClass() throws ClassNotFoundException {
if (clazz != null) {
return clazz;
}
if (classLoader == null) {
clazz = Class.forName(className);
} else {
clazz = classLoader.loadClass(className);
}
return clazz;
}
/**
* Create an instance of the definition.
* The instance may be wrapped in a proxy class.
* @param project the current project.
* @return the created object.
*/
public Object create(Project project) {
return icreate(project);
}
/**
* Create a component object based on
* its definition.
* @return the component as an <code>Object</code>.
*/
private Object icreate(Project project) {
Class c = getTypeClass(project);
if (c == null) {
return null;
}
Object o = createAndSet(project, c);
if (o == null || adapterClass == null) {
return o;
}
if (adaptToClass != null) {
if (adaptToClass.isAssignableFrom(o.getClass())) {
return o;
}
}
TypeAdapter adapterObject = (TypeAdapter) createAndSet(
project, adapterClass);
if (adapterObject == null) {
return null;
}
adapterObject.setProxy(o);
return adapterObject;
}
/**
* Checks if the attributes are correct.
* <dl>
* <li>if the class can be created.</li>
* <li>if an adapter class can be created</li>
* <li>if the type is assignable from adapto</li>
* <li>if the type can be used with the adapter class</li>
* </dl>
* @param project the current project.
*/
public void checkClass(Project project) {
if (clazz == null) {
clazz = getTypeClass(project);
if (clazz == null) {
throw new BuildException(
"Unable to create class for " + getName());
}
}
// check adapter
if (adapterClass != null && (adaptToClass == null
|| !adaptToClass.isAssignableFrom(clazz))) {
TypeAdapter adapter = (TypeAdapter) createAndSet(
project, adapterClass);
if (adapter == null) {
throw new BuildException("Unable to create adapter object");
}
adapter.checkProxyClass(clazz);
}
}
/**
* Get the constructor of the definition
* and invoke it.
* @return the instantiated <code>Object</code>.
*/
private Object createAndSet(Project project, Class c) {
try {
Object o = innerCreateAndSet(c, project);
return o;
} catch (InvocationTargetException ex) {
Throwable t = ex.getTargetException();
throw new BuildException(
"Could not create type " + name + " due to " + t, t);
} catch (NoClassDefFoundError ncdfe) {
String msg = "Type " + name + ": A class needed by class "
+ c + " cannot be found: " + ncdfe.getMessage();
throw new BuildException(msg, ncdfe);
} catch (NoSuchMethodException nsme) {
throw new BuildException("Could not create type " + name
+ " as the class " + c + " has no compatible constructor");
} catch (InstantiationException nsme) {
throw new BuildException("Could not create type "
+ name + " as the class " + c + " is abstract");
} catch (IllegalAccessException e) {
throw new BuildException("Could not create type "
+ name + " as the constructor " + c + " is not accessible");
} catch (Throwable t) {
throw new BuildException(
"Could not create type " + name + " due to " + t, t);
}
}
/**
* Inner implementation of the {@link #createAndSet(Project, Class)} logic, with no
* exception catching
* @param newclass class to create
* @param project the project to use
* @return a newly constructed and bound instance.
* @throws NoSuchMethodException no good construtor.
* @throws InstantiationException cannot initialize the object.
* @throws IllegalAccessException cannot access the object.
* @throws InvocationTargetException error in invocation.
*/
public Object innerCreateAndSet(Class newclass, Project project)
throws NoSuchMethodException,
InstantiationException,
IllegalAccessException,
InvocationTargetException {
Constructor ctor = null;
boolean noArg = false;
// DataType can have a "no arg" constructor or take a single
// Project argument.
try {
ctor = newclass.getConstructor(new Class[0]);
noArg = true;
} catch (NoSuchMethodException nse) {
//can throw the same exception, if there is no this(Project) ctor.
ctor = newclass.getConstructor(new Class[] {Project.class});
noArg = false;
}
//now we instantiate
Object o = ctor.newInstance(
((noArg) ? new Object[0] : new Object[] {project}));
//set up project references.
project.setProjectReference(o);
return o;
}
/**
* Equality method for this definition (assumes the names are the same).
*
* @param other another definition.
* @param project the project the definition.
* @return true if the definitions are the same.
*/
public boolean sameDefinition(AntTypeDefinition other, Project project) {
return (other != null && other.getClass() == getClass()
&& other.getTypeClass(project).equals(getTypeClass(project))
&& other.getExposedClass(project).equals(getExposedClass(project))
&& other.restrict == restrict
&& other.adapterClass == adapterClass
&& other.adaptToClass == adaptToClass);
}
/**
* Similar definition;
* used to compare two definitions defined twice with the same
* name and the same types.
* The classloader may be different but have the same
* path so #sameDefinition cannot
* be used.
* @param other the definition to compare to.
* @param project the current project.
* @return true if the definitions are the same.
*/
public boolean similarDefinition(AntTypeDefinition other, Project project) {
if (other == null
|| getClass() != other.getClass()
|| !getClassName().equals(other.getClassName())
|| !extractClassname(adapterClass).equals(
extractClassname(other.adapterClass))
|| !extractClassname(adaptToClass).equals(
extractClassname(other.adaptToClass))
|| restrict != other.restrict) {
return false;
}
// all the names are the same: check if the class path of the loader
// is the same
ClassLoader oldLoader = other.getClassLoader();
ClassLoader newLoader = getClassLoader();
return oldLoader == newLoader
|| (oldLoader instanceof AntClassLoader
&& newLoader instanceof AntClassLoader
&& ((AntClassLoader) oldLoader).getClasspath()
.equals(((AntClassLoader) newLoader).getClasspath()));
}
private String extractClassname(Class c) {
return (c == null) ? "<null>" : c.getClass().getName();
}
}