view src/main/java/org/msgpack/template/builder/beans/XMLDecoder.java @ 0:cb825acd883a

first commit
author sugi
date Sat, 18 Oct 2014 15:06:15 +0900
parents
children
line wrap: on
line source

// MODIFIED FOR THE MSGPACK PROJECT
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements.  See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License.  You may obtain a copy of the License at
// 
// http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
//

package org.msgpack.template.builder.beans;

import java.io.InputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Stack;

import javax.xml.parsers.SAXParserFactory;

import org.apache.harmony.beans.internal.nls.Messages;
import org.msgpack.template.builder.beans.Statement.MethodComparator;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;


/**
 * <code>XMLDecoder</code> reads objects from xml created by
 * <code>XMLEncoder</code>.
 * <p>
 * The API is similar to <code>ObjectInputStream</code>.
 * </p>
 */
public class XMLDecoder {

    private ClassLoader defaultClassLoader = null;

    private static class DefaultExceptionListener implements ExceptionListener {

        public void exceptionThrown(Exception e) {
            System.err.println(e.getMessage());
            System.err.println("Continue..."); //$NON-NLS-1$
        }
    }

    private class SAXHandler extends DefaultHandler {

        boolean inJavaElem = false;

        HashMap<String, Object> idObjMap = new HashMap<String, Object>();

        @Override
        public void characters(char[] ch, int start, int length)
                throws SAXException {
            if (!inJavaElem) {
                return;
            }
            if (readObjs.size() > 0) {
                Elem elem = readObjs.peek();
                if (elem.isBasicType) {
                    String str = new String(ch, start, length);
                    elem.methodName = elem.methodName == null ? str
                            : elem.methodName + str;
                }
            }
        }

        @SuppressWarnings("nls")
        @Override
        public void startElement(String uri, String localName, String qName,
                Attributes attributes) throws SAXException {
            if (!inJavaElem) {
                if ("java".equals(qName)) {
                    inJavaElem = true;
                } else {
                    listener.exceptionThrown(new Exception(
                            Messages.getString("custom.beans.72", qName)));
                }
                return;
            }

            if ("object".equals(qName)) {
                startObjectElem(attributes);
            } else if ("array".equals(qName)) {
                startArrayElem(attributes);
            } else if ("void".equals(qName)) {
                startVoidElem(attributes);
            } else if ("boolean".equals(qName) || "byte".equals(qName)
                    || "char".equals(qName) || "class".equals(qName)
                    || "double".equals(qName) || "float".equals(qName)
                    || "int".equals(qName) || "long".equals(qName)
                    || "short".equals(qName) || "string".equals(qName)
                    || "null".equals(qName)) {
                startBasicElem(qName, attributes);
            }
        }

        @SuppressWarnings("nls")
        private void startObjectElem(Attributes attributes) {
            Elem elem = new Elem();
            elem.isExpression = true;
            elem.id = attributes.getValue("id");
            elem.idref = attributes.getValue("idref");
            elem.attributes = attributes;
            if (elem.idref == null) {
                obtainTarget(elem, attributes);
                obtainMethod(elem, attributes);
            }

            readObjs.push(elem);
        }

        private void obtainTarget(Elem elem, Attributes attributes) {
            String className = attributes.getValue("class"); //$NON-NLS-1$
            if (className != null) {
                try {
                    elem.target = classForName(className);
                } catch (ClassNotFoundException e) {
                    listener.exceptionThrown(e);
                }
            } else {
                Elem parent = latestUnclosedElem();
                if (parent == null) {
                    elem.target = owner;
                    return;
                }
                elem.target = execute(parent);
            }
        }

        @SuppressWarnings("nls")
        private void obtainMethod(Elem elem, Attributes attributes) {
            elem.methodName = attributes.getValue("method");
            if (elem.methodName != null) {
                return;
            }

            elem.methodName = attributes.getValue("property");
            if (elem.methodName != null) {
                elem.fromProperty = true;
                return;
            }

            elem.methodName = attributes.getValue("index");
            if (elem.methodName != null) {
                elem.fromIndex = true;
                return;
            }

            elem.methodName = attributes.getValue("field");
            if (elem.methodName != null) {
                elem.fromField = true;
                return;
            }

            elem.methodName = attributes.getValue("owner");
            if (elem.methodName != null) {
                elem.fromOwner = true;
                return;
            }

            elem.methodName = "new"; // default method name
        }

