/*
 * 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 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;

//java imports
import java.io.*;
import java.util.*;

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

//jasper imports
import org.apache.jasper.JspC;
import org.apache.jasper.JasperException;
//yet another apache project logger. 
import org.apache.jasper.logging.*;

/**
 *  Class to precompile JSP's using jasper.
 *
 * @author     Steve 'If it's a support call, I deny all knowledge' Loughran
 * @author     <a href="mailto:avik@aviksengupta.com">Avik Sengupta</a>
 *      http://www.webteksoftware.com 
 
 *      This class used wljspc as a starting
 *      point, but little is reused. The core information comes from tomcat4.0
 *      docs, in the jasper section the initial implementation builds
 *      a command line and feeds it to jasper, turning a JasperException into
*       a BuildException when raised.
*       Subclassing: doJspc and validate() are both good starting points, 
*       along with execute() itself.
 * @created    June 2, 2001
 */

public class Jasperc extends MatchingTask {

    /**
     *  flag to control action on execution trouble
     * 
     */
    protected boolean failOnError=true;

    /**
     *  web apps
     */
    protected Vector webApps;

    /**
     *  source filesets
     */
    protected Vector sources;

    /**
     *  root of compiled files tree
     */
    private File destinationDirectory;

    /**
     *  root of source files tree
     */
    private File sourceDirectory;

    /**
     *  option -c: name of first class in task
     */
    private String classname;

    /**
     *  option -mapped Generate separate write() calls for each HTML line
     *  in the JSP
     */
    private boolean mapped = false;

    /**
     *  -ieplugin <clsid>Java Plugin classid for Internet Explorer
     */

    private String ieplugin;

    /**
     *  -sax2 <driverclassname>Driver class name for the SAX 2.0 parser to
     *  be used
     */

    private String sax2classname;

    /**
     *  flag to switch from flat to heirarchical output. controls -d / -dd
     *  switch
     */
    private boolean flatten = false;

    /**
     *  -webinc <file>Creates partial servlet mappings for the -webapp option
     */
    private File webinc;

    /**
     *  -webxml <file>Creates a complete web.xml when using the -webapp option.
     */

    private File webxml;

    /**
     *  verbose flag; 2 is the default; 0 means silent. 
     */
    private int verbosity = 2;

    /**
     *  -uribase <dir>The uri directory compilations should be relative to
     *  (Default is "/")
     */

    private File uribase;

    /**
     *  -uriroot <dir>The root directory that uri files should be resolved
     *  against, (Default is the directory jspc is invoked from)
     */
    private File uriroot;

    /**
     *  package under which resultant classes will reside
     */
    private String packageName;

    /**
     *  classpath used to compile the jsp files.
     */
    private Path compileClasspath;


    /**
     *  set option -c: name of first class to generate
     *
     * @param  classname  The new Classname value
     */
    public void setClassname(String classname) {
        this.classname = classname;
    }


    /**
     *  option -mapped Generate separate write() calls for each HTML line
     *  in the JSP
     *
     * @param  mapped  The new Mapped value
     */
    public void setMapped(boolean mapped) {
        this.mapped = mapped;
    }


    /**
     *  -ieplugin <clsid>Java Plugin classid for Internet Explorer
     *
     * @param  clsid  The new Ieplugin value
     */
    public void setIeplugin(String clsid) {
        this.ieplugin = clsid;
    }


    /**
     *  -sax2 <driverclassname>Driver class name for the SAX 2.0 parser to
     *  be used
     *
     * @param  sax2classname  The new Sax2Classname value
     */
    public void setSax2Classname(String sax2classname) {
        this.sax2classname = sax2classname;
    }


    /**
     *  flag to switch from flat to heirarchical output. controls -d / -dd
     *  switch
     *
     * @param  flatten  The new Flatten value
     * @return          Description of the Returned Value
     */
    public void setFlatten(boolean flatten) {
        this.flatten = flatten;
    }


    /**
     *  -webxml <file>Creates a complete web.xml when using the -webapp option.
     *
     * @param  webxml  The new Webxml value
     */
    public void setWebxml(File webxml) {
        this.webxml = webxml;
    }


