/*
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 1999 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.apache.tools.ant.taskdefs.optional.jsp;

//apache/ant imports
import org.apache.tools.ant.taskdefs.MatchingTask;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.taskdefs.Java;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.Project;


//java imports
import java.io.File;
import java.util.Vector;
import java.util.Date;
import java.util.StringTokenizer;

/**
 * Class to precompile JSP's using weblogic's jsp compiler version 6.x (weblogic.jspc)
 * 
 * @author <a href="mailto:erik.gollot@valtech.fr">Erik Gollot</a> http://www.valtech.com
 * 
 * Tested on Weblogic 6.0 and 6.1 - Win2000 and Solaris8 
 * 
 * required attributes
 *      src  : root of source tree for JSP, ie, the document root for your weblogic server
 *      dest : root of destination directory, what you have set as WorkingDir in the weblogic properties
 *      package : start package name under which your JSP's would be compiled
 * 
 * other attributes
 *     classpath : the classpath used to compile the JSP files
 *     compileFlags : set the JSP compiler flags
 *     webapp    : the root directory of your Web Application if your JSP are located into
 *                 a web application and if the JSP use extra libraries such as taglibs
 *     depend    : if set to true, all already compiled JSP will not be compiled again
 *     g         : set compiler debug mode on/off
 *     keepgenerated : does generated .java files are removed or not
 *     superclass : super class use for servlet inheritance
 * 
 * A classpath should be set which contains the weblogic classes as well as all application classes
 * referenced by the JSP. The system classpath is also appended when the jspc is called, so you may 
 * choose to put everything in the classpath while calling Ant. However, since presumably the JSP's will reference
 * classes being build by Ant, it would be better to explicitly add the classpath in the task
 * 
 * The task checks timestamps on the JSP's and the generated classes, and compiles
 * only those files that have changed. 
 * 
 * It follows the weblogic naming convention of putting classes in 
 *  <b> _dirName/_fileName.class for dirname/fileName.jsp   </b>
 * 
 * Limitation: It compiles the files thru the Classic compiler only. 
 * 
 * <pre>
 * example
 * <target name="jspcompile" depends="compile">
 *   <wl6jspc src="../web/frontend" dest="../deploy/ears/myEAR/frontend/WEB-INF/_tmp_war_wlserver_wlserver_frontend" 
 *            g="yes" keepgenerated="yes" webapp="../web/frontend" package="jsp_servlet" >
 *   <classpath>
 *          <pathelement location="${weblogic.classpath}" />
 *          <pathelement path="${compile.dest}" />
 *      </classpath>
 * 
 *   </wl6jspc>
 * </target>
 * </pre>
 * 
 */

public class WL6Jspc extends MatchingTask
{
    private File destinationDirectory; //root of compiled files tree
    private File sourceDirectory; // root of source files tree
    private String destinationPackage; //package under which resultant classes will reside
    private Path compileClasspath; //classpath used to compile the jsp files. 
    //private String compilerPath; //fully qualified name for the compiler executable
    private File    webAppDir; // -webapp option
    private boolean webAppDirIsSet = false;
    private String  compileFlags; // -compileFlags option
    private boolean depend = false; // -depend option
    private boolean debug = false; // -g option
    private boolean keepgenerated = false; // -keepgenerated option
    private String  superclass; // -superclass option
    
    private String pathToPackage = "";
    private Vector filesToDo = new Vector();
    
    public void execute() throws BuildException {
        if (!destinationDirectory.isDirectory()) {
            throw new BuildException("destination directory " + destinationDirectory.getPath() + 
                                     " is not valid");
        }
                               
        if (!sourceDirectory.isDirectory()) {
            throw new BuildException("src directory " + sourceDirectory.getPath() + 
                                     " is not valid");
        }

        if (destinationPackage == null) {
            throw new BuildException("package attribute must be present.", location);
        }
        
        
        String systemClassPath = System.getProperty("java.class.path");
        
        pathToPackage = this.destinationPackage.replace('.',File.separatorChar);
        // get all the files in the sourceDirectory
        DirectoryScanner ds = super.getDirectoryScanner(sourceDirectory);

        //use the systemclasspath as well, to include the ant jar
        if (compileClasspath == null) {
            compileClasspath = new Path(project);
        }
        
        compileClasspath.append(Path.systemClasspath);
        String[] files = ds.getIncludedFiles();
        
        //Weblogic.jspc calls System.exit() ... have to fork
        // Therefore, takes loads of time 
        // Can pass directories at a time (*.jsp) but easily runs out of memory on hefty dirs 
        // (even on  a Sun)
        Java helperTask = (Java)project.createTask("java");
        helperTask.setFork(true);
        helperTask.setClassname("weblogic.jspc");
        helperTask.setTaskName(getTaskName());
	int argsnum = 50; // ?? enough but should be set dynamically with the right value
        String[] args = new String[argsnum];
        
        File jspFile = null;
        String parents = "";
        String arg = "";
        int j=0;
	if (webAppDirIsSet == true) {
          args[j++] = "-webapp";
          args[j++] = webAppDir.getAbsolutePath().trim();
	}
	if (keepgenerated)
          args[j++] = "-keepgenerated";
	if (depend)
          args[j++] = "-depend";
	if (debug)
          args[j++] = "-g";
	if (superclass != null) {
          args[j++] = "-superclass";
          args[j++] = superclass;
	}
        args[j++] = "-d";
        args[j++] = destinationDirectory.getAbsolutePath().trim(); 
        args[j++] = "-docroot";
        args[j++] = sourceDirectory.getAbsolutePath().trim();
        //Call compiler as class... dont want to fork again 
        //Use classic compiler -- can be parameterised?
        args[j++] =  "-compilerclass";
        args[j++] = "sun.tools.javac.Main";
        //Weblogic jspc does not seem to work unless u explicitly set this...
        // Does not take the classpath from the env....
        // Am i missing something about the Java task??
        args[j++] = "-classpath";
        args[j++] = compileClasspath.toString();

        this.scanDir(files);
        log("Compiling "+filesToDo.size() + " JSP files");
            
        for (int i=0;i<filesToDo.size();i++) {
            jspFile = new File((String) filesToDo.elementAt(i));
            args[j] = "-package";
	    args[j+1] = destinationPackage;
            args[j+2] =  sourceDirectory+File.separator+(String) filesToDo.elementAt(i);
            arg="";
            
            for (int x=0;x<=j+2;x++) {
                arg += " "+ args[x];
            }
            
            helperTask.clearArgs();
            helperTask.setArgs(arg);
            helperTask.setClasspath(compileClasspath);
            if (helperTask.executeJava() != 0) {                         
                log(files[i] + " failed to compile",Project.MSG_WARN) ;
            }
        }
    }

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

