On Fri, 27 Jul 2001, Conor MacNeill wrote:

> This is something that is on my todo list so it would be good if you run
> with it. I would, however, like to see it integrated with the current <jar>
> task and also the code from the <depend> task. No sense in duplicating that
> sort of stuff. If your code is better, of course, we can change <depend> to
> use it instead. What I would really like to see is a jar service and
> possibly a dependency service that these tasks layer on top of. That way, I
> can re-jig ejbjar to use the jar service and automatically have the
> dependency gathering facility. I really want this to have the capability to
> automatically ad support classes into an EJB jar.

Ok, I've just added this capability to the GenericDeploymentTool. The
implementation uses
org.apache.tools.ant.taskdefs.optional.depend.ClassFile to determine the
class dependencies. I had to add another method getClassDependencies to
ClassFile, because getClassRefs actually misses some referenced classes.


I've attached both modified Sources ..


Holger Engels
/*
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000 The Apache Software Foundation.  All rights 
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:  
 *       "This product includes software developed by the 
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Ant", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written 
 *    permission, please contact [EMAIL PROTECTED]
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */
package org.apache.tools.ant.taskdefs.optional.depend;

import java.io.*;
import java.lang.reflect.Modifier;
import java.util.*;
import org.apache.tools.ant.taskdefs.optional.depend.constantpool.*;

/**
 * A ClassFile object stores information about a Java class.
 * 
 * The class may be read from a DataInputStream.and written
 * to a DataOutputStream. These are usually streams from a Java
 * class file or a class file component of a Jar file.
 * 
 * @author Conor MacNeill
 */
public class ClassFile {

    /**
     * The Magic Value that marks the start of a Java class file
     */
    static private final int CLASS_MAGIC = 0xCAFEBABE;


    /**
     * This class' constant pool.
     */
    private ConstantPool constantPool;


    /**
     * The class name for this class.
     */
    private String className;

    /**
     * Read the class from a data stream.
     * 
     * This method takes an InputStream as input and
     * parses the class from the stream.
     * <p>
     * 
     * @param stream an InputStream from which the class will be read
     * 
     * @throws IOException if there is a problem reading from the given stream.
     * @throws ClassFormatError if the class cannot be parsed correctly
     * 
     */
    public void read(InputStream stream) throws IOException, ClassFormatError {
        DataInputStream classStream = new DataInputStream(stream);


        if (classStream.readInt() != CLASS_MAGIC) {
            throw new ClassFormatError("No Magic Code Found - probably not a 
Java class file.");
        } 

        // right we have a good looking class file.
        int minorVersion = classStream.readUnsignedShort();
        int majorVersion = classStream.readUnsignedShort();

        // read the constant pool in and resolve it
        constantPool = new ConstantPool();

        constantPool.read(classStream);
        constantPool.resolve();

        int accessFlags = classStream.readUnsignedShort();
        int thisClassIndex = classStream.readUnsignedShort();
        int superClassIndex = classStream.readUnsignedShort();
        className = ((ClassCPInfo) 
constantPool.getEntry(thisClassIndex)).getClassName();
    } 


    /**
     * Get the classes which this class references.
     */
    public Vector getClassRefs() {

        Vector classRefs = new Vector();

        for (int i = 0; i < constantPool.size(); ++i) {
            ConstantPoolEntry entry = constantPool.getEntry(i);

            if (entry != null && entry.getTag() == 
ConstantPoolEntry.CONSTANT_Class) {
                ClassCPInfo classEntry = (ClassCPInfo) entry;

                if (!classEntry.getClassName().equals(className)) {
                    
classRefs.addElement(ClassFileUtils.convertSlashName(classEntry.getClassName()));
                } 
            } 
        } 

        return classRefs;
    } 

    public Vector getClassDependencies() {

        Vector classRefs = new Vector();

        for (int i = 0; i < constantPool.size(); ++i) {
            ConstantPoolEntry entry = constantPool.getEntry(i);

            if (entry != null) {
                if (entry.getTag() == ConstantPoolEntry.CONSTANT_Class) {
                    ClassCPInfo classEntry = (ClassCPInfo) entry;

                    if (!classEntry.getClassName().equals(className)) {
                        
classRefs.addElement(ClassFileUtils.convertSlashName(classEntry.getClassName()));
                    } 
                }
                else if (entry.getTag() == 
ConstantPoolEntry.CONSTANT_NameAndType) {
                    NameAndTypeCPInfo nameEntry = (NameAndTypeCPInfo)entry;
                    
                    String[] types = extractTypes(nameEntry.getType());
                    for (int t=0; t < types.length; t++) {
                        if (!types[t].equals(className)) {
                            
classRefs.addElement(ClassFileUtils.convertSlashName(types[t]));
                        }
                    }
                }
            }
        }

        return classRefs;
    }

    private String[] extractTypes(String string) {
        List list = new LinkedList();
        int start = 0;
        int end = 0;
        String type;

        while (true) {
            start = string.indexOf('L', end+1);
            if (start == -1)
                break;

            end = string.indexOf(';', start+1);

            type = string.substring(start+1, end);
            list.add(type);
        }
        return (String[])list.toArray(new String[list.size()]);
    }

    /**
     * Get the class' fully qualified name in dot format.
     * 
     * @return the class name in dot format (eg. java.lang.Object)
     */
    public String getFullClassName() {
        return ClassFileUtils.convertSlashName(className);
    } 
}

/*
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Ant", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact [EMAIL PROTECTED]
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */
package org.apache.tools.ant.taskdefs.optional.ejb;

import java.io.*;
import java.util.*;
import java.util.jar.*;
import java.util.zip.*;
import java.net.*;
import java.lang.reflect.*;

import javax.xml.parsers.SAXParser;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import org.apache.tools.ant.*;
import org.apache.tools.ant.types.*;
import org.apache.tools.ant.taskdefs.optional.depend.*;

/**
 * A deployment tool which creates generic EJB jars. Generic jars contains
 * only those classes and META-INF entries specified in the EJB 1.1 standard
 *
 * This class is also used as a framework for the creation of vendor specific
 * deployment tools. A number of template methods are provided through which the
 * vendor specific tool can hook into the EJB creation process.
 */
public class GenericDeploymentTool implements EJBDeploymentTool {
    /** Private constants that are used when constructing the standard jarfile 
*/
    protected static final String META_DIR  = "META-INF/";
    protected static final String EJB_DD    = "ejb-jar.xml";

