/* 
 * E-XML Library:  For XML, XML-RPC, HTTP, and related.
 * Copyright (C) 2002-2008  Elias Ross
 * 
 * genman@noderunner.net
 * http://noderunner.net/~genman
 * 
 * 1025 NE 73RD ST
 * SEATTLE WA 98115
 * USA
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 * 
 * $Id$
 */

package net.noderunner.xmlrpc;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Vector;

import net.noderunner.exml.Document;
import net.noderunner.exml.NullWriter;
import net.noderunner.exml.XmlException;
import net.noderunner.exml.XmlReader;

/**
 * This class is for unit testing.the Xml-Rpc classes.
 *
 * @see XmlRpcServer
 * @see XmlRpcClient
 * @author Elias Ross
 * @version 1.0
 */
public class XmlRpcTest
	extends junit.framework.TestCase
	implements XmlRpcHandler
{
	public XmlRpcTest(String name) {
		super(name);
	}

	public static void main(String[] args) {
		junit.textui.TestRunner.run(XmlRpcTest.class);
	}

	static final String RETURN_TO_SENDER  = "return_to_sender";
	static final String UNPACK_PACK = "unpack_pack";
	static final String FAIL_FAIL = "fail";
	static final String HANDLER = "generic_handler";
	static final String ADDITION = "add_handler";
	static final XmlRpcException WRATH_OF_THE_PROGRAMMER_EXCEPTION =
		new XmlRpcException("Wrath of the Programmer Exception occured");
	static final XmlRpcException NO_IDEA_EXCEPTION =
		new XmlRpcException("I have no idea what you want.");

	/**
	 * Calls itself recursively to "unravel" a ParamIterator.
	 * @return an object that contains all the ParamIterator contents
	 */
	static Object createCollection(ParamIterator pi) 
		throws XmlRpcException
	{
		Vector v = new Vector();
		// The HashMap is used if a StructPair is detected.  
		HashMap map = new HashMap();
		while (pi.hasNext()) {
			Object o = pi.next();
			if (o instanceof StructPair) {
				StructPair pair = (StructPair)o;
				Object value = pair.getValue();
				if (value instanceof ParamIterator)
					value = createCollection((ParamIterator)value);
				map.put(pair.getName(), value);
			}
			if (o instanceof ParamIterator) {
				o = createCollection((ParamIterator)o);
				v.add(o);
			} else {
				v.add(o);
			}
		}
		pi.close();
		if (map.isEmpty())
			return v;
		return map;
	}

	/**
	 * Supports several test methods.
	 * RETURN_TO_SENDER -- just pass the same parameter iterator back.
	 * The response should be the same as the response.  (However, this
	 * will obviously fail, since <code>params</code> must be closed
	 * before a response is sent to the client by the server.
	 * UNPACK_PACK -- iterate over the parameter iterator, create a
	 * bunch of Vector and HashMaps, return a IteratorAdapter.
	 * FAIL -- throws an exception.
	 */
	public ParamIterator execute(String method, ParamIterator params) 
		throws XmlRpcException
	{
		if (method.equals(RETURN_TO_SENDER)) {
			return params;
		}
		else if (method.equals(UNPACK_PACK)) {
			Object o = createCollection(params);
			// A StructPair, hence Map, cannot occur at the "top-level"
			assertTrue(o instanceof Collection);
			Collection c = (Collection)o;
			ParamIterator pia = new IteratorAdapter(c.iterator());
			return pia;
		}
		else if (method.equals(ADDITION)) {
			int total = 0;
			while (params.hasNext()) {
				Object o = params.next();
				if (o instanceof Integer) {
					Integer i = (Integer)o;
					total += i.intValue();
				}
			}
			Vector v = new Vector();
			v.add(new Integer(total));
			return new IteratorAdapter(v.iterator());
		}
		else if (method.equals(FAIL_FAIL)) {
			throw WRATH_OF_THE_PROGRAMMER_EXCEPTION;
		}
		throw NO_IDEA_EXCEPTION;
	}

	/**
	 * Creates a bunch of stuff to iterate over.
	 */
	public static ParamIterator createStuff() {
		// Simple lists
		Vector fruits = new Vector();
		fruits.add("<Apples>"); 
		fruits.add("&Bananas&");
		fruits.add("_Cherries_");
		Vector veggies = new Vector();
		veggies.add("Potato"); veggies.add("Tomato"); veggies.add("Brocolli");
		// Random types to test
		Vector types = new Vector();
		types.add(null); types.add(new Integer(42)); 
		types.add(new Boolean(true)); types.add(new Double(1.0f));
		types.add(new Date(0)); types.add(new byte[] { 1, 2, 3, 4 });
		types.add(fruits.elements());
		// A HashMap with a Vector inside
		HashMap map = new HashMap(); 
		map.put("favorite fruits", fruits);
		// List of lists
		Vector list = new Vector();
		list.add(fruits); list.add(veggies); list.add(types);
		list.add(map);
		ParamIterator pia = new IteratorAdapter(list.iterator());
		return pia;
	}

	public static String makeNoDotRequest(String method) 
		throws IOException, XmlRpcException
	{
		StringWriter out = new StringWriter();
		XmlRpcWriter writer = new XmlRpcWriterImpl(out);
		writer.writeRequest(method, createStuff());
		return out.getBuffer().toString();
	}

	public static String makeRequest(String method) 
		throws IOException, XmlRpcException
	{
		StringWriter out = new StringWriter();
		XmlRpcWriter writer = new XmlRpcWriterImpl(out);
		writer.writeRequest(HANDLER + "." + method, createStuff());
		return out.getBuffer().toString();
	}

	public static String makeResponse(ParamIterator pi) 
		throws IOException, XmlRpcException
	{
		StringWriter out = new StringWriter();
		XmlRpcWriter writer = new XmlRpcWriterImpl(out);
		writer.writeResponse(pi);
		return out.getBuffer().toString();
	}

	public static String makeErrorResponse(XmlRpcException xre) 
		throws IOException, XmlRpcException
	{
		StringWriter out = new StringWriter();
		XmlRpcWriter writer = new XmlRpcWriterImpl(out);
		writer.writeError(xre);
		return out.getBuffer().toString();
	}

	public static String makeLongRequest(String method) 
		throws IOException, XmlRpcException
	{
		StringWriter out = new StringWriter();
		XmlRpcWriter writer = new XmlRpcWriterImpl(out);
		writer.writeRequest(HANDLER + "." + method, 
			makeLongParamIterator(1000));
		return out.getBuffer().toString();
	}


	StringReader in;
	StringWriter out;

	/**
	 * Test XmlRpcServerImpl class, see if default handler works.
	 */
	public void testXmlRpcServerDefaultHandler() 
		throws XmlRpcException, XmlException, IOException
	{
		XmlRpcServer server = new XmlRpcServerImpl();
		server.addHandler(XmlRpcServer.DEFAULT_HANDLER, this);
		in = new StringReader(makeRequest(ADDITION));
		server.accept(in, NullWriter.getInstance());

		server.removeHandler(XmlRpcServer.DEFAULT_HANDLER);
		System.out.println(makeRequest(ADDITION));
		in = new StringReader(makeRequest(ADDITION));
		try {
			server.accept(in, NullWriter.getInstance());
			fail("Should get NoHandlerException");
		} catch (NoHandlerException nhe) {
		}
	}

	public void testNoDotMethod() 
		throws XmlRpcException, XmlException, IOException
	{
		XmlRpcServer server = new XmlRpcServerImpl();
		server.addHandler(XmlRpcServer.DEFAULT_HANDLER, this);
		in = new StringReader(makeNoDotRequest(ADDITION));
		// should work, because of default handler
		server.accept(in, NullWriter.getInstance());

		// should fail because of bad request
		server.removeHandler(XmlRpcServer.DEFAULT_HANDLER);
		server.addHandler(HANDLER, this);
		in = new StringReader(makeNoDotRequest(ADDITION));
		try {
			server.accept(in, NullWriter.getInstance());
			fail("Should get NoHandlerException");
		} catch (NoHandlerException nhe) {
		}
	}


	public void testEmptyMethodName() 
		throws XmlRpcException, IOException
	{
		XmlRpcServer server = new XmlRpcServerImpl();
		server.addHandler(XmlRpcServer.DEFAULT_HANDLER, this);

		in = new StringReader(makeNoDotRequest("<!-- NO METHOD NAME -->"));
		XmlRpcReader reader = new XmlRpcReaderImpl(in);
		try {
			String name = reader.readMethodName();
			fail("Should get XmlRpcException, not " + name);
		} catch (XmlRpcException xre) {
		}
	}

	public void testReadResponseForRequest() 
		throws XmlRpcException, IOException
	{
		in = new StringReader(makeRequest(RETURN_TO_SENDER));
		XmlRpcReader reader = new XmlRpcReaderImpl(in);
		try {
			reader.readMethodResponse();
			fail("Should get XmlRpcException");
		} catch (XmlRpcException xre) {
			// xre.printStackTrace();
		}
	}

	public void testGarbageXml() 
		throws XmlRpcException, XmlException, IOException
	{
		XmlRpcServer server = new XmlRpcServerImpl();
		server.addHandler(XmlRpcServer.DEFAULT_HANDLER, this);
		String problematic = "<methodName><methodCall></methodName>";
		in = new StringReader(problematic);
		try {
			server.accept(in, NullWriter.getInstance());
			fail("Should get XmlRpcException");
		} catch (XmlRpcException xre) {
		}
	}

	public void testInValidXml() 
		throws XmlRpcException, XmlException, IOException
	{
		XmlRpcServer server = new XmlRpcServerImpl();
		server.addHandler(XmlRpcServer.DEFAULT_HANDLER, this);

		StringBuffer problematic = new StringBuffer();
		problematic.append(
			"<methodCall>" +
			"<methodName>generic_handler." + ADDITION + "</methodName>" +
			"<params><param>");
		for (int i = 0; i < 64; i++) {
			problematic.append("<value><array><data>");
		}

		// Ensure handler okay
		in = new StringReader(problematic.toString());
		out = new StringWriter();
		server.accept(in, out);
		in = new StringReader(out.getBuffer().toString());
		XmlRpcReader reader = new XmlRpcReaderImpl(in);
		ParamIterator pi = reader.readMethodResponse();
		assertEquals("Fault occured", pi.getIteratorType(), ParamIterator.FAULT_ITERATOR);
	}

	public void testValidXml2() 
		throws XmlRpcException, XmlException, IOException
	{
		String okay = 
        "<methodCall>" +
        "<methodName>" + HANDLER + "." + UNPACK_PACK + "</methodName>" +
        "<params>" +
                "<param><value>" +
                "<array><data>" +
                        "<value><string>Apples</string></value>" +
                        "<value><string>Bananas</string></value>" +
                        "<value><string>Cherries</string></value>" +
                "</data>" +
                "</array>" +
                "</value>" +
                "</param>" +
        "</params>" +
        "</methodCall>";

		// Ensure Xml is valid Xml
		in = new StringReader(okay);
		XmlReader reader = new XmlReader(in);
		Document document = reader.document();
		assertTrue(document != null);

		// Ensure handler okay
		in = new StringReader(okay);
		XmlRpcServer server = new XmlRpcServerImpl();
		server.addHandler(HANDLER, this);
		out = new StringWriter();
		server.accept(in, out);
	}

	/**
	 * Test XmlRpcWriter class.
	 */
	public void testValidXml() 
		throws XmlRpcException, XmlException, IOException
	{
		XmlReader reader;
		Document document;

		// Ensure Xml is valid Xml
		in = new StringReader(makeRequest(RETURN_TO_SENDER));
		reader = new XmlReader(in);
		document = reader.document();
		assertTrue(document != null);

		// Ensure Xml is valid Xml for failure
		in = new StringReader(makeRequest(FAIL_FAIL));
		reader = new XmlReader(in);
		document = reader.document();
		assertTrue(document != null);

		// Ensure Xml is valid Xml for response
		in = new StringReader(makeResponse(createStuff()));
		reader = new XmlReader(in);
		document = reader.document();
		assertTrue(document != null);

		// Ensure Xml is valid Xml for long request
		in = new StringReader(makeLongRequest(ADDITION));
		reader = new XmlReader(in);
		document = reader.document();
		assertTrue(document != null);

		// ensure valid NoDotRequest
		in = new StringReader(makeNoDotRequest("<!-- NO METHOD NAME -->"));
		reader = new XmlReader(in);
		document = reader.document();
		assertTrue(document != null);
	}

	/**
	 * Test XmlRpcServer classes.
	 */
	public void testValidXmlRpcServer() 
		throws XmlRpcException, IOException
	{
		XmlRpcServer server = new XmlRpcServerImpl();
		server.addHandler(HANDLER, this);

		// Try the UNPACK_PACK
		System.out.println(" XXX " + makeRequest(UNPACK_PACK));
		in = new StringReader(makeRequest(UNPACK_PACK));
		out = new StringWriter();
		server.accept(in, out);
		// See if what the server wrote matches what was received
		assertEquals(out.getBuffer().toString(),
			makeResponse(createStuff()));

		// Try the FAIL_FAIL
		in = new StringReader(makeRequest(FAIL_FAIL));
		out = new StringWriter();
		server.accept(in, out);
		// See if what the server wrote matches what was received
		assertEquals(out.getBuffer().toString(),
			makeErrorResponse(WRATH_OF_THE_PROGRAMMER_EXCEPTION));

		// Try the ""
		in = new StringReader(makeRequest(""));
		out = new StringWriter();
		server.accept(in, out);
		// See if what the server wrote matches what was received
		assertEquals(out.getBuffer().toString(),
			makeErrorResponse(NO_IDEA_EXCEPTION));

		// Try the ADDITION
		in = new StringReader(makeLongRequest(ADDITION));
		out = new StringWriter();
		server.accept(in, out);
		// See if what the server wrote matches the total we want
		Vector v = new Vector();
		v.add(new Integer(500));
		assertEquals(out.getBuffer().toString(), 
			makeResponse(new IteratorAdapter(v.iterator())));
	}

	private static class TestReader extends Reader {
		private Reader r;
		public boolean closed;
		public TestReader(Reader r) {
			this.r = r;
		}
		public int read(char[] c, int off, int len)
			throws IOException
		{
			return r.read(c, off, len);
		}
		public void close()
			throws IOException
		{
			r.close();
			closed = false;
		}
	}

	/**
	 * Test no closing at end of reading.
	 */
	public void testCloseReading() 
		throws XmlRpcException, IOException
	{
		XmlRpcServer server = new XmlRpcServerImpl();
		server.addHandler(HANDLER, this);

		TestReader in = new TestReader(new StringReader(makeRequest(UNPACK_PACK)));
		out = new StringWriter();
		server.accept(in, out);
		assertTrue("Not closed", !in.closed); 
	}

	/**
	 * Test no closing at end.
	 */
	public void testValidRR() 
		throws XmlRpcException, IOException
	{
	}

	/**
	 * Returns an interator with <code>until</code> entries, half of
	 * these are the integer 1, the other half are strings.
	 */
	public static ParamIterator makeLongParamIterator(final int until) {
		return new ParamIterator() {
			int times = 0;
			public Object next() 
			{
				times++;
				if (times % 2 == 0)
					return " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA ";
				else
					return new Integer(1);
			}
			public boolean hasNext() {
				return times < until;
			}
			public int getIteratorType() {
				return PARAMS_ITERATOR;
			}
			public void close() {
			}
		};
	}

}
