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

package org.apache.jasper.runtime;

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;

import javax.el.ELException;
import javax.el.ELContext;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.jsp.JspContext;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.BodyContent;
import javax.servlet.jsp.tagext.VariableInfo;
import javax.servlet.jsp.el.VariableResolver;
import javax.servlet.jsp.el.ExpressionEvaluator;

import org.apache.jasper.compiler.Localizer;

/**
 * Implementation of a JSP Context Wrapper.
 *
 * The JSP Context Wrapper is a JspContext created and maintained by a tag
 * handler implementation. It wraps the Invoking JSP Context, that is, the
 * JspContext instance passed to the tag handler by the invoking page via
 * setJspContext().
 *
 * @author Kin-man Chung
 * @author Jan Luehe
 */
public class JspContextWrapper extends PageContext {

    // Invoking JSP context
    private PageContext invokingJspCtxt;

    private Hashtable<String, Object> pageAttributes;

    // ArrayList of NESTED scripting variables
    private ArrayList<String> nestedVars;

    // ArrayList of AT_BEGIN scripting variables
    private ArrayList<String> atBeginVars;

    // ArrayList of AT_END scripting variables
    private ArrayList<String> atEndVars;

    private Map<String, String> aliases;
    private HashMap<String, Object> originalNestedVars;
    private ELContext elContext;

    public JspContextWrapper(JspContext jspContext,
                             ArrayList<String> nestedVars,
                             ArrayList<String> atBeginVars,
                             ArrayList<String> atEndVars,
                             Map<String, String> aliases) {
        this.invokingJspCtxt = (PageContext) jspContext;
	this.nestedVars = nestedVars;
	this.atBeginVars = atBeginVars;
	this.atEndVars = atEndVars;
	this.pageAttributes = new Hashtable<String, Object>(16);
	this.aliases = aliases;

	if (nestedVars != null) {
	    this.originalNestedVars =
                new HashMap<String, Object>(nestedVars.size());
	}
	syncBeginTagFile();
    }

    public void initialize(Servlet servlet, ServletRequest request,
                           ServletResponse response, String errorPageURL,
                           boolean needsSession, int bufferSize,
                           boolean autoFlush)
        throws IOException, IllegalStateException, IllegalArgumentException
    {
    }

    public Object getAttribute(String name) {

	if (name == null) {
	    throw new NullPointerException(
	            Localizer.getMessage("jsp.error.attribute.null_name"));
	}

	return pageAttributes.get(name);
    }

    public Object getAttribute(String name, int scope) {

	if (name == null) {
	    throw new NullPointerException(
	            Localizer.getMessage("jsp.error.attribute.null_name"));
	}

	if (scope == PAGE_SCOPE) {
	    return pageAttributes.get(name);
	}

	return invokingJspCtxt.getAttribute(name, scope);
    }

    public void setAttribute(String name, Object value) {

	if (name == null) {
	    throw new NullPointerException(
	            Localizer.getMessage("jsp.error.attribute.null_name"));
	}

	if (value != null) {
	    pageAttributes.put(name, value);
	} else {
	    removeAttribute(name, PAGE_SCOPE);
	}
    }

    public void setAttribute(String name, Object value, int scope) {

	if (name == null) {
	    throw new NullPointerException(
	            Localizer.getMessage("jsp.error.attribute.null_name"));
	}

	if (scope == PAGE_SCOPE) {
	    if (value != null) {
		pageAttributes.put(name, value);
	    } else {
		removeAttribute(name, PAGE_SCOPE);
	    }
	} else {
	    invokingJspCtxt.setAttribute(name, value, scope);
	}
    }

    public Object findAttribute(String name) {

	if (name == null) {
	    throw new NullPointerException(
	            Localizer.getMessage("jsp.error.attribute.null_name"));
	}

        Object o = pageAttributes.get(name);
        if (o == null) {
	    o = invokingJspCtxt.getAttribute(name, REQUEST_SCOPE);
	    if (o == null) {
		if (getSession() != null) {
		    o = invokingJspCtxt.getAttribute(name, SESSION_SCOPE);
		}
		if (o == null) {
		    o = invokingJspCtxt.getAttribute(name, APPLICATION_SCOPE);
		}
	    }
	}

	return o;
    }

    public void removeAttribute(String name) {

	if (name == null) {
	    throw new NullPointerException(
	            Localizer.getMessage("jsp.error.attribute.null_name"));
	}

	pageAttributes.remove(name);
	invokingJspCtxt.removeAttribute(name, REQUEST_SCOPE);
	if (getSession() != null) {
	    invokingJspCtxt.removeAttribute(name, SESSION_SCOPE);
	}
	invokingJspCtxt.removeAttribute(name, APPLICATION_SCOPE);
    }

    public void removeAttribute(String name, int scope) {

	if (name == null) {
	    throw new NullPointerException(
	            Localizer.getMessage("jsp.error.attribute.null_name"));
	}

	if (scope == PAGE_SCOPE){
	    pageAttributes.remove(name);
	} else {
	    invokingJspCtxt.removeAttribute(name, scope);
	}
    }

    public int getAttributesScope(String name) {

	if (name == null) {
	    throw new NullPointerException(
	            Localizer.getMessage("jsp.error.attribute.null_name"));
	}

	if (pageAttributes.get(name) != null) {
	    return PAGE_SCOPE;
	} else {
	    return invokingJspCtxt.getAttributesScope(name);
	}
    }

    public Enumeration<String> getAttributeNamesInScope(int scope) {
        if (scope == PAGE_SCOPE) {
            return pageAttributes.keys();
	}

	return invokingJspCtxt.getAttributeNamesInScope(scope);
    }

    public void release() {
	invokingJspCtxt.release();
    }

    public JspWriter getOut() {
	return invokingJspCtxt.getOut();
    }

    public HttpSession getSession() {
	return invokingJspCtxt.getSession();
    }

    public Object getPage() {
	return invokingJspCtxt.getPage();
    }

    public ServletRequest getRequest() {
	return invokingJspCtxt.getRequest();
    }

    public ServletResponse getResponse() {
	return invokingJspCtxt.getResponse();
    }

    public Exception getException() {
	return invokingJspCtxt.getException();
    }

    public ServletConfig getServletConfig() {
	return invokingJspCtxt.getServletConfig();
    }

    public ServletContext getServletContext() {
	return invokingJspCtxt.getServletContext();
    }

    static public PageContext getRootPageContext(PageContext pc) {
        while (pc instanceof JspContextWrapper) {
            pc = ((JspContextWrapper)pc).invokingJspCtxt;
        }
        return pc;
    }

    public ELContext getELContext() {
        if (elContext == null) {
            PageContext pc = invokingJspCtxt;
            while (pc instanceof JspContextWrapper) {
                pc = ((JspContextWrapper)pc).invokingJspCtxt;
            }
            PageContextImpl pci = (PageContextImpl) pc;
            elContext = pci.getJspApplicationContext().createELContext(
                              invokingJspCtxt.getELContext().getELResolver());
            elContext.putContext(javax.servlet.jsp.JspContext.class, this);
            ((ELContextImpl)elContext).setVariableMapper(
                new VariableMapperImpl());
        }
        return elContext;
    }

    public void forward(String relativeUrlPath)
        throws ServletException, IOException
    {
	invokingJspCtxt.forward(relativeUrlPath);
    }

    public void include(String relativeUrlPath)
	throws ServletException, IOException
    {
	invokingJspCtxt.include(relativeUrlPath);
    }

    public void include(String relativeUrlPath, boolean flush)
	    throws ServletException, IOException {
	invokingJspCtxt.include(relativeUrlPath, flush);
    }

    public VariableResolver getVariableResolver() {
	return null;
    }

    public BodyContent pushBody() {
	return invokingJspCtxt.pushBody();
    }

