/*-- $Id: Utils.java,v 1.23 2001/01/19 00:23:48 greenrd Exp $ --

 ============================================================================
                   The Apache Software License, Version 1.1
 ============================================================================

 Copyright (C) 1999-2001 The Apache Software Foundation. All rights reserved.

 Redistribution and use in source and binary forms, with or without modifica-
 tion, are permitted provided that the following conditions are met:

 1. Redistributions of  source code must  retain the above copyright  notice,
    this list of conditions and the following disclaimer.

 2. Redistributions in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution.

 3. The end-user documentation included with the redistribution, if any, must
    include  the following  acknowledgment:  "This product includes  software
    developed  by the  Apache Software Foundation  (http://www.apache.org/)."
    Alternately, this  acknowledgment may  appear in the software itself,  if
    and wherever such third-party acknowledgments normally appear.

 4. The names "Cocoon" and  "Apache Software Foundation"  must not be used to
    endorse  or promote  products derived  from this  software without  prior
    written permission. For written permission, please contact
    apache@apache.org.

 5. Products  derived from this software may not  be called "Apache", nor may
    "Apache" appear  in their name,  without prior written permission  of the
    Apache Software Foundation.

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
 APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
 INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
 DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
 OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
 ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
 (INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

 This software  consists of voluntary contributions made  by many individuals
 on  behalf of the Apache Software  Foundation and was  originally created by
 Stefano Mazzocchi  <stefano@apache.org>. For more  information on the Apache
 Software Foundation, please see <http://www.apache.org/>.

 */
package org.apache.cocoon;

import java.io.*;
import java.net.*;
import java.util.*;
import org.w3c.dom.*;
import javax.servlet.*;
import javax.servlet.http.*;

// XXX: Most of the methods in XSPUtil are not specific to XSP
// and should be moved somewhere else.
import org.apache.cocoon.processor.xsp.XSPUtil;

/**
 * Utility methods for Cocoon and its classes.
 *
 * @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
 * @author <a href="mailto:greenrd@hotmail.com">Robin Green</a>
 * @version $Revision: 1.23 $ $Date: 2001/01/19 00:23:48 $
 */

public final class Utils {

    public static byte[] getBytes (String s, String encoding)
    throws UnsupportedEncodingException {
        return (encoding == null) ? s.getBytes () : s.getBytes (encoding);
    }

    /**
     * This method returns a vector of PI nodes based on the PI target name.
     */
    public static final Vector getAllPIs(Document document, String name) {
        return getAllPIs(document, name, false);
    }

    /**
     * This method returns a vector of PI nodes based on the PI target name
     * and removes the found PIs from the document if the remove flag is
     * true.
     */
    public static final Vector getAllPIs(Document document, String name, boolean remove) {
        Vector pis = new Vector();

        NodeList nodelist = document.getChildNodes();
        int i = nodelist.getLength();
        for (int j = 0; j < i; j++) {
            Node node = nodelist.item(j);
            if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) {
                if (((ProcessingInstruction) node).getTarget().equals(name)) {
                    pis.addElement(node);
                    if (remove) {
                        node.getParentNode().removeChild(node);
                        i--;
                    }
                }
            }
        }