    /**
     * The configuration from the containing task. This config combined with 
the 
     * settings of the individual attributes here constitues the complete 
config for
     * this deployment tool.
     */
    private EjbJar.Config config;

    /** Stores a handle to the directory to put the Jar files in */
    private File destDir;
    
    /** The classpath to use with this deployment tool. This is appended to 
        any paths from the ejbjar task itself.*/
    private Path classpath;

    /** Instance variable that stores the suffix for the generated jarfile. */
    private String genericJarSuffix = "-generic.jar";

    /**
     * The task to which this tool belongs. This is used to access services 
provided
     * by the ant core, such as logging.
     */
    private Task task;
    
    /**
     * The classloader generated from the given classpath to load
     * the super classes and super interfaces.
     */
    private ClassLoader classpathLoader = null;
    
     /**
     * List of files have been loaded into the EJB jar
     */
    private List addedfiles;

    /**
     * Handler used to parse the EJB XML descriptor
     */
    private DescriptorHandler handler;

    /**
     * Setter used to store the value of destination directory prior to 
execute()
     * being called.
     * @param inDir the destination directory.
     */
    public void setDestdir(File inDir) {
        this.destDir = inDir;
    }

    /**
     * Get the desitination directory.
     */
    protected File getDestDir() {
        return destDir;
    }
    

    /**
     * Set the task which owns this tool
     */
    public void setTask(Task task) {
        this.task = task;
    }
       
    /**
     * Get the task for this tool.
     */
    protected Task getTask() {
        return task;
    }

    /**
     * Get the basename terminator.
     */
    protected EjbJar.Config getConfig() {
        return config;
    }
    
    /**
     * Returns true, if the meta-inf dir is being explicitly set, false 
otherwise.
     */
    protected boolean usingBaseJarName() {
        return config.baseJarName != null;
    }
    
    /**
     * Setter used to store the suffix for the generated jar file.
     * @param inString the string to use as the suffix.
     */
    public void setGenericJarSuffix(String inString) {
        this.genericJarSuffix = inString;
    }

