/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2011 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 *
 *
 * This file incorporates work covered by the following copyright and
 * permission notice:
 *
 * Copyright 2004 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.jasper;

import java.io.BufferedReader;
import java.io.CharArrayWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.io.UnsupportedEncodingException;
// START GlassFish 750
import java.util.concurrent.ConcurrentHashMap;
// END GlassFish 750
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.logging.Logger;
import java.util.logging.Level;

// START GlassFish 750
import javax.servlet.jsp.tagext.TagLibraryInfo;
// END GlassFish 750

import org.apache.jasper.compiler.Compiler;
import org.apache.jasper.compiler.JspConfig;
import org.apache.jasper.compiler.JspRuntimeContext;
import org.apache.jasper.compiler.Localizer;
import org.apache.jasper.compiler.PageInfo;
import org.apache.jasper.compiler.TagPluginManager;
import org.apache.jasper.runtime.TldScanner;
import org.apache.jasper.servlet.JspCServletContext;
// START PWC 6386258
import org.apache.jasper.xmlparser.ParserUtils;
// END PWC 6386258

// START SJSAS 6258619
// XXX Remove the dependency on glassfish.webtier for now
// import com.sun.appserv.ClassLoaderUtil;
// END SJSAS 6258619

/**
 * Shell for the jspc compiler.  Handles all options associated with the
 * command line and creates compilation contexts which it then compiles
 * according to the specified options.
 *
 * This version can process files from a _single_ webapp at once, i.e.
 * a single docbase can be specified.
 *
 * It can be used as an Ant task using:
 * <pre>
 *   <taskdef classname="org.apache.jasper.JspC" name="jasper2" >
 *      <classpath>
 *          <pathelement location="${java.home}/../lib/tools.jar"/>
 *          <fileset dir="${ENV.CATALINA_HOME}/server/lib">
 *              <include name="*.jar"/>
 *          </fileset>
 *          <fileset dir="${ENV.CATALINA_HOME}/common/lib">
 *              <include name="*.jar"/>
 *          </fileset>
 *          <path refid="myjars"/>
 *       </classpath>
 *  </taskdef>
 *
 *  <jasper2 verbose="0"
 *           package="my.package"
 *           uriroot="${webapps.dir}/${webapp.name}"
 *           webXmlFragment="${build.dir}/generated_web.xml"
 *           outputDir="${webapp.dir}/${webapp.name}/WEB-INF/src/my/package" />
 * </pre>
 *
 * @author Danno Ferrin
 * @author Pierre Delisle
 * @author Costin Manolache
 */
public class JspC implements Options {

    public static final String DEFAULT_IE_CLASS_ID =
            "clsid:8AD9C840-044E-11D1-B3E9-00805F499D93";

    // START SJSAS 6402545
    private static final String JAVA_1_0 = "1.0";
    private static final String JAVA_1_1 = "1.1";
    private static final String JAVA_1_2 = "1.2";
    private static final String JAVA_1_3 = "1.3";
    private static final String JAVA_1_4 = "1.4";
    private static final String JAVA_1_5 = "1.5";
    private static final String JAVA_1_6 = "1.6";
    private static final String JAVA_1_7 = "1.7";
    private static final String JAVA_1_8 = "1.8";
    private static final String JAVA_5 = "5";
    private static final String JAVA_6 = "6";
    private static final String JAVA_7 = "7";
    private static final String JAVA_8 = "8";
    // END SJSAS 6402545

    // Logger
    private static Logger log = Logger.getLogger(JspC.class.getName());

    private static final String SWITCH_VERBOSE = "-v";
    private static final String SWITCH_HELP = "-help";
    private static final String SWITCH_QUIET = "-q";
    private static final String SWITCH_OUTPUT_DIR = "-d";
    private static final String SWITCH_IE_CLASS_ID = "-ieplugin";
    private static final String SWITCH_PACKAGE_NAME = "-p";
    private static final String SWITCH_CLASS_NAME = "-c";
    private static final String SWITCH_FULL_STOP = "--";
    private static final String SWITCH_COMPILE = "-compile";
    private static final String SWITCH_SOURCE = "-compilerSourceVM";
    private static final String SWITCH_TARGET = "-compilerTargetVM";
    private static final String SWITCH_URI_BASE = "-uribase";
    private static final String SWITCH_URI_ROOT = "-uriroot";
    private static final String SWITCH_FILE_WEBAPP = "-webapp";
    private static final String SWITCH_WEBAPP_INC = "-webinc";
    private static final String SWITCH_WEBAPP_XML = "-webxml";
    private static final String SWITCH_MAPPED = "-mapped";
    private static final String SWITCH_XPOWERED_BY = "-xpoweredBy";
    private static final String SWITCH_TRIM_SPACES = "-trimSpaces";
    private static final String SWITCH_CLASSPATH = "-classpath";
    private static final String SWITCH_SYSCLASSPATH = "-sysClasspath";
    private static final String SWITCH_DIE = "-die";
    private static final String SWITCH_SMAP = "-smap";
    private static final String SWITCH_DUMP_SMAP = "-dumpsmap";
    // START PWC 6386258
    private static final String SWITCH_SCHEMAS_PREFIX = "-schemas";
    private static final String SWITCH_DTDS_PREFIX = "-dtds";
    // END PWC 6386258
    // START IASRI 4660687
    private static final String SWITCH_GENERATE_CLASSES = "-genclass";
    // END IASRI 4660687
    // START PWC 6385018
    private static final String SWITCH_VALIDATE = "-validate";
    // END PWC 6385018
    // START SJSAS 6393940
    private static final String SWITCH_IGNORE_JSP_FRAGMENTS
        = "-ignoreJspFragmentErrors";
    // END SJSAS 6393940
    private static final String SWITCH_DISABLE_POOLING = "-disablePooling";

    private static final String SHOW_SUCCESS ="-s";
    private static final String LIST_ERRORS = "-l";
    private static final int NO_WEBXML = 0;
    private static final int INC_WEBXML = 10;
    private static final int ALL_WEBXML = 20;
    private static final int DEFAULT_DIE_LEVEL = 1;
    private static final int NO_DIE_LEVEL = 0;

    private static final String[] insertBefore =
    { "</web-app>", "<servlet-mapping>", "<session-config>",
      "<mime-mapping>", "<welcome-file-list>", "<error-page>", "<taglib>",
      "<resource-env-ref>", "<resource-ref>", "<security-constraint>",
      "<login-config>", "<security-role>", "<env-entry>", "<ejb-ref>",
      "<ejb-local-ref>" };

    private int dieLevel;
    private String classPath = null;
    // START PWC 1.2 6311155
    private String sysClassPath = null;
    // END PWC 1.2 6311155
    private URLClassLoader loader = null;
    private boolean trimSpaces = false;
    private boolean genStringAsCharArray = false;
    private boolean genStringAsByteArray = true;
    private boolean defaultBufferNone = false;
    private boolean xpoweredBy;
    private boolean mappedFile = false;
    private boolean poolingEnabled = true;
    private File scratchDir;
    private String ieClassId = DEFAULT_IE_CLASS_ID;
    private String targetPackage;
    private String targetClassName;
    private String uriBase;
    private String uriRoot;
    private boolean helpNeeded = false;
    private boolean compile = false;
    private boolean smapSuppressed = true;
    private boolean smapDumped = false;

    private String compiler = null;

    private String compilerTargetVM = JAVA_1_5;
    private String compilerSourceVM = JAVA_1_5;

    private boolean classDebugInfo = true;

    /**
     * Throw an exception if there's a compilation error, or swallow it.
     * Default is true to preserve old behavior.
     */
    private boolean failOnError = true;

    private ArrayList<String> extensions;
    private ArrayList<String> pages = new ArrayList<String>();
    private boolean errorOnUseBeanInvalidClassAttribute = false;

    /**
     * The java file encoding.  Default
     * is UTF-8.  Added per bugzilla 19622.
     */
    private String javaEncoding = "UTF-8";

    // Generation of web.xml fragments
    private String webxmlFile;
    private int webxmlLevel;
    private boolean addWebXmlMappings = false;

    private Writer mapout;
    private CharArrayWriter servletout;
    private CharArrayWriter mappingout;

    private JspCServletContext context;

    // Maintain a dummy JspRuntimeContext for compiling tag files
    private JspRuntimeContext rctxt;

    /**
     * Cache for the TLD locations
     */
    private TldScanner tldScanner = null;

    private JspConfig jspConfig = null;
    private TagPluginManager tagPluginManager = null;

    private boolean verbose = false;
    private boolean listErrors = false;
    private boolean showSuccess = false;
    private int argPos;
    private boolean fullstop = false;
    private String args[];

    // START SJSAS 6384538
    private boolean isValidationEnabled;
    // END SJSAS 6384538

    // START SJSAS 6329723
    private HashMap<String,JasperException> jspErrors
        = new HashMap<String,JasperException>();
    // END SJSAS 6329723

    // START SJSAS 6403017
    private static String myJavaVersion =
        System.getProperty("java.specification.version");
    // END SJSAS 6403017

    // START SJSAS 6393940
    private boolean ignoreJspFragmentErrors = false;
    private Set<String> dependents = new HashSet<String>();
    // END SJSAS 6393940

    // START GlassFish 750
    private ConcurrentHashMap<String, TagLibraryInfo> taglibs;
    private ConcurrentHashMap<String, URL> tagFileJarUrls;
    // END GlassFish 750

