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/09/28 02:35:35 UTC

svn commit: r819432 - in /incubator/pivot/trunk: tutorials/src/org/apache/pivot/tutorials/KitchenSink.java wtk/src/org/apache/pivot/wtkx/WTKXSerializer.java

Author: gbrown
Date: Mon Sep 28 00:35:35 2009
New Revision: 819432

URL: http://svn.apache.org/viewvc?rev=819432&view=rev
Log:
Refactor WTKXSerializer#readObject() into private methods for each StAX event to improve readability; eliminate initialBindings and replace with a reset() method.

Modified:
    incubator/pivot/trunk/tutorials/src/org/apache/pivot/tutorials/KitchenSink.java
    incubator/pivot/trunk/wtk/src/org/apache/pivot/wtkx/WTKXSerializer.java

Modified: incubator/pivot/trunk/tutorials/src/org/apache/pivot/tutorials/KitchenSink.java
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/tutorials/src/org/apache/pivot/tutorials/KitchenSink.java?rev=819432&r1=819431&r2=819432&view=diff
==============================================================================
--- incubator/pivot/trunk/tutorials/src/org/apache/pivot/tutorials/KitchenSink.java (original)
+++ incubator/pivot/trunk/tutorials/src/org/apache/pivot/tutorials/KitchenSink.java Mon Sep 28 00:35:35 2009
@@ -345,6 +345,8 @@
 
                 rollup.setContent(component);
 
+                wtkxSerializer.reset();
+
                 try {
                     menuSection = (Menu.Section)wtkxSerializer.readObject(this, "menu_section.wtkx");
                     imageMenuGroup = (ButtonGroup)wtkxSerializer.get("imageMenuGroup");

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=819432&r1=819431&r2=819432&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 Mon Sep 28 00:35:35 2009
@@ -275,14 +275,13 @@
 
     private URL location = null;
     private Resources resources = null;
-    private HashMap<String, Object> initialBindings = new HashMap<String, Object>();
 
     private Object root = null;
     private HashMap<String, Object> namedObjects = new HashMap<String, Object>();
     private HashMap<String, WTKXSerializer> includeSerializers = new HashMap<String, WTKXSerializer>();
 
     private ScriptEngineManager scriptEngineManager = null;
-    private String language = "javascript";
+    private String language = DEFAULT_LANGUAGE;
 
     private XMLInputFactory xmlInputFactory;
     private Element element = null;
@@ -306,6 +305,8 @@
 
     public static final String DEFINE_TAG = "define";
 
+    public static final String DEFAULT_LANGUAGE = "javascript";
+
     public static final String MIME_TYPE = "application/wtkx";
 
     public WTKXSerializer() {
@@ -384,7 +385,6 @@
         }
     }
 
-    @SuppressWarnings({"unchecked"})
     @Override
     public Object readObject(InputStream inputStream)
         throws IOException, SerializationException {
@@ -392,13 +392,10 @@
             throw new IllegalArgumentException("inputStream is null.");
         }
 
-        // Add the initial bindings
-        for (String key : initialBindings) {
-            namedObjects.put(key, initialBindings.get(key));
+        if (root != null) {
+            throw new IllegalStateException("Serializer must be reset.");
         }
 
-        initialBindings.clear();
-
         // Parse the XML stream
         element = null;
 
@@ -411,618 +408,633 @@
 
                     switch (event) {
                         case XMLStreamConstants.PROCESSING_INSTRUCTION: {
-                            String piTarget = reader.getPITarget();
-                            String piData = reader.getPIData();
-
-                            if (piTarget.equals(LANGUAGE_PROCESSING_INSTRUCTION)) {
-                                language = piData;
-                            }
-
+                            processProcessingInstruction(reader);
                             break;
                         }
 
                         case XMLStreamConstants.CHARACTERS: {
-                            if (!reader.isWhiteSpace()) {
-                                String text = reader.getText();
+                            processCharacters(reader);
+                            break;
+                        }
 
-                                if (text.length() > 0) {
-                                    switch (element.type) {
-                                        case INSTANCE: {
-                                            if (element.value instanceof Sequence) {
-                                                Sequence<Object> sequence = (Sequence<Object>)element.value;
-
-                                                try {
-                                                    Method addMethod = sequence.getClass().getMethod("add",
-                                                        new Class<?>[] {String.class});
-                                                    addMethod.invoke(sequence, new Object[] {text});
-                                                } catch (NoSuchMethodException exception) {
-                                                    throw new SerializationException("Text content cannot be added to "
-                                                        + sequence.getClass().getName() + ".", exception);
-                                                } catch (InvocationTargetException exception) {
-                                                    throw new SerializationException(exception);
-                                                } catch (IllegalAccessException exception) {
-                                                    throw new SerializationException(exception);
-                                                }
-                                            }
-
-                                            break;
-                                        }
-
-                                        case SCRIPT:
-                                        case WRITABLE_PROPERTY: {
-                                            element.value = text;
-                                            break;
-                                        }
-
-                                        default: {
-                                            throw new SerializationException("Unexpected characters in "
-                                                + element.type + " element.");
-                                        }
-                                    }
-                                }
-                            }
+                        case XMLStreamConstants.START_ELEMENT: {
+                            processStartElement(reader);
+                            break;
+                        }
 
+                        case XMLStreamConstants.END_ELEMENT: {
+                            processEndElement(reader);
                             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("");
-                                }
+                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
+        // subsequent call to this method
+        location = null;
+
+        return root;
+    }
 
-                                String attributePrefix = reader.getAttributePrefix(i);
-                                String attributeLocalName = reader.getAttributeLocalName(i);
-                                String attributeValue = reader.getAttributeValue(i);
+    private void processProcessingInstruction(XMLStreamReader reader) {
+        String piTarget = reader.getPITarget();
+        String piData = reader.getPIData();
+
+        if (piTarget.equals(LANGUAGE_PROCESSING_INSTRUCTION)) {
+            language = piData;
+        }
+    }
 
-                                attributes.add(new Attribute(attributeNamespaceURI,
-                                    attributePrefix, attributeLocalName, attributeValue));
+    @SuppressWarnings("unchecked")
+    private void processCharacters(XMLStreamReader reader) throws SerializationException {
+        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);
                             }
