/**
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2002 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 apache@apache.org.
 *
 * 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.ow2.jonas.ant;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.Manifest;

import javax.xml.parsers.SAXParser;

import org.apache.tools.ant.AntClassLoader;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Java;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.Reference;

/**
 * The deployment tool to add the jonas specific deployment descriptors to the
 * ejb JAR file. JONAS only requires one additional file jonas-ejb-jar.xml.
 * @author <a href="mailto:cmorvan@ingenosya.com">Cyrille Morvan </a>, <a
 *         href="http://www.ingenosya.com">Ingenosya France </a>,
 *         Contributor(s): <a href="mailto:mathieu.peltier@inrialpes.fr">Mathieu
 *         Peltier </a> Adriana Danes: change configuration directory name from
 *         "config" to "conf"
 * @version 1.0
 * @see EjbJar#createJonas
 */
public class JonasDeploymentTool extends GenericDeploymentTool {

    /** Public Id of the standard deployment descriptor DTD. */
    protected static final String EJB_JAR_1_1_PUBLIC_ID = "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN";
    protected static final String EJB_JAR_2_0_PUBLIC_ID = "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN";

    /** Public Id of the JOnAS-specific deployment descriptor DTD. */
    protected static final String JONAS_EJB_JAR_2_4_PUBLIC_ID = "-//ObjectWeb//DTD JOnAS 2.4//EN";
    protected static final String JONAS_EJB_JAR_2_5_PUBLIC_ID = "-//ObjectWeb//DTD JOnAS 2.5//EN";
    protected static final String JONAS_EJB_JAR_3_0_PUBLIC_ID = "-//ObjectWeb//DTD JOnAS 3.0//EN";
    protected static final String JONAS_EJB_JAR_3_2_PUBLIC_ID = "-//ObjectWeb//DTD JOnAS 3.2//EN";
    protected static final String JONAS_EJB_JAR_3_3_PUBLIC_ID = "-//ObjectWeb//DTD JOnAS 3.3//EN";
    protected static final String JONAS_EJB_JAR_3_3_2_PUBLIC_ID = "-//ObjectWeb//DTD JOnAS 3.3.2//EN";

    /**
     * Name of the standard deployment descriptor DTD (these files are stored in
     * the ${JONAS_ROOT}/xml directory).
     */
    protected static final String EJB_JAR_1_1_DTD = "/org/ow2/jonas/deployment/ejb/ejb-jar_1_1.dtd";
    protected static final String EJB_JAR_2_0_DTD = "/org/ow2/jonas/deployment/ejb/ejb-jar_2_0.dtd";

    /**
     * Name of the JOnAS-specific deployment descriptor DTD (these files are
     * stored in the ${JONAS_ROOT}/xml directory).
     */
    protected static final String JONAS_EJB_JAR_2_4_DTD = "/org/ow2/jonas/deployment/ejb/jonas-ejb-jar_2_4.dtd";
    protected static final String JONAS_EJB_JAR_2_5_DTD = "/org/ow2/jonas/deployment/ejb/jonas-ejb-jar_2_5.dtd";
    protected static final String JONAS_EJB_JAR_3_0_DTD = "/org/ow2/jonas/deployment/ejb/jonas-ejb-jar_3_0.dtd";
    protected static final String JONAS_EJB_JAR_3_2_DTD = "/org/ow2/jonas/deployment/ejb/jonas-ejb-jar_3_2.dtd";
    protected static final String JONAS_EJB_JAR_3_3_DTD = "/org/ow2/jonas/deployment/ejb/jonas-ejb-jar_3_3.dtd";
    protected static final String JONAS_EJB_JAR_3_3_2_DTD = "/org/ow2/jonas/deployment/ejb/jonas-ejb-jar_3_3_2.dtd";

    /** Default JOnAS deployment descriptor name. */
    protected static final String JONAS_DD = "jonas-ejb-jar.xml";

    /** Bootstrap class name. */
    protected static final String BOOTSTRAP_CLASS = "org.ow2.jonas.client.boot.Bootstrap";

    /** GenIC class name. */
    protected static final String GENIC_CLASS = "org.ow2.jonas.generators.genic.GenIC";

