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 2009/07/23 19:53:04 UTC

svn commit: r797172 - in /incubator/pivot/trunk/wtk: src/org/apache/pivot/wtk/ src/org/apache/pivot/wtkx/ test/org/apache/pivot/wtk/test/

Author: gbrown
Date: Thu Jul 23 17:53:04 2009
New Revision: 797172

URL: http://svn.apache.org/viewvc?rev=797172&view=rev
Log:
Convert Window.ActionDictionary to ActionMappingSequence so we can define mappings in WTKX; add console error output including line number to WTKXSerializer.

Added:
    incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/WindowActionMappingListener.java
    incubator/pivot/trunk/wtk/test/org/apache/pivot/wtk/test/ActionMappingTest.java
    incubator/pivot/trunk/wtk/test/org/apache/pivot/wtk/test/action_mapping_test.wtkx
Removed:
    incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/WindowActionListener.java
Modified:
    incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/Menu.java
    incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/Window.java
    incubator/pivot/trunk/wtk/src/org/apache/pivot/wtkx/WTKXSerializer.java

Modified: incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/Menu.java
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/Menu.java?rev=797172&r1=797171&r2=797172&view=diff
==============================================================================
--- incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/Menu.java (original)
+++ incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/Menu.java Thu Jul 23 17:53:04 2009
@@ -96,22 +96,22 @@
             String previousName = this.name;
 
             if (name != previousName) {
-                this.name = name;
-
                 if (section != null
                     && section.menu != null) {
-                    if (name == null) {
-                        section.menu.itemMap.remove(name);
-                    } else {
-                        if (section.menu.itemMap.containsKey(name)) {
-                            throw new IllegalArgumentException("A menu item named \"" + name
-                                + "\" already exists.");
-                        }
+                    if (section.menu.itemMap.containsKey(name)) {
+                        throw new IllegalArgumentException("A menu item named \"" + name
+                            + "\" already exists.");
+                    }
 
-                        section.menu.itemMap.put(name, this);
+                    if (previousName != null) {
+                        section.menu.itemMap.remove(previousName);
                     }
+
+                    section.menu.itemMap.put(name, this);
                 }
 
+                this.name = name;
+
                 itemListeners.nameChanged(this, previousName);
             }
         }
@@ -242,21 +242,21 @@
             String previousName = this.name;
 
             if (name != previousName) {
-                this.name = name;
-
                 if (menu != null) {
-                    if (name == null) {
-                        menu.sectionMap.remove(name);
-                    } else {
-                        if (menu.sectionMap.containsKey(name)) {
-                            throw new IllegalArgumentException("A menu section named \"" + name
-                                + "\" already exists.");
-                        }
+                    if (menu.sectionMap.containsKey(name)) {
+                        throw new IllegalArgumentException("A menu section named \"" + name
+                            + "\" already exists.");
+                    }
 
-                        menu.sectionMap.put(name, this);
+                    if (previousName != null) {
+                        menu.sectionMap.remove(previousName);
                     }
+
+                    menu.sectionMap.put(name, this);
                 }
 
+                this.name = name;
+
                 sectionListeners.nameChanged(this, previousName);
             }
         }
@@ -508,6 +508,7 @@
 
     private ArrayList<Section> sections = new ArrayList<Section>();
     private SectionSequence sectionSequence = new SectionSequence();
+
     private HashMap<String, Section> sectionMap = new HashMap<String, Section>();
     private HashMap<String, Item> itemMap = new HashMap<String, Item>();
 

Modified: incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/Window.java
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/Window.java?rev=797172&r1=797171&r2=797172&view=diff
==============================================================================
--- incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/Window.java (original)
+++ incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/Window.java Thu Jul 23 17:53:04 2009
@@ -20,17 +20,14 @@
 
 import org.apache.pivot.collections.ArrayList;
 import org.apache.pivot.collections.ArrayStack;
-import org.apache.pivot.collections.Dictionary;
 import org.apache.pivot.collections.HashMap;
 import org.apache.pivot.collections.Sequence;
 import org.apache.pivot.util.ListenerList;
 import org.apache.pivot.util.ThreadUtilities;
 import org.apache.pivot.util.Vote;
 import org.apache.pivot.util.concurrent.TaskExecutionException;
-import org.apache.pivot.wtk.Keyboard.KeyStroke;
 import org.apache.pivot.wtk.media.Image;
 