+                        }
 
-                            // 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.");
-                                }
+                        break;
+                    }
 
-                                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 SCRIPT:
+                    case WRITABLE_PROPERTY: {
+                        element.value = text;
+                        break;
+                    }
+
+                    default: {
+                        throw new SerializationException("Unexpected characters in "
+                            + element.type + " element.");
+                    }
+                }
+            }
+        }
+    }
+
+    private void processStartElement(XMLStreamReader reader) throws SerializationException {
+        // 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));
+        }
+
+        // Determine the type and value of this element
+        Element.Type elementType = null;
+        Object value = null;
+
+        if (prefix != null
+            && prefix.equals(WTKX_PREFIX)) {
+            // The element represents a WTKX operation
+            if (element == null) {
+                throw new SerializationException(prefix + ":" + localName
+                    + " is not a valid root element.");
+            }
+
+            if (localName.equals(INCLUDE_TAG)) {
+                elementType = Element.Type.INCLUDE;
+            } else if (localName.equals(SCRIPT_TAG)) {
+                elementType = Element.Type.SCRIPT;
+            } else if (localName.equals(DEFINE_TAG)) {
+                if (attributes.getLength() > 0) {
+                    throw new SerializationException(WTKX_PREFIX + ":" + DEFINE_TAG
+                        + " cannot have attributes.");
+                }
+
+                elementType = Element.Type.DEFINE;
+            } else {
+                throw new SerializationException(prefix + ":" + localName
+                    + " is not a valid element.");
+            }
+        } else {
+            if (Character.isUpperCase(localName.charAt(0))) {
+                // The element represents a typed object
+                if (namespaceURI == null) {
+                    throw new SerializationException("No XML namespace specified for "
+                        + localName + " tag.");
+                }
 
-                                    elementType = Element.Type.DEFINE;
-                                } else {
-                                    throw new SerializationException(prefix + ":" + localName
-                                        + " is not a valid element.");
+                String className = namespaceURI + "." + localName.replace('.', '$');
+
+                try {
+                    Class<?> type = Class.forName(className);
+                    elementType = Element.Type.INSTANCE;
+                    value = type.newInstance();
+                } catch (Exception exception) {
+                    throw new SerializationException(exception);
+                }
+            } else {
+                // The element represents a property
+                if (element == null
+                    || element.type != Element.Type.INSTANCE) {
+                    throw new SerializationException("Parent element must be a typed object.");
+                }
+
+                if (prefix != null
+                    && prefix.length() > 0) {
+                    throw new SerializationException("Property elements cannot have a namespace prefix.");
+                }
+
+                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;
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private void processEndElement(XMLStreamReader reader)
+        throws SerializationException, IOException {
+        String localName = reader.getLocalName();
+
+        switch (element.type) {
+            case INSTANCE:
+            case INCLUDE: {
+                String id = null;
+                ArrayList<Attribute> instancePropertyAttributes = new ArrayList<Attribute>();
+                ArrayList<Attribute> staticPropertyAttributes = 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 {
+                            if (attribute.localName.equals(INCLUDE_SRC_ATTRIBUTE)) {
+                                src = attribute.value;
+                            } else if (attribute.localName.equals(INCLUDE_RESOURCES_ATTRIBUTE)) {
+                                resources = new Resources(resources, attribute.value);
+                            } else {
+                                if (!Character.isUpperCase(attribute.localName.charAt(0))) {
+                                    throw new SerializationException("Instance property setters are not"
+                                        + " supported for " + WTKX_PREFIX + ":" + INCLUDE_TAG
+                                        + " " + " tag.");
                                 }
+
+                                staticPropertyAttributes.add(attribute);
+                            }
+                        }
+                    }
+
+                    if (src == null) {
+                        throw new SerializationException(INCLUDE_SRC_ATTRIBUTE
+                            + " attribute is required for " + WTKX_PREFIX + ":" + INCLUDE_TAG
+                            + " tag.");
+                    }
+
+                    // Read the object
+                    WTKXSerializer serializer = new WTKXSerializer(resources);
+                    if (id != null) {
+                        includeSerializers.put(id, serializer);
+                    }
+
+                    if (src.charAt(0) == '/') {
+                        element.value = serializer.readObject(src.substring(1));
+                    } else {
+                        element.value = serializer.readObject(new URL(location, src));
+                    }
+
+                    if (id == null
+                        && !serializer.isEmpty()
+                        && serializer.scriptEngineManager == null) {
+                        System.err.println("Include \"" + src + "\" defines unreachable objects.");
+                    }
+                } 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 {
-                                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.");
-                                    }
+                                throw new SerializationException(WTKX_PREFIX + ":" + attribute.localName
+                                    + " is not a valid attribute.");
+                            }
+                        } else {
+                            if (Character.isUpperCase(attribute.localName.charAt(0))) {
+                                staticPropertyAttributes.add(attribute);
+                            } else {
+                                instancePropertyAttributes.add(attribute);
+                            }
+                        }
+                    }
+                }
 
