/*******************************************************************************
 * Copyright (c) 2015 IBH SYSTEMS GmbH.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBH SYSTEMS GmbH - initial API and implementation
 *     M-Ezzat - code cleanup - squid:S2162
 *******************************************************************************/
package org.eclipse.packagedrone.repo.adapter.maven.upload;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Maven coordinates for the uploader
 */
public class Coordinates
{
    private static final String SNAPSHOT_SUFFIX = "-SNAPSHOT";

    private final String groupId;

    private final String artifactId;

    private final String version;

    private final String qualifiedVersion;

    private final String classifier;

    private final String extension;

    public Coordinates ( final String groupId, final String artifactId, final String version, final String extension )
    {
        this ( groupId, artifactId, version, version, null, extension );
    }

    public Coordinates ( final String groupId, final String artifactId, final String version, final String classifier, final String extension )
    {
        this ( groupId, artifactId, version, version, classifier, extension );
    }

    public Coordinates ( final String groupId, final String artifactId, final String version, final String qualifiedVersion, final String classifier, final String extension )
    {
        this.groupId = groupId;
        this.artifactId = artifactId;
        this.version = version;
        this.qualifiedVersion = qualifiedVersion;
        this.classifier = classifier;
        this.extension = extension;
    }

    public String getGroupId ()
    {
        return this.groupId;
    }

    public String getArtifactId ()
    {
        return this.artifactId;
    }

    public String getVersion ()
    {
        return this.version;
    }

    public String getQualifiedVersion ()
    {
        return this.qualifiedVersion;
    }

    public String getClassifier ()
    {
        return this.classifier;
    }

    public String getExtension ()
    {
        return this.extension;
    }

    @Override
    public int hashCode ()
    {
        final int prime = 31;
        int result = 1;
        result = prime * result + ( this.artifactId == null ? 0 : this.artifactId.hashCode () );
        result = prime * result + ( this.classifier == null ? 0 : this.classifier.hashCode () );
        result = prime * result + ( this.extension == null ? 0 : this.extension.hashCode () );
        result = prime * result + ( this.groupId == null ? 0 : this.groupId.hashCode () );
        result = prime * result + ( this.qualifiedVersion == null ? 0 : this.qualifiedVersion.hashCode () );
        result = prime * result + ( this.version == null ? 0 : this.version.hashCode () );
        return result;
    }

    @Override
    public boolean equals ( final Object obj )
    {
        if ( this == obj )
        {
            return true;
        }
        if ( obj == null )
        {
            return false;
        }
        if ( this.getClass() != obj.getClass() )
        {
            return false;
        }
        final Coordinates other = (Coordinates)obj;
        if ( this.artifactId == null )
        {
            if ( other.artifactId != null )
            {
                return false;
            }
        }
        else if ( !this.artifactId.equals ( other.artifactId ) )
        {
            return false;
        }
        if ( this.classifier == null )
        {
            if ( other.classifier != null )
            {
                return false;
            }
        }
        else if ( !this.classifier.equals ( other.classifier ) )
        {
            return false;
        }
        if ( this.extension == null )
        {
            if ( other.extension != null )
            {
                return false;
            }
        }
        else if ( !this.extension.equals ( other.extension ) )
        {
            return false;
        }
        if ( this.groupId == null )
        {
            if ( other.groupId != null )
            {
                return false;
            }
        }
        else if ( !this.groupId.equals ( other.groupId ) )
        {
            return false;
        }
        if ( this.qualifiedVersion == null )
        {
            if ( other.qualifiedVersion != null )
            {
                return false;
            }
        }
        else if ( !this.qualifiedVersion.equals ( other.qualifiedVersion ) )
        {
            return false;
        }
        if ( this.version == null )
        {
            if ( other.version != null )
            {
                return false;
            }
        }
        else if ( !this.version.equals ( other.version ) )
        {
            return false;
        }
        return true;
    }

    @Override
    public String toString ()
    {
        return toFullPath ();
    }

    public String toFullPath ()
    {
        final StringBuilder sb = new StringBuilder ();
        appendFullPath ( sb );
        return sb.toString ();
    }

    public String toFileName ()
    {
        final StringBuilder sb = new StringBuilder ();
        appendFileName ( sb );
        return sb.toString ();
    }

    public static String makePath ( final String groupId, final String artifactId, final String version, final String qualifiedVersion, final String classifier, final String extension )
    {
        return new Coordinates ( groupId, artifactId, version, qualifiedVersion, classifier, extension ).toFullPath ();
    }

    public void appendFullPath ( final StringBuilder sb )
    {
        sb.append ( '/' );
        sb.append ( this.groupId.replace ( '.', '/' ) );

        sb.append ( '/' );
        sb.append ( this.artifactId );

        sb.append ( '/' );
        sb.append ( this.version );

        sb.append ( '/' );
        appendFileName ( sb );
    }

    public void appendFileName ( final StringBuilder sb )
    {
        sb.append ( this.artifactId ).append ( '-' ).append ( this.qualifiedVersion == null ? this.version : this.qualifiedVersion );
        if ( this.classifier != null )
        {
            sb.append ( '-' ).append ( this.classifier );
        }
        sb.append ( '.' ).append ( this.extension );
    }

    public static Coordinates parse ( final String path )
    {
        final LinkedList<String> toks = new LinkedList<> ( Arrays.asList ( path.split ( "/" ) ) );

        final String fileName = toks.pollLast ();
        final String version = toks.pollLast ();
        final String artifactId = toks.pollLast ();

        final StringBuilder sb = new StringBuilder ();
        for ( final String tok : toks )
        {
            if ( sb.length () > 0 )
            {
                sb.append ( '.' );
            }
            sb.append ( tok );
        }
        final String groupId = sb.toString ();

        for ( final String expectedVersion : makeExpectedVersions ( version ) )
        {
            final String[] split = extractInformation ( artifactId, expectedVersion, fileName );
            if ( split != null )
            {
                return new Coordinates ( groupId, artifactId, version, split[0], split[1], split[2] );
            }
        }

        return null;
    }

    public static boolean isSnapshot ( final String version )
    {
        return version.endsWith ( SNAPSHOT_SUFFIX );
    }

    private static String[] makeExpectedVersions ( final String version )
    {
        if ( !isSnapshot ( version ) )
        {
            return new String[] { Pattern.quote ( version ) };
        }

        final String baseVersion = version.substring ( 0, version.length () - SNAPSHOT_SUFFIX.length () );

        return new String[] { Pattern.quote ( version ), Pattern.quote ( baseVersion ) + "-\\d{8}\\.\\d{6}-\\d+" };
    }

    private static String[] extractInformation ( final String artifactId, final String expectedVersionPattern, final String name )
    {
        final StringBuilder sb = new StringBuilder ();

        sb.append ( Pattern.quote ( artifactId ) );
        sb.append ( "-" );
        sb.append ( "(?<v>" + expectedVersionPattern + ")" );
        sb.append ( "(|-(?<cl>[^\\.]+))" );
        sb.append ( "(|\\.(?<ext>.*+))" );

        final Pattern p = Pattern.compile ( sb.toString () );

        final Matcher m = p.matcher ( name );
        if ( m.matches () )
        {
            final String version = m.group ( "v" );
            final String classifier = m.group ( "cl" );
            final String extension = m.group ( "ext" );

            return new String[] { version, classifier, extension };
        }
        return null;
    }

    /**
     * Create an unclassified version of ourself
     *
     * @return either a new instance without a classifier, or the same instance
     *         if it already is without a classifier
     */
    public Coordinates makeUnclassified ()
    {
        if ( this.classifier == null )
        {
            return this;
        }

        return new Coordinates ( this.groupId, this.artifactId, this.version, this.qualifiedVersion, null, this.extension );
    }

    public Coordinates replaceExtension ( final String newExtension )
    {
        return new Coordinates ( this.groupId, this.artifactId, this.version, this.qualifiedVersion, this.classifier, newExtension );
    }
}


/*******************************************************************************
 * Copyright (c) 2014, 2015 IBH SYSTEMS GmbH.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBH SYSTEMS GmbH - initial API and implementation
 *******************************************************************************/

package org.eclipse.packagedrone.web.controller;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.Response;

import org.eclipse.packagedrone.web.ModelAndView;
import org.eclipse.packagedrone.web.RequestHandler;
import org.eclipse.packagedrone.web.controller.binding.Binder;
import org.eclipse.packagedrone.web.controller.binding.BindingManager;
import org.eclipse.packagedrone.web.controller.binding.ErrorBinder;
import org.eclipse.packagedrone.web.controller.binding.PathVariableBinder;
import org.eclipse.packagedrone.web.controller.binding.RequestParameterBinder;
import org.eclipse.packagedrone.web.controller.form.FormDataBinder;
import org.eclipse.packagedrone.web.controller.routing.RequestMappingInformation;
import org.eclipse.packagedrone.web.controller.routing.RequestMappingInformation.Match;

