public class

ObjectHandler

extends HandlerBase
/*
 * Copyright (c) 2003, 2006, Oracle and/or its affiliates. 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.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.beans;

import com.sun.beans.finder.ClassFinder;

import java.beans.*;
import java.util.*;

import org.xml.sax.*;

import static java.util.Locale.ENGLISH;

/**
 * <b>WARNING</b>: This class is an implementation detail and only meant
 * for use within the core platform. You should NOT depend upon it! This
 * API may change drastically between dot dot release, and it may even be
 * removed.
 *
 * @see java.beans.XMLEncoder
 * @see java.io.ObjectInputStream
 *
 * @since 1.4
 *
 * @author Philip Milne
 */
public class ObjectHandler extends HandlerBase {

    public static Class typeNameToClass(String typeName) {
        typeName = typeName.intern();
        if (typeName == "boolean") return Boolean.class;
        if (typeName == "byte") return Byte.class;
        if (typeName == "char") return Character.class;
        if (typeName == "short") return Short.class;
        if (typeName == "int") return Integer.class;
        if (typeName == "long") return Long.class;
        if (typeName == "float") return Float.class;
        if (typeName == "double") return Double.class;
        if (typeName == "void") return Void.class;
        return null;
    }

    public static Class typeNameToPrimitiveClass(String typeName) {
        typeName = typeName.intern();
        if (typeName == "boolean") return boolean.class;
        if (typeName == "byte") return byte.class;
        if (typeName == "char") return char.class;
        if (typeName == "short") return short.class;
        if (typeName == "int") return int.class;
        if (typeName == "long") return long.class;
        if (typeName == "float") return float.class;
        if (typeName == "double") return double.class;
        if (typeName == "void") return void.class;
        return null;
    }

    /**
     * Returns the <code>Class</code> object associated with
     * the class or interface with the given string name,
     * using the default class loader.
     *
     * @param name  fully qualified name of the desired class
     * @param cl    class loader from which the class must be loaded
     * @return class object representing the desired class
     *
     * @exception ClassNotFoundException  if the class cannot be located
     *                                    by the specified class loader
     *
     * @deprecated As of JDK version 7, replaced by
     *             {@link ClassFinder#resolveClass(String)}.
     */
    @Deprecated
    public static Class classForName(String name) throws ClassNotFoundException {
        return ClassFinder.resolveClass(name);
    }

    /**
     * Returns the <code>Class</code> object associated with
     * the class or interface with the given string name,
     * using the given class loader.
     *
     * @param name  fully qualified name of the desired class
     * @param cl    class loader from which the class must be loaded
     * @return class object representing the desired class
     *
     * @exception ClassNotFoundException  if the class cannot be located
     *                                    by the specified class loader
     *
     * @deprecated As of JDK version 7, replaced by
     *             {@link ClassFinder#resolveClass(String,ClassLoader)}.
     */
    @Deprecated
    public static Class classForName(String name, ClassLoader cl)
        throws ClassNotFoundException {
        return ClassFinder.resolveClass(name, cl);
    }

    private Hashtable environment;
    private Vector expStack;
    private StringBuffer chars;
    private XMLDecoder is;
    private ClassLoader ldr;
    private int itemsRead = 0;
    private boolean isString;

    public ObjectHandler() {
        environment = new Hashtable();
        expStack = new Vector();
        chars = new StringBuffer();
    }

    public ObjectHandler(XMLDecoder is) {
        this();
        this.is = is;
    }

    /* loader can be null */
    public ObjectHandler(XMLDecoder is, ClassLoader loader) {
        this(is);
        this.ldr = loader;
    }


    public void reset() {
        expStack.clear();
        chars.setLength(0);
        MutableExpression e = new MutableExpression();
        e.setTarget(classForName2("java.lang.Object"));
        e.setMethodName("null");
        expStack.add(e);
    }

    private Object getValue(Expression exp) {
        try {
            return exp.getValue();
        }
        catch (Exception e) {
            if (is != null) {
                is.getExceptionListener().exceptionThrown(e);
            }
            return null;
        }
    }