-                                    String className = namespaceURI + "." + localName.replace('.', '$');
+                // 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.");
+                    }
 
-                                    try {
-                                        Class<?> type = Class.forName(className);
-                                        elementType = Element.Type.INSTANCE;
-                                        value = type.newInstance();
-                                    } catch (Exception exception) {
-                                        throw new SerializationException(exception);
-                                    }
-                                } else {
-                                    // The element represents a property
-                                    if (element == null
-                                        || element.type != Element.Type.INSTANCE) {
-                                        throw new SerializationException("Parent element must be a typed object.");
-                                    }
+                    namedObjects.put(id, element.value);
+                }
 
-                                    if (prefix != null
-                                        && prefix.length() > 0) {
-                                        throw new SerializationException("Property elements cannot have a namespace prefix.");
-                                    }
+                // Apply instance attributes
+                Dictionary<String, Object> dictionary;
+                if (element.value instanceof Dictionary<?, ?>) {
+                    dictionary = (Dictionary<String, Object>)element.value;
+                } else {
+                    dictionary = new BeanDictionary(element.value);
+                }
 
-                                    BeanDictionary beanDictionary = new BeanDictionary(element.value);
+                for (Attribute attribute : instancePropertyAttributes) {
+                    dictionary.put(attribute.localName, resolve(attribute.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.");
-                                        }
+                // 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);
+                        }
+                    }
+                }
 