public class ControllerCall
{
    private final RequestMappingInformation rmi;

    private final Method m;

    private final ControllerInterceptorProcessor[] interceptors;

    private final Object controller;

    public ControllerCall ( final Object controller, final RequestMappingInformation rmi, final Method m, final Set<ControllerInterceptorProcessor> interceptors )
    {
        this.controller = controller;
        this.rmi = rmi;
        this.m = m;
        this.interceptors = interceptors.toArray ( new ControllerInterceptorProcessor[interceptors.size ()] );
    }

    public Match matches ( final HttpServletRequest request )
    {
        return this.rmi.matches ( request );
    }

    protected RequestHandler runForward ( final int index, final HttpServletRequest request, final HttpServletResponse response, final Callable<RequestHandler> last ) throws Exception
    {
        if ( index < this.interceptors.length )
        {
            return this.interceptors[index].before ( this.controller, this.m, request, response, ( chainReq, chainRes ) -> {
                try
                {
                    return runForward ( index + 1, chainReq, chainRes, last );
                }
                catch ( final RuntimeException e )
                {
                    throw e;
                }
                catch ( final Exception e )
                {
                    throw new RuntimeException ( e );
                }
            } );
        }
        else
        {
            return last.call ();
        }
    }

    protected RequestHandler runBackward ( final int index, final HttpServletRequest request, final HttpServletResponse response, final Callable<RequestHandler> last ) throws Exception
    {
        if ( index > 0 )
        {
            return this.interceptors[index - 1].after ( this.controller, this.m, request, response, ( chainReq, chainRes ) -> {
                try
                {
                    return runBackward ( index - 1, chainReq, chainRes, last );
                }
                catch ( final Exception e )
                {
                    throw new RuntimeException ( e );
                }
            } );
        }
        else
        {
            return last.call ();
        }
    }

    public RequestHandler call ( final Match match, final HttpServletRequest request, final HttpServletResponse response )
    {
        // process call

        try
        {
            final RequestHandler result = runForward ( 0, request, response, () -> processCall ( match, request, response ) );

            return runBackward ( this.interceptors.length, request, response, new Callable<RequestHandler> () {

                @Override
                public RequestHandler call () throws Exception
                {
                    return result;
                }
            } );
        }
        catch ( final RuntimeException e )
        {
            throw e;
        }
        catch ( final Exception e )
        {
            throw new RuntimeException ( e );
        }
    }

    protected RequestHandler processCall ( final Match match, final HttpServletRequest request, final HttpServletResponse response ) throws Exception
    {
        final Map<String, Object> data = new HashMap<String, Object> ();

        data.put ( "request", request );
        data.put ( "response", response );
        data.put ( "session", request.getSession () );
        data.put ( "principal", request.getUserPrincipal () );

        // create new binding manager

        final BindingManager manager = BindingManager.create ( data );

        // add controller binders

        manager.addBinder ( new RequestParameterBinder ( request ) );
        manager.addBinder ( new PathVariableBinder ( match ) );
        manager.addBinder ( new FormDataBinder ( request, this.controller ) );

        addMethodBinders ( manager, this.m );

        final org.eclipse.packagedrone.web.controller.binding.BindingManager.Call call = manager.bind ( this.m, this.controller );
        final Object result = call.invoke ();

        if ( result instanceof ModelAndView )
        {
            return new ModelAndViewRequestHandler ( (ModelAndView)result, this.controller.getClass (), this.m );
        }
        else if ( result instanceof String )
        {
            return new ModelAndViewRequestHandler ( new ModelAndView ( (String)result ), this.controller.getClass (), this.m );
        }
        else if ( result == null )
        {
            return new NoOpRequestHandler ();
        }
        else if ( result instanceof Response )
        {
            return new JaxRsResponseHandler ( (Response)result );
        }
        else
        {
            throw new IllegalStateException ( String.format ( "Response type %s is unsupported", result.getClass () ) );
        }

    }

    /**
     * Add custom binders assigned to the method
     * <p>
     * Custom binders assigned to the method will be added to the binding
     * manager instance.
     * </p>
     *
     * @param manager
     *            the manager to add binders to
     * @param method
     *            the method to evaluate for additional binders
     */
    protected static void addMethodBinders ( final BindingManager manager, final Method method )
    {
        final ControllerBinder[] binders = method.getAnnotationsByType ( ControllerBinder.class );

        if ( binders == null )
        {
            return;
        }

        for ( final ControllerBinder binder : binders )
        {
            try
            {
                final Binder binderImpl = binder.value ().newInstance ();
                if ( binderImpl instanceof ControllerBinderParametersAware )
                {
                    ( (ControllerBinderParametersAware)binderImpl ).setParameters ( binder.parameters () );
                }
                manager.addBinder ( binderImpl );
            }
            catch ( InstantiationException | IllegalAccessException e )
            {
                manager.addBinder ( new ErrorBinder ( e ) );
            }
        }
    }
}


/*
 * 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.util.List;
import java.util.ArrayList;

/**
 * Represents the line and file mappings associated with a JSR-045
 * "stratum".
 *
 * @author Jayson Falkner
 * @author Shawn Bayern
 */
public class SmapStratum {

    //*********************************************************************
    // Class for storing LineInfo data

    /**
     * Represents a single LineSection in an SMAP, associated with
     * a particular stratum.
     */
    public static class LineInfo {
        private int inputStartLine = -1;
        private int outputStartLine = -1;
        private int lineFileID = 0;
        private int inputLineCount = 1;
        private int outputLineIncrement = 1;
        private boolean lineFileIDSet = false;

        /** Sets InputStartLine. */
        public void setInputStartLine(int inputStartLine) {
            if (inputStartLine < 0)
                throw new IllegalArgumentException("" + inputStartLine);
            this.inputStartLine = inputStartLine;
        }

        /** Sets OutputStartLine. */
        public void setOutputStartLine(int outputStartLine) {
            if (outputStartLine < 0)
                throw new IllegalArgumentException("" + outputStartLine);
            this.outputStartLine = outputStartLine;
        }

        /**
             * Sets lineFileID.  Should be called only when different from
             * that of prior LineInfo object (in any given context) or 0
             * if the current LineInfo has no (logical) predecessor.
             * <tt>LineInfo</tt> will print this file number no matter what.
             */
        public void setLineFileID(int lineFileID) {
            if (lineFileID < 0)
                throw new IllegalArgumentException("" + lineFileID);
            this.lineFileID = lineFileID;
            this.lineFileIDSet = true;
        }

        /** Sets InputLineCount. */
        public void setInputLineCount(int inputLineCount) {
            if (inputLineCount < 0)
                throw new IllegalArgumentException("" + inputLineCount);
            this.inputLineCount = inputLineCount;
        }

        /** Sets OutputLineIncrement. */
        public void setOutputLineIncrement(int outputLineIncrement) {
            if (outputLineIncrement < 0)
                throw new IllegalArgumentException("" + outputLineIncrement);
            this.outputLineIncrement = outputLineIncrement;
        }

        /**
         * Retrieves the current LineInfo as a String, print all values
         * only when appropriate (but LineInfoID if and only if it's been
         * specified, as its necessity is sensitive to context).
         */
        public String getString() {
            if (inputStartLine == -1 || outputStartLine == -1)
                throw new IllegalStateException();
            StringBuilder out = new StringBuilder();
            out.append(inputStartLine);
            if (lineFileIDSet)
                out.append("#" + lineFileID);
            if (inputLineCount != 1)
                out.append("," + inputLineCount);
            out.append(":" + outputStartLine);
            if (outputLineIncrement != 1)
                out.append("," + outputLineIncrement);
            out.append('\n');
            return out.toString();
        }

        public String toString() {
            return getString();
        }
    }

    //*********************************************************************
    // Private state

    private String stratumName;
    private List<String> fileNameList;
    private List<String> filePathList;
    private List<LineInfo> lineData;
    private int lastFileID;

    //*********************************************************************
    // Constructor

    /**
     * Constructs a new SmapStratum object for the given stratum name
     * (e.g., JSP).
     *
     * @param stratumName the name of the stratum (e.g., JSP)
     */
    public SmapStratum(String stratumName) {
        this.stratumName = stratumName;
        fileNameList = new ArrayList<String>();
        filePathList = new ArrayList<String>();
        lineData = new ArrayList<LineInfo>();
        lastFileID = 0;
    }

    //*********************************************************************
    // Methods to add mapping information

    /**
     * Adds record of a new file, by filename.
     *
     * @param filename the filename to add, unqualified by path.
     */
    public void addFile(String filename) {
        addFile(filename, filename);
    }

