package org.apache.xmlrpc;

/*
 * The Apache Software License, Version 1.1
 *
 *
 * Copyright (c) 2001 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, 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 "XML-RPC" 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 [EMAIL PROTECTED]
 *
 * 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 (INCLUDING, 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.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

import java.io.InputStream;
import java.util.Stack;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import org.xml.sax.AttributeList;
import org.xml.sax.HandlerBase;
import org.xml.sax.InputSource;
import org.xml.sax.Parser;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import uk.co.wilson.xml.MinML;

/**
 * This abstract base class provides basic capabilities for XML-RPC,
 * like parsing of parameters or encoding Java objects into XML-RPC
 * format.  Any XML parser with a <a
 * href="http://www.megginson.com/SAX/";>SAX</a> interface can be used.
 *
 * <p>XmlRpcServer and XmlRpcClient are the classes that actually
 * implement an XML-RPC server and client.
 *
 * @see org.apache.xmlrpc.XmlRpcServer
 * @see org.apache.xmlrpc.XmlRpcClient
 *
 * @author <a href="mailto:[EMAIL PROTECTED]";>Hannes Wallnoefer</a>
 * @author <a href="mailto:[EMAIL PROTECTED]";>Daniel Rall</a>
 * @author <a href="mailto:[EMAIL PROTECTED]";>Andrew Evers</a>
 * @version $Id: XmlRpc.java,v 1.35 2002/11/21 01:28:16 dlr Exp $
 */
public abstract class XmlRpc extends HandlerBase
{
    /**
     * The version string used in HTTP communication.
     */
    // FIXME: Use Ant <filter> to preprocess during compilation
    public static final String version = "Apache XML-RPC 1.2-a3-dev";

    /**
     * The default parser to use (MinML).
     */
    private static final String DEFAULT_PARSER = MinML.class.getName();

    /**
     * The maximum number of threads which can be used concurrently.
     */
    private static int maxThreads = 100;

    String methodName;

    /**
     * The class name of SAX parser to use.
     */
    private static Class parserClass;
    private static Map saxDrivers = new HashMap ();

    static
    {
        // A mapping of short identifiers to the fully qualified class
        // names of common SAX parsers.  If more mappings are added
        // here, increase the size of the saxDrivers Map used to store
        // them.
        saxDrivers.put("xerces", "org.apache.xerces.parsers.SAXParser");
        saxDrivers.put("xp", "com.jclark.xml.sax.Driver");
        saxDrivers.put("ibm1", "com.ibm.xml.parser.SAXDriver");
        saxDrivers.put("ibm2", "com.ibm.xml.parsers.SAXParser");
        saxDrivers.put("aelfred", "com.microstar.xml.SAXDriver");
        saxDrivers.put("oracle1", "oracle.xml.parser.XMLParser");
        saxDrivers.put("oracle2", "oracle.xml.parser.v2.SAXParser");
        saxDrivers.put("openxml", "org.openxml.parser.XMLSAXParser");
    }

    // the stack we're parsing our values into.
    Stack values;
    Value currentValue;

    /**
     * Used to collect character data (<code>CDATA</code>) of
     * parameter values.
     */
    StringBuffer cdata;
    boolean readCdata;

    // XML RPC parameter types used for dataMode
    static final int STRING = 0;
    static final int INTEGER = 1;
    static final int BOOLEAN = 2;
    static final int DOUBLE = 3;
    static final int DATE = 4;
    static final int BASE64 = 5;
    static final int STRUCT = 6;
    static final int ARRAY = 7;

    // Error level + message
    int errorLevel;
    String errorMsg;

    static final int NONE = 0;
    static final int RECOVERABLE = 1;
    static final int FATAL = 2;

    /**
     * Wheter to use HTTP Keep-Alive headers.
     */
    static boolean keepalive = false;

    /**
     * Whether to log debugging output.
     */
    public static boolean debug = false;

    /**
     * The list of valid XML elements used for RPC.
     */
    final static String types[] =
    {
        "String",
        "Integer",
        "Boolean",
        "Double",
        "Date",
        "Base64",
        "Struct",
        "Array"
    };