-                                        elementType = Element.Type.WRITABLE_PROPERTY;
-                                    }
-                                }
+                // Apply static attributes
+                if (element.value instanceof Dictionary<?, ?>) {
+                    if (staticPropertyAttributes.getLength() > 0) {
+                        throw new SerializationException("Static setters are only supported"
+                            + " for typed objects.");
+                    }
+                } else {
+                    for (Attribute attribute : staticPropertyAttributes) {
+                        // Determine the type of the attribute
+                        String propertyClassName = attribute.namespaceURI + "."
+                            + attribute.localName.substring(0, attribute.localName.lastIndexOf("."));
+
+                        Class<?> propertyClass = null;
+                        try {
+                            propertyClass = Class.forName(propertyClassName);
+                        } catch (ClassNotFoundException exception) {
+                            throw new SerializationException(exception);
+                        }
+
+                        if (propertyClass.isInterface()) {
+                            // The attribute represents an event listener
+                            String listenerClassName = propertyClassName.substring(propertyClassName.lastIndexOf('.') + 1);
+                            String getListenerListMethodName = "get" + Character.toUpperCase(listenerClassName.charAt(0))
+                                + listenerClassName.substring(1) + "s";
+
+                            // Get the listener list
+                            Method getListenerListMethod;
+                            try {
+                                Class<?> type = element.value.getClass();
+                                getListenerListMethod = type.getMethod(getListenerListMethodName, new Class<?>[]{});
+                            } catch (NoSuchMethodException exception) {
+                                throw new SerializationException(exception);
                             }
 
-                            // Set the current element
-                            String tagName = localName;
-                            if (prefix != null
-                                && prefix.length() > 0) {
-                                tagName = prefix + ":" + tagName;
+                            Object listenerList;
+                            try {
+                                listenerList = getListenerListMethod.invoke(element.value, new Object[]{});
+                            } catch (InvocationTargetException exception) {
+                                throw new SerializationException(exception);
+                            } catch (IllegalAccessException exception) {
+                                throw new SerializationException(exception);
                             }
 
-                            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;
+                            // Don't pollute the engine namespace with the listener functions
+                            ScriptEngine scriptEngine = scriptEngineManager.getEngineByName(language);
+                            scriptEngine.setBindings(new SimpleBindings(), ScriptContext.ENGINE_SCOPE);
+
+                            // Create an invocation handler for this listener
+                            AttributeInvocationHandler handler =
+                                new AttributeInvocationHandler(scriptEngine,
+                                    attribute.localName.substring(attribute.localName.lastIndexOf(".") + 1),
+                                    attribute.value);
+
+                            Object listener = Proxy.newProxyInstance(ThreadUtilities.getClassLoader(),
+                                new Class[]{propertyClass}, handler);
+
+                            // Add the listener
+                            Class<?> listenerListClass = listenerList.getClass();
+                            Method addMethod;
+                            try {
+                                addMethod = listenerListClass.getMethod("add", new Class<?>[] {Object.class});
+                            } catch (NoSuchMethodException exception) {
+                                throw new RuntimeException(exception);
                             }
 
-                            break;
-                        }
+                            try {
+                                addMethod.invoke(listenerList, new Object[] {listener});
+                            } catch (IllegalAccessException exception) {
+                                throw new SerializationException(exception);
+                            } catch (InvocationTargetException exception) {
+                                throw new SerializationException(exception);
+                            }
+                        } else {
+                            // The attribute reprsents a static setter
+                            Object value = resolve(attribute.value);
+
+                            Class<?> objectType = element.value.getClass();
+
+                            String propertyName = attribute.localName.substring(attribute.localName.lastIndexOf(".") + 1);
+                            propertyName = Character.toUpperCase(propertyName.charAt(0)) +
+                            propertyName.substring(1);
+
+                            Method setterMethod = null;
+                            if (value != null) {
+                                setterMethod = getStaticSetterMethod(propertyClass, propertyName,
+                                    objectType, value.getClass());
+                            }
 
-                        case XMLStreamConstants.END_ELEMENT: {
-                            String localName = reader.getLocalName();
+                            if (setterMethod == null) {
+                                Method getterMethod = getStaticGetterMethod(propertyClass, propertyName, objectType);
+
+                                if (getterMethod != null) {
+                                    Class<?> propertyType = getterMethod.getReturnType();
+                                    setterMethod = getStaticSetterMethod(propertyClass, propertyName,
+                                        objectType, propertyType);
 
-                            switch (element.type) {
-                                case INSTANCE:
-                                case INCLUDE: {
-                                    String id = null;
-                                    ArrayList<Attribute> instancePropertyAttributes = new ArrayList<Attribute>();
-                                    ArrayList<Attribute> staticPropertyAttributes = 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 {
-                                                if (attribute.localName.equals(INCLUDE_SRC_ATTRIBUTE)) {
-                                                    src = attribute.value;
-                                                } else if (attribute.localName.equals(INCLUDE_RESOURCES_ATTRIBUTE)) {
-                                                    resources = new Resources(resources, attribute.value);
-                                                } else {
-                                                    if (!Character.isUpperCase(attribute.localName.charAt(0))) {
-                                                        throw new SerializationException("Instance property setters are not"
-                                                            + " supported for " + WTKX_PREFIX + ":" + INCLUDE_TAG
-                                                            + " " + " tag.");
-                                                    }
-
-                                                    staticPropertyAttributes.add(attribute);
-                                                }
-                                            }
-                                        }
-
-                                        if (src == null) {
-                                            throw new SerializationException(INCLUDE_SRC_ATTRIBUTE
-                                                + " attribute is required for " + WTKX_PREFIX + ":" + INCLUDE_TAG
-                                                + " tag.");
-                                        }
-
-                                        // Read the object
-                                        WTKXSerializer serializer = new WTKXSerializer(resources);
-                                        if (id != null) {
-                                            includeSerializers.put(id, serializer);
-                                        }
-
-                                        if (src.charAt(0) == '/') {
-                                            element.value = serializer.readObject(src.substring(1));
-                                        } else {
-                                            element.value = serializer.readObject(new URL(location, src));
-                                        }
-
-                                        if (id == null
-                                            && !serializer.isEmpty()
-                                            && serializer.scriptEngineManager == null) {
-                                            System.err.println("Include \"" + src + "\" defines unreachable objects.");
-                                        }
-                                    } 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 {
-                                                if (Character.isUpperCase(attribute.localName.charAt(0))) {
-                                                    staticPropertyAttributes.add(attribute);
-                                                } else {
-                                                    instancePropertyAttributes.add(attribute);
-                                                }
-                                            }
-                                        }
+                                    if (value instanceof String) {
+                                        value = BeanDictionary.coerce((String)value, propertyType);
                                     }
+                                }
+                            }
 
-                                    // 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 (setterMethod == null) {
+                                throw new SerializationException(attribute.localName + " is not valid static property.");
+                            }
 
-                                        namedObjects.put(id, element.value);
-                                    }
+                            // Invoke the setter
+                            try {
+                                setterMethod.invoke(null, new Object[] {element.value, value});
+                            } catch (Exception exception) {
+                                throw new SerializationException(exception);
+                            }
+                        }
+                    }
+                }
 
-                                    // Apply instance attributes
-                                    Dictionary<String, Object> dictionary;
-                                    if (element.value instanceof Dictionary) {
-                                        dictionary = (Dictionary<String, Object>)element.value;
-                                    } else {
-                                        dictionary = new BeanDictionary(element.value);
-                                    }
+                // If the parent element is a writable property, set this as its
+                // value; it will be applied later in the parent's closing tag
+                if (element.parent != null
+                    && element.parent.type == Element.Type.WRITABLE_PROPERTY) {
+                    element.parent.value = element.value;
+                }
 
