/*
 * $Id$
 *
 * Copyright 1996-2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */
package com.sun.javatest.httpd;

import java.util.Dictionary;
import java.util.Properties;

/**
 * URL object including support for key-value pairs.
 */

public class httpURL {
    public httpURL(String url) {
	firstQ = url.indexOf('?');
	file = url;
	fileLen = file.length();

	resetIterator();

	if (firstQ != -1) {
	    parseURLKeys();
	}
    }

    /**
     * Get the next part of the requested path.  This is an iterator.
     * Ex: from /harness/foo/, it may return "harness" or "foo".
     *
     * @return null if the end of the file path has been reached
     */
    public String getNextFile() {
	// XXX 4 May 2000 rewrite this method so it is not so complex!
	if (pathPos == fileLen) return null;

	String ss;   // the substring of the whole file name
	if (pathPos == fileLen - 1) {	// * special cases
	    if (fileLen == 1 && file.charAt(0) == '/') {	// should only happen when full URL is "/"
		ss = "/";
		pathPos = fileLen;
	    }
	    else {
		ss = file.substring(pathPos, fileLen);
		pathPos = fileLen;
	    }
	}
	else {				// * normal cases
	    int nextPos = file.indexOf('/', pathPos+1);
 
	    if (nextPos == -1) {
		// give the remainder of the string up to:
		// 1) the first question mark ahead
		// 2) the end of the string
		String result = file.substring(pathPos, (firstQ == -1 ? fileLen : firstQ));
		pathPos = fileLen;
		ss = result;
	    }
	    else {
		ss = file.substring(pathPos, nextPos);
		pathPos = nextPos + 1;
	    }
	}

	//System.out.println("next file is: " + ss);
	return ss;
    }

    public void resetIterator() {
	// if the URL is only a slash, we let that through
	// otherwise we ignore the leading slash
	// this allows the web server root page to work, eg. http://foo:1903/
	if (file.indexOf('/') == 0 && fileLen > 1) 
	    pathPos = 1;
	else
	    pathPos = 0;
    }

    /**
     * Returns the URL, minus the protocol, hostname and port.
     */
    public String getFullPath() {
	return file;
    }

    /**
     * Get the host portion of the URL.
     *
     * @throws Fault This info is not always available, so this exception may
     *               be thrown.
     */
    public String getLocalHost() throws Fault {
	if (lHost == null)
	    throw new Fault("Local hostname for URL not available");
	else
	    return lHost;
    }

    /**
     * Get the local hostname portion of the URL.
     *
     * @throws Fault This info is not always available, so this exception may
     *               be thrown.
     */
    public int getLocalPort() throws Fault {
	if (lPort == -1)
	    throw new Fault("Local port for URL not available");
	else
	    return lPort;
    }

    /**
     * Get the remote hostname.  Fully resolved host names are not required.
     *
     * @throws Fault This info is not always available, so this exception may
     *               be thrown.
     */
    public String getRemoteHost() throws Fault {
	if (rHost == null)
	    throw new Fault("Remote hostname for URL not available");
	else
	    return rHost;
    }

    // ------- Key-Value Processing ---------
    /**
     * Set the key-value pairs encoded in this URL.
     * Not implemented yet.
     */
    public void setProperties(Dictionary props) {
    }

    /**
     * Not implemented yet.
     */
    public String[] getKeys() {
	return null;
    }

    /**
     * Not implemented yet.
     */
    public String[] getValues() {
	return null;
    }

    /**
     * Not implemented yet.
     */
    public String getValue(String key) {
	return urlValues.getProperty(key);
    }

    // ----------- Utitlity -----------
    /**
     * Assembles a slash seperated path from the elements of the given array.
     * Null entries in the array are ignored.
     *
     * @param path An empty or null array results in a zero length string.
     * @param leadingSlash Whether or not to put a slash at the beginning of the path
     * @param trailSlash Whether or not to put a slash at the end of the path
     */
    public static String reassemblePath(String[] path, boolean leadingSlash,
					boolean trailSlash) {
	if (path == null || path.length == 0)
	    return "";

	StringBuffer result = new StringBuffer();

	if (leadingSlash) result.append("/");

	for (int i = 0; i < path.length; i++) {
	    if (path[i] != null) {
		result.append(path);
		result.append("/");
	    }
	}   // for

	// remove trailing slash if needed
	if (!trailSlash && result.length() > 1) result.setLength(result.length()-1);

	return result.toString();
    }

    // ----------- NON-PUBLIC ------------

    void setRemoteHost(String name) {
	rHost = name;
    }

    void setLocalHost(String name) {
	lHost = name;
    }

    void setLocalPort(int port) {
	lPort = port;
    }

    private void parseURLKeys() {
	String data = file.substring(firstQ + 1, fileLen);

	if (data.length() == 0) return;

	String key = null;
	int pos = 0;
	int dataLen = data.length();	// optimization

	while ((key = readSegment(data, pos, dataLen)) != null) {
	    pos = pos + key.length();
	    if (debug) System.out.println("   Read key: " + key);

	    if (pos >= dataLen || data.charAt(pos) == '&') {
		// there is no value with this key
		if (debug) System.out.println("key: " + key + "  value: null");
		urlValues.put(key, "true");
	    }
	    else {
		// char at pos must be '='
		String val = readSegment(data, ++pos, dataLen);
		if (debug) System.out.println("key: " + key + "  value: " + val);
		urlValues.put(key, val);

		// assume that the resulting value will either jump over
		// the & or put us past the end of the string
		// http://machine1:1904/version?apple=orange&grape=apricot
		//                                         ^
		//                                        pos
		pos = pos + val.length() + 1;

		// end of data string
		if (pos > dataLen) break;
	    }
	}   // while
    }

    private String readSegment(String data, int position, int dataLen) {
	StringBuffer buf = new StringBuffer();
	int i = position;

	// loop until you hit end of string, a & or a =
	while (i < dataLen && data.charAt(i) != '&' &&
	       data.charAt(i) != '=') {
	    buf.append(data.charAt(i));
	    i++;
	}   // while

	// a zero length string is not acceptable
	return (buf.length() == 0 ? null : buf.toString());
    }

    private String lHost;
    private int lPort = -1;
    private String rHost;
    private int pathPos;
    private int firstQ;	    // location of the first question mark in the file path
    protected static boolean debug = Boolean.getBoolean("debug." + httpURL.class.getName());

    /**
     * The URL, of the format:
     * <tt>/harness/foo/index.html?key1=value1?key2=value2</tt>
     */
    private String file;

    /**
     * The length of the file string.  Used often.
     */
    private int fileLen;

    /**
     * Any key-values encoded in the URL string.
     */
    private Properties urlValues = new Properties();

    public static class Fault extends Exception {
	public Fault(String s) {
	    super(s);
	}

	/**
	 * Provide description and pass-thru exception
	 */
	public Fault(String s, Throwable e) {
	    super(s);
	}

	/**
	 * Get the original exception.
	 */
	public Throwable getException() {
	    return orig;
	}

	private Throwable orig;
    }
}