    /**
     * Filename of the standard EJB descriptor (which is passed to this class
     * from the parent "ejbjar" task). This file is relative to the directory
     * specified by the "srcdir" attribute in the ejbjar task.
     */
    private String descriptorName;

    /**
     * Filename of the JOnAS-specific EJB descriptor (which is passed to this
     * class from the parent "ejbjar" task). This file is relative to the
     * directory specified by the "srcdir" attribute in the ejbjar task.
     */
    private String jonasDescriptorName;

    /* ------------- */
    /* GenIC options */
    /* ------------- */

    /**
     * Temporary output directory used by GenIC.
     */
    private File outputdir;

    /**
     * <code>true</code> if the intermediate Java source files generated by
     * GenIC must be deleted or not. The default is <code>false</code>
     */
    private boolean keepgenerated = false;


    /**
     * If true, the JDK-provided RMIC compiler is used.  If false, the built-in
     * rmic compiler is used.
     */
    private boolean nofastrmic = false;

    /**
     * <code>true</code> if the generated source files must not be compiled
     * via the java and rmi compilers. The default is <code>false</code>.
     */
    private boolean nocompil = false;

    /**
     * Equivalent to the <code>-invokecmd</code> GenIC option. The default is
     * <code>false</code>.
     */
    private boolean invokeCmd = false;

    /**
     * <code>true</code> if the XML deployment descriptors must be parsed
     * without validation. The default is <code>false</code>.
     */
    private boolean novalidation = false;

    /**
     * Java compiler to use. The default is the value of
     * <code>build.compiler</code> property.
     */
    private String javac;

    /** Options to pass to the java compiler. */
    private String javacopts;

    /** Options to pass to the rmi compiler. */
    private String rmicopts;

    /**
     * <code>true</code> if the GenIC call must be verbose. The default is
     * <code>false</code>.
     */
    private boolean verbose = false;

    /** Additional args to send to GenIC. */
    private String additionalargs;

    /** Additional args to pass to the GenIC JVM. */
    private String jvmopts = null;

    /** List of protocols (used for Carol) */
    private String protocols = null;

    /* ------------- */
    /* other options */
    /* ------------- */

    /** JOnAS root directory. */
    private File jonasroot;

    /** JOnAS base directory. */
    private File jonasbase;

    /**
     * <code>true</code> if the generic JAR file used as input to GenIC must
     * be retained. The default is <code>false</code>.
     */
    private boolean keepgeneric = false;

    /** Stores the suffix for the JOnAS JAR file. The default is '.jar'. */
    private String suffix = ".jar";

    /**
     * <code>true</code> if GenIC must not be run on the EJB JAR. The default
     * is <code>false</code>.
     */
    private boolean nogenic = false;

    /* -------------------- */
    /* GenIC options setter */
    /* -------------------- */

    /**
     * Set the {@link #keepgenerated}flag.
     * @param aBoolean <code>true</code> if the flag must be set.
     */
    public void setKeepgenerated(boolean aBoolean) {
        keepgenerated = aBoolean;
    }

    public void setNoFastRMIC(boolean value) {
        nofastrmic = value;
    }

    /**
     * Do nothing because the mappernames attribute is deprecated
     * @param aString list of mapper names separed by ,
     */
    public void setMappernames(String aString) {
        log("'mappernames' attribute ignored (deprecated)", Project.MSG_WARN);
    }

    /**
     * Set the protocols list
     * @param aString list of the protocol names separed by ,
     */
    public void setProtocols(String aString) {
        protocols = aString;
    }

    /**
     * Set the classpath as a Reference on another path
     * @param r Referenced Classpath
     */
    public void setClasspathref(Reference r) {
        createClasspath().setRefid(r);
    }
    /**
     * Set the {@link #additionalargs}.
     * @param aString additional args.
     */
    public void setAdditionalargs(String aString) {
        additionalargs = aString;
    }

    /**
     * Set the {@link #nocompil}flag.
     * @param aBoolean <code>true</code> if the flag must be set.
     */
    public void setNocompil(boolean aBoolean) {
        nocompil = aBoolean;
    }

    /**
     * Set the {@link #invokecmd}flag.
     * @param aBoolean <code>true</code> if the flag must be set.
     */
    public void setInvokecmd(boolean aBoolean) {
        invokeCmd = aBoolean;
    }

