import java.util.* ;
import java.io.*;
import java.net.URLDecoder ;
import java.net.URLEncoder ;

public class QueryString {
          
	// A simple class to represent a set of HTTP parameters
	// May be used to parse apart an HTTP query string.
	// Or to compose one.

	// Copyright:  this code, originally written by Steven J. Owens,
	// is in the public domain.  Take it and do what you will with it.
	// Use this as an example of how to do it, or use it as your own.
	// I don't care.
	//
	// It is so stupidly trivial to write that it ought to already be
	// in java.net or the servlets API, and it's stupidly frustrating
	// to have to cobble this together for whatever project.
	//
	// It'd be nice if you left a comment in giving me credit for it,
	// or pointing to darksleep.com so people could get their own
	// copy.  Or even just emailed me and said "thanks". But do
	// whatever you want.

	// Quickie Overview
	//
	// QueryString is basically a wrapper around a
	// Map-of-Lists-of-Strings, and some convenience methods to
	// combine those strings into a HTTP parameter string, or parse
	// them apart.
	//
	// A GET parameter string is appended onto the URL with a ? in between.
        // So if you wanted to send parameter name "bar",
        // with parameter value "bat", you'd send it this way:
	//      http://foo.com?bar=bat
        // And if you wanted to add parameter name "xyzzy" 
        // with value "elbereth":
	//      http://foo.com?bar=bat&xyzzy=elbereth
	//
	// A POST parameter string is essentially the same thing, except
	// the string is sent separate from the URL, in the body of the
	// POST (essentially just a line of foo=bar&bat=baz&xyzzy=elbereth).
        //
        // If you want to read up on this further, start with understanding
        // that an HTTP request is essentially a MIME, and so is the response.
        // Most requests don't have a MIME body, just the headers, with the
        // exception of POST requests.
	//
	// THE ONE THING I'M NOT SURE ABOUT, offhand, is what the deal is
	// with posts and multiple body lines.  I suspect that unless
	// you're uploading tons of data, it won't matter to you.  If you
	// are, dig up the appropriate rfcs and w3c specs and figure it
	// out. Let me know and I'll update this.

	// Quickie Usage Notes
	//
	// To decode parameters:
	//      QueryString q = new QueryString(parameterstring);
	// or:
	//      QueryString q = new QueryString();
	//      q.decodeQueryString("foo=bar&baz=bat&xyzzy=elbereth") ;
	// or:
	//      QueryString q = new QueryString();
	//      q.decodeGetURL("http://foo.com/bar?baz=bat&xyzzy=elbereth") ;
	//
	// Note that decodeGetURL just trims off everything before the "?" and
	// then passes the remainder to decodeQueryString().
	//
	// Note that you can do multiple calls to decodeQueryString() and
	// it will just keep appending the new parameter values to its
	// Map-of-Lists.
	// 
	//
	// Note that, as per HTTP spec, you can have multiple parameter
	// values for each parameter name.  Therefore:
	//
	//      QueryString.getParameter(String name)      // returns a List of Strings
	//      QueryString.getParameterFirst(String name) // returns the first element in the List
	//
	// Also note that, strictly speaking, there is no prohibition
	// against multiple repeats of a value, though if you want that
	// you can just hack the parameter methods to use a Set instead of
	// a List.
	//
	// To encode parameters:
	//     QueryString query = new QueryString();
	//     query.appendParameter("name", "value") ; 
	//     // repeat as many times as you like.
	//     String queryString = query.toString();
	//
	// Or, if you have a List of value Strings for a given parameter:
	//     query.setParameterList("name", ListOfValues) ;
	// 
	// Or, for convenience, you can set the parameters as a Map (you
	// cannot represent all possible HTTP parameter sets as a Map,
	// since parameter names are allowed to repeat with different
	// values, but you can easily represent any Map as an HTTP
	// parameter set):
	//     query.setParameters(MapOfNamesAndValues) ;
	//
	// Note on encoding: HTTP defaults to UTF8, so that's what
	// QueryString defaults to.  I did the crudest, simplest possible
	// handling of UnsupportedEncodingException; catch it, print it,
	// throw a runtime exception.  Hack this to do something useful
	// for your code.
	//
	// Finally, there's a printParameters() method, for pretty-printing
	// parameter values, for debugging convenience.
	

	public static void main(String[] args) {
		// Test code goes here
		QueryString q = new QueryString();
		q.decodeGetURL("foo?bar=bat&foo=bar&bar=baz%09bat&baz=bat");
		System.out.println(q.printParameters());
		System.out.println("Query String: " + q.toString()) ; 
	}

	// Constructors
	public QueryString() {
		// default constructor
	}
	public QueryString(String queryString) {
		decodeQueryString(queryString) ;
	}

	// A bunch of standard values
	public String querySeparator = "?" ;
	public String pairSeparator = "&" ;
	public String nameValueSeparator = "=" ;

	// http defaults to UTF8
	public String encoding = "UTF8";
	public String getEncoding() {
		return this.encoding ;
	}
	public void setEncoding(String encoding) {
		this.encoding = encoding;
	}

	// The parameters are stored as a Map of Lists
	Map parameters = new HashMap() ;
	public List getParameter(String name) {
		return (List)parameters.get(name) ;
	}
	public String getParameterFirst(String name) {
		List values = (List)parameters.get(name) ;
		return (String)values.get(0) ;
	}
	public void appendParameter(String name, String value) {
		// FIXME  Should I be using set so only one of each value can exist?

		// Make sure there's a List for this name
		if (!parameters.containsKey(name)) {
			parameters.put(name, new ArrayList()) ;
		} 
		// Now append the new value to the List
		List values = (List)parameters.get(name) ;
		values.add(value) ;
	}