    /**
     * Add the classpath for the user classes
     */
    public Path createClasspath() {
        if (classpath == null) {
            classpath = new Path(task.getProject());
        }
        return classpath.createPath();
    }

    /**
     * Set the classpath to be used for this compilation.
     */
    public void setClasspath(Path classpath) {
        this.classpath = classpath;
    }

    /**
     * Get the classpath by combining the one from the surrounding task, if any
     * and the one from tis tool.
     */
    protected Path getCombinedClasspath() {
        Path combinedPath = classpath;
        if (config.classpath != null) {
            if (combinedPath == null) {
                combinedPath = config.classpath;
            }
            else {
                combinedPath.append(config.classpath);
            }
        }
        
        return combinedPath;
    }
    
    protected void log(String message, int level) {
        getTask().log(message, level);
    }

    protected Location getLocation() {
        return getTask().getLocation();
    }


    /**
     * Configure this tool for use in the ejbjar task.
     */
    public void configure(EjbJar.Config config) {
        this.config = config;
        
        classpathLoader = null;
    }

    /**
     * Utility method that encapsulates the logic of adding a file entry to
     * a .jar file.  Used by execute() to add entries to the jar file as it is
     * constructed.
     * @param jStream A JarOutputStream into which to write the
     *        jar entry.
     * @param inputFile A File from which to read the
     *        contents the file being added.
     * @param logicalFilename A String representing the name, including
     *        all relevant path information, that should be stored for the entry
     *        being added.
     */
    protected void addFileToJar(JarOutputStream jStream,
                                File inputFile,
                                String logicalFilename)
        throws BuildException {
        FileInputStream iStream = null;
        try {
            if (!addedfiles.contains(logicalFilename)) {
                iStream = new FileInputStream(inputFile);
                // Create the zip entry and add it to the jar file
                ZipEntry zipEntry = new 
ZipEntry(logicalFilename.replace('\\','/'));
                jStream.putNextEntry(zipEntry);
                   
                // Create the file input stream, and buffer everything over
                // to the jar output stream
                byte[] byteBuffer = new byte[2 * 1024];
                int count = 0;
                do {
                    jStream.write(byteBuffer, 0, count);
                    count = iStream.read(byteBuffer, 0, byteBuffer.length);
                } while (count != -1);
                
                //add it to list of files in jar
                addedfiles.add(logicalFilename);
           }       
        }
        catch (IOException ioe) {
            String msg = "IOException while adding entry "
                         + logicalFilename + " to jarfile from " + 
inputFile.getPath() + "."
                         + ioe.getMessage();
            throw new BuildException(msg, ioe);
        }
        finally {
            // Close up the file input stream for the class file
            if (iStream != null) {
                try {
                    iStream.close();
                }
                catch (IOException closeException) {}
            }
        }
    }

    protected DescriptorHandler getDescriptorHandler(File srcDir) { 
        DescriptorHandler handler = new DescriptorHandler(getTask(), srcDir); 
        
        registerKnownDTDs(handler);
        
        // register any DTDs supplied by the user
        for (Iterator i = getConfig().dtdLocations.iterator(); i.hasNext();) { 
            EjbJar.DTDLocation dtdLocation = (EjbJar.DTDLocation)i.next(); 
            handler.registerDTD(dtdLocation.getPublicId(), 
dtdLocation.getLocation()); 
        } 
        return handler; 
    } 
    
    /**
     * Register the locations of all known DTDs.
     *
     * vendor-specific subclasses should override this method to define
     * the vendor-specific locations of the EJB DTDs
     */
    protected void registerKnownDTDs(DescriptorHandler handler) {
        // none to register for generic
    }