        @SuppressWarnings("nls")
        private Class<?> classForName(String className)
                throws ClassNotFoundException {
            if ("boolean".equals(className)) {
                return Boolean.TYPE;
            } else if ("byte".equals(className)) {
                return Byte.TYPE;
            } else if ("char".equals(className)) {
                return Character.TYPE;
            } else if ("double".equals(className)) {
                return Double.TYPE;
            } else if ("float".equals(className)) {
                return Float.TYPE;
            } else if ("int".equals(className)) {
                return Integer.TYPE;
            } else if ("long".equals(className)) {
                return Long.TYPE;
            } else if ("short".equals(className)) {
                return Short.TYPE;
            } else {
                return Class.forName(className, true,
                        defaultClassLoader == null ? Thread.currentThread()
                                .getContextClassLoader() : defaultClassLoader);
            }
        }

        private void startArrayElem(Attributes attributes) {
            Elem elem = new Elem();
            elem.isExpression = true;
            elem.id = attributes.getValue("id"); //$NON-NLS-1$
            elem.attributes = attributes;
            try {
                // find component class
                Class<?> compClass = classForName(attributes.getValue("class")); //$NON-NLS-1$
                String lengthValue = attributes.getValue("length"); //$NON-NLS-1$
                if (lengthValue != null) {
                    // find length
                    int length = Integer
                            .parseInt(attributes.getValue("length")); //$NON-NLS-1$
                    // execute, new array instance
                    elem.result = Array.newInstance(compClass, length);
                    elem.isExecuted = true;
                } else {
                    // create array without length attribute,
                    // delay the excution to the end,
                    // get array length from sub element
                    elem.target = compClass;
                    elem.methodName = "newArray"; //$NON-NLS-1$
                    elem.isExecuted = false;
                }
            } catch (Exception e) {
                listener.exceptionThrown(e);
            }
            readObjs.push(elem);
        }

        @SuppressWarnings("nls")
        private void startVoidElem(Attributes attributes) {
            Elem elem = new Elem();
            elem.id = attributes.getValue("id");
            elem.attributes = attributes;
            obtainTarget(elem, attributes);
            obtainMethod(elem, attributes);
            readObjs.push(elem);
        }

        @SuppressWarnings("nls")
        private void startBasicElem(String tagName, Attributes attributes) {
            Elem elem = new Elem();
            elem.isBasicType = true;
            elem.isExpression = true;
            elem.id = attributes.getValue("id");
            elem.idref = attributes.getValue("idref");
            elem.attributes = attributes;
            elem.target = tagName;
            readObjs.push(elem);
        }

        @Override
        public void endElement(String uri, String localName, String qName)
                throws SAXException {
            if (!inJavaElem) {
                return;
            }
            if ("java".equals(qName)) { //$NON-NLS-1$
                inJavaElem = false;
                return;
            }
            // find the elem to close
            Elem toClose = latestUnclosedElem();
            if ("string".equals(toClose.target)) {
                StringBuilder sb = new StringBuilder();
                for (int index = readObjs.size() - 1; index >= 0; index--) {
                    Elem elem = (Elem) readObjs.get(index);
                    if (toClose == elem) {
                        break;
                    }
                    if ("char".equals(elem.target)) {
                        sb.insert(0, elem.methodName);
                    }
                }
                toClose.methodName = toClose.methodName != null ? toClose.methodName
                        + sb.toString()
                        : sb.toString();
            }
            // make sure it is executed
            execute(toClose);
            // set to closed
            toClose.isClosed = true;
            // pop it and its children
            while (readObjs.pop() != toClose) {
                //
            }

            if (toClose.isExpression) {
                // push back expression
                readObjs.push(toClose);
            }
        }

        private Elem latestUnclosedElem() {
            for (int i = readObjs.size() - 1; i >= 0; i--) {
                Elem elem = readObjs.get(i);
                if (!elem.isClosed) {
                    return elem;
                }
            }
            return null;
        }

        private Object execute(Elem elem) {
            if (elem.isExecuted) {
                return elem.result;
            }

            // execute to obtain result
            try {
                if (elem.idref != null) {
                    elem.result = idObjMap.get(elem.idref);
                } else if (elem.isBasicType) {
                    elem.result = executeBasic(elem);
                } else {
                    elem.result = executeCommon(elem);
                }
            } catch (Exception e) {
                listener.exceptionThrown(e);
            }

            // track id
            if (elem.id != null) {
                idObjMap.put(elem.id, elem.result);
            }

            elem.isExecuted = true;
            return elem.result;
        }