    /**
     * Adds record of a new file, by filename and path.  The path
     * may be relative to a source compilation path.
     *
     * @param filename the filename to add, unqualified by path
     * @param filePath the path for the filename, potentially relative
     *                 to a source compilation path
     */
    public void addFile(String filename, String filePath) {
        int pathIndex = filePathList.indexOf(filePath);
        if (pathIndex == -1) {
            fileNameList.add(filename);
            filePathList.add(filePath);
        }
    }

    /**
     * Combines consecutive LineInfos wherever possible
     */
    public void optimizeLineSection() {

/* Some debugging code
        for (int i = 0; i < lineData.size(); i++) {
            LineInfo li = lineData.get(i);
            System.out.print(li.toString());
        }
*/
        //Incorporate each LineInfo into the previous LineInfo's
        //outputLineIncrement, if possible
        int i = 0;
        while (i < lineData.size() - 1) {
            LineInfo li = lineData.get(i);
            LineInfo liNext = lineData.get(i + 1);
            if (!liNext.lineFileIDSet
                && liNext.inputStartLine == li.inputStartLine
                && liNext.inputLineCount == 1
                && li.inputLineCount == 1
                && liNext.outputStartLine
                    == li.outputStartLine
                        + li.inputLineCount * li.outputLineIncrement) {
                li.setOutputLineIncrement(
                    liNext.outputStartLine
                        - li.outputStartLine
                        + liNext.outputLineIncrement);
                lineData.remove(i + 1);
            } else {
                i++;
            }
        }

        //Incorporate each LineInfo into the previous LineInfo's
        //inputLineCount, if possible
        i = 0;
        while (i < lineData.size() - 1) {
            LineInfo li = lineData.get(i);
            LineInfo liNext = lineData.get(i + 1);
            if (!liNext.lineFileIDSet
                && liNext.inputStartLine == li.inputStartLine + li.inputLineCount
                && liNext.outputLineIncrement == li.outputLineIncrement
                && liNext.outputStartLine
                    == li.outputStartLine
                        + li.inputLineCount * li.outputLineIncrement) {
                li.setInputLineCount(li.inputLineCount + liNext.inputLineCount);
                lineData.remove(i + 1);
            } else {
                i++;
            }
        }
    }

    /**
     * Adds complete information about a simple line mapping.  Specify
     * all the fields in this method; the back-end machinery takes care
     * of printing only those that are necessary in the final SMAP.
     * (My view is that fields are optional primarily for spatial efficiency,
     * not for programmer convenience.  Could always add utility methods
     * later.)
     *
     * @param inputStartLine starting line in the source file
     *        (SMAP <tt>InputStartLine</tt>)
     * @param inputFileName the filepath (or name) from which the input comes
     *        (yields SMAP <tt>LineFileID</tt>)  Use unqualified names
     *        carefully, and only when they uniquely identify a file.
     * @param inputLineCount the number of lines in the input to map
     *        (SMAP <tt>LineFileCount</tt>)
     * @param outputStartLine starting line in the output file
     *        (SMAP <tt>OutputStartLine</tt>)
     * @param outputLineIncrement number of output lines to map to each
     *        input line (SMAP <tt>OutputLineIncrement</tt>).  <i>Given the
     *        fact that the name starts with "output", I continuously have
     *        the subconscious urge to call this field
     *        <tt>OutputLineExcrement</tt>.</i>
     */
    public void addLineData(
        int inputStartLine,
        String inputFileName,
        int inputLineCount,
        int outputStartLine,
        int outputLineIncrement) {
        // check the input - what are you doing here??
        int fileIndex = filePathList.indexOf(inputFileName);
        if (fileIndex == -1) // still
            throw new IllegalArgumentException(
                "inputFileName: " + inputFileName);

        //Jasper incorrectly SMAPs certain Nodes, giving them an
        //outputStartLine of 0.  This can cause a fatal error in
        //optimizeLineSection, making it impossible for Jasper to
        //compile the JSP.  Until we can fix the underlying
        //SMAPping problem, we simply ignore the flawed SMAP entries.
        if (outputStartLine == 0)
            return;

        // build the LineInfo
        LineInfo li = new LineInfo();
        li.setInputStartLine(inputStartLine);
        li.setInputLineCount(inputLineCount);
        li.setOutputStartLine(outputStartLine);
        li.setOutputLineIncrement(outputLineIncrement);
        if (fileIndex != lastFileID)
            li.setLineFileID(fileIndex);
        lastFileID = fileIndex;

        // save it
        lineData.add(li);
    }

    //*********************************************************************
    // Methods to retrieve information

    /**
     * Returns the name of the stratum.
     */
    public String getStratumName() {
        return stratumName;
    }

    /**
     * Returns the given stratum as a String:  a StratumSection,
     * followed by at least one FileSection and at least one LineSection.
     */
    public String getString() {
        // check state and initialize buffer
        if (fileNameList.size() == 0 || lineData.size() == 0)
            return null;

        StringBuilder out = new StringBuilder();

        // print StratumSection
        out.append("*S " + stratumName + "\n");

        // print FileSection
        out.append("*F\n");
        int bound = fileNameList.size();
        for (int i = 0; i < bound; i++) {
            if (filePathList.get(i) != null) {
                out.append("+ " + i + " " + fileNameList.get(i) + "\n");
                // Source paths must be relative, not absolute, so we
                // remove the leading "/", if one exists.
                String filePath = filePathList.get(i);
                if (filePath.startsWith("/")) {
                    filePath = filePath.substring(1);
                }
                out.append(filePath + "\n");
            } else {
                out.append(i + " " + fileNameList.get(i) + "\n");
            }
        }

        // print LineSection
        out.append("*L\n");
        bound = lineData.size();
        for (int i = 0; i < bound; i++) {
            LineInfo li = lineData.get(i);
            out.append(li.getString());
        }

        return out.toString();
    }

    public String toString() {
        return getString();
    }

}


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

package org.apache.jasper.compiler;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.io.UnsupportedEncodingException;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.logging.Logger;
import java.util.logging.Level;

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

import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.internal.compiler.ClassFile;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.Compiler;
import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
import org.eclipse.jdt.internal.compiler.ICompilerRequestor;
import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy;
import org.eclipse.jdt.internal.compiler.IProblemFactory;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;

/**
 * JDT class compiler. This compiler will load source dependencies from the
 * context classloader, reducing dramatically disk access during
 * the compilation process.
 *
 * @author Cocoon2
 * @author Remy Maucherat
 * @author Kin-man Chung   Modified to implement JavaCompiler
 */

public class JDTJavaCompiler implements JavaCompiler {

    private final Map settings = new HashMap();
    private JspCompilationContext ctxt;
    private ErrorDispatcher errDispatcher;
    private Logger log;
    private String javaFileName;

    public void init(JspCompilationContext ctxt,
                     ErrorDispatcher errDispatcher,
                     boolean suppressLogging) {
        this.errDispatcher = errDispatcher;
        this.ctxt = ctxt;
        log = Logger.getLogger(JDTJavaCompiler.class.getName());
        if (suppressLogging) {
            log.setLevel(Level.OFF);
        }
    }

    public void release() {
    }

    public void setExtdirs(String exts) {
        // no op here
    }

    public void setClassPath(List<File> cpath) {
       // No op here, because the current classloader is used.  However,
       // This may not include the system classpath specified in options
    }

    public long getClassLastModified() {
        File classFile = new File(ctxt.getClassFileName());
        return classFile.lastModified();
    }

    public Writer getJavaWriter(String javaFileName,
                                String javaEncoding)
            throws JasperException {

        this.javaFileName = javaFileName;

        Writer writer = null;
        try {
            writer = new OutputStreamWriter(
                        new FileOutputStream(javaFileName), javaEncoding);
        } catch (UnsupportedEncodingException ex) {
            errDispatcher.jspError("jsp.error.needAlternateJavaEncoding",
                                   javaEncoding);
        } catch (IOException ex) {
            errDispatcher.jspError("jsp.error.unableToCreateOutputWriter",
                                   javaFileName, ex);
        }
        return writer;
    }

    public void setDebug(boolean debug) {
        settings.put(CompilerOptions.OPTION_LineNumberAttribute,
                     CompilerOptions.GENERATE);
        settings.put(CompilerOptions.OPTION_SourceFileAttribute,
                     CompilerOptions.GENERATE);
        settings.put(CompilerOptions.OPTION_ReportDeprecation,
                     CompilerOptions.IGNORE);
        if (debug) {
            settings.put(CompilerOptions.OPTION_LocalVariableAttribute,
                         CompilerOptions.GENERATE);
        }
    }