    public JspWriter pushBody(Writer writer) {
	return invokingJspCtxt.pushBody(writer);
    }

    public JspWriter popBody() {
        return invokingJspCtxt.popBody();
    }

    public ExpressionEvaluator getExpressionEvaluator() {
	return invokingJspCtxt.getExpressionEvaluator();
    }

    public void handlePageException(Exception ex)
        throws IOException, ServletException
    {
	// Should never be called since handleException() called with a
	// Throwable in the generated servlet.
	handlePageException((Throwable) ex);
    }

    public void handlePageException(Throwable t)
        throws IOException, ServletException
    {
	invokingJspCtxt.handlePageException(t);
    }

    /**
     * Synchronize variables at begin of tag file
     */
    public void syncBeginTagFile() {
	saveNestedVariables();
    }

    /**
     * Synchronize variables before fragment invokation
     */
    public void syncBeforeInvoke() {
	copyTagToPageScope(VariableInfo.NESTED);
	copyTagToPageScope(VariableInfo.AT_BEGIN);
    }

    /**
     * Synchronize variables at end of tag file
     */
    public void syncEndTagFile() {
	copyTagToPageScope(VariableInfo.AT_BEGIN);
	copyTagToPageScope(VariableInfo.AT_END);
	restoreNestedVariables();
    }

    /**
     * Copies the variables of the given scope from the virtual page scope of
     * this JSP context wrapper to the page scope of the invoking JSP context.
     *
     * @param scope variable scope (one of NESTED, AT_BEGIN, or AT_END)
     */
    private void copyTagToPageScope(int scope) {
	Iterator<String> iter = null;

	switch (scope) {
	case VariableInfo.NESTED:
	    if (nestedVars != null) {
		iter = nestedVars.iterator();
	    }
	    break;
	case VariableInfo.AT_BEGIN:
	    if (atBeginVars != null) {
		iter = atBeginVars.iterator();
	    }
	    break;
	case VariableInfo.AT_END:
	    if (atEndVars != null) {
		iter = atEndVars.iterator();
	    }
	    break;
	}

	while ((iter != null) && iter.hasNext()) {
	    String varName = iter.next();
	    Object obj = getAttribute(varName);
	    varName = findAlias(varName);
	    if (obj != null) {
		invokingJspCtxt.setAttribute(varName, obj);
	    } else {
		invokingJspCtxt.removeAttribute(varName, PAGE_SCOPE);
	    }
	}
    }

    /**
     * Saves the values of any NESTED variables that are present in
     * the invoking JSP context, so they can later be restored.
     */
    private void saveNestedVariables() {
	if (nestedVars != null) {
	    Iterator<String> iter = nestedVars.iterator();
	    while (iter.hasNext()) {
		String varName = iter.next();
		varName = findAlias(varName);
		Object obj = invokingJspCtxt.getAttribute(varName);
		if (obj != null) {
		    originalNestedVars.put(varName, obj);
		}
	    }
	}
    }

    /**
     * Restores the values of any NESTED variables in the invoking JSP
     * context.
     */
    private void restoreNestedVariables() {
	if (nestedVars != null) {
	    Iterator<String> iter = nestedVars.iterator();
	    while (iter.hasNext()) {
		String varName = iter.next();
		varName = findAlias(varName);
		Object obj = originalNestedVars.get(varName);
		if (obj != null) {
		    invokingJspCtxt.setAttribute(varName, obj);
		} else {
		    invokingJspCtxt.removeAttribute(varName, PAGE_SCOPE);
		}
	    }
	}
    }

    /**
     * Checks to see if the given variable name is used as an alias, and if so,
     * returns the variable name for which it is used as an alias.
     *
     * @param varName The variable name to check
     * @return The variable name for which varName is used as an alias, or
     * varName if it is not being used as an alias
     */
    private String findAlias(String varName) {

	if (aliases == null)
	    return varName;

	String alias = aliases.get(varName);
	if (alias == null) {
	    return varName;
	}
	return alias;
    }
}


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

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;

/**
 * This helper class may be used to do sophisticated redirection of
 * System.out and System.err.
 *
 * @author Remy Maucherat
 */
public class SystemLogHandler extends PrintStream {

    // ----------------------------------------------------------- Constructors

    /**
     * Construct the handler to capture the output of the given steam.
     */
    public SystemLogHandler(PrintStream wrapped) {
        super(wrapped);
        this.wrapped = wrapped;
    }

    // ----------------------------------------------------- Instance Variables

    /**
     * Wrapped PrintStream.
     */
    protected PrintStream wrapped = null;

    /**
     * Thread <-> PrintStream associations.
     */
    protected static final ThreadLocal streams = new ThreadLocal();

    /**
     * Thread <-> ByteArrayOutputStream associations.
     */
    protected static final ThreadLocal data = new ThreadLocal();

    // --------------------------------------------------------- Public Methods

    public PrintStream getWrapped() {
      return wrapped;
    }

    /**
     * Start capturing thread's output.
     */
    public static void setThread() {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        data.set(baos);
        streams.set(new PrintStream(baos));
    }

    /**
     * Stop capturing thread's output and return captured data as a String.
     */
    public static String unsetThread() {
        ByteArrayOutputStream baos =
            (ByteArrayOutputStream) data.get();
        if (baos == null) {
            return null;
        }
        streams.set(null);
        data.set(null);
        return baos.toString();
    }

    // ------------------------------------------------------ Protected Methods

    /**
     * Find PrintStream to which the output must be written to.
     */
    protected PrintStream findStream() {
        PrintStream ps = (PrintStream) streams.get();
        if (ps == null) {
            ps = wrapped;
        }
        return ps;
    }

    // ---------------------------------------------------- PrintStream Methods

    public void flush() {
        findStream().flush();
    }

    public void close() {
        findStream().close();
    }

    public boolean checkError() {
        return findStream().checkError();
    }

    protected void setError() {
        //findStream().setError();
    }

    public void write(int b) {
        findStream().write(b);
    }

    public void write(byte[] b)
        throws IOException {
        findStream().write(b);
    }

    public void write(byte[] buf, int off, int len) {
        findStream().write(buf, off, len);
    }

    public void print(boolean b) {
        findStream().print(b);
    }

    public void print(char c) {
        findStream().print(c);
    }

    public void print(int i) {
        findStream().print(i);
    }

    public void print(long l) {
        findStream().print(l);
    }

    public void print(float f) {
        findStream().print(f);
    }

    public void print(double d) {
        findStream().print(d);
    }

    public void print(char[] s) {
        findStream().print(s);
    }

    public void print(String s) {
        findStream().print(s);
    }

    public void print(Object obj) {
        findStream().print(obj);
    }

    public void println() {
        findStream().println();
    }

    public void println(boolean x) {
        findStream().println(x);
    }

    public void println(char x) {
        findStream().println(x);
    }

    public void println(int x) {
        findStream().println(x);
    }

    public void println(long x) {
        findStream().println(x);
    }

    public void println(float x) {
        findStream().println(x);
    }

    public void println(double x) {
        findStream().println(x);
    }

    public void println(char[] x) {
        findStream().println(x);
    }

    public void println(String x) {
        findStream().println(x);
    }

    public void println(Object x) {
        findStream().println(x);
    }

}


/*******************************************************************************
 * Copyright (c) 2015, 2016 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:S2131
 *******************************************************************************/
package org.eclipse.packagedrone.repo.adapter.deb.aspect.internal;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.zip.GZIPOutputStream;

import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;
import org.eclipse.packagedrone.repo.Severity;
import org.eclipse.packagedrone.repo.adapter.deb.aspect.DistributionInformation;
import org.eclipse.packagedrone.repo.signing.SigningService;
import org.eclipse.packagedrone.repo.utils.HashHelper;
import org.eclipse.packagedrone.utils.deb.Packages;
import org.eclipse.scada.utils.str.StringHelper;

