Package pivot.wtkx

Source Code of pivot.wtkx.WTKXSerializer$Attribute

/*
* 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 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.io.Reader;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamException;

import pivot.beans.BeanDictionary;
import pivot.beans.PropertyNotFoundException;
import pivot.collections.ArrayList;
import pivot.collections.Dictionary;
import pivot.collections.HashMap;
import pivot.collections.List;
import pivot.collections.Sequence;
import pivot.serialization.Serializer;
import pivot.serialization.SerializationException;
import pivot.util.ListenerList;
import pivot.util.Resources;

/**
* Loads an object hierarchy from an XML document.
*
* @author gbrown
*/
public class WTKXSerializer implements Serializer<Object> {
    private static class Element  {
        public enum Type {
            INSTANCE,
            INCLUDE,
            SCRIPT,
            READ_ONLY_PROPERTY,
            WRITABLE_PROPERTY
        }

        public final Element parent;
        public final String tagName;
        public final Type type;
        public final List<Attribute> attributes;

        public Object value;

        public Element(Element parent, String tagName, Type type, List<Attribute> attributes, Object value) {
            this.parent = parent;
            this.tagName = tagName;
            this.type = type;
            this.attributes = attributes;
            this.value = value;
        }
    }

    private static class Attribute {
        public final String namespaceURI;
        public final String prefix;
        public final String localName;
        public final String value;

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

    private URL location = null;
    private Resources resources = null;

    private HashMap<String, Object> namedObjects = new HashMap<String, Object>();
    private HashMap<String, WTKXSerializer> includeSerializers = new HashMap<String, WTKXSerializer>();

    private XMLInputFactory xmlInputFactory;
    private Object scriptEngineManager;
    private Class<?> scriptEngineManagerClass;

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

    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_NAMESPACE_ATTRIBUTE = "namespace";
    public static final String INCLUDE_RESOURCES_ATTRIBUTE = "resources";

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

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

    public WTKXSerializer() {
        this(null);
    }

    public WTKXSerializer(Resources resources) {
        this.resources = resources;

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

        try {
          scriptEngineManagerClass = Class.forName("javax.script.ScriptEngineManager");
          scriptEngineManager = scriptEngineManagerClass.newInstance();
        } catch(Exception exception) {
          scriptEngineManagerClass = null;
          scriptEngineManager = null;
        }
    }

    public Resources getResources() {
        return resources;
    }

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

        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        URL location = classLoader.getResource(resourceName);

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

        return readObject(location);
    }

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