    public void setSourceVM(String sourceVM) {
        if(sourceVM.equals("1.1")) {
            settings.put(CompilerOptions.OPTION_Source,
                         CompilerOptions.VERSION_1_1);
        } else if(sourceVM.equals("1.2")) {
            settings.put(CompilerOptions.OPTION_Source,
                         CompilerOptions.VERSION_1_2);
        } else if(sourceVM.equals("1.3")) {
            settings.put(CompilerOptions.OPTION_Source,
                         CompilerOptions.VERSION_1_3);
        } else if(sourceVM.equals("1.4")) {
            settings.put(CompilerOptions.OPTION_Source,
                         CompilerOptions.VERSION_1_4);
        } else if(sourceVM.equals("1.5")) {
            settings.put(CompilerOptions.OPTION_Source,
                         CompilerOptions.VERSION_1_5);
        } else if(sourceVM.equals("1.6")) {
            settings.put(CompilerOptions.OPTION_Source,
                         CompilerOptions.VERSION_1_6);
        } else if(sourceVM.equals("1.7")) {
            settings.put(CompilerOptions.OPTION_Source,
                         CompilerOptions.VERSION_1_7);
        } else if(sourceVM.equals("1.8")) {
            settings.put(CompilerOptions.OPTION_Source,
                         CompilerOptions.VERSION_1_8);
        } else {
            log.warning("Unknown source VM " + sourceVM + " ignored.");
            settings.put(CompilerOptions.OPTION_Source,
                    CompilerOptions.VERSION_1_5);
        }
    }

    public void setTargetVM(String targetVM) {
        if(targetVM.equals("1.1")) {
            settings.put(CompilerOptions.OPTION_TargetPlatform,
                         CompilerOptions.VERSION_1_1);
        } else if(targetVM.equals("1.2")) {
            settings.put(CompilerOptions.OPTION_TargetPlatform,
                         CompilerOptions.VERSION_1_2);
        } else if(targetVM.equals("1.3")) {
            settings.put(CompilerOptions.OPTION_TargetPlatform,
                         CompilerOptions.VERSION_1_3);
        } else if(targetVM.equals("1.4")) {
            settings.put(CompilerOptions.OPTION_TargetPlatform,
                         CompilerOptions.VERSION_1_4);
        } else if(targetVM.equals("1.5")) {
            settings.put(CompilerOptions.OPTION_TargetPlatform,
                         CompilerOptions.VERSION_1_5);
        } else if(targetVM.equals("1.6")) {
            settings.put(CompilerOptions.OPTION_TargetPlatform,
                         CompilerOptions.VERSION_1_6);
        } else if(targetVM.equals("1.7")) {
            settings.put(CompilerOptions.OPTION_TargetPlatform,
                         CompilerOptions.VERSION_1_7);
        } else if(targetVM.equals("1.8")) {
            settings.put(CompilerOptions.OPTION_TargetPlatform,
                         CompilerOptions.VERSION_1_8);
        } else {
            log.warning("Unknown target VM " + targetVM + " ignored.");
            settings.put(CompilerOptions.OPTION_TargetPlatform,
                    CompilerOptions.VERSION_1_5);
        }
    }

    public void saveClassFile(String className, String classFileName) {
        // class file are alwyas saved.
    }

    public void doJavaFile(boolean keep) {
        if (!keep) {
            File javaFile = new File(javaFileName);
            javaFile.delete();
        }
    }

    public JavacErrorDetail[] compile(final String targetClassName,
                                      final Node.Nodes pageNodes)
            throws JasperException {

        final String sourceFile = ctxt.getServletJavaFileName();
        final String outputDir =
            ctxt.getOptions().getScratchDir().getAbsolutePath();
        String packageName = ctxt.getServletPackageName();

        final ClassLoader classLoader = ctxt.getJspLoader();
        String[] fileNames = new String[] {sourceFile};
        String[] classNames = new String[] {targetClassName};
        final ArrayList<JavacErrorDetail> problemList =
                new ArrayList<JavacErrorDetail>();

        class CompilationUnit implements ICompilationUnit {

            String className;
            String sourceFile;

            CompilationUnit(String sourceFile, String className) {
                this.className = className;
                this.sourceFile = sourceFile;
            }

            public char[] getFileName() {
                return className.toCharArray();
            }

            public char[] getContents() {
                char[] result = null;
                Reader reader = null;
                try {
                    InputStreamReader isReader =
                        new InputStreamReader(new FileInputStream(sourceFile),
                                ctxt.getOptions().getJavaEncoding());
                    reader = new BufferedReader(isReader);
                    if (reader != null) {
                        char[] chars = new char[8192];
                        StringBuilder buf = new StringBuilder();
                        int count;
                        while ((count = reader.read(chars, 0,
                                                    chars.length)) > 0) {
                            buf.append(chars, 0, count);
                        }
                        result = new char[buf.length()];
                        buf.getChars(0, result.length, result, 0);
                    }
                } catch (IOException e) {
                    log.log(Level.SEVERE, "Compilation error", e);
                } finally {
                    if (reader != null) {
                        try {
                            reader.close();
                        } catch (IOException ioe) {
                            // Ignore
                        }
                    }
                }
                return result;
            }

            public char[] getMainTypeName() {
                int dot = className.lastIndexOf('.');
                if (dot > 0) {
                    return className.substring(dot + 1).toCharArray();
                }
                return className.toCharArray();
            }

            public char[][] getPackageName() {
                StringTokenizer izer =
                    new StringTokenizer(className, ".");
                char[][] result = new char[izer.countTokens()-1][];
                for (int i = 0; i < result.length; i++) {
                    String tok = izer.nextToken();
                    result[i] = tok.toCharArray();
                }
                return result;
            }

            public boolean ignoreOptionalProblems() {
                return true;
            }
        }

        final INameEnvironment env = new INameEnvironment() {

                public NameEnvironmentAnswer
                    findType(char[][] compoundTypeName) {
                    String result = "";
                    String sep = "";
                    for (int i = 0; i < compoundTypeName.length; i++) {
                        result += sep;
                        result += new String(compoundTypeName[i]);
                        sep = ".";
                    }
                    return findType(result);
                }

                public NameEnvironmentAnswer
                    findType(char[] typeName,
                             char[][] packageName) {
                        String result = "";
                        String sep = "";
                        for (int i = 0; i < packageName.length; i++) {
                            result += sep;
                            result += new String(packageName[i]);
                            sep = ".";
                        }
                        result += sep;
                        result += new String(typeName);
                        return findType(result);
                }

                private NameEnvironmentAnswer findType(String className) {

                    InputStream is = null;
                    try {
                        if (className.equals(targetClassName)) {
                            ICompilationUnit compilationUnit =
                                new CompilationUnit(sourceFile, className);
                            return
                                new NameEnvironmentAnswer(compilationUnit, null);
                        }
                        String resourceName =
                            className.replace('.', '/') + ".class";
                        is = classLoader.getResourceAsStream(resourceName);
                        if (is != null) {
                            byte[] classBytes;
                            byte[] buf = new byte[8192];
                            ByteArrayOutputStream baos =
                                new ByteArrayOutputStream(buf.length);
                            int count;
                            while ((count = is.read(buf, 0, buf.length)) > 0) {
                                baos.write(buf, 0, count);
                            }
                            baos.flush();
                            classBytes = baos.toByteArray();
                            char[] fileName = className.toCharArray();
                            ClassFileReader classFileReader =
                                new ClassFileReader(classBytes, fileName,
                                                    true);
                            return
                                new NameEnvironmentAnswer(classFileReader, null);
                        }
                    } catch (IOException exc) {
                        log.log(Level.SEVERE, "Compilation error", exc);
                    } catch (org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException exc) {
                        log.log(Level.SEVERE, "Compilation error", exc);
                    } finally {
                        if (is != null) {
                            try {
                                is.close();
                            } catch (IOException exc) {
                                // Ignore
                            }
                        }
                    }
                    return null;
                }

                private boolean isPackage(String result) {
                    if (result.equals(targetClassName)) {
                        return false;
                    }
                    String resourceName = result.replace('.', '/') + ".class";
                    InputStream is =
                        classLoader.getResourceAsStream(resourceName);
                    return is == null;
                }

                public boolean isPackage(char[][] parentPackageName,
                                         char[] packageName) {
                    String result = "";
                    String sep = "";
                    if (parentPackageName != null) {
                        for (int i = 0; i < parentPackageName.length; i++) {
                            result += sep;
                            String str = new String(parentPackageName[i]);
                            result += str;
                            sep = ".";
                        }
                    }
                    String str = new String(packageName);
                    if (Character.isUpperCase(str.charAt(0))) {
                        if (!isPackage(result)) {
                            return false;
                        }
                    }
                    result += sep;
                    result += str;
                    return isPackage(result);
                }

                public void cleanup() {
                }

            };

        final IErrorHandlingPolicy policy =
            DefaultErrorHandlingPolicies.proceedWithAllProblems();

        if (ctxt.getOptions().getJavaEncoding() != null) {
            settings.put(CompilerOptions.OPTION_Encoding,
                    ctxt.getOptions().getJavaEncoding());
        }

        final IProblemFactory problemFactory =
            new DefaultProblemFactory(Locale.getDefault());

        final ICompilerRequestor requestor = new ICompilerRequestor() {
                public void acceptResult(CompilationResult result) {
                    try {
                        if (result.hasProblems()) {
                            IProblem[] problems = safeGetProblems(result);
                            for (int i = 0; i < problems.length; i++) {
                                IProblem problem = problems[i];
                                if (problem.isError()) {
                                    String name =
                                        new String(problems[i].getOriginatingFileName());
                                    try {
                                        problemList.add(
                                            ErrorDispatcher.createJavacError(
                                                name,
                                                pageNodes,
                                                new StringBuilder(problem.getMessage()),
                                                problem.getSourceLineNumber()));
                                    } catch (JasperException e) {
                                        log.log(Level.SEVERE, "Error visiting node", e);
                                    }
                                }
                            }
                        }
                        if (problemList.isEmpty()) {
                            ClassFile[] classFiles = result.getClassFiles();
                            for (int i = 0; i < classFiles.length; i++) {
                                ClassFile classFile = classFiles[i];
                                char[][] compoundName =
                                    classFile.getCompoundName();
                                String className = "";
                                String sep = "";
                                for (int j = 0;
                                     j < compoundName.length; j++) {
                                    className += sep;
                                    className += new String(compoundName[j]);
                                    sep = ".";
                                }
                                byte[] bytes = classFile.getBytes();
                                String outFile = outputDir + "/" +
                                    className.replace('.', '/') + ".class";
                                FileOutputStream fout =
                                    new FileOutputStream(outFile);
                                BufferedOutputStream bos =
                                    new BufferedOutputStream(fout);
                                bos.write(bytes);
                                bos.close();
                            }
                        }
                    } catch (IOException exc) {
                        log.log(Level.SEVERE, "Compilation error", exc);
                    }
                }
            };

        ICompilationUnit[] compilationUnits =
            new ICompilationUnit[classNames.length];
        for (int i = 0; i < compilationUnits.length; i++) {
            compilationUnits[i] = new CompilationUnit(fileNames[i], classNames[i]);
        }

        Compiler compiler = new Compiler(env,
                                         policy,
                                         settings,
                                         requestor,
                                         problemFactory);
        compiler.compile(compilationUnits);

        if (problemList.isEmpty()) {
            return null;
        }
        return problemList.toArray(new JavacErrorDetail[]{});
    }