    public void processDescriptor(String descriptorFileName, SAXParser 
saxParser) {

        checkConfiguration(descriptorFileName, saxParser);
                    
        try {
            handler = getDescriptorHandler(config.srcDir);

            // Retrive the files to be added to JAR from EJB descriptor
            Hashtable ejbFiles = parseEjbFiles(descriptorFileName, saxParser);

            // Add any support classes specified in the build file
            addSupportClasses(ejbFiles);

            // Determine the JAR filename (without filename extension)
            String baseName = getJarBaseName(descriptorFileName);

            String ddPrefix = getVendorDDPrefix(baseName, descriptorFileName);

            // First the regular deployment descriptor
            ejbFiles.put(META_DIR + EJB_DD,
                         new File(config.descriptorDir, descriptorFileName));
            
            // now the vendor specific files, if any             
            addVendorFiles(ejbFiles, ddPrefix);

            // add any inherited files
            checkAndAddDependants(ejbFiles);

            // Lastly create File object for the Jar files. If we are using
            // a flat destination dir, then we need to redefine baseName!
            if (config.flatDestDir && baseName.length() != 0) {
                int startName = baseName.lastIndexOf(File.separator);
                if (startName == -1) {
                    startName = 0;
                }
                
                int endName   = baseName.length();
                baseName = baseName.substring(startName, endName);
            }
            
            File jarFile = getVendorOutputJarFile(baseName);
            
            
            // Check to see if we need a build and start doing the work!
            if (needToRebuild(ejbFiles, jarFile)) {
                // Log that we are going to build...
                log( "building "
                              + jarFile.getName()
                              + " with "
                              + String.valueOf(ejbFiles.size())
                              + " files",
                              Project.MSG_INFO);
    
                // Use helper method to write the jarfile
                String publicId = getPublicId();
                writeJar(baseName, jarFile, ejbFiles, publicId);

            }
            else {
                // Log that the file is up to date...
                log(jarFile.toString() + " is up to date.",
                              Project.MSG_VERBOSE);
            }

        }
        catch (SAXException se) {
            String msg = "SAXException while parsing '"
                + descriptorFileName.toString()
                + "'. This probably indicates badly-formed XML."
                + "  Details: "
                + se.getMessage();
            throw new BuildException(msg, se);
        }
        catch (IOException ioe) {
            String msg = "IOException while parsing'"
                + descriptorFileName.toString()
                + "'.  This probably indicates that the descriptor"
                + " doesn't exist. Details: "
                + ioe.getMessage();
            throw new BuildException(msg, ioe);
        }
    }
    
    /**
     * This method is called as the first step in the processDescriptor method
     * to allow vendor-specific subclasses to validate the task configuration
     * prior to processing the descriptor.  If the configuration is invalid,
     * a BuildException should be thrown.
     *
     * @param descriptorFileName String representing the file name of an EJB
     *                           descriptor to be processed
     * @param saxParser          SAXParser which may be used to parse the XML
     *                           descriptor
     * @thows BuildException     Thrown if the configuration is invalid
     */
    protected void checkConfiguration(String descriptorFileName, 
                                    SAXParser saxParser) throws BuildException {

        /* 
         * For the GenericDeploymentTool, do nothing.  Vendor specific 
         * subclasses should throw a BuildException if the configuration is 
         * invalid for their server.
         */
    }

    /**
     * This method returns a list of EJB files found when the specified EJB
     * descriptor is parsed and processed.
     *
     * @param descriptorFileName String representing the file name of an EJB
     *                           descriptor to be processed
     * @param saxParser          SAXParser which may be used to parse the XML
     *                           descriptor
     * @return                   Hashtable of EJB class (and other) files to be
     *                           added to the completed JAR file
     * @throws SAXException      Any SAX exception, possibly wrapping another 
     *                           exception
     * @throws IOException       An IOException from the parser, possibly from a
     *                           the byte stream or character stream 
     */
    protected Hashtable parseEjbFiles(String descriptorFileName, SAXParser 
saxParser)
                            throws IOException, SAXException {
        FileInputStream descriptorStream = null;
        Hashtable ejbFiles = null;

        try {

            /* Parse the ejb deployment descriptor.  While it may not
             * look like much, we use a SAXParser and an inner class to
             * get hold of all the classfile names for the descriptor.
             */
            descriptorStream = new FileInputStream(new 
File(config.descriptorDir, descriptorFileName));
            saxParser.parse(new InputSource(descriptorStream), handler);
                            
            ejbFiles = handler.getFiles();

        } finally {
            if (descriptorStream != null) {
                try {
                    descriptorStream.close();
                }
                catch (IOException closeException) {}
            }
        }

        return ejbFiles;
    }

