Package org.apache.pivot.wtkx

Source Code of org.apache.pivot.wtkx.WTKXSerializer$AttributeInvocationHandler

/*
* 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.apache.pivot.wtkx;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.Map;
import java.util.Set;

import javax.script.Bindings;
import javax.script.Invocable;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleBindings;
import javax.xml.stream.Location;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

import org.apache.pivot.beans.BeanAdapter;
import org.apache.pivot.collections.ArrayList;
import org.apache.pivot.collections.Dictionary;
import org.apache.pivot.collections.HashMap;
import org.apache.pivot.collections.List;
import org.apache.pivot.collections.Sequence;
import org.apache.pivot.serialization.SerializationException;
import org.apache.pivot.serialization.Serializer;
import org.apache.pivot.util.ListenerList;
import org.apache.pivot.util.Resources;
import org.apache.pivot.util.ThreadUtilities;
import org.apache.pivot.util.Vote;

/**
* Loads an object hierarchy from an XML document.
*/
public class WTKXSerializer implements Serializer<Object>, Dictionary<String, Object> {
    private class NamedObjectBindings implements Bindings {
        @Override
        public Object get(Object key) {
            return namedObjects.get(key.toString());
        }

        @Override
        public Object put(String key, Object value) {
            return namedObjects.put(key, value);
        }

        @Override
        public void putAll(Map<? extends String, ? extends Object> map) {
            for (String key : map.keySet()) {
                put(key, map.get(key));
            }
        }

        @Override
        public Object remove(Object key) {
            return namedObjects.remove(key.toString());
        }

        @Override
        public void clear() {
            namedObjects.clear();
        }

        @Override
        public boolean containsKey(Object key) {
            return namedObjects.containsKey(key.toString());
        }

        @Override
        public boolean containsValue(Object value) {
            boolean contains = false;
            for (String key : namedObjects) {
                if (namedObjects.get(key).equals(value)) {
                    contains = true;
                    break;
                }
            }

            return contains;
        }

        @Override
        public boolean isEmpty() {
            return namedObjects.isEmpty();
        }

        @Override
        public Set<String> keySet() {
            java.util.HashSet<String> keySet = new java.util.HashSet<String>();
            for (String key : namedObjects) {
                keySet.add(key);
            }

            return keySet;
        }

        @Override
        public Set<Entry<String, Object>> entrySet() {
            java.util.HashMap<String, Object> hashMap = new java.util.HashMap<String, Object>();
            for (String key : namedObjects) {
                hashMap.put(key, namedObjects.get(key));
            }

            return hashMap.entrySet();
        }

        @Override
        public int size() {
            return namedObjects.getCount();
        }

        @Override
        public Collection<Object> values() {
            java.util.ArrayList<Object> values = new java.util.ArrayList<Object>();
            for (String key : namedObjects) {
                values.add(namedObjects.get(key));
            }

            return values;
        }
    }

    protected static class Element  {
        public enum Type {
            DEFINE,
            INSTANCE,
            INCLUDE,
            SCRIPT,
            READ_ONLY_PROPERTY,
            WRITABLE_PROPERTY
        }

        public final Element parent;
        public final String namespaceURI;
        public final String localName;
        public final List<Attribute> attributes;
        public final Type type;
        public final String id;
        public Object value;
        public final int lineNumber;

        public Element(Element parent, String namespaceURI, String localName, List<Attribute> attributes,
            Type type, String id, Object value, int lineNumber) {
            this.parent = parent;
            this.namespaceURI = namespaceURI;
            this.localName = localName;
            this.attributes = attributes;
            this.type = type;
            this.id = id;
            this.value = value;
            this.lineNumber = lineNumber;
        }
    }

    protected static class Attribute {
        public final String namespaceURI;
        public final String localName;
        public final String value;

        public Attribute(String namespaceURI, String localName, String value) {
            this.namespaceURI = namespaceURI;
            this.localName = localName;
            this.value = value;
        }
    }

    private static class AttributeInvocationHandler implements InvocationHandler {
        private ScriptEngine scriptEngine;
        private String event;
        private String script;

        private static final String ARGUMENTS_KEY = "arguments";

        public AttributeInvocationHandler(ScriptEngine scriptEngine, String event, String script) {
            this.scriptEngine = scriptEngine;
            this.event = event;
            this.script = script;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
            Object result = null;

            String methodName = method.getName();
            if (methodName.equals(event)) {
                try {
                    SimpleBindings bindings = new SimpleBindings();
                    bindings.put(ARGUMENTS_KEY, args);
                    scriptEngine.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
                    scriptEngine.eval(script);
                } catch (ScriptException exception) {
                    System.err.println(exception);
                    System.err.println(script);
                }
            }

            // If the function didn't return a value, return the default
            Class<?> returnType = method.getReturnType();
            if (returnType == Vote.class) {
                result = Vote.APPROVE;
            } else if (returnType == Boolean.TYPE) {
                result = false;
            }

            return result;
        }
    }

    private static class ElementInvocationHandler implements InvocationHandler {
        private ScriptEngine scriptEngine;

        public ElementInvocationHandler(ScriptEngine scriptEngine) {
            this.scriptEngine = scriptEngine;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
            Object result = null;

            String methodName = method.getName();
            Bindings bindings = scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE);
            if (bindings.containsKey(methodName)) {
                Invocable invocable;
                try {
                    invocable = (Invocable)scriptEngine;
                } catch (ClassCastException exception) {
                    throw new SerializationException(exception);
                }

                result = invocable.invokeFunction(methodName, args);
            }