    private static boolean USE_INTROSPECTION_TO_INVOKE_GET_PROBLEM = false;
    private static java.lang.reflect.Method GET_PROBLEM_METH = null;
    /**
     * Invoke CompilationResult#getProblems safely so that it works with
     * 3.1.1 and more recent versions of the eclipse java compiler.
     * See https://jsp.dev.java.net/issues/show_bug.cgi?id=13
     *
     * @param result The compilation result.
     * @return The same object than CompilationResult#getProblems
     */
    private static final IProblem[] safeGetProblems(CompilationResult result) {
        if (!USE_INTROSPECTION_TO_INVOKE_GET_PROBLEM) {
            try {
                return result.getProblems();
            } catch (NoSuchMethodError re) {
                USE_INTROSPECTION_TO_INVOKE_GET_PROBLEM = true;
            }
        }
        try {
            if (GET_PROBLEM_METH == null) {
                GET_PROBLEM_METH = result.getClass()
                        .getDeclaredMethod("getProblems", new Class[] {});
            }
            //an array of a particular type can be casted into an array of a super type.
            return (IProblem[]) GET_PROBLEM_METH.invoke(result, null);
        } catch (Throwable e) {
            if (e instanceof RuntimeException) {
                throw (RuntimeException)e;
            } else {
                throw new RuntimeException(e);
            }
        }
    }
}


/*
 * LZMA2Options
 *
 * 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;
import java.io.IOException;
import org.tukaani.xz.lz.LZEncoder;
import org.tukaani.xz.lzma.LZMAEncoder;

/**
 * LZMA2 compression options.
 *

 * While this allows setting the LZMA2 compression options in detail,
 * often you only need LZMA2Options() or
 * LZMA2Options(int).
 */
public class LZMA2Options extends FilterOptions {
    /**
     * Minimum valid compression preset level is 0.
     */
    public static final int PRESET_MIN = 0;

    /**
     * Maximum valid compression preset level is 9.
     */
    public static final int PRESET_MAX = 9;

    /**
     * Default compression preset level is 6.
     */
    public static final int PRESET_DEFAULT = 6;

    /**
     * Minimum dictionary size is 4 KiB.
     */
    public static final int DICT_SIZE_MIN = 4096;

    /**
     * Maximum dictionary size for compression is 768 MiB.
     *

     * The decompressor supports bigger dictionaries, up to almost 2 GiB.
     * With HC4 the encoder would support dictionaries bigger than 768 MiB.
     * The 768 MiB limit comes from the current implementation of BT4 where
     * we would otherwise hit the limits of signed ints in array indexing.
     *

     * If you really need bigger dictionary for decompression,
     * use {@link LZMA2InputStream} directly.
     */
    public static final int DICT_SIZE_MAX = 768 << 20;

    /**
     * The default dictionary size is 8 MiB.
     */
    public static final int DICT_SIZE_DEFAULT = 8 << 20;

    /**
     * Maximum value for lc + lp is 4.
     */
    public static final int LC_LP_MAX = 4;

    /**
     * The default number of literal context bits is 3.
     */
    public static final int LC_DEFAULT = 3;

    /**
     * The default number of literal position bits is 0.
     */
    public static final int LP_DEFAULT = 0;

    /**
     * Maximum value for pb is 4.
     */
    public static final int PB_MAX = 4;

    /**
     * The default number of position bits is 2.
     */
    public static final int PB_DEFAULT = 2;

    /**
     * Compression mode: uncompressed.
     * The data is wrapped into a LZMA2 stream without compression.
     */
    public static final int MODE_UNCOMPRESSED = 0;

    /**
     * Compression mode: fast.
     * This is usually combined with a hash chain match finder.
     */
    public static final int MODE_FAST = LZMAEncoder.MODE_FAST;

    /**
     * Compression mode: normal.
     * This is usually combined with a binary tree match finder.
     */
    public static final int MODE_NORMAL = LZMAEncoder.MODE_NORMAL;

    /**
     * Minimum value for niceLen is 8.
     */
    public static final int NICE_LEN_MIN = 8;

    /**
     * Maximum value for niceLen is 273.
     */
    public static final int NICE_LEN_MAX = 273;

    /**
     * Match finder: Hash Chain 2-3-4
     */
    public static final int MF_HC4 = LZEncoder.MF_HC4;

    /**
     * Match finder: Binary tree 2-3-4
     */
    public static final int MF_BT4 = LZEncoder.MF_BT4;

    private static final int[] presetToDictSize = {
            1 << 18, 1 << 20, 1 << 21, 1 << 22, 1 << 22,
            1 << 23, 1 << 23, 1 << 24, 1 << 25, 1 << 26 };

    private static final int[] presetToDepthLimit = { 4, 8, 24, 48 };

    private int dictSize;
    private byte[] presetDict = null;
    private int lc;
    private int lp;
    private int pb;
    private int mode;
    private int niceLen;
    private int mf;
    private int depthLimit;

    /**
     * Creates new LZMA2 options and sets them to the default values.
     * This is equivalent to LZMA2Options(PRESET_DEFAULT).
     */
    public LZMA2Options() {
        try {
            setPreset(PRESET_DEFAULT);
        } catch (UnsupportedOptionsException e) {
            assert false;
            throw new RuntimeException();
        }
    }

    /**
     * Creates new LZMA2 options and sets them to the given preset.
     *
     * @throws      UnsupportedOptionsException
     *                          preset is not supported
     */
    public LZMA2Options(int preset) throws UnsupportedOptionsException {
        setPreset(preset);
    }

    /**
     * Creates new LZMA2 options and sets them to the given custom values.
     *
     * @throws      UnsupportedOptionsException
     *                          unsupported options were specified
     */
    public LZMA2Options(int dictSize, int lc, int lp, int pb, int mode,
                        int niceLen, int mf, int depthLimit)
            throws UnsupportedOptionsException {
        setDictSize(dictSize);
        setLcLp(lc, lp);
        setPb(pb);
        setMode(mode);
        setNiceLen(niceLen);
        setMatchFinder(mf);
        setDepthLimit(depthLimit);
    }