    public static void main(String arg[]) {
        if (arg.length == 0) {
           System.out.println(Localizer.getMessage("jspc.usage"));
        } else {
            JspC jspc = new JspC();
            try {
                jspc.setArgs(arg);
                if (jspc.helpNeeded) {
                    System.out.println(Localizer.getMessage("jspc.usage"));
                } else {
                    jspc.execute();
                }
            } catch (JasperException je) {
                System.err.println(je);
                //System.err.println(je.getMessage());
                if (jspc.getDieLevel() != NO_DIE_LEVEL) {
                    System.exit(jspc.getDieLevel());
                }
            }
        }
    }

    public void setArgs(String[] arg) throws JasperException {
        args = arg;
        String tok;

        dieLevel = NO_DIE_LEVEL;

        while ((tok = nextArg()) != null) {
            if (tok.equals(SWITCH_VERBOSE)) {
                verbose = true;
                showSuccess = true;
                listErrors = true;
            } else if (tok.equals(SWITCH_OUTPUT_DIR)) {
                tok = nextArg();
                setOutputDir( tok );
            } else if (tok.equals(SWITCH_PACKAGE_NAME)) {
                targetPackage = nextArg();
            } else if (tok.equals(SWITCH_COMPILE)) {
                compile=true;
            } else if (tok.equals(SWITCH_CLASS_NAME)) {
                targetClassName = nextArg();
            } else if (tok.equals(SWITCH_URI_BASE)) {
                uriBase=nextArg();
            } else if (tok.equals(SWITCH_URI_ROOT)) {
                setUriroot( nextArg());
            // START IASRI 4660687
            } else if (tok.equals(SWITCH_GENERATE_CLASSES)) {
                compile = true;
            // END IASRI 4660687
            } else if (tok.equals(SWITCH_FILE_WEBAPP)) {
                setUriroot( nextArg());
            } else if ( tok.equals( SHOW_SUCCESS ) ) {
                showSuccess = true;
            } else if ( tok.equals( LIST_ERRORS ) ) {
                listErrors = true;
            } else if (tok.equals(SWITCH_WEBAPP_INC)) {
                webxmlFile = nextArg();
                if (webxmlFile != null) {
                    webxmlLevel = INC_WEBXML;
                }
            } else if (tok.equals(SWITCH_WEBAPP_XML)) {
                webxmlFile = nextArg();
                if (webxmlFile != null) {
                    webxmlLevel = ALL_WEBXML;
                }
            } else if (tok.equals(SWITCH_MAPPED)) {
                mappedFile = true;
            } else if (tok.equals(SWITCH_XPOWERED_BY)) {
                xpoweredBy = true;
            } else if (tok.equals(SWITCH_TRIM_SPACES)) {
                setTrimSpaces(true);
            } else if (tok.equals(SWITCH_CLASSPATH)) {
                setClassPath(nextArg());
            } else if (tok.equals(SWITCH_SYSCLASSPATH)) {
                setSystemClassPath(nextArg());
            } else if (tok.startsWith(SWITCH_DIE)) {
                try {
                    dieLevel = Integer.parseInt(
                        tok.substring(SWITCH_DIE.length()));
                } catch (NumberFormatException nfe) {
                    dieLevel = DEFAULT_DIE_LEVEL;
                }
            } else if (tok.equals(SWITCH_HELP)) {
                helpNeeded = true;
            } else if (tok.equals(SWITCH_SOURCE)) {
                setCompilerSourceVM(nextArg());
            } else if (tok.equals(SWITCH_TARGET)) {
                setCompilerTargetVM(nextArg());
            } else if (tok.equals(SWITCH_SMAP)) {
                smapSuppressed = false;
            } else if (tok.equals(SWITCH_DUMP_SMAP)) {
                smapDumped = true;
                smapSuppressed = false;
            // START PWC 6386258
            } else if (tok.equals(SWITCH_SCHEMAS_PREFIX)) {
                setSchemaResourcePrefix(nextArg());
            } else if (tok.equals(SWITCH_DTDS_PREFIX)) {
                setDtdResourcePrefix(nextArg());
            // END PWC 6386258
            // START PWC 6385018
            } else if (tok.equals(SWITCH_VALIDATE)) {
                setValidateXml(true);
            // END PWC 6385018
            // START SJSAS 6393940
            } else if (tok.equals(SWITCH_IGNORE_JSP_FRAGMENTS)) {
                setIgnoreJspFragmentErrors(true);
            // END SJSAS 6393940
            } else if (tok.equals(SWITCH_DISABLE_POOLING)) {
                setPoolingEnabled(false);
            } else {
                if (tok.startsWith("-")) {
                    throw new JasperException("Unrecognized option: " + tok +
                        ".  Use -help for help.");
                }
                if (!fullstop) {
                    argPos--;
                }
                // Start treating the rest as JSP Pages
                break;
            }
        }

        // Add all extra arguments to the list of files
        while( true ) {
            String file = nextFile();
            if( file==null ) break;
            pages.add( file );
        }
    }

    public int getDieLevel() {
        return dieLevel;
    }

    public boolean getKeepGenerated() {
        // isn't this why we are running jspc?
        return true;
    }

    public boolean getSaveBytecode() {
        return true;
    }

    public boolean getTrimSpaces() {
        return trimSpaces;
    }

    public void setTrimSpaces(boolean ts) {
        this.trimSpaces = ts;
    }

    public boolean isPoolingEnabled() {
        return poolingEnabled;
    }

    public void setPoolingEnabled(boolean poolingEnabled) {
        this.poolingEnabled = poolingEnabled;
    }

    public boolean isXpoweredBy() {
        return xpoweredBy;
    }

    public void setXpoweredBy(boolean xpoweredBy) {
        this.xpoweredBy = xpoweredBy;
    }

    public boolean getErrorOnUseBeanInvalidClassAttribute() {
        return errorOnUseBeanInvalidClassAttribute;
    }

    public void setErrorOnUseBeanInvalidClassAttribute(boolean b) {
        errorOnUseBeanInvalidClassAttribute = b;
    }

    public int getTagPoolSize() {
	return Constants.MAX_POOL_SIZE;
    }

    // START SJSWS
    /**
     * Gets initial capacity of HashMap which maps JSPs to their corresponding
     * servlets.
     */
    public int getInitialCapacity() {
        return Constants.DEFAULT_INITIAL_CAPACITY;
    }
    // END SJSWS

    /**
     * Are we supporting HTML mapped servlets?
     */
    public boolean getMappedFile() {
	return mappedFile;
    }

    // Off-line compiler, no need for security manager
    public Object getProtectionDomain() {
	return null;
    }

    public boolean getSendErrorToClient() {
        // implied send to System.err
        return true;
    }

    public void setClassDebugInfo( boolean b ) {
        classDebugInfo=b;
    }

    public boolean getClassDebugInfo() {
        // compile with debug info
        return classDebugInfo;
    }

    /**
     * Background compilation check intervals in seconds
     */
    public int getCheckInterval() {
        return 0;
    }

    /**
     * Modification test interval.
     */
    public int getModificationTestInterval() {
        return 0;
    }

    /**
     * Is Jasper being used in development mode?
     */
    public boolean getDevelopment() {
        return false;
    }

    // BEGIN S1AS 6181923
    public boolean getUsePrecompiled() {
        return false;
    }
    // END S1AS 6181923

    /**
     * Is the generation of SMAP info for JSR45 debugging suppressed?
     */
    public boolean isSmapSuppressed() {
        return smapSuppressed;
    }

    /**
     * Set smapSuppressed flag.
     */
    public void setSmapSuppressed(boolean smapSuppressed) {
        this.smapSuppressed = smapSuppressed;
    }

    /**
     * Should SMAP info for JSR45 debugging be dumped to a file?
     */
    public boolean isSmapDumped() {
        return smapDumped;
    }

    /**
     * Set smapSuppressed flag.
     */
    public void setSmapDumped(boolean smapDumped) {
        this.smapDumped = smapDumped;
    }

    /**
     * Determines whether text strings are to be generated as char arrays,
     * which improves performance in some cases.
     *
     * @param genStringAsCharArray true if text strings are to be generated as
     * char arrays, false otherwise
     */
    public void setGenStringAsCharArray(boolean genStringAsCharArray) {
        this.genStringAsCharArray = genStringAsCharArray;
    }

    /**
     * Indicates whether text strings are to be generated as char arrays.
     *
     * @return true if text strings are to be generated as char arrays, false
     * otherwise
     */
    public boolean genStringAsCharArray() {
        return genStringAsCharArray;
    }

    public void setGenStringAsByteArray(boolean genStringAsByteArray) {
        this.genStringAsByteArray = genStringAsByteArray;
    }

    public boolean genStringAsByteArray() {
        return genStringAsByteArray;
    }

    public boolean isDefaultBufferNone() {
        return defaultBufferNone;
    }

    public void setDefaultBufferNone(boolean defaultBufferNone) {
        this.defaultBufferNone = defaultBufferNone;
    }

    /**
     * Sets the class-id value to be sent to Internet Explorer when using
     * <jsp:plugin> tags.
     *
     * @param ieClassId Class-id value
     */
    public void setIeClassId(String ieClassId) {
        this.ieClassId = ieClassId;
    }

    /**
     * Gets the class-id value that is sent to Internet Explorer when using
     * <jsp:plugin> tags.
     *
     * @return Class-id value
     */
    public String getIeClassId() {
        return ieClassId;
    }

    public File getScratchDir() {
        return scratchDir;
    }