    /**
     * Set the {@link #novalidation}flag.
     * @param aBoolean <code>true</code> if the flag must be set.
     */
    public void setNovalidation(boolean aBoolean) {
        novalidation = aBoolean;
    }

    /**
     * Set the java compiler {@link #javac}to use.
     * @param aString the java compiler.
     */
    public void setJavac(String aString) {
        javac = aString;
    }

    /**
     * Set the options to pass to the java compiler.
     * @param aString the options.
     */
    public void setJavacopts(String aString) {
        javacopts = aString;
    }

    /**
     * Set the options to pass to the rmi compiler.
     * @param aString the options.
     */
    public void setRmicopts(String aString) {
        rmicopts = aString;
    }

    /**
     * Set the {@link #verbose}flag.
     * @param aBoolean <code>true</code> if the flag must be set.
     */
    public void setVerbose(boolean aBoolean) {
        verbose = aBoolean;
    }

    /* -------------------- */
    /* other options setter */
    /* -------------------- */

    /**
     * Set the JOnAS root directory.
     * @param aFile the JOnAS root directory.
     */
    public void setJonasroot(File aFile) {
        jonasroot = aFile;
    }

    /**
     * Set the JOnAS base directory.
     * @param aFile the JOnAS base directory.
     */
    public void setJonasbase(File aFile) {
        jonasbase = aFile;
    }

    /**
     * Set the {@link #keepgeneric}flag.
     * @param aBoolean <code>true</code> if the flag must be set.
     */
    public void setKeepgeneric(boolean aBoolean) {
        keepgeneric = aBoolean;
    }

    /**
     * Set the {@link #jarsuffix}.
     * @param aString the string to use as the suffix.
     */
    public void setJarsuffix(String aString) {
        suffix = aString;
    }

    /**
     * Set the {@link #nogenic}flag.
     * @param aBoolean <code>true</code> if the flag must be set.
     */
    public void setNogenic(boolean aBoolean) {
        nogenic = aBoolean;
    }

    /**
     * Set the additional args to pass to the GenIC JVM.
     * @param aString the options.
     */
    public void setJvmopts(String aString) {
        jvmopts = aString;
    }

    /* ------------- */
    /* other methods */
    /* ------------- */

    public void processDescriptor(String aDescriptorName, SAXParser saxParser) {

        descriptorName = aDescriptorName;

        log("JOnAS Deployment Tool processing for JOnAS: " + descriptorName, Project.MSG_VERBOSE);

        super.processDescriptor(descriptorName, saxParser);

        if (outputdir != null) {
            // the method deleteOnExit() do not work because the directory is
            // not empty
            log("Deleting temp output directory '" + outputdir + "'.", Project.MSG_VERBOSE);
            JOnASAntTool.deleteAllFiles(outputdir);
        }
    }

    protected void writeJar(String baseName, File jarfile, Hashtable ejbFiles, String publicId,
            boolean includeInnerClasses) throws BuildException {

        // create the generic jar first
        File genericJarFile = super.getVendorOutputJarFile(baseName);

        super.writeJar(baseName, genericJarFile, ejbFiles, publicId, true);


        // GenIC call on generic jar
        addGenICGeneratedFiles(genericJarFile, ejbFiles);

        // merge Manifest give by user if exist with this generated by Genic
        if (this.getConfig() != null && this.getConfig().manifest != null
                && this.getConfig().manifest.exists()) {
            File filePathGenicManifest = (File) ejbFiles.get("META-INF/MANIFEST.MF");
            if (filePathGenicManifest != null)
                mergeManifest(this.getConfig().manifest, filePathGenicManifest);
            else
                ejbFiles.put("META-INF/MANIFEST.MF", this.getConfig().manifest);
        }

        // create the real jar
        super.writeJar(baseName, getVendorOutputJarFile(baseName), ejbFiles, publicId, false);

        //log("genericJarFile: " + genericJarFile, Project.MSG_VERBOSE);
        //log("getVendorOutputJarFile(baseName): " +
        //getVendorOutputJarFile(baseName), Project.MSG_VERBOSE);
        //log("baseName: " + baseName, Project.MSG_VERBOSE);

        if (!keepgeneric) {
            log("Deleting generic JAR " + genericJarFile.toString(), Project.MSG_VERBOSE);
            genericJarFile.delete();
        }
    }