    /**
     * Sets the compression options to the given preset.
     *

     * The presets 0-3 are fast presets with medium compression.
     * The presets 4-6 are fairly slow presets with high compression.
     * The default preset (PRESET_DEFAULT) is 6.
     *

     * The presets 7-9 are like the preset 6 but use bigger dictionaries
     * and have higher compressor and decompressor memory requirements.
     * Unless the uncompressed size of the file exceeds 8 MiB,
     * 16 MiB, or 32 MiB, it is waste of memory to use the
     * presets 7, 8, or 9, respectively.
     *
     * @throws      UnsupportedOptionsException
     *                          preset is not supported
     */
    public void setPreset(int preset) throws UnsupportedOptionsException {
        if (preset < 0 || preset > 9)
            throw new UnsupportedOptionsException(
                    "Unsupported preset: " + preset);

        lc = LC_DEFAULT;
        lp = LP_DEFAULT;
        pb = PB_DEFAULT;
        dictSize = presetToDictSize[preset];

        if (preset <= 3) {
            mode = MODE_FAST;
            mf = MF_HC4;
            niceLen = preset <= 1 ? 128 : NICE_LEN_MAX;
            depthLimit = presetToDepthLimit[preset];
        } else {
            mode = MODE_NORMAL;
            mf = MF_BT4;
            niceLen = (preset == 4) ? 16 : (preset == 5) ? 32 : 64;
            depthLimit = 0;
        }
    }

    /**
     * Sets the dictionary size in bytes.
     *

     * The dictionary (or history buffer) holds the most recently seen
     * uncompressed data. Bigger dictionary usually means better compression.
     * However, using a dictioanary bigger than the size of the uncompressed
     * data is waste of memory.
     *

     * Any value in the range [DICT_SIZE_MIN, DICT_SIZE_MAX] is valid,
     * but sizes of 2^n and 2^n + 2^(n-1) bytes are somewhat
     * recommended.
     *
     * @throws      UnsupportedOptionsException
     *                          dictSize is not supported
     */
    public void setDictSize(int dictSize) throws UnsupportedOptionsException {
        if (dictSize < DICT_SIZE_MIN)
            throw new UnsupportedOptionsException(
                    "LZMA2 dictionary size must be at least 4 KiB: "
                    + dictSize + " B");

        if (dictSize > DICT_SIZE_MAX)
            throw new UnsupportedOptionsException(
                    "LZMA2 dictionary size must not exceed "
                    + (DICT_SIZE_MAX >> 20) + " MiB: " + dictSize + " B");

        this.dictSize = dictSize;
    }

    /**
     * Gets the dictionary size in bytes.
     */
    public int getDictSize() {
        return dictSize;
    }

    /**
     * Sets a preset dictionary. Use null to disable the use of
     * a preset dictionary. By default there is no preset dictionary.
     *

     * The .xz format doesn't support a preset dictionary for now.
     * Do not set a preset dictionary unless you use raw LZMA2.
     *

     * Preset dictionary can be useful when compressing many similar,
     * relatively small chunks of data independently from each other.
     * A preset dictionary should contain typical strings that occur in
     * the files being compressed. The most probable strings should be
     * near the end of the preset dictionary. The preset dictionary used
     * for compression is also needed for decompression.
     */
    public void setPresetDict(byte[] presetDict) {
        this.presetDict = presetDict;
    }

    /**
     * Gets the preset dictionary.
     */
    public byte[] getPresetDict() {
        return presetDict;
    }

    /**
     * Sets the number of literal context bits and literal position bits.
     *

     * The sum of lc and lp is limited to 4.
     * Trying to exceed it will throw an exception. This function lets
     * you change both at the same time.
     *
     * @throws      UnsupportedOptionsException
     *                          lc and lp
     *                          are invalid
     */
    public void setLcLp(int lc, int lp) throws UnsupportedOptionsException {
        if (lc < 0 || lp < 0 || lc > LC_LP_MAX || lp > LC_LP_MAX
                || lc + lp > LC_LP_MAX)
            throw new UnsupportedOptionsException(
                    "lc + lp must not exceed " + LC_LP_MAX + ": "
                    + lc + " + " + lp);

        this.lc = lc;
        this.lp = lp;
    }

    /**
     * Sets the number of literal context bits.
     *

     * All bytes that cannot be encoded as matches are encoded as literals.
     * That is, literals are simply 8-bit bytes that are encoded one at
     * a time.
     *

     * The literal coding makes an assumption that the highest lc
     * bits of the previous uncompressed byte correlate with the next byte.
     * For example, in typical English text, an upper-case letter is often
     * followed by a lower-case letter, and a lower-case letter is usually
     * followed by another lower-case letter. In the US-ASCII character set,
     * the highest three bits are 010 for upper-case letters and 011 for
     * lower-case letters. When lc is at least 3, the literal
     * coding can take advantage of this property in the  uncompressed data.
     *

     * The default value (3) is usually good. If you want maximum compression,
     * try setLc(4). Sometimes it helps a little, and sometimes it
     * makes compression worse. If it makes it worse, test for example
     * setLc(2) too.
     *
     * @throws      UnsupportedOptionsException
     *                          lc is invalid, or the sum
     *                          of lc and lp
     *                          exceed LC_LP_MAX
     */
    public void setLc(int lc) throws UnsupportedOptionsException {
        setLcLp(lc, lp);
    }

    /**
     * Sets the number of literal position bits.
     *

     * This affets what kind of alignment in the uncompressed data is
     * assumed when encoding literals. See {@link #setPb(int) setPb} for
     * more information about alignment.
     *
     * @throws      UnsupportedOptionsException
     *                          lp is invalid, or the sum
     *                          of lc and lp
     *                          exceed LC_LP_MAX
     */
    public void setLp(int lp) throws UnsupportedOptionsException {
        setLcLp(lc, lp);
    }

    /**
     * Gets the number of literal context bits.
     */
    public int getLc() {
        return lc;
    }

    /**
     * Gets the number of literal position bits.
     */
    public int getLp() {
        return lp;
    }

    /**
     * Sets the number of position bits.
     *

     * This affects what kind of alignment in the uncompressed data is
     * assumed in general. The default (2) means four-byte alignment
     * (2^pb = 2^2 = 4), which is often a good choice when
     * there's no better guess.
     *

     * When the alignment is known, setting the number of position bits
     * accordingly may reduce the file size a little. For example with text
     * files having one-byte alignment (US-ASCII, ISO-8859-*, UTF-8), using
     * setPb(0) can improve compression slightly. For UTF-16
     * text, setPb(1) is a good choice. If the alignment is
     * an odd number like 3 bytes, setPb(0) might be the best
     * choice.
     *

     * Even though the assumed alignment can be adjusted with
     * setPb and setLp, LZMA2 still slightly favors
     * 16-byte alignment. It might be worth taking into account when designing
     * file formats that are likely to be often compressed with LZMA2.
     *
     * @throws      UnsupportedOptionsException
     *                          pb is invalid
     */
    public void setPb(int pb) throws UnsupportedOptionsException {
        if (pb < 0 || pb > PB_MAX)
            throw new UnsupportedOptionsException(
                    "pb must not exceed " + PB_MAX + ": " + pb);

        this.pb = pb;
    }

    /**
     * Gets the number of position bits.
     */
    public int getPb() {
        return pb;
    }

    /**
     * Sets the compression mode.
     *

     * This specifies the method to analyze the data produced by
     * a match finder. The default is MODE_FAST for presets
     * 0-3 and MODE_NORMAL for presets 4-9.
     *

     * Usually MODE_FAST is used with Hash Chain match finders
     * and MODE_NORMAL with Binary Tree match finders. This is
     * also what the presets do.
     *

     * The special mode MODE_UNCOMPRESSED doesn't try to
     * compress the data at all (and doesn't use a match finder) and will
     * simply wrap it in uncompressed LZMA2 chunks.
     *
     * @throws      UnsupportedOptionsException
     *                          mode is not supported
     */
    public void setMode(int mode) throws UnsupportedOptionsException {
        if (mode < MODE_UNCOMPRESSED || mode > MODE_NORMAL)
            throw new UnsupportedOptionsException(
                    "Unsupported compression mode: " + mode);

        this.mode = mode;
    }

    /**
     * Gets the compression mode.
     */
    public int getMode() {
        return mode;
    }