    public Class getJspCompilerPlugin() {
       // we don't compile, so this is meanlingless
        return null;
    }

    public String getJspCompilerPath() {
       // we don't compile, so this is meanlingless
        return null;
    }

    /**
     * Compiler to use.
     */
    public String getCompiler() {
        return compiler;
    }

    public void setCompiler(String c) {
        compiler=c;
    }

    /**
     * @see Options#getCompilerTargetVM
     */
    public String getCompilerTargetVM() {
        return compilerTargetVM;
    }

    public void setCompilerTargetVM(String vm) {
        // START SJSAS 6402545
        String tvm = vm;
        if (JAVA_5.equals(vm)) {
            vm = JAVA_1_5;
        } else if (JAVA_6.equals(vm)) {
            vm = JAVA_1_6;
        } else if (JAVA_7.equals(vm)) {
            vm = JAVA_1_7;
        } else if (JAVA_8.equals(vm)) {
            vm = JAVA_1_8;
        }
        if (!JAVA_1_1.equals(vm) && !JAVA_1_2.equals(vm)
                && !JAVA_1_3.equals(vm) && !JAVA_1_4.equals(vm)
                && !JAVA_1_5.equals(vm) && !JAVA_1_6.equals(vm)
                && !JAVA_1_7.equals(vm) && !JAVA_1_8.equals(vm)){
            throw new IllegalArgumentException(
                Localizer.getMessage("jspc.illegalCompilerTargetVM", tvm));
        }
        // END SJSAS 6402545
        // START SJSAS 6403017
        Double targetVersion = Double.valueOf(vm);
        if (targetVersion.compareTo(Double.valueOf(myJavaVersion)) > 0) {
            throw new IllegalArgumentException(
                Localizer.getMessage("jspc.compilerTargetVMTooHigh", vm));
        }
        // END SJSAS 6403017
        compilerTargetVM = vm;
    }

    /**
     * @see Options#getCompilerSourceVM.
     */
     public String getCompilerSourceVM() {
         return compilerSourceVM;
     }

    /**
     * @see Options#getCompilerSourceVM.
     */
    public void setCompilerSourceVM(String vm) {
        // START SJSAS 6402545
        if (!JAVA_1_3.equals(vm) && !JAVA_1_4.equals(vm)
                && !JAVA_1_5.equals(vm) && !JAVA_5.equals(vm)
                && !JAVA_1_6.equals(vm) && !JAVA_6.equals(vm)
                && !JAVA_1_7.equals(vm) && !JAVA_7.equals(vm)
                && !JAVA_1_8.equals(vm) && !JAVA_8.equals(vm)) {
            throw new IllegalArgumentException(
                Localizer.getMessage("jspc.illegalCompilerSourceVM", vm));
        }
        // END SJSAS 6402545
        compilerSourceVM = vm;
    }

    public TldScanner getTldScanner() {
	return tldScanner;
    }

    /**
     * Returns the encoding to use for
     * java files.  The default is UTF-8.
     *
     * @return String The encoding
     */
    public String getJavaEncoding() {
	return javaEncoding;
    }

    /**
     * Sets the encoding to use for
     * java files.
     *
     * @param encodingName The name, e.g. "UTF-8"
     */
    public void setJavaEncoding(String encodingName) {
      javaEncoding = encodingName;
    }

    public boolean getFork() {
        return false;
    }

    public String getClassPath() {
        if( classPath != null )
            return classPath;
        /* PWC 1.2 6311155
        return System.getProperty("java.class.path");
        */
        // START PWC 1.2 6311155
        return "";
        // END PWC 1.2 6311155
    }

    public void setClassPath(String s) {
        classPath=s;
    }

    // START PWC 1.2 6311155
    /**
     * Gets the system class path.
     *
     * @return The system class path
     */
    public String getSystemClassPath() {
        if (sysClassPath != null) {
            return sysClassPath;
        } else {
            return System.getProperty("java.class.path");
        }
    }

    /**
     * Sets the system class path.
     *
     * @param s The system class path to use
     */
    public void setSystemClassPath(String s) {
        sysClassPath = s;
    }
    // END PWC 1.2 6311155

    /**
     * Base dir for the webapp. Used to generate class names and resolve
     * includes
     */
    public void setUriroot( String s ) {
        uriRoot = s;
        if (s != null) {
            try {
                uriRoot=new File( s ).getCanonicalPath();
            } catch( Exception ex ) {
                uriRoot=s;
            }
        }
    }

    // START PWC 6386258
    /**
     * Sets the path prefix for .xsd resources
     */
    public static void setSchemaResourcePrefix(String prefix) {
        ParserUtils.setSchemaResourcePrefix(prefix);
    }

    /**
     * Sets the path prefix for .dtd resources
     */
    public static void setDtdResourcePrefix(String prefix) {
        ParserUtils.setDtdResourcePrefix(prefix);
    }
    // END PWC 6386258

    /*
     * Parses comma-separated list of JSP files to be processed.
     *
     * <p>Each file is interpreted relative to uriroot, unless it is absolute,
     * in which case it must start with uriroot.
     *
     * @param jspFiles Comma-separated list of JSP files to be processed
     */
    public void setJspFiles(String jspFiles) {
        StringTokenizer tok = new StringTokenizer(jspFiles, " ,");
        while (tok.hasMoreTokens()) {
            pages.add(tok.nextToken());
        }
    }

    public void setCompile( boolean b ) {
        compile=b;
    }

    public void setVerbose( int level ) {
        if (level > 0) {
            verbose = true;
            showSuccess = true;
            listErrors = true;
        }
    }

    public void setValidateXml( boolean b ) {
        /* SJSAS 6384538
        org.apache.jasper.xmlparser.ParserUtils.validating=b;
        */
        // START SJSAS 6384538
        setIsValidationEnabled(b);
        // END SJSAS 6384538
    }

    // START SJSAS 6384538
    public void setIsValidationEnabled(boolean b) {
        isValidationEnabled = b;
    }

    public boolean isValidationEnabled() {
        return isValidationEnabled;
    }
    // END SJSAS 6384538

    public void setListErrors( boolean b ) {
        listErrors = b;
    }

    public void setOutputDir( String s ) {
        if( s!= null ) {
            scratchDir = new File(s).getAbsoluteFile();
        } else {
            scratchDir=null;
        }
    }

    public void setPackage( String p ) {
        targetPackage=p;
    }

    /**
     * Class name of the generated file ( without package ).
     * Can only be used if a single file is converted.
     * XXX Do we need this feature ?
     */
    public void setClassName( String p ) {
        targetClassName=p;
    }

    /**
     * File where we generate a web.xml fragment with the class definitions.
     */
    public void setWebXmlFragment( String s ) {
        webxmlFile=s;
        webxmlLevel=INC_WEBXML;
    }

    /**
     * File where we generate a complete web.xml with the class definitions.
     */
    public void setWebXml( String s ) {
        webxmlFile=s;
        webxmlLevel=ALL_WEBXML;
    }

    public void setAddWebXmlMappings(boolean b) {
        addWebXmlMappings = b;
    }

    /**
     * Set the option that throws an exception in case of a compilation
     * error.
     */
    public void setFailOnError(final boolean b) {
        failOnError = b;
    }

    public boolean getFailOnError() {
        return failOnError;
    }

    // START SJSAS 6393940
    public void setIgnoreJspFragmentErrors(boolean ignore) {
        ignoreJspFragmentErrors = ignore;
    }
    // END SJSAS 6393940

    /**
     * Obtain JSP configuration informantion specified in web.xml.
     */
    public JspConfig getJspConfig() {
	return jspConfig;
    }

    public TagPluginManager getTagPluginManager() {
        return tagPluginManager;
    }

    public void generateWebMapping( String file, JspCompilationContext clctxt )
        throws IOException
    {
        String className = clctxt.getServletClassName();
        String packageName = clctxt.getServletPackageName();

        String thisServletName;
        if  ("".equals(packageName)) {
            thisServletName = className;
        } else {
            thisServletName = packageName + '.' + className;
        }

        if (servletout != null) {
            servletout.write("\n    <servlet>\n        <servlet-name>");
            servletout.write(thisServletName);
            servletout.write("</servlet-name>\n        <servlet-class>");
            servletout.write(thisServletName);
            servletout.write("</servlet-class>\n    </servlet>\n");
        }
        if (mappingout != null) {
            mappingout.write("\n    <servlet-mapping>\n        <servlet-name>");
            mappingout.write(thisServletName);
            mappingout.write("</servlet-name>\n        <url-pattern>");
            mappingout.write(file.replace('\\', '/'));
            mappingout.write("</url-pattern>\n    </servlet-mapping>\n");

        }
    }

    // START SJSAS 6329723
    /**
     * Gets the list of JSP compilation errors caught during the most recent
     * invocation of this instance's <code>execute</code> method when
     * failOnError has been set to FALSE.
     *
     * Each error error in the list is represented by an instance of
     * org.apache.jasper.JasperException.
     *
     * @return List of JSP compilation errors caught during most recent
     * invocation of this instance's <code>execute</code> method, or an empty
     * list if no errors were encountered or this instance's failOnError
     * property was set to TRUE
     */
    public List<JasperException> getJSPCompilationErrors() {

        ArrayList<JasperException> ret = null;

        Collection<JasperException> c = jspErrors.values();
        if (c != null) {
            ret = new ArrayList<JasperException>();
            Iterator<JasperException> it = c.iterator();
            while (it.hasNext()) {
                ret.add(it.next());
            }
        }

        return ret;
    }
    // END SJSAS 6329723

