0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: *
0017: */
0018:
0019: package org.apache.tools.ant;
0020:
0021: import java.lang.reflect.Method;
0022: import java.lang.reflect.Modifier;
0023: import java.lang.reflect.InvocationTargetException;
0024: import java.io.InputStream;
0025: import java.io.IOException;
0026: import java.io.File;
0027: import java.io.StringWriter;
0028: import java.io.PrintWriter;
0029: import java.util.Enumeration;
0030: import java.util.Hashtable;
0031: import java.util.HashSet;
0032: import java.util.Iterator;
0033: import java.util.Properties;
0034: import java.util.Set;
0035: import java.util.Stack;
0036:
0037: import org.apache.tools.ant.taskdefs.Typedef;
0038: import org.apache.tools.ant.taskdefs.Definer;
0039: import org.apache.tools.ant.launch.Launcher;
0040: import org.apache.tools.ant.util.FileUtils;
0041:
0042: /**
0043: * Component creation and configuration.
0044: *
0045: * The class is based around handing component
0046: * definitions in an AntTypeTable.
0047: *
0048: * The old task/type methods have been kept
0049: * for backward compatibly.
0050: * Project will just delegate its calls to this class.
0051: *
0052: * A very simple hook mechanism is provided that allows users to plug
0053: * in custom code. It is also possible to replace the default behavior
0054: * ( for example in an app embedding ant )
0055: *
0056: * @since Ant1.6
0057: */
0058: public class ComponentHelper {
0059: /** Map from component name to anttypedefinition */
0060: private AntTypeTable antTypeTable;
0061:
0062: /** Map of tasks generated from antTypeTable */
0063: private Hashtable taskClassDefinitions = new Hashtable();
0064: /** flag to rebuild taskClassDefinitions */
0065: private boolean rebuildTaskClassDefinitions = true;
0066:
0067: /** Map of types generated from antTypeTable */
0068: private Hashtable typeClassDefinitions = new Hashtable();
0069: /** flag to rebuild typeClassDefinitions */
0070: private boolean rebuildTypeClassDefinitions = true;
0071:
0072: /** Set of namespaces that have been checked for antlibs */
0073: private Set checkedNamespaces = new HashSet();
0074:
0075: /**
0076: * Stack of antlib contexts used to resolve definitions while
0077: * processing antlib
0078: */
0079: private Stack antLibStack = new Stack();
0080: /** current antlib uri */
0081: private String antLibCurrentUri = null;
0082:
0083: /**
0084: * this does not appear to be used anywhere in the Ant codebase
0085: * even via its accessors
0086: */
0087: private ComponentHelper next;
0088:
0089: /**
0090: * Project that owns a component helper
0091: */
0092: private Project project;
0093:
0094: /**
0095: * Error string when the file taskdefs/defaults.properties cannot be found
0096: */
0097: private static final String ERROR_NO_TASK_LIST_LOAD = "Can't load default task list";
0098: /**
0099: * Error string when the typedefs/defaults.properties cannot be found
0100: */
0101: private static final String ERROR_NO_TYPE_LIST_LOAD = "Can't load default type list";
0102:
0103: /**
0104: * reference under which we register ourselves with a project -{@value}
0105: */
0106: public static final String COMPONENT_HELPER_REFERENCE = "ant.ComponentHelper";
0107:
0108: /**
0109: * string used to control build.syspath policy {@value}
0110: */
0111: private static final String BUILD_SYSCLASSPATH_ONLY = "only";
0112:
0113: /**
0114: * special name of ant's property task -{@value}. There is some
0115: * contrived work here to enable this early.
0116: */
0117: private static final String ANT_PROPERTY_TASK = "property";
0118:
0119: // {tasks, types}
0120: private static Properties[] defaultDefinitions = new Properties[2];
0121:
0122: /**
0123: * Find a project component for a specific project, creating
0124: * it if it does not exist.
0125: * @param project the project.
0126: * @return the project component for a specific project.
0127: */
0128: public static ComponentHelper getComponentHelper(Project project) {
0129: if (project == null) {
0130: return null;
0131: }
0132: // Singleton for now, it may change ( per/classloader )
0133: ComponentHelper ph = (ComponentHelper) project
0134: .getReference(COMPONENT_HELPER_REFERENCE);
0135: if (ph != null) {
0136: return ph;
0137: }
0138: ph = new ComponentHelper();
0139: ph.setProject(project);
0140:
0141: project.addReference(COMPONENT_HELPER_REFERENCE, ph);
0142: return ph;
0143: }
0144:
0145: /**
0146: * Creates a new ComponentHelper instance.
0147: */
0148: protected ComponentHelper() {
0149: }
0150:
0151: /**
0152: * Set the next chained component helper.
0153: *
0154: * @param next the next chained component helper.
0155: */
0156: public void setNext(ComponentHelper next) {
0157: this .next = next;
0158: }
0159:
0160: /**
0161: * Get the next chained component helper.
0162: *
0163: * @return the next chained component helper.
0164: */
0165: public ComponentHelper getNext() {
0166: return next;
0167: }
0168:
0169: /**
0170: * Sets the project for this component helper.
0171: *
0172: * @param project the project for this helper.
0173: */
0174: public void setProject(Project project) {
0175: this .project = project;
0176: antTypeTable = new AntTypeTable(project);
0177: }
0178:
0179: /**
0180: * Used with creating child projects. Each child
0181: * project inherits the component definitions
0182: * from its parent.
0183: * @param helper the component helper of the parent project.
0184: */
0185: public void initSubProject(ComponentHelper helper) {
0186: // add the types of the parent project
0187: AntTypeTable typeTable = helper.antTypeTable;
0188: for (Iterator i = typeTable.values().iterator(); i.hasNext();) {
0189: AntTypeDefinition def = (AntTypeDefinition) i.next();
0190: antTypeTable.put(def.getName(), def);
0191: }
0192: // add the parsed namespaces of the parent project
0193: for (Iterator i = helper.checkedNamespaces.iterator(); i
0194: .hasNext();) {
0195: checkedNamespaces.add(i.next());
0196: }
0197: }
0198:
0199: /**
0200: * Factory method to create the components.
0201: *
0202: * This should be called by UnknownElement.
0203: *
0204: * @param ue The Unknown Element creating this component.
0205: * @param ns Namespace URI. Also available as ue.getNamespace().
0206: * @param componentType The component type,
0207: * Also available as ue.getComponentName().
0208: * @return the created component.
0209: * @throws BuildException if an error occurs.
0210: */
0211: public Object createComponent(UnknownElement ue, String ns,
0212: String componentType) throws BuildException {
0213: Object component = createComponent(componentType);
0214: if (component instanceof Task) {
0215: Task task = (Task) component;
0216: task.setLocation(ue.getLocation());
0217: task.setTaskType(componentType);
0218: task.setTaskName(ue.getTaskName());
0219: task.setOwningTarget(ue.getOwningTarget());
0220: task.init();
0221: }
0222: return component;
0223: }
0224:
0225: /**
0226: * Create an object for a component.
0227: *
0228: * @param componentName the name of the component, if
0229: * the component is in a namespace, the
0230: * name is prefixed with the namespace uri and ":".
0231: * @return the class if found or null if not.
0232: */
0233: public Object createComponent(String componentName) {
0234: AntTypeDefinition def = getDefinition(componentName);
0235: return (def == null) ? null : def.create(project);
0236: }
0237:
0238: /**
0239: * Return the class of the component name.
0240: *
0241: * @param componentName the name of the component, if
0242: * the component is in a namespace, the
0243: * name is prefixed with the namespace uri and ":".
0244: * @return the class if found or null if not.
0245: */
0246: public Class getComponentClass(String componentName) {
0247: AntTypeDefinition def = getDefinition(componentName);
0248: return (def == null) ? null : def.getExposedClass(project);
0249: }
0250:
0251: /**
0252: * Return the antTypeDefinition for a componentName.
0253: * @param componentName the name of the component.
0254: * @return the ant definition or null if not present.
0255: */
0256: public AntTypeDefinition getDefinition(String componentName) {
0257: checkNamespace(componentName);
0258: return antTypeTable.getDefinition(componentName);
0259: }
0260:
0261: /**
0262: * This method is initialization code implementing the original ant component
0263: * loading from /org/apache/tools/ant/taskdefs/default.properties
0264: * and /org/apache/tools/ant/types/default.properties.
0265: */
0266: public void initDefaultDefinitions() {
0267: initTasks();
0268: initTypes();
0269: }
0270:
0271: /**
0272: * Adds a new task definition to the project.
0273: * Attempting to override an existing definition with an
0274: * equivalent one (i.e. with the same classname) results in
0275: * a verbose log message. Attempting to override an existing definition
0276: * with a different one results in a warning log message.
0277: *
0278: * @param taskName The name of the task to add.
0279: * Must not be <code>null</code>.
0280: * @param taskClass The full name of the class implementing the task.
0281: * Must not be <code>null</code>.
0282: *
0283: * @exception BuildException if the class is unsuitable for being an Ant
0284: * task. An error level message is logged before
0285: * this exception is thrown.
0286: *
0287: * @see #checkTaskClass(Class)
0288: */
0289: public void addTaskDefinition(String taskName, Class taskClass) {
0290: checkTaskClass(taskClass);
0291: AntTypeDefinition def = new AntTypeDefinition();
0292: def.setName(taskName);
0293: def.setClassLoader(taskClass.getClassLoader());
0294: def.setClass(taskClass);
0295: def.setAdapterClass(TaskAdapter.class);
0296: def.setClassName(taskClass.getName());
0297: def.setAdaptToClass(Task.class);
0298: updateDataTypeDefinition(def);
0299: }
0300:
0301: /**
0302: * Checks whether or not a class is suitable for serving as Ant task.
0303: * Ant task implementation classes must be public, concrete, and have
0304: * a no-arg constructor.
0305: *
0306: * @param taskClass The class to be checked.
0307: * Must not be <code>null</code>.
0308: *
0309: * @exception BuildException if the class is unsuitable for being an Ant
0310: * task. An error level message is logged before
0311: * this exception is thrown.
0312: */
0313: public void checkTaskClass(final Class taskClass)
0314: throws BuildException {
0315: if (!Modifier.isPublic(taskClass.getModifiers())) {
0316: final String message = taskClass + " is not public";
0317: project.log(message, Project.MSG_ERR);
0318: throw new BuildException(message);
0319: }
0320: if (Modifier.isAbstract(taskClass.getModifiers())) {
0321: final String message = taskClass + " is abstract";
0322: project.log(message, Project.MSG_ERR);
0323: throw new BuildException(message);
0324: }
0325: try {
0326: taskClass.getConstructor((Class[]) null);
0327: // don't have to check for public, since
0328: // getConstructor finds public constructors only.
0329: } catch (NoSuchMethodException e) {
0330: final String message = "No public no-arg constructor in "
0331: + taskClass;
0332: project.log(message, Project.MSG_ERR);
0333: throw new BuildException(message);
0334: }
0335: if (!Task.class.isAssignableFrom(taskClass)) {
0336: TaskAdapter.checkTaskClass(taskClass, project);
0337: }
0338: }
0339:
0340: /**
0341: * Returns the current task definition hashtable. The returned hashtable is
0342: * "live" and so should not be modified.
0343: *
0344: * @return a map of from task name to implementing class
0345: * (String to Class).
0346: */
0347: public Hashtable getTaskDefinitions() {
0348: synchronized (taskClassDefinitions) {
0349: synchronized (antTypeTable) {
0350: if (rebuildTaskClassDefinitions) {
0351: taskClassDefinitions.clear();
0352: for (Iterator i = antTypeTable.keySet().iterator(); i
0353: .hasNext();) {
0354: String name = (String) i.next();
0355: Class clazz = antTypeTable
0356: .getExposedClass(name);
0357: if (clazz == null) {
0358: continue;
0359: }
0360: if (Task.class.isAssignableFrom(clazz)) {
0361: taskClassDefinitions.put(name, antTypeTable
0362: .getTypeClass(name));
0363: }
0364: }
0365: rebuildTaskClassDefinitions = false;
0366: }
0367: }
0368: }
0369: return taskClassDefinitions;
0370: }
0371:
0372: /**
0373: * Returns the current type definition hashtable. The returned hashtable is
0374: * "live" and so should not be modified.
0375: *
0376: * @return a map of from type name to implementing class
0377: * (String to Class).
0378: */
0379: public Hashtable getDataTypeDefinitions() {
0380: synchronized (typeClassDefinitions) {
0381: synchronized (antTypeTable) {
0382: if (rebuildTypeClassDefinitions) {
0383: typeClassDefinitions.clear();
0384: for (Iterator i = antTypeTable.keySet().iterator(); i
0385: .hasNext();) {
0386: String name = (String) i.next();
0387: Class clazz = antTypeTable
0388: .getExposedClass(name);
0389: if (clazz == null) {
0390: continue;
0391: }
0392: if (!(Task.class.isAssignableFrom(clazz))) {
0393: typeClassDefinitions.put(name, antTypeTable
0394: .getTypeClass(name));
0395: }
0396: }
0397: rebuildTypeClassDefinitions = false;
0398: }
0399: }
0400: }
0401: return typeClassDefinitions;
0402: }
0403:
0404: /**
0405: * Adds a new datatype definition.
0406: * Attempting to override an existing definition with an
0407: * equivalent one (i.e. with the same classname) results in
0408: * a verbose log message. Attempting to override an existing definition
0409: * with a different one results in a warning log message, but the
0410: * definition is changed.
0411: *
0412: * @param typeName The name of the datatype.
0413: * Must not be <code>null</code>.
0414: * @param typeClass The full name of the class implementing the datatype.
0415: * Must not be <code>null</code>.
0416: */
0417: public void addDataTypeDefinition(String typeName, Class typeClass) {
0418: AntTypeDefinition def = new AntTypeDefinition();
0419: def.setName(typeName);
0420: def.setClass(typeClass);
0421: updateDataTypeDefinition(def);
0422: project.log(" +User datatype: " + typeName + " "
0423: + typeClass.getName(), Project.MSG_DEBUG);
0424: }
0425:
0426: /**
0427: * Describe <code>addDataTypeDefinition</code> method here.
0428: *
0429: * @param def an <code>AntTypeDefinition</code> value.
0430: */
0431: public void addDataTypeDefinition(AntTypeDefinition def) {
0432: updateDataTypeDefinition(def);
0433: }
0434:
0435: /**
0436: * Returns the current datatype definition hashtable. The returned
0437: * hashtable is "live" and so should not be modified.
0438: *
0439: * @return a map of from datatype name to implementing class
0440: * (String to Class).
0441: */
0442: public Hashtable getAntTypeTable() {
0443: return antTypeTable;
0444: }
0445:
0446: /**
0447: * Creates a new instance of a task.
0448: *
0449: * Called from Project.createTask(), which can be called by tasks.
0450: *
0451: * @param taskType The name of the task to create an instance of.
0452: * Must not be <code>null</code>.
0453: *
0454: * @return an instance of the specified task, or <code>null</code> if
0455: * the task name is not recognised.
0456: *
0457: * @exception BuildException if the task name is recognised but task
0458: * creation fails.
0459: */
0460: public Task createTask(String taskType) throws BuildException {
0461: Task task = createNewTask(taskType);
0462: if (task == null && taskType.equals(ANT_PROPERTY_TASK)) {
0463: // quick fix for Ant.java use of property before
0464: // initializing the project
0465: addTaskDefinition(ANT_PROPERTY_TASK,
0466: org.apache.tools.ant.taskdefs.Property.class);
0467: task = createNewTask(taskType);
0468: }
0469: return task;
0470: }
0471:
0472: /**
0473: * Creates a new instance of a task.
0474: * @since ant1.6
0475: * @param taskType The name of the task to create an instance of.
0476: * Must not be <code>null</code>.
0477: *
0478: * @return an instance of the specified task, or <code>null</code> if
0479: * the task name is not recognised.
0480: *
0481: * @exception BuildException if the task name is recognised but task
0482: * creation fails.
0483: */
0484: private Task createNewTask(String taskType) throws BuildException {
0485: Class c = getComponentClass(taskType);
0486: if (c == null || !(Task.class.isAssignableFrom(c))) {
0487: return null;
0488: }
0489: Object obj = createComponent(taskType);
0490: if (obj == null) {
0491: return null;
0492: }
0493: if (!(obj instanceof Task)) {
0494: throw new BuildException("Expected a Task from '"
0495: + taskType + "' but got an instance of "
0496: + obj.getClass().getName() + " instead");
0497: }
0498: Task task = (Task) obj;
0499: task.setTaskType(taskType);
0500:
0501: // set default value, can be changed by the user
0502: task.setTaskName(taskType);
0503:
0504: project.log(" +Task: " + taskType, Project.MSG_DEBUG);
0505: return task;
0506: }
0507:
0508: /**
0509: * Creates a new instance of a data type.
0510: *
0511: * @param typeName The name of the data type to create an instance of.
0512: * Must not be <code>null</code>.
0513: *
0514: * @return an instance of the specified data type, or <code>null</code> if
0515: * the data type name is not recognised.
0516: *
0517: * @exception BuildException if the data type name is recognised but
0518: * instance creation fails.
0519: */
0520: public Object createDataType(String typeName) throws BuildException {
0521: return createComponent(typeName);
0522: }
0523:
0524: /**
0525: * Returns a description of the type of the given element.
0526: * <p>
0527: * This is useful for logging purposes.
0528: *
0529: * @param element The element to describe.
0530: * Must not be <code>null</code>.
0531: *
0532: * @return a description of the element type.
0533: *
0534: * @since Ant 1.6
0535: */
0536: public String getElementName(Object element) {
0537: return getElementName(element, false);
0538: }
0539:
0540: /**
0541: * Returns a description of the type of the given element.
0542: * <p>
0543: * This is useful for logging purposes.
0544: *
0545: * @param o The element to describe.
0546: * Must not be <code>null</code>.
0547: * @param brief whether to use a brief description.
0548: * @return a description of the element type.
0549: *
0550: * @since Ant 1.7
0551: */
0552: public String getElementName(Object o, boolean brief) {
0553: // PR: I do not know what to do if the object class
0554: // has multiple defines
0555: // but this is for logging only...
0556: Class elementClass = o.getClass();
0557: String elementClassname = elementClass.getName();
0558: for (Iterator i = antTypeTable.values().iterator(); i.hasNext();) {
0559: AntTypeDefinition def = (AntTypeDefinition) i.next();
0560: if (elementClassname.equals(def.getClassName())
0561: && (elementClass == def.getExposedClass(project))) {
0562: String name = def.getName();
0563: return brief ? name : "The <" + name + "> type";
0564: }
0565: }
0566: return getUnmappedElementName(o.getClass(), brief);
0567: }
0568:
0569: /**
0570: * Convenient way to get some element name even when you may not have a
0571: * Project context.
0572: * @param p The optional Project instance.
0573: * @param o The element to describe.
0574: * Must not be <code>null</code>.
0575: * @param brief whether to use a brief description.
0576: * @return a description of the element type.
0577: * @since Ant 1.7
0578: */
0579: public static String getElementName(Project p, Object o,
0580: boolean brief) {
0581: if (p == null) {
0582: p = getProject(o);
0583: }
0584: return p == null ? getUnmappedElementName(o.getClass(), brief)
0585: : getComponentHelper(p).getElementName(o, brief);
0586: }
0587:
0588: private static String getUnmappedElementName(Class c, boolean brief) {
0589: if (brief) {
0590: String name = c.getName();
0591: return name.substring(name.lastIndexOf('.') + 1);
0592: }
0593: return c.toString();
0594: }
0595:
0596: private static Project getProject(Object o) {
0597: if (o instanceof ProjectComponent) {
0598: return ((ProjectComponent) o).getProject();
0599: }
0600: try {
0601: Method m = o.getClass().getMethod("getProject",
0602: (Class[]) null);
0603: if (Project.class == m.getReturnType()) {
0604: return (Project) m.invoke(o, (Object[]) null);
0605: }
0606: } catch (Exception e) {
0607: //too bad
0608: }
0609: return null;
0610: }
0611:
0612: /**
0613: * Check if definition is a valid definition--it may be a
0614: * definition of an optional task that does not exist.
0615: * @param def the definition to test.
0616: * @return true if exposed type of definition is present.
0617: */
0618: private boolean validDefinition(AntTypeDefinition def) {
0619: return !(def.getTypeClass(project) == null || def
0620: .getExposedClass(project) == null);
0621: }
0622:
0623: /**
0624: * Check if two definitions are the same.
0625: * @param def the new definition.
0626: * @param old the old definition.
0627: * @return true if the two definitions are the same.
0628: */
0629: private boolean sameDefinition(AntTypeDefinition def,
0630: AntTypeDefinition old) {
0631: boolean defValid = validDefinition(def);
0632: boolean sameValidity = (defValid == validDefinition(old));
0633: //must have same validity; then if they are valid they must also be the same:
0634: return sameValidity
0635: && (!defValid || def.sameDefinition(old, project));
0636: }
0637:
0638: /**
0639: * Update the component definition table with a new or
0640: * modified definition.
0641: * @param def the definition to update or insert.
0642: */
0643: private void updateDataTypeDefinition(AntTypeDefinition def) {
0644: String name = def.getName();
0645: synchronized (antTypeTable) {
0646: rebuildTaskClassDefinitions = true;
0647: rebuildTypeClassDefinitions = true;
0648: AntTypeDefinition old = antTypeTable.getDefinition(name);
0649: if (old != null) {
0650: if (sameDefinition(def, old)) {
0651: return;
0652: }
0653: Class oldClass = antTypeTable.getExposedClass(name);
0654: boolean isTask = (oldClass != null && Task.class
0655: .isAssignableFrom(oldClass));
0656: project
0657: .log("Trying to override old definition of "
0658: + (isTask ? "task " : "datatype ")
0659: + name, (def.similarDefinition(old,
0660: project)) ? Project.MSG_VERBOSE
0661: : Project.MSG_WARN);
0662: }
0663: project.log(
0664: " +Datatype " + name + " " + def.getClassName(),
0665: Project.MSG_DEBUG);
0666: antTypeTable.put(name, def);
0667: }
0668: }
0669:
0670: /**
0671: * Called at the start of processing an antlib.
0672: * @param uri the uri that is associated with this antlib.
0673: */
0674: public void enterAntLib(String uri) {
0675: antLibCurrentUri = uri;
0676: antLibStack.push(uri);
0677: }
0678:
0679: /**
0680: * @return the current antlib uri.
0681: */
0682: public String getCurrentAntlibUri() {
0683: return antLibCurrentUri;
0684: }
0685:
0686: /**
0687: * Called at the end of processing an antlib.
0688: */
0689: public void exitAntLib() {
0690: antLibStack.pop();
0691: antLibCurrentUri = (antLibStack.size() == 0) ? null
0692: : (String) antLibStack.peek();
0693: }
0694:
0695: /**
0696: * Load ant's tasks.
0697: */
0698: private void initTasks() {
0699: ClassLoader classLoader = getClassLoader(null);
0700: Properties props = getDefaultDefinitions(false);
0701: Enumeration e = props.propertyNames();
0702: while (e.hasMoreElements()) {
0703: String name = (String) e.nextElement();
0704: String className = props.getProperty(name);
0705: AntTypeDefinition def = new AntTypeDefinition();
0706: def.setName(name);
0707: def.setClassName(className);
0708: def.setClassLoader(classLoader);
0709: def.setAdaptToClass(Task.class);
0710: def.setAdapterClass(TaskAdapter.class);
0711: antTypeTable.put(name, def);
0712: }
0713: }
0714:
0715: private ClassLoader getClassLoader(ClassLoader classLoader) {
0716: String buildSysclasspath = project
0717: .getProperty(MagicNames.BUILD_SYSCLASSPATH);
0718: if (project.getCoreLoader() != null
0719: && !(BUILD_SYSCLASSPATH_ONLY.equals(buildSysclasspath))) {
0720: classLoader = project.getCoreLoader();
0721: }
0722: return classLoader;
0723: }
0724:
0725: /**
0726: * Load default task or type definitions - just the names,
0727: * no class loading.
0728: * Caches results between calls to reduce overhead.
0729: * @param type true for typedefs, false for taskdefs
0730: * @return a mapping from definition names to class names
0731: * @throws BuildException if there was some problem loading
0732: * or parsing the definitions list
0733: */
0734: private static synchronized Properties getDefaultDefinitions(
0735: boolean type) throws BuildException {
0736: int idx = type ? 1 : 0;
0737: if (defaultDefinitions[idx] == null) {
0738: String resource = type ? MagicNames.TYPEDEFS_PROPERTIES_RESOURCE
0739: : MagicNames.TASKDEF_PROPERTIES_RESOURCE;
0740: String errorString = type ? ERROR_NO_TYPE_LIST_LOAD
0741: : ERROR_NO_TASK_LIST_LOAD;
0742: InputStream in = null;
0743: try {
0744: in = ComponentHelper.class
0745: .getResourceAsStream(resource);
0746: if (in == null) {
0747: throw new BuildException(errorString);
0748: }
0749: Properties p = new Properties();
0750: p.load(in);
0751: defaultDefinitions[idx] = p;
0752: } catch (IOException e) {
0753: throw new BuildException(errorString, e);
0754: } finally {
0755: FileUtils.close(in);
0756: }
0757: }
0758: return defaultDefinitions[idx];
0759: }
0760:
0761: /**
0762: * Load ant's datatypes.
0763: */
0764: private void initTypes() {
0765: ClassLoader classLoader = getClassLoader(null);
0766: Properties props = getDefaultDefinitions(true);
0767: Enumeration e = props.propertyNames();
0768: while (e.hasMoreElements()) {
0769: String name = (String) e.nextElement();
0770: String className = props.getProperty(name);
0771: AntTypeDefinition def = new AntTypeDefinition();
0772: def.setName(name);
0773: def.setClassName(className);
0774: def.setClassLoader(classLoader);
0775: antTypeTable.put(name, def);
0776: }
0777: }
0778:
0779: /**
0780: * Called for each component name, check if the
0781: * associated URI has been examined for antlibs.
0782: */
0783: private synchronized void checkNamespace(String componentName) {
0784: String uri = ProjectHelper
0785: .extractUriFromComponentName(componentName);
0786: if ("".equals(uri)) {
0787: uri = ProjectHelper.ANT_CORE_URI;
0788: }
0789: if (!uri.startsWith(ProjectHelper.ANTLIB_URI)) {
0790: return; // namespace that does not contain antlib
0791: }
0792: if (checkedNamespaces.contains(uri)) {
0793: return; // Already processed
0794: }
0795: checkedNamespaces.add(uri);
0796: Typedef definer = new Typedef();
0797: definer.setProject(project);
0798: definer.init();
0799: definer.setURI(uri);
0800: //there to stop error messages being "null"
0801: definer.setTaskName(uri);
0802: //if this is left out, bad things happen. like all build files break
0803: //on the first element encountered.
0804: definer.setResource(Definer.makeResourceFromURI(uri));
0805: // a fishing expedition :- ignore errors if antlib not present
0806: definer.setOnError(new Typedef.OnError(
0807: Typedef.OnError.POLICY_IGNORE));
0808: definer.execute();
0809: }
0810:
0811: /**
0812: * Handler called to do decent diagnosis on instantiation failure.
0813: * @param componentName component name.
0814: * @param type component type, used in error messages
0815: * @return a string containing as much diagnostics info as possible.
0816: */
0817: public String diagnoseCreationFailure(String componentName,
0818: String type) {
0819: StringWriter errorText = new StringWriter();
0820: PrintWriter out = new PrintWriter(errorText);
0821: out.println("Problem: failed to create " + type + " "
0822: + componentName);
0823: //class of problem
0824: boolean lowlevel = false;
0825: boolean jars = false;
0826: boolean definitions = false;
0827: boolean antTask;
0828: String home = System.getProperty(Launcher.USER_HOMEDIR);
0829: File libDir = new File(home, Launcher.USER_LIBDIR);
0830: String antHomeLib;
0831: boolean probablyIDE = false;
0832: String anthome = System.getProperty(MagicNames.ANT_HOME);
0833: if (anthome != null) {
0834: File antHomeLibDir = new File(anthome, "lib");
0835: antHomeLib = antHomeLibDir.getAbsolutePath();
0836: } else {
0837: //running under an IDE that doesn't set ANT_HOME
0838: probablyIDE = true;
0839: antHomeLib = "ANT_HOME" + File.separatorChar + "lib";
0840: }
0841: StringBuffer dirListingText = new StringBuffer();
0842: final String tab = " -";
0843: dirListingText.append(tab);
0844: dirListingText.append(antHomeLib);
0845: dirListingText.append('\n');
0846: if (probablyIDE) {
0847: dirListingText.append(tab);
0848: dirListingText.append("the IDE Ant configuration dialogs");
0849: } else {
0850: dirListingText.append(tab);
0851: dirListingText.append(libDir);
0852: dirListingText.append('\n');
0853: dirListingText.append(tab);
0854: dirListingText
0855: .append("a directory added on the command line with the -lib argument");
0856: }
0857:
0858: String dirListing = dirListingText.toString();
0859:
0860: //look up the name
0861: AntTypeDefinition def = getDefinition(componentName);
0862: if (def == null) {
0863: //not a known type
0864: boolean isAntlib = componentName
0865: .indexOf(MagicNames.ANTLIB_PREFIX) == 0;
0866: out.println("Cause: The name is undefined.");
0867: out.println("Action: Check the spelling.");
0868: out
0869: .println("Action: Check that any custom tasks/types have been declared.");
0870: out.println("Action: Check that any <presetdef>/<macrodef>"
0871: + " declarations have taken place.");
0872: if (isAntlib) {
0873: out.println();
0874: out
0875: .println("This appears to be an antlib declaration. ");
0876: out
0877: .println("Action: Check that the implementing library exists in one of:");
0878: out.println(dirListing);
0879: }
0880: definitions = true;
0881: } else {
0882: //we are defined, so it is an instantiation problem
0883: final String classname = def.getClassName();
0884: antTask = classname.startsWith("org.apache.tools.ant.");
0885: boolean optional = classname
0886: .startsWith("org.apache.tools.ant.taskdefs.optional");
0887: optional |= classname
0888: .startsWith("org.apache.tools.ant.types.optional");
0889:
0890: //start with instantiating the class.
0891: Class clazz = null;
0892: try {
0893: clazz = def.innerGetTypeClass();
0894: } catch (ClassNotFoundException e) {
0895: out.println("Cause: the class " + classname
0896: + " was not found.");
0897: jars = true;
0898: if (optional) {
0899: out
0900: .println(" This looks like one of Ant's optional components.");
0901: out
0902: .println("Action: Check that the appropriate optional JAR exists in");
0903: out.println(dirListing);
0904: } else {
0905: out
0906: .println("Action: Check that the component has been correctly declared");
0907: out
0908: .println(" and that the implementing JAR is in one of:");
0909: out.println(dirListing);
0910: definitions = true;
0911: }
0912: } catch (NoClassDefFoundError ncdfe) {
0913: jars = true;
0914: out.println("Cause: Could not load a dependent class "
0915: + ncdfe.getMessage());
0916: if (optional) {
0917: out
0918: .println(" It is not enough to have Ant's optional JARs");
0919: out
0920: .println(" you need the JAR files that the"
0921: + " optional tasks depend upon.");
0922: out
0923: .println(" Ant's optional task dependencies are"
0924: + " listed in the manual.");
0925: } else {
0926: out
0927: .println(" This class may be in a separate JAR"
0928: + " that is not installed.");
0929: }
0930: out
0931: .println("Action: Determine what extra JAR files are"
0932: + " needed, and place them in one of:");
0933: out.println(dirListing);
0934: }
0935: //here we successfully loaded the class or failed.
0936: if (clazz != null) {
0937: //success: proceed with more steps
0938: try {
0939: def.innerCreateAndSet(clazz, project);
0940: //hey, there is nothing wrong with us
0941: out.println("The component could be instantiated.");
0942: } catch (NoSuchMethodException e) {
0943: lowlevel = true;
0944: out.println("Cause: The class " + classname
0945: + " has no compatible constructor.");
0946:
0947: } catch (InstantiationException e) {
0948: lowlevel = true;
0949: out
0950: .println("Cause: The class "
0951: + classname
0952: + " is abstract and cannot be instantiated.");
0953: } catch (IllegalAccessException e) {
0954: lowlevel = true;
0955: out.println("Cause: The constructor for "
0956: + classname
0957: + " is private and cannot be invoked.");
0958: } catch (InvocationTargetException ex) {
0959: lowlevel = true;
0960: Throwable t = ex.getTargetException();
0961: out
0962: .println("Cause: The constructor threw the exception");
0963: out.println(t.toString());
0964: t.printStackTrace(out);
0965: } catch (NoClassDefFoundError ncdfe) {
0966: jars = true;
0967: out.println("Cause: A class needed by class "
0968: + classname + " cannot be found: ");
0969: out.println(" " + ncdfe.getMessage());
0970: out
0971: .println("Action: Determine what extra JAR files are"
0972: + " needed, and place them in:");
0973: out.println(dirListing);
0974: }
0975: }
0976: out.println();
0977: out.println("Do not panic, this is a common problem.");
0978: if (definitions) {
0979: out
0980: .println("It may just be a typographical error in the build file "
0981: + "or the task/type declaration.");
0982: }
0983: if (jars) {
0984: out.println("The commonest cause is a missing JAR.");
0985: }
0986: if (lowlevel) {
0987: out
0988: .println("This is quite a low level problem, which may need "
0989: + "consultation with the author of the task.");
0990: if (antTask) {
0991: out
0992: .println("This may be the Ant team. Please file a "
0993: + "defect or contact the developer team.");
0994: } else {
0995: out
0996: .println("This does not appear to be a task bundled with Ant.");
0997: out
0998: .println("Please take it up with the supplier of the third-party "
0999: + type + ".");
1000: out
1001: .println("If you have written it yourself, you probably have a bug to fix.");
1002: }
1003: } else {
1004: out.println();
1005: out
1006: .println("This is not a bug; it is a configuration problem");
1007: }
1008: }
1009: out.flush();
1010: out.close();
1011: return errorText.toString();
1012: }
1013:
1014: /**
1015: * Map that contains the component definitions.
1016: */
1017: private static class AntTypeTable extends Hashtable {
1018: private Project project;
1019:
1020: AntTypeTable(Project project) {
1021: this .project = project;
1022: }
1023:
1024: AntTypeDefinition getDefinition(String key) {
1025: return (AntTypeDefinition) (super .get(key));
1026: }
1027:
1028: public Object get(Object key) {
1029: return getTypeClass((String) key);
1030: }
1031:
1032: Object create(String name) {
1033: AntTypeDefinition def = getDefinition(name);
1034: return (def == null) ? null : def.create(project);
1035: }
1036:
1037: Class getTypeClass(String name) {
1038: AntTypeDefinition def = getDefinition(name);
1039: return (def == null) ? null : def.getTypeClass(project);
1040: }
1041:
1042: Class getExposedClass(String name) {
1043: AntTypeDefinition def = getDefinition(name);
1044: return (def == null) ? null : def.getExposedClass(project);
1045: }
1046:
1047: public boolean contains(Object clazz) {
1048: boolean found = false;
1049: if (clazz instanceof Class) {
1050: for (Iterator i = values().iterator(); i.hasNext()
1051: && !found;) {
1052: found |= (((AntTypeDefinition) (i.next()))
1053: .getExposedClass(project) == clazz);
1054: }
1055: }
1056: return found;
1057: }
1058:
1059: public boolean containsValue(Object value) {
1060: return contains(value);
1061: }
1062: }
1063:
1064: }
|