    /**
     * Java's name for the encoding we're using.  Defaults to
     * <code>ISO8859_1</code>.
     */
    static String encoding = XmlWriter.ISO8859_1;

    private TypeFactory typeFactory;

    /**
     * Creates a new instance with the {@link
     * org.apache.xmlrpc.TypeFactory} set to an instance of the class
     * named by the <code>org.apache.xmlrpc.TypeFactory</code> System
     * property.  If property not set or class is unavailable, uses
     * the default.
     */
    protected XmlRpc()
    {
        this(System.getProperty(TypeFactory.class.getName()));
    }

    /**
     * Creates a new instance with the specified {@link
     * org.apache.xmlrpc.TypeFactory}.
     *
     * @param typeFactory The implementation to use.
     */
    protected XmlRpc(String typeFactory)
    {
        Class c = null;
        if (typeFactory != null && typeFactory.length() > 0)
        {
            try
            {
                c = Class.forName(typeFactory);
            }
            catch (ClassNotFoundException e)
            {
                System.err.println("Error loading TypeFactory specified by " +
                                   "the " + TypeFactory.class.getName() +
                                   " property, using default instead: " +
                                   e.getMessage());
            }
        }
        this.typeFactory = createTypeFactory(c);
    }

    /**
     * Creates a new instance of the specified {@link
     * org.apache.xmlrpc.TypeFactory}.
     *
     * @param typeFactory The implementation to use.
     * @return The new type mapping.
     */
    private TypeFactory createTypeFactory(Class typeFactory)
    {
        // If we're using the default, serve it up immediately.
        if (typeFactory == null ||
            DefaultTypeFactory.class.equals(typeFactory))
        {
            return new DefaultTypeFactory();
        }

        try
        {
            return (TypeFactory) typeFactory.newInstance();
        }
        catch (Exception e)
        {
            System.err.println("Unable to create configured TypeFactory '" +
                               typeFactory.getName() + "': " + e.getMessage() +
                               ": Using default");
            // Call self recursively to acquire default.
            return createTypeFactory(null);
        }
    }

    /**
     * Set the SAX Parser to be used. The argument can either be the
     * full class name or a user friendly shortcut if the parser is
     * known to this class. The parsers that can currently be set by
     * shortcut are listed in the main documentation page. If you are
     * using another parser please send me the name of the SAX driver
     * and I'll include it in a future release.  If setDriver() is
     * never called then the System property "sax.driver" is
     * consulted. If that is not defined the driver defaults to
     * OpenXML.
     */
    public static void setDriver(String driver) throws ClassNotFoundException
    {
        String parserClassName = null;
        try
        {
            parserClassName = (String) saxDrivers.get(driver);
            if (parserClassName == null)
            {
                // Identifier lookup failed, assuming we were provided
                // with the fully qualified class name.
                parserClassName = driver;
            }
            parserClass = Class.forName(parserClassName);
        }
        catch (ClassNotFoundException x)
        {
            throw new ClassNotFoundException ("SAX driver not found: "
                    + parserClassName);
        }
    }

    /**
     * Set the SAX Parser to be used by directly passing the Class object.
     */
    public static void setDriver(Class driver)
    {
        parserClass = driver;
    }

    /**
     * Set the encoding of the XML.
     *
     * @param enc The Java name of the encoding.
     */
    public static void setEncoding(String enc)
    {
        encoding = enc;
    }

    /**
     * Return the encoding, transforming to the canonical name if
     * possible.
     *
     * @see org.apache.xmlrpc.XmlWriter#canonicalizeEncoding(String)
     */
    public String getEncoding ()
    {
        return XmlWriter.canonicalizeEncoding(encoding);
    }

    /**
     * Gets the maximum number of threads used at any given moment.
     */
    public static int getMaxThreads()
    {
        return maxThreads;
    }

    /**
     * Sets the maximum number of threads used at any given moment.
     */
    public static void setMaxThreads(int maxThreads)
    {
        XmlRpc.maxThreads = maxThreads;
    }