    /**
     * Include the generated web.xml inside the webapp's web.xml.
     */
    protected void mergeIntoWebXml() throws IOException {

        File webappBase = new File(uriRoot);
        File webXml = new File(webappBase, "WEB-INF/web.xml");
        File webXml2 = new File(webappBase, "WEB-INF/web2.xml");
        String insertStartMarker =
            Localizer.getMessage("jspc.webinc.insertStart");
        String insertEndMarker =
            Localizer.getMessage("jspc.webinc.insertEnd");

        BufferedReader reader = new BufferedReader(
                    new InputStreamReader(new FileInputStream(webXml),"UTF-8"));
        BufferedReader fragmentReader = new BufferedReader(
                    new InputStreamReader(new FileInputStream(webxmlFile),"UTF-8"));
        PrintWriter writer = new PrintWriter(
                    new OutputStreamWriter(new FileOutputStream(webXml2),"UTF-8"));

        // Insert the <servlet> and <servlet-mapping> declarations
        int pos = -1;
        String line = null;
        while (true) {
            line = reader.readLine();
            if (line == null) {
                break;
            }
            // Skip anything previously generated by JSPC
            if (line.indexOf(insertStartMarker) >= 0) {
                while (true) {
                    line = reader.readLine();
                    if (line == null) {
                        return;
                    }
                    if (line.indexOf(insertEndMarker) >= 0) {
                        line = reader.readLine();
                        if (line == null) {
                            return;
                        }
                        break;
                    }
                }
            }
            for (int i = 0; i < insertBefore.length; i++) {
                pos = line.indexOf(insertBefore[i]);
                if (pos >= 0)
                    break;
            }
            if (pos >= 0) {
                writer.println(line.substring(0, pos));
                break;
            } else {
                writer.println(line);
            }
        }

        writer.println(insertStartMarker);
        while (true) {
            String line2 = fragmentReader.readLine();
            if (line2 == null) {
                writer.println();
                break;
            }
            writer.println(line2);
        }
        writer.println(insertEndMarker);
        writer.println();

        for (int i = 0; i < pos; i++) {
            writer.print(" ");
        }
        if (line != null) {
            writer.println(line.substring(pos));
        }

        while (true) {
            line = reader.readLine();
            if (line == null) {
                break;
            }
            writer.println(line);
        }
        writer.close();

        reader.close();
        fragmentReader.close();

        FileInputStream fis = new FileInputStream(webXml2);
        FileOutputStream fos = new FileOutputStream(webXml);

        byte buf[] = new byte[512];

        try {
            while (true) {
                int n = fis.read(buf);
                if (n < 0) {
                    break;
                }
                fos.write(buf, 0, n);
            }
        } finally {
            if (fis != null) {
                fis.close();
            }
            if (fos != null) {
                fos.close();
            }
        }

        webXml2.delete();
        (new File(webxmlFile)).delete();

    }

    private void processFile(String file)
        throws JasperException
    {
        ClassLoader originalClassLoader = null;
        String jspUri=file.replace('\\','/');

        try {
            // set up a scratch/output dir if none is provided
            if (scratchDir == null) {
                String temp = System.getProperty("java.io.tmpdir");
                if (temp == null) {
                    temp = "";
                }
                scratchDir = new File(new File(temp).getAbsolutePath());
            }

            JspCompilationContext clctxt = new JspCompilationContext
                ( jspUri, false,  this, context, null, rctxt );

            /* Override the defaults */
            if ((targetClassName != null) && (targetClassName.length() > 0)) {
                clctxt.setServletClassName(targetClassName);
                targetClassName = null;
            }
            if (targetPackage != null) {
                clctxt.setServletPackageName(targetPackage);
            }

            originalClassLoader = Thread.currentThread().getContextClassLoader();
            if( loader==null ) {
                initClassLoader( clctxt );
            }
            Thread.currentThread().setContextClassLoader(loader);

            clctxt.setClassLoader(loader);
            clctxt.setClassPath(classPath);

            Compiler clc = clctxt.createCompiler(true);

            // If compile is set, generate both .java and .class, if
            // .jsp file is newer than .class file;
            // Otherwise only generate .java, if .jsp file is newer than
            // the .java file
            if( clc.isOutDated(compile) ) {
                clc.compile(compile);
            }

            // START SJSAS 6393940
            if (ignoreJspFragmentErrors) {
                PageInfo pi = clc.getPageInfo();
                if (pi != null) {
                    List<String> deps = pi.getDependants();
                    if (deps != null) {
                        Iterator<String> it = deps.iterator();
                        if (it != null) {
                            while (it.hasNext()) {
                                dependents.add(it.next());
                            }
                        }
                    }
                    clc.setPageInfo(null);
                }
	    }
            // END SJSAS 6393940

            // Generate mapping
            generateWebMapping( file, clctxt );
            if ( showSuccess ) {
                log.info( "Built File: " + file );
            }

        } catch (JasperException je) {
            Throwable rootCause = je;
            while (rootCause instanceof JasperException
                    && ((JasperException) rootCause).getRootCause() != null) {
                rootCause = ((JasperException) rootCause).getRootCause();
            }
            if (listErrors && rootCause != je) {
                log.log(Level.SEVERE,
                    Localizer.getMessage("jspc.error.generalException", file),
                    rootCause);
            }

            // Bugzilla 35114.
            if (getFailOnError() && !ignoreJspFragmentErrors) {
                throw je;
            } else {
                if (listErrors && !ignoreJspFragmentErrors) {
                    log.severe(je.getMessage());
                }
                // START SJAS 6329723
                jspErrors.put(jspUri, je);
                // END SJSAS 6329723
            }

        } catch (Exception e) {
            if ((e instanceof FileNotFoundException) &&
                        log.isLoggable(Level.WARNING)) {
                log.warning(Localizer.getMessage("jspc.error.fileDoesNotExist",
                                                  e.getMessage()));
            }
            throw new JasperException(e);
        } finally {
            if(originalClassLoader != null) {
                Thread.currentThread().setContextClassLoader(originalClassLoader);
            }
        }

    }

    /**
     * Locate all jsp files in the webapp. Used if no explicit
     * jsps are specified.
     */
    public void scanFiles( File base ) throws JasperException {
        Stack<String> dirs = new Stack<String>();
        dirs.push(base.toString());
        if (extensions == null) {
            extensions = new ArrayList<String>();
            extensions.add("jsp");
            extensions.add("jspx");
        }
        while (!dirs.isEmpty()) {
            String s = dirs.pop();
            File f = new File(s);
            if (f.exists() && f.isDirectory()) {
                String[] files = f.list();
                String ext;
                for (int i = 0; (files != null) && i < files.length; i++) {
                    File f2 = new File(s, files[i]);
                    if (f2.isDirectory()) {
                        dirs.push(f2.getPath());
                    } else {
                        String path = f2.getPath();
                        String uri = path.substring(uriRoot.length());
                        ext = files[i].substring(files[i].lastIndexOf('.') +1);
                        if (extensions.contains(ext) ||
                                jspConfig.isJspPage(uri)) {
                            pages.add(path);
                        }
                    }
                }
            }
        }
    }

    public void execute() throws JasperException {

        // START SJSAS 6329723
        jspErrors.clear();
        // END SJSAS 6329723
        // START SJSAS 6393940
        dependents.clear();
        // END SJSAS 6393940

        try {
	    if (uriRoot == null) {
		if( pages.size() == 0 ) {
		    throw new JasperException(
                        Localizer.getMessage("jsp.error.jspc.missingTarget"));
		}
		String firstJsp=pages.get( 0 );
                File firstJspF = new File( firstJsp );
                if (!firstJspF.exists()) {
                   throw new JasperException(
                       Localizer.getMessage("jspc.error.fileDoesNotExist",
                                            firstJsp));
                }
                locateUriRoot( firstJspF );
	    }

            if (uriRoot == null) {
		throw new JasperException(
                    Localizer.getMessage("jsp.error.jspc.no_uriroot"));
	    }

	    if( context==null )
		initServletContext();

	    // No explicit pages, we'll process all .jsp in the webapp
	    if (pages.size() == 0) {
		scanFiles( new File( uriRoot ));
	    }

	    File uriRootF = new File(uriRoot);
	    if (!uriRootF.exists() || !uriRootF.isDirectory()) {
		throw new JasperException(
                    Localizer.getMessage("jsp.error.jspc.uriroot_not_dir"));
	    }

	    initWebXml();

            for (String nextjsp: pages) {
                File fjsp = new File(nextjsp);
                if (!fjsp.isAbsolute()) {
                    fjsp = new File(uriRootF, nextjsp);
                }
                if (!fjsp.exists()) {
                    if (log.isLoggable(Level.WARNING)) {
                        log.warning(Localizer.getMessage
                            ("jspc.error.fileDoesNotExist", fjsp.toString()));
                    }
                    continue;
                }
                String s = fjsp.getAbsolutePath();
                if (s.startsWith(uriRoot)) {
                    nextjsp = s.substring(uriRoot.length());
                }
                if (nextjsp.startsWith("." + File.separatorChar)) {
                    nextjsp = nextjsp.substring(2);
                }
		processFile(nextjsp);
	    }

            // START SJSAS 6393940
            if (ignoreJspFragmentErrors) {
                purgeJspFragmentErrors();
            }
            if (getFailOnError() && !jspErrors.isEmpty()) {
                throw jspErrors.values().iterator().next();
            }
            // END SJJAS 6393940

	    completeWebXml();

            if (addWebXmlMappings) {
                mergeIntoWebXml();
            }

        } catch (IOException ioe) {
            throw new JasperException(ioe);

        } catch (JasperException je) {
            Throwable rootCause = je;
            while (rootCause instanceof JasperException
                    && ((JasperException) rootCause).getRootCause() != null) {
                rootCause = ((JasperException) rootCause).getRootCause();
            }
            if (rootCause != je) {
                rootCause.printStackTrace();
            }
            throw je;
        } finally {
            // START S1AS 5032338
            if (loader != null) {
                // XXX APACHE-COMMONS-LOGGING-PATCH
                // LogFactory.release(loader);
                // START SJSAS 6258619
                // ClassLoaderUtil.releaseLoader(loader);
                // END SJSAS 6258619
            }
            // END S1AS 5032338
            // START SJSAS 6356052
            if (rctxt != null) {
                rctxt.destroy();
            }
            // END SJSAS 6356052

            // START GlassFish 750
            if (taglibs != null) {
                taglibs.clear();
            }
            if (tagFileJarUrls != null) {
                tagFileJarUrls.clear();
            }
            // END GlassFish 750
        }
    }