    /**
     * Adds any classes the user specifies using <i>support</i> nested elements
     * to the <code>ejbFiles</code> Hashtable.
     *
     * @param ejbFiles Hashtable of EJB classes (and other) files that will be
     *                 added to the completed JAR file
     */
    protected void addSupportClasses(Hashtable ejbFiles) {
        // add in support classes if any
        Project project = task.getProject();
        for (Iterator i = config.supportFileSets.iterator(); i.hasNext();) {
            FileSet supportFileSet = (FileSet)i.next();
            File supportBaseDir = supportFileSet.getDir(project);
            DirectoryScanner supportScanner = 
supportFileSet.getDirectoryScanner(project);
            supportScanner.scan();
            String[] supportFiles = supportScanner.getIncludedFiles();
            for (int j = 0; j < supportFiles.length; ++j) {
                ejbFiles.put(supportFiles[j], new File(supportBaseDir, 
supportFiles[j]));
            }
        }            
    }


    /**
     * Using the EJB descriptor file name passed from the <code>ejbjar</code>
     * task, this method returns the "basename" which will be used to name the
     * completed JAR file.
     *
     * @param descriptorFileName String representing the file name of an EJB
     *                           descriptor to be processed
     * @return                   The "basename" which will be used to name the
     *                           completed JAR file
     */
    protected String getJarBaseName(String descriptorFileName) {

        String baseName = "";

        // Work out what the base name is
        if 
(config.namingScheme.getValue().equals(EjbJar.NamingScheme.BASEJARNAME)) {
            String canonicalDescriptor = descriptorFileName.replace('\\', '/');
            int index = canonicalDescriptor.lastIndexOf('/');
            if (index != -1) {
                baseName = descriptorFileName.substring(0, index + 1);
            }
            baseName += config.baseJarName;
        } else if 
(config.namingScheme.getValue().equals(EjbJar.NamingScheme.DESCRIPTOR)) {
            int lastSeparatorIndex = 
descriptorFileName.lastIndexOf(File.separator);
            int endBaseName = -1;
            if (lastSeparatorIndex != -1) {
                endBaseName = 
descriptorFileName.indexOf(config.baseNameTerminator, 
                                                            lastSeparatorIndex);
            } else {
                endBaseName = 
descriptorFileName.indexOf(config.baseNameTerminator);
            }

            if (endBaseName != -1) {
                baseName = descriptorFileName.substring(0, endBaseName);
            }
            baseName = descriptorFileName.substring(0, endBaseName);
        } else if 
(config.namingScheme.getValue().equals(EjbJar.NamingScheme.DIRECTORY)) {
            int lastSeparatorIndex = 
descriptorFileName.lastIndexOf(File.separator);
            String dirName = descriptorFileName.substring(0, 
lastSeparatorIndex);
            int dirSeparatorIndex = dirName.lastIndexOf(File.separator);
            if (dirSeparatorIndex != -1) {
                dirName = dirName.substring(dirSeparatorIndex + 1);
            }
            
            baseName = dirName;
        } else if 
(config.namingScheme.getValue().equals(EjbJar.NamingScheme.EJB_NAME)) {
            baseName = handler.getEjbName();
        }
        return baseName;
    }

    /**
     * Get the prefix for vendor deployment descriptors.
     *
     * This will contain the path and the start of the descriptor name, 
     * depending on the naming scheme
     */
    public String getVendorDDPrefix(String baseName, String descriptorFileName) 
{
        String ddPrefix = null;

        if 
(config.namingScheme.getValue().equals(EjbJar.NamingScheme.DESCRIPTOR)) {
            ddPrefix = baseName + config.baseNameTerminator;
        } else if 
(config.namingScheme.getValue().equals(EjbJar.NamingScheme.BASEJARNAME) ||
                   
config.namingScheme.getValue().equals(EjbJar.NamingScheme.EJB_NAME) ||
                   
config.namingScheme.getValue().equals(EjbJar.NamingScheme.DIRECTORY)) {
            String canonicalDescriptor = descriptorFileName.replace('\\', '/');
            int index = canonicalDescriptor.lastIndexOf('/');
            if (index == -1) {
                ddPrefix = "";
            }
            else {
                ddPrefix = descriptorFileName.substring(0, index + 1);
            }
        }
        return ddPrefix;
    }

    /**
     * Add any vendor specific files which should be included in the 
     * EJB Jar.
     */
    protected void addVendorFiles(Hashtable ejbFiles, String ddPrefix) {
        // nothing to add for generic tool.
    }