    /**
     * Merge Manifest file
     * @param manifestUserFile : input file
     * @param manifestGenicFile : output file
     */
    protected void mergeManifest(File manifestUserFile, File manifestGenicFile) {
        try {
            InputStream userStream = new FileInputStream(manifestUserFile);
            InputStream genICStream = new FileInputStream(manifestGenicFile);

            Manifest userManifest = new Manifest(userStream);
            Manifest genICManifest = new Manifest(genICStream);

            // close streams
            userStream.close();
            genICStream.close();

            // Merge main attributes
            Attributes userMainAttributes = userManifest.getMainAttributes();
            Attributes genICMainAttributes = genICManifest.getMainAttributes();
            Iterator itUserMainAttributes = userMainAttributes.keySet().iterator();
            while (itUserMainAttributes.hasNext()) {
                Object name = itUserMainAttributes.next();
                Object value = userMainAttributes.get(name);
                genICMainAttributes.put(name, value);
            }


            // Merge specific entries
            Map userEntries = userManifest.getEntries();
            Set keys = userEntries.keySet();
            Iterator itKeys = keys.iterator();

            // Get keys of user manifest
            while (itKeys.hasNext()) {
                String key = (String) itKeys.next();
                Attributes attributes = userManifest.getAttributes(key);
                // key is present in final manifest ?
                Attributes genICAttributes = genICManifest.getAttributes(key);
                if (genICAttributes == null) {
                    genICAttributes = new Attributes();
                    genICManifest.getEntries().put(key, genICAttributes);
                }

                // write attributes of user manifest into genic manifest
                Set atKeys = attributes.keySet();
                Iterator itAt = atKeys.iterator();
                while (itAt.hasNext()) {
                    Object name = itAt.next();
                    Object value = attributes.get(name);
                    genICAttributes.put(name, value);
                }
            }
            FileOutputStream fos = new FileOutputStream(manifestGenicFile);
            genICManifest.write(fos);
            fos.close();



        } catch (IOException e) {
            log("Unable to update Manifest with user manifest :" + e.getMessage(), Project.MSG_WARN);
        }
    }

    protected void addVendorFiles(Hashtable ejbFiles, String ddPrefix) {

        // JOnAS-specific descriptor deployment
        jonasDescriptorName = getJonasDescriptorName();
        File jonasDD = new File(getConfig().descriptorDir, jonasDescriptorName);

        if (jonasDD.exists()) {
            ejbFiles.put(META_DIR + JONAS_DD, jonasDD);
        } else {
            log("Unable to locate the JOnAS deployment descriptor. It was expected to be in: " + jonasDD.getPath()
                    + ".", Project.MSG_WARN);
        }
    }

    protected File getVendorOutputJarFile(String baseName) {
        return new File(getDestDir(), baseName + suffix);
    }

    /**
     * Determines the name of the JOnAS-specific EJB descriptor using the
     * specified standard EJB descriptor name. In general, the standard
     * descriptor will be named "[basename]-ejb-jar.xml", and this method will
     * return "[basename]-jonas-ejb-jar.xml" or "jonas-[basename].xml"
     * @return The name of the JOnAS-specific EJB descriptor file.
     */
    private String getJonasDescriptorName() {

        // descriptorName = <path><basename><basenameterminator><remainder>
        // examples = /org/objectweb/fooAppli/foo/Foo-ejb-jar.xml
        // examples = /org/objectweb/fooAppli/foo/Foo.xml (JOnAS convention)

        String jonasDescriptorName; // JOnAS-specific DD
        boolean jonasConvention = false; // true if the JOnAS convention is used
                                         // for the DD
        String path; // Directory path of the EJB descriptor
        String fileName; // EJB descriptor file name
        String baseName; // Filename appearing before name terminator
        String remainder; // Filename appearing after the name terminator

        int startOfFileName = descriptorName.lastIndexOf(File.separatorChar);
        if (startOfFileName != -1) {
            // extract path info
            path = descriptorName.substring(0, startOfFileName + 1);
            fileName = descriptorName.substring(startOfFileName + 1);
        } else {
            // descriptorName is just a file without path
            path = "";
            fileName = descriptorName;
        }

        if (fileName.startsWith(EJB_DD)) {
            return path + JONAS_DD;
        }

        int endOfBaseName = descriptorName.indexOf(getConfig().baseNameTerminator, startOfFileName);

        /*
         * Check for the odd case where the terminator and/or filename extension
         * aren't found. These will ensure "jonas-" appears at the end of the
         * name and before the '.' (if present).
         */
        if (endOfBaseName < 0) {
            // baseNameTerminator not found: the descriptor use the
            // JOnAS naming convention, ie [Foo.xml,jonas-Foo.xml] and
            // not [Foo<baseNameTerminator>-ejb-jar.xml,
            // Foo<baseNameTerminator>-jonas-ejb-jar.xml].
            endOfBaseName = descriptorName.lastIndexOf('.') - 1;
            if (endOfBaseName < 0) {
                // no . found
                endOfBaseName = descriptorName.length() - 1;
            }

            jonasConvention = true;
        }

        baseName = descriptorName.substring(startOfFileName + 1, endOfBaseName + 1);
        remainder = descriptorName.substring(endOfBaseName + 1);

        if (jonasConvention) {
            jonasDescriptorName = path + "jonas-" + baseName + ".xml";
        } else {
            jonasDescriptorName = path + baseName + "jonas-" + remainder;
        }

        log("Standard EJB descriptor name: " + descriptorName, Project.MSG_VERBOSE);
        log("JOnAS-specific descriptor name: " + jonasDescriptorName, Project.MSG_VERBOSE);

        return jonasDescriptorName;
    }