-
 /**
  * Top-level container representing the entry point into a user interface.
  * Windows are direct descendants of the display.
@@ -39,58 +36,188 @@
  */
 public class Window extends Container {
     /**
-     * Action dictionary implementation.
+     * Class representing a mapping from keystrokes to actions.
      *
      * @author gbrown
      */
-    public final class ActionDictionary
-        implements Dictionary<Keyboard.KeyStroke, Action> {
-        private ActionDictionary() {
+    public static class ActionMapping {
+        private Window window = null;
+
+        private Keyboard.KeyStroke keyStroke = null;
+        private Action action = null;
+
+        public ActionMapping() {
+        }
+
+        public ActionMapping(Keyboard.KeyStroke keyStroke, Action action) {
+            setKeyStroke(keyStroke);
+            setAction(action);
+        }
+
+        public ActionMapping(Keyboard.KeyStroke keyStroke, String actionID) {
+            setKeyStroke(keyStroke);
+            setAction(actionID);
+        }
+
+        public Window getWindow() {
+            return window;
+        }
+
+        public Keyboard.KeyStroke getKeyStroke() {
+            return keyStroke;
+        }
+
+        public void setKeyStroke(Keyboard.KeyStroke keyStroke) {
+            if (keyStroke == null) {
+                throw new IllegalArgumentException("keyStroke is null.");
+            }
+
+            Keyboard.KeyStroke previousKeyStroke = this.keyStroke;
+
+            if (keyStroke != previousKeyStroke) {
+                if (window != null) {
+                    if (window.actionMap.containsKey(keyStroke)) {
+                        throw new IllegalArgumentException("A mapping for " + keyStroke
+                            + " already exists.");
+                    }
+
+                    // We can't simply enforce that keyStroke and action are
+                    // null when the mapping is added to the sequence, since
+                    // the mapping will be added before the attributes are
+                    // applied in WTKX
+                    if (previousKeyStroke != null) {
+                        window.actionMap.remove(previousKeyStroke);
+                    }
+
+                    if (keyStroke != null) {
+                        window.actionMap.put(keyStroke, action);
+                    }
+
+                    window.windowActionMappingListeners.keyStrokeChanged(this, previousKeyStroke);
+                }
+
+                this.keyStroke = keyStroke;
+            }
+        }
+
+        public void setKeyStroke(String keyStroke) {
+            if (keyStroke == null) {
+                throw new IllegalArgumentException("keyStroke is null.");
+            }
+
+            setKeyStroke(Keyboard.KeyStroke.decode(keyStroke));
         }
 
-        public Action get(Keyboard.KeyStroke keyStroke) {
-            return actions.get(keyStroke);
+        public Action getAction() {
+            return action;
         }
 
-        public Action put(Keyboard.KeyStroke keyStroke, Action value) {
-            boolean update = containsKey(keyStroke);
-            Action previousAction = actions.put(keyStroke, value);
+        public void setAction(Action action) {
+            Action previousAction = this.action;
 
-            if (update) {
-                windowActionListeners.actionUpdated(Window.this, keyStroke, previousAction);
+            if (action != previousAction) {
+                if (window != null) {
+                    if (keyStroke != null) {
+                        if (action == null) {
+                            window.actionMap.remove(keyStroke);
+                        } else {
+                            window.actionMap.put(keyStroke, action);
+                        }
+                    }
+
+                    window.windowActionMappingListeners.actionChanged(this, previousAction);
+                }
+
+                this.action = action;
             }
-            else {
-                windowActionListeners.actionAdded(Window.this, keyStroke);
+        }
+
+        public void setAction(String actionID) {
+            if (actionID == null) {
+                throw new IllegalArgumentException("actionID is null");
+            }
+
+            Action action = Action.getNamedActions().get(actionID);
+            if (action == null) {
+                throw new IllegalArgumentException("An action with ID "
+                    + actionID + " does not exist.");
             }
 
-            return previousAction;
+            setAction(action);
         }
+    }
 
-        public Action remove(Keyboard.KeyStroke keyStroke) {
-            Action action = null;
+    public class ActionMappingSequence implements Sequence<ActionMapping> {
+        public int add(ActionMapping actionMapping) {
+            if (actionMapping.window != null) {
+                throw new IllegalArgumentException("Action mapping already has a window.");
+            }
 
-            if (containsKey(keyStroke)) {
-                action = actions.remove(keyStroke);
-                windowActionListeners.actionRemoved(Window.this, keyStroke, action);
+            if (actionMapping.keyStroke == null
+                && actionMap.containsKey(actionMapping.keyStroke)) {
+                throw new IllegalArgumentException("A mapping for " + actionMapping.keyStroke
+                    + " already exists.");
             }
 
-            return action;
+            actionMapping.window = Window.this;
+
+            int index = actionMappings.add(actionMapping);
+            actionMap.put(actionMapping.keyStroke, actionMapping.action);
+
+            windowActionMappingListeners.actionMappingAdded(Window.this);
+
+            return index;
+        }
+
+        public void insert(ActionMapping actionMapping, int index) {
+            throw new UnsupportedOperationException();
+        }
+
+        public ActionMapping update(int index, ActionMapping actionMapping) {
+            throw new UnsupportedOperationException();
+        }
+
+        public int remove(ActionMapping actionMapping) {
+            int index = indexOf(actionMapping);
+
+            if (index >= 0) {
+               remove(index, 1);
+            }
+
+            return index;
+        }
+
+        public Sequence<ActionMapping> remove(int index, int count) {
+            Sequence<ActionMapping> removed = actionMappings.remove(index, count);
+
+            for (int i = 0, n = removed.getLength(); i < n; i++) {
+                ActionMapping actionMapping = removed.get(i);
+
+                actionMapping.window = null;
+
+                if (actionMapping.keyStroke != null) {
+                    actionMap.remove(actionMapping.keyStroke);
+                }
+            }
+
+            windowActionMappingListeners.actionMappingsRemoved(Window.this, index, removed);
+
+            return removed;
+        }
+
+        public ActionMapping get(int index) {
+            return actionMappings.get(index);
         }
 
-        public boolean containsKey(Keyboard.KeyStroke keyStroke) {
-            return actions.containsKey(keyStroke);
+        public int indexOf(ActionMapping actionMapping) {
+            return actionMappings.indexOf(actionMapping);
         }
 
-        public boolean isEmpty() {
-            return actions.isEmpty();
+        public int getLength() {
+            return actionMappings.getLength();
         }
     }
 
-    /**
-     * Window listener list.
-     *
-     * @author gbrown
-     */
     private static class WindowListenerList extends ListenerList<WindowListener>
         implements WindowListener {
         public void titleChanged(Window window, String previousTitle) {
@@ -136,11 +263,6 @@
         }
     }
 
-    /**
-     * Window state listener list.
-     *
-     * @author gbrown
-     */
     private static class WindowStateListenerList extends ListenerList<WindowStateListener>
         implements WindowStateListener {
         public Vote previewWindowOpen(Window window, Display display) {
@@ -188,32 +310,33 @@
         }
     }
 
-    private static class WindowActionListenerList extends ListenerList<WindowActionListener>
-        implements WindowActionListener {
-        public void actionAdded(Window window, KeyStroke keyStroke) {
-            for (WindowActionListener listener : this) {
-                listener.actionAdded(window, keyStroke);
+    private static class WindowActionMappingListenerList extends ListenerList<WindowActionMappingListener>
+        implements WindowActionMappingListener {
+        public void actionMappingAdded(Window window) {
+            for (WindowActionMappingListener listener : this) {
+                listener.actionMappingAdded(window);
             }
         }
 
-        public void actionUpdated(Window window, KeyStroke keyStroke, Action previousAction) {
-            for (WindowActionListener listener : this) {
-                listener.actionUpdated(window, keyStroke, previousAction);
+        public void actionMappingsRemoved(Window window, int index, Sequence<Window.ActionMapping> removed) {
+            for (WindowActionMappingListener listener : this) {
+                listener.actionMappingsRemoved(window, index, removed);
             }
         }
 
-        public void actionRemoved(Window window, KeyStroke keyStroke, Action action) {
-            for (WindowActionListener listener : this) {
-                listener.actionRemoved(window, keyStroke, action);
+        public void keyStrokeChanged(Window.ActionMapping actionMapping, Keyboard.KeyStroke previousKeyStroke) {
+            for (WindowActionMappingListener listener : this) {
+                listener.keyStrokeChanged(actionMapping, previousKeyStroke);
+            }
+        }
+
+        public void actionChanged(Window.ActionMapping actionMapping, Action previousAction) {
+            for (WindowActionMappingListener listener : this) {
+                listener.actionChanged(actionMapping, previousAction);
             }
         }
     }
 
-    /**
-     * Window class listener list.
-     *
-     * @author tvolkert
-     */
     private static class WindowClassListenerList
         extends ListenerList<WindowClassListener>
         implements WindowClassListener {
@@ -229,8 +352,10 @@
     private Window owner = null;
     private ArrayList<Window> ownedWindows = new ArrayList<Window>();
 
-    private HashMap<Keyboard.KeyStroke, Action> actions = new HashMap<Keyboard.KeyStroke, Action>();
-    private ActionDictionary actionDictionary = new ActionDictionary();
+    private ArrayList<ActionMapping> actionMappings = new ArrayList<ActionMapping>();
+    private ActionMappingSequence actionMappingSequence = new ActionMappingSequence();
+
+    private HashMap<Keyboard.KeyStroke, Action> actionMap = new HashMap<Keyboard.KeyStroke, Action>();
 
     private String title = null;
     private Image icon = null;
@@ -244,7 +369,8 @@
 
     private WindowListenerList windowListeners = new WindowListenerList();
     private WindowStateListenerList windowStateListeners = new WindowStateListenerList();
-    private WindowActionListenerList windowActionListeners = new WindowActionListenerList();
+    private WindowActionMappingListenerList windowActionMappingListeners = new WindowActionMappingListenerList();
+
     private static WindowClassListenerList windowClassListeners = new WindowClassListenerList();
 
     private static Window activeWindow = null;
@@ -793,10 +919,10 @@
     }
 
     /**
-     * Returns the global action map for this window.
+     * Returns the action mappings for this window.
      */
-    public ActionDictionary getActions() {
-        return actionDictionary;
+    public ActionMappingSequence getActionMappings() {
+        return actionMappingSequence;
     }
 
     /**
@@ -1017,7 +1143,7 @@
         Keyboard.KeyStroke keyStroke = new Keyboard.KeyStroke(keyCode,
             Keyboard.getModifiers());
 
-        Action action = actions.get(keyStroke);
+        Action action = actionMap.get(keyStroke);
         if (action != null
             && action.isEnabled()) {
             action.perform();
@@ -1034,8 +1160,8 @@
         return windowStateListeners;
     }
 
-    public ListenerList<WindowActionListener> getWindowActionListeners() {
-        return windowActionListeners;
+    public ListenerList<WindowActionMappingListener> getWindowActionMappingListeners() {
+        return windowActionMappingListeners;
     }
 
     public static ListenerList<WindowClassListener> getWindowClassListeners() {

Added: incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/WindowActionMappingListener.java
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/WindowActionMappingListener.java?rev=797172&view=auto
==============================================================================
--- incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/WindowActionMappingListener.java (added)
+++ incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/WindowActionMappingListener.java Thu Jul 23 17:53:04 2009
@@ -0,0 +1,58 @@
+/*
+ * 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.wtk;
+
+import org.apache.pivot.collections.Sequence;
+
+/**
+ * Window action mapping listener interface.
+ *
+ * @author gbrown
+ */
+public interface WindowActionMappingListener {
+    /**
+     * Called when an action mapping has been added to a window.
+     *
+     * @param window
+     */
+    public void actionMappingAdded(Window window);
+
+    /**
+     * Called when action mappings have been removed from a window.
+     *
+     * @param window
+     * @param index
+     * @param removed
+     */
+    public void actionMappingsRemoved(Window window, int index, Sequence<Window.ActionMapping> removed);
+
+    /**
+     * Called when an action mapping's keystroke has changed.
+     *
+     * @param actionMapping
+     * @param previousKeyStroke
+     */
+    public void keyStrokeChanged(Window.ActionMapping actionMapping, Keyboard.KeyStroke previousKeyStroke);
+
+    /**
+     * Called when an action mapping's action has changed.
+     *
+     * @param actionMapping
+     * @param previousAction
+     */
+    public void actionChanged(Window.ActionMapping actionMapping, Action previousAction);
+}

Modified: incubator/pivot/trunk/wtk/src/org/apache/pivot/wtkx/WTKXSerializer.java
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/wtk/src/org/apache/pivot/wtkx/WTKXSerializer.java?rev=797172&r1=797171&r2=797172&view=diff
==============================================================================
--- incubator/pivot/trunk/wtk/src/org/apache/pivot/wtkx/WTKXSerializer.java (original)
+++ incubator/pivot/trunk/wtk/src/org/apache/pivot/wtkx/WTKXSerializer.java Thu Jul 23 17:53:04 2009
@@ -326,541 +326,552 @@
         element = null;
 
         try {
-            XMLStreamReader reader = xmlInputFactory.createXMLStreamReader(inputStream);
+            try {
+                XMLStreamReader reader = xmlInputFactory.createXMLStreamReader(inputStream);
 
-            while (reader.hasNext()) {
-                int event = reader.next();
+                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;
+                    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);
+                                                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;
-                                    }
+                                            break;
+                                        }
 
-                                    case SCRIPT:
-                                    case WRITABLE_PROPERTY: {
-                                        element.value = text;
-                                        break;
-                                    }
+                                        case SCRIPT:
+                                        case WRITABLE_PROPERTY: {
+                                            element.value = text;
+                                            break;
+                                        }
 
-                                    default: {
-                                        throw new SerializationException("Unexpected characters in "
-                                            + element.type + " element.");
+                                        default: {
+                                            throw new SerializationException("Unexpected characters in "
+                                                + element.type + " element.");
+                                        }
                                     }
                                 }
                             }
-                        }
-
-                        break;
-                    }
-
-                    case XMLStreamConstants.START_ELEMENT: {
-                        // Get element properties
-                        String namespaceURI = reader.getNamespaceURI();
-                        String prefix = reader.getPrefix();
-                        String localName = reader.getLocalName();
-
-                        // Build attribute list; these will be processed in the close tag
-                        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);
-
-                            attributes.add(new Attribute(attributeNamespaceURI,
-                                attributePrefix, attributeLocalName, attributeValue));
+                            break;
                         }
 
-                        // Determine the type and value of this element
-                        Element.Type elementType = null;
-                        Object value = null;
-
-                        if (prefix != null
-                            && prefix.equals(WTKX_PREFIX)) {
-                            // The element represents a WTKX operation
-                            if (element == null) {
-                                throw new SerializationException(prefix + ":" + localName
-                                    + " is not a valid root element.");
-                            }
-
-                            if (localName.equals(INCLUDE_TAG)) {
-                                elementType = Element.Type.INCLUDE;
-                            } else if (localName.equals(SCRIPT_TAG)) {
-                                elementType = Element.Type.SCRIPT;
-                            } else if (localName.equals(DEFINE_TAG)) {
-                                if (attributes.getLength() > 0) {
-                                    throw new SerializationException(WTKX_PREFIX + ":" + DEFINE_TAG
-                                        + " cannot have attributes.");
+                        case XMLStreamConstants.START_ELEMENT: {
+                            // Get element properties
+                            String namespaceURI = reader.getNamespaceURI();
+                            String prefix = reader.getPrefix();
+                            String localName = reader.getLocalName();
+
+                            // Build attribute list; these will be processed in the close tag
+                            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("");
                                 }
 
-                                elementType = Element.Type.DEFINE;
-                            } else {
-                                throw new SerializationException(prefix + ":" + localName
-                                    + " is not a valid element.");
+                                String attributePrefix = reader.getAttributePrefix(i);
+                                String attributeLocalName = reader.getAttributeLocalName(i);
+                                String attributeValue = reader.getAttributeValue(i);
+
+                                attributes.add(new Attribute(attributeNamespaceURI,
+                                    attributePrefix, attributeLocalName, attributeValue));
                             }
-                        } 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.");
+
+                            // Determine the type and value of this element
+                            Element.Type elementType = null;
+                            Object value = null;
+
+                            if (prefix != null
+                                && prefix.equals(WTKX_PREFIX)) {
+                                // The element represents a WTKX operation
+                                if (element == null) {
+                                    throw new SerializationException(prefix + ":" + localName
+                                        + " is not a valid root element.");
                                 }
 
-                                String className = namespaceURI + "." + localName.replace('.', '$');
+                                if (localName.equals(INCLUDE_TAG)) {
+                                    elementType = Element.Type.INCLUDE;
+                                } else if (localName.equals(SCRIPT_TAG)) {
+                                    elementType = Element.Type.SCRIPT;
+                                } else if (localName.equals(DEFINE_TAG)) {
+                                    if (attributes.getLength() > 0) {
+                                        throw new SerializationException(WTKX_PREFIX + ":" + DEFINE_TAG
+                                            + " cannot have attributes.");
+                                    }
 
-                                try {
-                                    Class<?> type = Class.forName(className);
-                                    elementType = Element.Type.INSTANCE;
-                                    value = type.newInstance();
-                                } catch (Exception exception) {
-                                    throw new SerializationException(exception);
+                                    elementType = Element.Type.DEFINE;
+                                } else {
+                                    throw new SerializationException(prefix + ":" + localName
+                                        + " is not a valid element.");
                                 }
                             } else {
-                                // The element represents a property
-                                if (element == null
-                                    || element.type != Element.Type.INSTANCE) {
-                                    throw new SerializationException("Parent element must be a typed object.");
-                                }
-
-                                if (prefix != null
-                                    && prefix.length() > 0) {
-                                    throw new SerializationException("Property elements cannot have a namespace prefix.");
-                                }
+                                if (Character.isUpperCase(localName.charAt(0))) {
+                                    // The element represents a typed object
+                                    if (namespaceURI == null) {
+                                        throw new SerializationException("No XML namespace specified for "
+                                            + localName + " tag.");
+                                    }
 
-                                BeanDictionary beanDictionary = new BeanDictionary(element.value);
+                                    String className = namespaceURI + "." + localName.replace('.', '$');
 
-                                if (beanDictionary.isReadOnly(localName)) {
-                                    elementType = Element.Type.READ_ONLY_PROPERTY;
-                                    value = beanDictionary.get(localName);
-                                    assert (value != null) : "Read-only properties cannot be null.";
-
-                                    if (attributes.getLength() > 0
-                                        && !(value instanceof Dictionary<?, ?>)) {
-                                        throw new SerializationException("Only read-only dictionaries can have attributes.");
+                                    try {
+                                        Class<?> type = Class.forName(className);
+                                        elementType = Element.Type.INSTANCE;
+                                        value = type.newInstance();
+                                    } catch (Exception exception) {
+                                        throw new SerializationException(exception);
                                     }
                                 } else {
-                                    if (attributes.getLength() > 0) {
-                                        throw new SerializationException("Writable property elements cannot have attributes.");
+                                    // The element represents a property
+                                    if (element == null
+                                        || element.type != Element.Type.INSTANCE) {
+                                        throw new SerializationException("Parent element must be a typed object.");
+                                    }
+
+                                    if (prefix != null
+                                        && prefix.length() > 0) {
+                                        throw new SerializationException("Property elements cannot have a namespace prefix.");
                                     }
 
-                                    elementType = Element.Type.WRITABLE_PROPERTY;
+                                    BeanDictionary beanDictionary = new BeanDictionary(element.value);
+
+                                    if (beanDictionary.isReadOnly(localName)) {
+                                        elementType = Element.Type.READ_ONLY_PROPERTY;
+                                        value = beanDictionary.get(localName);
+                                        assert (value != null) : "Read-only properties cannot be null.";
+
+                                        if (attributes.getLength() > 0
+                                            && !(value instanceof Dictionary<?, ?>)) {
+                                            throw new SerializationException("Only read-only dictionaries can have attributes.");
+                                        }
+                                    } else {
+                                        if (attributes.getLength() > 0) {
+                                            throw new SerializationException("Writable property elements cannot have attributes.");
+                                        }
+
+                                        elementType = Element.Type.WRITABLE_PROPERTY;
+                                    }
                                 }
                             }
-                        }
-
-                        // Set the current element
-                        String tagName = localName;
-                        if (prefix != null
-                            && prefix.length() > 0) {
-                            tagName = prefix + ":" + tagName;
-                        }
 
-                        Location xmlStreamLocation = reader.getLocation();
-                        element = new Element(element, elementType, tagName, xmlStreamLocation.getLineNumber(),
-                            attributes, value);
-
-                        // If this is the root, set it
-                        if (element.parent == null) {
-                            root = element.value;
-                        }
+                            // Set the current element
+                            String tagName = localName;
+                            if (prefix != null
+                                && prefix.length() > 0) {
+                                tagName = prefix + ":" + tagName;
+                            }
 
-                        break;
-                    }
+                            Location xmlStreamLocation = reader.getLocation();
+                            element = new Element(element, elementType, tagName, xmlStreamLocation.getLineNumber(),
+                                attributes, value);
+
+                            // If this is the root, set it
+                            if (element.parent == null) {
+                                root = element.value;
+                            }
 
-                    case XMLStreamConstants.END_ELEMENT: {
-                        String localName = reader.getLocalName();
+                            break;
+                        }
 
-                        switch (element.type) {
-                            case INSTANCE:
-                            case INCLUDE: {
-                                String id = null;
-                                ArrayList<Attribute> attributes = new ArrayList<Attribute>();
-
-                                if (element.type == Element.Type.INCLUDE) {
-                                    // Process attributes looking for wtkx:id, src, resources, asynchronous,
-                                    // and static property setters only
-                                    String src = null;
-                                    Resources resources = this.resources;
+                        case XMLStreamConstants.END_ELEMENT: {
+                            String localName = reader.getLocalName();
 
-                                    for (Attribute attribute : element.attributes) {
-                                        if (attribute.prefix != null
-                                            && attribute.prefix.equals(WTKX_PREFIX)) {
-                                            if (attribute.localName.equals(ID_ATTRIBUTE)) {
-                                                id = attribute.value;
+                            switch (element.type) {
+                                case INSTANCE:
+                                case INCLUDE: {
+                                    String id = null;
+                                    ArrayList<Attribute> attributes = new ArrayList<Attribute>();
+
+                                    if (element.type == Element.Type.INCLUDE) {
+                                        // Process attributes looking for wtkx:id, src, resources, asynchronous,
+                                        // and static property setters only
+                                        String src = null;
+                                        Resources resources = this.resources;
+
+                                        for (Attribute attribute : element.attributes) {
+                                            if (attribute.prefix != null
+                                                && attribute.prefix.equals(WTKX_PREFIX)) {
+                                                if (attribute.localName.equals(ID_ATTRIBUTE)) {
+                                                    id = attribute.value;
+                                                } else {
+                                                    throw new SerializationException(WTKX_PREFIX + ":" + attribute.localName
+                                                        + " is not a valid attribute.");
+                                                }
                                             } else {
-                                                throw new SerializationException(WTKX_PREFIX + ":" + attribute.localName
-                                                    + " is not a valid attribute.");
+                                                if (attribute.localName.equals(INCLUDE_SRC_ATTRIBUTE)) {
+                                                    src = attribute.value;
+                                                } else if (attribute.localName.equals(INCLUDE_RESOURCES_ATTRIBUTE)) {
+                                                    resources = new Resources(attribute.value);
+                                                } else if (attribute.localName.equals(INCLUDE_ASYNCHRONOUS_ATTRIBUTE)) {
+                                                    // TODO
+                                                    throw new UnsupportedOperationException("Asynchronous includes are not"
+                                                        + " yet supported.");
+                                                } else {
+                                                    if (attribute.namespaceURI == null) {
+                                                        throw new SerializationException("Instance property setters are not"
+                                                            + " supported for " + WTKX_PREFIX + ":" + INCLUDE_TAG
+                                                            + " " + " tag.");
+                                                    }
+
+                                                    attributes.add(attribute);
+                                                }
                                             }
+                                        }
+
+                                        if (src == null) {
+                                            throw new SerializationException(INCLUDE_SRC_ATTRIBUTE
+                                                + " attribute is required for " + WTKX_PREFIX + ":" + INCLUDE_TAG
+                                                + " tag.");
+                                        }
+
+                                        // Read the object
+                                        WTKXSerializer serializer = new WTKXSerializer(resources);
+                                        if (id != null) {
+                                            includeSerializers.put(id, serializer);
+                                        }
+
+                                        if (src.charAt(0) == '/') {
+                                            element.value = serializer.readObject(src.substring(1));
                                         } else {
-                                            if (attribute.localName.equals(INCLUDE_SRC_ATTRIBUTE)) {
-                                                src = attribute.value;
-                                            } else if (attribute.localName.equals(INCLUDE_RESOURCES_ATTRIBUTE)) {
-                                                resources = new Resources(attribute.value);
-                                            } else if (attribute.localName.equals(INCLUDE_ASYNCHRONOUS_ATTRIBUTE)) {
-                                                // TODO
-                                                throw new UnsupportedOperationException("Asynchronous includes are not"
-                                                    + " yet supported.");
-                                            } else {
-                                                if (attribute.namespaceURI == null) {
-                                                    throw new SerializationException("Instance property setters are not"
-                                                        + " supported for " + WTKX_PREFIX + ":" + INCLUDE_TAG
-                                                        + " " + " tag.");
-                                                }
+                                            element.value = serializer.readObject(new URL(location, src));
+                                        }
 
+                                        if (id == null
+                                            && !serializer.isEmpty()) {
+                                            System.err.println("Include \"" + src + "\" is missing an " + ID_ATTRIBUTE
+                                                + " attribute.");
+                                        }
+                                    } else {
+                                        // Process attributes looking for wtkx:id and all property setters
+                                        for (Attribute attribute : element.attributes) {
+                                            if (attribute.prefix != null
+                                                && attribute.prefix.equals(WTKX_PREFIX)) {
+                                                if (attribute.localName.equals(ID_ATTRIBUTE)) {
+                                                    id = attribute.value;
+                                                } else {
+                                                    throw new SerializationException(WTKX_PREFIX + ":" + attribute.localName
+                                                        + " is not a valid attribute.");
+                                                }
+                                            } else {
                                                 attributes.add(attribute);
                                             }
                                         }
                                     }
 
-                                    if (src == null) {
-                                        throw new SerializationException(INCLUDE_SRC_ATTRIBUTE
-                                            + " attribute is required for " + WTKX_PREFIX + ":" + INCLUDE_TAG
-                                            + " tag.");
+                                    // If an ID was specified, add the value to the named object map
+                                    if (id != null) {
+                                        if (id.length() == 0) {
+                                            throw new IllegalArgumentException(WTKX_PREFIX + ":" + ID_ATTRIBUTE
+                                                + " must not be null.");
+                                        }
+
+                                        namedObjects.put(id, element.value);
                                     }
 
-                                    // Read the object
-                                    WTKXSerializer serializer = new WTKXSerializer(resources);
-                                    if (id != null) {
-                                        includeSerializers.put(id, serializer);
+                                    // 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 (src.charAt(0) == '/') {
-                                        element.value = serializer.readObject(src.substring(1));
+                                    // Apply remaining attributes
+                                    if (element.value instanceof Dictionary) {
+                                        // The element is already a dictionary
+                                        Dictionary<String, Object> dictionary = (Dictionary<String, Object>)element.value;
+
+                                        for (Attribute attribute : attributes) {
+                                            if (Character.isUpperCase(attribute.localName.charAt(0))) {
+                                                throw new SerializationException("Static setters are only supported"
+                                                    + " for typed objects.");
+                                            }
+
+                                            // Resolve and apply the attribute
+                                            dictionary.put(attribute.localName, resolve(attribute.value, null));
+                                        }
                                     } else {
-                                        element.value = serializer.readObject(new URL(location, src));
-                                    }
+                                        // The element is not a dictionary; wrap it in a bean dictionary
+                                        BeanDictionary beanDictionary = new BeanDictionary(element.value);
 
-                                    if (id == null
-                                        && !serializer.isEmpty()) {
-                                        System.err.println("Include \"" + src + "\" is missing an " + ID_ATTRIBUTE
-                                            + " attribute.");
-                                    }
-                                } else {
-                                    // Process attributes looking for wtkx:id and all property setters
-                                    for (Attribute attribute : element.attributes) {
-                                        if (attribute.prefix != null
-                                            && attribute.prefix.equals(WTKX_PREFIX)) {
-                                            if (attribute.localName.equals(ID_ATTRIBUTE)) {
-                                                id = attribute.value;
+                                        for (Attribute attribute : attributes) {
+                                            if (Character.isUpperCase(attribute.localName.charAt(0))) {
+                                                // The property represents an attached value
+                                                setStaticProperty(attribute, element.value);
                                             } else {
-                                                throw new SerializationException(WTKX_PREFIX + ":" + attribute.localName
-                                                    + " is not a valid attribute.");
+                                                beanDictionary.put(attribute.localName,
+                                                    resolve(attribute.value, beanDictionary.getType(attribute.localName)));
                                             }
-                                        } else {
-                                            attributes.add(attribute);
                                         }
                                     }
-                                }
 
-                                // If an ID was specified, add the value to the named object map
-                                if (id != null) {
-                                    if (id.length() == 0) {
-                                        throw new IllegalArgumentException(WTKX_PREFIX + ":" + ID_ATTRIBUTE
-                                            + " must not be null.");
+                                    // If the parent element is a writable property or a define, 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.type == Element.Type.DEFINE)) {
+                                        element.parent.value = element.value;
                                     }
 
-                                    namedObjects.put(id, element.value);
+                                    break;
                                 }
 
-                                // 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);
-                                        }
-                                    }
-                                }
+                                case READ_ONLY_PROPERTY: {
+                                    if (element.value instanceof Dictionary<?, ?>) {
+                                        // Process attributes looking for instance property setters
+                                        for (Attribute attribute : element.attributes) {
+                                            if (Character.isUpperCase(attribute.localName.charAt(0))) {
+                                                throw new SerializationException("Static setters are not supported"
+                                                    + " for read-only properties.");
+                                            }
 
-                                // Apply remaining attributes
-                                if (element.value instanceof Dictionary) {
-                                    // The element is already a dictionary
-                                    Dictionary<String, Object> dictionary = (Dictionary<String, Object>)element.value;
-
-                                    for (Attribute attribute : attributes) {
-                                        if (Character.isUpperCase(attribute.localName.charAt(0))) {
-                                            throw new SerializationException("Static setters are only supported"
-                                                + " for typed objects.");
+                                            Dictionary<String, Object> dictionary =
+                                                (Dictionary<String, Object>)element.value;
+                                            dictionary.put(attribute.localName, resolve(attribute.value, null));
                                         }
-
-                                        // Resolve and apply the attribute
-                                        dictionary.put(attribute.localName, resolve(attribute.value, null));
                                     }
-                                } else {
-                                    // The element is not a dictionary; wrap it in a bean dictionary
-                                    BeanDictionary beanDictionary = new BeanDictionary(element.value);
 
-                                    for (Attribute attribute : attributes) {
-                                        if (Character.isUpperCase(attribute.localName.charAt(0))) {
-                                            // The property represents an attached value
-                                            setStaticProperty(attribute, element.value);
-                                        } else {
-                                            beanDictionary.put(attribute.localName,
-                                                resolve(attribute.value, beanDictionary.getType(attribute.localName)));
-                                        }
-                                    }
+                                    break;
                                 }
 
-                                // If the parent element is a writable property or a define, 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.type == Element.Type.DEFINE)) {
-                                    element.parent.value = element.value;
+                                case WRITABLE_PROPERTY: {
+                                    BeanDictionary beanDictionary = new BeanDictionary(element.parent.value);
+                                    beanDictionary.put(localName, element.value);
+                                    break;
                                 }
 
-                                break;
-                            }
+                                case SCRIPT: {
+                                    // Load the script engine manager
+                                    if (scriptEngineManager == null) {
+                                        scriptEngineManager = new javax.script.ScriptEngineManager();
+                                        scriptEngineManager.setBindings(new NamedObjectBindings());
+                                    }
 
-                            case READ_ONLY_PROPERTY: {
-                                if (element.value instanceof Dictionary<?, ?>) {
-                                    // Process attributes looking for instance property setters
+                                    // Process attributes looking for src and language
+                                    String src = null;
+                                    String language = null;
                                     for (Attribute attribute : element.attributes) {
-                                        if (Character.isUpperCase(attribute.localName.charAt(0))) {
-                                            throw new SerializationException("Static setters are not supported"
-                                                + " for read-only properties.");
+                                        if (attribute.prefix != null
+                                            && attribute.prefix.length() > 0) {
+                                            throw new SerializationException(attribute.prefix + ":" +
+                                                attribute.localName + " is not a valid" + " attribute for the "
+                                                + WTKX_PREFIX + ":" + SCRIPT_TAG + " tag.");
                                         }
 
-                                        Dictionary<String, Object> dictionary =
-                                            (Dictionary<String, Object>)element.value;
-                                        dictionary.put(attribute.localName, resolve(attribute.value, null));
+                                        if (attribute.localName.equals(SCRIPT_SRC_ATTRIBUTE)) {
+                                            src = attribute.value;
+                                        } else if (attribute.localName.equals(SCRIPT_LANGUAGE_ATTRIBUTE)) {
+                                            language = attribute.value;
+                                        } else {
+                                            throw new SerializationException(attribute.localName + " is not a valid"
+                                                + " attribute for the " + WTKX_PREFIX + ":" + SCRIPT_TAG + " tag.");
+                                        }
                                     }
-                                }
-
-                                break;
-                            }
 
-                            case WRITABLE_PROPERTY: {
-                                BeanDictionary beanDictionary = new BeanDictionary(element.parent.value);
-                                beanDictionary.put(localName, element.value);
-                                break;
-                            }
-
-                            case SCRIPT: {
-                                // Load the script engine manager
-                                if (scriptEngineManager == null) {
-                                    scriptEngineManager = new javax.script.ScriptEngineManager();
-                                    scriptEngineManager.setBindings(new NamedObjectBindings());
-                                }
-
-                                // Process attributes looking for src and language
-                                String src = null;
-                                String language = null;
-                                for (Attribute attribute : element.attributes) {
-                                    if (attribute.prefix != null
-                                        && attribute.prefix.length() > 0) {
-                                        throw new SerializationException(attribute.prefix + ":" +
-                                            attribute.localName + " is not a valid" + " attribute for the "
-                                            + WTKX_PREFIX + ":" + SCRIPT_TAG + " tag.");
+                                    if (element.value != null
+                                        && language == null) {
+                                        language = "javascript";
                                     }
 
-                                    if (attribute.localName.equals(SCRIPT_SRC_ATTRIBUTE)) {
-                                        src = attribute.value;
-                                    } else if (attribute.localName.equals(SCRIPT_LANGUAGE_ATTRIBUTE)) {
-                                        language = attribute.value;
+                                    Bindings bindings;
+                                    if (element.parent.value instanceof ListenerList<?>) {
+                                        // Don't pollute the engine namespace with the listener functions
+                                        bindings = new SimpleBindings();
                                     } else {
-                                        throw new SerializationException(attribute.localName + " is not a valid"
-                                            + " attribute for the " + WTKX_PREFIX + ":" + SCRIPT_TAG + " tag.");
+                                        bindings = scriptEngineManager.getBindings();
                                     }
-                                }
 
-                                if (element.value != null
-                                    && language == null) {
-                                    language = "javascript";
-                                }
+                                    // Execute script
+                                    final ScriptEngine scriptEngine;
 
-                                Bindings bindings;
-                                if (element.parent.value instanceof ListenerList<?>) {
-                                    // Don't pollute the engine namespace with the listener functions
-                                    bindings = new SimpleBindings();
-                                } else {
-                                    bindings = scriptEngineManager.getBindings();
-                                }
-
-                                // Execute script
-                                final ScriptEngine scriptEngine;
+                                    if (src != null) {
+                                        // The script is located in an external file
+                                        if (language != null) {
+                                            throw new SerializationException("Cannot combine " + SCRIPT_SRC_ATTRIBUTE
+                                                + " and " + SCRIPT_LANGUAGE_ATTRIBUTE + " in a "
+                                                + WTKX_PREFIX + ":" + SCRIPT_TAG + " tag.");
+                                        }
 
-                                if (src != null) {
-                                    // The script is located in an external file
-                                    if (language != null) {
-                                        throw new SerializationException("Cannot combine " + SCRIPT_SRC_ATTRIBUTE
-                                            + " and " + SCRIPT_LANGUAGE_ATTRIBUTE + " in a "
-                                            + WTKX_PREFIX + ":" + SCRIPT_TAG + " tag.");
-                                    }
+                                        int i = src.lastIndexOf(".");
+                                        if (i == -1) {
+                                            throw new SerializationException("Cannot determine type of script \""
+                                                + src + "\".");
+                                        }
 
-                                    int i = src.lastIndexOf(".");
-                                    if (i == -1) {
-                                        throw new SerializationException("Cannot determine type of script \""
-                                            + src + "\".");
-                                    }
+                                        String extension = src.substring(i + 1);
+                                        scriptEngine = scriptEngineManager.getEngineByExtension(extension);
 
-                                    String extension = src.substring(i + 1);
-                                    scriptEngine = scriptEngineManager.getEngineByExtension(extension);
+                                        if (scriptEngine == null) {
+                                            throw new SerializationException("Unable to find scripting engine for"
+                                                + " extension " + extension + ".");
+                                        }
 
-                                    if (scriptEngine == null) {
-                                        throw new SerializationException("Unable to find scripting engine for"
-                                            + " extension " + extension + ".");
-                                    }
+                                        scriptEngine.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
 
-                                    scriptEngine.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
+                                        try {
+                                            URL scriptLocation;
+                                            if (src.charAt(0) == '/') {
+                                                ClassLoader classLoader = ThreadUtilities.getClassLoader();
+                                                scriptLocation = classLoader.getResource(src);
+                                            } else {
+                                                scriptLocation = new URL(location, src);
+                                            }
 
-                                    try {
-                                        URL scriptLocation;
-                                        if (src.charAt(0) == '/') {
-                                            ClassLoader classLoader = ThreadUtilities.getClassLoader();
-                                            scriptLocation = classLoader.getResource(src);
-                                        } else {
-                                            scriptLocation = new URL(location, src);
+                                            BufferedReader scriptReader = null;
+                                            try {
+                                                scriptReader = new BufferedReader(new InputStreamReader(scriptLocation.openStream()));
+                                                scriptEngine.eval(scriptReader);
+                                            } catch(ScriptException exception) {
+                                                System.err.println(exception.getMessage());
+                                            } finally {
+                                                if (scriptReader != null) {
+                                                    scriptReader.close();
+                                                }
+                                            }
+                                        } catch (IOException exception) {
+                                            throw new SerializationException(exception);
+                                        }
+                                    } else if (language != null) {
+                                        // The script is inline
+                                        scriptEngine = scriptEngineManager.getEngineByName(language);
+
+                                        if (scriptEngine == null) {
+                                            throw new SerializationException("Unable to find scripting engine for"
+                                                + " language " + language + ".");
                                         }
 
-                                        BufferedReader scriptReader = null;
-                                        try {
-                                            scriptReader = new BufferedReader(new InputStreamReader(scriptLocation.openStream()));
-                                            scriptEngine.eval(scriptReader);
-                                        } catch(ScriptException exception) {
-                                            System.err.println(exception.getMessage());
-                                        } finally {
-                                            if (scriptReader != null) {
-                                                scriptReader.close();
+                                        scriptEngine.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
+
+                                        if (element.value != null) {
+                                            try {
+                                                scriptEngine.eval((String)element.value);
+                                            } catch (ScriptException exception) {
+                                                System.err.println(exception.getMessage());
                                             }
                                         }
-                                    } catch (IOException exception) {
-                                        throw new SerializationException(exception);
-                                    }
-                                } else if (language != null) {
-                                    // The script is inline
-                                    scriptEngine = scriptEngineManager.getEngineByName(language);
-
-                                    if (scriptEngine == null) {
-                                        throw new SerializationException("Unable to find scripting engine for"
-                                            + " language " + language + ".");
+                                    } else {
+                                        throw new SerializationException("Either " + SCRIPT_SRC_ATTRIBUTE + " or "
+                                            + SCRIPT_LANGUAGE_ATTRIBUTE + " is required for the "
+                                            + WTKX_PREFIX + ":" + SCRIPT_TAG + " tag.");
                                     }
 
-                                    scriptEngine.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
+                                    if (element.parent.value instanceof ListenerList<?>) {
+                                        // Create an invocation handler for this listener
+                                        Class<?> listenerListClass = element.parent.value.getClass();
 
-                                    if (element.value != null) {
+                                        Method addMethod;
                                         try {
-                                            scriptEngine.eval((String)element.value);
-                                        } catch (ScriptException exception) {
-                                            System.err.println(exception.getMessage());
+                                            addMethod = listenerListClass.getMethod("add",
+                                                new Class<?>[] {Object.class});
+                                        } catch (NoSuchMethodException exception) {
+                                            throw new RuntimeException(exception);
                                         }
-                                    }
-                                } else {
-                                    throw new SerializationException("Either " + SCRIPT_SRC_ATTRIBUTE + " or "
-                                        + SCRIPT_LANGUAGE_ATTRIBUTE + " is required for the "
-                                        + WTKX_PREFIX + ":" + SCRIPT_TAG + " tag.");
-                                }
 
-                                if (element.parent.value instanceof ListenerList<?>) {
-                                    // Create an invocation handler for this listener
-                                    Class<?> listenerListClass = element.parent.value.getClass();
+                                        InvocationHandler handler = new InvocationHandler() {
+                                            public Object invoke(Object proxy, Method method, Object[] args)
+                                                throws Throwable {
+                                                Object result = null;
+
+                                                String methodName = method.getName();
+                                                Bindings bindings = scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE);
+                                                if (bindings.containsKey(methodName)) {
+                                                    Invocable invocable;
+                                                    try {
+                                                        invocable = (Invocable)scriptEngine;
+                                                    } catch (ClassCastException exception) {
+                                                        throw new SerializationException(exception);
+                                                    }
 
-                                    Method addMethod;
-                                    try {
-                                        addMethod = listenerListClass.getMethod("add",
-                                            new Class<?>[] {Object.class});
-                                    } catch (NoSuchMethodException exception) {
-                                        throw new RuntimeException(exception);
-                                    }
+                                                    result = invocable.invokeFunction(methodName, args);
+                                                }
 
-                                    InvocationHandler handler = new InvocationHandler() {
-                                        public Object invoke(Object proxy, Method method, Object[] args)
-                                            throws Throwable {
-                                            Object result = null;
-
-                                            String methodName = method.getName();
-                                            Bindings bindings = scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE);
-                                            if (bindings.containsKey(methodName)) {
-                                                Invocable invocable;
-                                                try {
-                                                    invocable = (Invocable)scriptEngine;
-                                                } catch (ClassCastException exception) {
-                                                    throw new SerializationException(exception);
+                                                // If the function didn't return a value, return the default
+                                                Class<?> returnType = method.getReturnType();
+                                                if (returnType == Vote.class
+                                                    && result == null) {
+                                                    result = Vote.APPROVE;
+                                                } else if (returnType == Boolean.TYPE
+                                                    && result == null) {
+                                                    result = false;
                                                 }
 
-                                                result = invocable.invokeFunction(methodName, args);
+                                                return result;
                                             }
+                                        };
 
-                                            // If the function didn't return a value, return the default
-                                            Class<?> returnType = method.getReturnType();
-                                            if (returnType == Vote.class
-                                                && result == null) {
-                                                result = Vote.APPROVE;
-                                            } else if (returnType == Boolean.TYPE
-                                                && result == null) {
-                                                result = false;
-                                            }
+                                        // Create the listener and add it to the list
+                                        java.lang.reflect.Type[] genericInterfaces = listenerListClass.getGenericInterfaces();
+                                        Class<?> listenerClass = (Class<?>)genericInterfaces[0];
+
+                                        Object listener =
+                                            Proxy.newProxyInstance(ThreadUtilities.getClassLoader(),
+                                                new Class[]{listenerClass}, handler);
 
-                                            return result;
+                                        try {
+                                            addMethod.invoke(element.parent.value, new Object[] {listener});
+                                        } catch (IllegalAccessException exception) {
+                                            throw new SerializationException(exception);
+                                        } catch (InvocationTargetException exception) {
+                                            throw new SerializationException(exception);
                                         }
-                                    };
-
-                                    // Create the listener and add it to the list
-                                    java.lang.reflect.Type[] genericInterfaces = listenerListClass.getGenericInterfaces();
-                                    Class<?> listenerClass = (Class<?>)genericInterfaces[0];
-
-                                    Object listener =
-                                        Proxy.newProxyInstance(ThreadUtilities.getClassLoader(),
-                                            new Class[]{listenerClass}, handler);
-
-                                    try {
-                                        addMethod.invoke(element.parent.value, new Object[] {listener});
-                                    } catch (IllegalAccessException exception) {
-                                        throw new SerializationException(exception);
-                                    } catch (InvocationTargetException exception) {
-                                        throw new SerializationException(exception);
                                     }
+
+                                    break;
                                 }
+                            }
 
-                                break;
+                            // Move up the stack
+                            if (element.parent != null) {
+                                element = element.parent;
                             }
-                        }
 
-                        // Move up the stack
-                        if (element.parent != null) {
-                            element = element.parent;
+                            break;
                         }
-
-                        break;
                     }
                 }
-            }
 
-            reader.close();
-        } catch (XMLStreamException exception) {
-            throw new SerializationException(exception);
+                reader.close();
+            } 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;
         }
 
         // Clear the location so the previous value won't be re-used in a
@@ -870,6 +881,20 @@
         return root;
     }
 
+    private void logException(Exception exception) {
+        String message = "An error occurred while processing element <" + getTagName() + ">"
+            + " starting at line number " + getLineNumber();
+
+        if (location != null) {
+            message += " in file" + location.getPath();
+        }
+
+        message += ":";
+
+        System.err.println(message);
+        exception.printStackTrace();
+    }
+
     public void writeObject(Object object, OutputStream outputStream) throws IOException,
         SerializationException {
         throw new UnsupportedOperationException();

Added: incubator/pivot/trunk/wtk/test/org/apache/pivot/wtk/test/ActionMappingTest.java
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/wtk/test/org/apache/pivot/wtk/test/ActionMappingTest.java?rev=797172&view=auto
==============================================================================
--- incubator/pivot/trunk/wtk/test/org/apache/pivot/wtk/test/ActionMappingTest.java (added)
+++ incubator/pivot/trunk/wtk/test/org/apache/pivot/wtk/test/ActionMappingTest.java Thu Jul 23 17:53:04 2009
@@ -0,0 +1,78 @@
+/*
+ * 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.wtk.test;
+
+import org.apache.pivot.collections.Map;
+import org.apache.pivot.wtk.Action;
+import org.apache.pivot.wtk.Alert;
+import org.apache.pivot.wtk.Application;
+import org.apache.pivot.wtk.DesktopApplicationContext;
+import org.apache.pivot.wtk.Display;
+import org.apache.pivot.wtk.Keyboard;
+import org.apache.pivot.wtk.MessageType;
+import org.apache.pivot.wtk.Window;
+import org.apache.pivot.wtkx.WTKXSerializer;
+
+public class ActionMappingTest implements Application {
+    private Window window = null;
+
+    @Override
+    public void startup(Display display, Map<String, String> properties) throws Exception {
+        Action.getNamedActions().put("action1", new Action() {
+            @Override
+            public void perform() {
+                Alert.alert(MessageType.INFO, "Action 1 performed.", window);
+            }
+        });
+
+        Action.getNamedActions().put("action2", new Action() {
+            @Override
+            public void perform() {
+                Alert.alert(MessageType.INFO, "Action 2 performed.", window);
+            }
+        });
+
+        WTKXSerializer wtkxSerializer = new WTKXSerializer();
+        window = (Window)wtkxSerializer.readObject(this, "action_mapping_test.wtkx");
+        window.getActionMappings().add(new Window.ActionMapping(new Keyboard.KeyStroke(Keyboard.KeyCode.B,
+            Keyboard.Modifier.SHIFT.getMask()), "action2"));
+
+        window.open(display);
+        window.requestFocus();
+    }
+
+    @Override
+    public boolean shutdown(boolean optional) {
+        if (window != null) {
+            window.close();
+        }
+
+        return false;
+    }
+
+    @Override
+    public void suspend() {
+    }
+
+    @Override
+    public void resume() {
+    }
+
+    public static void main(String[] args) {
+        DesktopApplicationContext.main(ActionMappingTest.class, args);
+    }
+}

Added: incubator/pivot/trunk/wtk/test/org/apache/pivot/wtk/test/action_mapping_test.wtkx
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/wtk/test/org/apache/pivot/wtk/test/action_mapping_test.wtkx?rev=797172&view=auto
==============================================================================
--- incubator/pivot/trunk/wtk/test/org/apache/pivot/wtk/test/action_mapping_test.wtkx (added)
+++ incubator/pivot/trunk/wtk/test/org/apache/pivot/wtk/test/action_mapping_test.wtkx Thu Jul 23 17:53:04 2009
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+
+<Window title="Action Mapping Test" maximized="true"
+    xmlns:wtkx="http://pivot.apache.org/wtkx"
+    xmlns:io="org.apache.pivot.io"
+    xmlns:content="org.apache.pivot.wtk.content"
+    xmlns="org.apache.pivot.wtk">
+    <actionMappings>
+        <Window.ActionMapping keyStroke="SHIFT-A" action="action1" foo="bar"/>
+    </actionMappings>
+
+    <content>
+        <PushButton wtkx:id="pushButton" buttonData="Push Button"/>
+    </content>
+</Window>