        @SuppressWarnings("nls")
        private Object executeCommon(Elem elem) throws Exception {
            // pop args
            ArrayList<Object> args = new ArrayList<Object>(5);
            while (readObjs.peek() != elem) {
                Elem argElem = readObjs.pop();
                args.add(0, argElem.result);
            }
            // decide method name
            String method = elem.methodName;
            if (elem.fromProperty) {
                method = (args.size() == 0 ? "get" : "set")
                        + capitalize(method);
            }
            if (elem.fromIndex) {
                Integer index = Integer.valueOf(method);
                args.add(0, index);
                method = args.size() == 1 ? "get" : "set";
            }
            if (elem.fromField) {
                Field f = ((Class<?>) elem.target).getField(method);
                return (new Expression(f, "get", new Object[] { null }))
                        .getValue();
            }
            if (elem.fromOwner) {
                return owner;
            }

            if (elem.target == owner) {
                if ("getOwner".equals(method)) {
                    return owner;
                }
                Class<?>[] c = new Class[args.size()];
                for (int i = 0; i < args.size(); i++) {
                    Object arg = args.get(i);
                    c[i] = (arg == null ? null: arg.getClass());
                }

                // Try actual match method
                try {
                    Method m = owner.getClass().getMethod(method, c);
                    return m.invoke(owner, args.toArray());
                } catch (NoSuchMethodException e) {
                    // Do nothing
                }

                // Find the specific method matching the parameter
                Method mostSpecificMethod = findMethod(
                        owner instanceof Class<?> ? (Class<?>) owner : owner
                                .getClass(), method, c);

                return mostSpecificMethod.invoke(owner, args.toArray());
            }

            // execute
            Expression exp = new Expression(elem.target, method, args.toArray());
            return exp.getValue();
        }

        private Method findMethod(Class<?> clazz, String methodName,
                Class<?>[] clazzes) throws Exception {
            Method[] methods = clazz.getMethods();
            ArrayList<Method> matchMethods = new ArrayList<Method>();

            // Add all matching methods into a ArrayList
            for (Method method : methods) {
                if (!methodName.equals(method.getName())) {
                    continue;
                }
                Class<?>[] parameterTypes = method.getParameterTypes();
                if (parameterTypes.length != clazzes.length) {
                    continue;
                }
                boolean match = true;
                for (int i = 0; i < parameterTypes.length; i++) {
                    boolean isNull = (clazzes[i] == null);
                    boolean isPrimitive = isPrimitiveWrapper(clazzes[i], parameterTypes[i]);
                    boolean isAssignable = isNull? false : parameterTypes[i].isAssignableFrom(clazzes[i]);
                    if ( isNull || isPrimitive || isAssignable ) {
                        continue;
                    }
                    match = false;
                }
                if (match) {
                    matchMethods.add(method);
                }
            }

            int size = matchMethods.size();
            if (size == 1) {
                // Only one method matches, just invoke it
                return matchMethods.get(0);
            } else if (size == 0) {
                // Does not find any matching one, throw exception
                throw new NoSuchMethodException(Messages.getString(
                        "custom.beans.41", methodName)); //$NON-NLS-1$
            }

            // There are more than one method matching the signature
            // Find the most specific one to invoke
            MethodComparator comparator = new MethodComparator(methodName,
                    clazzes);
            Method chosenOne = matchMethods.get(0);
            matchMethods.remove(0);
            int methodCounter = 1;
            for (Method method : matchMethods) {
                int difference = comparator.compare(chosenOne, method);
                if (difference > 0) {
                    chosenOne = method;
                    methodCounter = 1;
                } else if (difference == 0) {
                    methodCounter++;
                }
            }
            if (methodCounter > 1) {
                // if 2 methods have same relevance, throw exception
                throw new NoSuchMethodException(Messages.getString(
                                                                   "custom.beans.62", methodName)); //$NON-NLS-1$
            }
            return chosenOne;
        }

        private boolean isPrimitiveWrapper(Class<?> wrapper, Class<?> base) {
            return (base == boolean.class) && (wrapper == Boolean.class)
                    || (base == byte.class) && (wrapper == Byte.class)
                    || (base == char.class) && (wrapper == Character.class)
                    || (base == short.class) && (wrapper == Short.class)
                    || (base == int.class) && (wrapper == Integer.class)
                    || (base == long.class) && (wrapper == Long.class)
                    || (base == float.class) && (wrapper == Float.class)
                    || (base == double.class) && (wrapper == Double.class);
        }

        private String capitalize(String str) {
            StringBuilder buf = new StringBuilder(str);
            buf.setCharAt(0, Character.toUpperCase(buf.charAt(0)));
            return buf.toString();
        }