    protected String getJarBaseName(String descriptorFileName) {

        String baseName = null;

        if (getConfig().namingScheme.getValue().equals(EjbJar.NamingScheme.DESCRIPTOR)) {

            // try to find JOnAS specific convention name
            if (descriptorFileName.indexOf(getConfig().baseNameTerminator) == -1) {

                // baseNameTerminator not found: the descriptor use the
                // JOnAS naming convention, ie [Foo.xml,jonas-Foo.xml] and
                // not [Foo<baseNameTerminator>-ejb-jar.xml,
                // Foo<baseNameTerminator>-jonas-ejb-jar.xml].

                String aCanonicalDescriptor = descriptorFileName.replace('\\', '/');
                int lastSeparatorIndex = aCanonicalDescriptor.lastIndexOf('/');
                int endOfBaseName;

                if (lastSeparatorIndex != -1) {
                    endOfBaseName = descriptorFileName.indexOf(".xml", lastSeparatorIndex);
                } else {
                    endOfBaseName = descriptorFileName.indexOf(".xml");
                }

                if (endOfBaseName != -1) {
                    baseName = descriptorFileName.substring(0, endOfBaseName);
                }
            }
        }

        if (baseName == null) {
            // else get standard baseName
            baseName = super.getJarBaseName(descriptorFileName);
        }

        log("JAR base name: " + baseName, Project.MSG_VERBOSE);

        return baseName;
    }

    protected void registerKnownDTDs(DescriptorHandler handler) {

        handler.registerDTD(EJB_JAR_1_1_PUBLIC_ID, EJB_JAR_1_1_DTD);
        handler.registerDTD(EJB_JAR_2_0_PUBLIC_ID, EJB_JAR_2_0_DTD);

        handler.registerDTD(JONAS_EJB_JAR_2_4_PUBLIC_ID, JONAS_EJB_JAR_2_4_DTD);
        handler.registerDTD(JONAS_EJB_JAR_2_5_PUBLIC_ID, JONAS_EJB_JAR_2_5_DTD);
        handler.registerDTD(JONAS_EJB_JAR_3_0_PUBLIC_ID, JONAS_EJB_JAR_3_0_DTD);
        handler.registerDTD(JONAS_EJB_JAR_3_2_PUBLIC_ID, JONAS_EJB_JAR_3_2_DTD);
        handler.registerDTD(JONAS_EJB_JAR_3_3_PUBLIC_ID, JONAS_EJB_JAR_3_3_DTD);
        handler.registerDTD(JONAS_EJB_JAR_3_3_2_PUBLIC_ID, JONAS_EJB_JAR_3_3_2_DTD);
    }