import com.google.common.hash.HashCode;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;

/**
 * Build a repository

 * Before packages are added with
 * {@link #addPackage(String, String, String, PackageInformation)} all
 * distributions must be created with
 * {@link #addDistribution(String, DistributionInformation)}
 */
public class RepoBuilder
{

    private static final DateFormat DATE_FORMAT;

    static
    {
        DATE_FORMAT = new SimpleDateFormat ( "EEE, dd MMM YYYY HH:mm:ss z", Locale.US );
        DATE_FORMAT.setTimeZone ( TimeZone.getTimeZone ( "UTC" ) );
    }

    public static class Distribution
    {
        private final String name;

        private final DistributionInformation information;

        private final Map<String, Component> components = new TreeMap<> ();

        public Distribution ( final String name, final DistributionInformation information )
        {
            this.name = name;
            this.information = information;

            // populate components
            for ( final String component : information.getComponents () )
            {
                this.components.put ( component, new Component ( component, information.getArchitectures () ) );
            }
        }

        public DistributionInformation getInformation ()
        {
            return this.information;
        }

        public Map<String, Component> getComponents ()
        {
            return Collections.unmodifiableMap ( this.components );
        }

        public String getName ()
        {
            return this.name;
        }

        public void addPackage ( final String component, final String architecture, final PackageInformation packageInfo, final ValidationListener validationListener )
        {
            final Component comp = this.components.get ( component );
            if ( comp == null )
            {
                return;
            }

            if ( "all".equals ( architecture ) )
            {
                for ( final String arch : this.information.getArchitectures () )
                {
                    comp.addPackage ( "binary-" + arch, arch, packageInfo );
                }
            }
            else
            {
                if ( !this.information.getArchitectures ().contains ( architecture ) )
                {
                    if ( validationListener != null )
                    {
                        validationListener.validationMessage ( Severity.WARNING, String.format ( "Architecture '%s' is not configured. Package will be ignored.", architecture ) );
                    }
                    return;
                }

                comp.addPackage ( "binary-" + architecture, architecture, packageInfo );
            }
        }
    }

    public static class Component
    {
        private final String name;

        private final Map<String, SubComponent> subComponents = new HashMap<> ();

        public Component ( final String name, final SortedSet<String> archs )
        {
            this.name = name;

            for ( final String arch : archs )
            {
                final String subName = "binary-" + arch;
                this.subComponents.put ( subName, new SubComponent ( subName, arch ) );
            }
        }

        public String getName ()
        {
            return this.name;
        }

        public void addPackage ( final String subComponent, final String architecture, final PackageInformation packageInfo )
        {
            final SubComponent sub = this.subComponents.get ( subComponent );
            if ( sub == null )
            {
                return;
            }

            sub.addPackage ( packageInfo );
        }

        public Map<String, SubComponent> getSubComponents ()
        {
            return Collections.unmodifiableMap ( this.subComponents );
        }
    }

    public static class SubComponent
    {
        private final String name;

        private final StringWriter sw = new StringWriter ();

        private final PrintWriter pw = new PrintWriter ( this.sw );

        private final String architecture;

        public SubComponent ( final String name, final String architecture )
        {
            this.name = name;
            this.architecture = architecture;
        }

        public String getName ()
        {
            return this.name;
        }

        public String getArchitecture ()
        {
            return this.architecture;
        }

        public void addPackage ( final PackageInformation packageInfo )
        {
            final Map<String, String> values = new HashMap<> ( packageInfo.getControl () );

            values.put ( "Filename", packageInfo.getPoolName () );
            values.put ( "Size", Long.toString ( packageInfo.getFileSize () ) );

            if ( !values.containsKey ( "Description-md5" ) )
            {
                values.put ( "Description-md5", Packages.makeDescriptionMd5 ( values.get ( "Description" ) ) );
            }

            // add checksum entries

            for ( final Map.Entry<String, String> entry : packageInfo.getChecksums ().entrySet () )
            {
                final String v = entry.getValue ();
                if ( v != null )
                {
                    values.put ( entry.getKey (), v );
                }
            }

            try
            {
                Packages.writeBinaryPackageValues ( this.pw, values );
            }
            catch ( final IOException e )
            {
                throw new RuntimeException ( "Failed to write package stream", e );
            }

            this.pw.print ( "\n" );
        }

        public byte[] toReleaseFile ( final Distribution dist, final Component comp )
        {
            final StringWriter sw = new StringWriter ();

            final DistributionInformation info = dist.getInformation ();

            writeOptional ( sw, "Version", info.getVersion () );
            writeOptional ( sw, "Origin", info.getOrigin () );
            writeOptional ( sw, "Label", info.getLabel () );
            writeOptional ( sw, "Archive", info.getSuite () );

            sw.write ( "Component: " + comp.getName () + "\n" );
            sw.write ( "Architecture: " + this.architecture + "\n" );

            return sw.toString ().getBytes ( StandardCharsets.UTF_8 );
        }

        public byte[] toPackageFile ()
        {
            return this.sw.toString ().getBytes ( StandardCharsets.UTF_8 );
        }
    }

    public static class PackageInformation
    {
        private final String poolName;

        private final Map<String, String> control;

        private final long fileSize;

        private final Map<String, String> checksums;

        public PackageInformation ( final String poolName, final long fileSize, final Map<String, String> control, final Map<String, String> checksums )
        {
            this.poolName = poolName;
            this.fileSize = fileSize;
            this.control = control;
            this.checksums = checksums;
        }

        public long getFileSize ()
        {
            return this.fileSize;
        }

        public Map<String, String> getControl ()
        {
            return this.control;
        }

        public String getPoolName ()
        {
            return this.poolName;
        }

        public Map<String, String> getChecksums ()
        {
            return this.checksums;
        }
    }

    private final Map<String, Distribution> distributions = new HashMap<> ();

    private final SigningService signingService;

    public RepoBuilder ( final SigningService signingService )
    {
        this.signingService = signingService;
    }

    public void addDistribution ( final String name, final DistributionInformation information )
    {
        this.distributions.put ( name, new Distribution ( name, information ) );
    }

    public void addPackage ( final String distribution, final String component, final String architecture, final PackageInformation packageInfo )
    {
        addPackage ( distribution, component, architecture, packageInfo, null );
    }

    public void addPackage ( final String distribution, final String component, final String architecture, final PackageInformation packageInfo, final ValidationListener validationListener )
    {
        final Distribution dist = this.distributions.get ( distribution );
        if ( dist == null )
        {
            return; // ignore
        }

        dist.addPackage ( component, architecture, packageInfo, validationListener );
    }

    private static class Checksums
    {
        private final Map<String, HashCode> codes;

        private final int size;

        public Checksums ( final Map<String, HashCode> result, final int length )
        {
            this.codes = result;
            this.size = length;
        }

        public Map<String, HashCode> getCodes ()
        {
            return this.codes;
        }

        public int getSize ()
        {
            return this.size;
        }

        public static Checksums create ( final byte[] data ) throws IOException
        {
            final Map<String, HashFunction> functions = new HashMap<> ();

            functions.put ( "MD5Sum", Hashing.md5 () );
            functions.put ( "SHA1", Hashing.sha1 () );
            functions.put ( "SHA256", Hashing.sha256 () );

            final Map<String, HashCode> result = HashHelper.createChecksums ( new ByteArrayInputStream ( data ), functions );

            return new Checksums ( result, data.length );
        }
    }