    /**
     * Switch debugging output on/off.
     */
    public static void setDebug(boolean val)
    {
        debug = val;
    }

    /**
     * Switch HTTP keepalive on/off.
     */
    public static void setKeepAlive(boolean val)
    {
        keepalive = val;
    }

    /**
     * get current HTTP keepalive mode.
     */
    public static boolean getKeepAlive()
    {
        return keepalive;
    }

    /**
     * Parse the input stream. For each root level object, method
     * <code>objectParsed</code> is called.
     */
    synchronized void parse(InputStream is) throws Exception
    {
        // reset values (XmlRpc objects are reusable)
        errorLevel = NONE;
        errorMsg = null;
        values = new Stack ();
        if (cdata == null)
        {
            cdata = new StringBuffer(128);
        }
        else
        {
            cdata.setLength(0);
        }
        readCdata = false;
        currentValue = null;

        long now = System.currentTimeMillis();
        if (parserClass == null)
        {
            // try to get the name of the SAX driver from the System properties
            String driver;
            try
            {
                driver = System.getProperty("sax.driver", DEFAULT_PARSER);
            }
            catch (SecurityException e)
            {
                // An unsigned applet may not access system properties.
                driver = DEFAULT_PARSER;
            }
            setDriver(driver);
        }

        Parser parser = null;
        try
        {
            parser = (Parser) parserClass.newInstance();
        }
        catch (NoSuchMethodError nsm)
        {
            // This is thrown if no constructor exists for the parser class
            // and is transformed into a regular exception.
            throw new Exception("Can't create Parser: " + parserClass);
        }

        parser.setDocumentHandler(this);
        parser.setErrorHandler(this);

        if (debug)
        {
            System.out.println("Beginning parsing XML input stream");
        }
        try
        {
            parser.parse(new InputSource (is));
        }
        finally
        {
            // Clear any huge buffers.
            if (cdata.length() > 128 * 4)
            {
                // Exceeded original capacity by greater than 4x; release
                // buffer to prevent leakage.
                cdata = null;
            }
        }
        if (debug)
        {
            System.out.println ("Spent " + (System.currentTimeMillis() - now)
                    + " millis parsing");
        }
    }

    /**
     * This method is called when a root level object has been parsed.
     * Sub-classes implement this callback to receive the fully parsed
     * object.
     */
    protected abstract void objectParsed(Object what);


    ////////////////////////////////////////////////////////////////
    // methods called by XML parser

    /**
     * Method called by SAX driver.
     */
    public void characters(char ch[], int start, int length)
            throws SAXException
    {
        if (readCdata)
        {
            cdata.append(ch, start, length);
        }
    }

    /**
     * Method called by SAX driver.
     */
    public void endElement(String name) throws SAXException
    {

        if (debug)
        {
            System.out.println("endElement: " + name);
        }

        // finalize character data, if appropriate
        if (currentValue != null && readCdata)
        {
            currentValue.characterData(cdata.toString());
            cdata.setLength(0);
            readCdata = false;
        }

        if ("value".equals(name))
        {
            // Only handle top level objects or objects contained in
            // arrays here.  For objects contained in structs, wait
            // for </member> (see code below).
            int depth = values.size ();
            if (depth < 2 || values.elementAt(depth - 2).hashCode() != STRUCT)
            {
                Value v = currentValue;
                values.pop();
                if (depth < 2)
                {
                    // This is a top-level object
                    objectParsed(v.value);
                    currentValue = null;
                }
                else
                {
                    // Add object to sub-array; if current container
                    // is a struct, add later (at </member>).
                    currentValue = (Value) values.peek();
                    currentValue.endElement(v);
                }
            }
        }

        // Handle objects contained in structs.
        if ("member".equals(name))
        {
            Value v = currentValue;
            values.pop();
            currentValue = (Value) values.peek();
            currentValue.endElement(v);
        }

        else if ("methodName".equals(name))
        {
            methodName = cdata.toString();
            cdata.setLength(0);
            readCdata = false;
        }
    }