    /**
     * Get the vendor specific name of the Jar that will be output. The 
modification date
     * of this jar will be checked against the dependent bean classes.
     */
    File getVendorOutputJarFile(String baseName) {
        return new File(destDir, baseName + genericJarSuffix);
    }

    /**
     * This method checks the timestamp on each file listed in the <code>
     * ejbFiles</code> and compares them to the timestamp on the <code>jarFile
     * </code>.  If the <code>jarFile</code>'s timestamp is more recent than
     * each EJB file, <code>true</code> is returned.  Otherwise, <code>false
     * </code> is returned.
     *
     * @param ejbFiles Hashtable of EJB classes (and other) files that will be
     *                 added to the completed JAR file
     * @param jarFile  JAR file which will contain all of the EJB classes (and
     *                 other) files
     * @return         boolean indicating whether or not the 
<code>jarFile</code>
     *                 is up to date
     */
    protected boolean needToRebuild(Hashtable ejbFiles, File jarFile) {
        if (jarFile.exists()) {
            long lastBuild = jarFile.lastModified();
            if (config.manifest != null && config.manifest.exists() &&
                config.manifest.lastModified() > lastBuild) {
                log("Build needed because manifest " + config.manifest + " is 
out of date",
                    Project.MSG_VERBOSE);
                return true;
            }
                            
            
            Iterator fileIter = ejbFiles.values().iterator();

            // Loop through the files seeing if any has been touched
            // more recently than the destination jar.
            while(fileIter.hasNext()) {
                File currentFile = (File) fileIter.next();
                if (lastBuild < currentFile.lastModified()) {
                    log("Build needed because " + currentFile.getPath() + " is 
out of date",
                        Project.MSG_VERBOSE);
                    return true;                        
                }
            }
            return false;
        }
        
        return true;
    }

    /**
     * Returns the Public ID of the DTD specified in the EJB descriptor.  Not
     * every vendor-specific <code>DeploymentTool</code> will need to reference
     * this value or may want to determine this value in a vendor-specific way.
     *
     * @return         Public ID of the DTD specified in the EJB descriptor.
     */
    protected String getPublicId() {
        return handler.getPublicId();
    }

    /**
     * Method used to encapsulate the writing of the JAR file. Iterates over the
     * filenames/java.io.Files in the Hashtable stored on the instance variable
     * ejbFiles.
     */
    protected void writeJar(String baseName, File jarfile, Hashtable files, 
                            String publicId) throws BuildException{

        JarOutputStream jarStream = null;
        try {
            // clean the addedfiles Vector 
            addedfiles = new ArrayList();

            /* If the jarfile already exists then whack it and recreate it.
             * Should probably think of a more elegant way to handle this
             * so that in case of errors we don't leave people worse off
             * than when we started =)
             */
            if (jarfile.exists()) {
                jarfile.delete();
            }
            jarfile.getParentFile().mkdirs();
            jarfile.createNewFile();
            
            InputStream in = null;
            Manifest manifest = null;
            try {
                if (config.manifest != null) {
                    in = new FileInputStream(config.manifest);
                    if ( in == null ) {
                        throw new BuildException("Could not find manifest file: 
" + config.manifest, 
                                                  getLocation());
                    }
                }
                else {
                    String defaultManifest = 
"/org/apache/tools/ant/defaultManifest.mf";
                    in = this.getClass().getResourceAsStream(defaultManifest);
                    if ( in == null ) {
                        throw new BuildException("Could not find default 
manifest: " + defaultManifest,
                                                  getLocation());
                    }
                }
                            
                manifest = new Manifest(in);
            }
            catch (IOException e) {
                throw new BuildException ("Unable to read manifest", e, 
getLocation());
            }
            finally {
                if (in != null) {
                    in.close();
                }
            }
            
            // Create the streams necessary to write the jarfile
            
            jarStream = new JarOutputStream(new FileOutputStream(jarfile), 
manifest);
            jarStream.setMethod(JarOutputStream.DEFLATED);
            
            // Loop through all the class files found and add them to the jar
            for (Iterator entryIterator = files.keySet().iterator(); 
entryIterator.hasNext(); ) {
                String entryName = (String) entryIterator.next();
                File entryFile = (File) files.get(entryName);
                
                log("adding file '" + entryName + "'",
                              Project.MSG_VERBOSE);

                addFileToJar(jarStream, entryFile, entryName);

                // See if there are any inner classes for this class and add 
them in if there are
                InnerClassFilenameFilter flt = new 
InnerClassFilenameFilter(entryFile.getName());
                File entryDir = entryFile.getParentFile();
                String[] innerfiles = entryDir.list(flt);
                for (int i=0, n=innerfiles.length; i < n; i++) {
            
                    //get and clean up innerclass name
                    entryName = entryName.substring(0, 
entryName.lastIndexOf(entryFile.getName())-1) + File.separatorChar + 
innerfiles[i];
        
                    // link the file
                    entryFile = new File(config.srcDir, entryName);
        
                    log("adding innerclass file '" + entryName + "'", 
                            Project.MSG_VERBOSE);
        
                    addFileToJar(jarStream, entryFile, entryName);
        
                }
            }
        }
        catch(IOException ioe) {
            String msg = "IOException while processing ejb-jar file '"
                + jarfile.toString()
                + "'. Details: "
                + ioe.getMessage();
            throw new BuildException(msg, ioe);
        }
        finally {
            if (jarStream != null) {
                try {
                    jarStream.close();
                }
                catch (IOException closeException) {}
            }
        }
    } // end of writeJar