    /**
     *  verbose flag; 
     *
     * @param  verbosity  The new Verbosity value
     */
    public void setVerbosity(int verbosity) {
        this.verbosity = verbosity;
    }


    /**
     *  -uribase. the uri context of relative URI 
     * references in the JSP pages. If it does not 
     * exist then it is derived from the location of the file
     * relative to the declared or derived value of -uriroot. 
     *
     * @param  uribase  The new Uribase value
     */
    public void setUribase(File uribase) {
        this.uribase = uribase;
    }


    /**
     *  -uriroot <dir>The root directory that uri files should be resolved
     *  against, (Default is the directory jspc is invoked from)
     *
     * @param  uriroot  The new Uribase value
     */
    public void setUriroot(File uriroot) {
        this.uriroot = uriroot;
    }


    /**
     *  set fail on error flag
     *
     * @param  b  The new FailOnError value
     */
    public void setFailOnError(boolean b) {
        this.failOnError = b;
    }

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

    /**
     *  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 for output
     *
     * @param  dirName  the directory classes go in to 
     */
    public void setDest(String dirName) {
        destinationDirectory = project.resolveFile(dirName);
    }

    /**
     *  Set the package under which the compiled classes go
     *
     * @param  packageName  the package name for the clases
     */
    public void setPackage(String packageName) {
        packageName = packageName;
    }


    /**
     *  validate parameters.
     *
     * @return                     true iff everything was ok. False means
     *      an error was printed and the task should bail out
     * @exception  BuildException  Description of Exception
     */
    public boolean validate() throws BuildException {
        String exceptionText=null;
        if(verbosity<0 || verbosity>4) 
            exceptionText="Verbosity out of range";
        //add new tests here.
        if(exceptionText != null) {
            if(failOnError) {
                throw new BuildException(exceptionText);
            } else {
                log(exceptionText);
            }
            return false;
        }
        return true;
    }

    /**
     *  execute method
     *
     * @exception  BuildException  trouble
     */
    public void execute() throws BuildException {

        //validate parameters. check for a graceful failure; buildexceptions
        //may be thrown instead
        if(!validate()) {
            return;
        }
        //reset the argument list for safety
        _arguments=new Vector();

        //apply basic args
        addArg("-ieplugin", ieplugin);
        addArg("-sax2classname", sax2classname);
        addArg("-c", classname);
        addArg("-uribase", uribase);
        addArg("-uriroot", uriroot);
        addArg("-p", packageName);
        addArg("-webinc", webinc);
        addArg("-webxml", webxml);
        if(mapped)
            addArg("-mapped");
        addArg("-v"+verbosity);
        addArg((flatten ? "-dd" : "-d"), destinationDirectory);
        //indicate that files come next
        //addArg("--");
        
        //now the source files
        int fileCount=0;
        if(sources!=null) {
            int size=sources.size();
            for(int i=0;i<size;i++) {
                FileSet fs = (FileSet) sources.elementAt(i);
                DirectoryScanner scanner = fs.getDirectoryScanner(project);
                String[] dependencies = scanner.getIncludedFiles();
                String baseDir=scanner.getBasedir().toString();
                //add to the command
                for (int j = 0; j < dependencies.length; j++) {
                    String targetFile=dependencies[j];
                    targetFile=baseDir+File.separator+targetFile;
                    log("+"+targetFile,Project.MSG_VERBOSE);
                    addArg(targetFile);
                    fileCount++;
                }
            } 
        }
        //now the web app directories
        int webAppCount=0;
        if(webApps!=null) {
            int size=webApps.size();
            webAppCount=size;
            for(int i=0;i<size;i++) {
                WebAppParameter webappParam=(WebAppParameter)webApps.elementAt(i);
                String dirName=webappParam.getDirectory();
                log("webapp "+dirName,Project.MSG_VERBOSE);
                File dir=project.resolveFile(dirName);
                if(!dir.isDirectory()) {
                    String s=dir.toString()+" is not a directory";
                    if(failOnError) 
                        throw new BuildException(s);
                    else {
                        log(s,Project.MSG_ERR);
                        //exceptions turned off? just exit the routine
                        return;
                    }
                }
                addArg("-webapp",dir);              
            }
        }        
        log("compiling "+fileCount+" file"+(fileCount!=1?"s":"")+" and "+
            webAppCount+ " webapp"+(webAppCount!=1?"s":""),
            Project.MSG_INFO);
        //turn the command line into a sized array 
        if(fileCount>0 || webAppCount>0) {
            int size=_arguments.size();
            String[] argv=new String[size];
            for(int i=0;i<size;i++) {
                argv[i]=(String)_arguments.elementAt(i);
                log(argv[i],Project.MSG_DEBUG);
            }
            //call the task
            runJspc(argv);
        }
     }