	// Not sure why I added this, it's identical to getParameter().
	public List getParameterList(String name) {
		return (List)parameters.get(name) ;
	}
	// Replaces any current values with the new List
	public void setParameterList(String name, List values) {
		parameters.put(name, values) ;
	}

	// Convenience method to set a map of parameters
	// Strictly speaking, a Map is not a valid representation of
	// http parameters, but for many purposes it is a convenient
	// shortcut, so we provide for it.
	public void setParameters(Map parameterMap) {
		Iterator i = parameterMap.keySet().iterator() ;
		while (i.hasNext()) {
			String name = (String)i.next() ;
			String value = (String)parameterMap.get(name) ;
			appendParameter(name, value) ;
		}
	}

	// prep for decoding a string like foo.cgi?bar=bat 
	public void decodeGetURL(String queryString) {
		System.out.println("QueryString is:" + queryString) ;
		// Trim off ? and anything before it.
		if (queryString.indexOf(querySeparator) >= 0) {
			queryString = queryString.substring(queryString.indexOf(querySeparator)+1) ;
			System.out.println("Trimmed to:    " + queryString) ;
		}
		decodeQueryString(queryString) ;
 	}
    
    // parse and decode a string like foo=bar&bat=baz&xyzzy=elbereth
    public void decodeQueryString(String queryString) {
	try {
	    // split on & to get individual chunks.
	    String[] parameterStrings = queryString.split(this.pairSeparator) ;
	    for (int i=0; i < parameterStrings.length; i++) {
		decodeParameter(parameterStrings[i]) ;
	    }
	} catch (UnsupportedEncodingException e) {
	    e.printStackTrace() ;
	    throw new RuntimeException("Unsupported Encoding: " + e.getMessage()) ;
	}
    }

    public void decodeParameter(String parameter) throws UnsupportedEncodingException {
	if (null == parameter) {
	    debug("Parameter is null, skipping") ;
	    return ;
	}
	parameter = parameter.trim() ;
	if ("".equals(parameter)) {
	    debug("Parameter is blank, skipping") ;
	    return ;
	}
	// Split on = to get name/value
	String[] chunks = parameter.split(this.nameValueSeparator, 2) ;
	if (chunks.length < 2) {
	    debug("Attempting to decode parameter string [" + parameter + "] but split only results in " + chunks.length + " chunks.") ;
	    return ;
	}
	String name  = URLDecoder.decode(chunks[0], getEncoding()) ;
	String value = URLDecoder.decode(chunks[1], getEncoding()) ;
	appendParameter(name, value) ;
    }

	// pretty-prints the parameters, for debugging purposes  
	public String printParameters() {
		StringBuffer out = new StringBuffer() ;
		Iterator i = parameters.keySet().iterator() ;
		while (i.hasNext()) {
			String name = (String)i.next() ;
			List values = (List)parameters.get(name) ;
			Iterator j = values.iterator() ;
			while (j.hasNext()) {
				String value = (String)j.next() ;
				out.append(name + "=" + value + "\n") ; 
			}
		}
		return out.toString();
	}

	// Produces a postable query string
	public String toString() {
		return join(getPairsList(), this.pairSeparator) ;
	}

	// Produces a "flattened" list of name=value pairs
	// from the parameters Map-of-Lists. 
	// any name may be duplicated multiple times 
	public List getPairsList() {
		List pairs = new LinkedList();
		Iterator i = parameters.keySet().iterator() ;
		while (i.hasNext()) {
			String name = (String)i.next() ;
			List values = (List)parameters.get(name) ;
			Iterator j = values.iterator() ;
			while (j.hasNext()) {
				String value = (String)j.next() ;
				pairs.add(nameValuePair(name, value)) ;
			}
		}
		return pairs ;
	}

	public String nameValuePair(String name, String value) {
		try {
			return URLEncoder.encode(name, getEncoding())
				+ this.nameValueSeparator
				+ URLEncoder.encode(value, getEncoding()) ; 
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace() ;
			throw new RuntimeException("Unsupported Encoding: " + e.getMessage()) ;
		}
	}

	public String join(List pairs, String separator) {
		StringBuffer out = new StringBuffer() ;
		Iterator i = pairs.iterator() ;
		while (i.hasNext()) {
			out.append(i.next()) ;
			if (i.hasNext()) {
				out.append(separator) ;
			} 
		}
		return out.toString();
	}


    
    /** Pasted this in and switched from String.split to make this
     * class pre-JDK1.4 compatible.
     */
    /** Since StringTokenizer sucks, we use this instead.
     */
    public String[] split(String list, char delim) {
        if (list == null)
            return null;
        if (list.equals(""))
            return null;

        List returnList = new ArrayList();

        // Copy list into a char array.
        char listChars[] = new char[list.length()];
        list.getChars(0, list.length(), listChars, 0);

        int count = 0;
        int itemStart = 0;
        int itemEnd = 0;
        String newItem = null;

        while (count < listChars.length) {
            count = itemEnd;
            if (count >= listChars.length)
                break;
            itemStart = count;
            itemEnd = itemStart;
            while (itemEnd < listChars.length) {
                if (delim != listChars[itemEnd]) {
                    itemEnd++;
                } else
                    break;
            }
            newItem = new String(listChars, itemStart, itemEnd - itemStart);
            itemEnd++;
            count = itemEnd;
            returnList.add(newItem);
        }

        String[] array = (String[])returnList.toArray();

        return array;
    }
}