    private void addArg(Object arg) {
        lastExp().addArg(arg);
    }

    private Object pop(Vector v) {
        int last = v.size()-1;
        Object result = v.get(last);
        v.remove(last);
        return result;
    }

    private Object eval() {
        return getValue(lastExp());
    }

    private MutableExpression lastExp() {
        return (MutableExpression)expStack.lastElement();
    }

    public Object dequeueResult() {
        Object[] results = lastExp().getArguments();
        return results[itemsRead++];
    }

    private boolean isPrimitive(String name) {
        return name != "void" && typeNameToClass(name) != null;
    }

    private void simulateException(String message) {
        Exception e = new Exception(message);
        e.fillInStackTrace();
        if (is != null) {
            is.getExceptionListener().exceptionThrown(e);
        }
    }

    private Class classForName2(String name) {
        try {
            return ClassFinder.resolveClass(name, this.ldr);
        }
        catch (ClassNotFoundException e) {
            if (is != null) {
                is.getExceptionListener().exceptionThrown(e);
            }
        }
        return null;
    }

    private HashMap getAttributes(AttributeList attrs) {
        HashMap attributes = new HashMap();
        if (attrs != null && attrs.getLength() > 0) {
            for(int i = 0; i < attrs.getLength(); i++) {
                attributes.put(attrs.getName(i), attrs.getValue(i));
            }
        }
        return attributes;
    }

    public void startElement(String name, AttributeList attrs) throws SAXException {
        name = name.intern(); // Xerces parser does not supply unique tag names.
        if (this.isString) {
            parseCharCode(name, getAttributes(attrs));
            return;
        }
        chars.setLength(0);

        HashMap attributes = getAttributes(attrs);
        MutableExpression e = new MutableExpression();

        // Target
        String className = (String)attributes.get("class");
        if (className != null) {
            e.setTarget(classForName2(className));
        }

        // Property
        Object property = attributes.get("property");
        String index = (String)attributes.get("index");
        if (index != null) {
            property = new Integer(index);
            e.addArg(property);
        }
        e.setProperty(property);

        // Method
        String methodName = (String)attributes.get("method");
        if (methodName == null && property == null) {
            methodName = "new";
        }
        e.setMethodName(methodName);

        // Tags
        if (name == "string") {
            e.setTarget(String.class);
            e.setMethodName("new");
            this.isString = true;
        }
        else if (isPrimitive(name)){
            Class wrapper = typeNameToClass(name);
            e.setTarget(wrapper);
            e.setMethodName("new");
            parseCharCode(name, attributes);
        }
        else if (name == "class") {
            e.setTarget(Class.class);
            e.setMethodName("forName");
        }
        else if (name == "null") {
            // Create an arbitrary expression that has a value of null - for
            // consistency.
            e.setTarget(Object.class);
            e.setMethodName("getSuperclass");
            e.setValue(null);
        }
        else if (name == "void") {
            if (e.getTarget() == null) { // this check is for "void class="foo" method= ..."
                e.setTarget(eval());
            }
        }
        else if (name == "array") {
            // The class attribute means sub-type for arrays.
            String subtypeName = (String)attributes.get("class");
            Class subtype = (subtypeName == null) ? Object.class : classForName2(subtypeName);
            String length = (String)attributes.get("length");
            if (length != null) {
                e.setTarget(java.lang.reflect.Array.class);
                e.addArg(subtype);
                e.addArg(new Integer(length));
            }
            else {
                Class arrayClass = java.lang.reflect.Array.newInstance(subtype, 0).getClass();
                e.setTarget(arrayClass);
            }
        }
        else if (name == "java") {
            e.setValue(is); // The outermost scope is the stream itself.
        }
        else if (name == "object") {
        }
        else {
            simulateException("Unrecognized opening tag: " + name + " " + attrsToString(attrs));
            return;
        }

        // ids
        String idName = (String)attributes.get("id");
        if (idName != null) {
            environment.put(idName, e);
        }

        // idrefs
        String idrefName = (String)attributes.get("idref");
        if (idrefName != null) {
            e.setValue(lookup(idrefName));
        }

        // fields
        String fieldName = (String)attributes.get("field");
        if (fieldName != null) {
            e.setValue(getFieldValue(e.getTarget(), fieldName));
        }
        expStack.add(e);
    }