-                                    for (Attribute attribute : instancePropertyAttributes) {
-                                        dictionary.put(attribute.localName, resolve(attribute.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 static attributes
-                                    if (element.value instanceof Dictionary) {
-                                        if (staticPropertyAttributes.getLength() > 0) {
-                                            throw new SerializationException("Static setters are only supported"
-                                                + " for typed objects.");
-                                        }
-                                    } else {
-                                        for (Attribute attribute : staticPropertyAttributes) {
-                                            // Determine the type of the attribute
-                                            String propertyClassName = attribute.namespaceURI + "."
-                                                + attribute.localName.substring(0, attribute.localName.lastIndexOf("."));
-
-                                            Class<?> propertyClass = null;
-                                            try {
-                                                propertyClass = Class.forName(propertyClassName);
-                                            } catch (ClassNotFoundException exception) {
-                                                throw new SerializationException(exception);
-                                            }
-
-                                            if (propertyClass.isInterface()) {
-                                                // The attribute represents an event listener
-                                                String listenerClassName = propertyClassName.substring(propertyClassName.lastIndexOf('.') + 1);
-                                                String getListenerListMethodName = "get" + Character.toUpperCase(listenerClassName.charAt(0))
-                                                    + listenerClassName.substring(1) + "s";
-
-                                                // Get the listener list
-                                                Method getListenerListMethod;
-                                                try {
-                                                    Class<?> type = element.value.getClass();
-                                                    getListenerListMethod = type.getMethod(getListenerListMethodName, new Class<?>[]{});
-                                                } catch (NoSuchMethodException exception) {
-                                                    throw new SerializationException(exception);
-                                                }
-
-                                                Object listenerList;
-                                                try {
-                                                    listenerList = getListenerListMethod.invoke(element.value, new Object[]{});
-                                                } catch (InvocationTargetException exception) {
-                                                    throw new SerializationException(exception);
-                                                } catch (IllegalAccessException exception) {
-                                                    throw new SerializationException(exception);
-                                                }
-
-                                                // Don't pollute the engine namespace with the listener functions
-                                                ScriptEngine scriptEngine = scriptEngineManager.getEngineByName(language);
-                                                scriptEngine.setBindings(new SimpleBindings(), ScriptContext.ENGINE_SCOPE);
-
-                                                // Create an invocation handler for this listener
-                                                AttributeInvocationHandler handler =
-                                                    new AttributeInvocationHandler(scriptEngine,
-                                                        attribute.localName.substring(attribute.localName.lastIndexOf(".") + 1),
-                                                        attribute.value);
-
-                                                Object listener = Proxy.newProxyInstance(ThreadUtilities.getClassLoader(),
-                                                    new Class[]{propertyClass}, handler);
-
-                                                // Add the listener
-                                                Class<?> listenerListClass = listenerList.getClass();
-                                                Method addMethod;
-                                                try {
-                                                    addMethod = listenerListClass.getMethod("add", new Class<?>[] {Object.class});
-                                                } catch (NoSuchMethodException exception) {
-                                                    throw new RuntimeException(exception);
-                                                }
-
-                                                try {
-                                                    addMethod.invoke(listenerList, new Object[] {listener});
-                                                } catch (IllegalAccessException exception) {
-                                                    throw new SerializationException(exception);
-                                                } catch (InvocationTargetException exception) {
-                                                    throw new SerializationException(exception);
-                                                }
-                                            } else {
-                                                // The attribute reprsents a static setter
-                                                Object value = resolve(attribute.value);
-
-                                                Class<?> objectType = element.value.getClass();
-
-                                                String propertyName = attribute.localName.substring(attribute.localName.lastIndexOf(".") + 1);
-                                                propertyName = Character.toUpperCase(propertyName.charAt(0)) +
-                                                propertyName.substring(1);
-
-                                                Method setterMethod = null;
-                                                if (value != null) {
-                                                    setterMethod = getStaticSetterMethod(propertyClass, propertyName,
-                                                        objectType, value.getClass());
-                                                }
-
-                                                if (setterMethod == null) {
-                                                    Method getterMethod = getStaticGetterMethod(propertyClass, propertyName, objectType);
-
-                                                    if (getterMethod != null) {
-                                                        Class<?> propertyType = getterMethod.getReturnType();
-                                                        setterMethod = getStaticSetterMethod(propertyClass, propertyName,
-                                                            objectType, propertyType);
-
-                                                        if (value instanceof String) {
-                                                            value = BeanDictionary.coerce((String)value, propertyType);
-                                                        }
-                                                    }
-                                                }
-
-                                                if (setterMethod == null) {
-                                                    throw new SerializationException(attribute.localName + " is not valid static property.");
-                                                }
-
-                                                // Invoke the setter
-                                                try {
-                                                    setterMethod.invoke(null, new Object[] {element.value, value});
-                                                } catch (Exception exception) {
-                                                    throw new SerializationException(exception);
-                                                }
-                                            }
-                                        }
-                                    }
+                        Dictionary<String, Object> dictionary =
+                            (Dictionary<String, Object>)element.value;
+                        dictionary.put(attribute.localName, resolve(attribute.value));
+                    }
+                }
 
-                                    // If the parent element is a writable property, set this as its
-                                    // value; it will be applied later in the parent's closing tag
-                                    if (element.parent != null
-                                        && element.parent.type == Element.Type.WRITABLE_PROPERTY) {
-                                        element.parent.value = element.value;
-                                    }
+                break;
+            }
 