    /**
     * Add to the given hashtable all the file generated by GenIC.
     * @param genericJarFile jar file.
     * @param ejbFiles the hashtable.
     */
    private void addGenICGeneratedFiles(File genericJarFile, Hashtable ejbFiles) {

        Java genicTask = null; // GenIC task

        if (nogenic) {
            return;
        }

        genicTask = (Java) getTask().getProject().createTask("java");
        genicTask.setTaskName("genic");
        genicTask.setFork(true);

        // jonas root
        genicTask.createJvmarg().setValue("-Djonas.root=" + jonasroot);

        // jonas base
        genicTask.createJvmarg().setValue("-Djonas.base=" + jonasbase);

        // Endorsed directory
        File endorsedDir = new File(new File(jonasroot, "lib"), "endorsed");
        genicTask.createJvmarg().setValue("-Djava.endorsed.dirs=" + endorsedDir);

        // java policy file
        String jonasConfigDir = jonasroot + File.separator + "conf";
        File javaPolicyFile = new File(jonasConfigDir, "java.policy");
        if (javaPolicyFile.exists()) {
            genicTask.createJvmarg().setValue("-Djava.security.policy=" + javaPolicyFile.toString());
        }

        // The bootstrap class must launch the GenIC class
        genicTask.createArg().setValue(JonasDeploymentTool.GENIC_CLASS);

        // outputdir
        try {
            outputdir = createTempDir();
        } catch (IOException aIOException) {
            throw new BuildException("Cannot create temp dir", aIOException);
        }
        log("Using temporary output directory: " + outputdir, Project.MSG_VERBOSE);

        genicTask.createArg().setValue("-d");
        genicTask.createArg().setFile(outputdir);

        // classpath
        Path classpath = getCombinedClasspath();
        if (classpath == null) {
            classpath = new Path(getTask().getProject());
        }
        // add client-bootstrap.jar
        String bootJar = jonasroot + File.separator + "lib" + File.separator + "bootstrap" + File.separator
                + "client-bootstrap.jar";
        String clientJar = jonasroot + File.separator + "lib" + File.separator + "client.jar";
        Path bootstrap = new Path(getTask().getProject(), bootJar);
        Path clientPath = new Path(getTask().getProject(), clientJar);
        bootstrap.append(clientPath);
        //classpath.append(new Path(classpath.getProject(), bootJar));

        classpath.append(new Path(classpath.getProject(), outputdir.toString()));

        log("Using classpath: " + bootstrap.toString(), Project.MSG_VERBOSE);
        genicTask.setClasspath(bootstrap);

        if (!checkBootstrapClassName(bootstrap)) {
            log("Cannot find bootstrap class in classpath.", Project.MSG_ERR);
            throw new BuildException("Bootstrap class not found, please check the classpath.");
        } else {
            genicTask.setClassname(JonasDeploymentTool.BOOTSTRAP_CLASS);
        }

        // classpath
        genicTask.createArg().setValue("-classpath");
        genicTask.createArg().setPath(classpath);

        // keepgenerated
        if (keepgenerated) {
            genicTask.createArg().setValue("-keepgenerated");
        }

        if (nofastrmic) {
            genicTask.createArg().setValue("-nofastrmic");
        }

        // nocompil
        if (nocompil) {
            genicTask.createArg().setValue("-nocompil");
        }

        // invokecmd
        if (invokeCmd) {
            genicTask.createArg().setValue("-invokecmd");
        }

        // novalidation
        if (novalidation) {
            genicTask.createArg().setValue("-novalidation");
        }

        // javac
        if (javac != null) {
            genicTask.createArg().setValue("-javac");
            genicTask.createArg().setLine(javac);
        }

        // javacopts
        if (javacopts != null && !javacopts.equals("")) {
            genicTask.createArg().setValue("-javacopts");
            genicTask.createArg().setValue(javacopts);
        }

        // rmicopts
        if (rmicopts != null && !rmicopts.equals("")) {
            genicTask.createArg().setValue("-rmicopts");
            genicTask.createArg().setValue(rmicopts);
        }

        // verbose
        if (verbose) {
            genicTask.createArg().setValue("-verbose");
        }

        // additionalargs
        if (additionalargs != null) {
            genicTask.createArg().setLine(additionalargs);
        }

        // protocols
        if (protocols != null) {
            genicTask.createArg().setValue("-protocols");
            genicTask.createArg().setValue(protocols);
        }

        // the generated classes must not be added in the generic JAR!
        genicTask.createArg().setValue("-noaddinjar");

        // jvmopts
        if (jvmopts != null && !jvmopts.equals("")) {
            genicTask.createJvmarg().setLine(jvmopts);
        }

        // input file to process by GenIC
        genicTask.createArg().setValue(genericJarFile.getPath());

        // calling GenIC task
        log("Calling  GenIC task for " + getConfig().descriptorDir + File.separator + descriptorName + ".",
                Project.MSG_VERBOSE);

        if (genicTask.executeJava() != 0) {
            // Don't delete the temp output directory in case of error:

            //log("Deleting temp output directory '" + outputdir + "'.",
            // Project.MSG_VERBOSE);
            // the method deleteOnExit() do not work because the directory is
            // not empty
            //JOnASAntTool.deleteAllFiles(outputdir);

            if (!keepgeneric) {
                log("Deleting generic JAR " + genericJarFile.toString(), Project.MSG_VERBOSE);
                genericJarFile.delete();
            }

            throw new BuildException("GenIC reported an error.");
        }

        // add the generated files to the ejbFiles
        addAllFiles(outputdir, "", ejbFiles);
    }