    /**
     * Uses the depend-task's ClassFile to read class dependencies from all 
class files
     * of <code>checkEntries</code>
     */
    protected void checkAndAddDependants(Hashtable checkEntries)
        throws BuildException
    {
        Hashtable candidates = (Hashtable)checkEntries.clone();

        while (candidates.size() > 0) {
            Hashtable newEntries = new Hashtable();

            Iterator iterator = candidates.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry entry = (Map.Entry)iterator.next();
                String entryName = (String)entry.getKey();
                File entryFile = (File)entry.getValue();

                // only want class files .. xml does not really comply to the 
java class format ;-)
                if (entryName.endsWith(".class")) {
                    File checkFile = new File(config.srcDir.getAbsolutePath(), 
entryName);
                    try {
                        log("class: " +  checkFile.getCanonicalPath(), 
Project.MSG_VERBOSE);

                        InputStream in = new FileInputStream(checkFile);
                        ClassFile classFile = new ClassFile();
                        classFile.read(in);
                        Vector dependencyList = 
classFile.getClassDependencies();

                        Iterator deps = dependencyList.iterator();
                        while (deps.hasNext()) {
                            String depName = (String)deps.next();
                            File depFile = new 
File(config.srcDir.getAbsolutePath()
                                                    + File.separatorChar
                                                    + depName.replace('.', 
File.separatorChar)
                                                    + ".class");
                            if (depFile.exists() && depFile.isFile()) {
                                log("  depends on: " + depName, 
Project.MSG_VERBOSE);

                                if 
(!checkEntries.containsKey(depName.replace('.', File.separatorChar) + ".class"))
                                    newEntries.put(depName.replace('.', 
File.separatorChar) + ".class", depFile);
                            }
                        }
                    }
                    catch (IOException e) {
                        log("exception: " +  entryName, Project.MSG_INFO);
                    }
                }
            }
            candidates = newEntries;
            checkEntries.putAll(newEntries);
        }
    }


    /**
     * Returns a Classloader object which parses the passed in generic EjbJar 
classpath.
     * The loader is used to dynamically load classes from javax.ejb.* and the 
classes 
     * being added to the jar.
     *
     */ 
    protected ClassLoader getClassLoaderForBuild()
    {
        if (classpathLoader != null) {
            return classpathLoader;
        }
        
        Path combinedClasspath = getCombinedClasspath();

        // only generate a new ClassLoader if we have a classpath
        if (combinedClasspath == null) {
            classpathLoader = getClass().getClassLoader();
        }
        else {
            classpathLoader = new AntClassLoader(getTask().getProject(), 
combinedClasspath);
        }
        
        return classpathLoader;
    }
 
    /**
     * Called to validate that the tool parameters have been configured.
     *
     * @throws BuildException If the Deployment Tool's configuration isn't
     *                        valid
     */
    public void validateConfigured() throws BuildException {
        if ((destDir == null) || (!destDir.isDirectory())) {
            String msg = "A valid destination directory must be specified "
                            + "using the \"destdir\" attribute.";
            throw new BuildException(msg, getLocation());
        }
    }
}

Reply via email to