            // If the function didn't return a value, return the default
            if (result == null) {
                Class<?> returnType = method.getReturnType();
                if (returnType == Vote.class) {
                    result = Vote.APPROVE;
                } else if (returnType == Boolean.TYPE) {
                    result = false;
                }
            }

            return result;
        }
    }

    private Resources resources;
    private HashMap<String, Object> namedObjects;
    private HashMap<String, WTKXSerializer> namedSerializers;

    private XMLInputFactory xmlInputFactory;
    private ScriptEngineManager scriptEngineManager;

    private boolean inline;
    private boolean clearNamespaceOnRead;
    private URL location = null;
    private Element element = null;
    private Object root = null;

    private String language = DEFAULT_LANGUAGE;

    public static final char URL_PREFIX = '@';
    public static final char RESOURCE_KEY_PREFIX = '%';
    public static final char OBJECT_REFERENCE_PREFIX = '$';

    public static final String LANGUAGE_PROCESSING_INSTRUCTION = "language";

    public static final String WTKX_PREFIX = "wtkx";
    public static final String ID_ATTRIBUTE = "id";

    public static final String INCLUDE_TAG = "include";
    public static final String INCLUDE_SRC_ATTRIBUTE = "src";
    public static final String INCLUDE_RESOURCES_ATTRIBUTE = "resources";
    public static final String INCLUDE_INLINE_ATTRIBUTE = "inline";

    public static final String SCRIPT_TAG = "script";
    public static final String SCRIPT_SRC_ATTRIBUTE = "src";
    public static final String SCRIPT_LANGUAGE_ATTRIBUTE = "language";

    public static final String DEFINE_TAG = "define";

    public static final String DEFAULT_LANGUAGE = "javascript";

    public static final String MIME_TYPE = "application/wtkx";

    public WTKXSerializer() {
        this(null, null);
    }

    public WTKXSerializer(Resources resources) {
        this(resources, null);
    }

    protected WTKXSerializer(Resources resources, WTKXSerializer owner) {
        this.resources = resources;

        if (owner == null) {
            inline = false;
            namedObjects = new HashMap<String, Object>();
            namedSerializers = new HashMap<String, WTKXSerializer>();
        } else {
            inline = true;
            namedObjects = owner.namedObjects;
            namedSerializers = owner.namedSerializers;
        }

        clearNamespaceOnRead = !inline;

        xmlInputFactory = XMLInputFactory.newInstance();
        xmlInputFactory.setProperty("javax.xml.stream.isCoalescing", true);

        scriptEngineManager = new javax.script.ScriptEngineManager();
        scriptEngineManager.setBindings(new NamedObjectBindings());
    }

    public Resources getResources() {
        return resources;
    }

    public Object readObject(String resourceName)
        throws IOException, SerializationException {
        if (resourceName == null) {
            throw new IllegalArgumentException("resourceName is null.");
        }

        ClassLoader classLoader = ThreadUtilities.getClassLoader();
        URL location = classLoader.getResource(resourceName);

        if (location == null) {
            throw new SerializationException("Could not find resource named \""
                + resourceName + "\".");
        }

        return readObject(location);
    }

    public Object readObject(Object baseObject, String resourceName)
        throws IOException, SerializationException {
        if (baseObject == null) {
            throw new IllegalArgumentException("baseObject is null.");
        }

        if (resourceName == null) {
            throw new IllegalArgumentException("resourceName is null.");
        }

        return readObject(baseObject.getClass(), resourceName);
    }

    public Object readObject(Class<?> baseType, String resourceName)
        throws IOException, SerializationException {
        if (baseType == null) {
            throw new IllegalArgumentException("baseType is null.");
        }

        if (resourceName == null) {
            throw new IllegalArgumentException("resourceName is null.");
        }

        URL location = baseType.getResource(resourceName);
        if (location == null) {
            throw new IllegalArgumentException("Could not find resource \""
                + resourceName + "\" for class \"" + baseType.getName() + "\".");
        }

        return readObject(location);
    }

    public Object readObject(URL location)
        throws IOException, SerializationException {
        if (location == null) {
            throw new IllegalArgumentException("location is null.");
        }

        this.location = location;

        InputStream inputStream = new BufferedInputStream(location.openStream());
        try {
            return readObject(inputStream);
        } finally {
            inputStream.close();
        }
    }

    @Override
    public Object readObject(InputStream inputStream)
        throws IOException, SerializationException {
        if (inputStream == null) {
            throw new IllegalArgumentException("inputStream is null.");
        }

        // Reset the serializer
        if (clearNamespaceOnRead) {
            namedObjects.clear();
            namedSerializers.clear();
        }

        root = null;
        language = DEFAULT_LANGUAGE;

        // Parse the XML stream
        element = null;

        try {
            try {
                XMLStreamReader xmlStreamReader = xmlInputFactory.createXMLStreamReader(inputStream);

                while (xmlStreamReader.hasNext()) {
                    int event = xmlStreamReader.next();

                    switch (event) {
                        case XMLStreamConstants.PROCESSING_INSTRUCTION: {
                            processProcessingInstruction(xmlStreamReader);
                            break;
                        }

                        case XMLStreamConstants.CHARACTERS: {
                            processCharacters(xmlStreamReader);
                            break;
                        }

                        case XMLStreamConstants.START_ELEMENT: {
                            processStartElement(xmlStreamReader);
                            break;
                        }

                        case XMLStreamConstants.END_ELEMENT: {
                            processEndElement(xmlStreamReader);
                            break;
                        }
                    }
                }
            } catch (XMLStreamException exception) {
                throw new SerializationException(exception);
            }
        } catch (IOException exception) {
            logException();
            throw exception;
        } catch (SerializationException exception) {
            logException();
            throw exception;
        } catch (RuntimeException exception) {
            logException();
            throw exception;
        }

        if (root instanceof Bindable
            && !inline) {
            bind(root);

            Bindable bindable = (Bindable)root;
            bindable.initialize(resources);
        }

        // Reset the serializer
        clearNamespaceOnRead = true;
        location = null;

        return root;
    }

    private void processProcessingInstruction(XMLStreamReader xmlStreamReader) {
        String piTarget = xmlStreamReader.getPITarget();
        String piData = xmlStreamReader.getPIData();

        if (piTarget.equals(LANGUAGE_PROCESSING_INSTRUCTION)) {
            language = piData;
        }
    }

    @SuppressWarnings("unchecked")
    private void processCharacters(XMLStreamReader xmlStreamReader) throws SerializationException {
        if (!xmlStreamReader.isWhiteSpace()) {
            // Process the text
            String text = xmlStreamReader.getText();

            switch (element.type) {
                case INSTANCE: {
                    if (element.value instanceof Sequence<?>) {
                        Sequence<Object> sequence = (Sequence<Object>)element.value;

                        try {
                            Method addMethod = sequence.getClass().getMethod("add", String.class);
                            addMethod.invoke(sequence, text);
                        } catch (NoSuchMethodException exception) {
                            throw new SerializationException("Text content cannot be added to "
                                + sequence.getClass().getName() + ".", exception);
                        } catch (InvocationTargetException exception) {
                            throw new SerializationException(exception);
                        } catch (IllegalAccessException exception) {
                            throw new SerializationException(exception);
                        }
                    }

                    break;
                }

                case SCRIPT:
                case WRITABLE_PROPERTY: {
                    element.value = text;
                    break;
                }

                default: {
                    throw new SerializationException("Unexpected characters in "
                        + element.type + " element.");
                }
            }
        }
    }

    private void processStartElement(XMLStreamReader xmlStreamReader) throws SerializationException {
        // Get element properties
        String namespaceURI = xmlStreamReader.getNamespaceURI();
        String prefix = xmlStreamReader.getPrefix();
        String localName = xmlStreamReader.getLocalName();

        // Build attribute list; these will be processed in the close tag
        String id = null;
        ArrayList<Attribute> attributes = new ArrayList<Attribute>();

        for (int i = 0, n = xmlStreamReader.getAttributeCount(); i < n; i++) {
            String attributePrefix = xmlStreamReader.getAttributePrefix(i);
            String attributeLocalName = xmlStreamReader.getAttributeLocalName(i);
            String attributeValue = xmlStreamReader.getAttributeValue(i);

            if (attributePrefix != null
                && attributePrefix.equals(WTKX_PREFIX)) {
                if (attributeLocalName.equals(ID_ATTRIBUTE)) {
                    if (attributeValue.length() == 0) {
                        throw new IllegalArgumentException(WTKX_PREFIX + ":" + ID_ATTRIBUTE
                            + " must not be empty.");
                    }

                    id = attributeValue;
                } else {
                    throw new SerializationException(WTKX_PREFIX + ":" + attributeLocalName
                        + " is not a valid attribute.");
                }
            } else {
                String attributeNamespaceURI = xmlStreamReader.getAttributeNamespace(i);
                if (attributeNamespaceURI == null) {
                    attributeNamespaceURI = xmlStreamReader.getNamespaceURI("");
                }

                attributes.add(new Attribute(attributeNamespaceURI, attributeLocalName,
                    attributeValue));
            }
        }

        // Determine the type and value of this element
        Element.Type elementType = null;
        Object value = null;

        if (prefix != null
            && prefix.equals(WTKX_PREFIX)) {
            // The element represents a WTKX operation
            if (element == null) {
                throw new SerializationException(prefix + ":" + localName
                    + " is not a valid root element.");
            }

            if (localName.equals(INCLUDE_TAG)) {
                elementType = Element.Type.INCLUDE;
            } else if (localName.equals(SCRIPT_TAG)) {
                elementType = Element.Type.SCRIPT;
            } else if (localName.equals(DEFINE_TAG)) {
                if (attributes.getLength() > 0) {
                    throw new SerializationException(WTKX_PREFIX + ":" + DEFINE_TAG
                        + " cannot have attributes.");
                }

                elementType = Element.Type.DEFINE;
            } else {
                throw new SerializationException(prefix + ":" + localName
                    + " is not a valid element.");
            }
        } else {
            if (Character.isUpperCase(localName.charAt(0))) {
                int i = localName.indexOf('.');
                if (i == localName.length() - 1) {
                    throw new SerializationException(localName + " is not a valid element name.");
                }

                if (i != -1
                    && Character.isLowerCase(localName.charAt(i + 1))) {
                    // The element represents an attached property
                    elementType = Element.Type.WRITABLE_PROPERTY;
                } else {
                    // The element represents a typed object
                    if (namespaceURI == null) {
                        throw new SerializationException("No XML namespace specified for "
                            + localName + " tag.");
                    }

                    String className = namespaceURI + "." + localName.replace('.', '$');

                    try {
                        Class<?> type = Class.forName(className);
                        elementType = Element.Type.INSTANCE;
                        value = createInstance(type, id);
                    } catch (Exception exception) {
                        throw new SerializationException(exception);
                    }
                }
            } else {
                // The element represents a property
                if (element == null
                    || element.type != Element.Type.INSTANCE) {
                    throw new SerializationException("Parent element must be a typed object.");
                }

                if (prefix != null
                    && prefix.length() > 0) {
                    throw new SerializationException("Property elements cannot have a namespace prefix.");
                }

                if (element.value instanceof Dictionary<?, ?>) {
                    elementType = Element.Type.WRITABLE_PROPERTY;
                } else {
                    BeanAdapter beanAdapter = new BeanAdapter(element.value);

                    if (beanAdapter.isReadOnly(localName)) {
                        elementType = Element.Type.READ_ONLY_PROPERTY;
                        value = beanAdapter.get(localName);
                        assert (value != null) : "Read-only properties cannot be null.";

                        if (attributes.getLength() > 0
                            && !(value instanceof Dictionary<?, ?>)) {
                            throw new SerializationException("Only read-only dictionaries can have attributes.");
                        }
                    } else {
                        if (attributes.getLength() > 0) {
                            throw new SerializationException("Writable property elements cannot have attributes.");
                        }

                        elementType = Element.Type.WRITABLE_PROPERTY;
                    }
                }
            }
        }

        // Set the current element
        String tagName = localName;
        if (prefix != null
            && prefix.length() > 0) {
            tagName = prefix + ":" + tagName;
        }

        Location xmlStreamLocation = xmlStreamReader.getLocation();
        element = new Element(element, namespaceURI, localName, attributes, elementType,
            id, value, xmlStreamLocation.getLineNumber());

        // If this is the root, set it
        if (element.parent == null) {
            root = element.value;
        }
    }

    @SuppressWarnings("unchecked")
    private void processEndElement(XMLStreamReader xmlStreamReader)
        throws SerializationException, IOException {
        String localName = xmlStreamReader.getLocalName();

        switch (element.type) {
            case INSTANCE:
            case INCLUDE: {
                ArrayList<Attribute> instancePropertyAttributes = new ArrayList<Attribute>();
                ArrayList<Attribute> staticPropertyAttributes = new ArrayList<Attribute>();

                if (element.type == Element.Type.INCLUDE) {
                    // Process attributes looking for src, resources, and static property
                    // setters only
                    String src = null;
                    Resources resources = this.resources;
                    boolean inline = false;

                    for (Attribute attribute : element.attributes) {
                        if (attribute.localName.equals(INCLUDE_SRC_ATTRIBUTE)) {
                            src = attribute.value;
                        } else if (attribute.localName.equals(INCLUDE_RESOURCES_ATTRIBUTE)) {
                            resources = new Resources(resources, attribute.value);
                        } else if (attribute.localName.equals(INCLUDE_INLINE_ATTRIBUTE)) {
                            inline = Boolean.parseBoolean(attribute.value);
                        } else {
                            if (!Character.isUpperCase(attribute.localName.charAt(0))) {
                                throw new SerializationException("Instance property setters are not"
                                    + " supported for " + WTKX_PREFIX + ":" + INCLUDE_TAG
                                    + " " + " tag.");
                            }

                            staticPropertyAttributes.add(attribute);
                        }
                    }

                    if (src == null) {
                        throw new SerializationException(INCLUDE_SRC_ATTRIBUTE
                            + " attribute is required for " + WTKX_PREFIX + ":" + INCLUDE_TAG
                            + " tag.");
                    }

                    // Read the object
                    WTKXSerializer serializer = createSerializer(resources, inline ? this : null);

                    if (element.id != null) {
                        if (namedSerializers.containsKey(element.id)) {
                            throw new SerializationException("Namespace ID " + element.id
                                + " is already in use.");
                        }

                        namedSerializers.put(element.id, serializer);
                    }

                    if (src.charAt(0) == '/') {
                        element.value = serializer.readObject(src.substring(1));
                    } else {
                        element.value = serializer.readObject(new URL(location, src));
                    }
                } else {
                    // Process attributes looking for all property setters
                    for (Attribute attribute : element.attributes) {
                        if (Character.isUpperCase(attribute.localName.charAt(0))) {
                            staticPropertyAttributes.add(attribute);
                        } else {
                            instancePropertyAttributes.add(attribute);
                        }
                    }
                }

                // Add the value to the named objects map
                if (element.id != null) {
                    if (namedObjects.containsKey(element.id)) {
                        throw new SerializationException("Element ID " + element.id
                            + " is already in use.");
                    }

                    namedObjects.put(element.id, element.value);
                }

                // Apply instance attributes
                Dictionary<String, Object> dictionary;
                if (element.value instanceof Dictionary<?, ?>) {
                    dictionary = (Dictionary<String, Object>)element.value;
                } else {
                    dictionary = new BeanAdapter(element.value);
                }

                for (Attribute attribute : instancePropertyAttributes) {
                    dictionary.put(attribute.localName, resolve(attribute.value));
                }

                // If the element's parent is a sequence or a listener list, add
                // the element value to it
                if (element.parent != null) {
                    if (element.parent.value instanceof Sequence<?>) {
                        Sequence<Object> sequence = (Sequence<Object>)element.parent.value;
                        sequence.add(element.value);
                    } else {
                        if (element.parent.value instanceof ListenerList<?>) {
                            ListenerList<Object> listenerList = (ListenerList<Object>)element.parent.value;
                            listenerList.add(element.value);
                        }
                    }
                }

                // Apply static attributes
                if (element.value instanceof Dictionary<?, ?>) {
                    if (staticPropertyAttributes.getLength() > 0) {
                        throw new SerializationException("Static setters are only supported"
                            + " for typed objects.");
                    }
                } else {
                    for (Attribute attribute : staticPropertyAttributes) {
                        // Split the local name
                        String[] localNameComponents = attribute.localName.split("\\.");
                        if (localNameComponents.length != 2) {
                            throw new SerializationException("\"" + attribute.localName
                                + "\" is not a valid attribute name.");
                        }

                        // Determine the type of the attribute
                        String propertyClassName = attribute.namespaceURI + "." + localNameComponents[0];

                        Class<?> propertyClass = null;
                        try {
                            propertyClass = Class.forName(propertyClassName);
                        } catch (ClassNotFoundException exception) {
                            throw new SerializationException(exception);
                        }

                        if (propertyClass.isInterface()) {
                            // The attribute represents an event listener
                            String listenerClassName = localNameComponents[0];
                            String getListenerListMethodName = "get" + Character.toUpperCase(listenerClassName.charAt(0))
                                + listenerClassName.substring(1) + "s";

                            // Get the listener list
                            Method getListenerListMethod;
                            try {
                                Class<?> type = element.value.getClass();
                                getListenerListMethod = type.getMethod(getListenerListMethodName);
                            } catch (NoSuchMethodException exception) {
                                throw new SerializationException(exception);
                            }

                            Object listenerList;
                            try {
                                listenerList = getListenerListMethod.invoke(element.value);
                            } catch (InvocationTargetException exception) {
                                throw new SerializationException(exception);
                            } catch (IllegalAccessException exception) {
                                throw new SerializationException(exception);
                            }

                            // Create an invocation handler for this listener
                            ScriptEngine scriptEngine = scriptEngineManager.getEngineByName(language);
                            AttributeInvocationHandler handler =
                                new AttributeInvocationHandler(scriptEngine,
                                    localNameComponents[1],
                                    attribute.value);

                            Object listener = Proxy.newProxyInstance(ThreadUtilities.getClassLoader(),
                                new Class<?>[]{propertyClass}, handler);

                            // Add the listener
                            Class<?> listenerListClass = listenerList.getClass();
                            Method addMethod;
                            try {
                                addMethod = listenerListClass.getMethod("add", Object.class);
                            } catch (NoSuchMethodException exception) {
                                throw new RuntimeException(exception);
                            }

                            try {
                                addMethod.invoke(listenerList, listener);
                            } catch (IllegalAccessException exception) {
                                throw new SerializationException(exception);
                            } catch (InvocationTargetException exception) {
                                throw new SerializationException(exception);
                            }
                        } else {
                            // The attribute represents a static setter
                            setStaticProperty(element.value, propertyClass, localNameComponents[1],
                                resolve(attribute.value));
                        }
                    }
                }

                // If the parent element is a writable property, set this as its
                // value; it will be applied later in the parent's closing tag
                if (element.parent != null
                    && element.parent.type == Element.Type.WRITABLE_PROPERTY) {
                    element.parent.value = element.value;
                }

                break;
            }

            case READ_ONLY_PROPERTY: {
                if (element.value instanceof Dictionary<?, ?>) {
                    // Process attributes looking for instance property setters
                    for (Attribute attribute : element.attributes) {
                        if (Character.isUpperCase(attribute.localName.charAt(0))) {
                            throw new SerializationException("Static setters are not supported"
                                + " for read-only properties.");
                        }

                        Dictionary<String, Object> dictionary =
                            (Dictionary<String, Object>)element.value;
                        dictionary.put(attribute.localName, resolve(attribute.value));
                    }
                }

                break;
            }

            case WRITABLE_PROPERTY: {
                Dictionary<String, Object> dictionary;
                if (element.parent.value instanceof Dictionary) {
                    dictionary = (Dictionary<String, Object>)element.parent.value;
                } else {
                    dictionary = new BeanAdapter(element.parent.value);
                }

                if (Character.isUpperCase(element.localName.charAt(0))) {
                    if (element.parent == null
                        || element.parent.value == null) {
                        throw new SerializationException("Element does not have a parent.");
                    }

                    // Set static property
                    String[] localNameComponents = element.localName.split("\\.");
                    if (localNameComponents.length != 2) {
                        throw new SerializationException("\"" + element.localName
                            + "\" is not a valid attribute name.");
                    }

                    String propertyClassName = element.namespaceURI + "." + localNameComponents[0];

                    Class<?> propertyClass = null;
                    try {
                        propertyClass = Class.forName(propertyClassName);
                    } catch (ClassNotFoundException exception) {
                        throw new SerializationException(exception);
                    }

                    setStaticProperty(element.parent.value, propertyClass, localNameComponents[1],
                        element.value);
                } else {
                    dictionary.put(localName, element.value);
                }

                break;
            }

            case SCRIPT: {
                // Process attributes looking for src and language
                String src = null;
                String language = this.language;
                for (Attribute attribute : element.attributes) {
                    if (attribute.localName.equals(SCRIPT_SRC_ATTRIBUTE)) {
                        src = attribute.value;
                    } else if (attribute.localName.equals(SCRIPT_LANGUAGE_ATTRIBUTE)) {
                        language = attribute.value;
                    } else {
                        throw new SerializationException(attribute.localName + " is not a valid"
                            + " attribute for the " + WTKX_PREFIX + ":" + SCRIPT_TAG + " tag.");
                    }
                }

                Bindings bindings;
                if (element.parent.value instanceof ListenerList<?>) {
                    // Don't pollute the engine namespace with the listener functions
                    bindings = new SimpleBindings();
                } else {
                    bindings = scriptEngineManager.getBindings();
                }

                // Execute script
                final ScriptEngine scriptEngine;

                if (src != null) {
                    // The script is located in an external file
                    int i = src.lastIndexOf(".");
                    if (i == -1) {
                        throw new SerializationException("Cannot determine type of script \""
                            + src + "\".");
                    }

                    String extension = src.substring(i + 1);
                    scriptEngine = scriptEngineManager.getEngineByExtension(extension);

                    if (scriptEngine == null) {
                        throw new SerializationException("Unable to find scripting engine for"
                            + " extension " + extension + ".");
                    }

                    scriptEngine.setBindings(bindings, ScriptContext.ENGINE_SCOPE);

                    try {
                        URL scriptLocation;
                        if (src.charAt(0) == '/') {
                            ClassLoader classLoader = ThreadUtilities.getClassLoader();
                            scriptLocation = classLoader.getResource(src.substring(1));
                        } else {
                            scriptLocation = new URL(location, src);
                        }

                        BufferedReader scriptReader = null;
                        try {
                            scriptReader = new BufferedReader(new InputStreamReader(scriptLocation.openStream()));
                            scriptEngine.eval(scriptReader);
                        } catch(ScriptException exception) {
                            exception.printStackTrace();
                        } finally {
                            if (scriptReader != null) {
                                scriptReader.close();
                            }
                        }
                    } catch (IOException exception) {
                        throw new SerializationException(exception);
                    }
                } else {
                    // The script is inline
                    scriptEngine = scriptEngineManager.getEngineByName(language);

                    if (scriptEngine == null) {
                        throw new SerializationException("Unable to find scripting engine for"
                            + " language \"" + language + "\".");
                    }

                    scriptEngine.setBindings(bindings, ScriptContext.ENGINE_SCOPE);

                    String script = (String)element.value;
                    if (script != null) {
                        try {
                            scriptEngine.eval(script);
                        } catch (ScriptException exception) {
                            System.err.println(exception);
                            System.err.println(script);
                        }
                    }
                }

                if (element.parent.value instanceof ListenerList<?>) {
                    // Create the listener and add it to the list
                    Class<?> listenerListClass = element.parent.value.getClass();

                    java.lang.reflect.Type[] genericInterfaces = listenerListClass.getGenericInterfaces();
                    Class<?> listenerClass = (Class<?>)genericInterfaces[0];

                    ElementInvocationHandler handler = new ElementInvocationHandler(scriptEngine);

                    Method addMethod;
                    try {
                        addMethod = listenerListClass.getMethod("add", Object.class);
                    } catch (NoSuchMethodException exception) {
                        throw new RuntimeException(exception);
                    }

                    Object listener = Proxy.newProxyInstance(ThreadUtilities.getClassLoader(),
                        new Class<?>[]{listenerClass}, handler);

                    try {
                        addMethod.invoke(element.parent.value, listener);
                    } catch (IllegalAccessException exception) {
                        throw new SerializationException(exception);
                    } catch (InvocationTargetException exception) {
                        throw new SerializationException(exception);
                    }
                }

                break;
            }

            case DEFINE: {
                // No-op
            }
        }

        // Move up the stack
        if (element.parent != null) {
            element = element.parent;
        }
    }

    /**
     * Returns the element that is currently being processed.
     */
    protected Element getCurrentElement() {
        return element;
    }

    /**
     * Serializer factory method. Subclasses can override this method to return custom
     * serializer instances.
     *
     * @param resources
     * @param owner
     */
    protected WTKXSerializer createSerializer(Resources resources, WTKXSerializer owner) {
        return new WTKXSerializer(resources, owner);
    }

    /**
     * Object factory method. Subclasses can override this method to perform custom
     * object instantiation.
     *
     * @param type
     * @param id
     */
    protected Object createInstance(Class<?> type, String id) throws Exception {
        return type.newInstance();
    }

    private void logException() {
        String message = "An error occurred while processing ";

        if (element == null) {
            message += " the root element";
        } else {
            message += " " + element.namespaceURI + "." + element.localName
                + " starting at line number " + element.lineNumber;
        }

        if (location != null) {
            message += " in file " + location.getPath();
        }

        message += ":";

        System.err.println(message);
    }

    @Override
    public void writeObject(Object object, OutputStream outputStream) throws IOException,
        SerializationException {
        throw new UnsupportedOperationException();
    }

    @Override
    public String getMIMEType(Object object) {
        return MIME_TYPE;
    }

    /**
     * Retrieves a named object.
     *
     * @param name
     * The name of the object, relative to this loader. The object's name is
     * the concatenation of its parent IDs and its ID, separated by periods
     * (e.g. "foo.bar.baz").
     *
     * @return The named object, or <tt>null</tt> if an object with the given
     * name does not exist. Use {@link #containsKey(String)} to distinguish
     * between the two cases.
     */
    @Override
    public Object get(String name) {
        if (name == null) {
            throw new IllegalArgumentException("name is null.");
        }

        Object value = null;

        int i = name.lastIndexOf('.');
        if (i == -1) {
            value = namedObjects.get(name);
        } else {
            String serializerName = name.substring(0, name.lastIndexOf('.'));
            String id = name.substring(serializerName.length() + 1);
            WTKXSerializer serializer = getSerializer(serializerName);

            if (serializer != null) {
                value = serializer.get(id);
            }
        }

        return value;
    }

    /**
     * Provides typed access to named objects. Delegates to {@link #get(String)} and casts
     * the return value to <tt>T</tt>.
     *
     * @param name
     */
    @SuppressWarnings("unchecked")
    public <T> T getValue(String name) {
        return (T)get(name);
    }

    @Override
    public Object put(String name, Object value) {
        if (name == null) {
            throw new IllegalArgumentException("name is null.");
        }

        Object previousValue;

        int i = name.lastIndexOf('.');
        if (i == -1) {
            previousValue = namedObjects.put(name, value);
        } else {
            String serializerName = name.substring(0, name.lastIndexOf('.'));
            String id = name.substring(serializerName.length() + 1);
            WTKXSerializer serializer = getSerializer(serializerName);
            previousValue = serializer.put(id, value);
        }

        clearNamespaceOnRead = false;

        return previousValue;
    }

    @Override
    public Object remove(String name) {
        if (name == null) {
            throw new IllegalArgumentException("name is null.");
        }

        Object previousValue;

        int i = name.lastIndexOf('.');
        if (i == -1) {
            previousValue = namedObjects.remove(name);
        } else {
            String serializerName = name.substring(0, name.lastIndexOf('.'));
            String id = name.substring(serializerName.length() + 1);
            WTKXSerializer serializer = getSerializer(serializerName);
            previousValue = serializer.remove(id);
        }

        return previousValue;
    }

    @Override
    public boolean containsKey(String name) {
        if (name == null) {
            throw new IllegalArgumentException("name is null.");
        }

        boolean containsKey = false;

        int i = name.lastIndexOf('.');
        if (i == -1) {
            containsKey = namedObjects.containsKey(name);
        } else {
            String serializerName = name.substring(0, name.lastIndexOf('.'));
            String id = name.substring(serializerName.length() + 1);
            WTKXSerializer serializer = getSerializer(serializerName);

            if (serializer != null) {
                containsKey = serializer.containsKey(id);
            }
        }

        return containsKey;
    }

    public boolean isEmpty() {
        return namedObjects.isEmpty()
            && namedSerializers.isEmpty();
    }

    /**
     * Retrieves the root of the object hierarchy most recently processed by
     * this serializer.
     *
     * @return
     * The root object, or <tt>null</tt> if this serializer has not yet read an
     * object from an input stream.
     */
    public Object getRoot() {
        return root;
    }

    /**
     * Retrieves a nested serializer.
     *
     * @param name
     * The name of the serializer, relative to this loader. The serializer's name
     * is the concatentation of its parent IDs and its ID, separated by periods
     * (e.g. "foo.bar.baz").
     *
     * @return The named serializer, or <tt>null</tt> if a serializer with the
     * given name does not exist.
     */
    public WTKXSerializer getSerializer(String name) {
        if (name == null) {
            throw new IllegalArgumentException("name is null.");
        }

        WTKXSerializer serializer = this;
        String[] path = name.split("\\.");

        int i = 0;
        int n = path.length;
        while (i < n && serializer != null) {
            String id = path[i++];
            serializer = serializer.namedSerializers.get(id);
        }

        return serializer;
    }

    /**
     * Returns the location of the WTKX most recently processed by this
     * serializer.
     *
     * @return
     * The location of the WTKX, or <tt>null</tt> if this serializer has not
     * yet read an object from a URL.
     */
    public URL getLocation() {
        return location;
    }

    /**
     * Applies WTKX binding annotations to an object.
     *
     * @param object
     *
     * @see #bind(Object, Class)
     */
    public void bind(Object object) {
        if (object == null) {
            throw new IllegalArgumentException();
        }

        bind(object, object.getClass());
    }

    /**
     * Applies WTKX binding annotations to an object.
     * <p>
     * NOTE This method uses reflection to set internal member variables. As
     * a result, it may only be called from trusted code.
     *
     * @param object
     * @param type
     *
     * @throws BindException
     * If an error occurs during binding
     */
    public void bind(Object object, Class<?> type) throws BindException {
        if (object == null) {
            throw new IllegalArgumentException();
        }

        if (!type.isAssignableFrom(object.getClass())) {
            throw new IllegalArgumentException();
        }

        Field[] fields = type.getDeclaredFields();

        // Process bind annotations
        for (int j = 0, n = fields.length; j < n; j++) {
            Field field = fields[j];
            String fieldName = field.getName();
            int fieldModifiers = field.getModifiers();

            WTKX wtkxAnnotation = field.getAnnotation(WTKX.class);
            if (wtkxAnnotation != null) {
                // Ensure that we can write to the field
                if ((fieldModifiers & Modifier.FINAL) > 0) {
                    throw new BindException(fieldName + " is final.");
                }

                if ((fieldModifiers & Modifier.PUBLIC) == 0) {
                    try {
                        field.setAccessible(true);
                    } catch (SecurityException exception) {
                        throw new BindException(fieldName + " is not accessible.");
                    }
                }

                String id = wtkxAnnotation.id();
                if (id.equals("\0")) {
                    id = field.getName();
                }

                if (containsKey(id)) {
                    // Set the value into the field
                    Object value = get(id);
                    try {
                        field.set(object, value);
                    } catch (IllegalAccessException exception) {
                        throw new BindException(exception);
                    }
                }
            }
        }
    }

    /**
     * Resolves an attribute value as either a URL, resource value, or
     * object reference, depending on the value's prefix. If the value can't
     * or doesn't need to be resolved, the original attribute value is
     * returned.
     *
     * @param attributeValue
     * The attribute value to resolve.
     *
     * @return
     * The resolved value.
     */
    private Object resolve(String attributeValue)
        throws MalformedURLException {
        Object resolvedValue = null;

        if (attributeValue.length() > 0) {
            if (attributeValue.charAt(0) == URL_PREFIX) {
                if (attributeValue.length() > 1) {
                    if (attributeValue.charAt(1) == URL_PREFIX) {
                        resolvedValue = attributeValue.substring(1);
                    } else {
                        if (location == null) {
                            throw new IllegalStateException("Base location is undefined.");
                        }

                        resolvedValue = new URL(location, attributeValue.substring(1));
                    }
                }
            } else if (attributeValue.charAt(0) == RESOURCE_KEY_PREFIX) {
                if (attributeValue.length() > 1) {
                    if (attributeValue.charAt(1) == RESOURCE_KEY_PREFIX) {
                        resolvedValue = attributeValue.substring(1);
                    } else {
                        if (resources == null) {
                            throw new IllegalStateException("Resources is null.");
                        }

                        resolvedValue = resources.get(attributeValue.substring(1));

                        if (resolvedValue == null) {
                            resolvedValue = attributeValue;
                        }
                    }
                }
            } else if (attributeValue.charAt(0) == OBJECT_REFERENCE_PREFIX) {
                if (attributeValue.length() > 1) {
                    if (attributeValue.charAt(1) == OBJECT_REFERENCE_PREFIX) {
                        resolvedValue = attributeValue.substring(1);
                    } else {
                        resolvedValue = get(attributeValue.substring(1));

                        if (resolvedValue == null) {
                            resolvedValue = attributeValue;
                        }
                    }
                }
            } else {
                resolvedValue = attributeValue;
            }
        } else {
            resolvedValue = attributeValue;
        }

        return resolvedValue;
    }

    private static Method getStaticGetterMethod(Class<?> propertyClass, String propertyName,
        Class<?> objectType) {
        Method method = null;

        if (objectType != null) {
            try {
                method = propertyClass.getMethod(BeanAdapter.GET_PREFIX
                    + propertyName, objectType);
            } catch (NoSuchMethodException exception) {
                // No-op
            }

            if (method == null) {
                try {
                    method = propertyClass.getMethod(BeanAdapter.IS_PREFIX
                        + propertyName, objectType);
                } catch (NoSuchMethodException exception) {
                    // No-op
                }
            }

            if (method == null) {
                method = getStaticGetterMethod(propertyClass, propertyName,
                    objectType.getSuperclass());
            }
        }

        return method;
    }

    private static Method getStaticSetterMethod(Class<?> propertyClass, String propertyName,
        Class<?> objectType, Class<?> propertyValueType) {
        Method method = null;

        if (objectType != null) {
            final String methodName = BeanAdapter.SET_PREFIX + propertyName;

            try {
                method = propertyClass.getMethod(methodName, objectType, propertyValueType);
            } catch (NoSuchMethodException exception) {
                // No-op
            }

            if (method == null) {
                // If value type is a primitive wrapper, look for a method
                // signature with the corresponding primitive type
                try {
                    Field primitiveTypeField = propertyValueType.getField("TYPE");
                    Class<?> primitivePropertyValueType = (Class<?>)primitiveTypeField.get(null);

                    try {
                        method = propertyClass.getMethod(methodName,
                            objectType, primitivePropertyValueType);
                    } catch (NoSuchMethodException exception) {
                        // No-op
                    }
                } catch (NoSuchFieldException exception) {
                    // No-op; not a wrapper type
                } catch (IllegalAccessException exception) {
                    // No-op; not a wrapper type
                }
            }

            if (method == null) {
                method = getStaticSetterMethod(propertyClass, propertyName,
                    objectType.getSuperclass(), propertyValueType);
            }
        }

        return method;
    }

    private static void setStaticProperty(Object object, Class<?> propertyClass,
        String propertyName, Object value)
        throws SerializationException {
        Class<?> objectType = object.getClass();
        propertyName = Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);

        Method setterMethod = null;
        if (value != null) {
            setterMethod = getStaticSetterMethod(propertyClass, propertyName,
                objectType, value.getClass());
        }

        if (setterMethod == null) {
            Method getterMethod = getStaticGetterMethod(propertyClass, propertyName, objectType);

            if (getterMethod != null) {
                Class<?> propertyType = getterMethod.getReturnType();
                setterMethod = getStaticSetterMethod(propertyClass, propertyName,
                    objectType, propertyType);

                if (value instanceof String) {
                    value = BeanAdapter.coerce((String)value, propertyType);
                }
            }
        }

        if (setterMethod == null) {
            throw new SerializationException(propertyClass.getName() + "." + propertyName
                + " is not valid static property.");
        }

        // Invoke the setter
        try {
            setterMethod.invoke(null, object, value);
        } catch (Exception exception) {
            throw new SerializationException(exception);
        }
    }
}
TOP

Related Classes of org.apache.pivot.wtkx.WTKXSerializer$AttributeInvocationHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.