    /** despach jspc in the current JVM; classpath and things
     * can not be changed
     */
    public void runJspc(String[] argv) throws BuildException {
        try {
            //get an output stream
            LogOutputStream outstream=new LogOutputStream(this, Project.MSG_INFO);
            //then a printstream from that 
            PrintStream pw=new PrintStream(outstream);
            //and tack a printwriter from from that in ast the output channel for jasper logging
            Logger.setDefaultSink(new PrintWriter(pw));
            log("Creating JspC object",Project.MSG_DEBUG);
            JspC jspc = new JspC(argv, pw);
            log("calling JspC object",Project.MSG_DEBUG);
            jspc.parseFiles(pw);
        } catch (JasperException e) {
                log(e.getMessage(),Project.MSG_ERR);
                if(failOnError)
                    throw new BuildException(e);
        }
    }


    /**
     *  web apps
     *
     * @param  fs  add another web app fileset
     */
    public void addWebApp(WebAppParameter webappParam) {
        //demand create vector of filesets
        if(webApps == null) {
            webApps = new Vector();
        }
        webApps.add(webappParam);
    }


    /**
     *  source filesets
     *
     * @param  fs  add another source fileset
     */
    public void addSource(FileSet fs) {
        //demand create vector of filesets
        if(sources == null) {
            sources = new Vector();
        }
        sources.add(fs);
    }

    /**
     *  query fail on error flag
     *
     * @return    The FailFailOnError value
     */
    protected boolean getFailOnError() {
        return failOnError;
    }

    /**
     * list of arguments; needs to be initialised before addArg() is called
     */
    protected Vector _arguments;

    /**
     *  add an argument tuple to the argument list, if the value aint null
     *
     * @param  argument  The argument
     */
    protected void addArg(String argument) {
        if(argument != null && argument.length() != 0) {
           _arguments.add(argument);
        }
    }


    /**
     *  add an argument tuple to the argument list, if the value aint null
     *
     * @param  argument  The argument
     * @param  value     the parameter
     */
    protected void addArg(String argument, String value) {
        if(value!= null) {
            _arguments.add(argument);
            _arguments.add(value);
        }
    }

    /**
     *  add an argument tuple to the arg list, if the file parameter aint null
     *
     * @param  argument  The argument
     * @param  file     the parameter
     */
    protected void addArg(String argument, File file) {
        if(file != null) {
            _arguments.add(argument);
            _arguments.add(file.getAbsolutePath());
        }
    }

    /**
     *  name output file for the fraction of web.xml that lists
     *  servlets
     * @param  webinc  The new Webinc value
     */
    public void setWebinc(File webinc) {
        this.webinc = webinc;
    }
    
    /**
     * static inner class used as a parameter element
     */
    public static class WebAppParameter {
        
        /**
         * the sole option
         */
        private String directory;

        /**
         * query current directory
         */
         
        public String getDirectory() {
            return directory;
        }
        
    /**
     * set directory
     */
        public void setDirectory(String directory) {
            this.directory=directory;
        }
        
    /**
     * set directory; alternate syntax
     */
        public void setDir(String directory) {
            this.directory=directory;
        }
    //end inner class    
    }
    
//end jspc    
}