    /**
     * Sets the nice length of matches.
     * Once a match of at least niceLen bytes is found,
     * the algorithm stops looking for better matches. Higher values tend
     * to give better compression at the expense of speed. The default
     * depends on the preset.
     *
     * @throws      UnsupportedOptionsException
     *                          niceLen is invalid
     */
    public void setNiceLen(int niceLen) throws UnsupportedOptionsException {
        if (niceLen < NICE_LEN_MIN)
            throw new UnsupportedOptionsException(
                    "Minimum nice length of matches is "
                    + NICE_LEN_MIN + " bytes: " + niceLen);

        if (niceLen > NICE_LEN_MAX)
            throw new UnsupportedOptionsException(
                    "Maximum nice length of matches is " + NICE_LEN_MAX
                    + ": " + niceLen);

        this.niceLen = niceLen;
    }

    /**
     * Gets the nice length of matches.
     */
    public int getNiceLen() {
        return niceLen;
    }

    /**
     * Sets the match finder type.
     *

     * Match finder has a major effect on compression speed, memory usage,
     * and compression ratio. Usually Hash Chain match finders are faster
     * than Binary Tree match finders. The default depends on the preset:
     * 0-3 use MF_HC4 and 4-9 use MF_BT4.
     *
     * @throws      UnsupportedOptionsException
     *                          mf is not supported
     */
    public void setMatchFinder(int mf) throws UnsupportedOptionsException {
        if (mf != MF_HC4 && mf != MF_BT4)
            throw new UnsupportedOptionsException(
                    "Unsupported match finder: " + mf);

        this.mf = mf;
    }

    /**
     * Gets the match finder type.
     */
    public int getMatchFinder() {
        return mf;
    }

    /**
     * Sets the match finder search depth limit.
     *

     * The default is a special value of 0 which indicates that
     * the depth limit should be automatically calculated by the selected
     * match finder from the nice length of matches.
     *

     * Reasonable depth limit for Hash Chain match finders is 4-100 and
     * 16-1000 for Binary Tree match finders. Using very high values can
     * make the compressor extremely slow with some files. Avoid settings
     * higher than 1000 unless you are prepared to interrupt the compression
     * in case it is taking far too long.
     *
     * @throws      UnsupportedOptionsException
     *                          depthLimit is invalid
     */
    public void setDepthLimit(int depthLimit)
            throws UnsupportedOptionsException {
        if (depthLimit < 0)
            throw new UnsupportedOptionsException(
                    "Depth limit cannot be negative: " + depthLimit);

        this.depthLimit = depthLimit;
    }

    /**
     * Gets the match finder search depth limit.
     */
    public int getDepthLimit() {
        return depthLimit;
    }

    public int getEncoderMemoryUsage() {
        return (mode == MODE_UNCOMPRESSED)
               ? UncompressedLZMA2OutputStream.getMemoryUsage()
               : LZMA2OutputStream.getMemoryUsage(this);
    }

    public FinishableOutputStream getOutputStream(FinishableOutputStream out,
                                                  ArrayCache arrayCache) {
        if (mode == MODE_UNCOMPRESSED)
            return new UncompressedLZMA2OutputStream(out, arrayCache);

        return new LZMA2OutputStream(out, this, arrayCache);
    }

    /**
     * Gets how much memory the LZMA2 decoder will need to decompress the data
     * that was encoded with these options and stored in a .xz file.
     *

     * The returned value may bigger than the value returned by a direct call
     * to {@link LZMA2InputStream#getMemoryUsage(int)} if the dictionary size
     * is not 2^n or 2^n + 2^(n-1) bytes. This is because the .xz
     * headers store the dictionary size in such a format and other values
     * are rounded up to the next such value. Such rounding is harmess except
     * it might waste some memory if an unsual dictionary size is used.
     *

     * If you use raw LZMA2 streams and unusual dictioanary size, call
     * {@link LZMA2InputStream#getMemoryUsage} directly to get raw decoder
     * memory requirements.
     */
    public int getDecoderMemoryUsage() {
        // Round the dictionary size up to the next 2^n or 2^n + 2^(n-1).
        int d = dictSize - 1;
        d |= d >>> 2;
        d |= d >>> 3;
        d |= d >>> 4;
        d |= d >>> 8;
        d |= d >>> 16;
        return LZMA2InputStream.getMemoryUsage(d + 1);
    }

    public InputStream getInputStream(InputStream in, ArrayCache arrayCache)
            throws IOException {
        return new LZMA2InputStream(in, dictSize, presetDict, arrayCache);
    }