    /**
     * Method called by SAX driver.
     */
    public void startElement(String name, AttributeList atts)
            throws SAXException
    {
        if (debug)
        {
            System.out.println("startElement: " + name);
        }

        if ("value".equals(name))
        {
            Value v = new Value();
            values.push(v);
            currentValue = v;
            // cdata object is reused
            cdata.setLength(0);
            readCdata = true;
        }
        else if ("methodName".equals(name))
        {
            cdata.setLength(0);
            readCdata = true;
        }
        else if ("name".equals(name))
        {
            cdata.setLength(0);
            readCdata = true;
        }
        else if ("string".equals(name))
        {
            // currentValue.setType (STRING);
            cdata.setLength(0);
            readCdata = true;
        }
        else if ("i4".equals(name) || "int".equals(name))
        {
            currentValue.setType(INTEGER);
            cdata.setLength(0);
            readCdata = true;
        }
        else if ("boolean".equals(name))
        {
            currentValue.setType(BOOLEAN);
            cdata.setLength(0);
            readCdata = true;
        }
        else if ("double".equals(name))
        {
            currentValue.setType(DOUBLE);
            cdata.setLength(0);
            readCdata = true;
        }
        else if ("dateTime.iso8601".equals(name))
        {
            currentValue.setType(DATE);
            cdata.setLength(0);
            readCdata = true;
        }
        else if ("base64".equals(name))
        {
            currentValue.setType(BASE64);
            cdata.setLength(0);
            readCdata = true;
        }
        else if ("struct".equals(name))
        {
            currentValue.setType(STRUCT);
        }
        else if ("array".equals(name))
        {
            currentValue.setType(ARRAY);
        }
    }

    /**
     *
     * @param e
     * @throws SAXException
     */
    public void error(SAXParseException e) throws SAXException
    {
        System.err.println("Error parsing XML: " + e);
        errorLevel = RECOVERABLE;
        errorMsg = e.toString();
    }

    /**
     *
     * @param e
     * @throws SAXException
     */
    public void fatalError(SAXParseException e) throws SAXException
    {
        System.err.println("Fatal error parsing XML: " + e);
        errorLevel = FATAL;
        errorMsg = e.toString();
    }

    /**
     * This represents a XML-RPC value parsed from the request.
     */
    class Value
    {
        int type;
        Object value;
        // the name to use for the next member of struct values
        String nextMemberName;

        Map struct;
        List array;

        /**
         * Constructor.
         */
        public Value()
        {
            this.type = STRING;
        }

        /**
         * Notification that a new child element has been parsed.
         */
        public void endElement(Value child)
        {
            switch (type)
            {
                case ARRAY:
                    array.add(child.value);
                    break;
                case STRUCT:
                    struct.put(nextMemberName, child.value);
            }
        }

        /**
         * Set the type of this value. If it's a container, create the
         * corresponding java container.
         */
        public void setType(int type)
        {
            //System.out.println ("setting type to "+types[type]);
            this.type = type;
            switch (type)
            {
                case ARRAY:
                    value = array = new ArrayList ();
                    break;
                case STRUCT:
                    value = struct = new HashMap ();
                    break;
            }
        }

        /**
         * Set the character data for the element and interpret it
         * according to the element type.
         */
        public void characterData(String cdata)
        {
            switch (type)
            {
                case INTEGER:
                    value = typeFactory.createInteger(cdata);
                    break;
                case BOOLEAN:
                    value = typeFactory.createBoolean(cdata);
                    break;
                case DOUBLE:
                    value = typeFactory.createDouble(cdata);
                    break;
                case DATE:
                    value = typeFactory.createDate(cdata);
                    break;
                case BASE64:
                    value = typeFactory.createBase64(cdata);
                    break;
                case STRING:
                    value = typeFactory.createString(cdata);
                    break;
                case STRUCT:
                    // this is the name to use for the next member of this struct
                    nextMemberName = cdata;
                    break;
            }
        }