    public void spoolOut ( final SpoolOutHandler handler ) throws IOException
    {
        for ( final Distribution dist : this.distributions.values () )
        {
            final SortedMap<String, Checksums> checksums = new TreeMap<> ();

            for ( final Component comp : dist.getComponents ().values () )
            {
                for ( final SubComponent sub : comp.getSubComponents ().values () )
                {
                    spoolOutFile ( checksums, "dists/" + dist.getName (), String.format ( "%s/%s/Release", comp.getName (), sub.getName () ), "text/plain", sub.toReleaseFile ( dist, comp ), handler );

                    final byte[] pkgData = sub.toPackageFile ();

                    spoolOutFile ( checksums, "dists/" + dist.getName (), String.format ( "%s/%s/Packages", comp.getName (), sub.getName () ), "text/plain", pkgData, handler );
                    spoolOutFile ( checksums, "dists/" + dist.getName (), String.format ( "%s/%s/Packages.gz", comp.getName (), sub.getName () ), "application/x-gzip", compressGzip ( pkgData ), handler );
                    spoolOutFile ( checksums, "dists/" + dist.getName (), String.format ( "%s/%s/Packages.bz2", comp.getName (), sub.getName () ), "application/x-bzip2", compressBzip2 ( pkgData ), handler );
                }
            }

            spoolOutDistRelease ( dist, checksums, handler );
        }
    }

    private byte[] compressGzip ( final byte[] data ) throws IOException
    {
        final ByteArrayOutputStream bos = new ByteArrayOutputStream ();
        final GZIPOutputStream gos = new GZIPOutputStream ( bos );

        gos.write ( data );

        gos.close ();
        return bos.toByteArray ();
    }

    private byte[] compressBzip2 ( final byte[] data ) throws IOException
    {
        final ByteArrayOutputStream bos = new ByteArrayOutputStream ();
        final BZip2CompressorOutputStream b2os = new BZip2CompressorOutputStream ( bos );

        b2os.write ( data );

        b2os.close ();
        return bos.toByteArray ();
    }

    private void spoolOutDistRelease ( final Distribution dist, final SortedMap<String, Checksums> checksums, final SpoolOutHandler handler ) throws IOException
    {
        final StringWriter sw = new StringWriter ();

        final DistributionInformation info = dist.getInformation ();

        writeOptional ( sw, "Origin", info.getOrigin () );
        writeOptional ( sw, "Label", info.getLabel () );
        writeOptional ( sw, "Suite", info.getSuite () );
        writeOptional ( sw, "Version", info.getVersion () );
        writeOptional ( sw, "Codename", info.getCodename () );
        write ( sw, "Date", DATE_FORMAT.format ( new Date () ) );
        write ( sw, "Components", StringHelper.join ( dist.getComponents ().keySet (), " " ) );
        write ( sw, "Architectures", StringHelper.join ( info.getArchitectures (), " " ) );
        writeOptional ( sw, "Description", info.getDescription () );

        {
            // create checksum fields

            final StringWriter md5 = new StringWriter ();
            final StringWriter sha1 = new StringWriter ();
            final StringWriter sha256 = new StringWriter ();
            final PrintWriter md5Pw = new PrintWriter ( md5 );
            final PrintWriter sha1Pw = new PrintWriter ( sha1 );
            final PrintWriter sha256Pw = new PrintWriter ( sha256 );

            for ( final Map.Entry<String, Checksums> entry : checksums.entrySet () )
            {
                addChecksum ( md5Pw, entry.getKey (), entry.getValue (), "MD5Sum" );
                addChecksum ( sha1Pw, entry.getKey (), entry.getValue (), "SHA1" );
                addChecksum ( sha256Pw, entry.getKey (), entry.getValue (), "SHA256" );
            }

            write ( sw, "MD5Sum", md5.toString () );
            write ( sw, "SHA1", sha1.toString () );
            write ( sw, "SHA256", sha256.toString () );
        }

        final byte[] data = sw.toString ().getBytes ( StandardCharsets.UTF_8 );
        handler.spoolOut ( String.format ( "dists/%s/Release", dist.getName () ), "text/plain", new ByteArrayInputStream ( data ) );

        if ( this.signingService != null )
        {
            handler.spoolOut ( String.format ( "dists/%s/Release.gpg", dist.getName () ), "text/plain", new ByteArrayInputStream ( sign ( data, false ) ) );
            handler.spoolOut ( String.format ( "dists/%s/InRelease", dist.getName () ), "text/plain", new ByteArrayInputStream ( sign ( data, true ) ) );

            final ByteArrayOutputStream bos = new ByteArrayOutputStream ();
            this.signingService.printPublicKey ( bos );
            bos.close ();

            handler.spoolOut ( "GPG-KEY", "text/plain", new ByteArrayInputStream ( bos.toByteArray () ) );
        }
    }

    private byte[] sign ( final byte[] data, final boolean inline ) throws IOException
    {
        final ByteArrayOutputStream bos = new ByteArrayOutputStream ();
        try
        {
            this.signingService.sign ( new ByteArrayInputStream ( data ), bos, inline );
        }
        catch ( final Exception e )
        {
            throw new RuntimeException ( "Failed to sign", e );
        }
        finally
        {
            bos.close ();
        }

        return bos.toByteArray ();
    }

    private void addChecksum ( final PrintWriter writer, final String name, final Checksums chk, final String alg )
    {
        final HashCode hashCode = chk.getCodes ().get ( alg );
        writer.format ( "\n %s %16d %s", hashCode.toString (), chk.getSize (), name );
    }

    private void spoolOutFile ( final Map<String, Checksums> checksums, final String prefix, final String fileName, final String mimeType, final byte[] data, final SpoolOutHandler handler ) throws IOException
    {
        checksums.put ( fileName, Checksums.create ( data ) );
        handler.spoolOut ( prefix + "/" + fileName, mimeType, new ByteArrayInputStream ( data ) );
    }

    /**
     * Write field only when the value is set
     *
     * @param writer
     *            the writer to use
     * @param fieldName
     *            the name of field
     * @param value
     *            the value
     */
    protected static void writeOptional ( final StringWriter writer, final String fieldName, final String value )
    {
        if ( value != null )
        {
            write ( writer, fieldName, value );
        }
    }

    /**
     * Write a field
     *
     * @param writer
     *            the writer to use
     * @param fieldName
     *            the field name
     * @param value
     *            the value, should not be null since this would
     *            cause the string <q>null</q> in the file.
     */
    protected static void write ( final StringWriter writer, final String fieldName, final String value )
    {
        writer.write ( fieldName + ": " + value + "\n" );
    }
}


/*******************************************************************************
 * Copyright (c) 2014, 2016 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:S2131
 *******************************************************************************/
package org.eclipse.packagedrone.repo.xml;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Iterator;
import java.util.function.Consumer;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.eclipse.packagedrone.repo.xml.internal.Activator;
import org.eclipse.packagedrone.utils.xml.XmlToolsFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * A helper class when working with XML documents
 *

 * This class is not thread-safe and methods may throw Exceptions when accessing
 * XML parsers from multiple threads concurrently.
 *

 */
public class XmlHelper
{
    public static final class NodeListIterator implements Iterator<Node>
    {
        private final NodeList list;

        private int index;

        public NodeListIterator ( final NodeList list )
        {
            this.list = list;
        }

        @Override
        public Node next ()
        {
            return this.list.item ( this.index++ );
        }

        @Override
        public boolean hasNext ()
        {
            return this.index < this.list.getLength ();
        }
    }

    /**
     * Iterate over the direct child elements of an element
     */
    public static final class ElementIterator implements Iterator<Element>
    {
        private final Element element;

        private int index;

        private final String name;

        public ElementIterator ( final Element element )
        {
            this ( element, null );
        }

        public ElementIterator ( final Element element, final String name )
        {
            this.element = element;
            this.name = name;
        }

        private Element peek ()
        {
            Node node;
            while ( ( node = this.element.getChildNodes ().item ( this.index ) ) != null )
            {
                if ( ! ( node instanceof Element ) )
                {
                    this.index++;
                    continue;
                }

                final Element ele = (Element)node;
                if ( this.name != null && !ele.getNodeName ().equals ( this.name ) )
                {
                    this.index++;
                    continue;
                }
                return ele;
            }
            // out of nodes
            return null;
        }