-                                    break;
-                                }
+            case WRITABLE_PROPERTY: {
+                BeanDictionary beanDictionary = new BeanDictionary(element.parent.value);
+                beanDictionary.put(localName, element.value);
+                break;
+            }
 
-                                case READ_ONLY_PROPERTY: {
-                                    if (element.value instanceof Dictionary<?, ?>) {
-                                        // Process attributes looking for instance property setters
-                                        for (Attribute attribute : element.attributes) {
-                                            if (Character.isUpperCase(attribute.localName.charAt(0))) {
-                                                throw new SerializationException("Static setters are not supported"
-                                                    + " for read-only properties.");
-                                            }
-
-                                            Dictionary<String, Object> dictionary =
-                                                (Dictionary<String, Object>)element.value;
-                                            dictionary.put(attribute.localName, resolve(attribute.value));
-                                        }
-                                    }
+            case SCRIPT: {
+                // Process attributes looking for src and language
+                String src = null;
+                String language = this.language;
+                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.");
+                    }
 
-                                    break;
-                                }
+                    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.");
+                    }
+                }
 
-                                case WRITABLE_PROPERTY: {
-                                    BeanDictionary beanDictionary = new BeanDictionary(element.parent.value);
-                                    beanDictionary.put(localName, element.value);
-                                    break;
-                                }
+                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();
+                }
 
-                                case SCRIPT: {
-                                    // Process attributes looking for src and language
-                                    String src = null;
-                                    String language = this.language;
-                                    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 (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.");
-                                        }
-                                    }
+                // 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();
-                                    }
+                if (src != null) {
+                    // The script is located in an external file
+                    int i = src.lastIndexOf(".");
+                    if (i == -1) {
+                        throw new SerializationException("Cannot determine type of script \""
+                            + src + "\".");
+                    }
 
-                                    // Execute script
-                                    final ScriptEngine scriptEngine;
+                    String extension = src.substring(i + 1);
+                    scriptEngine = scriptEngineManager.getEngineByExtension(extension);
 
-                                    if (src != null) {
-                                        // The script is located in an external file
-                                        int i = src.lastIndexOf(".");
-                                        if (i == -1) {
-                                            throw new SerializationException("Cannot determine type of script \""
-                                                + src + "\".");
-                                        }
-
-                                        String extension = src.substring(i + 1);
-                                        scriptEngine = scriptEngineManager.getEngineByExtension(extension);
-
-                                        if (scriptEngine == null) {
-                                            throw new SerializationException("Unable to find scripting engine for"
-                                                + " extension " + extension + ".");
-                                        }
-
-                                        scriptEngine.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
-
-                                        try {
-                                            URL scriptLocation;
-                                            if (src.charAt(0) == '/') {
-                                                ClassLoader classLoader = ThreadUtilities.getClassLoader();
-                                                scriptLocation = classLoader.getResource(src);
-                                            } 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(System.err);
-                                            } finally {
-                                                if (scriptReader != null) {
-                                                    scriptReader.close();
-                                                }
-                                            }
-                                        } catch (IOException exception) {
-                                            throw new SerializationException(exception);
-                                        }
-                                    } else {
-                                        // The script is inline
-                                        scriptEngine = scriptEngineManager.getEngineByName(language);
-
-                                        if (scriptEngine == null) {
-                                            throw new SerializationException("Unable to find scripting engine for"
-                                                + " language \"" + language + "\".");
-                                        }
-
-                                        scriptEngine.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
-
-                                        if (element.value != null) {
-                                            try {
-                                                scriptEngine.eval((String)element.value);
-                                            } catch (ScriptException exception) {
-                                                exception.printStackTrace(System.err);
-                                            }
-                                        }
-                                    }
+                    if (scriptEngine == null) {
+                        throw new SerializationException("Unable to find scripting engine for"
+                            + " extension " + extension + ".");
+                    }
 
-                                    if (element.parent.value instanceof ListenerList<?>) {
-                                        // Create the listener and add it to the list
-                                        Class<?> listenerListClass = element.parent.value.getClass();
-
-                                        java.lang.reflect.Type[] genericInterfaces = listenerListClass.getGenericInterfaces();
-                                        Class<?> listenerClass = (Class<?>)genericInterfaces[0];
-
-                                        ElementInvocationHandler handler = new ElementInvocationHandler(scriptEngine);
-
-                                        Method addMethod;
-                                        try {
-                                            addMethod = listenerListClass.getMethod("add",
-                                                new Class<?>[] {Object.class});
-                                        } catch (NoSuchMethodException exception) {
-                                            throw new RuntimeException(exception);
-                                        }
-
-                                        Object listener =
-                                            Proxy.newProxyInstance(ThreadUtilities.getClassLoader(),
-                                                new Class[]{listenerClass}, handler);
-
-                                        try {
-                                            addMethod.invoke(element.parent.value, new Object[] {listener});
-                                        } catch (IllegalAccessException exception) {
-                                            throw new SerializationException(exception);
-                                        } catch (InvocationTargetException exception) {
-                                            throw new SerializationException(exception);
-                                        }
-                                    }
+                    scriptEngine.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
 
