From b0003afe366c593c70d5af9b037ae55dee28df1e Mon Sep 17 00:00:00 2001 From: Steve Loughran Date: Tue, 1 Mar 2005 14:55:47 +0000 Subject: [PATCH] complete rework of reporting when we cant instantiate an element old: generic error message new: step by step diagnostics with instructions. The code treats ant tasks and ant optional tasks specially, based on package names. Also: moved some constants into the appropriate places. git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@277750 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/tools/ant/AntTypeDefinition.java | 98 ++++++++--- .../org/apache/tools/ant/ComponentHelper.java | 164 +++++++++++++++++- .../org/apache/tools/ant/Diagnostics.java | 9 +- src/main/org/apache/tools/ant/MagicNames.java | 36 +++- .../org/apache/tools/ant/UnknownElement.java | 41 +---- .../org/apache/tools/ant/launch/Launcher.java | 5 +- 6 files changed, 273 insertions(+), 80 deletions(-) diff --git a/src/main/org/apache/tools/ant/AntTypeDefinition.java b/src/main/org/apache/tools/ant/AntTypeDefinition.java index f56bd9128..3e5778ed6 100644 --- a/src/main/org/apache/tools/ant/AntTypeDefinition.java +++ b/src/main/org/apache/tools/ant/AntTypeDefinition.java @@ -17,6 +17,9 @@ package org.apache.tools.ant; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Constructor; + /** * This class contains all the information @@ -143,15 +146,8 @@ public class AntTypeDefinition { * @return the type of the definition. */ public Class getTypeClass(Project project) { - if (clazz != null) { - return clazz; - } try { - if (classLoader == null) { - clazz = Class.forName(className); - } else { - clazz = classLoader.loadClass(className); - } + return innerGetTypeClass(); } catch (NoClassDefFoundError ncdfe) { project.log("Could not load a dependent class (" + ncdfe.getMessage() + ") for type " @@ -160,6 +156,24 @@ public class AntTypeDefinition { 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 + * @throws NoClassDefFoundError + */ + public Class innerGetTypeClass() throws ClassNotFoundException { + if (clazz != null) { + return clazz; + } + if (classLoader == null) { + clazz = Class.forName(className); + } else { + clazz = classLoader.loadClass(className); + } return clazz; } @@ -238,23 +252,9 @@ public class AntTypeDefinition { */ private Object createAndSet(Project project, Class c) { try { - java.lang.reflect.Constructor ctor = null; - boolean noArg = false; - // DataType can have a "no arg" constructor or take a single - // Project argument. - try { - ctor = c.getConstructor(new Class[0]); - noArg = true; - } catch (NoSuchMethodException nse) { - ctor = c.getConstructor(new Class[] {Project.class}); - noArg = false; - } - Object o = ctor.newInstance( - ((noArg) ? new Object[0] : new Object[] {project})); - - project.setProjectReference(o); + Object o = innerCreateAndSet(c, project); return o; - } catch (java.lang.reflect.InvocationTargetException ex) { + } catch (InvocationTargetException ex) { Throwable t = ex.getTargetException(); throw new BuildException( "Could not create type " + name + " due to " + t, t); @@ -262,12 +262,60 @@ public class AntTypeDefinition { String msg = "Type " + name + ": A class needed by class " + c + " cannot be found: " + ncdfe.getMessage(); throw new BuildException(msg, ncdfe); - } catch (Throwable t) { + } 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 {@see #createAndSet} logic, with no + * exception catching + * @param newclass class to create + * @param project + * @return a newly constructed and bound instance. + * @throws NoSuchMethodException + * @throws InstantiationException + * @throws IllegalAccessException + * @throws InvocationTargetException + */ + 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). * diff --git a/src/main/org/apache/tools/ant/ComponentHelper.java b/src/main/org/apache/tools/ant/ComponentHelper.java index 7786c2574..a5887cb0f 100644 --- a/src/main/org/apache/tools/ant/ComponentHelper.java +++ b/src/main/org/apache/tools/ant/ComponentHelper.java @@ -28,10 +28,15 @@ import java.util.Stack; import java.util.Vector; import java.io.InputStream; import java.io.IOException; +import java.io.File; +import java.io.StringWriter; +import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.lang.reflect.Modifier; +import java.lang.reflect.InvocationTargetException; import org.apache.tools.ant.taskdefs.Typedef; +import org.apache.tools.ant.launch.Launcher; /** * Component creation and configuration. @@ -83,6 +88,9 @@ public class ComponentHelper { private ComponentHelper next; private Project project; + private static final String ERROR_NO_TASK_LIST_LOAD = "Can't load default task list"; + private static final String ERROR_NO_TYPE_LIST_LOAD = "Can't load default type list"; + public static final String COMPONENT_HELPER_REFERENCE = "ant.ComponentHelper"; /** * Find a project component for a specific project, creating @@ -93,14 +101,14 @@ public class ComponentHelper { public static ComponentHelper getComponentHelper(Project project) { // Singleton for now, it may change ( per/classloader ) ComponentHelper ph = (ComponentHelper) project.getReference( - "ant.ComponentHelper"); + COMPONENT_HELPER_REFERENCE); if (ph != null) { return ph; } ph = new ComponentHelper(); ph.setProject(project); - project.addReference("ant.ComponentHelper", ph); + project.addReference(COMPONENT_HELPER_REFERENCE, ph); return ph; } @@ -654,14 +662,14 @@ public class ComponentHelper { && !("only".equals(project.getProperty("build.sysclasspath")))) { classLoader = project.getCoreLoader(); } - String dataDefs = "/org/apache/tools/ant/taskdefs/defaults.properties"; + String dataDefs = MagicNames.TASKDEF_PROPERTIES_RESOURCE; InputStream in = null; try { Properties props = new Properties(); in = this.getClass().getResourceAsStream(dataDefs); if (in == null) { - throw new BuildException("Can't load default task list"); + throw new BuildException(ERROR_NO_TASK_LIST_LOAD); } props.load(in); @@ -678,7 +686,7 @@ public class ComponentHelper { antTypeTable.put(name, def); } } catch (IOException ex) { - throw new BuildException("Can't load default type list"); + throw new BuildException(ERROR_NO_TASK_LIST_LOAD); } finally { if (in != null) { try { @@ -699,14 +707,14 @@ public class ComponentHelper { && !("only".equals(project.getProperty("build.sysclasspath")))) { classLoader = project.getCoreLoader(); } - String dataDefs = "/org/apache/tools/ant/types/defaults.properties"; + String dataDefs = MagicNames.TYPEDEFS_PROPERTIES_RESOURCE; InputStream in = null; try { Properties props = new Properties(); in = this.getClass().getResourceAsStream(dataDefs); if (in == null) { - throw new BuildException("Can't load default datatype list"); + throw new BuildException(ERROR_NO_TYPE_LIST_LOAD); } props.load(in); @@ -721,7 +729,7 @@ public class ComponentHelper { antTypeTable.put(name, def); } } catch (IOException ex) { - throw new BuildException("Can't load default type list"); + throw new BuildException(ERROR_NO_TYPE_LIST_LOAD); } finally { if (in != null) { try { @@ -761,6 +769,146 @@ public class ComponentHelper { definer.execute(); } + /** + * Handler called to do decent diagnosis on instantiation failure + * @param componentName + * @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; + //look up the name + AntTypeDefinition def = getDefinition(componentName); + if(def==null) { + //not a known type + 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"); + 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"); + String home = System.getProperty(Launcher.USER_HOMEDIR); + File libDir = new File(home, + Launcher.ANT_PRIVATEDIR + + File.separator + + Launcher.ANT_PRIVATELIB); + + //start with instantiating the class. + Class clazz= null; + try { + clazz = def.innerGetTypeClass(); + } catch (ClassNotFoundException e) { + out.println("Cause: the class " + + classname + + " was not found"); + jars = true; + if (optional) { + out.println(" This looks like one of Ant's optional components"); + out.println("Action: check that the appropriate optional JAR exists " + + "in ANT_HOME/lib or in "); + out.println(" " + libDir); + } else { + out.println("Action: check that the component has been correctly declared"); + out.println(" And that the implementing JAR is in ANT_HOME/lib or in"); + out.println(" " + libDir); + definitions = true; + } + } catch (NoClassDefFoundError ncdfe) { + jars = true; + out.println("Cause: Could not load a dependent class " + + ncdfe.getMessage()); + if(optional) { + out.println(" It is not enough to have Ant's optional JAR, you need the JAR"); + out.println(" files that it depends 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"); + out.println(" In ANT_HOME/lib or"); + out.println(" in " + libDir ); + } + //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"); + out.println(" In ANT_HOME/lib or"); + out.println(" in " + libDir); + } + } + out.println(); + out.println("Do not panic, this is a common problem."); + if(definitions) { + out.println("It may just be a typing 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("It is not an Ant bug; there is no need to file a bug" + + " report or contact the developers"); + } + + } + out.flush(); + out.close(); + return errorText.toString(); + } + /** * Map that contains the component definitions. */ diff --git a/src/main/org/apache/tools/ant/Diagnostics.java b/src/main/org/apache/tools/ant/Diagnostics.java index f3a345d9d..eb320a933 100644 --- a/src/main/org/apache/tools/ant/Diagnostics.java +++ b/src/main/org/apache/tools/ant/Diagnostics.java @@ -72,7 +72,7 @@ public final class Diagnostics { public static void validateVersion() throws BuildException { try { Class optional - = Class.forName("org.apache.tools.ant.taskdefs.optional.Test"); + = Class.forName(TEST_CLASS); String coreVersion = getImplementationVersion(Main.class); String optionalVersion = getImplementationVersion(optional); @@ -94,7 +94,7 @@ public final class Diagnostics { * null if an error occurs. */ public static File[] listLibraries() { - String home = System.getProperty("ant.home"); + String home = System.getProperty(Launcher.ANTHOME_PROPERTY); if (home == null) { return null; } @@ -211,8 +211,7 @@ public final class Diagnostics { Class optional = null; try { - optional = Class.forName( - "org.apache.tools.ant.taskdefs.optional.Test"); + optional = Class.forName(TEST_CLASS); out.println("optional tasks : " + getImplementationVersion(optional)); } catch (ClassNotFoundException e) { @@ -340,7 +339,7 @@ public final class Diagnostics { */ private static void doReportTasksAvailability(PrintStream out) { InputStream is = Main.class.getResourceAsStream( - "/org/apache/tools/ant/taskdefs/defaults.properties"); + MagicNames.TASKDEF_PROPERTIES_RESOURCE); if (is == null) { out.println("None available"); } else { diff --git a/src/main/org/apache/tools/ant/MagicNames.java b/src/main/org/apache/tools/ant/MagicNames.java index 2c3821926..3d24b81a5 100644 --- a/src/main/org/apache/tools/ant/MagicNames.java +++ b/src/main/org/apache/tools/ant/MagicNames.java @@ -24,11 +24,43 @@ package org.apache.tools.ant; * @since Ant 1.6 */ public class MagicNames { - /** The name of the script repository used by the script repo task */ + /** + * The name of the script repository used by the script repo task + * Value {@value} + */ public static final String SCRIPT_REPOSITORY = "org.apache.ant.scriptrepo"; - /** The name of the reference to the System Class Loader */ + /** + * The name of the reference to the System Class Loader + * Value {@value} + **/ public static final String SYSTEM_LOADER_REF = "ant.coreLoader"; + /** + * Name of the property which can provide an override of the repository dir + * for the libraries task + * Value {@value} + */ + public static final String REPOSITORY_DIR_PROPERTY = "ant.maven.repository.dir"; + /** + * Name of the property which can provide an override of the repository URL + * for the libraries task + * Value {@value} + */ + public static final String REPOSITORY_URL_PROPERTY = "ant.maven.repository.url"; + + /** + * name of the resource that taskdefs are stored under + * Value: {@value} + */ + public static final String TASKDEF_PROPERTIES_RESOURCE = + "/org/apache/tools/ant/taskdefs/defaults.properties"; + /** + * name of the resource that typedefs are stored under + * Value: {@value} + */ + public static final String TYPEDEFS_PROPERTIES_RESOURCE = + "/org/apache/tools/ant/types/defaults.properties"; + } diff --git a/src/main/org/apache/tools/ant/UnknownElement.java b/src/main/org/apache/tools/ant/UnknownElement.java index fd888f42e..ceb42e8ca 100644 --- a/src/main/org/apache/tools/ant/UnknownElement.java +++ b/src/main/org/apache/tools/ant/UnknownElement.java @@ -463,45 +463,8 @@ public class UnknownElement extends Task { */ protected BuildException getNotFoundException(String what, String elementName) { - String lSep = System.getProperty("line.separator"); - String msg = "Could not create " + what + " of type: " + elementName - + "." + lSep + lSep - + "Ant could not find the task or a class this " - + "task relies upon." + lSep + lSep - + "This is common and has a number of causes; the usual " + lSep - + "solutions are to read the manual pages then download and" + lSep - + "install needed JAR files, or fix the build file: " + lSep - + " - You have misspelt '" + elementName + "'." + lSep - + " Fix: check your spelling." + lSep - + " - The task needs an external JAR file to execute" + lSep - + " and this is not found at the right place in the classpath." + lSep - + " Fix: check the documentation for dependencies." + lSep - + " Fix: declare the task." + lSep - + " - The task is an Ant optional task and the JAR file and/or libraries" + lSep - + " implementing the functionality were not found at the time you" + lSep - + " yourself built your installation of Ant from the Ant sources." + lSep - + " Fix: Look in the ANT_HOME/lib for the 'ant-' JAR corresponding to the" + lSep - + " task and make sure it contains more than merely a META-INF/MANIFEST.MF." + lSep - + " If all it contains is the manifest, then rebuild Ant with the needed" + lSep - + " libraries present in ${ant.home}/lib/optional/ , or alternatively," + lSep - + " download a pre-built release version from apache.org" + lSep - + " - The build file was written for a later version of Ant" + lSep - + " Fix: upgrade to at least the latest release version of Ant" + lSep - + " - The task is not an Ant core or optional task " + lSep - + " and needs to be declared using ." + lSep - + " - You are attempting to use a task defined using " + lSep - + " or but have spelt wrong or not " + lSep - + " defined it at the point of use" + lSep - + lSep - + "Remember that for JAR files to be visible to Ant tasks implemented" + lSep - + "in ANT_HOME/lib, the files must be in the same directory or on the" + lSep - + "classpath" + lSep - + lSep - + "Please neither file bug reports on this problem, nor email the" + lSep - + "Ant mailing lists, until all of these causes have been explored," + lSep - + "as this is not an Ant bug."; - - + ComponentHelper helper = ComponentHelper.getComponentHelper(getProject()); + String msg = helper.diagnoseCreationFailure(elementName, what); return new BuildException(msg, getLocation()); } diff --git a/src/main/org/apache/tools/ant/launch/Launcher.java b/src/main/org/apache/tools/ant/launch/Launcher.java index c7977e8b4..8afd0710a 100644 --- a/src/main/org/apache/tools/ant/launch/Launcher.java +++ b/src/main/org/apache/tools/ant/launch/Launcher.java @@ -32,7 +32,10 @@ import java.util.Iterator; * @since Ant 1.6 */ public class Launcher { - /** The Ant Home property */ + /** + * Ant home directory + * Value : {@value} + */ public static final String ANTHOME_PROPERTY = "ant.home"; /** The Ant Library Directory property */