    private Object getFieldValue(Object target, String fieldName) {
        try {
            Class type = target.getClass();
            if (type == Class.class) {
                type = (Class)target;
            }
            java.lang.reflect.Field f = sun.reflect.misc.FieldUtil.getField(type, fieldName);
            return f.get(target);
        }
        catch (Exception e) {
            if (is != null) {
                is.getExceptionListener().exceptionThrown(e);
            }
            return null;
        }
    }

    private String attrsToString(AttributeList attrs) {
        StringBuffer b = new StringBuffer();
        for (int i = 0; i < attrs.getLength (); i++) {
            b.append(attrs.getName(i)+"=\""+attrs.getValue(i)+"\" ");
        }
        return b.toString();
    }

    public void characters(char buf [], int offset, int len) throws SAXException {
        chars.append(new String(buf, offset, len));
    }

    private void parseCharCode(String name, Map map) {
        if (name == "char") {
            String value = (String) map.get("code");
            if (value != null) {
                this.chars.append(parseIntAsChar(value));
            }
        }
    }

    private static char parseIntAsChar(String data) {
        try {
            int i = data.startsWith("#")
                    ? Integer.parseInt(data.substring(1), 16)
                    : Integer.parseInt(data);

            // be convinced, that valid character code is read
            char ch = (char) i;
            if (ch == i)
                return ch;

            throw new IllegalArgumentException("Wrong character code: '" + data + "'");
        } catch (NumberFormatException exception) {
            throw new IllegalArgumentException("Wrong character code: '" + data + "'", exception);
        }
    }

    public Object lookup(String s) {
        Expression e = (Expression)environment.get(s);
        if (e == null) {
            simulateException("Unbound variable: " + s);
        }
        return getValue(e);
    }

    public void register(String id, Object value) {
        Expression e = new MutableExpression();
        e.setValue(value);
        environment.put(id, e);
    }

    public void endElement(String name) throws SAXException {
        name = name.intern(); // Xerces parser does not supply unique tag names.
        if (name == "string") {
            this.isString = false;
        } else if (this.isString) {
            return;
        }
        if (name == "java") {
            return;
        }
        if (isPrimitive(name) || name == "string" || name == "class") {
            addArg(chars.toString());
        }
        if (name == "object" || name == "array" || name == "void" ||
                isPrimitive(name) || name == "string" || name == "class" ||
                name == "null") {
            Expression e = (Expression)pop(expStack);
            Object value = getValue(e);
            if (name != "void") {
                addArg(value);
            }
        }
        else {
            simulateException("Unrecognized closing tag: " + name);
        }
    }
}


class MutableExpression extends Expression {
    private Object target;
    private String methodName;

    private Object property;
    private Vector argV = new Vector();

    private String capitalize(String propertyName) {
        if (propertyName.length() == 0) {
            return propertyName;
        }
        return propertyName.substring(0, 1).toUpperCase(ENGLISH) + propertyName.substring(1);
    }

    public MutableExpression() {
        super(null, null, null);
    }

    public Object[] getArguments() {
        return argV.toArray();
    }

    public String getMethodName() {
        if (property == null) {
            return methodName;
        }
        int setterArgs = (property instanceof String) ? 1 : 2;
        String methodName = (argV.size() == setterArgs) ? "set" : "get";
        if (property instanceof String) {
            return methodName + capitalize((String)property);
        }
        else {
            return methodName;
        }
    }

    public void addArg(Object arg) {
        argV.add(arg);
    }

    public void setTarget(Object target) {
        this.target = target;
    }

    public Object getTarget() {
        return target;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public void setProperty(Object property) {
        this.property = property;
    }

    public void setValue(Object value) {
        super.setValue(value);
    }

    public Object getValue() throws Exception {
        return super.getValue();
    }
}