-                                    break;
-                                }
+                    try {
+                        URL scriptLocation;
+                        if (src.charAt(0) == '/') {
+                            ClassLoader classLoader = ThreadUtilities.getClassLoader();
+                            scriptLocation = classLoader.getResource(src);
+                        } else {
+                            scriptLocation = new URL(location, src);
+                        }
 
-                                case DEFINE: {
-                                    // No-op
-                                }
+                        BufferedReader scriptReader = null;
+                        try {
+                            scriptReader = new BufferedReader(new InputStreamReader(scriptLocation.openStream()));
+                            scriptEngine.eval(scriptReader);
+                        } catch(ScriptException exception) {
+                            exception.printStackTrace(System.err);
+                        } finally {
+                            if (scriptReader != null) {
+                                scriptReader.close();
                             }
+                        }
+                    } catch (IOException exception) {
+                        throw new SerializationException(exception);
+                    }
+                } else {
+                    // The script is inline
+                    scriptEngine = scriptEngineManager.getEngineByName(language);
+
+                    if (scriptEngine == null) {
+                        throw new SerializationException("Unable to find scripting engine for"
+                            + " language \"" + language + "\".");
+                    }
 
-                            // Move up the stack
-                            if (element.parent != null) {
-                                element = element.parent;
-                            }
+                    scriptEngine.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
 
-                            break;
+                    if (element.value != null) {
+                        try {
+                            scriptEngine.eval((String)element.value);
+                        } catch (ScriptException exception) {
+                            exception.printStackTrace(System.err);
                         }
                     }
                 }
 
-                reader.close();
-            } catch (XMLStreamException exception) {
-                throw new SerializationException(exception);
+                if (element.parent.value instanceof ListenerList<?>) {
+                    // Create the listener and add it to the list
+                    Class<?> listenerListClass = element.parent.value.getClass();
+
+                    java.lang.reflect.Type[] genericInterfaces = listenerListClass.getGenericInterfaces();
+                    Class<?> listenerClass = (Class<?>)genericInterfaces[0];
+
+                    ElementInvocationHandler handler = new ElementInvocationHandler(scriptEngine);
+
+                    Method addMethod;
+                    try {
+                        addMethod = listenerListClass.getMethod("add",
+                            new Class<?>[] {Object.class});
+                    } catch (NoSuchMethodException exception) {
+                        throw new RuntimeException(exception);
+                    }
+
+                    Object listener =
+                        Proxy.newProxyInstance(ThreadUtilities.getClassLoader(),
+                            new Class[]{listenerClass}, handler);
+
+                    try {
+                        addMethod.invoke(element.parent.value, new Object[] {listener});
+                    } catch (IllegalAccessException exception) {
+                        throw new SerializationException(exception);
+                    } catch (InvocationTargetException exception) {
+                        throw new SerializationException(exception);
+                    }
+                }
+
+                break;
             }
-        } 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
-        // subsequent call to this method
-        location = null;
+            case DEFINE: {
+                // No-op
+            }
+        }
 
