/* * 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.Constructor; import java.lang.reflect.InvocationTargetException; /** * This class contains all the information * on a particular ant type, * the classname, adapter 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 unable 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. * <ul> * <li>if the class can be created.</li> * <li>if an adapter class can be created</li> * <li>if the type is assignable from adapter</li> * <li>if the type can be used with the adapter class</li> * </ul> * @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 <T> return type of the method * @param newclass class to create * @param project the project to use * @return a newly constructed and bound instance. * @throws NoSuchMethodException no good constructor. * @throws InstantiationException cannot initialize the object. * @throws IllegalAccessException cannot access the object. * @throws InvocationTargetException error in invocation. */ public <T> T innerCreateAndSet(Class<T> newclass, Project project) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { Constructor<T> ctor; 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 T 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(); } }