        @Override
        public Element next ()
        {
            final Element ele = peek ();
            if ( ele != null )
            {
                this.index++;
            }
            return ele;
        }

        @Override
        public boolean hasNext ()
        {
            // we could cache the result for quicker checking
            return peek () != null;
        }
    }

    private final TransformerFactory transformerFactory;

    private final XPathFactory xpathFactory;

    private final DocumentBuilderFactory dbf;

    private final DocumentBuilderFactory dbfNs;

    private final XmlToolsFactory tools;

    public XmlHelper ()
    {
        this.tools = Activator.getXmlToolsFactory ();
        this.dbf = this.tools.newDocumentBuilderFactory ();
        this.dbfNs = this.tools.newDocumentBuilderFactory ();
        this.dbfNs.setNamespaceAware ( true );

        this.transformerFactory = this.tools.newTransformerFactory ();
        this.xpathFactory = this.tools.newXPathFactory ();
    }

    /**
     * @deprecated Instead the class
     *             {@link org.eclipse.packagedrone.utils.xml.XmlToolsFactory}
     *             should be
     *             used.
     */
    @Deprecated
    public static XPathFactory createXPathFactory ()
    {
        return Activator.getXmlToolsFactory ().newXPathFactory ();
    }

    public Document create ()
    {
        try
        {
            return this.dbf.newDocumentBuilder ().newDocument ();
        }
        catch ( final ParserConfigurationException e )
        {
            throw new RuntimeException ( e );
        }
    }

    public Document createNs ()
    {
        try
        {
            return this.dbfNs.newDocumentBuilder ().newDocument ();
        }
        catch ( final ParserConfigurationException e )
        {
            throw new RuntimeException ( e );
        }
    }

    public DocumentBuilder getBuilder () throws Exception
    {
        return this.dbf.newDocumentBuilder ();
    }

    public Document parse ( final InputStream stream ) throws Exception
    {
        return this.dbf.newDocumentBuilder ().parse ( stream );
    }

    public Document parseNs ( final InputStream stream ) throws Exception
    {
        return this.dbfNs.newDocumentBuilder ().parse ( stream );
    }

    public String toString ( final Node doc )
    {
        try
        {
            final StringWriter sw = new StringWriter ();
            write ( doc, sw );
            sw.close ();
            return sw.toString ();
        }
        catch ( final Exception e )
        {
            throw new RuntimeException ( e );
        }
    }

    public void write ( final Node doc, final OutputStream stream ) throws Exception
    {
        write ( doc, new StreamResult ( stream ) );
    }

    public void write ( final Node doc, final Writer writer ) throws Exception
    {
        write ( doc, new StreamResult ( writer ) );
    }

    public void write ( final Node doc, final Result result ) throws TransformerException
    {
        final Transformer transformer = this.transformerFactory.newTransformer ();
        final DOMSource source = new DOMSource ( doc );
        transformer.setOutputProperty ( OutputKeys.INDENT, "yes" );
        transformer.setOutputProperty ( OutputKeys.ENCODING, "UTF-8" );
        transformer.setOutputProperty ( "{http://xml.apache.org/xslt}indent-amount", "2" );
        transformer.transform ( source, result );
    }

    public static void write ( final TransformerFactory transformerFactory, final Node node, final Result result ) throws TransformerException
    {
        write ( transformerFactory, node, result, null );
    }

    public static void write ( final TransformerFactory transformerFactory, final Node node, final Result result, final Consumer<Transformer> transformerCustomizer ) throws TransformerException
    {
        final Transformer transformer = transformerFactory.newTransformer ();
        final DOMSource source = new DOMSource ( node );

        transformer.setOutputProperty ( OutputKeys.INDENT, "yes" );
        transformer.setOutputProperty ( OutputKeys.ENCODING, "UTF-8" );
        transformer.setOutputProperty ( "{http://xml.apache.org/xslt}indent-amount", "2" );

        if ( transformerCustomizer != null )
        {
            transformerCustomizer.accept ( transformer );
        }

        transformer.transform ( source, result );
    }

    public byte[] toData ( final Node doc ) throws Exception
    {
        final ByteArrayOutputStream out = new ByteArrayOutputStream ();
        write ( doc, out );
        out.close ();
        return out.toByteArray ();
    }

    public String getElementValue ( final Node element, final String path ) throws Exception
    {
        return getElementValue ( path ( element, path ) );
    }

    public static String getElementValue ( final Node element, final XPathExpression expression ) throws Exception
    {
        return getElementValue ( executePath ( element, expression ) );
    }

    public static String getElementValue ( final NodeList list )
    {
        for ( final Node n : iter ( list ) )
        {
            return text ( n );
        }
        return null;
    }

    private static String text ( final Node node )
    {
        return node.getTextContent ();
    }

    public static Iterable<Element> iterElement ( final Element element, final String name )
    {
        return new Iterable<Element> () {

            @Override
            public Iterator<Element> iterator ()
            {
                return new ElementIterator ( element, name );
            }
        };
    }

    public static Iterable<Node> iter ( final NodeList list )
    {
        return new Iterable<Node> () {

            @Override
            public Iterator<Node> iterator ()
            {
                return new NodeListIterator ( list );
            }
        };
    }

    public NodeList path ( final Node node, final String path ) throws XPathExpressionException
    {
        final XPath xpath = this.xpathFactory.newXPath ();
        final XPathExpression expression = xpath.compile ( path );
        return executePath ( node, expression );
    }

    public static NodeList executePath ( final Node node, final XPathExpression expression ) throws XPathExpressionException
    {
        return (NodeList)expression.evaluate ( node, XPathConstants.NODESET );
    }

    /**
     * Create a new element and add it as the last child
     *
     * @param parent
     *            the parent of the new element
     * @param name
     *            the name of the element
     * @return the new element
     */
    public static Element addElement ( final Element parent, final String name )
    {
        final Element ele = parent.getOwnerDocument ().createElement ( name );
        parent.appendChild ( ele );
        return ele;
    }

    public static Element addElement ( final Element parent, final String name, final Object value )
    {
        final Element ele = addElement ( parent, name );
        if ( value != null )
        {
            ele.setTextContent ( value.toString () );
        }
        return ele;
    }

    public static Element addOptionalElement ( final Element parent, final String name, final Object value )
    {
        if ( value == null )
        {
            return null;
        }

        final Element ele = addElement ( parent, name );
        ele.setTextContent ( value.toString () );
        return ele;
    }

    /**
     * Create a new element and add it as the first child
     *
     * @param parent
     *            the parent to which to add the element
     * @param name
     *            the name of the element
     * @return the new element
     */
    public static Element addElementFirst ( final Element parent, final String name )
    {
        final Element ele = parent.getOwnerDocument ().createElement ( name );
        parent.insertBefore ( ele, null );
        return ele;
    }

    public static void fixSize ( final Element element )
    {
        final int len = element.getChildNodes ().getLength ();

        if ( len > 0 )
        {
            element.setAttribute ( "size", Integer.toString ( len ) );
        }
        else
        {
            element.getParentNode ().removeChild ( element );
        }
    }

    /**
     * Get the text value of the first element with the matching name
     *

     * Assuming you have an XML file:
     *
     *

     * <parent>
     *   <foo>bar</foo>
     *   <hello>world</hello>
     * </parent>
     *

     *
     * Calling {@link #getText(Element, String)} with "parent" as element and
     * "hello" as name, it would return "world".
     *

     *
     * @param ele
     *            the element to check
     * @param name
     *            the name of the child element
     * @return the text value of the element
     */
    public static String getText ( final Element ele, final String name )
    {
        for ( final Node child : iter ( ele.getChildNodes () ) )
        {
            if ( ! ( child instanceof Element ) )
            {
                continue;
            }

            final Element childEle = (Element)child;

            if ( !childEle.getNodeName ().equals ( name ) )
            {
                continue;
            }

            return childEle.getTextContent ();
        }
        return null;
    }

}


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