-        return root;
+        // Move up the stack
+        if (element.parent != null) {
+            element = element.parent;
+        }
     }
 
     private void logException(Exception exception) {
-        String message = "An error occurred while processing element <" + getTagName() + ">"
-            + " starting at line number " + getLineNumber();
+        String message = "An error occurred while processing element <" + element.tagName + ">"
+            + " starting at line number " + element.lineNumber;
 
         if (location != null) {
             message += " in file " + location.getPath();
@@ -1063,56 +1075,61 @@
             throw new IllegalArgumentException("name is null.");
         }
 
-        WTKXSerializer serializer = this;
-        String[] path = name.split("\\.");
-
-        Object object = null;
+        Object value;
 
-        if (root == null) {
-            object = initialBindings.get(name);
+        int i = name.lastIndexOf('.');
+        if (i == -1) {
+            value = namedObjects.get(name);
         } else {
-            int i = 0;
-            int n = path.length - 1;
-            while (i < n && serializer != null) {
-                String namespace = path[i++];
-                serializer = serializer.includeSerializers.get(namespace);
-            }
-
-            String id = path[i];
-
-            if (serializer != null
-                && serializer.namedObjects.containsKey(id)) {
-                object = serializer.namedObjects.get(id);
-            }
+            String serializerName = name.substring(0, name.lastIndexOf('.'));
+            String id = name.substring(serializerName.length() + 1);
+            WTKXSerializer serializer = getSerializer(serializerName);
+            value = serializer.get(id);
         }
 
-        return object;
+        return value;
     }
 
     @Override
-    public Object put(String id, Object value) {
-        if (id == null) {
-            throw new IllegalArgumentException("id is null.");
+    public Object put(String name, Object value) {
+        if (name == null) {
+            throw new IllegalArgumentException("name is null.");
         }
 
-        root = null;
-        namedObjects.clear();
-        includeSerializers.clear();
+        Object previousValue;
+
+        int i = name.lastIndexOf('.');
+        if (i == -1) {
+            previousValue = namedObjects.put(name, value);
+        } else {
+            String serializerName = name.substring(0, name.lastIndexOf('.'));
+            String id = name.substring(serializerName.length() + 1);
+            WTKXSerializer serializer = getSerializer(serializerName);
+            previousValue = serializer.put(id, value);
+        }
 
-        return initialBindings.put(id, value);
+        return previousValue;
     }
 
     @Override
-    public Object remove(String id) {
-        if (id == null) {
-            throw new IllegalArgumentException("id is null.");
+    public Object remove(String name) {
+        if (name == null) {
+            throw new IllegalArgumentException("name is null.");
         }
 
-        if (root != null) {
-            throw new IllegalStateException();
+        Object previousValue;
+
+        int i = name.lastIndexOf('.');
+        if (i == -1) {
+            previousValue = namedObjects.remove(name);
+        } else {
+            String serializerName = name.substring(0, name.lastIndexOf('.'));
+            String id = name.substring(serializerName.length() + 1);
+            WTKXSerializer serializer = getSerializer(serializerName);
+            previousValue = serializer.remove(id);
         }
 
-        return initialBindings.remove(id);
+        return previousValue;
     }
 
     @Override
@@ -1121,42 +1138,32 @@
             throw new IllegalArgumentException("name is null.");
         }
 
-        WTKXSerializer serializer = this;
-        String[] path = name.split("\\.");
-
-        boolean result = false;
+        boolean containsKey;
 
-        if (root == null) {
-            result = initialBindings.containsKey(name);
+        int i = name.lastIndexOf('.');
+        if (i == -1) {
+            containsKey = namedObjects.containsKey(name);
         } else {
-            int i = 0;
-            int n = path.length - 1;
-            while (i < n && serializer != null) {
-                String namespace = path[i++];
-                serializer = serializer.includeSerializers.get(namespace);
-            }
-
-            String id = path[i];
-
-            result = (serializer != null
-                && serializer.namedObjects.containsKey(id));
+            String serializerName = name.substring(0, name.lastIndexOf('.'));
+            String id = name.substring(serializerName.length() + 1);
+            WTKXSerializer serializer = getSerializer(serializerName);
+            containsKey = serializer.containsKey(id);
         }
 
-        return result;
+        return containsKey;
     }
 
     @Override
     public boolean isEmpty() {
-        boolean empty = false;
-
-        if (root == null) {
-            empty = initialBindings.isEmpty();
-        } else {
-            empty = namedObjects.isEmpty()
-                && includeSerializers.isEmpty();
-        }
+        return namedObjects.isEmpty()
+            && includeSerializers.isEmpty();
+    }
 
-        return empty;
+    public void reset() {
+        namedObjects.clear();
+        includeSerializers.clear();
+        root = null;
+        language = DEFAULT_LANGUAGE;
     }
 
     /**
@@ -1172,57 +1179,35 @@
     }
 
     /**
-     * Retrieves an include serializer by its ID.
+     * Retrieves a nested serializer.
      *
-     * @param id
-     * The ID of the serializer, relative to this loader. The serializer's ID
+     * @param name
+     * The name of the serializer, relative to this loader. The serializer's name
      * is the concatentation of its parent IDs and its ID, separated by periods
      * (e.g. "foo.bar.baz").
      *
      * @return The named serializer, or <tt>null</tt> if a serializer with the
      * given name does not exist.
      */
-    public WTKXSerializer getSerializer(String id) {
-        if (id == null) {
-            throw new IllegalArgumentException("id is null.");
+    public WTKXSerializer getSerializer(String name) {
+        if (name == null) {
+            throw new IllegalArgumentException("name is null.");
         }
 
         WTKXSerializer serializer = this;
-        String[] namespacePath = id.split("\\.");
+        String[] path = name.split("\\.");
 
         int i = 0;
-        int n = namespacePath.length;
+        int n = path.length;
         while (i < n && serializer != null) {
-            String namespace = namespacePath[i++];
-            serializer = serializer.includeSerializers.get(namespace);
+            String id = path[i++];
+            serializer = serializer.includeSerializers.get(id);
         }
 
         return serializer;
     }
 
     /**
-     * Returns the name of the element currently being processed.
-     *
-     * @return
-     * The name of the element currently being processed, or <tt>null</tt> if
-     * no element is currently being processed.
-     */
-    public String getTagName() {
-        return (element == null ? null : element.tagName);
-    }
-
-    /**
-     * Returns the line number of the element currently being processed.
-     *
-     * @return
-     * The line number of the element currently being processed, or <tt>-1</tt>
-     * if no element is currently being processed.
-     */
-    public int getLineNumber() {
-        return (element == null ? -1 : element.lineNumber);
-    }
-
-    /**
      * Returns the location of the WTKX currently being processed.
      *
      * @return