        this.location = location;
        return readObject(new BufferedInputStream(location.openStream()));
    }

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

        Object object = null;

        // Clear any previous named objects and include serializers
        namedObjects.clear();
        includeSerializers.clear();

        // Parse the XML stream
        Element element = null;
        try {
            XMLStreamReader reader = xmlInputFactory.createXMLStreamReader(inputStream);

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

                switch (event) {
                    case XMLStreamConstants.CHARACTERS: {
                        if (!reader.isWhiteSpace()) {
                            String text = reader.getText();

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

                                            try {
                                                Method addMethod = sequence.getClass().getMethod("add",
                                                    new Class<?>[] {String.class});
                                                addMethod.invoke(sequence, new Object[] {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 WRITABLE_PROPERTY: {
                                        element.value = text;
                                        break;
                                    }
                                }
                            }
                        }

                        break;
                    }

                    case XMLStreamConstants.START_ELEMENT: {
                        String namespaceURI = reader.getNamespaceURI();
                        String prefix = reader.getPrefix();
                        String localName = reader.getLocalName();

                        String id = null;

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

                            if (localName.equals(INCLUDE_TAG)) {
                                // The element represents an include
                                String src = null;
                                String namespace = null;

                                Resources includeResources = resources;

                                ArrayList<Attribute> attributes = new ArrayList<Attribute>();

                                for (int i = 0, n = reader.getAttributeCount(); i < n; i++) {
                                    String attributeNamespaceURI = reader.getAttributeNamespace(i);
                                    if (attributeNamespaceURI == null) {
                                        attributeNamespaceURI = reader.getNamespaceURI("");
                                    }

                                    String attributePrefix = reader.getAttributePrefix(i);
                                    String attributeLocalName = reader.getAttributeLocalName(i);
                                    String attributeValue = reader.getAttributeValue(i);

                                    if (attributePrefix != null
                                        && attributePrefix.equals(WTKX_PREFIX)) {
                                        if (attributeLocalName.equals(ID_ATTRIBUTE)) {
                                            id = attributeValue;
                                        }
                                    } else {
                                        if (attributeLocalName.equals(INCLUDE_SRC_ATTRIBUTE)) {
                                            src = attributeValue;
                                        } else if (attributeLocalName.equals(INCLUDE_NAMESPACE_ATTRIBUTE)) {
                                            namespace = attributeValue;
                                        } else if (attributeLocalName.equals(INCLUDE_RESOURCES_ATTRIBUTE)) {
                                            includeResources = new Resources(attributeValue);
                                        } else {
                                            attributes.add(new Attribute(attributeNamespaceURI,
                                                attributePrefix, attributeLocalName, attributeValue));
                                        }
                                    }
                                }

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

                                // Process the include
                                WTKXSerializer serializer = new WTKXSerializer(includeResources);
                                if (namespace != null) {
                                    includeSerializers.put(namespace, serializer);
                                }

                                Object value;
                                if (src.charAt(0) == '/') {
                                    value = serializer.readObject(src.substring(1));
                                } else {
                                    value = serializer.readObject(new URL(location, src));
                                }

                                element = new Element(element, localName, Element.Type.INCLUDE, attributes, value);
                            } else if (localName.equals(SCRIPT_TAG)) {
                              if (scriptEngineManagerClass == null) {
                                throw new SerializationException("Scripting is not supported on this platform.");
                              }

                                // The element represents a script
                                String src = null;

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

                                    if (attributeLocalName.equals(SCRIPT_SRC_ATTRIBUTE)) {
                                        src = attributeValue;
                                    } else {
                                      throw new SerializationException(attributeLocalName + " is not a valid "
                                      + " attribute for the " + WTKX_PREFIX + ":" + SCRIPT_TAG + ".");
                                    }
                                }

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

                              int i = src.lastIndexOf(".");
                              if (i == -1) {
                                throw new SerializationException("Cannot determine type of script \""
                                + src + "\".");
                              }

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

                              Object scriptEngine = null;
                              try {
                                  Method getEngineByExtensionMethod =
                                    scriptEngineManagerClass.getMethod("getEngineByExtension", new Class<?>[] {String.class});

                                    scriptEngine = getEngineByExtensionMethod.invoke(scriptEngineManager, new Object[] {extension});
                              } catch(Exception exception) {
                                throw new RuntimeException(exception);
                              }

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

                                try {
                                    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
                                  URL scriptLocation;

                                  if (src.charAt(0) == '/') {
                                    scriptLocation = classLoader.getResource(src);
                                    } else {
                                      scriptLocation = new URL(location, src);
                                    }

                                  Class<?> bindingsClass = Class.forName("javax.script.Bindings");
                                  Method evalMethod = scriptEngine.getClass().getMethod("eval",
                                      new Class<?>[] {Reader.class, bindingsClass});

                                  Method getBindingsMethod =
                                    scriptEngineManagerClass.getMethod("getBindings", new Class<?>[] {});

                                  Object bindings = getBindingsMethod.invoke(scriptEngineManager, new Object[] {});
                                  Reader scriptReader = new BufferedReader(new InputStreamReader(scriptLocation.openStream()));
                                  evalMethod.invoke(scriptEngine, new Object[] {scriptReader, bindings});
                                } catch(Exception exception) {
                                  throw new SerializationException(exception);
                                }

                                element = new Element(element, localName, Element.Type.SCRIPT, null, null);
                            } else {
                                throw new SerializationException(prefix + ":" + localName
                                    + " is not a valid tag.");
                            }
                        } else {
                            ArrayList<Attribute> attributes = new ArrayList<Attribute>();

                            for (int i = 0, n = reader.getAttributeCount(); i < n; i++) {
                                String attributeNamespaceURI = reader.getAttributeNamespace(i);
                                if (attributeNamespaceURI == null) {
                                    attributeNamespaceURI = reader.getNamespaceURI("");
                                }

                                String attributePrefix = reader.getAttributePrefix(i);
                                String attributeLocalName = reader.getAttributeLocalName(i);
                                String attributeValue = reader.getAttributeValue(i);

                                if (attributePrefix != null
                                    && attributePrefix.equals(WTKX_PREFIX)) {
                                    if (attributeLocalName.equals(ID_ATTRIBUTE)) {
                                        id = attributeValue;
                                    }
                                } else {
                                    attributes.add(new Attribute(attributeNamespaceURI,
                                        attributePrefix, attributeLocalName, attributeValue));
                                }
                            }

                            if (Character.isUpperCase(localName.charAt(0))) {
                                // 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);
                                    element = new Element(element, localName, Element.Type.INSTANCE, attributes, type.newInstance());
                                } catch(Exception exception) {
                                    throw new SerializationException(exception);
                                }
                            } else {
                                // This element represents a property
                                if (element == null) {
                                    throw new SerializationException("Root node must represent a typed object.");
                                }

                                BeanDictionary propertyDictionary = new BeanDictionary(element.value);

                                if (propertyDictionary.isReadOnly(localName)) {
                                    Object value = propertyDictionary.get(localName);
                                    assert (value != null) : "Read-only properties cannot be null.";
                                    element = new Element(element, localName, Element.Type.READ_ONLY_PROPERTY, attributes, value);
                                } else {
                                    if (attributes.getLength() > 0) {
                                        throw new SerializationException("Writable property elements cannot have attributes.");
                                    }

                                    element = new Element(element, localName, Element.Type.WRITABLE_PROPERTY, null, null);
                                }
                            }
                        }

                        switch (element.type) {
                            case INCLUDE:
                            case INSTANCE: {
                                // 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);
                                        }
                                    }
                                }

                                // If an ID was specified, add the value to the named object map
                                if (id != null) {
                                    namedObjects.put(id, element.value);

                                    if (scriptEngineManager != null) {
                                      try {
                                        Method putMethod = scriptEngineManagerClass.getMethod("put",
                                          new Class<?>[] {String.class, Object.class});
                                        putMethod.invoke(scriptEngineManager, new Object[] {id, namedObjects.get(id)});
                                      } catch(Exception exception) {
                                        throw new RuntimeException(exception);
                                      }
                                  }
                                }

                                break;
                            }
                        }

                        break;
                    }

                    case XMLStreamConstants.END_ELEMENT: {
                        String localName = reader.getLocalName();

                        switch (element.type) {
                            case WRITABLE_PROPERTY: {
                                BeanDictionary propertyDictionary = new BeanDictionary(element.parent.value);
                                propertyDictionary.put(localName, element.value);
                                break;
                            }

                            case SCRIPT: {
                              break;
                            }

                            default: {
                                if (element.value instanceof Dictionary) {
                                    // The element is an untyped object
                                    Dictionary<String, Object> dictionary = (Dictionary<String, Object>)element.value;

                                    for (Attribute attribute : element.attributes) {
                                        if (Character.isUpperCase(attribute.localName.charAt(0))) {
                                            throw new SerializationException("Static setters are only supported for typed instances.");
                                        }

                                        // Resolve and apply the attribute
                                        dictionary.put(attribute.localName, resolve(attribute.value, null));
                                    }
                                } else {
                                    // The element represents a typed object; apply the attributes
                                    BeanDictionary valueDictionary = new BeanDictionary(element.value);

                                    for (Attribute attribute : element.attributes) {
                                        if (Character.isUpperCase(attribute.localName.charAt(0))) {
                                            // The property represents an attached value
                                            setStaticProperty(attribute, element.value);
                                        } else {
                                            Class<?> type = valueDictionary.getType(attribute.localName);

                                            if (type != null
                                                && ListenerList.class.isAssignableFrom(type)) {
                                                // The property represents a listener list
                                                ListenerList<Object> listenerList = (ListenerList<Object>)valueDictionary.get(attribute.localName);

                                                // The attribute value is a comma-separated list of listener IDs
                                                String[] listenerIDs = attribute.value.split(",");

                                                for (int i = 0, n = listenerIDs.length; i < n; i++) {
                                                    String listenerID = listenerIDs[i].trim();

                                                    if (listenerID.length() > 0) {
                                                        listenerID = listenerID.substring(1);

                                                        if (listenerID.length() > 0) {
                                                            listenerList.add(getObjectByID(listenerID));
                                                        }
                                                    }
                                                }
                                            } else {
                                                valueDictionary.put(attribute.localName,
                                                    resolve(attribute.value, valueDictionary.getType(attribute.localName)));
                                            }
                                        }
                                    }
                                }

                                // 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;
                                }
                            }
                        }

                        // If this is the top of the stack, return this element's value;
                        // otherwise, move up the stack
                        if (element.parent == null) {
                            object = element.value;
                        } else {
                            element = element.parent;
                        }

                        break;
                    }
                }
            }

            reader.close();
        } catch(XMLStreamException exception) {
            throw new SerializationException(exception);
        }

        // Clear the location so the previous value won't be re-used in a
        // subsequent call to this method
        location = null;

        return object;
    }

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

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

    /**
     * Retrieves a included serializer by its namespace.
     *
     * @param name
     * The name of the serializer, relative to this loader. The values's name
     * is the concatentation of its parent namespaces and its namespace,
     * 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 getSerializerByName(String name) {
        if (name == null) {
            throw new IllegalArgumentException("name is null.");
        }

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

        int i = 0;
        int n = namespacePath.length;
        while (i < n && serializer != null) {
            String namespace = namespacePath[i++];
            serializer = serializer.includeSerializers.get(namespace);
        }

        return serializer;
    }

    /**
     * Retrieves a named object.
     *
     * @param name
     * The name of the object, relative to this loader. The values's name is the
     * concatentation of its parent namespaces 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.
     */
    public Object getObjectByName(String name) {
        if (name == null) {
            throw new IllegalArgumentException("name is null.");
        }

        Object object = null;
        WTKXSerializer serializer = this;
        String[] namespacePath = name.split("\\.");

        int i = 0;
        int n = namespacePath.length - 1;
        while (i < n && serializer != null) {
            String namespace = namespacePath[i++];
            serializer = serializer.includeSerializers.get(namespace);
        }

        if (serializer != null) {
          object = serializer.getObjectByID(namespacePath[i]);
        }

        return object;
    }

    private Object getObjectByID(String id) {
      Object object = null;

      if (namedObjects.containsKey(id)) {
        object = namedObjects.get(id);
      } else {
        if (scriptEngineManager != null) {
              try {
                  Method getMethod = scriptEngineManagerClass.getMethod("get", new Class<?>[] {String.class});
                  object = getMethod.invoke(scriptEngineManager, new Object[] {id});
              } catch(Exception exception) {
                throw new RuntimeException(exception);
              }
        }
      }

      return object;
    }

    /**
     * Resolves an attribute value. If the property type is a primitive or
     * primitive wrapper, converts the string value to the primitive type.
     * Otherwise, resolves the 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.
     *
     * @param propertyType
     * The property type, or <tt>null</tt> if the type is not known.
     *
     * @return
     * The resolved value.
     */
    private Object resolve(String attributeValue, Class<?> propertyType)
        throws MalformedURLException {
        Object resolvedValue = null;

        if (propertyType == Boolean.class
            || propertyType == Boolean.TYPE) {
            try {
                resolvedValue = Boolean.parseBoolean(attributeValue);
            } catch(NumberFormatException exception) {
                resolvedValue = attributeValue;
            }
        } else if (propertyType == Character.class
            || propertyType == Character.TYPE) {
            if (attributeValue.length() > 0) {
                resolvedValue = attributeValue.charAt(0);
            }
        } else if (propertyType == Byte.class
            || propertyType == Byte.TYPE) {
            try {
                resolvedValue = Byte.parseByte(attributeValue);
            } catch(NumberFormatException exception) {
                resolvedValue = attributeValue;
            }
        } else if (propertyType == Short.class
            || propertyType == Short.TYPE) {
            try {
                resolvedValue = Short.parseShort(attributeValue);
            } catch(NumberFormatException exception) {
                resolvedValue = attributeValue;
            }
        } else if (propertyType == Integer.class
            || propertyType == Integer.TYPE) {
            try {
                resolvedValue = Integer.parseInt(attributeValue);
            } catch(NumberFormatException exception) {
                resolvedValue = attributeValue;
            }
        } else if (propertyType == Long.class
            || propertyType == Long.TYPE) {
            try {
                resolvedValue = Long.parseLong(attributeValue);
            } catch(NumberFormatException exception) {
                resolvedValue = attributeValue;
            }
        } else if (propertyType == Float.class
            || propertyType == Float.TYPE) {
            try {
                resolvedValue = Float.parseFloat(attributeValue);
            } catch(NumberFormatException exception) {
                resolvedValue = attributeValue;
            }
        } else if (propertyType == Double.class
            || propertyType == Double.TYPE) {
            try {
                resolvedValue = Double.parseDouble(attributeValue);
            } catch(NumberFormatException exception) {
                resolvedValue = attributeValue;
            }
        } else {
            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("Resource dictionary is undefined.");
                        }

                        resolvedValue = resources.get(attributeValue.substring(1));
                    }
                }
            } 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 = getObjectByID(attributeValue.substring(1));
                    }
                }
            } else {
                resolvedValue = attributeValue;
            }
        }

        return resolvedValue;
    }

    /**
     * Invokes a static property setter.
     *
     * @param attribute
     * The attribute whose corresponding static setter is to be invoked.
     *
     * @param object
     * The object on which to invoke the static setter.
     */
    private void setStaticProperty(Attribute attribute, Object object)
        throws SerializationException, MalformedURLException {
        String propertyName =
            attribute.localName.substring(attribute.localName.lastIndexOf(".") + 1);
        propertyName = Character.toUpperCase(propertyName.charAt(0)) +
            propertyName.substring(1);

        String propertyClassName = attribute.namespaceURI + "."
            + attribute.localName.substring(0, attribute.localName.length()
                - (propertyName.length() + 1));

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

        Class<?> objectType = object.getClass();

        // Determine the property type from the getter method
        Method getterMethod = getStaticGetterMethod(propertyClass, propertyName, objectType);
        if (getterMethod == null) {
            throw new PropertyNotFoundException("Static property \"" + attribute
                + "\" does not exist.");
        }

        // Resolve the attribute value
        Class<?> propertyType = getterMethod.getReturnType();
        Object propertyValue = resolve(attribute.value, propertyType);
        Class<?> propertyValueType = (propertyValue == null) ?
            getterMethod.getReturnType() : propertyValue.getClass();

        Method setterMethod = getStaticSetterMethod(propertyClass, propertyName,
            objectType, propertyValueType);

        if (setterMethod == null) {
            throw new SerializationException("Unable to determine type for "
                + " static property \"" + attribute + "\".");
        }

        // Invoke the setter
        try {
            setterMethod.invoke(null, new Object[] {object, propertyValue});
        } catch(Exception exception) {
            throw new SerializationException(exception);
        }
    }

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

        if (objectType != null) {
            try {
                method = propertyClass.getMethod(BeanDictionary.GET_PREFIX
                    + propertyName, new Class<?>[] {objectType});
            } catch(NoSuchMethodException exception) {
                // No-op
            }

            if (method == null) {
                try {
                    method = propertyClass.getMethod(BeanDictionary.IS_PREFIX
                        + propertyName, new Class<?>[] {objectType});
                } catch(NoSuchMethodException exception) {
                    // No-op
                }
            }

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

        return method;
    }

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

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

            try {
                method = propertyClass.getMethod(methodName,
                    new Class<?>[] {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,
                            new Class<?>[] {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;
    }
}
TOP

Related Classes of pivot.wtkx.WTKXSerializer$Attribute

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.