org.apache.tools.ant.ComponentHelper Maven / Gradle / Ivy
/*
* 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.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Stack;
import org.apache.tools.ant.launch.Launcher;
import org.apache.tools.ant.taskdefs.Definer;
import org.apache.tools.ant.taskdefs.Typedef;
import org.apache.tools.ant.util.FileUtils;
/**
* Component creation and configuration.
*
* The class is based around handing component
* definitions in an AntTypeTable.
*
* The old task/type methods have been kept
* for backward compatibly.
* Project will just delegate its calls to this class.
*
* A very simple hook mechanism is provided that allows users to plug
* in custom code. It is also possible to replace the default behavior
* ( for example in an app embedding ant )
*
* @since Ant1.6
*/
public class ComponentHelper {
/** Map of component name to lists of restricted definitions */
private Map> restrictedDefinitions = new HashMap>();
/** Map from component name to anttypedefinition */
private final Hashtable antTypeTable = new Hashtable();
/** Map of tasks generated from antTypeTable */
private final Hashtable> taskClassDefinitions = new Hashtable>();
/** flag to rebuild taskClassDefinitions */
private boolean rebuildTaskClassDefinitions = true;
/** Map of types generated from antTypeTable */
private final Hashtable> typeClassDefinitions = new Hashtable>();
/** flag to rebuild typeClassDefinitions */
private boolean rebuildTypeClassDefinitions = true;
/** Set of namespaces that have been checked for antlibs */
private final HashSet checkedNamespaces = new HashSet();
/**
* Stack of antlib contexts used to resolve definitions while
* processing antlib
*/
private Stack antLibStack = new Stack();
/** current antlib uri */
private String antLibCurrentUri = null;
/**
* this does not appear to be used anywhere in the Ant codebase
* even via its accessors
*/
private ComponentHelper next;
/**
* Project that owns a component helper
*/
private Project project;
/**
* Error string when the file taskdefs/defaults.properties cannot be found
*/
private static final String ERROR_NO_TASK_LIST_LOAD = "Can't load default task list";
/**
* Error string when the typedefs/defaults.properties cannot be found
*/
private static final String ERROR_NO_TYPE_LIST_LOAD = "Can't load default type list";
/**
* reference under which we register ourselves with a project -{@value}
*/
public static final String COMPONENT_HELPER_REFERENCE = "ant.ComponentHelper";
/**
* string used to control build.syspath policy {@value}
*/
private static final String BUILD_SYSCLASSPATH_ONLY = "only";
/**
* special name of ant's property task -{@value}. There is some
* contrived work here to enable this early.
*/
private static final String ANT_PROPERTY_TASK = "property";
// {tasks, types}
private static Properties[] defaultDefinitions = new Properties[2];
/**
* Get the project.
* @return the project owner of this helper.
*/
public Project getProject() {
return project;
}
/**
* Find a project component for a specific project, creating
* it if it does not exist.
* @param project the project.
* @return the project component for a specific project.
*/
public static ComponentHelper getComponentHelper(Project project) {
if (project == null) {
return null;
}
// Singleton for now, it may change ( per/classloader )
ComponentHelper ph = (ComponentHelper) project.getReference(COMPONENT_HELPER_REFERENCE);
if (ph != null) {
return ph;
}
ph = new ComponentHelper();
ph.setProject(project);
project.addReference(COMPONENT_HELPER_REFERENCE, ph);
return ph;
}
/**
* Creates a new ComponentHelper instance.
*/
protected ComponentHelper() {
}
/**
* Set the next chained component helper.
*
* @param next the next chained component helper.
*/
public void setNext(ComponentHelper next) {
this.next = next;
}
/**
* Get the next chained component helper.
*
* @return the next chained component helper.
*/
public ComponentHelper getNext() {
return next;
}
/**
* Sets the project for this component helper.
*
* @param project the project for this helper.
*/
public void setProject(Project project) {
this.project = project;
// antTypeTable = new Hashtable(project);
}
/**
* @return A copy of the CheckedNamespace.
*/
private synchronized Set getCheckedNamespace() {
@SuppressWarnings("unchecked")
final Set result = (Set) checkedNamespaces.clone();
return result;
}
/**
* @return A deep copy of the restrictredDefinition
*/
private Map> getRestrictedDefinition() {
final Map> result = new HashMap>();
synchronized (restrictedDefinitions) {
for (Map.Entry> entry : restrictedDefinitions.entrySet()) {
List entryVal = entry.getValue();
synchronized (entryVal) {
//copy the entryVal
entryVal = new ArrayList (entryVal);
}
result.put(entry.getKey(), entryVal);
}
}
return result;
}
/**
* Used with creating child projects. Each child
* project inherits the component definitions
* from its parent.
* @param helper the component helper of the parent project.
*/
public void initSubProject(ComponentHelper helper) {
// add the types of the parent project
@SuppressWarnings("unchecked")
final Hashtable typeTable = (Hashtable) helper.antTypeTable.clone();
synchronized (antTypeTable) {
for (AntTypeDefinition def : typeTable.values()) {
antTypeTable.put(def.getName(), def);
}
}
// add the parsed namespaces of the parent project
Set inheritedCheckedNamespace = helper.getCheckedNamespace();
synchronized (this) {
checkedNamespaces.addAll(inheritedCheckedNamespace);
}
Map> inheritedRestrictedDef = helper.getRestrictedDefinition();
synchronized (restrictedDefinitions) {
restrictedDefinitions.putAll(inheritedRestrictedDef);
}
}
/**
* Factory method to create the components.
*
* This should be called by UnknownElement.
*
* @param ue The Unknown Element creating this component.
* @param ns Namespace URI. Also available as ue.getNamespace().
* @param componentType The component type,
* Also available as ue.getComponentName().
* @return the created component.
* @throws BuildException if an error occurs.
*/
public Object createComponent(UnknownElement ue, String ns, String componentType)
throws BuildException {
Object component = createComponent(componentType);
if (component instanceof Task) {
Task task = (Task) component;
task.setLocation(ue.getLocation());
task.setTaskType(componentType);
task.setTaskName(ue.getTaskName());
task.setOwningTarget(ue.getOwningTarget());
task.init();
}
return component;
}
/**
* Create an object for a component.
*
* @param componentName the name of the component, if
* the component is in a namespace, the
* name is prefixed with the namespace uri and ":".
* @return the class if found or null if not.
*/
public Object createComponent(String componentName) {
AntTypeDefinition def = getDefinition(componentName);
return def == null ? null : def.create(project);
}
/**
* Return the class of the component name.
*
* @param componentName the name of the component, if
* the component is in a namespace, the
* name is prefixed with the namespace uri and ":".
* @return the class if found or null if not.
*/
public Class> getComponentClass(String componentName) {
AntTypeDefinition def = getDefinition(componentName);
return def == null ? null : def.getExposedClass(project);
}
/**
* Return the antTypeDefinition for a componentName.
* @param componentName the name of the component.
* @return the ant definition or null if not present.
*/
public AntTypeDefinition getDefinition(String componentName) {
checkNamespace(componentName);
return antTypeTable.get(componentName);
}
/**
* This method is initialization code implementing the original ant component
* loading from /org/apache/tools/ant/taskdefs/default.properties
* and /org/apache/tools/ant/types/default.properties.
*/
public void initDefaultDefinitions() {
initTasks();
initTypes();
new DefaultDefinitions(this).execute();
}
/**
* Adds a new task definition to the project.
* Attempting to override an existing definition with an
* equivalent one (i.e. with the same classname) results in
* a verbose log message. Attempting to override an existing definition
* with a different one results in a warning log message.
*
* @param taskName The name of the task to add.
* Must not be null
.
* @param taskClass The full name of the class implementing the task.
* Must not be null
.
*
* @exception BuildException if the class is unsuitable for being an Ant
* task. An error level message is logged before
* this exception is thrown.
*
* @see #checkTaskClass(Class)
*/
public void addTaskDefinition(String taskName, Class> taskClass) {
checkTaskClass(taskClass);
AntTypeDefinition def = new AntTypeDefinition();
def.setName(taskName);
def.setClassLoader(taskClass.getClassLoader());
def.setClass(taskClass);
def.setAdapterClass(TaskAdapter.class);
def.setClassName(taskClass.getName());
def.setAdaptToClass(Task.class);
updateDataTypeDefinition(def);
}
/**
* Checks whether or not a class is suitable for serving as Ant task.
* Ant task implementation classes must be public, concrete, and have
* a no-arg constructor.
*
* @param taskClass The class to be checked.
* Must not be null
.
*
* @exception BuildException if the class is unsuitable for being an Ant
* task. An error level message is logged before
* this exception is thrown.
*/
public void checkTaskClass(final Class> taskClass) throws BuildException {
if (!Modifier.isPublic(taskClass.getModifiers())) {
final String message = taskClass + " is not public";
project.log(message, Project.MSG_ERR);
throw new BuildException(message);
}
if (Modifier.isAbstract(taskClass.getModifiers())) {
final String message = taskClass + " is abstract";
project.log(message, Project.MSG_ERR);
throw new BuildException(message);
}
try {
taskClass.getConstructor((Class[]) null);
// don't have to check for public, since
// getConstructor finds public constructors only.
} catch (NoSuchMethodException e) {
final String message = "No public no-arg constructor in " + taskClass;
project.log(message, Project.MSG_ERR);
throw new BuildException(message);
}
if (!Task.class.isAssignableFrom(taskClass)) {
TaskAdapter.checkTaskClass(taskClass, project);
}
}
/**
* Returns the current task definition hashtable. The returned hashtable is
* "live" and so should not be modified. Also, the returned table may be
* modified asynchronously.
*
* @return a map of from task name to implementing class
* (String to Class).
*/
public Hashtable> getTaskDefinitions() {
synchronized (taskClassDefinitions) {
synchronized (antTypeTable) {
if (rebuildTaskClassDefinitions) {
taskClassDefinitions.clear();
for (Map.Entry e : antTypeTable.entrySet()) {
final Class> clazz = e.getValue().getExposedClass(project);
if (clazz == null) {
continue;
}
if (Task.class.isAssignableFrom(clazz)) {
taskClassDefinitions.put(e.getKey(), e.getValue().getTypeClass(project));
}
}
rebuildTaskClassDefinitions = false;
}
}
}
return taskClassDefinitions;
}
/**
* Returns the current type definition hashtable. The returned hashtable is
* "live" and so should not be modified.
*
* @return a map of from type name to implementing class
* (String to Class).
*/
public Hashtable> getDataTypeDefinitions() {
synchronized (typeClassDefinitions) {
synchronized (antTypeTable) {
if (rebuildTypeClassDefinitions) {
typeClassDefinitions.clear();
for (Map.Entry e : antTypeTable.entrySet()) {
final Class> clazz = e.getValue().getExposedClass(project);
if (clazz == null) {
continue;
}
if (!Task.class.isAssignableFrom(clazz)) {
typeClassDefinitions.put(e.getKey(), e.getValue().getTypeClass(project));
}
}
rebuildTypeClassDefinitions = false;
}
}
}
return typeClassDefinitions;
}
/**
* This returns a list of restricted definitions for a name.
* The returned List is "live" and so should not be modified.
* Also, the returned list may be modified asynchronously.
* Any access must be guarded with a lock on the list itself.
*
* @param componentName the name to use.
* @return the list of restricted definitions for a particular name.
*/
public List getRestrictedDefinitions(String componentName) {
synchronized (restrictedDefinitions) {
return restrictedDefinitions.get(componentName);
}
}
/**
* Adds a new datatype definition.
* Attempting to override an existing definition with an
* equivalent one (i.e. with the same classname) results in
* a verbose log message. Attempting to override an existing definition
* with a different one results in a warning log message, but the
* definition is changed.
*
* @param typeName The name of the datatype.
* Must not be null
.
* @param typeClass The full name of the class implementing the datatype.
* Must not be null
.
*/
public void addDataTypeDefinition(String typeName, Class> typeClass) {
final AntTypeDefinition def = new AntTypeDefinition();
def.setName(typeName);
def.setClass(typeClass);
updateDataTypeDefinition(def);
project.log(" +User datatype: " + typeName + " " + typeClass.getName(),
Project.MSG_DEBUG);
}
/**
* Describe addDataTypeDefinition
method here.
*
* @param def an AntTypeDefinition
value.
*/
public void addDataTypeDefinition(AntTypeDefinition def) {
if (!def.isRestrict()) {
updateDataTypeDefinition(def);
} else {
updateRestrictedDefinition(def);
}
}
/**
* Returns the current datatype definition hashtable. The returned
* hashtable is "live" and so should not be modified.
*
* @return a map of from datatype name to datatype definition
* (String to {@link AntTypeDefinition}).
*/
public Hashtable getAntTypeTable() {
return antTypeTable;
}
/**
* Creates a new instance of a task.
*
* Called from Project.createTask(), which can be called by tasks.
*
* @param taskType The name of the task to create an instance of.
* Must not be null
.
*
* @return an instance of the specified task, or null
if
* the task name is not recognised.
*
* @exception BuildException if the task name is recognised but task
* creation fails.
*/
public Task createTask(String taskType) throws BuildException {
Task task = createNewTask(taskType);
if (task == null && taskType.equals(ANT_PROPERTY_TASK)) {
// quick fix for Ant.java use of property before
// initializing the project
addTaskDefinition(ANT_PROPERTY_TASK, org.apache.tools.ant.taskdefs.Property.class);
task = createNewTask(taskType);
}
return task;
}
/**
* Creates a new instance of a task.
* @since ant1.6
* @param taskType The name of the task to create an instance of.
* Must not be null
.
*
* @return an instance of the specified task, or null
if
* the task name is not recognised.
*
* @exception BuildException if the task name is recognised but task
* creation fails.
*/
private Task createNewTask(String taskType) throws BuildException {
Class> c = getComponentClass(taskType);
if (c == null || !(Task.class.isAssignableFrom(c))) {
return null;
}
Object obj = createComponent(taskType);
if (obj == null) {
return null;
}
if (!(obj instanceof Task)) {
throw new BuildException("Expected a Task from '" + taskType
+ "' but got an instance of " + obj.getClass().getName() + " instead");
}
Task task = (Task) obj;
task.setTaskType(taskType);
// set default value, can be changed by the user
task.setTaskName(taskType);
project.log(" +Task: " + taskType, Project.MSG_DEBUG);
return task;
}
/**
* Creates a new instance of a data type.
*
* @param typeName The name of the data type to create an instance of.
* Must not be null
.
*
* @return an instance of the specified data type, or null
if
* the data type name is not recognised.
*
* @exception BuildException if the data type name is recognised but
* instance creation fails.
*/
public Object createDataType(String typeName) throws BuildException {
return createComponent(typeName);
}
/**
* Returns a description of the type of the given element.
*
* This is useful for logging purposes.
*
* @param element The element to describe.
* Must not be null
.
*
* @return a description of the element type.
*
* @since Ant 1.6
*/
public String getElementName(Object element) {
return getElementName(element, false);
}
/**
* Returns a description of the type of the given element.
*
* This is useful for logging purposes.
*
* @param o The element to describe.
* Must not be null
.
* @param brief whether to use a brief description.
* @return a description of the element type.
*
* @since Ant 1.7
*/
public String getElementName(Object o, boolean brief) {
// PR: I do not know what to do if the object class
// has multiple defines
// but this is for logging only...
Class> elementClass = o.getClass();
String elementClassname = elementClass.getName();
synchronized (antTypeTable) {
for (AntTypeDefinition def : antTypeTable.values()) {
if (elementClassname.equals(def.getClassName())
&& (elementClass == def.getExposedClass(project))) {
String name = def.getName();
return brief ? name : "The <" + name + "> type";
}
}
}
return getUnmappedElementName(o.getClass(), brief);
}
/**
* Convenient way to get some element name even when you may not have a
* Project context.
* @param p The optional Project instance.
* @param o The element to describe.
* Must not be null
.
* @param brief whether to use a brief description.
* @return a description of the element type.
* @since Ant 1.7
*/
public static String getElementName(Project p, Object o, boolean brief) {
if (p == null) {
p = Project.getProject(o);
}
return p == null ? getUnmappedElementName(o.getClass(), brief) : getComponentHelper(p)
.getElementName(o, brief);
}
private static String getUnmappedElementName(Class> c, boolean brief) {
if (brief) {
String name = c.getName();
return name.substring(name.lastIndexOf('.') + 1);
}
return c.toString();
}
/**
* Check if definition is a valid definition--it may be a
* definition of an optional task that does not exist.
* @param def the definition to test.
* @return true if exposed type of definition is present.
*/
private boolean validDefinition(AntTypeDefinition def) {
return !(def.getTypeClass(project) == null || def.getExposedClass(project) == null);
}
/**
* Check if two definitions are the same.
* @param def the new definition.
* @param old the old definition.
* @return true if the two definitions are the same.
*/
private boolean sameDefinition(AntTypeDefinition def, AntTypeDefinition old) {
boolean defValid = validDefinition(def);
boolean sameValidity = (defValid == validDefinition(old));
//must have same validity; then if they are valid they must also be the same:
return sameValidity && (!defValid || def.sameDefinition(old, project));
}
/**
* update the restricted definition table with a new or
* modified definition.
*/
private void updateRestrictedDefinition(AntTypeDefinition def) {
String name = def.getName();
List list = null;
synchronized (restrictedDefinitions) {
list = restrictedDefinitions.get(name);
if (list == null) {
list = new ArrayList();
restrictedDefinitions.put(name, list);
}
}
// Check if the classname is already present and remove it
// if it is
synchronized (list) {
for (Iterator i = list.iterator(); i.hasNext();) {
AntTypeDefinition current = i.next();
if (current.getClassName().equals(def.getClassName())) {
i.remove();
break;
}
}
list.add(def);
}
}
/**
* Update the component definition table with a new or
* modified definition.
* @param def the definition to update or insert.
*/
private void updateDataTypeDefinition(AntTypeDefinition def) {
String name = def.getName();
synchronized (antTypeTable) {
rebuildTaskClassDefinitions = true;
rebuildTypeClassDefinitions = true;
final AntTypeDefinition old = antTypeTable.get(name);
if (old != null) {
if (sameDefinition(def, old)) {
return;
}
Class> oldClass = old.getExposedClass(project);
boolean isTask = oldClass != null && Task.class.isAssignableFrom(oldClass);
project.log("Trying to override old definition of "
+ (isTask ? "task " : "datatype ") + name, (def.similarDefinition(old,
project)) ? Project.MSG_VERBOSE : Project.MSG_WARN);
}
project.log(" +Datatype " + name + " " + def.getClassName(), Project.MSG_DEBUG);
antTypeTable.put(name, def);
}
}
/**
* Called at the start of processing an antlib.
* @param uri the uri that is associated with this antlib.
*/
public void enterAntLib(String uri) {
antLibCurrentUri = uri;
antLibStack.push(uri);
}
/**
* @return the current antlib uri.
*/
public String getCurrentAntlibUri() {
return antLibCurrentUri;
}
/**
* Called at the end of processing an antlib.
*/
public void exitAntLib() {
antLibStack.pop();
antLibCurrentUri = (antLibStack.size() == 0) ? null : (String) antLibStack.peek();
}
/**
* Load ant's tasks.
*/
private void initTasks() {
ClassLoader classLoader = getClassLoader(null);
Properties props = getDefaultDefinitions(false);
Enumeration> e = props.propertyNames();
while (e.hasMoreElements()) {
String name = (String) e.nextElement();
String className = props.getProperty(name);
AntTypeDefinition def = new AntTypeDefinition();
def.setName(name);
def.setClassName(className);
def.setClassLoader(classLoader);
def.setAdaptToClass(Task.class);
def.setAdapterClass(TaskAdapter.class);
antTypeTable.put(name, def);
}
}
private ClassLoader getClassLoader(ClassLoader classLoader) {
String buildSysclasspath = project.getProperty(MagicNames.BUILD_SYSCLASSPATH);
if (project.getCoreLoader() != null
&& !(BUILD_SYSCLASSPATH_ONLY.equals(buildSysclasspath))) {
classLoader = project.getCoreLoader();
}
return classLoader;
}
/**
* Load default task or type definitions - just the names,
* no class loading.
* Caches results between calls to reduce overhead.
* @param type true for typedefs, false for taskdefs
* @return a mapping from definition names to class names
* @throws BuildException if there was some problem loading
* or parsing the definitions list
*/
private static synchronized Properties getDefaultDefinitions(boolean type)
throws BuildException {
int idx = type ? 1 : 0;
if (defaultDefinitions[idx] == null) {
String resource = type ? MagicNames.TYPEDEFS_PROPERTIES_RESOURCE
: MagicNames.TASKDEF_PROPERTIES_RESOURCE;
String errorString = type ? ERROR_NO_TYPE_LIST_LOAD : ERROR_NO_TASK_LIST_LOAD;
InputStream in = null;
try {
in = ComponentHelper.class.getResourceAsStream(resource);
if (in == null) {
throw new BuildException(errorString);
}
Properties p = new Properties();
p.load(in);
defaultDefinitions[idx] = p;
} catch (IOException e) {
throw new BuildException(errorString, e);
} finally {
FileUtils.close(in);
}
}
return defaultDefinitions[idx];
}
/**
* Load ant's datatypes.
*/
private void initTypes() {
ClassLoader classLoader = getClassLoader(null);
Properties props = getDefaultDefinitions(true);
Enumeration> e = props.propertyNames();
while (e.hasMoreElements()) {
String name = (String) e.nextElement();
String className = props.getProperty(name);
AntTypeDefinition def = new AntTypeDefinition();
def.setName(name);
def.setClassName(className);
def.setClassLoader(classLoader);
antTypeTable.put(name, def);
}
}
/**
* Called for each component name, check if the
* associated URI has been examined for antlibs.
* @param componentName the name of the component, which should include a URI
* prefix if it is in a namespace
*/
private synchronized void checkNamespace(String componentName) {
String uri = ProjectHelper.extractUriFromComponentName(componentName);
if ("".equals(uri)) {
uri = ProjectHelper.ANT_CORE_URI;
}
if (!uri.startsWith(ProjectHelper.ANTLIB_URI)) {
return; // namespace that does not contain antlib
}
if (checkedNamespaces.contains(uri)) {
return; // Already processed
}
checkedNamespaces.add(uri);
if (antTypeTable.size() == 0) {
// Project instance doesn't know the tasks and types
// defined in defaults.properties, likely created by the
// user - without those definitions it cannot parse antlib
// files as taskdef, typedef and friends are unknown
initDefaultDefinitions();
}
Typedef definer = new Typedef();
definer.setProject(project);
definer.init();
definer.setURI(uri);
//there to stop error messages being "null"
definer.setTaskName(uri);
//if this is left out, bad things happen. like all build files break
//on the first element encountered.
definer.setResource(Definer.makeResourceFromURI(uri));
// a fishing expedition :- ignore errors if antlib not present
definer.setOnError(new Typedef.OnError(Typedef.OnError.POLICY_IGNORE));
definer.execute();
}
/**
* Handler called to do decent diagnosis on instantiation failure.
* @param componentName component name.
* @param type component type, used in error messages
* @return a string containing as much diagnostics info as possible.
*/
public String diagnoseCreationFailure(String componentName, String type) {
StringWriter errorText = new StringWriter();
PrintWriter out = new PrintWriter(errorText);
out.println("Problem: failed to create " + type + " " + componentName);
//class of problem
boolean lowlevel = false;
boolean jars = false;
boolean definitions = false;
boolean antTask;
String home = System.getProperty(Launcher.USER_HOMEDIR);
File libDir = new File(home, Launcher.USER_LIBDIR);
String antHomeLib;
boolean probablyIDE = false;
String anthome = System.getProperty(MagicNames.ANT_HOME);
if (anthome != null) {
File antHomeLibDir = new File(anthome, "lib");
antHomeLib = antHomeLibDir.getAbsolutePath();
} else {
//running under an IDE that doesn't set ANT_HOME
probablyIDE = true;
antHomeLib = "ANT_HOME" + File.separatorChar + "lib";
}
StringBuffer dirListingText = new StringBuffer();
final String tab = " -";
dirListingText.append(tab);
dirListingText.append(antHomeLib);
dirListingText.append('\n');
if (probablyIDE) {
dirListingText.append(tab);
dirListingText.append("the IDE Ant configuration dialogs");
} else {
dirListingText.append(tab);
dirListingText.append(libDir);
dirListingText.append('\n');
dirListingText.append(tab);
dirListingText.append("a directory added on the command line with the -lib argument");
}
String dirListing = dirListingText.toString();
//look up the name
AntTypeDefinition def = getDefinition(componentName);
if (def == null) {
//not a known type
printUnknownDefinition(out, componentName, dirListing);
definitions = true;
} else {
//we are defined, so it is an instantiation problem
final String classname = def.getClassName();
antTask = classname.startsWith("org.apache.tools.ant.");
boolean optional = classname.startsWith("org.apache.tools.ant.taskdefs.optional");
optional |= classname.startsWith("org.apache.tools.ant.types.optional");
//start with instantiating the class.
Class> clazz = null;
try {
clazz = def.innerGetTypeClass();
} catch (ClassNotFoundException e) {
jars = true;
if (!optional) {
definitions = true;
}
printClassNotFound(out, classname, optional, dirListing);
} catch (NoClassDefFoundError ncdfe) {
jars = true;
printNotLoadDependentClass(out, optional, ncdfe, dirListing);
}
//here we successfully loaded the class or failed.
if (clazz != null) {
//success: proceed with more steps
try {
def.innerCreateAndSet(clazz, project);
//hey, there is nothing wrong with us
out.println("The component could be instantiated.");
} catch (NoSuchMethodException e) {
lowlevel = true;
out.println("Cause: The class " + classname
+ " has no compatible constructor.");
} catch (InstantiationException e) {
lowlevel = true;
out.println("Cause: The class " + classname
+ " is abstract and cannot be instantiated.");
} catch (IllegalAccessException e) {
lowlevel = true;
out.println("Cause: The constructor for " + classname
+ " is private and cannot be invoked.");
} catch (InvocationTargetException ex) {
lowlevel = true;
Throwable t = ex.getTargetException();
out.println("Cause: The constructor threw the exception");
out.println(t.toString());
t.printStackTrace(out);
} catch (NoClassDefFoundError ncdfe) {
jars = true;
out.println("Cause: A class needed by class " + classname
+ " cannot be found: ");
out.println(" " + ncdfe.getMessage());
out.println("Action: Determine what extra JAR files are"
+ " needed, and place them in:");
out.println(dirListing);
}
}
out.println();
out.println("Do not panic, this is a common problem.");
if (definitions) {
out.println("It may just be a typographical error in the build file "
+ "or the task/type declaration.");
}
if (jars) {
out.println("The commonest cause is a missing JAR.");
}
if (lowlevel) {
out.println("This is quite a low level problem, which may need "
+ "consultation with the author of the task.");
if (antTask) {
out.println("This may be the Ant team. Please file a "
+ "defect or contact the developer team.");
} else {
out.println("This does not appear to be a task bundled with Ant.");
out.println("Please take it up with the supplier of the third-party " + type
+ ".");
out.println("If you have written it yourself, you probably have a bug to fix.");
}
} else {
out.println();
out.println("This is not a bug; it is a configuration problem");
}
}
out.flush();
out.close();
return errorText.toString();
}
/**
* Print unknown definition.forking
*/
private void printUnknownDefinition(PrintWriter out, String componentName, String dirListing) {
boolean isAntlib = componentName.startsWith(MagicNames.ANTLIB_PREFIX);
String uri = ProjectHelper.extractUriFromComponentName(componentName);
out.println("Cause: The name is undefined.");
out.println("Action: Check the spelling.");
out.println("Action: Check that any custom tasks/types have been declared.");
out.println("Action: Check that any /"
+ " declarations have taken place.");
if (uri.length() > 0) {
final List matches = findTypeMatches(uri);
if (matches.size() > 0) {
out.println();
out.println("The definitions in the namespace " + uri + " are:");
for (AntTypeDefinition def : matches) {
String local = ProjectHelper.extractNameFromComponentName(def.getName());
out.println(" " + local);
}
} else {
out.println("No types or tasks have been defined in this namespace yet");
if (isAntlib) {
out.println();
out.println("This appears to be an antlib declaration. ");
out.println("Action: Check that the implementing library exists in one of:");
out.println(dirListing);
}
}
}
}
/**
* Print class not found.
*/
private void printClassNotFound(PrintWriter out, String classname, boolean optional,
String dirListing) {
out.println("Cause: the class " + classname + " was not found.");
if (optional) {
out.println(" This looks like one of Ant's optional components.");
out.println("Action: Check that the appropriate optional JAR exists in");
out.println(dirListing);
} else {
out.println("Action: Check that the component has been correctly declared");
out.println(" and that the implementing JAR is in one of:");
out.println(dirListing);
}
}
/**
* Print could not load dependent class.
*/
private void printNotLoadDependentClass(PrintWriter out, boolean optional,
NoClassDefFoundError ncdfe, String dirListing) {
out.println("Cause: Could not load a dependent class "
+ ncdfe.getMessage());
if (optional) {
out.println(" It is not enough to have Ant's optional JARs");
out.println(" you need the JAR files that the" + " optional tasks depend upon.");
out.println(" Ant's optional task dependencies are" + " listed in the manual.");
} else {
out.println(" This class may be in a separate JAR" + " that is not installed.");
}
out.println("Action: Determine what extra JAR files are"
+ " needed, and place them in one of:");
out.println(dirListing);
}
/**
* Create a list of all definitions that match a prefix, usually the URI
* of a library
* @param prefix prefix to match off
* @return the (possibly empty) list of definitions
*/
private List findTypeMatches(String prefix) {
final List result = new ArrayList();
synchronized (antTypeTable) {
for (AntTypeDefinition def : antTypeTable.values()) {
if (def.getName().startsWith(prefix)) {
result.add(def);
}
}
}
return result;
}
}