package org.apache.jasper.runtime;

import java.io.CharArrayReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.Writer;
import java.io.OutputStreamWriter;
import javax.servlet.ServletResponse;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.BodyContent;
import org.apache.jasper.Constants;

/**
 * Write text to a character-output stream, buffering characters so as
 * to provide for the efficient writing of single characters, arrays,
 * and strings.
 *
 * Provide support for discarding for the output that has been buffered.
 *
 * @author Rajiv Mordani
 * @author Jan Luehe
 */
public class BodyContentImpl extends BodyContent {

    private static final String LINE_SEPARATOR = System.getProperty(
                                                    "line.separator");

    private char[] cb;
    private int nextChar;
    private boolean closed;

    // Enclosed writer to which any output is written
    private Writer writer;

    // See comment in setWriter()
    private int bufferSizeSave;

    /**
     * Constructor.
     */
    public BodyContentImpl(JspWriter enclosingWriter) {
        super(enclosingWriter);
	bufferSize = Constants.DEFAULT_TAG_BUFFER_SIZE;
	cb = new char[bufferSize];
	nextChar = 0;
	closed = false;
    }

    /**
     * Write a single character.
     */
    public void write(int c) throws IOException {
	if (writer != null) {
	    writer.write(c);
	} else {
	    ensureOpen();
	    if (nextChar >= bufferSize) {
		reAllocBuff (1);
	    }
	    cb[nextChar++] = (char) c;
	}
    }

    /**
     * Write a portion of an array of characters.
     *
     *
 Ordinarily this method stores characters from the given array into
     * this stream's buffer, flushing the buffer to the underlying stream as
     * needed.  If the requested length is at least as large as the buffer,
     * however, then this method will flush the buffer and write the characters
     * directly to the underlying stream.  Thus redundant
     * DiscardableBufferedWriters will not copy data
     * unnecessarily.
     *
     * @param cbuf A character array
     * @param off Offset from which to start reading characters
     * @param len Number of characters to write
     */
    public void write(char[] cbuf, int off, int len) throws IOException {
	if (writer != null) {
	    writer.write(cbuf, off, len);
	} else {
	    ensureOpen();

	    if ((off < 0) || (off > cbuf.length) || (len < 0) ||
		((off + len) > cbuf.length) || ((off + len) < 0)) {
		throw new IndexOutOfBoundsException();
	    } else if (len == 0) {
		return;
	    }

	    if (len >= bufferSize - nextChar)
		reAllocBuff (len);

	    System.arraycopy(cbuf, off, cb, nextChar, len);
	    nextChar+=len;
	}
    }

    /**
     * Write an array of characters.  This method cannot be inherited from the
     * Writer class because it must suppress I/O exceptions.
     */
    public void write(char[] buf) throws IOException {
	if (writer != null) {
	    writer.write(buf);
	} else {
	    write(buf, 0, buf.length);
	}
    }

    /**
     * Write a portion of a String.
     *
     * @param s String to be written
     * @param off Offset from which to start reading characters
     * @param len Number of characters to be written
     */
    public void write(String s, int off, int len) throws IOException {
	if (writer != null) {
	    writer.write(s, off, len);
	} else {
	    ensureOpen();
	    if (len >= bufferSize - nextChar)
		reAllocBuff(len);

	    s.getChars(off, off + len, cb, nextChar);
	    nextChar += len;
	}
    }

    /**
     * Write a string.  This method cannot be inherited from the Writer class
     * because it must suppress I/O exceptions.
     */
    public void write(String s) throws IOException {
	if (writer != null) {
	    writer.write(s);
	} else {
	    write(s, 0, s.length());
	}
    }

    /**
     * Write a line separator.  The line separator string is defined by the
     * system property <tt>line.separator</tt>, and is not necessarily a single
     * newline ('\n') character.
     *
     * @throws IOException If an I/O error occurs
     */
    public void newLine() throws IOException {
	if (writer != null) {
	    writer.write(LINE_SEPARATOR);
	} else {
	    write(LINE_SEPARATOR);
	}
    }

    /**
     * Print a boolean value.  The string produced by {@link
     * java.lang.String#valueOf(boolean)} is translated into bytes
     * according to the platform's default character encoding, and these bytes
     * are written in exactly the manner of the {@link
     * #write(int)} method.
     *
     * @param b The boolean to be printed
     * @throws IOException
     */
    public void print(boolean b) throws IOException {
	if (writer != null) {
	    writer.write(b ? "true" : "false");
	} else {
	    write(b ? "true" : "false");
	}
    }

    /**
     * Print a character.  The character is translated into one or more bytes
     * according to the platform's default character encoding, and these bytes
     * are written in exactly the manner of the {@link
     * #write(int)} method.
     *
     * @param c The char to be printed
     * @throws IOException
     */
    public void print(char c) throws IOException {
	if (writer != null) {
	    writer.write(String.valueOf(c));
	} else {
	    write(String.valueOf(c));
	}
    }

    /**
     * Print an integer.  The string produced by {@link
     * java.lang.String#valueOf(int)} is translated into bytes according
     * to the platform's default character encoding, and these bytes are
     * written in exactly the manner of the {@link #write(int)}
     * method.
     *
     * @param i The int to be printed
     * @throws IOException
     */
    public void print(int i) throws IOException {
	if (writer != null) {
	    writer.write(String.valueOf(i));
	} else {
	    write(String.valueOf(i));
	}
    }

    /**
     * Print a long integer.  The string produced by {@link
     * java.lang.String#valueOf(long)} is translated into bytes
     * according to the platform's default character encoding, and these bytes
     * are written in exactly the manner of the
     * {@link #write(int)} method.
     *
     * @param l The long to be printed
     * @throws IOException
     */
    public void print(long l) throws IOException {
	if (writer != null) {
	    writer.write(String.valueOf(l));
	} else {
	    write(String.valueOf(l));
	}
    }

    /**
     * Print a floating-point number.  The string produced by {@link
     * java.lang.String#valueOf(float)} is translated into bytes
     * according to the platform's default character encoding, and these bytes
     * are written in exactly the manner of the
     * {@link #write(int)} method.
     *
     * @param f The float to be printed
     * @throws IOException
     */
    public void print(float f) throws IOException {
	if (writer != null) {
	    writer.write(String.valueOf(f));
	} else {
	    write(String.valueOf(f));
	}
    }

    /**
     * Print a double-precision floating-point number.  The string produced by
     * {@link java.lang.String#valueOf(double)} is translated into
     * bytes according to the platform's default character encoding, and these
     * bytes are written in exactly the manner of the {@link
     * #write(int)} method.
     *
     * @param d The double to be printed
     * @throws IOException
     */
    public void print(double d) throws IOException {
	if (writer != null) {
	    writer.write(String.valueOf(d));
	} else {
	    write(String.valueOf(d));
	}
    }

    /**
     * Print an array of characters.  The characters are converted into bytes
     * according to the platform's default character encoding, and these bytes
     * are written in exactly the manner of the
     * {@link #write(int)} method.
     *
     * @param s The array of chars to be printed
     *
     * @throws NullPointerException If s is null
     * @throws IOException
     */
    public void print(char[] s) throws IOException {
	if (writer != null) {
	    writer.write(s);
	} else {
	    write(s);
	}
    }