        return pis;
    }

    /**
     * This method returns the first PI node based on the PI target name.
     */
    public static final ProcessingInstruction getFirstPI(Document document, String name) {
        return getFirstPI(document, name, false);
    }

    /**
     * This method returns the first PI node based on the PI target name and
     * removes it from the document if the remove flag is true.
     */
    public static final ProcessingInstruction getFirstPI(Document document, String name, boolean remove) {
        ProcessingInstruction pi = null;

        NodeList nodelist = document.getChildNodes();
        int i = nodelist.getLength();
        for (int j = 0; j < i; j++) {
            Node node = nodelist.item(j);
            if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) {
                if (((ProcessingInstruction) node).getTarget().equals(name)) {
                    pi = (ProcessingInstruction) node;
                    if (remove) node.getParentNode().removeChild(node);
                    break;
                }
            }
        }

        return pi;
    }

    /**
     * This method returns an hashtable with all the pseudo attributes collected
     * in the document. If more PI have the same target, the attributes are
     * all put in the same hashtable. If there are collisions, the last attribute
     * is insered.
     */
    public static final Hashtable getPIPseudoAttributes(Document document, String name) {
        Hashtable attributes = new Hashtable();
        Enumeration nodes = getAllPIs(document, name).elements();
        while (nodes.hasMoreElements()) {
            ProcessingInstruction pi = (ProcessingInstruction) nodes.nextElement();
            addPIPseudoAttributes(pi, attributes);
        }
        return attributes;
    }

    /**
     * This method returns an hashtable of pseudo attributes found in the first
     * occurrence of the PI with the given name in the given document.
     * No validation is performed on the PI pseudo syntax
     */
    public static final Hashtable getPIPseudoAttributes(ProcessingInstruction pi) {
        Hashtable attributes = new Hashtable();
        addPIPseudoAttributes(pi, attributes);
        return attributes;
    }

    /**
     * This method adds pseudo attributes from a pi to an existing attribute list.
     * All attributes are all put in the same hashtable.
     * If there are collisions, the last attribute is inserted.
     * No validation is performed on the PI pseudo syntax.
     */
    private static final void addPIPseudoAttributes(ProcessingInstruction pi, Hashtable attributes) {
        String data = pi.getData();

        Tokenizer st = new Tokenizer(data, "\"");
        try {
          while (st.hasMoreTokens()) {
              String key   = st.nextToken();     // attribute name and '='
              String token = st.nextToken();     // exact attribute value
              key = key.replace('=',' ').trim(); // remove whitespace and '='
              attributes.put(key, token);
          }
        } catch (NoSuchElementException nsee) {
          // ignore white-space at the end of pseudo-list
        }
    }

    /**
     * Encodes the given request into a string using the format
     *   protocol://serverName:serverPort/requestURI?query
     */
    public static final String encode(HttpServletRequest req) {
        return encode(req, true, true);
    }

    /**
     * Encodes the given request into a string using the format
     *   userAgent:method:protocol://serverName:serverPort/requestURI?query
     * with the agent flag controlling the presence of the userAgent
     * field.
     */
    public static final String encode(HttpServletRequest req, boolean agent) {
        return encode(req, agent, true);
    }

    /**
     * Encodes the given request into a string using the format
     *   userAgent:method:protocol://serverName:serverPort/requestURI?query
     * with the agent flag controlling the presence of the userAgent
     * field and the query flag controlling the query field.
     */
    public static final String encode(HttpServletRequest req, boolean agent, boolean query) {
        StringBuffer url = new StringBuffer();
        if (agent) {
            url.append(req.getHeader("user-Agent"));
            url.append(':');
        }
		url.append(req.getMethod());
		url.append(':');
        url.append(req.getScheme());
        url.append("://");
        url.append(req.getServerName());
        url.append(':');
        url.append(req.getServerPort());
        url.append(req.getRequestURI());
        if (query) {
            url.append('?');
            url.append(req.getQueryString());
        }

        /** This severely f***s up caching. Will fix later - RDG
        Enumeration headers = req.getHeaderNames();
        if (headers != null) {
          url.append("&headers:");
          while (headers.hasMoreElements()) {
            String name = (String)headers.nextElement();
            url.append(name);
            url.append("=");
            url.append((String)req.getHeader(name));
          }
        }
        */
        return url.toString();
    }

    /**
     * XXX: This is a dirty hack. The worst piece of code I ever wrote
     * and it clearly shows how Cocoon must change to support the Servlet API
     * 2.2 which has _much_ better mapping support thru the use of "getResource()"
     * but then, all the file system abstraction should be URL based.
     *
     * So, for now, leave the dirty code even if totally deprecated and work
     * out a better solution in the future.
     */
    public static final String getBasename(HttpServletRequest request, Object context) {
        String path;

        try {
            // detect if the engine supports at least Servlet API 2.2
            request.getClass ().getMethod ("getContextPath", null);

            // we need to check this in case we've been included in a servlet or jsp
            path = (String) request.getAttribute("javax.servlet.include.servlet_path");
            // otherwise, we find it out ourselves
            if (path == null) path = request.getServletPath();

            // FIXME (SM): we should use getResource() instead when we are
            // able to handle remote resources.
            String resource = ((ServletContext) context).getRealPath(path);

            if (resource != null) {
                return resource.replace('\\','/');
            } else {
                throw new RuntimeException("Cannot access non-file/war resources");
            }
        } catch (NoSuchMethodException e) { // thrown from detection line
            // if there is no such method we're not in Servlet API 2.2
            return getBaseName_Old (request);
        } catch (NullPointerException e) {
            // if there is no context set, we must be called from the command line
            return request.getPathTranslated().replace('\\','/');
        }
    }

    private static String getBaseName_Old (HttpServletRequest request) {
      String path;
      if (request.getPathInfo() != null) {
        // this must be Apache JServ
        path = request.getPathTranslated();
      } else {
        // otherwise use the deprecated method on all other servlet engines.
        path = request.getRealPath(request.getRequestURI());
      }
      return (path == null) ? "" : path.replace('\\','/');
    }

    /*
     * Returns the base path for the request.
     */
    public static final String getBasepath(HttpServletRequest request, Object context) {
        String basename = getBasename(request, context);
        return basename.substring(0, basename.lastIndexOf('/') + 1);
    }

    /*
     * Returns the base path for the request.
     */
    public static final String getRootpath(HttpServletRequest request, Object context) {
        // FIXME (SM): I have _no_absolute_idea_ how much this is portable. The whole
        // architecture should be based on URL rather than Files to allow the
        // use of Servlet 2.2 getResource() to void calling such nasty methods
        // but for now, well, it's the best I can do :(
        return request.getRealPath("/");
    }

    /*
     * Returns the stack trace as a string
     */
    public static final String getStackTraceAsString(Throwable e) {
        StringWriter sw = new StringWriter ();
        PrintWriter writer = new PrintWriter(sw);
        e.printStackTrace(writer);
        return XSPUtil.encodeMarkup (sw.toString());
    }

    /*
     * Returns the resource pointed by the given location.
     */
    public static final Object getLocationResource(String location) throws MalformedURLException {
        Object resource = null;

        if (location.indexOf("://") < 0) {
            resource = new File(location);
        } else if (location.startsWith("resource://")) {
            // FIXME (SM): this should _not_ be system resource, but rather a resource of current classloader
//            resource = ClassLoader.getSystemResource(location.substring("resource://".length()));

            // The Fix!
            Dummy classloadrefernce = new Dummy();
            resource = classloadrefernce.getClass().getClassLoader().getResource(location.substring("resource://".length()));
        } else {
            resource = new URL(location);
        }

        return resource;
    }

    /*
     * Returns the resource pointed by the given location relative to the given request.
     */
    public static final Object getLocationResource(String location, HttpServletRequest request, ServletContext context) throws Exception {
        Object resource = null;

        if (location.indexOf("://") < 0) {
            if (location.charAt(0) == '/') {
                // Location is relative to webserver's root
                location = request.getRealPath(location);
            } else {
                // Location is relative to requested page's virtual directory
                String basename = getBasename(request, context);
                location = basename.substring(0, basename.lastIndexOf('/') + 1) + location;
            }
            resource = new File(location);
        } else if (location.startsWith("resource://")) {
            // FIXME (SM): this should _not_ be system resource, but rather a resource of current classloader
//            resource = ClassLoader.getSystemResource(location.substring("resource://".length()));

            // The Fix!
            Dummy classloadrefernce = new Dummy();
            resource = classloadrefernce.getClass().getClassLoader().getResource(location.substring("resource://".length()));
        } else {
            resource = new URL(location);
        }

        return resource;
    }

}

class Dummy {
    String why = "to provide a classloader ref";
}