    // ==================== Private utility methods ====================

    private String nextArg() {
        if ((argPos >= args.length)
            || (fullstop = SWITCH_FULL_STOP.equals(args[argPos]))) {
            return null;
        } else {
            return args[argPos++];
        }
    }

    private String nextFile() {
        if (fullstop) argPos++;
        if (argPos >= args.length) {
            return null;
        } else {
            return args[argPos++];
        }
    }

    private void initWebXml() {
        try {
            if (webxmlLevel >= INC_WEBXML) {
                File fmapings = new File(webxmlFile);
                mapout = new OutputStreamWriter(new FileOutputStream(fmapings),"UTF-8");
                servletout = new CharArrayWriter();
                mappingout = new CharArrayWriter();
            } else {
                mapout = null;
                servletout = null;
                mappingout = null;
            }
            if (webxmlLevel >= ALL_WEBXML) {
                mapout.write(Localizer.getMessage("jspc.webxml.header"));
                mapout.flush();
            } else if ((webxmlLevel>= INC_WEBXML) && !addWebXmlMappings) {
                mapout.write(Localizer.getMessage("jspc.webinc.header"));
                mapout.flush();
            }
        } catch (IOException ioe) {
            mapout = null;
            servletout = null;
            mappingout = null;
        }
    }

    private void completeWebXml() {
        if (mapout != null) {
            try {
                servletout.writeTo(mapout);
                mappingout.writeTo(mapout);
                if (webxmlLevel >= ALL_WEBXML) {
                    mapout.write(Localizer.getMessage("jspc.webxml.footer"));
                } else if ((webxmlLevel >= INC_WEBXML) && !addWebXmlMappings) {
                    mapout.write(Localizer.getMessage("jspc.webinc.footer"));
                }
                mapout.close();
            } catch (IOException ioe) {
                // noting to do if it fails since we are done with it
            }
        }
    }

    private void initServletContext() {
        try {
            context =new JspCServletContext
                (new PrintWriter(new OutputStreamWriter(System.out, "UTF-8")),

                 new URL("file:" + uriRoot.replace('\\','/') + '/'));
            tldScanner = new TldScanner(context, isValidationEnabled);

            // START GlassFish 750
            taglibs = new ConcurrentHashMap<String, TagLibraryInfo>();
            context.setAttribute(Constants.JSP_TAGLIBRARY_CACHE, taglibs);

            tagFileJarUrls = new ConcurrentHashMap<String, URL>();
            context.setAttribute(Constants.JSP_TAGFILE_JAR_URLS_CACHE,
                                 tagFileJarUrls);
            // END GlassFish 750
        } catch (MalformedURLException me) {
            System.out.println("**" + me);
        } catch (UnsupportedEncodingException ex) {
        }
        rctxt = new JspRuntimeContext(context, this);
        jspConfig = new JspConfig(context);
        tagPluginManager = new TagPluginManager(context);
    }

    /**
     * Initializes the classloader as/if needed for the given
     * compilation context.
     *
     * @param clctxt The compilation context
     * @throws IOException If an error occurs
     */
    private void initClassLoader(JspCompilationContext clctxt)
	    throws IOException {

        classPath = getClassPath();

        ClassLoader jspcLoader = getClass().getClassLoader();

        // Turn the classPath into URLs
        ArrayList<URL> urls = new ArrayList<URL>();
        StringTokenizer tokenizer = new StringTokenizer(classPath,
                                                        File.pathSeparator);
        while (tokenizer.hasMoreTokens()) {
            String path = tokenizer.nextToken();
            try {
                File libFile = new File(path);
                urls.add(libFile.toURL());
            } catch (IOException ioe) {
                // Failing a toCanonicalPath on a file that
                // exists() should be a JVM regression test,
                // therefore we have permission to freak uot
                throw new RuntimeException(ioe.toString());
            }
        }

        File webappBase = new File(uriRoot);
        if (webappBase.exists()) {
            File classes = new File(webappBase, "/WEB-INF/classes");
            try {
                if (classes.exists()) {
                    classPath = classPath + File.pathSeparator
                        + classes.getCanonicalPath();
                    urls.add(classes.getCanonicalFile().toURL());
                }
            } catch (IOException ioe) {
                // failing a toCanonicalPath on a file that
                // exists() should be a JVM regression test,
                // therefore we have permission to freak out
                throw new RuntimeException(ioe.toString());
            }
            File lib = new File(webappBase, "/WEB-INF/lib");
            if (lib.exists() && lib.isDirectory()) {
                String[] libs = lib.list();
                for (int i = 0; i < libs.length; i++) {
                    if( libs[i].length() <5 ) continue;
                    String ext=libs[i].substring( libs[i].length() - 4 );
                    if (! ".jar".equalsIgnoreCase(ext)) {
                        if (".tld".equalsIgnoreCase(ext)) {
                            log.warning(
                              "TLD files should not be placed in /WEB-INF/lib");
                        }
                        continue;
                    }
                    try {
                        File libFile = new File(lib, libs[i]);
                        classPath = classPath + File.pathSeparator
                            + libFile.getCanonicalPath();
                        urls.add(libFile.getCanonicalFile().toURL());
                    } catch (IOException ioe) {
                        // failing a toCanonicalPath on a file that
                        // exists() should be a JVM regression test,
                        // therefore we have permission to freak out
                        throw new RuntimeException(ioe.toString());
                    }
                }
            }
        }

        // What is this ??
        urls.add(new File(clctxt.getRealPath("/")).getCanonicalFile().toURL());

        URL urlsA[]=new URL[urls.size()];
        urls.toArray(urlsA);

        /* SJSAS 6327357
        loader = new URLClassLoader(urlsA, this.getClass().getClassLoader());
         */
        // START SJSAS 6327357
        ClassLoader sysClassLoader = initSystemClassLoader();
        if (sysClassLoader != null) {
            loader = new URLClassLoader(urlsA, sysClassLoader);
        } else {
            loader = new URLClassLoader(urlsA, this.getClass().getClassLoader());
        }
        // END SJSAS 6327357
    }

    /**
     * Find the WEB-INF dir by looking up in the directory tree.
     * This is used if no explicit docbase is set, but only files.
     * XXX Maybe we should require the docbase.
     */
    private void locateUriRoot( File f ) {
        String tUriBase = uriBase;
        if (tUriBase == null) {
            tUriBase = "/";
        }
        try {
            if (f.exists()) {
                f = new File(f.getCanonicalPath());
                while (f != null) {
                    File g = new File(f, "WEB-INF");
                    if (g.exists() && g.isDirectory()) {
                        uriRoot = f.getCanonicalPath();
                        uriBase = tUriBase;
			if (log.isLoggable(Level.INFO)) {
                            log.info(
                                Localizer.getMessage("jspc.implicit.uriRoot",
                                                     uriRoot));
			}
                        break;
                    }
                    if (f.exists() && f.isDirectory()) {
                        tUriBase = "/" + f.getName() + "/" + tUriBase;
                    }

                    String fParent = f.getParent();
                    if (fParent == null) {
                        break;
                    } else {
                        f = new File(fParent);
                    }

                    // If there is no acceptible candidate, uriRoot will
                    // remain null to indicate to the CompilerContext to
                    // use the current working/user dir.
                }

                if (uriRoot != null) {
                    File froot = new File(uriRoot);
                    uriRoot = froot.getCanonicalPath();
                }
            }
        } catch (IOException ioe) {
            // since this is an optional default and a null value
            // for uriRoot has a non-error meaning, we can just
            // pass straight through
        }
    }

    // START SJSAS 6327357
    private ClassLoader initSystemClassLoader() throws IOException {

        String sysClassPath = getSystemClassPath();
        if (sysClassPath == null) {
            return null;
        }

        ArrayList<URL> urls = new ArrayList<URL>();
        StringTokenizer tokenizer = new StringTokenizer(sysClassPath,
                                                        File.pathSeparator);
        while (tokenizer.hasMoreTokens()) {
            urls.add(new File(tokenizer.nextToken()).toURL());
        }

        if (urls.size() == 0) {
            return null;
        }

        URL urlsArray[] = new URL[urls.size()];
        urls.toArray(urlsArray);

        return new URLClassLoader(urlsArray, this.getClass().getClassLoader());
    }
    // END SJAS 6327357