        @SuppressWarnings("nls")
        private Object executeBasic(Elem elem) throws Exception {
            String tag = (String) elem.target;
            String value = elem.methodName;

            if ("null".equals(tag)) {
                return null;
            } else if ("string".equals(tag)) {
                return value == null ? "" : value;
            } else if ("class".equals(tag)) {
                return classForName(value);
            } else if ("boolean".equals(tag)) {
                return Boolean.valueOf(value);
            } else if ("byte".equals(tag)) {
                return Byte.valueOf(value);
            } else if ("char".equals(tag)) {
                if (value == null && elem.attributes != null) {
                    String codeAttr = elem.attributes.getValue("code");
                    if (codeAttr != null) {
                        Character character = new Character((char) Integer
                                .valueOf(codeAttr.substring(1), 16).intValue());
                        elem.methodName = character.toString();
                        return character;
                    }
                }
                return Character.valueOf(value.charAt(0));
            } else if ("double".equals(tag)) {
                return Double.valueOf(value);
            } else if ("float".equals(tag)) {
                return Float.valueOf(value);
            } else if ("int".equals(tag)) {
                return Integer.valueOf(value);
            } else if ("long".equals(tag)) {
                return Long.valueOf(value);
            } else if ("short".equals(tag)) {
                return Short.valueOf(value);
            } else {
                throw new Exception(Messages.getString("custom.beans.71", tag));
            }
        }

        @Override
        public void error(SAXParseException e) throws SAXException {
            listener.exceptionThrown(e);
        }

        @Override
        public void fatalError(SAXParseException e) throws SAXException {
            listener.exceptionThrown(e);
        }

        @Override
        public void warning(SAXParseException e) throws SAXException {
            listener.exceptionThrown(e);
        }
    }

    private static class Elem {
        String id;

        String idref;

        boolean isExecuted;

        boolean isExpression;

        boolean isBasicType;

        boolean isClosed;

        Object target;

        String methodName;

        boolean fromProperty;

        boolean fromIndex;

        boolean fromField;

        boolean fromOwner;

        Attributes attributes;

        Object result;

    }

    private InputStream inputStream;

    private ExceptionListener listener;

    private Object owner;

    private Stack<Elem> readObjs = new Stack<Elem>();

    private int readObjIndex = 0;

    private SAXHandler saxHandler = null;

    /**
     * Create a decoder to read from specified input stream.
     * 
     * @param inputStream
     *            an input stream of xml
     */
    public XMLDecoder(InputStream inputStream) {
        this(inputStream, null, null, null);
    }

    /**
     * Create a decoder to read from specified input stream.
     * 
     * @param inputStream
     *            an input stream of xml
     * @param owner
     *            the owner of this decoder
     */
    public XMLDecoder(InputStream inputStream, Object owner) {
        this(inputStream, owner, null, null);
    }

    /**
     * Create a decoder to read from specified input stream.
     * 
     * @param inputStream
     *            an input stream of xml
     * @param owner
     *            the owner of this decoder
     * @param listener
     *            listen to the exceptions thrown by the decoder
     */
    public XMLDecoder(InputStream inputStream, Object owner,
            ExceptionListener listener) {
        this(inputStream, owner, listener, null);
    }

    public XMLDecoder(InputStream inputStream, Object owner,
            ExceptionListener listener, ClassLoader cl) {
        this.inputStream = inputStream;
        this.owner = owner;
        this.listener = (listener == null) ? new DefaultExceptionListener()
                : listener;
        defaultClassLoader = cl;
    }

    /**
     * Close the input stream of xml data.
     */
    public void close() {
        if (inputStream == null) {
            return;
        }
        try {
            inputStream.close();
        } catch (Exception e) {
            listener.exceptionThrown(e);
        }
    }

    /**
     * Returns the exception listener.
     * 
     * @return the exception listener
     */
    public ExceptionListener getExceptionListener() {
        return listener;
    }

    /**
     * Returns the owner of this decoder.
     * 
     * @return the owner of this decoder
     */
    public Object getOwner() {
        return owner;
    }

    /**
     * Reads the next object.
     * 
     * @return the next object
     * @exception ArrayIndexOutOfBoundsException
     *                if no more objects to read
     */
    @SuppressWarnings("nls")
    public Object readObject() {
        if (inputStream == null) {
            return null;
        }
        if (saxHandler == null) {
            saxHandler = new SAXHandler();
            try {
                SAXParserFactory.newInstance().newSAXParser().parse(
                        inputStream, saxHandler);
            } catch (Exception e) {
                this.listener.exceptionThrown(e);
            }
        }

        if (readObjIndex >= readObjs.size()) {
            throw new ArrayIndexOutOfBoundsException(Messages.getString("custom.beans.70"));
        }
        Elem elem = readObjs.get(readObjIndex);
        if (!elem.isClosed) {
            // bad element, error occurred while parsing
            throw new ArrayIndexOutOfBoundsException(Messages.getString("custom.beans.70"));
        }
        readObjIndex++;
        return elem.result;
    }

    /**
     * Sets the exception listener.
     * 
     * @param listener
     *            an exception listener
     */
    public void setExceptionListener(ExceptionListener listener) {
        if (listener != null) {
            this.listener = listener;
        }
    }

    /**
     * Sets the owner of this decoder.
     * 
     * @param owner
     *            the owner of this decoder
     */
    public void setOwner(Object owner) {
        this.owner = owner;
    }
}