        /**
         * This is a performance hack to get the type of a value
         * without casting the Object.  It breaks the contract of
         * method hashCode, but it doesn't matter since Value objects
         * are never used as keys in Hashtables.
         */
        public int hashCode()
        {
            return type;
        }

        /**
         *
         * @return
         */
        public String toString()
        {
            return (types[type] + " element " + value);
        }
    }
}
package org.apache.xmlrpc;

/*
 * The Apache Software License, Version 1.1
 *
 *
 * Copyright (c) 2001 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, 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 "XML-RPC" 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 [EMAIL PROTECTED]
 *
 * 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 (INCLUDING, 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.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Iterator;
import java.util.Properties;

import org.apache.xmlrpc.util.DateTool;

/**
 * A quick and dirty XML writer.  If you feed it a
 * <code>ByteArrayInputStream</code>, it may be necessary to call
 * <code>writer.flush()</code> before calling
 * <code>buffer.toByteArray()</code> to get the data written to
 * your byte buffer.
 *
 * @author <a href="mailto:[EMAIL PROTECTED]";>Hannes Wallnoefer</a>
 * @author <a href="mailto:[EMAIL PROTECTED]";>Daniel Rall</a>
 */
class XmlWriter extends OutputStreamWriter
{
    // Various XML pieces.
    protected static final String PROLOG_START =
        "<?xml version=\"1.0\" encoding=\"";
    protected static final String PROLOG_END = "\"?>";
    protected static final String CLOSING_TAG_START = "</";
    protected static final String SINGLE_TAG_END = "/>";
    protected static final String LESS_THAN_ENTITY = "&lt;";
    protected static final String GREATER_THAN_ENTITY = "&gt;";
    protected static final String AMPERSAND_ENTITY = "&amp;";

    /**
     * Java's name for the the ISO8859_1 encoding.
     */
    protected static final String ISO8859_1 = "ISO8859_1";

    /**
     * Java's name for the the UTF8 encoding.
     */
    protected static final String UTF8 = "UTF8";

    /**
     * Mapping between Java encoding names and "real" names used in
     * XML prolog.
     */
    private static Properties encodings = new Properties();

    static
    {
        encodings.put(UTF8, "UTF-8");
        encodings.put(ISO8859_1, "ISO-8859-1");
    }

    /**
     * Thread-safe wrapper for the <code>DateFormat</code> object used
     * to parse date/time values.
     */
    private static DateTool dateTool = new DateTool();

    /**
     * Creates a new instance.
     *
     * @param out The stream to write output to.
     * @param enc The encoding to using for outputing XML.
     * @throws UnsupportedEncodingException Encoding unrecognized.
     * @throws IOException Problem writing.
     */
    public XmlWriter(OutputStream out, String enc)
        throws UnsupportedEncodingException, IOException
    {
        // Super-class wants the Java form of the encoding.
        super(out, enc);

        // Add the XML prolog (including the encoding in XML form).
        write(PROLOG_START);
        write(canonicalizeEncoding(enc));
        write(PROLOG_END);
    }

    /**
     * Tranforms a Java encoding to the canonical XML form (if a
     * mapping is available).
     *
     * @param javaEncoding The name of the encoding as known by Java.
     * @return The XML encoding (if a mapping is available);
     * otherwise, the encoding as provided.
     */
    protected static String canonicalizeEncoding(String javaEncoding)
    {
        return encodings.getProperty(javaEncoding, javaEncoding);
    }