    // START SJSAS 6393940
    /*
     * Purges all compilation errors related to JSP fragments.
     */
    private void purgeJspFragmentErrors() {
        Iterator<String> it = dependents.iterator();
        if (it != null) {
            while (it.hasNext()) {
                jspErrors.remove(it.next());
            }
        }
    }
    // END SJSAS 6393940
}


/*
 * Hash Chain match finder with 2-, 3-, and 4-byte hashing
 *
 * Authors: Lasse Collin <lasse.collin@tukaani.org>
 *          Igor Pavlov <http://7-zip.org/>
 *
 * This file has been put into the public domain.
 * You can do whatever you want with this file.
 */

package org.tukaani.xz.lz;

import org.tukaani.xz.ArrayCache;

final class HC4 extends LZEncoder {
    private final Hash234 hash;
    private final int[] chain;
    private final Matches matches;
    private final int depthLimit;

    private final int cyclicSize;
    private int cyclicPos = -1;
    private int lzPos;

    /**
     * Gets approximate memory usage of the match finder as kibibytes.
     */
    static int getMemoryUsage(int dictSize) {
        return Hash234.getMemoryUsage(dictSize) + dictSize / (1024 / 4) + 10;
    }

    /**
     * Creates a new LZEncoder with the HC4 match finder.
     * See LZEncoder.getInstance for parameter descriptions.
     */
    HC4(int dictSize, int beforeSizeMin, int readAheadMax,
            int niceLen, int matchLenMax, int depthLimit,
            ArrayCache arrayCache) {
        super(dictSize, beforeSizeMin, readAheadMax, niceLen, matchLenMax,
              arrayCache);

        hash = new Hash234(dictSize, arrayCache);

        // +1 because we need dictSize bytes of history + the current byte.
        cyclicSize = dictSize + 1;
        chain = arrayCache.getIntArray(cyclicSize, false);
        lzPos = cyclicSize;

        // Substracting 1 because the shortest match that this match
        // finder can find is 2 bytes, so there's no need to reserve
        // space for one-byte matches.
        matches = new Matches(niceLen - 1);

        // Use a default depth limit if no other value was specified.
        // The default is just something based on experimentation;
        // it's nothing magic.
        this.depthLimit = (depthLimit > 0) ? depthLimit : 4 + niceLen / 4;
    }

    public void putArraysToCache(ArrayCache arrayCache) {
        arrayCache.putArray(chain);
        hash.putArraysToCache(arrayCache);
        super.putArraysToCache(arrayCache);
    }

    /**
     * Moves to the next byte, checks that there is enough available space,
     * and possibly normalizes the hash tables and the hash chain.
     *
     * @return      number of bytes available, including the current byte
     */
    private int movePos() {
        int avail = movePos(4, 4);

        if (avail != 0) {
            if (++lzPos == Integer.MAX_VALUE) {
                int normalizationOffset = Integer.MAX_VALUE - cyclicSize;
                hash.normalize(normalizationOffset);
                normalize(chain, cyclicSize, normalizationOffset);
                lzPos -= normalizationOffset;
            }

            if (++cyclicPos == cyclicSize)
                cyclicPos = 0;
        }

        return avail;
    }

    public Matches getMatches() {
        matches.count = 0;
        int matchLenLimit = matchLenMax;
        int niceLenLimit = niceLen;
        int avail = movePos();

        if (avail < matchLenLimit) {
            if (avail == 0)
                return matches;

            matchLenLimit = avail;
            if (niceLenLimit > avail)
                niceLenLimit = avail;
        }

        hash.calcHashes(buf, readPos);
        int delta2 = lzPos - hash.getHash2Pos();
        int delta3 = lzPos - hash.getHash3Pos();
        int currentMatch = hash.getHash4Pos();
        hash.updateTables(lzPos);

        chain[cyclicPos] = currentMatch;

        int lenBest = 0;

        // See if the hash from the first two bytes found a match.
        // The hashing algorithm guarantees that if the first byte
        // matches, also the second byte does, so there's no need to
        // test the second byte.
        if (delta2 < cyclicSize && buf[readPos - delta2] == buf[readPos]) {
            lenBest = 2;
            matches.len[0] = 2;
            matches.dist[0] = delta2 - 1;
            matches.count = 1;
        }

        // See if the hash from the first three bytes found a match that
        // is different from the match possibly found by the two-byte hash.
        // Also here the hashing algorithm guarantees that if the first byte
        // matches, also the next two bytes do.
        if (delta2 != delta3 && delta3 < cyclicSize
                && buf[readPos - delta3] == buf[readPos]) {
            lenBest = 3;
            matches.dist[matches.count++] = delta3 - 1;
            delta2 = delta3;
        }

        // If a match was found, see how long it is.
        if (matches.count > 0) {
            while (lenBest < matchLenLimit && buf[readPos + lenBest - delta2]
                                              == buf[readPos + lenBest])
                ++lenBest;

            matches.len[matches.count - 1] = lenBest;

            // Return if it is long enough (niceLen or reached the end of
            // the dictionary).
            if (lenBest >= niceLenLimit)
                return matches;
        }

        // Long enough match wasn't found so easily. Look for better matches
        // from the hash chain.
        if (lenBest < 3)
            lenBest = 3;

        int depth = depthLimit;

        while (true) {
            int delta = lzPos - currentMatch;

            // Return if the search depth limit has been reached or
            // if the distance of the potential match exceeds the
            // dictionary size.
            if (depth-- == 0 || delta >= cyclicSize)
                return matches;

            currentMatch = chain[cyclicPos - delta
                                 + (delta > cyclicPos ? cyclicSize : 0)];

            // Test the first byte and the first new byte that would give us
            // a match that is at least one byte longer than lenBest. This
            // too short matches get quickly skipped.
            if (buf[readPos + lenBest - delta] == buf[readPos + lenBest]
                    && buf[readPos - delta] == buf[readPos]) {
                // Calculate the length of the match.
                int len = 0;
                while (++len < matchLenLimit)
                    if (buf[readPos + len - delta] != buf[readPos + len])
                        break;

                // Use the match if and only if it is better than the longest
                // match found so far.
                if (len > lenBest) {
                    lenBest = len;
                    matches.len[matches.count] = len;
                    matches.dist[matches.count] = delta - 1;
                    ++matches.count;

                    // Return if it is long enough (niceLen or reached the
                    // end of the dictionary).
                    if (len >= niceLenLimit)
                        return matches;
                }
            }
        }
    }

    public void skip(int len) {
        assert len >= 0;

        while (len-- > 0) {
            if (movePos() != 0) {
                // Update the hash chain and hash tables.
                hash.calcHashes(buf, readPos);
                chain[cyclicPos] = hash.getHash4Pos();
                hash.updateTables(lzPos);
            }
        }
    }
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 *
 *
 * This file incorporates work covered by the following copyright and
 * permission notice:
 *
 * Copyright 2004 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.jasper.runtime;

import java.util.Iterator;

import javax.el.ELContext;
import javax.el.ELResolver;
import javax.el.ValueExpression;
import javax.el.ExpressionFactory;

import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.el.Expression;
import javax.servlet.jsp.el.ELException;
import javax.servlet.jsp.el.FunctionMapper;
import javax.servlet.jsp.el.ExpressionEvaluator;
import javax.servlet.jsp.el.VariableResolver;

/**
 * <p>This is the implementation of ExpreesioEvaluator
 * using implementation of JSP2.1.
 *
 * @author Kin-man Chung
 * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
 **/

public class ExpressionEvaluatorImpl extends ExpressionEvaluator
{
    private PageContext pageContext;

    //-------------------------------------
    /**
     * Constructor
     **/
    public ExpressionEvaluatorImpl (PageContext pageContext) {
        this.pageContext = pageContext;
    }

    //-------------------------------------
    public Expression parseExpression(String expression,
                                      Class expectedType,
                                      FunctionMapper fMapper )
            throws ELException {

        ExpressionFactory fac = ExpressionFactory.newInstance();
        javax.el.ValueExpression expr;
        ELContextImpl elContext = new ELContextImpl(null);
        javax.el.FunctionMapper fm = new FunctionMapperWrapper(fMapper);
        elContext.setFunctionMapper(fm);
        try {
            expr = fac.createValueExpression(
                           elContext,
                           expression, expectedType);
        } catch (javax.el.ELException ex) {
            throw new ELException(ex);
        }
        return new ExpressionImpl(expr, pageContext);
    }

     public Object evaluate(String expression,
                            Class expectedType,
                            VariableResolver vResolver,
                            FunctionMapper fMapper )
                throws ELException {

        ELContextImpl elContext;
        if (vResolver instanceof VariableResolverImpl) {
            elContext = (ELContextImpl) pageContext.getELContext();
        }
        else {
            // The provided variable Resolver is a custom resolver,
            // wrap it with a ELResolver
            elContext = new ELContextImpl(new ELResolverWrapper(vResolver));
        }

        javax.el.FunctionMapper fm = new FunctionMapperWrapper(fMapper);
        elContext.setFunctionMapper(fm);
        ExpressionFactory fac = ExpressionFactory.newInstance();
        Object value;
        try {
            ValueExpression expr = fac.createValueExpression(
                                 elContext,
                                 expression,
                                 expectedType);
            value = expr.getValue(elContext);
        } catch (javax.el.ELException ex) {
            throw new ELException(ex);
        }
        return value;
    }

    static private class ExpressionImpl extends Expression {

        private ValueExpression valueExpr;
        private PageContext pageContext;

