package xdoclet; /* * Copyright (c) 2001, 2002 The XDoclet team * All rights reserved. */ import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.DynamicConfigurator; import org.apache.tools.ant.Project; import org.apache.tools.ant.types.FileSet; import xjavadoc.ant.XJavadocTask; import xdoclet.loader.ModuleFinder; import xdoclet.loader.SubTaskDefinition; import xdoclet.loader.XDocletModule; import xdoclet.util.Translator; /** * A base class for all Tasks. It can also be used directly, useful for the case where you want to execute a template * file but you don't want to bother writing a new task. * * @author Ara Abrahamian (ara_e@email.com) * @author <a href="mailto:aslak.hellesoy@bekk.no">Aslak Helles�y</a> * @created June 19, 2001 * @ant.element name="xdoclet" display-name="XDoclet Standard Task" * @ant.attribute name="encoding" description="Specify the source file encoding name, such as Windows-31J, EUC-JP, * UTF-8. In default, system default encoding is used." * @ant.attribute name="docencoding" description="Specify encoding name for template engine. The generated file * encoding may be this value. In default, system default encoding is used." */ public class DocletTask extends XJavadocTask implements DynamicConfigurator { // ant will replace the tag with the version property specified in build.xml public final static String XDOCLET_VERSION = "@VERSION@"; /** * subtask class -> logical name (java.lang.String) Used to look up names. Lazily created. */ private static Map subtaskNameMap; /** * logical name (java.lang.String) -> subtask (xdoclet.SubTask or a subclass of it). Lazily created. */ private static Map subtaskMap; private List packageSubstitutions = new ArrayList(); private boolean isModulesRegistered = false; private File destDir; private File mergeDir; private String excludedTags = null; private boolean force = false; private boolean verbose = false; private String addedTags; private List subTasks = new ArrayList(); private List configParams = new ArrayList(); public DocletTask() { ModuleFinder.initClasspath(getClass()); } public static String getSubTaskName(Class subTaskClass) { return (String) getSubtaskNameMap().get(subTaskClass); } static Map getConfigParamsAsMap(List configParams) { HashMap map = new HashMap(); for (Iterator i = configParams.iterator(); i.hasNext(); ) { ConfigParameter cp = (ConfigParameter) i.next(); map.put(cp.getName(), cp.getValue()); } return map; } static void registerSubTaskName(SubTask subTask, String name) { getSubtaskNameMap().put(subTask.getClass(), name); } private static Map getSubtaskMap() { if (subtaskMap == null) subtaskMap = new HashMap(); return subtaskMap; } private static Map getSubtaskNameMap() { if (subtaskNameMap == null) subtaskNameMap = new HashMap(); return subtaskNameMap; } /** * Gets the PackageSubstitutions attribute of the EjbDocletTask object * * @return The PackageSubstitutions value */ public List getPackageSubstitutions() { return packageSubstitutions; } /** * Gets the ConfigParams attribute of the DocletTask object * * @return The ConfigParams value */ public List getConfigParams() { return configParams; } public Map getConfigParamsAsMap() { return getConfigParamsAsMap(getConfigParams()); } /** * Gets the MergeDir attribute of the DocletTask object * * @return The MergeDir value */ public File getMergeDir() { return mergeDir; } /** * Gets the ExcludedTags attribute of the DocletTask object * * @return The ExcludedTags value */ public String getExcludedTags() { return excludedTags; } /** * Gets the DestDir attribute of the DocletTask object * * @return The DestDir value */ public File getDestDir() { return destDir; } /** * Gets the Force attribute of the DocletTask object. * * @return The Force value */ public boolean isForce() { return force; } /** * Gets the Verbose attribute of the DocletTask object. * * @return The Verbose value */ public boolean isVerbose() { return verbose; } public String getAddedTags() { return addedTags; } /** * Sets the PackageSubstitutions attribute of the EjbDocletTask object * * @param packageSubstitutions The new PackageSubstitutions value * @ant.ignore */ public void setPackageSubstitutions(List packageSubstitutions) { this.packageSubstitutions = packageSubstitutions; } /** * @param name * @param value */ public void setDynamicAttribute(String name, String value) { throw new BuildException(Translator.getString(XDocletMessages.class, XDocletMessages.ATTRIBUTE_NOT_SUPPORTED, new String[]{getTaskName(), name})); } /** * Sets the PackageNames attribute of the DocletTask object * * @param src The new PackageNames value * @deprecated * @ant.ignore */ public void setPackageNames(String src) { throw new BuildException(Translator.getString(XDocletMessages.class, XDocletMessages.OBSOLETE_TASK_ATTRIBUTE, new String[]{"packageNames"})); } /** * Sets the ExcludePackageNames attribute of the DocletTask object * * @param src The new ExcludePackageNames value * @deprecated * @ant.ignore */ public void setExcludePackageNames(String src) { throw new BuildException(Translator.getString(XDocletMessages.class, XDocletMessages.OBSOLETE_TASK_ATTRIBUTE, new String[]{"excludePackageNames"})); } /** * Specify tags that should not be automatically written to output files. The normal behaviour is to include all @ * tags from the source file to the output files. This may cause trouble if you use cvs-like tag like $Revision: 1.5 * $ that will be overwritten at each build and causes a difference for CVS even if the code himself is not changed. * Example: excludedtags="@ version" For excluded tags, ejbdoclet will generate an hardcoded tag. Example: @ version * XDOCLET 1.0 * * @param tags The new ExcludedTags value * @deprecated */ public void setExcludedTags(String tags) { excludedTags = tags; } /** * Destination directory for output files * * @param dir The new DestDir value * @ant.not-required Only if it's not specified for a subtask. */ public void setDestDir(File dir) { destDir = dir; } /** * Directory where subtasks will look for files to be merged with generated files. * * @param dir The new MergeDir value * @ant.not-required No, but should be set if you want to use the merge feature. */ public void setMergeDir(File dir) { mergeDir = dir; } /** * Specify if the generation of files should be forced. In normal cases, the timestamp of generated file is checked * against the timestamps of the class (and its super classes) we generate from. When this timestamp checking should * be bypassed (for example after the installtion of a new xdoclet version) then the user should force the * regeneration. The easiest way is to run the Ant build file with a parameter "-Dxdoclet.force=true" and add the * option "force=${xdoclet.force}" to the doclet call. * * @param force The new Force value */ public void setForce(boolean force) { this.force = force; } /** * Sets the Verbose attribute of the DocletTask object. * * @param verbose The new Verbose value */ public void setVerbose(boolean verbose) { this.verbose = verbose; } /** * Add some JavaDoc tags (or comments) to the generated classes. A special case @ xdoclet-generated. If this is * included, ejbdoclet will not consider the file if it is by error in the fileset of the ejbdoclet task. * * @param addedTags */ public void setAddedTags(String addedTags) { this.addedTags = addedTags; } /** * Substitutes the package of the generated files. * * @param ps The feature to be added to the Fileset attribute */ public void addPackageSubstitution(xdoclet.tagshandler.PackageTagsHandler.PackageSubstitution ps) { packageSubstitutions.add(ps); } /** * Ant's <fileset> definition. To define the files to parse. * * @param set a fileset to add */ public void addFileset(FileSet set) { // does nothing apart from calling super - it's only here for the // javadocs, for the generated documentation. super.addFileset(set); } /** * @param name * @return * @exception BuildException */ public Object createDynamicElement(String name) throws BuildException { if (!isModulesRegistered) { registerModules(); isModulesRegistered = true; } SubTask subTask = (SubTask) getSubtaskMap().get(name); if (subTask == null) { throw new BuildException(Translator.getString(XDocletMessages.class, XDocletMessages.CREATE_TASK_ERROR, new String[]{name, getTaskName()})); } subTasks.add(subTask); return subTask; } /** * Generic subtask. * * @param subtask The subtask to be added * @ant.ignore */ public void addSubTask(SubTask subtask) { subTasks.add(subtask); } /** * Generic subtask for processing a user-supplied template. * * @param subtask Describe the method parameter * @exception BuildException * @ant.ignore */ public void addTemplate(TemplateSubTask subtask) throws BuildException { if (subtask.getSubTaskClassName() == null) { addSubTask(subtask); } else { try { Class subtaskClass = Class.forName(subtask.getSubTaskClassName()); TemplateSubTask alias = (TemplateSubTask) subtaskClass.newInstance(); // now copy from subtask to real alias alias.copyAttributesFrom(subtask); addSubTask(alias); } catch (ClassNotFoundException e) { throw new BuildException(Translator.getString(XDocletMessages.class, XDocletMessages.CLASS_NOT_FOUND_EXCEPTION, new String[]{subtask.getSubTaskClassName(), e.getMessage()}), e, location); } catch (InstantiationException e) { throw new BuildException(Translator.getString(XDocletMessages.class, XDocletMessages.INSTANTIATION_EXCEPTION, new String[]{subtask.getSubTaskClassName(), e.getMessage()}), e, location); } catch (IllegalAccessException e) { throw new BuildException(Translator.getString(XDocletMessages.class, XDocletMessages.ILLEGAL_ACCESS_EXCEPTION, new String[]{subtask.getSubTaskClassName(), e.getMessage()}), e, location); } } } /** * Generic subtask for processing a user-supplied template, to generate an XML document. * * @param subtask Describe the method parameter * @ant.ignore */ public void addXmlTemplate(XmlSubTask subtask) { addTemplate(subtask); } /** * Allows to set configuration parameters that will be included in the element as attribute value pair. * * @param configParam Describe the method parameter */ public void addConfigParam(ConfigParameter configParam) { configParams.add(configParam); } /** * Gets the SubTasks attribute of the DocletTask object * * @return The SubTasks value */ protected final List getSubTasks() { return subTasks; } /** * Gets the ConfigParams attribute of the DocletTask object * * @param subtasks Describe what the parameter does * @return The ConfigParams value */ protected HashMap getConfigParams(List subtasks) { HashMap configs = new HashMap(); // config params of task ConfigParamIntrospector.fillConfigParamsFor(this, configs); // config params of substask for (int i = 0; i < subtasks.size(); i++) { SubTask subtask = (SubTask) subtasks.get(i); if (subtask != null) { ConfigParamIntrospector.fillConfigParamsFor(subtask, configs); // user defined params of SubTask fillWithUserDefinedConfigParams(configs, subtask.getConfigParams(), subtask.getSubTaskName() + '.'); } } // user defined params of DocletTask fillWithUserDefinedConfigParams(configs, getConfigParams(), ""); return configs; } /** * @exception BuildException */ protected void start() throws BuildException { try { new XDocletMain().start(getXJavaDoc()); } catch (XDocletException e) { throw new BuildException(Translator.getString(XDocletMessages.class, XDocletMessages.XDOCLET_FAILED), e, location); } finally { DocletContext.setSingleInstance(null); // Fix for http://opensource.atlassian.com/projects/xdoclet/browse/XDT-879 subtaskNameMap = null; subtaskMap = null; //destDir = null; //mergeDir = null; //subTasks = null; //configParams = null; ModuleFinder.resetFoundModules(); } } /** * Called by superclass before start() is called * * @exception BuildException Describe the exception */ protected void validateOptions() throws BuildException { super.validateOptions(); if (destDir == null) { throw new BuildException(Translator.getString(XDocletMessages.class, XDocletMessages.ATTRIBUTE_NOT_PRESENT_ERROR, new String[]{"destDir"}), location); } validateSubTasks(); } /** * Throws BuildException if a specific class is not on the CP. Should be called from subclasses' validateOptions() * to verify that classpath is OK. * * @param className */ protected void checkClass(String className) { try { Class.forName(className); } catch (Exception e) { throw new BuildException(Translator.getString(XDocletMessages.class, XDocletMessages.CHECK_CLASS_FAILED, new String[]{className, getTaskName()})); } } /** * Describe what the method does * * @exception BuildException Describe the exception */ protected void validateSubTasks() throws BuildException { DocletContext context = createContext(); SubTask[] subtasks = context.getSubTasks(); for (int i = 0; i < subtasks.length; i++) { SubTask subtask = subtasks[i]; if (subtask != null) { log("validating subTask: " + subtask.getSubTaskName() + " class: " + subtask.getClass(), Project.MSG_DEBUG); try { subtask.validateOptions(); } catch (XDocletException ex) { throw new BuildException(subtask.getSubTaskName() + ": " + ex.getMessage(), location); } } } } private void registerModules() { // Register subtasks that apply to us (they do if they in xdoclet.xml have declared us as parent) List modules = ModuleFinder.findModules(); Iterator i = modules.iterator(); while (i.hasNext()) { XDocletModule module = (XDocletModule) i.next(); List subTaskDefinitions = module.getSubTaskDefinitions(); Iterator j = subTaskDefinitions.iterator(); while (j.hasNext()) { SubTaskDefinition subTaskDefinition = (SubTaskDefinition) j.next(); try { Class parentTaskClass = Class.forName(subTaskDefinition.parentTaskClass); if (parentTaskClass.isAssignableFrom(getClass())) { if (getSubtaskMap().containsKey(subTaskDefinition.name)) { String conflictingSubTaskClassName = getSubtaskMap().get(subTaskDefinition.name).getClass().getName(); if (!subTaskDefinition.implementationClass.equals(conflictingSubTaskClassName)) { // duplicate subtask definition, and it's not the same classname (which occurs // if a module is twice or more on classpath - which is OK) throw new BuildException(Translator.getString(XDocletMessages.class, XDocletMessages.AMBIGUOUS_SUBTASK_DEFINITION, new String[]{subTaskDefinition.name, conflictingSubTaskClassName, subTaskDefinition.implementationClass})); } //make sure a new subtask is not created when we can use an already created one. This happens //when you run the task several times. continue; } Class subTaskClass = Class.forName(subTaskDefinition.implementationClass); SubTask subTask = (SubTask) subTaskClass.newInstance(); log("Registering SubTask " + subTaskDefinition.name + " (" + subTaskDefinition.implementationClass + ") to DocletTask " + getClass().getName(), Project.MSG_DEBUG); getSubtaskMap().put(subTaskDefinition.name, subTask); registerSubTaskName(subTask, subTaskDefinition.name); } } catch (ClassNotFoundException e) { throw new BuildException(Translator.getString(XDocletMessages.class, XDocletMessages.DEPENDENT_CLASS_FOR_SUBTASK_NOT_FOUND, new String[]{subTaskDefinition.parentTaskClass, ModuleFinder.getClasspath()}), e); } catch (InstantiationException e) { throw new BuildException(Translator.getString(XDocletMessages.class, XDocletMessages.INSTANTIATION_EXCEPTION, new String[]{subTaskDefinition.implementationClass, e.getMessage()}), e); } catch (IllegalAccessException e) { throw new BuildException(Translator.getString(XDocletMessages.class, XDocletMessages.ILLEGAL_ACCESS_EXCEPTION, new String[]{subTaskDefinition.implementationClass, e.getMessage()}), e); } catch (ClassCastException e) { throw new BuildException(Translator.getString(XDocletMessages.class, XDocletMessages.CLASS_CAST_EXCEPTION, new String[]{subTaskDefinition.implementationClass, SubTask.class.getName()}), e); } } } } /** * Returns the singleton context object and creates it if not already created and registers it as the single * instance. * * @return the singleton context object */ private DocletContext createContext() { if (DocletContext.getInstance() != null) { return DocletContext.getInstance(); } List subtasks = getSubTasks(); HashMap configs = getConfigParams(subtasks); DocletContext context = new DocletContext( this.destDir.toString(), this.mergeDir != null ? this.mergeDir.toString() : null, this.excludedTags, (SubTask[]) subtasks.toArray(new SubTask[0]), project.getProperties(), configs, force, verbose, addedTags ); // now register this single instance DocletContext.setSingleInstance(context); return context; } /** * Describe what the method does * * @param configs Describe what the parameter does * @param configParams Describe what the parameter does * @param prefix Describe what the parameter does */ private void fillWithUserDefinedConfigParams(HashMap configs, List configParams, String prefix) { // config params declared with <configParam name="nnn" value="val"/> for (int i = 0; i < configParams.size(); i++) { ConfigParameter configParam = (ConfigParameter) configParams.get(i); configs.put((prefix + configParam.getName()).toLowerCase(), configParam.getValue()); } } }