    /**
     * Writes the XML representation of a supported Java object type.
     *
     * @param obj The <code>Object</code> to write.
     * @exception XmlRpcException Unsupported character data found.
     * @exception IOException Problem writing data.
     * @throws IllegalArgumentException If a <code>null</code>
     * parameter is passed to this method (not supported by the <a
     * href="http://xml-rpc.com/spec";>XML-RPC specification</a>).
     */
    public void writeObject(Object obj)
        throws XmlRpcClientException, IOException
    {
        startElement("value");
        if (obj == null)
        {
            throw new XmlRpcClientException
                ("null values not supported by XML-RPC", null);
        }
        else if (obj instanceof String)
        {
            chardata(obj.toString());
        }
        else if (obj instanceof Integer)
        {
            startElement("int");
            write(obj.toString());
            endElement("int");
        }
        else if (obj instanceof Boolean)
        {
            startElement("boolean");
            write(((Boolean) obj).booleanValue() ? "1" : "0");
            endElement("boolean");
        }
        else if (obj instanceof Double || obj instanceof Float)
        {
            startElement("double");
            write(obj.toString());
            endElement("double");
        }
        else if (obj instanceof Date)
        {
            startElement("dateTime.iso8601");
            Date d = (Date) obj;
            write(dateTool.format(d));
            endElement("dateTime.iso8601");
        }
        else if (obj instanceof byte[])
        {
            startElement("base64");
            this.write(Base64.encode((byte[]) obj));
            endElement("base64");
        }
        else if (obj instanceof Object[])
        {
            startElement("array");
            startElement("data");
            Object[] array = (Object []) obj;
            for (int i = 0; i < array.length; i++)
            {
                writeObject(array[i]);
            }
            endElement("data");
            endElement("array");
        }
        else if (obj instanceof List)
        {
            startElement("array");
            startElement("data");
            List array = (List) obj;
            int size = array.size();
            for (int i = 0; i < size; i++)
            {
                writeObject(array.get(i));
            }
            endElement("data");
            endElement("array");
        }
        else if (obj instanceof Map)
        {
            startElement("struct");
            Map struct = (Map) obj;
            for (Iterator e = struct.keySet().iterator(); e.hasNext(); )
            {
                String key = (String) e.next();
                Object value = struct.get(key);
                startElement("member");
                startElement("name");
                chardata(key);
                endElement("name");
                writeObject(value);
                endElement("member");
            }
            endElement("struct");
        }
        else
        {
            throw new XmlRpcClientException("unsupported Java type: "
                                       + obj.getClass(), null);
        }
        endElement("value");
    }

    /**
     * This is used to write out the Base64 output...
     */
    protected void write(byte[] byteData) throws IOException
    {
        for (int i = 0; i < byteData.length; i++)
        {
            write(byteData[i]);
        }
    }

    /**
     *
     * @param elem
     * @throws IOException
     */
    protected void startElement(String elem) throws IOException
    {
        write('<');
        write(elem);
        write('>');
    }

    /**
     *
     * @param elem
     * @throws IOException
     */
    protected void endElement(String elem) throws IOException
    {
        write(CLOSING_TAG_START);
        write(elem);
        write('>');
    }

    /**
     *
     * @param elem
     * @throws IOException
     */
    protected void emptyElement(String elem) throws IOException
    {
        write('<');
        write(elem);
        write(SINGLE_TAG_END);
    }

    /**
     * Writes text as <code>PCDATA</code>.
     *
     * @param text The data to write.
     * @exception XmlRpcClientException Unsupported character data found.
     * @exception IOException Problem writing data.
     */
    protected void chardata(String text)
        throws XmlRpcClientException, IOException
    {
        int l = text.length ();
        for (int i = 0; i < l; i++)
        {
            char c = text.charAt (i);
            switch (c)
            {
            case '\t':
            case '\r':
            case '\n':
                write(c);
                break;
            case '<':
                write(LESS_THAN_ENTITY);
                break;
            case '>':
                write(GREATER_THAN_ENTITY);
                break;
            case '&':
                write(AMPERSAND_ENTITY);
                break;
            default:
                if (c < 0x20 || c > 0xff)
                {
                    // Though the XML-RPC spec allows any ASCII
                    // characters except '<' and '&', the XML spec
                    // does not allow this range of characters,
                    // resulting in a parse error from most XML
                    // parsers.
                    throw new XmlRpcClientException("Invalid character data " +
                                              "corresponding to XML entity &#" +
                                              String.valueOf((int) c) + ';', null);
                }
                else
                {
                    write(c);
                }
            }
        }
    }
}


Reply via email to