        ExpressionImpl(ValueExpression valueExpr,
                       PageContext pageContext) {
            this.valueExpr = valueExpr;
            this.pageContext = pageContext;
        }

        public Object evaluate(VariableResolver vResolver) throws ELException {

            ELContext elContext;
            if (vResolver instanceof VariableResolverImpl) {
                elContext = pageContext.getELContext();
            }
            else {
                // The provided variable Resolver is a custom resolver,
                // wrap it with a ELResolver
                elContext = new ELContextImpl(new ELResolverWrapper(vResolver));
            }
            try {
                return valueExpr.getValue(elContext);
            } catch (javax.el.ELException ex) {
                throw new ELException(ex);
            }
        }
    }

    private static class FunctionMapperWrapper
        extends javax.el.FunctionMapper {

        private FunctionMapper mapper;

        FunctionMapperWrapper(FunctionMapper mapper) {
            this.mapper = mapper;
        }

        public java.lang.reflect.Method resolveFunction(String prefix,
                                                        String localName) {
            return mapper.resolveFunction(prefix, localName);
        }
    }

    private static class ELResolverWrapper extends ELResolver {
        private VariableResolver vResolver;

        ELResolverWrapper(VariableResolver vResolver) {
            this.vResolver = vResolver;
        }

        public Object getValue(ELContext context,
                               Object base,
                               Object property)
                throws javax.el.ELException {
            if (base == null) {
                context.setPropertyResolved(true);
                try {
                    return vResolver.resolveVariable(property.toString());
                } catch (ELException ex) {
                    throw new javax.el.ELException(ex);
                }
            }
            return null;
        }

        public Class getType(ELContext context,
                             Object base,
                             Object property)
                throws javax.el.ELException {
            return null;
        }

        public void setValue(ELContext context,
                             Object base,
                             Object property,
                             Object value)
                throws javax.el.ELException {
        }

        public boolean isReadOnly(ELContext context,
                                  Object base,
                                  Object property)
                throws javax.el.ELException {
            return false;
        }

        public Iterator<java.beans.FeatureDescriptor>
                getFeatureDescriptors(ELContext context, Object base) {
            return null;
        }

        public Class<?> getCommonPropertyType(ELContext context,
                                           Object base) {
            return null;
        }
    }
}


/*
 * DeltaOptions
 *
 * Author: Lasse Collin <lasse.collin@tukaani.org>
 *
 * This file has been put into the public domain.
 * You can do whatever you want with this file.
 */

package org.tukaani.xz;

import java.io.InputStream;

/**
 * Delta filter options. The Delta filter can be used only as a non-last
 * filter in the chain, for example Delta + LZMA2.
 *

 * Currently only simple byte-wise delta is supported. The only option
 * is the delta distance, which you should set to match your data.
 * It's not possible to provide a generic default value for it.
 *

 * For example, with distance = 2 and eight-byte input
 * A1 B1 A2 B3 A3 B5 A4 B7, the output will be A1 B1 01 02 01 02 01 02.
 *

 * The Delta filter can be good with uncompressed bitmap images. It can
 * also help with PCM audio, although special-purpose compressors like
 * FLAC will give much smaller result at much better compression speed.
 */
public class DeltaOptions extends FilterOptions {
    /**
     * Smallest supported delta calculation distance.
     */
    public static final int DISTANCE_MIN = 1;

    /**
     * Largest supported delta calculation distance.
     */
    public static final int DISTANCE_MAX = 256;

    private int distance = DISTANCE_MIN;

    /**
     * Creates new Delta options and sets the delta distance to 1 byte.
     */
    public DeltaOptions() {}

    /**
     * Creates new Delta options and sets the distance to the given value.
     */
    public DeltaOptions(int distance) throws UnsupportedOptionsException {
        setDistance(distance);
    }

    /**
     * Sets the delta distance in bytes. The new distance must be in
     * the range [DISTANCE_MIN, DISTANCE_MAX].
     */
    public void setDistance(int distance) throws UnsupportedOptionsException {
        if (distance < DISTANCE_MIN || distance > DISTANCE_MAX)
            throw new UnsupportedOptionsException(
                    "Delta distance must be in the range [" + DISTANCE_MIN
                    + ", " + DISTANCE_MAX + "]: " + distance);

        this.distance = distance;
    }

    /**
     * Gets the delta distance.
     */
    public int getDistance() {
        return distance;
    }

    public int getEncoderMemoryUsage() {
        return DeltaOutputStream.getMemoryUsage();
    }

    public FinishableOutputStream getOutputStream(FinishableOutputStream out,
                                                  ArrayCache arrayCache) {
        return new DeltaOutputStream(out, this);
    }

    public int getDecoderMemoryUsage() {
        return 1;
    }

    public InputStream getInputStream(InputStream in, ArrayCache arrayCache) {
        return new DeltaInputStream(in, distance);
    }

    FilterEncoder getFilterEncoder() {
        return new DeltaEncoder(this);
    }

    public Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            assert false;
            throw new RuntimeException();
        }
    }
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 *
 *
 * This file incorporates work covered by the following copyright and
 * permission notice:
 *
 * Copyright 2004 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.jasper.compiler;

import java.io.CharArrayWriter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.jar.JarFile;
import java.util.logging.Logger;
import java.util.logging.Level;
import java.net.URL;
import java.net.MalformedURLException;

import org.apache.jasper.JasperException;
import org.apache.jasper.JspCompilationContext;

/**
 * JspReader is an input buffer for the JSP parser. It should allow
 * unlimited lookahead and pushback. It also has a bunch of parsing
 * utility methods for understanding htmlesque thingies.
 *
 * @author Anil K. Vijendran
 * @author Anselm Baird-Smith
 * @author Harish Prabandham
 * @author Rajiv Mordani
 * @author Mandar Raje
 * @author Danno Ferrin
 * @author Kin-man Chung
 * @author Shawn Bayern
 * @author Mark Roth
 */

class JspReader {

    // Logger
    private static Logger log = Logger.getLogger(JspReader.class.getName());

    private Mark current;
    private String master;
    private ArrayList<String> sourceFiles;
    private int currFileId;
    private int size;
    private JspCompilationContext context;
    private ErrorDispatcher err;

    /*
     * Set to true when using the JspReader on a single file where we read up
     * to the end and reset to the beginning many times.
     * (as in ParserController.figureOutJspDocument()).
     */
    private boolean singleFile;

    /*
     * Constructor.
     */
    public JspReader(JspCompilationContext ctxt,
		     String fname,
		     String encoding,
		     JarFile jarFile,
		     ErrorDispatcher err)
	    throws JasperException, FileNotFoundException, IOException {

	this(ctxt, fname, encoding,
	     JspUtil.getReader(fname, encoding, jarFile, ctxt, err),
	     err);
    }

    /*
     * Constructor.
     */
    public JspReader(JspCompilationContext ctxt,
		     String fname,
		     String encoding,
		     InputStreamReader reader,
		     ErrorDispatcher err)
	    throws JasperException, FileNotFoundException {

        this.context = ctxt;
	this.err = err;
	sourceFiles = new ArrayList<String>();
	currFileId = 0;
	size = 0;
	singleFile = false;
	pushFile(fname, encoding, reader);
    }

    /*
     * @return JSP compilation context with which this JspReader is
     * associated
     */
    JspCompilationContext getJspCompilationContext() {
        return context;
    }

    String getFile(int fileid) {
	return sourceFiles.get(fileid);
    }

    boolean hasMoreInput() throws JasperException {
	if (current.cursor >= current.stream.length) {
            if (singleFile) return false;
	    while (popFile()) {
		if (current.cursor < current.stream.length) return true;
	    }
	    return false;
	}
	return true;
    }

    int nextChar() throws JasperException {
	if (!hasMoreInput())
	    return -1;

	int ch = current.stream[current.cursor];

	current.cursor++;

	if (ch == '\n') {
	    current.line++;
	    current.col = 0;
	} else {
	    current.col++;
	}
	return ch;
    }

    /**
     * Back up the current cursor by one char, assumes current.cursor > 0,
     * and that the char to be pushed back is not '\n'.
     */
    void pushChar() {
	current.cursor--;
	current.col--;
    }

    String getText(Mark start, Mark stop) throws JasperException {
	Mark oldstart = mark();
	reset(start);
	CharArrayWriter caw = new CharArrayWriter();
	while (!stop.equals(mark()))
	    caw.write(nextChar());
	caw.close();
	reset(oldstart);
	return caw.toString();
    }

    int peekChar() throws JasperException {
        if (!hasMoreInput())
            return -1;
	return current.stream[current.cursor];
    }

    Mark mark() {
	return new Mark(current);
    }

    void reset(Mark mark) {
	current = new Mark(mark);
    }

    boolean matchesIgnoreCase(String string) throws JasperException {
	Mark mark = mark();
	int ch = 0;
	int i = 0;
	do {
	    ch = nextChar();
	    if (Character.toLowerCase((char) ch) != string.charAt(i++)) {
		reset(mark);
		return false;
	    }
	} while (i < string.length());
	reset(mark);
	return true;
    }

    /**
     * search the stream for a match to a string
     * @param string The string to match
     * @return true is one is found, the current position
     *         in stream is positioned after the search string,
     *	       false otherwise, position in stream unchanged.
     */
    boolean matches(String string) throws JasperException {
	Mark mark = mark();
	int ch = 0;
	int i = 0;
	do {
	    ch = nextChar();
	    if (((char) ch) != string.charAt(i++)) {
		reset(mark);
		return false;
	    }
	} while (i < string.length());
	return true;
    }