    /**
     * Maybe creates a nested classpath element.
     */
    public Path createClasspath() {
        if (compileClasspath == null) {
            compileClasspath = new Path(project);
        }
        return compileClasspath;
    }

    /**
     * Set the directory containing the source jsp's
     * 
     *
     * @param dirName the directory containg the source jsp's
     */
    public void setSrc(File dirName) {
        
        sourceDirectory = dirName;
    }
    
     /**
     * Set the directory containing the source jsp's
     * 
     *
     * @param dirName the directory containg the source jsp's
     */
    public void setDest(File dirName) {
        
        destinationDirectory = dirName;
    }
    
    /**
     * Set the package under which the compiled classes go
     * 
     * @param packageName the package name for the clases
     */
    public void setPackage(String packageName) {
        
        destinationPackage=packageName; 
    }

    /**
     * Set the web application directory under which the JSP pages are
     * 
     * @param dirName the Web Application directory path
     */
    public void setWebapp(File dirName)
    {
        webAppDir = dirName;
	webAppDirIsSet = true;
    }

    
    /**
     * Set the -compileFlags option
     * 
     * @param compileFlags. String that represents the flag list 
     */
    public void setCompileFlags(String compileFlags)
    {
        this.compileFlags = compileFlags;
    }
    
    /**
     * Set the -depend option 
     * 
     * @param dependStr. Default is false. If set to true an already compiled JSP file 
     * will not be recompiled
     */
    public void setDepend(String dependStr)
    {
        if (dependStr.toLowerCase().equals("yes") || dependStr.toLowerCase().equals("y"))
          this.depend = true;
	else
          this.depend = false;
    }
     
    /**
     * Set the -g option 
     * 
     * @param debugStr. Default is false. If set to true compile with debugging on
     */
    public void setG(String debugStr)
    {
        if (debugStr.toLowerCase().equals("yes") || debugStr.toLowerCase().equals("y"))
          this.debug= true;
	else
          this.debug= false;
    }
    
    /**
     * Set the -keepgenerated option 
     * 
     * @param keepgeneratedStr. Default is false. 
     */
    public void setKeepgenerated(String keepgeneratedStr)
    {
        if (keepgeneratedStr.toLowerCase().equals("yes") || keepgeneratedStr.toLowerCase().equals("y"))
          this.keepgenerated = true;
	else
          this.keepgenerated = false;
    }
     
    /**
     * Set the -superclass option
     * 
     * @param superclass. Super class used to generate the servlet inheritance 
     */
    public void setSuperclass(String superclass)
    {
        this.superclass = superclass;
    }
    
    protected void scanDir(String files[]) {

        long now = (new Date()).getTime();
        File jspFile = null;
        String parents = null;
        String pack = "";
        for (int i = 0; i < files.length; i++) {
            File srcFile = new File(this.sourceDirectory, files[i]);
            //XXX
            // All this to convert source to destination directory according to weblogic standards
            // Can be written better... this is too hacky!
            jspFile = new File(files[i]);
            parents = jspFile.getParent();
            int loc=0;
            
            pack = pathToPackage;
            
            String filePath = pack + File.separator + "_";
            int startingIndex 
                = files[i].lastIndexOf(File.separator) != -1 ? files[i].lastIndexOf(File.separator) + 1 : 0;
            int endingIndex = files[i].indexOf(".jsp");
            if (endingIndex == -1) {
                continue;
            }
            
            filePath += files[i].substring(startingIndex, endingIndex);
            filePath += ".class";
            File classFile = new File(this.destinationDirectory, filePath);

            if (srcFile.lastModified() > now) {
                log("Warning: file modified in the future: " +
                    files[i], Project.MSG_WARN);
            }
            if (srcFile.lastModified() > classFile.lastModified()) {
               //log("Files are" + srcFile.getAbsolutePath()+" " +classFile.getAbsolutePath());
                filesToDo.addElement(files[i]);
                log("Recompiling File "+files[i],Project.MSG_VERBOSE);
            }
        }
    }
    
    
    protected String replaceString(String inpString,String escapeChars,String replaceChars) {
        String localString="";
        int numTokens=0;
        StringTokenizer st=new StringTokenizer(inpString,escapeChars,true);
        numTokens=st.countTokens();
        for(int i=0;i<numTokens;i++)
        {
            String test=st.nextToken();
            test=(test.equals(escapeChars)?replaceChars:test);
            localString+=test;
        }
        return localString;
    }
}