    FilterEncoder getFilterEncoder() {
        return new LZMA2Encoder(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.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.ResourceBundle;
import java.util.ArrayList;
import java.net.MalformedURLException;

import org.apache.jasper.JasperException;
import org.xml.sax.SAXException;

/**
 * Class responsible for dispatching JSP parse and javac compilation errors
 * to the configured error handler.
 *
 * This class is also responsible for localizing any error codes before they
 * are passed on to the configured error handler.
 *
 * In the case of a Java compilation error, the compiler error message is
 * parsed into an array of JavacErrorDetail instances, which is passed on to
 * the configured error handler.
 *
 * @author Jan Luehe
 * @author Kin-man Chung
 */
public class ErrorDispatcher {

    private static final ResourceBundle bundle = ResourceBundle.getBundle(
        "org.apache.jasper.resources.messages");

    // Custom error handler
    private ErrorHandler errHandler;

    // Indicates whether the compilation was initiated by JspServlet or JspC
    private boolean jspcMode = false;

    /*
     * Constructor.
     *
     * @param jspcMode true if compilation has been initiated by JspC, false
     * otherwise
     */
    public ErrorDispatcher(boolean jspcMode) {
	// XXX check web.xml for custom error handler
	errHandler = new DefaultErrorHandler();
        this.jspcMode = jspcMode;
    }

    /*
     * Dispatches the given JSP parse error to the configured error handler.
     *
     * The given error code is localized. If it is not found in the
     * resource bundle for localized error messages, it is used as the error
     * message.
     *
     * @param errCode Error code
     */
    public void jspError(String errCode) throws JasperException {
	dispatch(null, errCode, null, null);
    }

    /*
     * Dispatches the given JSP parse error to the configured error handler.
     *
     * The given error code is localized. If it is not found in the
     * resource bundle for localized error messages, it is used as the error
     * message.
     *
     * @param where Error location
     * @param errCode Error code
     */
    public void jspError(Mark where, String errCode) throws JasperException {
	dispatch(where, errCode, null, null);
    }

    /*
     * Dispatches the given JSP parse error to the configured error handler.
     *
     * @param where Error location
     * @param e The exception whose message is used as the error message
     */
    public void jspError(Mark where, Exception e) throws JasperException {
	dispatch(where, e.getMessage(), null, e);
    }

    /*
     * Dispatches the given JSP parse error to the configured error handler.
     *
     * The given error code is localized. If it is not found in the
     * resource bundle for localized error messages, it is used as the error
     * message.
     *
     * @param n Node that caused the error
     * @param errCode Error code
     */
    public void jspError(Node n, String errCode) throws JasperException {
	dispatch(n.getStart(), errCode, null, null);
    }

    /*
     * Dispatches the given JSP parse error to the configured error handler.
     *
     * The given error code is localized. If it is not found in the
     * resource bundle for localized error messages, it is used as the error
     * message.
     *
     * @param errCode Error code
     * @param args Arguments for parametric replacement
     */
    public void jspError(String errCode, String ... args)
             throws JasperException {
	dispatch(null, errCode, args, null);
    }

    /*
     * Dispatches the given JSP parse error to the configured error handler.
     *
     * The given error code is localized. If it is not found in the
     * resource bundle for localized error messages, it is used as the error
     * message.
     *
     * @param where Error location
     * @param errCode Error code
     * @param args Arguments for parametric replacement
     */
    public void jspError(Mark where, String errCode, String ... args)
	        throws JasperException {
	dispatch(where, errCode, args, null);
    }

    /*
     * Dispatches the given JSP parse error to the configured error handler.
     *
     * The given error code is localized. If it is not found in the
     * resource bundle for localized error messages, it is used as the error
     * message.
     *
     * @param n Node that caused the error
     * @param errCode Error code
     * @param args Arguments for parametric replacement
     */
    public void jspError(Node n, String errCode, String ... args)
	        throws JasperException {
	dispatch(n.getStart(), errCode, args, null);
    }

    /*
     * Dispatches the given parsing exception to the configured error handler.
     *
     * @param e Parsing exception
     */
    public void jspError(Exception e) throws JasperException {
	dispatch(null, null, null, e);
    }

    /*
     * Dispatches the given JSP parse error to the configured error handler.
     *
     * The given error code is localized. If it is not found in the
     * resource bundle for localized error messages, it is used as the error
     * message.
     *
     * @param errCode Error code
     * @param arg Argument for parametric replacement
     * @param e Parsing exception
     */
    public void jspError(String errCode, String arg, Exception e)
	        throws JasperException {
	dispatch(null, errCode, new Object[] {arg}, e);
    }

    /*
     * Dispatches the given JSP parse error to the configured error handler.
     *
     * The given error code is localized. If it is not found in the
     * resource bundle for localized error messages, it is used as the error
     * message.
     *
     * @param n Node that caused the error
     * @param errCode Error code
     * @param arg Argument for parametric replacement
     * @param e Parsing exception
     */
    public void jspError(Node n, String errCode, String arg, Exception e)
	        throws JasperException {
	dispatch(n.getStart(), errCode, new Object[] {arg}, e);
    }

    /**
     * Creates and throws a new exception from the given JasperException,
     * by prepending the given location information (containing file name,
     * line number, and column number) to the message of the given exception,
     * and copying the stacktrace of the given exception to the new exception.
     *
     * @param where The location information (containing file name,
     * line number, and column number) to prepend
     * @param je The JasperException to amend
     */
    public void throwException(Mark where, JasperException je)
                throws JasperException {

	if (where == null) {
            throw je;
        }

	// Get file location
        String file = null;
        if (jspcMode) {
            // Get the full URL of the resource that caused the error
            try {
                file = where.getURL().toString();
            } catch (MalformedURLException me) {
                // Fallback to using context-relative path
                file = where.getFile();
            }
        } else {
            // Get the context-relative resource path, so as to not
            // disclose any local filesystem details
            file = where.getFile();
        }

	JasperException newEx = new JasperException(file + "("
                + where.getLineNumber() + "," + where.getColumnNumber()
                + ")" + " " + je.getMessage(),je.getCause());
        newEx.setStackTrace(je.getStackTrace());

        throw newEx;
    }

    /*
     * Dispatches the given javac compilation errors to the configured error
     * handler.
     *
     * @param javacErrors Array of javac compilation errors
     */
    public void javacError(JavacErrorDetail[] javacErrors)
            throws JasperException {

        errHandler.javacError(javacErrors);
    }

    /*
     * Dispatches the given compilation error report and exception to the
     * configured error handler.
     *
     * @param errorReport Compilation error report
     * @param e Compilation exception
     */
    public void javacError(String errorReport, Exception e)
                throws JasperException {

        errHandler.javacError(errorReport, e);
    }

    //*********************************************************************
    // Private utility methods

    /*
     * Dispatches the given JSP parse error to the configured error handler.
     *
     * The given error code is localized. If it is not found in the
     * resource bundle for localized error messages, it is used as the error
     * message.
     *
     * @param where Error location
     * @param errCode Error code
     * @param args Arguments for parametric replacement
     * @param e Parsing exception
     */
    private void dispatch(Mark where, String errCode, Object[] args,
			  Exception e) throws JasperException {
	String file = null;
	String errMsg = null;
	int line = -1;
	int column = -1;
	boolean hasLocation = false;

	// Localize
	if (errCode != null) {
	    errMsg = Localizer.getMessage(errCode, args);
	} else if (e != null) {
	    // give a hint about what's wrong
	    errMsg = e.getMessage();
	}

	// Get error location
	if (where != null) {
            if (jspcMode) {
                // Get the full URL of the resource that caused the error
                try {
                    file = where.getURL().toString();
                } catch (MalformedURLException me) {
                    // Fallback to using context-relative path
                    file = where.getFile();
                }
            } else {
                // Get the context-relative resource path, so as to not
                // disclose any local filesystem details
                file = where.getFile();
            }
	    line = where.getLineNumber();
	    column = where.getColumnNumber();
	    hasLocation = true;
	}

	// Get nested exception
	Exception nestedEx = e;
	if ((e instanceof SAXException)
	        && (((SAXException) e).getException() != null)) {
	    nestedEx = ((SAXException) e).getException();
	}

	if (hasLocation) {
	    errHandler.jspError(file, line, column, errMsg, nestedEx);
	} else {
	    errHandler.jspError(errMsg, nestedEx);
	}
    }

    /*
     * Parses the given Java compilation error message, which may contain one
     * or more compilation errors, into an array of JavacErrorDetail instances.
     *
     * Each JavacErrorDetail instance contains the information about a single
     * compilation error.
     *
     * @param errMsg Compilation error message that was generated by the
     *        javac compiler
     * @param fname Name of Java source file whose compilation failed
     *
     * @return Array of JavacErrorDetail instances corresponding to the
     *         compilation errors, or null if the given error message does not
     *         contain any compilation error line numbers
     */
    public static JavacErrorDetail[] parseJavacMessage(
                                Node.Nodes pageNodes,
                                String errMsg,
                                String fname)
                throws IOException, JasperException {

	ArrayList<JavacErrorDetail> errors = new ArrayList<JavacErrorDetail>();
	StringBuilder errMsgBuf = null;
	int lineNum = -1;
        JavacErrorDetail javacError = null;

        BufferedReader reader = new BufferedReader(new StringReader(errMsg));

        /*
         * Parse compilation errors. Each compilation error consists of a file
         * path and error line number, followed by a number of lines describing
         * the error.
         */
        String line = null;
        while ((line = reader.readLine()) != null) {

            /*
	     * Error line number is delimited by set of colons.
	     * Ignore colon following drive letter on Windows (fromIndex = 2).
	     * XXX Handle deprecation warnings that don't have line info
	     */
            int beginColon = line.indexOf(':', 2);
            int endColon = line.indexOf(':', beginColon + 1);
            if ((beginColon >= 0) && (endColon >= 0)) {
                if (javacError != null) {
                    // add previous error to error vector
                    errors.add(javacError);
		}

		String lineNumStr = line.substring(beginColon + 1, endColon);
                try {
                    lineNum = Integer.parseInt(lineNumStr);
                } catch (NumberFormatException e) {
                    // XXX
                }

                errMsgBuf = new StringBuilder();

                javacError = createJavacError(fname, pageNodes,
                                              errMsgBuf, lineNum);
            }

            // Ignore messages preceding first error
            if (errMsgBuf != null) {
                errMsgBuf.append(line);
                errMsgBuf.append("\n");
            }
        }

        // Add last error to error vector
        if (javacError != null) {
            errors.add(javacError);
        }
        reader.close();
        return errors.toArray(new JavacErrorDetail[0]);
    }

    /**
     * @param fname
     * @param page
     * @param errMsgBuf
     * @param lineNum
     * @return JavacErrorDetail The error details
     * @throws JasperException
     */
    public static JavacErrorDetail createJavacError(String fname,
                                                    Node.Nodes page,
                                                    StringBuilder errMsgBuf,
                                                    int lineNum)

            throws JasperException {

        JavacErrorDetail javacError;
        // Attempt to map javac error line number to line in JSP page
        ErrorVisitor errVisitor = new ErrorVisitor(lineNum);
        if (page != null)
            page.visit(errVisitor);
        Node errNode = errVisitor.getJspSourceNode();
        if ((errNode != null) && (errNode.getStart() != null)) {
            javacError = new JavacErrorDetail(
                    fname,
                    lineNum,
                    errNode.getStart().getFile(),
                    errNode.getStart().getLineNumber(),
                    errMsgBuf);
        } else {
            /*
             * javac error line number cannot be mapped to JSP page
             * line number. For example, this is the case if a
             * scriptlet is missing a closing brace, which causes
             * havoc with the try-catch-finally block that the code
             * generator places around all generated code: As a result
             * of this, the javac error line numbers will be outside
             * the range of begin and end java line numbers that were
             * generated for the scriptlet, and therefore cannot be
             * mapped to the start line number of the scriptlet in the
             * JSP page.
             * Include just the javac error info in the error detail.
             */
            javacError = new JavacErrorDetail(
                    fname,
                    lineNum,
                    errMsgBuf);
        }
        return javacError;
    }

    /*
     * Visitor responsible for mapping a line number in the generated servlet
     * source code to the corresponding JSP node.
     */
    static class ErrorVisitor extends Node.Visitor {

	// Java source line number to be mapped
	private int lineNum;

	/*
	 * JSP node whose Java source code range in the generated servlet
	 * contains the Java source line number to be mapped
	 */
	Node found;

	/*
	 * Constructor.
	 *
	 * @param lineNum Source line number in the generated servlet code
	 */
	public ErrorVisitor(int lineNum) {
	    this.lineNum = lineNum;
	}

	public void doVisit(Node n) throws JasperException {
	    if ((lineNum >= n.getBeginJavaLine())
		    && (lineNum < n.getEndJavaLine())) {
		found = n;
	    }
        }

	/*
	 * Gets the JSP node to which the source line number in the generated
	 * servlet code was mapped.
	 *
	 * @return JSP node to which the source line number in the generated
	 * servlet code was mapped
	 */
	public Node getJspSourceNode() {
	    return found;
	}
    }
}