    boolean matchesETag(String tagName) throws JasperException {
	Mark mark = mark();

	if (!matches("</" + tagName))
	    return false;
	skipSpaces();
	if (nextChar() == '>')
	    return true;

	reset(mark);
	return false;
    }

    boolean matchesETagWithoutLessThan(String tagName)
        throws JasperException
    {
       Mark mark = mark();

       if (!matches("/" + tagName))
           return false;
       skipSpaces();
       if (nextChar() == '>')
           return true;

       reset(mark);
       return false;
    }

    /**
     * Looks ahead to see if there are optional spaces followed by
     * the given String.  If so, true is returned and those spaces and
     * characters are skipped.  If not, false is returned and the
     * position is restored to where we were before.
     */
    boolean matchesOptionalSpacesFollowedBy( String s )
	throws JasperException
    {
        Mark mark = mark();

        skipSpaces();
        boolean result = matches( s );
        if( !result ) {
            reset( mark );
        }

        return result;
    }

    int skipSpaces() throws JasperException {
	int i = 0;
	while (hasMoreInput() && isSpace()) {
	    i++;
	    nextChar();
	}
	return i;
    }

    /**
     * Skip until the given string is matched in the stream.
     * When returned, the context is positioned past the end of the match.
     *
     * @param s The String to match.
     * @return A non-null Mark instance (positioned immediately
     *         before the search string) if found, null
     *         otherwise.
     */
    Mark skipUntil(String limit) throws JasperException {
        Mark ret = null;
        int limlen = limit.length();
        int ch;

    skip:
        for (ret = mark(), ch = nextChar() ; ch != -1 ;
                 ret = mark(), ch = nextChar()) {
            if (ch == limit.charAt(0)) {
                Mark restart = mark();
                for (int i = 1 ; i < limlen ; i++) {
                    if (peekChar() == limit.charAt(i))
                        nextChar();
                    else {
                        reset(restart);
                        continue skip;
                    }
                }
                return ret;
            }
        }
        return null;
    }

    /**
     * Skip until the given string is matched in the stream, but ignoring
     * chars initially escaped by a '\'.
     * When returned, the context is positioned past the end of the match.
     *
     * @param s The String to match.
     * @return A non-null Mark instance (positioned immediately
     *         before the search string) if found, null
     *         otherwise.
     */
    Mark skipUntilIgnoreEsc(String limit) throws JasperException {
	Mark ret = null;
	int limlen = limit.length();
	int ch;
	int prev = 'x';	// Doesn't matter

    skip:
	for (ret = mark(), ch = nextChar() ; ch != -1 ;
	         ret = mark(), prev = ch, ch = nextChar()) {
	    if (ch == '\\' && prev == '\\') {
		ch = 0;		// Double \ is not an escape char anymore
	    }
	    else if (ch == limit.charAt(0) && prev != '\\') {
		for (int i = 1 ; i < limlen ; i++) {
		    if (peekChar() == limit.charAt(i))
			nextChar();
		    else
			continue skip;
		}
		return ret;
	    }
	}
	return null;
    }

    /**
     * Skip until the given end tag is matched in the stream.
     * When returned, the context is positioned past the end of the tag.
     *
     * @param tag The name of the tag whose ETag (</tag>) to match.
     * @return A non-null Mark instance (positioned immediately
     *	       before the ETag) if found, null otherwise.
     */
    Mark skipUntilETag(String tag) throws JasperException {
	Mark ret = skipUntil("</" + tag);
	if (ret != null) {
	    skipSpaces();
	    if (nextChar() != '>')
		ret = null;
	}
	return ret;
    }

    final boolean isSpace() throws JasperException {
        // Note: If this logic changes, also update Node.TemplateText.rtrim()
	return peekChar() <= ' ';
    }

    /**
     * Parse a space delimited token.
     * If quoted the token will consume all characters up to a matching quote,
     * otherwise, it consumes up to the first delimiter character.
     *
     * @param quoted If true accept quoted strings.
     */
    String parseToken(boolean quoted) throws JasperException {
	StringBuilder stringBuffer = new StringBuilder();
	skipSpaces();
	stringBuffer.setLength(0);

        if (!hasMoreInput()) {
            return "";
        }

	int ch = peekChar();

	if (quoted) {
	    if (ch == '"' || ch == '\'') {

		char endQuote = ch == '"' ? '"' : '\'';
		// Consume the open quote:
		ch = nextChar();
		for (ch = nextChar(); ch != -1 && ch != endQuote;
		         ch = nextChar()) {
		    if (ch == '\\')
			ch = nextChar();
		    stringBuffer.append((char) ch);
		}
		// Check end of quote, skip closing quote:
		if (ch == -1) {
		    err.jspError(mark(), "jsp.error.quotes.unterminated");
		}
	    } else {
		err.jspError(mark(), "jsp.error.attr.quoted");
	    }
	} else {
	    if (!isDelimiter()) {
		// Read value until delimiter is found:
		do {
		    ch = nextChar();
		    // Take care of the quoting here.
		    if (ch == '\\') {
			if (peekChar() == '"' || peekChar() == '\'' ||
			       peekChar() == '>' || peekChar() == '%')
			    ch = nextChar();
		    }
		    stringBuffer.append((char) ch);
		} while (!isDelimiter());
	    }
	}

	return stringBuffer.toString();
    }

    void setSingleFile(boolean val) {
        singleFile = val;
    }

    /**
     * Gets the URL for the given path name.
     *
     * @param path Path name
     *
     * @return URL for the given path name.
     *
     * @exception MalformedURLException if the path name is not given in
     * the correct form
     */
    URL getResource(String path) throws MalformedURLException {
        return context.getResource(path);
    }

    /**
     * Parse utils - Is current character a token delimiter ?
     * Delimiters are currently defined to be =, >, <, ", and ' or any
     * any space character as defined by isSpace.
     *
     * @return A boolean.
     */
    private boolean isDelimiter() throws JasperException {
	if (! isSpace()) {
	    int ch = peekChar();
	    // Look for a single-char work delimiter:
	    if (ch == '=' || ch == '>' || ch == '"' || ch == '\''
		    || ch == '/') {
		return true;
	    }
	    // Look for an end-of-comment or end-of-tag:
	    if (ch == '-') {
		Mark mark = mark();
		if (((ch = nextChar()) == '>')
		        || ((ch == '-') && (nextChar() == '>'))) {
		    reset(mark);
		    return true;
		} else {
		    reset(mark);
		    return false;
		}
	    }
	    return false;
	} else {
	    return true;
	}
    }

    /**
     * Register a new source file.
     * This method is used to implement file inclusion. Each included file
     * gets a unique identifier (which is the index in the array of source
     * files).
     *
     * @return The index of the now registered file.
     */
    private int registerSourceFile(String file) {
        if (sourceFiles.contains(file))
            return -1;
	sourceFiles.add(file);
	this.size++;
	return sourceFiles.size() - 1;
    }

    /**
     * Unregister the source file.
     * This method is used to implement file inclusion. Each included file
     * gets a uniq identifier (which is the index in the array of source
     * files).
     *
     * @return The index of the now registered file.
     */
    private int unregisterSourceFile(String file) {
        if (!sourceFiles.contains(file))
            return -1;
	sourceFiles.remove(file);
	this.size--;
	return sourceFiles.size() - 1;
    }

    /**
     * Push a file (and its associated Stream) on the file stack.  THe
     * current position in the current file is remembered.
     */
    private void pushFile(String file, String encoding,
			   InputStreamReader reader)
	        throws JasperException, FileNotFoundException {

	// Register the file
	String longName = file;

	int fileid = registerSourceFile(longName);

        if (fileid == -1) {
            err.jspError("jsp.error.file.already.registered", file);
	}

	currFileId = fileid;

	try {
	    CharArrayWriter caw = new CharArrayWriter();
	    char buf[] = new char[1024];
	    for (int i = 0 ; (i = reader.read(buf)) != -1 ;)
		caw.write(buf, 0, i);
	    caw.close();
	    if (current == null) {
		current = new Mark(this, caw.toCharArray(), fileid,
				   getFile(fileid), master, encoding);
	    } else {
		current.pushStream(caw.toCharArray(), fileid, getFile(fileid),
				   longName, encoding);
	    }
	} catch (Throwable ex) {
	    log.log(Level.SEVERE, "Exception parsing file ", ex);
	    // Pop state being constructed:
	    popFile();
	    err.jspError("jsp.error.file.cannot.read", file);
	} finally {
	    if (reader != null) {
		try {
		    reader.close();
		} catch (Exception any) {}
	    }
	}
    }

    /**
     * Pop a file from the file stack.  The field "current" is retored
     * to the value to point to the previous files, if any, and is set
     * to null otherwise.
     * @return true is there is a previous file on the stck.
     *         false otherwise.
     */
    private boolean popFile() throws JasperException {

	// Is stack created ? (will happen if the Jsp file we're looking at is
	// missing.
	if (current == null || currFileId < 0) {
	    return false;
	}

	// Restore parser state:
	String fName = getFile(currFileId);
	currFileId = unregisterSourceFile(fName);
	if (currFileId < -1) {
	    err.jspError("jsp.error.file.not.registered", fName);
	}

	Mark previous = current.popStream();
	if (previous != null) {
	    master = current.baseDir;
	    current = previous;
	    return true;
	}
	// Note that although the current file is undefined here, "current"
	// is not set to null just for convience, for it maybe used to
	// set the current (undefined) position.
	return false;
    }
}