    /**
     * Print a string.  If the argument is null then the string
     * "null" is printed.  Otherwise, the string's characters are
     * converted into bytes according to the platform's default character
     * encoding, and these bytes are written in exactly the manner of the
     * {@link #write(int)} method.
     *
     * @param s The String to be printed
     * @throws IOException
     */
    public void print(String s) throws IOException {
	if (s == null) s = "null";
	if (writer != null) {
	    writer.write(s);
	} else {
	    write(s);
	}
    }

    /**
     * Print an object.  The string produced by the {@link
     * java.lang.String#valueOf(Object)} method is translated into bytes
     * according to the platform's default character encoding, and these bytes
     * are written in exactly the manner of the
     * {@link #write(int)} method.
     *
     * @param obj The Object to be printed
     * @throws IOException
     */
    public void print(Object obj) throws IOException {
	if (writer != null) {
	    writer.write(String.valueOf(obj));
	} else {
	    write(String.valueOf(obj));
	}
    }

    /**
     * Terminate the current line by writing the line separator string.  The
     * line separator string is defined by the system property
     * line.separator, and is not necessarily a single newline
     * character ('\n').
     *
     * @throws IOException
     */
    public void println() throws IOException {
	newLine();
    }

    /**
     * Print a boolean value and then terminate the line.  This method behaves
     * as though it invokes {@link #print(boolean)} and then
     * {@link #println()}.
     *
     * @throws IOException
     */
    public void println(boolean x) throws IOException {
        print(x);
        println();
    }

    /**
     * Print a character and then terminate the line.  This method behaves as
     * though it invokes {@link #print(char)} and then
     * {@link #println()}.
     *
     * @throws IOException
     */
    public void println(char x) throws IOException {
        print(x);
        println();
    }

    /**
     * Print an integer and then terminate the line.  This method behaves as
     * though it invokes {@link #print(int)} and then
     * {@link #println()}.
     *
     * @throws IOException
     */
    public void println(int x) throws IOException {
        print(x);
        println();
    }

    /**
     * Print a long integer and then terminate the line.  This method behaves
     * as though it invokes {@link #print(long)} and then
     * {@link #println()}.
     *
     * @throws IOException
     */
    public void println(long x) throws IOException {
        print(x);
        println();
    }

    /**
     * Print a floating-point number and then terminate the line.  This method
     * behaves as though it invokes {@link #print(float)} and then
     * {@link #println()}.
     *
     * @throws IOException
     */
    public void println(float x) throws IOException {
        print(x);
        println();
    }

    /**
     * Print a double-precision floating-point number and then terminate the
     * line.  This method behaves as though it invokes {@link
     * #print(double)} and then {@link #println()}.
     *
     * @throws IOException
     */
    public void println(double x) throws IOException{
        print(x);
        println();
    }

    /**
     * Print an array of characters and then terminate the line.  This method
     * behaves as though it invokes {@link #print(char[])} and
     * then {@link #println()}.
     *
     * @throws IOException
     */
    public void println(char x[]) throws IOException {
        print(x);
        println();
    }

    /**
     * Print a String and then terminate the line.  This method behaves as
     * though it invokes {@link #print(String)} and then
     * {@link #println()}.
     *
     * @throws IOException
     */
    public void println(String x) throws IOException {
        print(x);
        println();
    }

    /**
     * Print an Object and then terminate the line.  This method behaves as
     * though it invokes {@link #print(Object)} and then
     * {@link #println()}.
     *
     * @throws IOException
     */
    public void println(Object x) throws IOException {
        print(x);
        println();
    }

    /**
     * Clear the contents of the buffer. If the buffer has been already
     * been flushed then the clear operation shall throw an IOException
     * to signal the fact that some data has already been irrevocably
     * written to the client response stream.
     *
     * @throws IOException If an I/O error occurs
     */
    public void clear() throws IOException {
	if (writer != null) {
	    throw new IOException();
	} else {
	    nextChar = 0;
	}
    }

    /**
     * Clears the current contents of the buffer. Unlike clear(), this
     * mehtod will not throw an IOException if the buffer has already been
     * flushed. It merely clears the current content of the buffer and
     * returns.
     *
     * @throws IOException If an I/O error occurs
     */
    public void clearBuffer() throws IOException {
        if (writer == null) {
	    this.clear();
	}
    }

    /**
     * Close the stream, flushing it first.  Once a stream has been closed,
     * further write() or flush() invocations will cause an IOException to be
     * thrown.  Closing a previously-closed stream, however, has no effect.
     *
     * @throws IOException If an I/O error occurs
     */
    public void close() throws IOException {
	if (writer != null) {
	    writer.close();
	} else {
	    cb = null;
	    closed = true;
	}
    }

    /**
     * @return the number of bytes unused in the buffer
     */
    public int getRemaining() {
	return (writer == null) ? bufferSize-nextChar : 0;
    }

    /**
     * Return the value of this BodyJspWriter as a Reader.
     * Note: this is after evaluation!!  There are no scriptlets,
     * etc in this stream.
     *
     * @return the value of this BodyJspWriter as a Reader
     */
    public Reader getReader() {
	return (writer == null) ? new CharArrayReader (cb, 0, nextChar) : null;
    }

    /**
     * Return the value of the BodyJspWriter as a String.
     * Note: this is after evaluation!!  There are no scriptlets,
     * etc in this stream.
     *
     * @return the value of the BodyJspWriter as a String
     */
    public String getString() {
	return (writer == null) ? new String(cb, 0, nextChar) : null;
    }

    /**
     * Write the contents of this BodyJspWriter into a Writer.
     * Subclasses are likely to do interesting things with the
     * implementation so some things are extra efficient.
     *
     * @param out The writer into which to place the contents of this body
     * evaluation
     */
    public void writeOut(Writer out) throws IOException {
	if (writer == null) {
	    out.write(cb, 0, nextChar);
	    // Flush not called as the writer passed could be a BodyContent and
	    // it doesn't allow to flush.
	}
    }

    public static void main (String[] args) throws Exception {
	char[] buff = {'f','o','o','b','a','r','b','a','z','y'};
   	BodyContentImpl bodyContent
	    = new BodyContentImpl(new JspWriterImpl(null, 100, false));
	bodyContent.println (buff);
	System.out.println (bodyContent.getString ());
	bodyContent.writeOut (new PrintWriter(
                                  new OutputStreamWriter(System.out,"UTF-8")));
    }

    /**
     * Sets the writer to which all output is written.
     */
    void setWriter(Writer writer) {
	this.writer = writer;
	if (writer != null) {
	    // According to the spec, the JspWriter returned by
	    // JspContext.pushBody(java.io.Writer writer) must behave as
	    // though it were unbuffered. This means that its getBufferSize()
	    // must always return 0. The implementation of
	    // JspWriter.getBufferSize() returns the value of JspWriter's
	    // 'bufferSize' field, which is inherited by this class.
	    // Therefore, we simply save the current 'bufferSize' (so we can
	    // later restore it should this BodyContentImpl ever be reused by
	    // a call to PageContext.pushBody()) before setting it to 0.
	    if (bufferSize != 0) {
		bufferSizeSave = bufferSize;
		bufferSize = 0;
	    }
	} else {
	    bufferSize = bufferSizeSave;
	    clearBody();
	}
    }

    private void ensureOpen() throws IOException {
	if (closed) throw new IOException("Stream closed");
    }

    /**
     * Reallocates buffer since the spec requires it to be unbounded.
     */
    private void reAllocBuff(int len) {

        if (bufferSize + len <= cb.length) {
            bufferSize = cb.length;
            return;
        }

        if (len < cb.length) {
            len = cb.length;
        }

        bufferSize = cb.length + len;
        char[] tmp = new char[bufferSize];

	System.arraycopy(cb, 0, tmp, 0, cb.length);
	cb = tmp;
	tmp = null;

    }

}


