You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pivot.apache.org by gb...@apache.org on 2011/01/10 00:19:29 UTC
svn commit: r1057053 [2/12] - in /pivot/branches/3.x: ./ core/ core/src/
core/src/org/ core/src/org/apache/ core/src/org/apache/pivot/
core/src/org/apache/pivot/beans/ core/src/org/apache/pivot/bxml/
core/src/org/apache/pivot/csv/ core/src/org/apache/p...
Added: pivot/branches/3.x/core/src/org/apache/pivot/bxml/BXMLSerializer.java
URL: http://svn.apache.org/viewvc/pivot/branches/3.x/core/src/org/apache/pivot/bxml/BXMLSerializer.java?rev=1057053&view=auto
==============================================================================
--- pivot/branches/3.x/core/src/org/apache/pivot/bxml/BXMLSerializer.java (added)
+++ pivot/branches/3.x/core/src/org/apache/pivot/bxml/BXMLSerializer.java Sun Jan 9 23:19:19 2011
@@ -0,0 +1,1163 @@
+/*
+ * 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.bxml;
+
+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.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+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 javax.xml.stream.util.StreamReaderDelegate;
+
+import org.apache.pivot.beans.BeanAdapter;
+import org.apache.pivot.io.SerializationException;
+import org.apache.pivot.io.Serializer;
+import org.apache.pivot.util.ListenerList;
+import org.apache.pivot.util.ObservableMap;
+import org.apache.pivot.util.ObservableMapListener;
+import org.apache.pivot.util.Resources;
+
+/**
+ * Loads an object hierarchy from an XML document.
+ */
+public class BXMLSerializer implements Serializer<Object> {
+ private static class Element {
+ public enum Type {
+ INSTANCE,
+ READ_ONLY_PROPERTY,
+ WRITABLE_PROPERTY,
+ LISTENER_LIST_PROPERTY,
+ INCLUDE,
+ SCRIPT,
+ DEFINE,
+ REFERENCE
+ }
+
+ public final Element parent;
+ public final Type type;
+ public final String name;
+ public Object value;
+
+ public String id = null;
+ public final HashMap<String, String> properties = new HashMap<String, String>();
+ public final LinkedList<Attribute> attributes = new LinkedList<Attribute>();
+
+ public Element(Element parent, Type type, String name, Object value) {
+ this.parent = parent;
+ this.type = type;
+ this.name = name;
+ this.value = value;
+ }
+ }
+
+ private static class Attribute {
+ public final Element element;
+ public final String name;
+ public Object value;
+
+ public Attribute(Element element, String name, Object value) {
+ this.element = element;
+ this.name = name;
+ this.value = value;
+ }
+ }
+
+ private static class ListenerInvocationHandler implements InvocationHandler {
+ public final HashMap<String, String> functionMap;
+ public final ScriptEngine scriptEngine;
+
+ public ListenerInvocationHandler(HashMap<String, String> functionMap,
+ ScriptEngine scriptEngine) {
+ this.functionMap = functionMap;
+ this.scriptEngine = scriptEngine;
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable {
+ String methodName = method.getName();
+ String functionName = functionMap.get(methodName);
+
+ Object result = null;
+ if (functionName != null) {
+ Invocable invocable;
+ try {
+ invocable = (Invocable)scriptEngine;
+ } catch (ClassCastException exception) {
+ throw new SerializationException(exception);
+ }
+
+ result = invocable.invokeFunction(methodName, args);
+ }
+
+ return result;
+ }
+ }
+
+ private static class NamespaceBinding {
+ public final Object source;
+ public final String sourceKey;
+ public final Object target;
+ public final String targetKey;
+ public final String mappingFunction;
+ public final ScriptEngine scriptEngine;
+
+ public NamespaceBinding(Object source, String sourceKey,
+ Object target, String targetKey,
+ String mappingFunction, ScriptEngine scriptEngine) {
+ this.source = source;
+ this.sourceKey = sourceKey;
+ this.target = target;
+ this.targetKey = targetKey;
+ this.mappingFunction = mappingFunction;
+ this.scriptEngine = scriptEngine;
+ }
+
+ public void apply() {
+ // Get source value
+ Object value = BeanAdapter.get(source, sourceKey);
+
+ // Apply mapping function, if specified
+ if (mappingFunction != null) {
+ Invocable invocable = (Invocable)scriptEngine;
+
+ try {
+ value = invocable.invokeFunction(mappingFunction, value);
+ } catch (NoSuchMethodException exception) {
+ throw new RuntimeException(exception);
+ } catch (ScriptException exception) {
+ throw new RuntimeException(exception);
+ }
+ }
+
+ // Set target value
+ BeanAdapter.set(target, targetKey, value);
+ }
+ };
+
+ private Map<String, Object> namespace;
+ private Charset charset;
+ private XMLInputFactory xmlInputFactory;
+ private ScriptEngineManager scriptEngineManager;
+
+ private URL location = null;
+ private Resources resources = null;
+
+ private XMLStreamReader xmlStreamReader = null;
+ private Element element = null;
+
+ private Object root = null;
+ private String language = null;
+
+ private LinkedList<Attribute> namespaceBindingAttributes = new LinkedList<Attribute>();
+
+ public static final String DEFAULT_CHARSET_NAME = "UTF-8";
+
+ public static final char URL_PREFIX = '@';
+ public static final char RESOURCE_KEY_PREFIX = '%';
+ public static final char OBJECT_REFERENCE_PREFIX = '$';
+
+ public static final String NAMESPACE_BINDING_PREFIX = OBJECT_REFERENCE_PREFIX + "{";
+ public static final String NAMESPACE_BINDING_SUFFIX = "}";
+ public static final String MAPPING_FUNCTION_DELIMITER = ":";
+
+ public static final String LANGUAGE_PROCESSING_INSTRUCTION = "language";
+
+ public static final String BXML_PREFIX = "bxml";
+ public static final String BXML_EXTENSION = "bxml";
+ 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_CHARSET_ATTRIBUTE = "charset";
+ 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 DEFINE_TAG = "define";
+
+ public static final String REFERENCE_TAG = "reference";
+ public static final String REFERENCE_ID_ATTRIBUTE = "id";
+
+ public static final String DEFAULT_LANGUAGE = "javascript";
+
+ public static final String MIME_TYPE = "application/bxml";
+
+ public BXMLSerializer() {
+ this(Charset.forName(DEFAULT_CHARSET_NAME));
+ }
+
+ public BXMLSerializer(Charset charset) {
+ this(new HashMap<String, Object>(), charset);
+ }
+
+ public BXMLSerializer(Map<String, Object> namespace, Charset charset) {
+ if (namespace == null) {
+ throw new IllegalArgumentException();
+ }
+
+ if (charset == null) {
+ throw new IllegalArgumentException();
+ }
+
+ this.namespace = namespace;
+ this.charset = charset;
+
+ xmlInputFactory = XMLInputFactory.newInstance();
+ xmlInputFactory.setProperty("javax.xml.stream.isCoalescing", true);
+
+ scriptEngineManager = new javax.script.ScriptEngineManager();
+ scriptEngineManager.setBindings(new SimpleBindings(namespace));
+ }
+
+ /**
+ * Deserializes an object hierarchy from a BXML resource.
+ * <p>
+ * This version of the method does not support location or resource resolution.
+ *
+ * @param inputStream
+ * An input stream containing the BXML data to deserialize.
+ *
+ * @return
+ * The deserialized object hierarchy.
+ */
+ @Override
+ public Object readObject(InputStream inputStream)
+ throws IOException, SerializationException {
+ if (inputStream == null) {
+ throw new IllegalArgumentException("inputStream is null.");
+ }
+
+ root = null;
+ language = null;
+
+ // Parse the XML stream
+ try {
+ try {
+ InputStreamReader inputStreamReader = new InputStreamReader(inputStream, charset);
+ xmlStreamReader = xmlInputFactory.createXMLStreamReader(inputStreamReader);
+
+ while (xmlStreamReader.hasNext()) {
+ int event = xmlStreamReader.next();
+
+ switch (event) {
+ case XMLStreamConstants.PROCESSING_INSTRUCTION: {
+ processProcessingInstruction();
+ break;
+ }
+
+ case XMLStreamConstants.CHARACTERS: {
+ processCharacters();
+ break;
+ }
+
+ case XMLStreamConstants.START_ELEMENT: {
+ processStartElement();
+ break;
+ }
+
+ case XMLStreamConstants.END_ELEMENT: {
+ processEndElement();
+ break;
+ }
+ }
+ }
+ } catch (XMLStreamException exception) {
+ throw new SerializationException(exception);
+ }
+ } catch (IOException exception) {
+ logException(exception);
+ throw exception;
+ } catch (SerializationException exception) {
+ logException(exception);
+ throw exception;
+ } catch (RuntimeException exception) {
+ logException(exception);
+ throw exception;
+ } finally {
+ xmlStreamReader = null;
+ }
+
+ // Apply the namespace bindings
+ applyNamespaceBindings();
+
+ // Bind the root to the namespace
+ if (root instanceof Bindable) {
+ Class<?> type = root.getClass();
+ while (Bindable.class.isAssignableFrom(type)) {
+ bind(root, type);
+ type = type.getSuperclass();
+ }
+
+ Bindable bindable = (Bindable)root;
+ bindable.initialize(namespace, location, resources);
+ }
+
+ return root;
+ }
+
+ /**
+ * Deserializes an object hierarchy from a BXML resource.
+ * <p>
+ * This version of the method does not support resource resolution.
+ *
+ * @param location
+ * The location of the BXML resource.
+ *
+ * @see #readObject(URL, Resources)
+ */
+ public final Object readObject(URL location)
+ throws IOException, SerializationException {
+ return readObject(location, null);
+ }
+
+ /**
+ * Deserializes an object hierarchy from a BXML resource.
+ *
+ * @param location
+ * The location of the BXML resource.
+ *
+ * @param resources
+ * The resources that will be used to localize the deserialized resource.
+ *
+ * #see readObject(InputStream)
+ */
+ public final Object readObject(URL location, Resources resources)
+ throws IOException, SerializationException {
+ if (location == null) {
+ throw new IllegalArgumentException("location is null.");
+ }
+
+ this.location = location;
+ this.resources = resources;
+
+ InputStream inputStream = new BufferedInputStream(location.openStream());
+
+ Object object;
+ try {
+ object = readObject(inputStream);
+ } finally {
+ inputStream.close();
+ }
+
+ this.location = null;
+ this.resources = null;
+
+ return object;
+ }
+
+ private void processProcessingInstruction() throws SerializationException {
+ String piTarget = xmlStreamReader.getPITarget();
+ String piData = xmlStreamReader.getPIData();
+
+ if (piTarget.equals(LANGUAGE_PROCESSING_INSTRUCTION)) {
+ if (language != null) {
+ throw new SerializationException("Language already set.");
+ }
+
+ language = piData;
+ }
+ }
+
+ private void processCharacters() throws SerializationException {
+ if (!xmlStreamReader.isWhiteSpace()) {
+ // Process the text
+ String text = xmlStreamReader.getText();
+
+ switch (element.type) {
+ case INSTANCE: {
+ // TODO If the parent element has a default property, set
+ // its value to text using BeanAdapter; otherwise, throw
+ // an exception
+
+ break;
+ }
+
+ case WRITABLE_PROPERTY:
+ case SCRIPT: {
+ element.value = text;
+ break;
+ }
+
+ default: {
+ throw new SerializationException("Unexpected characters in "
+ + element.type + " element.");
+ }
+ }
+ }
+ }
+
+ private void processStartElement() throws IOException, SerializationException {
+ // Initialize the page language
+ if (language == null) {
+ language = DEFAULT_LANGUAGE;
+ }
+
+ // Get element properties
+ String namespaceURI = xmlStreamReader.getNamespaceURI();
+ String prefix = xmlStreamReader.getPrefix();
+
+ // Some stream readers incorrectly report an empty string as the prefix
+ // for the default namespace
+ if (prefix != null
+ && prefix.length() == 0) {
+ prefix = null;
+ }
+
+ String localName = xmlStreamReader.getLocalName();
+
+ // Determine the type and value of this element
+ Element.Type elementType;
+ String name;
+ Object value = null;
+
+ if (prefix != null
+ && prefix.equals(BXML_PREFIX)) {
+ // The element represents a BXML operation
+ if (element == null) {
+ throw new SerializationException("Invalid 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)) {
+ elementType = Element.Type.DEFINE;
+ } else if (localName.equals(REFERENCE_TAG)) {
+ elementType = Element.Type.REFERENCE;
+ } else {
+ throw new SerializationException("Invalid element.");
+ }
+
+ name = "<" + prefix + ":" + localName + ">";
+ } else {
+ 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.");
+ }
+
+ elementType = Element.Type.INSTANCE;
+ name = "<" + ((prefix == null) ? "" : prefix + ":") + localName + ">";
+
+ String className = namespaceURI + "." + localName.replace('.', '$');
+
+ try {
+ Class<?> type = Class.forName(className);
+ value = newTypedObject(type);
+ } catch (ClassNotFoundException exception) {
+ throw new SerializationException(exception);
+ } catch (InstantiationException exception) {
+ throw new SerializationException(exception);
+ } catch (IllegalAccessException exception) {
+ throw new SerializationException(exception);
+ }
+ } else {
+ // The element represents a property
+ if (prefix != null) {
+ throw new SerializationException("Property elements cannot have a namespace prefix.");
+ }
+
+ if (element.value instanceof Map<?, ?>) {
+ elementType = Element.Type.WRITABLE_PROPERTY;
+ } else {
+ BeanAdapter beanAdapter = new BeanAdapter(element.value);
+
+ if (beanAdapter.isReadOnly(localName)) {
+ Class<?> propertyType = beanAdapter.getType(localName);
+ if (propertyType == null) {
+ throw new SerializationException("\"" + localName
+ + "\" is not a valid property of element "
+ + element.name + ".");
+ }
+
+ if (ListenerList.class.isAssignableFrom(propertyType)) {
+ elementType = Element.Type.LISTENER_LIST_PROPERTY;
+ } else {
+ elementType = Element.Type.READ_ONLY_PROPERTY;
+ value = beanAdapter.get(localName);
+ assert (value != null) : "Read-only properties cannot be null.";
+ }
+ } else {
+ elementType = Element.Type.WRITABLE_PROPERTY;
+ }
+ }
+
+ name = localName;
+ }
+ }
+
+ // Create the element and process the attributes
+ element = new Element(element, elementType, name, value);
+ processAttributes();
+
+ if (elementType == Element.Type.INCLUDE) {
+ // Load the include
+ if (!element.properties.containsKey(INCLUDE_SRC_ATTRIBUTE)) {
+ throw new SerializationException(INCLUDE_SRC_ATTRIBUTE
+ + " attribute is required for " + BXML_PREFIX + ":" + INCLUDE_TAG
+ + " tag.");
+ }
+
+ String src = element.properties.get(INCLUDE_SRC_ATTRIBUTE);
+
+ Resources resources = this.resources;
+ if (element.properties.containsKey(INCLUDE_RESOURCES_ATTRIBUTE)) {
+ resources = new Resources(resources,
+ element.properties.get(INCLUDE_RESOURCES_ATTRIBUTE));
+ }
+
+ boolean inline = false;
+ if (element.properties.containsKey(INCLUDE_INLINE_ATTRIBUTE)) {
+ inline = Boolean.parseBoolean(element.properties.get(INCLUDE_INLINE_ATTRIBUTE));
+ }
+
+ Charset charset = this.charset;
+ if (element.properties.containsKey(INCLUDE_CHARSET_ATTRIBUTE)) {
+ charset = Charset.forName(element.properties.get(INCLUDE_CHARSET_ATTRIBUTE));
+ }
+
+ // Create a serializer for the include
+ BXMLSerializer bxmlSerializer = (inline) ?
+ new BXMLSerializer(namespace, charset) : new BXMLSerializer(charset);
+
+ // Determine location from src attribute
+ URL location;
+ if (src.charAt(0) == '/') {
+ ClassLoader classLoader = getClass().getClassLoader();
+ location = classLoader.getResource(src.substring(1));
+ } else {
+ location = new URL(this.location, src);
+ }
+
+ element.value = bxmlSerializer.readObject(location, resources);
+ } else if (element.type == Element.Type.REFERENCE) {
+ // Dereference the value
+ if (!element.properties.containsKey(REFERENCE_ID_ATTRIBUTE)) {
+ throw new SerializationException(REFERENCE_ID_ATTRIBUTE
+ + " attribute is required for " + BXML_PREFIX + ":" + REFERENCE_TAG
+ + " tag.");
+ }
+
+ String id = element.properties.get(REFERENCE_ID_ATTRIBUTE);
+ if (!namespace.containsKey(id)) {
+ throw new SerializationException("A value with ID \"" + id + "\" does not exist.");
+ }
+
+ element.value = namespace.get(id);
+ }
+
+ // If the element has an ID, add the value to the namespace
+ if (element.id != null) {
+ namespace.put(element.id, element.value);
+
+ // If the type has an ID property, use it
+ Class<?> type = element.value.getClass();
+ IDProperty idProperty = type.getAnnotation(IDProperty.class);
+
+ if (idProperty != null) {
+ BeanAdapter beanAdapter = new BeanAdapter(element.value);
+ beanAdapter.put(idProperty.value(), element.id);
+ }
+ }
+ }
+
+ private void processAttributes() throws SerializationException {
+ for (int i = 0, n = xmlStreamReader.getAttributeCount(); i < n; i++) {
+ String prefix = xmlStreamReader.getAttributePrefix(i);
+ String localName = xmlStreamReader.getAttributeLocalName(i);
+ String value = xmlStreamReader.getAttributeValue(i);
+
+ if (prefix != null
+ && prefix.equals(BXML_PREFIX)) {
+ // The attribute represents an internal value
+ if (localName.equals(ID_ATTRIBUTE)) {
+ if (value.length() == 0
+ || value.contains(".")) {
+ throw new IllegalArgumentException("\"" + value + "\" is not a valid ID value.");
+ }
+
+ if (namespace.containsKey(value)) {
+ throw new SerializationException("ID " + value + " is already in use.");
+ }
+
+ if (element.type != Element.Type.INSTANCE
+ && element.type != Element.Type.INCLUDE) {
+ throw new SerializationException("An ID cannot be assigned to this element.");
+ }
+
+ element.id = value;
+ } else {
+ throw new SerializationException(BXML_PREFIX + ":" + localName
+ + " is not a valid attribute.");
+ }
+ } else {
+ boolean property = false;
+
+ switch (element.type) {
+ case INCLUDE: {
+ property = (localName.equals(INCLUDE_SRC_ATTRIBUTE)
+ || localName.equals(INCLUDE_RESOURCES_ATTRIBUTE)
+ || localName.equals(INCLUDE_INLINE_ATTRIBUTE));
+ break;
+ }
+
+ case SCRIPT: {
+ property = (localName.equals(SCRIPT_SRC_ATTRIBUTE));
+ break;
+ }
+
+ case REFERENCE: {
+ property = (localName.equals(REFERENCE_ID_ATTRIBUTE));
+ }
+ }
+
+ if (property) {
+ element.properties.put(localName, value);
+ } else {
+ // The attribute represents an instance property
+ if (value.startsWith(NAMESPACE_BINDING_PREFIX)
+ && value.endsWith(NAMESPACE_BINDING_SUFFIX)) {
+ // The attribute represents a namespace binding
+ namespaceBindingAttributes.add(new Attribute(element, localName,
+ value.substring(2, value.length() - 1)));
+ } else {
+ // Resolve the attribute value
+ Attribute attribute = new Attribute(element, localName, value);
+
+ if (value.length() > 0) {
+ if (value.charAt(0) == URL_PREFIX) {
+ value = value.substring(1);
+
+ if (value.length() > 0) {
+ if (value.charAt(0) == URL_PREFIX) {
+ attribute.value = value;
+ } else {
+ if (location == null) {
+ throw new IllegalStateException("Base location is undefined.");
+ }
+
+ try {
+ attribute.value = new URL(location, value);
+ } catch (MalformedURLException exception) {
+ throw new SerializationException(exception);
+ }
+ }
+ } else {
+ throw new SerializationException("Invalid URL resolution argument.");
+ }
+ } else if (value.charAt(0) == RESOURCE_KEY_PREFIX) {
+ value = value.substring(1);
+
+ if (value.length() > 0) {
+ if (value.charAt(0) == RESOURCE_KEY_PREFIX) {
+ attribute.value = value;
+ } else {
+ if (resources != null
+ && BeanAdapter.isDefined(resources, value)) {
+ attribute.value = BeanAdapter.get(resources, value);
+ } else {
+ attribute.value = value;
+ }
+ }
+ } else {
+ throw new SerializationException("Invalid resource resolution argument.");
+ }
+ } else if (value.charAt(0) == OBJECT_REFERENCE_PREFIX) {
+ value = value.substring(1);
+
+ if (value.length() > 0) {
+ if (value.charAt(0) == OBJECT_REFERENCE_PREFIX) {
+ attribute.value = value;
+ } else {
+ if (value.equals(BXML_PREFIX + ":" + null)) {
+ attribute.value = null;
+ } else {
+ if (!BeanAdapter.isDefined(namespace, value)) {
+ throw new SerializationException("Value \"" + value + "\" is not defined.");
+ }
+
+ attribute.value = BeanAdapter.get(namespace, value);
+ }
+ }
+ } else {
+ throw new SerializationException("Invalid object resolution argument.");
+ }
+ }
+ }
+
+ element.attributes.add(attribute);
+ }
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void processEndElement() throws SerializationException {
+ switch (element.type) {
+ case INSTANCE:
+ case INCLUDE:
+ case REFERENCE: {
+ // Apply attributes
+ for (Attribute attribute : element.attributes) {
+ Map<String, Object> map;
+ if (element.value instanceof Map<?, ?>) {
+ map = (Map<String, Object>)element.value;
+ } else {
+ map = new BeanAdapter(element.value);
+ }
+
+ map.put(attribute.name, attribute.value);
+ }
+
+ if (element.parent != null) {
+ if (element.parent.type == Element.Type.WRITABLE_PROPERTY) {
+ // Set this as the property value; it will be applied later in the
+ // parent's closing tag
+ element.parent.value = element.value;
+ } else if (element.parent.value != null) {
+ // If the parent element has a default property, use it; otherwise, if the
+ // parent is a list, add the element to it
+ Class<?> parentType = element.parent.value.getClass();
+ DefaultProperty defaultProperty = parentType.getAnnotation(DefaultProperty.class);
+
+ if (defaultProperty == null) {
+ if (element.parent.value instanceof List<?>) {
+ List<Object> list = (List<Object>)element.parent.value;
+ list.add(element.value);
+ } else {
+ throw new SerializationException(element.parent.value.getClass()
+ + " is not a list.");
+ }
+ } else {
+ String defaultPropertyName = defaultProperty.value();
+ BeanAdapter beanAdapter = new BeanAdapter(element.parent.value);
+ Object defaultPropertyValue = beanAdapter.get(defaultPropertyName);
+
+ if (defaultPropertyValue instanceof List<?>) {
+ List<Object> list = (List<Object>)defaultPropertyValue;
+ list.add(element.value);
+ } else {
+ beanAdapter.put(defaultPropertyName, element.value);
+ }
+ }
+ }
+ }
+
+ break;
+ }
+
+ case READ_ONLY_PROPERTY: {
+ Map<String, Object> map;
+ if (element.value instanceof Map<?, ?>) {
+ map = (Map<String, Object>)element.value;
+ } else {
+ map = new BeanAdapter(element.value);
+ }
+
+ // Process attributes looking for instance property setters
+ for (Attribute attribute : element.attributes) {
+ map.put(attribute.name, attribute.value);
+ }
+
+ break;
+ }
+
+ case WRITABLE_PROPERTY: {
+ Map<String, Object> map;
+ if (element.parent.value instanceof Map) {
+ map = (Map<String, Object>)element.parent.value;
+ } else {
+ map = new BeanAdapter(element.parent.value);
+ }
+
+ map.put(element.name, element.value);
+
+ break;
+ }
+
+ case LISTENER_LIST_PROPERTY: {
+ // Create the listener invocation handler
+ HashMap<String, String> functionMap = new HashMap<String, String>();
+ for (Attribute attribute : element.attributes) {
+ functionMap.put(attribute.name, (String)attribute.value);
+ }
+
+ ScriptEngine scriptEngine = scriptEngineManager.getEngineByName(language);
+ ListenerInvocationHandler handler = new ListenerInvocationHandler(functionMap, scriptEngine);
+
+ // Get the type of the listener interface and create the listener proxy
+ BeanAdapter beanAdapter = new BeanAdapter(element.parent.value);
+ ListenerList<?> listenerList = (ListenerList<?>)beanAdapter.get(element.name);
+ Class<?> listenerListClass = listenerList.getClass();
+
+ java.lang.reflect.Type[] genericInterfaces = listenerListClass.getGenericInterfaces();
+ Class<?> listenerClass = (Class<?>)genericInterfaces[0];
+
+ Object listener = Proxy.newProxyInstance(getClass().getClassLoader(),
+ new Class<?>[]{listenerClass}, handler);
+
+ // Add the listener
+ 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);
+ }
+
+ break;
+ }
+
+ case SCRIPT: {
+ String src = null;
+ if (element.properties.containsKey(INCLUDE_SRC_ATTRIBUTE)) {
+ src = element.properties.get(INCLUDE_SRC_ATTRIBUTE);
+ }
+
+ if (src != null) {
+ int i = src.lastIndexOf(".");
+ if (i == -1) {
+ throw new SerializationException("Cannot determine type of script \""
+ + src + "\".");
+ }
+
+ String extension = src.substring(i + 1);
+ ScriptEngine scriptEngine = scriptEngineManager.getEngineByExtension(extension);
+
+ if (scriptEngine == null) {
+ throw new SerializationException("Unable to find scripting engine for"
+ + " extension " + extension + ".");
+ }
+
+ scriptEngine.setBindings(scriptEngineManager.getBindings(), ScriptContext.ENGINE_SCOPE);
+
+ try {
+ URL scriptLocation;
+ if (src.charAt(0) == '/') {
+ ClassLoader classLoader = getClass().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);
+ }
+ }
+
+ if (element.value != null) {
+ // Evaluate the script
+ String script = (String)element.value;
+ ScriptEngine scriptEngine = scriptEngineManager.getEngineByName(language);
+
+ if (scriptEngine == null) {
+ throw new SerializationException("Unable to find scripting engine for"
+ + " language \"" + language + "\".");
+ }
+
+ scriptEngine.setBindings(scriptEngineManager.getBindings(), ScriptContext.ENGINE_SCOPE);
+
+ try {
+ scriptEngine.eval(script);
+ } catch (ScriptException exception) {
+ System.err.println(exception);
+ System.err.println(script);
+ }
+ }
+
+ break;
+ }
+
+ case DEFINE: {
+ // No-op
+ }
+ }
+
+ // Move up the stack
+ if (element.parent == null) {
+ root = element.value;
+ }
+
+ element = element.parent;
+ }
+
+ @SuppressWarnings("unchecked")
+ private void applyNamespaceBindings() throws SerializationException {
+ for (Attribute attribute : namespaceBindingAttributes) {
+ // Determine source object, key, and mapping function
+ Element element = attribute.element;
+ String sourcePath = (String)attribute.value;
+
+ String mappingFunction;
+ int i = sourcePath.indexOf(MAPPING_FUNCTION_DELIMITER);
+ if (i == -1) {
+ mappingFunction = null;
+ } else {
+ mappingFunction = sourcePath.substring(0, i);
+ sourcePath = sourcePath.substring(i + 1);
+ }
+
+ List<String> sourceKeys = BeanAdapter.parsePath(sourcePath);
+ String sourceKey = sourceKeys.remove(sourceKeys.size() - 1);
+ Object source = BeanAdapter.get(namespace, sourceKeys);
+
+ // Determine target object and key
+ Object target;
+ String targetKey;
+ switch (element.type) {
+ case INSTANCE:
+ case INCLUDE:
+ case READ_ONLY_PROPERTY: {
+ target = element.value;
+ targetKey = attribute.name;
+ break;
+ }
+
+ default: {
+ throw new RuntimeException("Unsupported element type in namespace binding.");
+ }
+ }
+
+ ScriptEngine scriptEngine = scriptEngineManager.getEngineByName(language);
+
+ final NamespaceBinding namespaceBinding = new NamespaceBinding(source, sourceKey,
+ target, targetKey, mappingFunction, scriptEngine);
+
+ // Perform the initial binding
+ namespaceBinding.apply();
+
+ // Listen for subsequent bind events
+ ObservableMap<String, Object> map = (source instanceof ObservableMap<?, ?>) ?
+ (ObservableMap<String, Object>)source : new BeanAdapter(source);
+
+ ObservableMapListener<String, Object> listener = new ObservableMapListener.Adapter<String, Object>() {
+ @Override
+ public void valueUpdated(ObservableMap<String, Object> map, String key, Object previousValue) {
+ if (key.equals(namespaceBinding.sourceKey)) {
+ namespaceBinding.apply();
+ }
+ }
+ };
+
+ map.getObservableMapListeners().add(listener);
+ }
+
+ namespaceBindingAttributes.clear();
+ }
+
+ private void logException(Exception exception) {
+ Location streamReaderlocation = xmlStreamReader.getLocation();
+ String message = "An error occurred at line number " + streamReaderlocation.getLineNumber();
+
+ if (location != null) {
+ message += " in file " + location.getPath();
+ }
+
+ message += ":\n" + exception.getMessage();
+
+ 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 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;
+ }
+
+ /**
+ * Applies BXML 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 BXML 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();
+
+ BXML bindingAnnotation = field.getAnnotation(BXML.class);
+
+ if (bindingAnnotation != 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 = bindingAnnotation.id();
+ if (id.equals("\0")) {
+ id = field.getName();
+ }
+
+ if (namespace.containsKey(id)) {
+ // Set the value into the field
+ Object value = namespace.get(id);
+ try {
+ field.set(object, value);
+ } catch (IllegalAccessException exception) {
+ throw new BindException(exception);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates a new typed object as part of the deserialization process.
+ * The base implementation simply calls {@link Class#newInstance()}.
+ * Subclasses may override this method to provide an alternate instantiation
+ * mechanism, such as dependency-injected construction.
+ *
+ * @param type
+ * The type of object being requested.
+ */
+ protected Object newTypedObject(Class<?> type)
+ throws InstantiationException, IllegalAccessException {
+ return type.newInstance();
+ }
+
+ /**
+ * Gets a read-only version of the XML stream reader that's being used by
+ * this serializer. Subclasses can use this to access information about the
+ * current event.
+ */
+ protected final XMLStreamReader getXMLStreamReader() {
+ return new StreamReaderDelegate(xmlStreamReader) {
+ @Override
+ public void close() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int next() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int nextTag() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+}
Added: pivot/branches/3.x/core/src/org/apache/pivot/bxml/BindException.java
URL: http://svn.apache.org/viewvc/pivot/branches/3.x/core/src/org/apache/pivot/bxml/BindException.java?rev=1057053&view=auto
==============================================================================
--- pivot/branches/3.x/core/src/org/apache/pivot/bxml/BindException.java (added)
+++ pivot/branches/3.x/core/src/org/apache/pivot/bxml/BindException.java Sun Jan 9 23:19:19 2011
@@ -0,0 +1,40 @@
+/*
+ * 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.bxml;
+
+/**
+ * Thrown when an error is encountered during binding.
+ */
+public class BindException extends RuntimeException {
+ private static final long serialVersionUID = 0;
+
+ public BindException() {
+ super();
+ }
+
+ public BindException(String message) {
+ super(message);
+ }
+
+ public BindException(Throwable cause) {
+ super(cause);
+ }
+
+ public BindException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
Added: pivot/branches/3.x/core/src/org/apache/pivot/bxml/Bindable.java
URL: http://svn.apache.org/viewvc/pivot/branches/3.x/core/src/org/apache/pivot/bxml/Bindable.java?rev=1057053&view=auto
==============================================================================
--- pivot/branches/3.x/core/src/org/apache/pivot/bxml/Bindable.java (added)
+++ pivot/branches/3.x/core/src/org/apache/pivot/bxml/Bindable.java Sun Jan 9 23:19:19 2011
@@ -0,0 +1,48 @@
+/*
+ * 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.bxml;
+
+import java.net.URL;
+import java.util.Map;
+
+import org.apache.pivot.util.Resources;
+
+
+/**
+ * Allows {@link BXMLSerializer} to initialize an instance of a deserialized class.
+ */
+public interface Bindable {
+ /**
+ * Called to initialize the class after it has been completely
+ * processed and bound by the serializer.
+ *
+ * @param namespace
+ * The serializer's namespace. The bindable object can use this to extract named
+ * values defined in the FXML file. Alternatively, the {@link BXML} annotation
+ * can be used by trusted code to automatically map namespace values to member
+ * variables.
+ *
+ * @param location
+ * The location of the FXML source. May be <tt>null</tt> if the location of the
+ * source is not known.
+ *
+ * @param resources
+ * The resources that were used to localize the deserialized content. May be
+ * <tt>null</tt> if no resources were specified.
+ */
+ public void initialize(Map<String, Object> namespace, URL location, Resources resources);
+}
Added: pivot/branches/3.x/core/src/org/apache/pivot/bxml/DefaultProperty.java
URL: http://svn.apache.org/viewvc/pivot/branches/3.x/core/src/org/apache/pivot/bxml/DefaultProperty.java?rev=1057053&view=auto
==============================================================================
--- pivot/branches/3.x/core/src/org/apache/pivot/bxml/DefaultProperty.java (added)
+++ pivot/branches/3.x/core/src/org/apache/pivot/bxml/DefaultProperty.java Sun Jan 9 23:19:19 2011
@@ -0,0 +1,37 @@
+/*
+ * 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.bxml;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Specifies a property to which child elements will be added or set when an
+ * explicit property is not given.
+ */
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface DefaultProperty {
+ /**
+ * The name of the default property.
+ */
+ public String value();
+}
Added: pivot/branches/3.x/core/src/org/apache/pivot/bxml/IDProperty.java
URL: http://svn.apache.org/viewvc/pivot/branches/3.x/core/src/org/apache/pivot/bxml/IDProperty.java?rev=1057053&view=auto
==============================================================================
--- pivot/branches/3.x/core/src/org/apache/pivot/bxml/IDProperty.java (added)
+++ pivot/branches/3.x/core/src/org/apache/pivot/bxml/IDProperty.java Sun Jan 9 23:19:19 2011
@@ -0,0 +1,37 @@
+/*
+ * 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.bxml;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Specifies a property to which BXML ID values will be propagated during
+ * serialization.
+ */
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface IDProperty {
+ /**
+ * The name of the ID property.
+ */
+ public String value();
+}
Added: pivot/branches/3.x/core/src/org/apache/pivot/csv/CSVSerializer.java
URL: http://svn.apache.org/viewvc/pivot/branches/3.x/core/src/org/apache/pivot/csv/CSVSerializer.java?rev=1057053&view=auto
==============================================================================
--- pivot/branches/3.x/core/src/org/apache/pivot/csv/CSVSerializer.java (added)
+++ pivot/branches/3.x/core/src/org/apache/pivot/csv/CSVSerializer.java Sun Jan 9 23:19:19 2011
@@ -0,0 +1,516 @@
+/*
+ * 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.csv;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.pivot.beans.BeanAdapter;
+import org.apache.pivot.io.SerializationException;
+import org.apache.pivot.io.Serializer;
+import org.apache.pivot.util.ListenerList;
+
+/**
+ * Implementation of the {@link Serializer} interface that reads data from
+ * and writes data to a comma-separated value (CSV) file.
+ */
+public class CSVSerializer implements Serializer<List<?>> {
+ private static class CSVSerializerListenerList
+ extends ListenerList<CSVSerializerListener>
+ implements CSVSerializerListener {
+ @Override
+ public void beginList(CSVSerializer csvSerializer, List<?> list) {
+ for (CSVSerializerListener listener : listeners()) {
+ listener.beginList(csvSerializer, list);
+ }
+ }
+
+ @Override
+ public void endList(CSVSerializer csvSerializer) {
+ for (CSVSerializerListener listener : listeners()) {
+ listener.endList(csvSerializer);
+ }
+ }
+
+ @Override
+ public void readItem(CSVSerializer csvSerializer, Object item) {
+ for (CSVSerializerListener listener : listeners()) {
+ listener.readItem(csvSerializer, item);
+ }
+ }
+ }
+
+ private Charset charset;
+ private Type itemType;
+
+ private List<String> keys = new ArrayList<String>();
+
+ private boolean writeKeys = false;
+
+ private int c = -1;
+
+ private CSVSerializerListenerList csvSerializerListeners = null;
+
+ public static final String DEFAULT_CHARSET_NAME = "ISO-8859-1";
+ public static final Type DEFAULT_ITEM_TYPE = HashMap.class;
+
+ public static final String CSV_EXTENSION = "csv";
+ public static final String MIME_TYPE = "text/csv";
+ public static final int BUFFER_SIZE = 2048;
+
+ public CSVSerializer() {
+ this(Charset.forName(DEFAULT_CHARSET_NAME), DEFAULT_ITEM_TYPE);
+ }
+
+ public CSVSerializer(Charset charset) {
+ this(charset, DEFAULT_ITEM_TYPE);
+ }
+
+ public CSVSerializer(Type itemType) {
+ this(Charset.forName(DEFAULT_CHARSET_NAME), itemType);
+ }
+
+ public CSVSerializer(Charset charset, Type itemType) {
+ if (charset == null) {
+ throw new IllegalArgumentException("charset is null.");
+ }
+
+ if (itemType == null) {
+ throw new IllegalArgumentException("itemType is null.");
+ }
+
+ this.charset = charset;
+ this.itemType = itemType;
+ }
+
+ /**
+ * Returns the character set used to encode/decode the CSV data.
+ */
+ public Charset getCharset() {
+ return charset;
+ }
+
+ /**
+ * Returns the type of the item that will be instantiated by the serializer
+ * during a read operation.
+ */
+ public Type getItemType() {
+ return itemType;
+ }
+
+ /**
+ * Returns the keys that will be read or written by this serializer.
+ */
+ public List<String> getKeys() {
+ return keys;
+ }
+
+ public void setKeys(List<String> keys) {
+ this.keys = keys;
+ }
+
+ /**
+ * Sets the keys that will be read or written by this serializer.
+ *
+ * @param keys
+ */
+ public void setKeys(String... keys) {
+ if (keys == null) {
+ throw new IllegalArgumentException();
+ }
+
+ setKeys(Arrays.asList(keys));
+ }
+
+ /**
+ * Returns the serializer's write keys flag.
+ */
+ public boolean getWriteKeys() {
+ return writeKeys;
+ }
+
+ /**
+ * Sets the serializer's write keys flag.
+ *
+ * @param writeKeys
+ * If <tt>true</tt>, the first line of the output will contain the keys.
+ * Otherwise, the first line will contain the first line of data.
+ */
+ public void setWriteKeys(boolean writeKeys) {
+ this.writeKeys = writeKeys;
+ }
+
+ /**
+ * Reads values from a comma-separated value stream.
+ *
+ * @param inputStream
+ * The input stream from which data will be read.
+ *
+ * @see #readObject(Reader)
+ */
+ @Override
+ public List<?> readObject(InputStream inputStream)
+ throws IOException, SerializationException {
+ if (inputStream == null) {
+ throw new IllegalArgumentException("inputStream is null.");
+ }
+
+ Reader reader = new BufferedReader(new InputStreamReader(inputStream, charset), BUFFER_SIZE);
+ return readObject(reader);
+ }
+
+ /**
+ * Reads values from a comma-separated value stream.
+ *
+ * @param reader
+ * The reader from which data will be read.
+ *
+ * @return
+ * A sequence containing the data read from the CSV file.
+ * <p>
+ * If no keys have been specified when this method is called, they are assumed
+ * to be defined in the first line of the file.
+ */
+ public List<?> readObject(Reader reader)
+ throws IOException, SerializationException {
+ if (reader == null) {
+ throw new IllegalArgumentException("reader is null.");
+ }
+
+ LineNumberReader lineNumberReader = new LineNumberReader(reader);
+
+ if (keys.size() == 0) {
+ // Read keys from first line
+ String line = lineNumberReader.readLine();
+ if (line == null) {
+ throw new SerializationException("Could not read keys from input.");
+ }
+
+ String[] keys = line.split(",");
+ this.keys = new ArrayList<String>();
+
+ for (int i = 0; i < keys.length; i++) {
+ String key = keys[i];
+ this.keys.add(key.trim());
+ }
+ }
+
+ // Create the list and notify the listeners
+ List<Object> items = new ArrayList<Object>();
+
+ if (csvSerializerListeners != null) {
+ csvSerializerListeners.beginList(this, items);
+ }
+
+ // Move to the first character
+ c = lineNumberReader.read();
+
+ // Ignore BOM (if present)
+ if (c == 0xFEFF) {
+ c = lineNumberReader.read();
+ }
+
+ try {
+ while (c != -1) {
+ Object item = readItem(lineNumberReader);
+ while (item != null) {
+ items.add(item);
+
+ // Move to next line
+ while (c != -1
+ && (c == '\r' || c == '\n')) {
+ c = lineNumberReader.read();
+ }
+
+ // Read the next item
+ item = readItem(lineNumberReader);
+ }
+ }
+ } catch (SerializationException exception) {
+ System.err.println("An error occurred while processing input at line number "
+ + (lineNumberReader.getLineNumber() + 1));
+
+ throw exception;
+ }
+
+ // Notify the listeners
+ if (csvSerializerListeners != null) {
+ csvSerializerListeners.endList(this);
+ }
+
+ return items;
+ }
+
+ @SuppressWarnings("unchecked")
+ private Object readItem(Reader reader)
+ throws IOException, SerializationException {
+ Object item = null;
+
+ if (c != -1) {
+ // Instantiate the item
+ Map<String, Object> itemMap;
+
+ try {
+ if (itemType instanceof ParameterizedType) {
+ ParameterizedType parameterizedItemType = (ParameterizedType)itemType;
+ Class<?> rawItemType = (Class<?>)parameterizedItemType.getRawType();
+ item = rawItemType.newInstance();
+ } else {
+ Class<?> classItemType = (Class<?>)itemType;
+ item = classItemType.newInstance();
+ }
+
+ if (item instanceof Map<?, ?>) {
+ itemMap = (Map<String, Object>)item;
+ } else {
+ itemMap = new BeanAdapter(item);
+ }
+ } catch(IllegalAccessException exception) {
+ throw new SerializationException(exception);
+ } catch(InstantiationException exception) {
+ throw new SerializationException(exception);
+ }
+
+ // Add values to the item
+ for (int i = 0, n = keys.size(); i < n; i++) {
+ String key = keys.get(i);
+ String value = readValue(reader);
+ if (value == null) {
+ throw new SerializationException("Error reading value for "
+ + key + " from input stream.");
+ }
+
+ if (c == '\r' || c == '\n') {
+ if (i < n - 1) {
+ throw new SerializationException("Line data is incomplete.");
+ }
+
+ // Move to next char; if LF, move again
+ c = reader.read();
+
+ if (c == '\n') {
+ c = reader.read();
+ }
+ }
+
+ itemMap.put(key, value);
+ }
+
+ // Notify the listeners
+ if (csvSerializerListeners != null) {
+ csvSerializerListeners.readItem(this, item);
+ }
+ }
+
+ return item;
+ }
+
+ private String readValue(Reader reader)
+ throws IOException, SerializationException {
+ String value = null;
+
+ // Read the next value from this line, returning null if there are
+ // no more values on the line
+ if (c != -1
+ && (c != '\r' && c != '\n')) {
+ // Read the value
+ StringBuilder valueBuilder = new StringBuilder();
+
+ // Values may be bounded in quotes; the double-quote character is
+ // escaped by two successive occurrences
+ boolean quoted = (c == '"');
+ if (quoted) {
+ c = reader.read();
+ }
+
+ while (c != -1
+ && (quoted || (c != ',' && c != '\r' && c != '\n'))) {
+ if (c == '"') {
+ if (!quoted) {
+ throw new SerializationException("Dangling quote.");
+ }
+
+ c = reader.read();
+
+ if (c != '"'
+ && (c != ',' && c != '\r' && c != '\n' && c != -1)) {
+ throw new SerializationException("Prematurely terminated quote.");
+ }
+
+ quoted &= (c == '"');
+ }
+
+ if (c != -1
+ && (quoted || (c != ',' && c != '\r' && c != '\n'))) {
+ valueBuilder.append((char)c);
+ c = reader.read();
+ }
+ }
+
+ if (quoted) {
+ throw new SerializationException("Unterminated string.");
+ }
+
+ value = valueBuilder.toString();
+
+ // Move to the next character after ',' (don't automatically advance to
+ // the next line)
+ if (c == ',') {
+ c = reader.read();
+ }
+ }
+
+ // Trim the value
+ if (value != null) {
+ value = value.trim();
+ }
+
+ return value;
+ }
+
+ /**
+ * Writes values to a comma-separated value stream.
+ *
+ * @param items
+ *
+ * @param outputStream
+ * The output stream to which data will be written.
+ *
+ * @see #writeObject(List, Writer)
+ */
+ @Override
+ public void writeObject(List<?> items, OutputStream outputStream)
+ throws IOException, SerializationException {
+ if (items == null) {
+ throw new IllegalArgumentException("items is null.");
+ }
+
+ if (outputStream == null) {
+ throw new IllegalArgumentException("outputStream is null.");
+ }
+
+ Writer writer = new BufferedWriter(new OutputStreamWriter(outputStream, charset), BUFFER_SIZE);
+ writeObject(items, writer);
+ }
+
+ /**
+ * Writes values to a comma-separated value stream.
+ *
+ * @param items
+ * A sequence containing the data to write to the CSV file.
+ *
+ * @param writer
+ * The writer to which data will be written.
+ */
+ @SuppressWarnings("unchecked")
+ public void writeObject(List<?> items, Writer writer) throws IOException {
+ if (items == null) {
+ throw new IllegalArgumentException("items is null.");
+ }
+
+ if (writer == null) {
+ throw new IllegalArgumentException("writer is null.");
+ }
+
+ if (writeKeys) {
+ // Write keys as first line
+ for (int i = 0, n = keys.size(); i < n; i++) {
+ String key = keys.get(i);
+
+ if (i > 0) {
+ writer.append(",");
+ }
+
+ writer.append(key);
+ }
+ }
+
+ for (Object item : items) {
+ Map<String, Object> itemMap;
+
+ if (item instanceof Map<?, ?>) {
+ itemMap = (Map<String, Object>)item;
+ } else {
+ itemMap = new BeanAdapter(item);
+ }
+
+ for (int i = 0, n = keys.size(); i < n; i++) {
+ String key = keys.get(i);
+
+ if (i > 0) {
+ writer.append(",");
+ }
+
+ Object value = itemMap.get(key);
+
+ if (value != null) {
+ String string = value.toString();
+
+ if (string.indexOf(',') >= 0
+ || string.indexOf('"') >= 0
+ || string.indexOf('\r') >= 0
+ || string.indexOf('\n') >= 0) {
+ writer.append('"');
+
+ if (string.indexOf('"') == -1) {
+ writer.append(string);
+ } else {
+ writer.append(string.replace("\"", "\"\""));
+ }
+
+ writer.append('"');
+ } else {
+ writer.append(string);
+ }
+ }
+ }
+
+ writer.append("\r\n");
+ }
+
+ writer.flush();
+ }
+
+ @Override
+ public String getMIMEType(List<?> objects) {
+ return MIME_TYPE + "; charset=" + charset.name();
+ }
+
+ public ListenerList<CSVSerializerListener> getCSVSerializerListeners() {
+ if (csvSerializerListeners == null) {
+ csvSerializerListeners = new CSVSerializerListenerList();
+ }
+
+ return csvSerializerListeners;
+ }
+}
Added: pivot/branches/3.x/core/src/org/apache/pivot/csv/CSVSerializerListener.java
URL: http://svn.apache.org/viewvc/pivot/branches/3.x/core/src/org/apache/pivot/csv/CSVSerializerListener.java?rev=1057053&view=auto
==============================================================================
--- pivot/branches/3.x/core/src/org/apache/pivot/csv/CSVSerializerListener.java (added)
+++ pivot/branches/3.x/core/src/org/apache/pivot/csv/CSVSerializerListener.java Sun Jan 9 23:19:19 2011
@@ -0,0 +1,64 @@
+/*
+ * 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.csv;
+
+import java.util.List;
+
+/**
+ * CSV serializer listener interface.
+ */
+public interface CSVSerializerListener {
+ /**
+ * CSV serializer listener adapter.
+ */
+ public static class Adapter implements CSVSerializerListener {
+ @Override
+ public void beginList(CSVSerializer csvSerializer, List<?> list) {
+ }
+
+ @Override
+ public void endList(CSVSerializer csvSerializer) {
+ }
+
+ @Override
+ public void readItem(CSVSerializer csvSerializer, Object item) {
+ }
+ }
+
+ /**
+ * Called when the serializer has begun reading the list.
+ *
+ * @param csvSerializer
+ * @param list
+ */
+ public void beginList(CSVSerializer csvSerializer, List<?> list);
+
+ /**
+ * Called when the serializer has finished reading the list.
+ *
+ * @param csvSerializer
+ */
+ public void endList(CSVSerializer csvSerializer);
+
+ /**
+ * Called when the serializer has read an item.
+ *
+ * @param csvSerializer
+ * @param item
+ */
+ public void readItem(CSVSerializer csvSerializer, Object item);
+}
Added: pivot/branches/3.x/core/src/org/apache/pivot/io/BinarySerializer.java
URL: http://svn.apache.org/viewvc/pivot/branches/3.x/core/src/org/apache/pivot/io/BinarySerializer.java?rev=1057053&view=auto
==============================================================================
--- pivot/branches/3.x/core/src/org/apache/pivot/io/BinarySerializer.java (added)
+++ pivot/branches/3.x/core/src/org/apache/pivot/io/BinarySerializer.java Sun Jan 9 23:19:19 2011
@@ -0,0 +1,83 @@
+/*
+ * 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.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+
+/**
+ * Implementation of the {@link Serializer} interface that uses Java's
+ * internal serialization mechanism to read and write values. All values in the
+ * object hierarchy are required to implement {@link java.io.Serializable}.
+ */
+public class BinarySerializer implements Serializer<Object> {
+ public static final String MIME_TYPE = "application/x-java-serialized-object";
+ public static final String CLASS_PARAMETER = "class";
+
+ /**
+ * Reads a graph of serialized objects from an input stream.
+ */
+ @Override
+ public Object readObject(InputStream inputStream) throws IOException,
+ SerializationException {
+ if (inputStream == null) {
+ throw new IllegalArgumentException("inputStream is null.");
+ }
+
+ Object object = null;
+
+ try {
+ ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
+ object = objectInputStream.readObject();
+ } catch(ClassNotFoundException exception) {
+ throw new SerializationException(exception);
+ }
+
+ return object;
+ }
+
+ /**
+ * Writes a graph of serializable objects to an output stream.
+ */
+ @Override
+ public void writeObject(Object object, OutputStream outputStream)
+ throws IOException, SerializationException {
+ if (object == null) {
+ throw new IllegalArgumentException("object is null.");
+ }
+
+ if (outputStream == null) {
+ throw new IllegalArgumentException("outputStream is null.");
+ }
+
+ ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
+ objectOutputStream.writeObject(object);
+ }
+
+ @Override
+ public String getMIMEType(Object object) {
+ String mimeType = MIME_TYPE;
+ if (object != null) {
+ mimeType += "; " + CLASS_PARAMETER + "=" + object.getClass().getName();
+ }
+
+ return mimeType;
+ }
+}
Added: pivot/branches/3.x/core/src/org/apache/pivot/io/ByteArraySerializer.java
URL: http://svn.apache.org/viewvc/pivot/branches/3.x/core/src/org/apache/pivot/io/ByteArraySerializer.java?rev=1057053&view=auto
==============================================================================
--- pivot/branches/3.x/core/src/org/apache/pivot/io/ByteArraySerializer.java (added)
+++ pivot/branches/3.x/core/src/org/apache/pivot/io/ByteArraySerializer.java Sun Jan 9 23:19:19 2011
@@ -0,0 +1,98 @@
+/*
+ * 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.io;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Implementation of the {@link Serializer} interface that reads and writes a
+ * byte array.
+ *
+ * @see Serializer
+ */
+public class ByteArraySerializer implements Serializer<byte[]> {
+ public static final String MIME_TYPE = "application/octet-stream";
+
+ public static final int BUFFER_SIZE = 4096;
+
+ /**
+ * Reads a byte array from an input stream.
+ */
+ @Override
+ public byte[] readObject(InputStream inputStream) throws IOException,
+ SerializationException {
+ if (inputStream == null) {
+ throw new IllegalArgumentException("inputStream is null.");
+ }
+
+ byte[] result = null;
+
+ try {
+ BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
+ ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+
+ byte[] buffer = new byte[BUFFER_SIZE];
+ int read;
+ while ((read = bufferedInputStream.read(buffer)) != -1) {
+ byteOutputStream.write(buffer, 0, read);
+ }
+
+ byteOutputStream.flush();
+
+ result = byteOutputStream.toByteArray();
+
+ } catch (IOException exception) {
+ throw new SerializationException(exception);
+ }
+
+ return result;
+ }
+
+ /**
+ * Writes a byte array to an output stream.
+ */
+ @Override
+ public void writeObject(byte[] bytes, OutputStream outputStream) throws IOException,
+ SerializationException {
+ if (bytes == null) {
+ throw new IllegalArgumentException("byte array is null.");
+ }
+
+ if (outputStream == null) {
+ throw new IllegalArgumentException("outputStream is null.");
+ }
+
+ try {
+ BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
+ bufferedOutputStream.write(bytes);
+ bufferedOutputStream.flush();
+ } catch (IOException exception) {
+ throw new SerializationException(exception);
+ }
+
+ }
+
+ @Override
+ public String getMIMEType(byte[] bytes) {
+ return MIME_TYPE;
+ }
+}
Added: pivot/branches/3.x/core/src/org/apache/pivot/io/FileSerializer.java
URL: http://svn.apache.org/viewvc/pivot/branches/3.x/core/src/org/apache/pivot/io/FileSerializer.java?rev=1057053&view=auto
==============================================================================
--- pivot/branches/3.x/core/src/org/apache/pivot/io/FileSerializer.java (added)
+++ pivot/branches/3.x/core/src/org/apache/pivot/io/FileSerializer.java Sun Jan 9 23:19:19 2011
@@ -0,0 +1,112 @@
+/*
+ * 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.io;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import javax.activation.MimetypesFileTypeMap;
+
+/**
+ * Implementation of the {@link Serializer} interface that reads and
+ * writes {@link java.io.File} objects.
+ */
+public class FileSerializer implements Serializer<File> {
+ private File tempFileDirectory;
+
+ public static final int BUFFER_SIZE = 1024;
+
+ private static final MimetypesFileTypeMap MIME_TYPES_FILE_MAP = new MimetypesFileTypeMap();
+
+ /**
+ * Creates a new file serializer that will store temporary files in the default
+ * temporary file directory.
+ */
+ public FileSerializer() {
+ this(null);
+ }
+
+ /**
+ * Creates a new file serializer that will store temporary files in a specific
+ * directory.
+ *
+ * @param tempFileDirectory
+ * The directory in which to store temporary folders.
+ */
+ public FileSerializer(File tempFileDirectory) {
+ if (tempFileDirectory != null
+ && !tempFileDirectory.isDirectory()) {
+ throw new IllegalArgumentException();
+ }
+
+ this.tempFileDirectory = tempFileDirectory;
+ }
+
+ /**
+ * Reads a file from an input stream. The returned file is a temporary file and must be
+ * deleted by the caller.
+ */
+ @Override
+ public File readObject(InputStream inputStream) throws IOException, SerializationException {
+ File file = File.createTempFile(getClass().getName(), null, tempFileDirectory);
+ OutputStream outputStream = null;
+
+ try {
+ outputStream = new BufferedOutputStream(new FileOutputStream(file), BUFFER_SIZE);
+ for (int data = inputStream.read(); data != -1; data = inputStream.read()) {
+ outputStream.write((byte)data);
+ }
+ } finally {
+ if (outputStream != null) {
+ outputStream.close();
+ }
+ }
+
+ return file;
+ }
+
+ /**
+ * Writes a file to an output stream.
+ */
+ @Override
+ public void writeObject(File file, OutputStream outputStream) throws IOException,
+ SerializationException {
+ InputStream inputStream = null;
+
+ try {
+ inputStream = new BufferedInputStream(new FileInputStream(file), BUFFER_SIZE);
+ for (int data = inputStream.read(); data != -1; data = inputStream.read()) {
+ outputStream.write((byte)data);
+ }
+ } finally {
+ if (inputStream != null) {
+ inputStream.close();
+ }
+ }
+ }
+
+ @Override
+ public String getMIMEType(File file) {
+ return MIME_TYPES_FILE_MAP.getContentType(file);
+ }
+}