    /**
     * Check the bootstrap class name to use in the given classpath.
     * @param classpath classpath where the boostrap class must be searched.
     * @return true if the bootstrap is available in the classpath
     */
    private boolean checkBootstrapClassName(Path classpath) {
        log("Looking for bootstrap class in classpath: " + classpath.toString(), Project.MSG_VERBOSE);
        AntClassLoader cl = new AntClassLoader(classpath.getProject(), classpath);
        try {
            cl.loadClass(JonasDeploymentTool.BOOTSTRAP_CLASS);
            log("Found Bootstrap class '" + JonasDeploymentTool.BOOTSTRAP_CLASS + "' in classpath.",
                    Project.MSG_VERBOSE);
        } catch (ClassNotFoundException cnf1) {
            log("Bootstrap class '" + JonasDeploymentTool.BOOTSTRAP_CLASS + "' not found in classpath.",
                    Project.MSG_VERBOSE);
            return false;
        }
        return true;
    }

    protected void checkConfiguration(String descriptorFileName, SAXParser saxParser) throws BuildException {
        // jonasroot
        if (jonasroot == null) {
            throw new BuildException("The jonasroot attribute is not set.");
        } else if (!jonasroot.isDirectory()) {
            throw new BuildException("The jonasroot attribute '" + jonasroot + "' is not a valid directory.");
        }
        // jonasbase default value is jonasroot
        if (jonasbase == null) {
            jonasbase = jonasroot;
        }

        // additionalargs
        if (additionalargs != null && additionalargs.equals("")) {
            throw new BuildException("Empty additionalargs attribute.");
        }

        // javac
        if (javac != null && javac.equals("")) {
            throw new BuildException("Empty javac attribute.");
        }
    }

    /* ----------------------------------------------------------------------------------- */
    /* utilitary methods */
    /* ----------------------------------------------------------------------------------- */

    /**
     * Create a temporary directory for GenIC output.
     * @return the temp directory.
     * @throws BuildException if a temp directory cannot be created.
     */
    private File createTempDir() throws IOException {
        File tmpDir = File.createTempFile("genic", null, null);
        tmpDir.delete();
        if (!tmpDir.mkdir()) {
            throw new IOException("Cannot create the temporary directory '" + tmpDir + "'.");
        }
        return tmpDir;
    }

    /**
     * Add a file to the a given hashtable. If the file is a directory, add
     * recursivly all the files inside to the hashtable.
     * @param file the file to add.
     * @param rootDir the current sub-directory to scan.
     * @param hashtable the hashtable where to add the files.
     */
    private void addAllFiles(File file, String rootDir, Hashtable hashtable) {

        if (!file.exists()) {
            throw new IllegalArgumentException();
        }

        String newRootDir;
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (int i = 0; i < files.length; i++) {
                if (rootDir.length() > 0) {
                    newRootDir = rootDir + File.separator + files[i].getName();
                } else {
                    newRootDir = files[i].getName();
                }
                addAllFiles(files[i], newRootDir, hashtable);
            }
        } else {
            hashtable.put(rootDir.replace(File.separatorChar , '/'), file); // for a window platform replace \ by / is necessary
        }
    }
}