/*
 * 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.*;
import javax.servlet.jsp.tagext.FunctionInfo;
import org.apache.jasper.JasperException;

/**
 * This class generates functions mappers for the EL expressions in the page.
 * Instead of a global mapper, a mapper is used for ecah call to EL
 * evaluator, thus avoiding the prefix overlapping and redefinition
 * issues.
 *
 * @author Kin-man Chung
 */

public class ELFunctionMapper {
    private int currFunc = 0;
    StringBuilder ds;  // Contains codes to initialize the functions mappers.
    StringBuilder ss;  // Contains declarations of the functions mappers.

    /**
     * Creates the functions mappers for all EL expressions in the JSP page.
     *
     * @param compiler Current compiler, mainly for accessing error dispatcher.
     * @param page The current compilation unit.
     */
    public static void map(Compiler compiler, Node.Nodes page)
		throws JasperException {

	ELFunctionMapper map = new ELFunctionMapper();
	map.ds = new StringBuilder();
	map.ss = new StringBuilder();

	page.visit(map.new ELFunctionVisitor());

	// Append the declarations to the root node
	String ds = map.ds.toString();
	if (ds.length() > 0) {
	    Node root = page.getRoot();
	    new Node.Declaration(map.ss.toString(), null, root);
	    new Node.Declaration("static {\n" + ds + "}\n", null, root);
	}
    }

    /**
     * A visitor for the page.  The places where EL is allowed are scanned
     * for functions, and if found functions mappers are created.
     */
    class ELFunctionVisitor extends Node.Visitor {

	/**
	 * Use a global name map to facilitate reuse of function maps.
	 * The key used is prefix:function:uri.
	 */
	private HashMap<String, String> gMap = new HashMap<String, String>();

	public void visit(Node.ParamAction n) throws JasperException {
	    doMap(n.getValue());
	    visitBody(n);
	}

	public void visit(Node.IncludeAction n) throws JasperException {
	    doMap(n.getPage());
	    visitBody(n);
	}

	public void visit(Node.ForwardAction n) throws JasperException {
	    doMap(n.getPage());
	    visitBody(n);
	}

        public void visit(Node.SetProperty n) throws JasperException {
	    doMap(n.getValue());
	    visitBody(n);
	}

        public void visit(Node.UseBean n) throws JasperException {
	    doMap(n.getBeanName());
	    visitBody(n);
	}

        public void visit(Node.PlugIn n) throws JasperException {
	    doMap(n.getHeight());
	    doMap(n.getWidth());
	    visitBody(n);
	}

        public void visit(Node.JspElement n) throws JasperException {

	    Node.JspAttribute[] attrs = n.getJspAttributes();
	    for (int i = 0; attrs != null && i < attrs.length; i++) {
		doMap(attrs[i]);
	    }
	    doMap(n.getNameAttribute());
	    visitBody(n);
	}

        public void visit(Node.UninterpretedTag n) throws JasperException {

	    Node.JspAttribute[] attrs = n.getJspAttributes();
	    for (int i = 0; attrs != null && i < attrs.length; i++) {
		doMap(attrs[i]);
	    }
	    visitBody(n);
	}

        public void visit(Node.CustomTag n) throws JasperException {
	    Node.JspAttribute[] attrs = n.getJspAttributes();
	    for (int i = 0; attrs != null && i < attrs.length; i++) {
		doMap(attrs[i]);
	    }
	    visitBody(n);
	}

        public void visit(Node.ELExpression n) throws JasperException {
	    doMap(n.getEL());
	}

	private void doMap(Node.JspAttribute attr)
		throws JasperException {
	    if (attr != null) {
		doMap(attr.getEL());
	    }
	}

        /**
         * Creates function mappers, if needed, from ELNodes
         */
	private void doMap(ELNode.Nodes el)
		throws JasperException {

            // Only care about functions in ELNode's
	    class Fvisitor extends ELNode.Visitor {
		ArrayList<ELNode.Function> funcs =
                        new ArrayList<ELNode.Function>();
		Set<String> keys = new HashSet<String>();
		public void visit(ELNode.Function n) throws JasperException {
                    if (n.getUri() == null) {
                        // Can be a lambda expresion call
                        return;
                    }
		    String key = n.getPrefix() + ":" + n.getName();
		    if (! keys.contains(key)) {
			keys.add(key);
			funcs.add(n);
		    }
		}
	    }

	    if (el == null) {
		return;
	    }

	    // First locate all unique functions in this EL
	    Fvisitor fv = new Fvisitor();
	    el.visit(fv);
	    ArrayList<ELNode.Function> functions = fv.funcs;

	    if (functions.size() == 0) {
		return;
	    }

	    // Reuse a previous map if possible
	    String decName = matchMap(functions);
	    if (decName != null) {
		el.setMapName(decName);
		return;
	    }

	    // Generate declaration for the map statically
	    decName = getMapName();
	    ss.append("static private org.apache.jasper.runtime.ProtectedFunctionMapper " + decName + ";\n");

	    ds.append("  " + decName + "= ");
	    ds.append("org.apache.jasper.runtime.ProtectedFunctionMapper");

	    // Special case if there is only one function in the map
	    String funcMethod = null;
	    if (functions.size() == 1) {
		funcMethod = ".getMapForFunction";
	    } else {
		ds.append(".getInstance();\n");
		funcMethod = "  " + decName + ".mapFunction";
	    }

            // Setup arguments for either getMapForFunction or mapFunction
	    for (int i = 0; i < functions.size(); i++) {
		ELNode.Function f = (ELNode.Function)functions.get(i);
		FunctionInfo funcInfo = f.getFunctionInfo();
		String key = f.getPrefix()+ ":" + f.getName();
		ds.append(funcMethod + "(\"" + key + "\", " +
			funcInfo.getFunctionClass() + ".class, " +
			'\"' + f.getMethodName() + "\", " +
			"new Class[] {");
		String params[] = f.getParameters();
		for (int k = 0; k < params.length; k++) {
		    if (k != 0) {
			ds.append(", ");
		    }
		    int iArray = params[k].indexOf('[');
		    if (iArray < 0) {
			ds.append(params[k] + ".class");
		    }
		    else {
			String baseType = params[k].substring(0, iArray);
			ds.append("java.lang.reflect.Array.newInstance(");
			ds.append(baseType);
			ds.append(".class,");

			// Count the number of array dimension
			int aCount = 0;
			for (int jj = iArray; jj < params[k].length(); jj++ ) {
			    if (params[k].charAt(jj) == '[') {
				aCount++;
			    }
			}
			if (aCount == 1) {
			    ds.append("0).getClass()");
			} else {
			    ds.append("new int[" + aCount + "]).getClass()");
			}
		    }
		}
		ds.append("});\n");
		// Put the current name in the global function map
		gMap.put(f.getPrefix() + ':' + f.getName() + ':' + f.getUri(),
			 decName);
	    }
	    el.setMapName(decName);
	}

        /**
         * Find the name of the function mapper for an EL.  Reuse a
         * previously generated one if possible.
         * @param functions An ArrayList of ELNode.Function instances that
         *                  represents the functions in an EL
         * @return A previous generated function mapper name that can be used
         *         by this EL; null if none found.
         */
	private String matchMap(ArrayList<ELNode.Function> functions) {

	    String mapName = null;
	    for (int i = 0; i < functions.size(); i++) {
		ELNode.Function f = functions.get(i);
		String temName = gMap.get(f.getPrefix() + ':' +
					f.getName() + ':' + f.getUri());
		if (temName == null) {
		    return null;
		}
		if (mapName == null) {
		    mapName = temName;
		} else if (!temName.equals(mapName)) {
		    // If not all in the previous match, then no match.
		    return null;
		}
	    }
	    return mapName;
	}

        /*
         * @return An unique name for a function mapper.
         */
	private String getMapName() {
	    return "_jspx_fnmap_" + currFunc++;
	}
    }
}