/*******************************************************************************
* CogTool Copyright Notice and Distribution Terms
* CogTool 1.3, Copyright (c) 2005-2013 Carnegie Mellon University
* This software is distributed under the terms of the FSF Lesser
* Gnu Public License (see LGPL.txt).
*
* CogTool is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* CogTool is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CogTool; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* CogTool makes use of several third-party components, with the
* following notices:
*
* Eclipse SWT version 3.448
* Eclipse GEF Draw2D version 3.2.1
*
* Unless otherwise indicated, all Content made available by the Eclipse
* Foundation is provided to you under the terms and conditions of the Eclipse
* Public License Version 1.0 ("EPL"). A copy of the EPL is provided with this
* Content and is also available at http://www.eclipse.org/legal/epl-v10.html.
*
* CLISP version 2.38
*
* Copyright (c) Sam Steingold, Bruno Haible 2001-2006
* This software is distributed under the terms of the FSF Gnu Public License.
* See COPYRIGHT file in clisp installation folder for more information.
*
* ACT-R 6.0
*
* Copyright (c) 1998-2007 Dan Bothell, Mike Byrne, Christian Lebiere &
* John R Anderson.
* This software is distributed under the terms of the FSF Lesser
* Gnu Public License (see LGPL.txt).
*
* Apache Jakarta Commons-Lang 2.1
*
* This product contains software developed by the Apache Software Foundation
* (http://www.apache.org/)
*
* jopt-simple version 1.0
*
* Copyright (c) 2004-2013 Paul R. Holser, Jr.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* Mozilla XULRunner 1.9.0.5
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (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.mozilla.org/MPL/.
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The J2SE(TM) Java Runtime Environment version 5.0
*
* Copyright 2009 Sun Microsystems, Inc., 4150
* Network Circle, Santa Clara, California 95054, U.S.A. All
* rights reserved. U.S.
* See the LICENSE file in the jre folder for more information.
******************************************************************************/
package edu.cmu.cs.hcii.cogtool.util;
import java.lang.reflect.Array;
import java.rmi.UnexpectedException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.eclipse.ecf.core.util.Base64;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
/**
* Class to support the deserialization of a previously saved set
* of Java objects.
* <p>
* Every object must register a loader (see, for example,
* <code>CogToolSerialization</code>).
* <p>
* Loaders come in three "flavors":
* (1) A standard object loader assigns values to the instance variables
* of the object being loaded and creates the aggregate objects (array,
* <code>Map</code>, or <code>Collection</code> into which nested objects
* are placed. (2) An aggregate loader simply creates nested aggregate
* objects. (3) An enumeration loader returns the unique (singleton)
* enumerated value for a given persisted enumeration code.
* <p>
* Creating array objects is actually performed by reflection;
* standard and aggregate loaders simply indicate the proper element type
* for the array (which may have changed since the objects were originally
* serialized).
* <p>
* Standard and aggregate loaders also provide for determining the appropriate
* loader(s) for nested aggregate objects.
* <p>
* Because several objects may be stored in a serialization (see
* <code>ObjectSaver</code>), the <code>load</code> method returns an ordered
* <code>Collection</code> of the saved objects.
* <p>
* The SAX parser handler methods are not really exported!
* (In other words, omit <code>startDocument</code>, <code>endDocument</code>,
* <code>startElement</code>, <code>endElement</code>, <code>characters</code>,
* <code>error</code>, <code>fatalError</code>, and <code>warning</code>.
*
* @author mlh
*/
public class ObjectLoader extends DefaultHandler implements ObjectPersist
{
/**
* The interface for an enumeration loader.
* An enumeration loader returns the unique (singleton)
* enumerated value for a given persisted enumeration code.
*
* @author mlh
*/
public interface IEnumLoader
{
/**
* Return the unique enumerated value for the given enumeration code.
* <p>
* Instances of this interface are only fetched from the loader
* registry and never pushed on the loader stack (activeLoaders).
*
* @param persistentValue the serialized enumeration code
* @return the unique enumerated value for the given enumeration code
* @author mlh
*/
public Object createEnum(String persistentValue);
public Object createEnum(ObjectLoader l, String persistentValue);
}
/**
* A common super-interface for <code>IAggregateLoader</code>
* and <code>IObjectLoader</code>; this allows us to say that loader stack
* has related type elements.
*
* @author mlh
*/
public interface ILoader<T>
{
/**
* Evolution support; override this to achieve changes that
* require the entire object to be complete.
*
* @param target the object to evolve; guaranteed to be the same
* object created by createObject()
*/
public void evolve(T target);
public void evolve(ObjectLoader l, T target);
}
/**
* The interface for an aggregate loader.
* An aggregate loader creates nested aggregate objects.
* It also provides the appropriate loader for nested aggregate objects
* (for a <code>Map</code>, this loader would be for a nested value
* component, since we assume that key components are not aggregate).
* Finally, methods are provided to add objects to aggregates.
* <p>
* Aggregate loaders not registered (what type would you use to fetch it?).
* Instead, they are "generated" via calls to <code>getLoader</code> on
* either an <code>IObjectLoader</code> or <code>IAggregateLoader</code>
* instance. Aggregate loaders are pushed onto the loader stack
* (<code>activeLoaders</code>) while an aggregate object is being
* reconstituted.
*
* @author mlh
*/
public interface IAggregateLoader extends ILoader<Object>
{
/**
* Ensure a <code>Map</code> exists as a nested object in the
* parent aggregate (that is, the parent array, <code>Map</code>,
* or <code>Collection</code>).
* <p>
* The given size may be used to optimize the created
* <code>Map</code>.
*
* @param size the number of key-value entries in the serialized
* <code>Map</code>
* @return the nested <code>Map</code> value
* @author mlh
*/
public Map<?, ?> createMap(int size);
public Map<?, ?> createMap(ObjectLoader l, int size);
/**
* Ensure a <code>Collection</code> exists as a nested object in the
* parent aggregate (that is, the parent array, <code>Map</code>,
* or <code>Collection</code>).
* <p>
* The given size may be used to optimize the created
* <code>Collection</code>.
*
* @param size the number of entries in the serialized
* <code>Collection</code>
* @return the nested <code>Collection</code> value
* @author mlh
*/
public Collection<?> createCollection(int size);
public Collection<?> createCollection(ObjectLoader l, int size);
/**
* Determine the actual class to use as the element type
* for the array to be created using reflection. The given
* class name is that of the element type of the array when
* it was serialized.
* <p>
* The array being created is a nested object in a parent
* aggregate (that is, the parent array, <code>Map</code>,
* or <code>Collection</code>).
*
* @param className the class name of the element type of the
* array when it was serialized
* @return the class to use as the element type of the array being
* created
* @author mlh
*/
public Class<?> getArrayEltType(String className);
public Class<?> getArrayEltType(ObjectLoader l, String className);
/**
* Return the aggregate loader for aggregate objects that are nested
* within the aggregate object associated with this loader. If this
* aggregate is a <code>Map</code>, the returned loader would be for
* the value component, since we assume that key components are not
* aggregate.
*
* @return the aggregate loader for nested aggregate objects
* @author mlh
*/
public IAggregateLoader getLoader();
public IAggregateLoader getLoader(ObjectLoader l);
/**
* Add key-value entry to given Map.
*
* @param l the object loader controlling the deserialization
* @param m the map to add entry mapping to
* @param key the key of the entry
* @param v the value of the entry
* @author mlh
*/
public <K, V> void putInMap(Map<K, V> m, K key, V v);
public <K, V> void putInMap(ObjectLoader l, Map<K, V> m, K key, V v);
/**
* Add value to given Collection.
*
* @param l the object loader controlling the deserialization
* @param c the collection to add value to
* @param v the value to add
* @author mlh
*/
public <T> void addToCollection(Collection<? super T> c, T v);
public <T> void addToCollection(ObjectLoader l,
Collection<? super T> c,
T v);
/**
* Add Object value to given Array.
*
* @param l the object loader controlling the deserialization
* @param a the array to add value to
* @param i index at which to add value
* @param v the value to add
* @author mlh
*/
public void addToArray(Object a, int i, Object v);
public void addToArray(ObjectLoader l, Object a, int i, Object v);
}
/**
* The interface for an object loader, which assigns values to the instance
* variables of the object being loaded and creates the aggregate objects
* (array, <code>Map</code>, or <code>Collection</code> into which nested
* objects are placed.
* It also provides the appropriate loader for nested aggregate objects.
* <p>
* For nested aggregate objects, the loader must override the appropriate
* <code>createMap</code>, <code>createCollection</code>, and
* <code>getArrayEltType</code>. If the object can receive the aggregate
* (instead of always creating the aggregate itself), then an appropriate
* clause in the <code>set</code> method that takes an <code>Object</code>
* value must also exist. Remember that the first occurrence of any
* object (including arrays, maps, and collections) is serialized in place
* whereas subsequent references to the same object generate a
* "reference" XML element and are "set".
* <p>
* Object loaders are fetched from the loader registry and pushed onto
* the loader stack (<code>activeLoaders</code>) while an object of the
* corresponding type is being reconstituted.
*
* @author mlh
*/
public interface IObjectLoader<T> extends ILoader<T>
{
/**
* Return the actual (empty) object instance of the class associated
* with this loader.
*
* @return an empty object instance of the associated class
* @author mlh
*/
public T createObject();
public T createObject(ObjectLoader l);
/**
* Assign the given <code>int</code> to the specified instance variable
* of the given target object which is of the associated class
* (created by <code>createObject</code>).
*
* @param target the object to assign into
* @param variable the instance variable to receive the value
* @param value the <code>int</code> value to assign
* @author mlh
*/
public void set(ObjectLoader l,
T target, String variable, int value);
public void set(T target, String variable, int value);
/**
* Assign the given <code>long</code> to the specified instance variable
* of the given target object which is of the associated class
* (created by <code>createObject</code>).
*
* @param target the object to assign into
* @param variable the instance variable to receive the value
* @param value the <code>long</code> value to assign
* @author mlh
*/
public void set(ObjectLoader l,
T target, String variable, long value);
public void set(T target, String variable, long value);
/**
* Assign the given <code>double</code> to the specified instance
* variable of the given target object which is of the associated class
* (created by <code>createObject</code>).
*
* @param target the object to assign into
* @param variable the instance variable to receive the value
* @param value the <code>double</code> value to assign
* @author mlh
*/
public void set(ObjectLoader l,
T target, String variable, double value);
public void set(T target, String variable, double value);
/**
* Assign the given <code>boolean</code> to the specified instance
* variable of the given target object which is of the associated class
* (created by <code>createObject</code>).
*
* @param target the object to assign into
* @param variable the instance variable to receive the value
* @param value the <code>boolean</code> value to assign
* @author mlh
*/
public void set(ObjectLoader l,
T target, String variable, boolean value);
public void set(T target, String variable, boolean value);
/**
* Assign the given <code>char</code> to the specified instance
* variable of the given target object which is of the associated class
* (created by <code>createObject</code>).
*
* @param target the object to assign into
* @param variable the instance variable to receive the value
* @param value the <code>char</code> value to assign
* @author mlh
*/
public void set(ObjectLoader l,
T target, String variable, char value);
public void set(T target, String variable, char value);
/**
* Assign the given <code>Object</code> to the specified instance
* variable of the given target object which is of the associated class
* (created by <code>createObject</code>).
* <p>
* NOTE: This is how a <code>String</code> value will be set.
*
* @param target the object to assign into
* @param variable the instance variable to receive the value
* @param value the <code>Object</code> value to assign
* @author mlh
*/
public void set(ObjectLoader l,
T target, String variable, Object value);
public void set(T target, String variable, Object value);
/**
* Create the <code>Map</code> and assign it to the specified instance
* variable for the given target object associated with the loader's
* class (that is, created by <code>createObject</code>).
*
* @param target the object to assign into
* @param variable the instance variable to receive the value
* @param size the number of key-value entries in the serialized
* <code>Map</code>
* @author mlh
*/
public Map<?, ?> createMap(ObjectLoader l,
T target, String variable, int size);
public Map<?, ?> createMap(T target, String variable, int size);
/**
* Create the <code>Collection</code> and assign it to the specified
* instance variable for the given target object associated with the
* loader's class (that is, created by <code>createObject</code>).
*
* @param target the object to assign into
* @param variable the instance variable to receive the value
* @param size the number of key-value entries in the serialized
* <code>Collection</code>
* @author mlh
*/
public Collection<?> createCollection(ObjectLoader l,
T target,
String variable,
int size);
public Collection<?> createCollection(T target,
String variable,
int size);
/**
* Determine the actual class to use as the element type
* for the array for the specified instance variable
* to be created using reflection. The given
* class name is that of the element type of the array when
* it was serialized.
*
* @param variable the instance variable to receive the value
* @param className the class name of the element type of the
* array when it was serialized
* @return the class to use as the element type of the array being
* created
* @author mlh
*/
public Class<?> getArrayEltType(ObjectLoader l,
String variable,
String className);
public Class<?> getArrayEltType(String variable, String className);
/**
* Return the aggregate loader for aggregate objects that are assigned
* to the instance variable of objects associated with this loader.
*
* @param variable the instance variable to receive the value
* @return the aggregate loader for nested aggregate objects
* @author mlh
*/
public IAggregateLoader getLoader(ObjectLoader l, String variable);
public IAggregateLoader getLoader(String variable);
}
public static class ALoader<T> implements ILoader<T>
{
public void evolve(ObjectLoader l, T target)
{
evolve(target);
}
public void evolve(T target)
{
// Nothing to do, in general
}
}
/**
* This class represents a default implementation of an nested
* aggregate loader.
*
* @author mlh
*/
public static class AAggregateLoader extends ALoader<Object>
implements IAggregateLoader
{
public static final AAggregateLoader ONLY = new AAggregateLoader();
public Map<?, ?> createMap(ObjectLoader l, int size)
{
// Override this only if the ObjectLoader is really needed
return createMap(size);
}
public Map<?, ?> createMap(int size)
{
throw new UnsupportedOperationException("createMap");
}
public Collection<?> createCollection(ObjectLoader l, int size)
{
// Override this only if the ObjectLoader is really needed
return createCollection(size);
}
public Collection<?> createCollection(int size)
{
throw new UnsupportedOperationException("createCollection");
}
public Class<?> getArrayEltType(ObjectLoader l, String className)
{
// Override this only if the ObjectLoader is really needed
return getArrayEltType(className);
}
public Class<?> getArrayEltType(String className)
{
throw new UnsupportedOperationException("getArrayEltType");
}
public IAggregateLoader getLoader(ObjectLoader l)
{
// Override this only if the ObjectLoader is really needed
return getLoader();
}
public IAggregateLoader getLoader()
{
return ONLY;
}
public <K, V> void putInMap(ObjectLoader l, Map<K, V> m, K key, V v)
{
// Override this only if the ObjectLoader is really needed
putInMap(m, key, v);
}
public <K, V> void putInMap(Map<K, V> m, K key, V v)
{
m.put(key, v);
}
public <T> void addToCollection(ObjectLoader l,
Collection<? super T> c,
T v)
{
// Override this only if the ObjectLoader is really needed
addToCollection(c, v);
}
public <T> void addToCollection(Collection<? super T> c, T v)
{
c.add(v);
}
public void addToArray(ObjectLoader l, Object a, int i, Object v)
{
// Override this only if the ObjectLoader is really needed
addToArray(a, i, v);
}
public void addToArray(Object a, int i, Object v)
{
Array.set(a, i, v);
}
}
/**
* This represents a default implementation for an enumeration loader.
*/
public static class AEnumLoader implements IEnumLoader
{
public Object createEnum(ObjectLoader l, String persistentValue)
{
// Override this only if the ObjectLoader is really needed
return createEnum(persistentValue);
}
public Object createEnum(String persistentValue)
{
throw new UnsupportedOperationException("createEnum");
}
}
/**
* This represents a default implementation for an object loader
* that assumes that none of the instance variables are aggregates.
* <p>
* If building a loader for an abstract superclass, one need not
* overide <code>createObject</code>.
*
* @author mlh
*/
public static class AObjectLoader<T> extends ALoader<T>
implements IObjectLoader<T>
{
public T createObject(ObjectLoader l)
{
// Override this only if the ObjectLoader is really needed
return createObject();
}
public T createObject()
{
throw new UnsupportedOperationException("createObject");
}
public void set(ObjectLoader l,
T target, String variable, int value)
{
// Override this only if the ObjectLoader is really needed
set(target, variable, value);
}
public void set(T target, String variable, int value)
{
throw new UnsupportedOperationException("set int");
}
public void set(ObjectLoader l,
T target, String variable, long value)
{
// Override this only if the ObjectLoader is really needed
set(target, variable, value);
}
public void set(T target, String variable, long value)
{
throw new UnsupportedOperationException("set long");
}
public void set(ObjectLoader l,
T target, String variable, double value)
{
// Override this only if the ObjectLoader is really needed
set(target, variable, value);
}
public void set(T target, String variable, double value)
{
throw new UnsupportedOperationException("set double");
}
public void set(ObjectLoader l,
T target, String variable, boolean value)
{
// Override this only if the ObjectLoader is really needed
set(target, variable, value);
}
public void set(T target, String variable, boolean value)
{
throw new UnsupportedOperationException("set boolean");
}
public void set(ObjectLoader l,
T target, String variable, char value)
{
// Override this only if the ObjectLoader is really needed
set(target, variable, value);
}
public void set(T target, String variable, char value)
{
throw new UnsupportedOperationException("set char");
}
public void set(ObjectLoader l,
T target, String variable, Object value)
{
// Override this only if the ObjectLoader is really needed
set(target, variable, value);
}
public void set(T target, String variable, Object value)
{
throw new UnsupportedOperationException("set object");
}
public Map<?, ?> createMap(ObjectLoader l,
T target, String variable, int size)
{
// Override this only if the ObjectLoader is really needed
return createMap(target, variable, size);
}
public Map<?, ?> createMap(T target, String variable, int size)
{
throw new UnsupportedOperationException("createMap");
}
public Collection<?> createCollection(ObjectLoader l,
T target,
String variable,
int size)
{
// Override this only if the ObjectLoader is really needed
return createCollection(target, variable, size);
}
public Collection<?> createCollection(T target,
String variable,
int size)
{
throw new UnsupportedOperationException("createCollection");
}
public Class<?> getArrayEltType(ObjectLoader l,
String variable,
String className)
{
// Override this only if the ObjectLoader is really needed
return getArrayEltType(variable, className);
}
public Class<?> getArrayEltType(String variable, String className)
{
throw new UnsupportedOperationException("getArrayEltType");
}
public IAggregateLoader getLoader(ObjectLoader l, String variable)
{
// Override this only if the ObjectLoader is really needed
return getLoader(variable);
}
public IAggregateLoader getLoader(String variable)
{
return AAggregateLoader.ONLY;
}
}
public interface ILoaderRegistry
{
/**
* Every object must register a loader that specifies how to reconstruct
* its value from a serialization of the given format version.
*
* @param className the name of the class; this should almost always be
* the value of <code>C.class.getName()</code>
* @param version the version of the format that was used to serialize
* the object
* @param loader the loader object that controls how to restore a value
* of the given type serialized to the given format version
* @author mlh
*/
public <E> void registerLoader(String className,
int version,
IObjectLoader<E> loader);
/**
* Every object must register a loader that specifies how to reconstruct
* its value from a serialization. This method returns the loader
* associated with the given class name for the given format version.
*
* @param className the name of the class; this should almost always be
* the value of <code>C.class.getName()</code>
* @param version the version of the format that was used to serialize
* the object
* @return the loader object that controls how to restore a value
* of the given type serialized to the given format version,
* or <code>null</code> if none was registered
* @author mlh
*/
public <E> IObjectLoader<E> getLoader(String className, int version);
/**
* Every enumerated type must register a loader that specifies how to
* reconstruct its value from a serialization of the given format version.
*
* @param className the name of the class; this should almost always be
* the value of <code>C.class.getName()</code>
* @param version the version of the format that was used to serialize
* the object
* @param loader the loader object that controls how to restore a value
* of the given type serialized to the given format version
* @author mlh
*/
public void registerEnumLoader(String className,
int version,
IEnumLoader loader);
/**
* Every enumerated type must register a loader that specifies how to
* reconstruct its value from a serialization. This method returns the
* loader associated with the given class name for the given format
* version.
*
* @param className the name of the class; this should almost always be
* the value of <code>C.class.getName()</code>
* @param version the version of the format that was used to serialize
* the object
* @return the loader object that controls how to restore a value
* of the given type serialized to the given format version,
* or <code>null</code> if none was registered
* @author mlh
*/
public IEnumLoader getEnumLoader(String className, int version);
}
/**
* Default implementation, especially for the default registry.
*/
public static class DefaultLoaderRegistry implements ILoaderRegistry
{
// registry of IObjectLoaders mapping
// (class-name + '#' + version) to ILoader or IEnumLoader
protected Map<String, Object> loaderRegistry =
new HashMap<String, Object>();
protected String buildKey(String className, int version)
{
return className + "#" + Integer.toString(version);
}
public <E> void registerLoader(String className,
int version,
IObjectLoader<E> loader)
{
loaderRegistry.put(buildKey(className, version), loader);
}
@SuppressWarnings("unchecked")
public <E> IObjectLoader<E> getLoader(String className, int version)
{
return (IObjectLoader<E>) loaderRegistry.get(buildKey(className,
version));
}
public void registerEnumLoader(String className,
int version,
IEnumLoader loader)
{
loaderRegistry.put(buildKey(className, version), loader);
}
public IEnumLoader getEnumLoader(String className, int version)
{
return (IEnumLoader) loaderRegistry.get(buildKey(className,
version));
}
}
/**
* Allows the caller to provide different behavior for a subset of
* objects, maintaining "normal" behavior for other objects.
*/
public static class OverrideLoaderRegistry extends DefaultLoaderRegistry
{
protected ILoaderRegistry overriddenRegistry;
public OverrideLoaderRegistry(ILoaderRegistry overridden)
{
overriddenRegistry = overridden;
}
@Override
public <E> IObjectLoader<E> getLoader(String className, int version)
{
IObjectLoader<E> loader = super.getLoader(className, version);
if (loader != null) {
return loader;
}
return overriddenRegistry.getLoader(className, version);
}
@Override
public IEnumLoader getEnumLoader(String className, int version)
{
IEnumLoader loader = super.getEnumLoader(className, version);
return (loader != null)
? loader
: overriddenRegistry.getEnumLoader(className,
version);
}
}
/**
* Default registry for savers.
*/
public static final ILoaderRegistry DEFAULT_REGISTRY =
new DefaultLoaderRegistry();
// Registry to use for the loading process
protected ILoaderRegistry loaderRegistry;
// Stack of active loaders; although all elements are of type ILoader,
// the actual type of loader at the top of the stack is determined by
// the current objectState:
// if (objectState == IN_OBJECT), then (loader instanceof IObjectLoader)
// otherwise, (loader instanceof IAggregateLoader)
protected Stack<ILoader<Object>> activeLoaders =
new Stack<ILoader<Object>>();
// Stack of pending Objects; the top element is the
// current object being reconstituted
protected Stack<Object> pendingObjects = new Stack<Object>();
// maps Integer(id) to Object
protected Map<Integer, Object> loadedObjects =
new HashMap<Integer, Object>();
// pending array indexes
protected static final int ARRAY_STACK_DEPTH = 100;
protected int[] arrayIndexes = new int[ARRAY_STACK_DEPTH];
protected int arrayStackTop = -1;
static SAXParserFactory parserFactory = SAXParserFactory.newInstance();
// Assignment states for String and char
protected static final int NORMAL = 0;
protected static final int IN_STRING = 1;
protected static final int IN_CHAR = 2;
protected static final int IN_BYTES = 3;
protected static final int ASSIGNED = -1;
protected int assignmentState = NORMAL;
// Save the id attribute for delayed construction objects (i.e., BYTE array)
protected int idref;
// Nesting states for object construction
protected static final char NONE = ' ';
protected static final char IN_OBJECT = 'o';
protected static final char IN_ARRAY = 'a';
protected static final char IN_COLLECTION = 'c';
protected static final char IN_MAP = 'm';
protected static final char IN_KEY = 'k';
// Current state
protected char objectState = NONE;
// Acting as a stack of nested object states
protected StringBuilder stateStack = new StringBuilder();
// The key value of a mapping entry (assigned when IN_KEY)
protected Object entryKey;
// The name of the instance variable for later invocation of "set"
// for STRING, BYTES, and CHAR
protected String pendingVariable;
// The number of (total) characters in the STRING or BYTES.
// For STRING, this will be one more than the actual STRING value.
protected int stringLength;
// The "characters" method gets called multiple times for long
// STRING or BYTES values (because reading is buffered). The value
// is accumulated using this StringBuilder.
protected StringBuilder accumulator = new StringBuilder();
/**
* Constructor -- the created object may be used for multiple loads.
*
* @author mlh
*/
public ObjectLoader()
{
this(null);
}
/**
* Constructor -- the created object may be used for multiple loads.
*
* @param registry registry that holds ILoader and IEnumLoader instances
* to support loading saved objects
* @author mlh
*/
public ObjectLoader(ILoaderRegistry registry)
{
loaderRegistry = (registry != null) ? registry : DEFAULT_REGISTRY;
}
/**
* Every object must register a loader that specifies how to reconstruct
* its value from a serialization of the given format version.
*
* @param className the name of the class; this should almost always be
* the value of <code>C.class.getName()</code>
* @param version the version of the format that was used to serialize
* the object
* @param loader the loader object that controls how to restore a value
* of the given type serialized to the given format version
* @author mlh
*/
public static <T> void registerLoader(String className,
int version,
IObjectLoader<T> loader)
{
DEFAULT_REGISTRY.registerLoader(className, version, loader);
}
/**
* Every enumerated type must register a loader that specifies how to
* reconstruct its value from a serialization of the given format version.
*
* @param className the name of the class; this should almost always be
* the value of <code>C.class.getName()</code>
* @param version the version of the format that was used to serialize
* the object
* @param loader the loader object that controls how to restore a value
* of the given type serialized to the given format version
* @author mlh
*/
public static void registerEnumLoader(String className,
int version,
IEnumLoader loader)
{
DEFAULT_REGISTRY.registerEnumLoader(className, version, loader);
}
/**
* The object being assigned into an aggregate (Array, Collection, or Map)
* may need to know which objects it is nested within (e.g., for setting
* "parentage"). This method may be used to fetch those nested objects.
* <p>
* Indexes are zero-based; increasing indexes yield deeper nested
* pending objects. In particular, to fetch the aggregate object itself,
* use an index of zero. An index of one, therefore, yields the object
* that contains the aggregate.
*
* @param depthIndex "nested-ness" of the pending object desired
* @return the object at the specified depth, or <code>null</code>
* if the index is greater than or equal to the number
* of pending objects
*/
public Object getPendingObject(int depthIndex)
{
if (depthIndex < 0) {
throw new IllegalArgumentException("Index cannot be negative.");
}
int lastValidStackIndex = pendingObjects.size() - 1;
if (depthIndex > lastValidStackIndex) {
return null;
}
return pendingObjects.get(lastValidStackIndex - depthIndex);
}
public <T> T getPendingObject(Class<T> ofType)
{
return getPendingObject(ofType, 0);
}
public <T> T getPendingObject(Class<T> ofType, int depthIndexOfType)
{
if (depthIndexOfType < 0) {
throw new IllegalArgumentException("Index cannot be negative.");
}
for (int i = pendingObjects.size() - 1; i >= 0; i--) {
Object pendingObject = pendingObjects.get(i);
if (ofType.isInstance(pendingObject)) {
if (depthIndexOfType == 0) {
return ofType.cast(pendingObject);
}
depthIndexOfType--;
}
}
return null;
}
/**
* Based on the serialization contained by the given input source
* and the initial aggregate loader (that is, for top-level objects
* that are themselves aggregates), reconstitute the top-level objects
* and return them in an ordered collection.
* <p>
* The caller is responsible for recovering any resources associated
* with the input source (such as <code>close()</code>).
*
* @param src the previously saved serialization of a sequence of objects
* @param initialLoader the loader to use for creating top-level aggregate
* objects; it may be <code>null</code> if no
* top-level object is an aggregate
* @throws ParserConfigurationException based on the SAX parsing
* @throws SAXException based on the SAX parsing
* @throws java.io.IOException if the source generates one
* during read calls generated by the SAX parser
* @throws java.io.IOException
*/
@SuppressWarnings("unchecked")
public List<Object> load(InputSource src, IAggregateLoader initialLoader)
throws ParserConfigurationException, SAXException, java.io.IOException
{
activeLoaders.push((initialLoader != null)
? initialLoader
: AAggregateLoader.ONLY);
// A List will keep the reconstituted objects in order
pendingObjects.push(new ArrayList<Object>());
pushObjectState(IN_COLLECTION);
SAXParser p = parserFactory.newSAXParser();
// The List is populated during the parse
p.parse(src, this);
return (List<Object>) pendingObjects.peek();
}
/**
* There is a corresponding object state for each nested object
* construction. Also, IN_KEY is pushed when the key component
* of a mapping entry is encountered.
* <p>
* The stack of nested object states is kept as a <code>StringBuilder</code>
* so we don't have to create actual objects for the states.
*
* @param newObjectState the object state that should become current
* @author mlh
*/
protected void pushObjectState(char newObjectState)
{
stateStack.append(objectState);
objectState = newObjectState;
}
/**
* Pop the most recent pending nested object state from the stack
* and make it the current state.
*
* @author mlh
*/
protected void popObjectState()
{
int lastIndex = stateStack.length() - 1;
objectState = stateStack.charAt(lastIndex);
stateStack.deleteCharAt(lastIndex);
}
/**
* When reconstructing a nested object, the current object's
* reconstruction must be "paused". The top of the stack of pending
* objects is the current object being reconstructed.
* <p>
* Push the new object to be reconstructed, push the new state
* reflecting the nature of the object (e.g., array, map, or normal),
* push the loader that supports the reconstruction of the object,
* and register the object in the <code>loadedObjects</code> registry
* (so that nested references to this object are recognized).
*
* @param attrs the attributes used to fetch the "id" value
* for registering the object
* @param value the new "current" object to be reconstructed
* @param loader the loader object that controls how to restore the value
* @param newState the object state reflecting the nature of the new object
* @author mlh
*/
protected void pushObject(Attributes attrs,
Object value,
ILoader<Object> loader,
char newState)
{
loadedObjects.put(new Integer(getIntAttribute(attrs, ID_ATTR)),
value);
pendingObjects.push(value);
activeLoaders.push(loader);
pushObjectState(newState);
}
/**
* Array elements are stored in index order; we need to keep track of the
* current index for assigning nested elements into the proper index.
* If we encounter an array of arrays, we need to "pause" the index
* assignment of the outer array and start assigning from zero for
* the nested array.
*
* @author mlh
*/
protected void pushArrayIndex()
{
if (++arrayStackTop >= ARRAY_STACK_DEPTH) {
arrayIndexes[arrayStackTop] = 0;
}
else {
throw new IndexOutOfBoundsException("array nesting too deep");
}
}
/**
* When we are done with a nested array, we can pop the stack of
* current index values so that the outer array (if one exists)
* can resume assigning nested elements into the proper index.
*
* @author mlh
*/
protected void popArrayIndex()
{
arrayStackTop--;
}
/**
* Fetch the current index value for the current array for assigning
* the next nested array element, then increment the current index
* value for the next assignment (if any).
*
* @return the current index value for the current array being
* reconstructed
* @author mlh
*/
protected int nextArrayIndex()
{
return arrayIndexes[arrayStackTop]++;
}
// Debugging support
protected void printAttrs(Attributes attrs)
{
int count = attrs.getLength();
for (int i = 0; i < count; i++) {
System.out.println(" " + attrs.getQName(i)
+ ": " + attrs.getValue(i));
}
}
/**
* A shortcut for fetching an attribute that should be an <code>int</code>.
* Note that a null access exception will be thrown if the requested
* attribute is not in the given set.
*
* @param attrs the set of attributes for the current XML element
* @param key the attribute name whose value should be an <code>int</code>
* @return the value of the requested attribute
* @author mlh
*/
protected int getIntAttribute(Attributes attrs, String key)
{
return Integer.parseInt(attrs.getValue(key));
}
/**
* A shortcut for fetching an attribute that should be an <code>long</code>.
* Note that a null access exception will be thrown if the requested
* attribute is not in the given set.
*
* @param attrs the set of attributes for the current XML element
* @param key the attribute name whose value should be an <code>long</code>
* @return the value of the requested attribute
* @author mlh
*/
protected long getLongAttribute(Attributes attrs, String key)
{
return Long.parseLong(attrs.getValue(key));
}
/**
* A shortcut for fetching an attribute that should be a
* <code>double</code>.
* Note that a null access exception will be thrown if the requested
* attribute is not in the given set.
*
* @param attrs the set of attributes for the current XML element
* @param key the attribute name whose value should be a
* <code>double</code>
* @return the value of the requested attribute
* @author mlh
*/
protected double getDoubleAttribute(Attributes attrs, String key)
{
return Double.parseDouble(attrs.getValue(key));
}
/**
* A shortcut for fetching an attribute that should be a
* <code>boolean</code>. Note that a null access exception will be
* thrown if the requested attribute is not in the given set.
* Note also that it expects the attribute value to be either
* <code>BOOL_TRUE</code> or <code>BOOL_FALSE</code>.
*
* @param attrs the set of attributes for the current XML element
* @param key the attribute name whose value should be a
* <code>boolean</code>
* @return the value of the requested attribute
* @author mlh
*/
protected boolean getBooleanAttribute(Attributes attrs, String key)
{
return attrs.getValue(key).equalsIgnoreCase(BOOL_TRUE);
}
/**
* A shortcut for fetching an attribute that should be a
* <code>char</code>. (This is probably never used.)
* Note that a null access exception will be thrown if the requested
* attribute is not in the given set.
*
* @param attrs the set of attributes for the current XML element
* @param key the attribute name whose value should be a
* <code>char</code>
* @return the value of the requested attribute
* @author mlh
*/
protected char getCharAttribute(Attributes attrs, String key)
{
return attrs.getValue(key).charAt(0);
}
/**
* A shortcut for fetching an attribute that should be a
* <code>String</code>. Callers are responsible for checking for a
* <code>null</code> return, if desired.
*
* @param attrs the set of attributes for the current XML element
* @param key the attribute name whose value should be a
* <code>String</code>
* @return the value of the requested attribute if it exists;
* <code>null</code> otherwise
* @author mlh
*/
protected String getStringAttribute(Attributes attrs, String key)
{
return attrs.getValue(key);
}
/**
* A shortcut for fetching the value of the attribute representing an
* instance variable name.
*
* @param attrs the set of attributes for the current XML element
* @return the value of the instance variable name attribute
* @throws UnexpectedException if the attribute is not in the given set
* @author mlh
*/
protected String getVariable(Attributes attrs)
{
String variable = getStringAttribute(attrs, VAR_ATTR);
// If we called this, we fully expect a value to exist.
if (variable == null) {
throw new IllegalStateException("variable attribute not found");
}
return variable;
}
/**
* See the three-parameter addObject.
* This version gets the current loader from the top of the
* activeLoaders stack.
*/
protected void addObject(Attributes attrs, Object value)
{
addObject(activeLoaders.peek(), attrs, value);
}
/**
* Add the given object (the next to be reconstituted) to the current
* object being reconstituted. The given object, if necessary, will
* become the "new" current object being reconstituted (unless, for
* example, the new object is an enumeration value).
* <p>
* The current object state determines the nature of the current
* object being reconstituted. If the current object state is
* <code>IN_KEY</code>, we simply remember the key value for later
* reconstruction of the key-value pair for the current mapping object.
* This occurs when the value part is "added" in the <code>IN_MAP</code>
* state.
* <p>
* The given attributes are used to determine to which instance variable
* the new, given object belongs if the current object is a
* non-aggregate object.
*
* @param loader the loader to control how to add the given value
* @param attrs the attributes used to fetch the instance variable name
* @param value the object just created to be added to the current object
* @throws UnexpectedException if the current object state is unknown
* @author mlh
*/
@SuppressWarnings("unchecked")
protected void addObject(ILoader<Object> loader,
Attributes attrs,
Object value)
{
switch (objectState) {
case IN_OBJECT: {
IObjectLoader<Object> objectLoader =
(IObjectLoader<Object>) loader;
// Assigning to an instance variable in a normal object
objectLoader.set(this,
pendingObjects.peek(),
getVariable(attrs),
value);
break;
}
case IN_ARRAY: {
IAggregateLoader aggLoader = (IAggregateLoader) loader;
// Assigning to the next index in the current array object
aggLoader.addToArray(this,
pendingObjects.peek(),
nextArrayIndex(),
value);
break;
}
case IN_COLLECTION: {
IAggregateLoader aggLoader = (IAggregateLoader) loader;
// Adding to the current collection object
aggLoader.addToCollection(this,
(Collection<Object>)
pendingObjects.peek(),
value);
break;
}
case IN_KEY: {
// Remember the key for the current key-value mapping entry
entryKey = value;
break;
}
case IN_MAP: {
IAggregateLoader aggLoader = (IAggregateLoader) loader;
// Use the remembered key and the given value to build
// the next key-value entry in the current mapping object
aggLoader.putInMap(this,
(Map<Object, Object>)
pendingObjects.peek(),
entryKey,
value);
break;
}
default: {
throw new IllegalStateException("Unknown state");
}
}
} // addObject
@Override
public void startDocument()
throws SAXException
{
// System.out.println("startDocument");
}
@Override
@SuppressWarnings("unchecked")
public void startElement(String uri,
String localName,
String qName,
Attributes attrs)
throws SAXException
{
// use qName
// System.out.println("startElement-- qName: " + qName);
// printAttrs(attrs);
if (qName.equalsIgnoreCase(KEY_ELT)) {
// Only need to change the object state to "remember" the key
// of the next key-value mapping entry. We could (I suppose)
// ensure that we are currently in the IN_MAP state.
pushObjectState(IN_KEY);
}
else if (qName.equalsIgnoreCase(ARRAY_ELT)) {
Object value;
IAggregateLoader aggregateLoader;
// Regardless of the current state, we will need the number
// of array elements and the base element class.
int eltCount = getIntAttribute(attrs, SIZE_ATTR);
String eltClass = getStringAttribute(attrs, CLASS_ATTR);
// If we are currently reconstituting a normal object, we
// need to assign the created array to the appropriate instance
// variable. We know that the loader is an IObjectLoader.
if (objectState == IN_OBJECT) {
IObjectLoader<Object> loader =
(IObjectLoader<Object>) activeLoaders.peek();
String variable = getVariable(attrs);
// Create the array using the element type returned by
// the loader for this variable.
value = Array.newInstance(loader.getArrayEltType(this,
variable,
eltClass),
eltCount);
// Currently, we insist that an array is actually created.
if (value == null) {
throw new IllegalStateException("array create was not successful");
}
// Assign the array to the appropriate instance variable
loader.set(this, pendingObjects.peek(), variable, value);
// Fetch the aggregate loader for controlling reconstruction
// of aggregate objects that are nested within this array.
aggregateLoader = loader.getLoader(this, variable);
}
else {
// The current object being reconstituted is a mapping,
// collection, or itself an array. Thus, we know the loader
// is an IAggregateLoader.
IAggregateLoader loader =
(IAggregateLoader) activeLoaders.peek();
// Create the array using the element type returned by
// the loader.
value =
Array.newInstance(loader.getArrayEltType(this, eltClass),
eltCount);
// Currently, we insist that an array is actually created.
if (value == null) {
throw new IllegalStateException("array create was not successful");
}
// Add the object to the current object as appropriate
// (mapping, collection, or array).
// This could be used instead of the "set" call in the
// IN_OBJECT case above, but we already had the right loader.
addObject(loader, attrs, value);
// Fetch the aggregate loader for controlling reconstruction
// of aggregate objects that are nested within this array.
aggregateLoader = loader.getLoader(this);
}
// "Pause" any outer array index tracking and create an new
// index counter for this array starting at zero.
pushArrayIndex();
// Push state, object, and loader, and register object
pushObject(attrs, value, aggregateLoader, IN_ARRAY);
}
else if (qName.equalsIgnoreCase(COLLECTION_ELT)) {
Collection<?> value;
IAggregateLoader aggregateLoader;
// Although most collection construction doesn't care, we
// pass the element count in case some optimization is possible.
int eltCount = getIntAttribute(attrs, SIZE_ATTR);
// If we are currently reconstituting a normal object, we
// need to create and assign the created collection to the
// appropriate instance variable. We know that the loader
// is an IObjectLoader.
if (objectState == IN_OBJECT) {
IObjectLoader<Object> loader =
(IObjectLoader<Object>) activeLoaders.peek();
String variable = getVariable(attrs);
// Create and assign the collection based on the specified
// instance variable using the loader.
value = loader.createCollection(this,
pendingObjects.peek(),
variable,
eltCount);
// Currently, we insist that a collection is actually created.
if (value == null) {
throw new IllegalStateException("collection create was not successful");
}
// Fetch the aggregate loader for controlling reconstruction
// of aggregate objects that are nested within this collection.
aggregateLoader = loader.getLoader(this, variable);
}
else {
// The current object being reconstituted is a mapping,
// array, or itself a collection. Thus, we know the loader
// is an IAggregateLoader.
IAggregateLoader loader =
(IAggregateLoader) activeLoaders.peek();
// Create the nested collection.
value = loader.createCollection(this, eltCount);
// Currently, we insist that a collection is actually created.
if (value == null) {
throw new IllegalStateException("collection create was not successful");
}
// Add the object to the current object as appropriate
// (mapping, collection, or array). Note that, since we needed
// to create the collection in the IN_OBJECT case above,
// we *cannot* use this call in that state.
addObject(loader, attrs, value);
// Fetch the aggregate loader for controlling reconstruction
// of aggregate objects that are nested within this collection.
aggregateLoader = loader.getLoader(this);
}
// Push state, object, and loader, and register object
pushObject(attrs, value, aggregateLoader, IN_COLLECTION);
}
else if (qName.equalsIgnoreCase(MAP_ELT)) {
Map<?, ?> value;
IAggregateLoader aggregateLoader;
// Although most mapping construction doesn't care, we
// pass the element count in case some optimization is possible.
int eltCount = getIntAttribute(attrs, SIZE_ATTR);
// If we are currently reconstituting a normal object, we
// need to create and assign the created mapping to the
// appropriate instance variable. We know that the loader
// is an IObjectLoader.
if (objectState == IN_OBJECT) {
IObjectLoader<Object> loader =
(IObjectLoader<Object>) activeLoaders.peek();
String variable = getVariable(attrs);
// Create and assign the mapping based on the specified
// instance variable using the loader.
value = loader.createMap(this,
pendingObjects.peek(),
variable,
eltCount);
// Currently, we insist that a mapping is actually created.
if (value == null) {
throw new IllegalStateException("map create was not successful for "
+ variable);
}
// Fetch the aggregate loader for controlling reconstruction
// of aggregate objects that are value components within
// this mapping.
aggregateLoader = loader.getLoader(this, variable);
}
else {
// The current object being reconstituted is a collection,
// array, or itself a mapping. Thus, we know the loader
// is an IAggregateLoader.
IAggregateLoader loader =
(IAggregateLoader) activeLoaders.peek();
// Create the nested mapping.
value = loader.createMap(this, eltCount);
// Currently, we insist that a mapping is actually created.
if (value == null) {
throw new IllegalStateException("map create was not successful");
}
// Add the object to the current object as appropriate
// (mapping, collection, or array). Note that, since we needed
// to create the mapping in the IN_OBJECT case above,
// we *cannot* use this call in that state.
addObject(loader, attrs, value);
// Fetch the aggregate loader for controlling reconstruction
// of aggregate objects that are value components within
// this mapping.
aggregateLoader = loader.getLoader(this);
}
// Push state, object, and loader, and register object
pushObject(attrs, value, aggregateLoader, IN_MAP);
}
else if (qName.equalsIgnoreCase(OBJ_ELT)) {
// Trying to reconstitute a non-aggregate, non-enumeration object;
// fetch the registered loader.
String className = getStringAttribute(attrs, CLASS_ATTR);
int savedVersion = getIntAttribute(attrs, VERSION_ATTR);
IObjectLoader<Object> loader =
loaderRegistry.getLoader(className, savedVersion);
// The loader is necessary.
if (loader == null) {
throw new IllegalStateException("Object class loader not "
+ "found for class:"
+ className
+ " version "
+ savedVersion);
}
// Ask the loader to create a new, empty object of the right type.
Object newObj = loader.createObject(this);
// Currently, we insist that the object is actually created.
if (newObj == null) {
throw new IllegalStateException("Object creation was not "
+ "successful for class:"
+ className
+ " version "
+ savedVersion);
}
// Add the object to the current object as appropriate (object,
// mapping, collection, or array).
addObject(attrs, newObj);
// Push state, object, and loader, and register object
pushObject(attrs, newObj, loader, IN_OBJECT);
}
else if (qName.equalsIgnoreCase(SUPER_ELT)) {
// At this point, we're trying to reconstitute the data
// associated with a superclass of the current object's type;
// fetch the registered loader.
String className = getStringAttribute(attrs, CLASS_ATTR);
int savedVersion = getIntAttribute(attrs, VERSION_ATTR);
IObjectLoader<Object> loader =
loaderRegistry.getLoader(className, savedVersion);
// The loader is necessary.
if (loader == null) {
throw new IllegalStateException("Super class loader not found "
+ "for class:"
+ className
+ " version "
+ savedVersion);
}
// The object doesn't change, nor the state (we don't need to know
// that we're in a superclass part), but the loader that controls
// the reconstitution does change -- push that.
activeLoaders.push(loader);
}
else if (qName.equalsIgnoreCase(REF_ELT)) {
// The object this reference represents has been saved earlier
// in the serialization (it may also be in the process of being
// reconstituted); it should have registered.
int id = getIntAttribute(attrs, IDREF_ATTR);
Object refObject = loadedObjects.get(new Integer(id));
// If not found, complain!
if (refObject == null) {
throw new IllegalStateException("ref object with id="
+ id + " not found");
}
// Add the object to the current object as appropriate (object,
// mapping, collection, or array).
addObject(attrs, refObject);
}
else if (qName.equalsIgnoreCase(NULL_ELT)) {
// Add null to the current object as appropriate (object,
// mapping, collection, or array).
addObject(attrs, null);
}
else if (qName.equalsIgnoreCase(INT_ELT)) {
// The int value is kept directly as an attribute
int value = getIntAttribute(attrs, VALUE_ATTR);
// Based on the current object's nature, assign the value.
switch (objectState) {
case IN_OBJECT: {
IObjectLoader<Object> loader =
(IObjectLoader<Object>) activeLoaders.peek();
// Assign to the appropriate instance variable
loader.set(this,
pendingObjects.peek(),
getVariable(attrs),
value);
break;
}
case IN_ARRAY: {
// Assign to the next index in the current array
Array.setInt(pendingObjects.peek(),
nextArrayIndex(),
value);
break;
}
case IN_COLLECTION: {
IAggregateLoader loader =
(IAggregateLoader) activeLoaders.peek();
// Add the value as an object to the current collection
loader.addToCollection(this,
(Collection<Object>)
pendingObjects.peek(),
new Integer(value));
break;
}
case IN_KEY: {
// Remember the value (as an object) as the key
// for the next key-value pair in the current mapping
entryKey = new Integer(value);
break;
}
case IN_MAP: {
IAggregateLoader loader =
(IAggregateLoader) activeLoaders.peek();
// Assign the remembered key and this value (as an object)
// as the next key-value pair in the current mapping
loader.putInMap(this,
(Map<Object, Object>)
pendingObjects.peek(),
entryKey,
new Integer(value));
break;
}
default: {
throw new IllegalStateException("Unknown state");
}
}
}
else if (qName.equalsIgnoreCase(LONG_ELT)) {
// The long value is kept directly as an attribute
long value = getLongAttribute(attrs, VALUE_ATTR);
// Based on the current object's nature, assign the value.
switch (objectState) {
case IN_OBJECT: {
IObjectLoader<Object> loader =
(IObjectLoader<Object>) activeLoaders.peek();
// Assign to the appropriate instance variable
loader.set(this,
pendingObjects.peek(),
getVariable(attrs),
value);
break;
}
case IN_ARRAY: {
// Assign to the next index in the current array
Array.setLong(pendingObjects.peek(),
nextArrayIndex(),
value);
break;
}
case IN_COLLECTION: {
IAggregateLoader loader =
(IAggregateLoader) activeLoaders.peek();
// Add the value as an object to the current collection
loader.addToCollection(this,
(Collection<Object>)
pendingObjects.peek(),
new Long(value));
break;
}
case IN_KEY: {
// Remember the value (as an object) as the key
// for the next key-value pair in the current mapping
entryKey = new Long(value);
break;
}
case IN_MAP: {
IAggregateLoader loader =
(IAggregateLoader) activeLoaders.peek();
// Assign the remembered key and this value (as an object)
// as the next key-value pair in the current mapping
loader.putInMap(this,
(Map<Object, Object>)
pendingObjects.peek(),
entryKey,
new Long(value));
break;
}
default: {
throw new IllegalStateException("Unknown state");
}
}
}
else if (qName.equalsIgnoreCase(DOUBLE_ELT)) {
// The double value is kept directly as an attribute
double value = getDoubleAttribute(attrs, VALUE_ATTR);
// Based on the current object's nature, assign the value.
switch (objectState) {
case IN_OBJECT: {
IObjectLoader<Object> loader =
(IObjectLoader<Object>) activeLoaders.peek();
// Assign to the appropriate instance variable
loader.set(this,
pendingObjects.peek(),
getVariable(attrs),
value);
break;
}
case IN_ARRAY: {
// Assign to the next index in the current array
Array.setDouble(pendingObjects.peek(),
nextArrayIndex(),
value);
break;
}
case IN_COLLECTION: {
IAggregateLoader loader =
(IAggregateLoader) activeLoaders.peek();
// Add the value as an object to the current collection
loader.addToCollection(this,
(Collection<Object>)
pendingObjects.peek(),
new Double(value));
break;
}
case IN_KEY: {
// Remember the value (as an object) as the key
// for the next key-value pair in the current mapping
entryKey = new Double(value);
break;
}
case IN_MAP: {
IAggregateLoader loader =
(IAggregateLoader) activeLoaders.peek();
// Assign the remembered key and this value (as an object)
// as the next key-value pair in the current mapping
loader.putInMap(this,
(Map<Object, Object>)
pendingObjects.peek(),
entryKey,
new Double(value));
break;
}
default: {
throw new IllegalStateException("Unknown state");
}
}
}
else if (qName.equalsIgnoreCase(BOOL_ELT)) {
// The boolean value is kept directly as an attribute
boolean value = getBooleanAttribute(attrs, VALUE_ATTR);
// Based on the current object's nature, assign the value.
switch (objectState) {
case IN_OBJECT: {
IObjectLoader<Object> loader =
(IObjectLoader<Object>) activeLoaders.peek();
// Assign to the appropriate instance variable
loader.set(this,
pendingObjects.peek(),
getVariable(attrs),
value);
break;
}
case IN_ARRAY: {
// Assign to the next index in the current array
Array.setBoolean(pendingObjects.peek(),
nextArrayIndex(),
value);
break;
}
case IN_COLLECTION: {
IAggregateLoader loader =
(IAggregateLoader) activeLoaders.peek();
// Add the value as an object to the current collection
loader.addToCollection(this,
(Collection<Object>)
pendingObjects.peek(),
new Boolean(value));
break;
}
case IN_KEY: {
// Remember the value (as an object) as the key
// for the next key-value pair in the current mapping
entryKey = new Boolean(value);
break;
}
case IN_MAP: {
IAggregateLoader loader =
(IAggregateLoader) activeLoaders.peek();
// Assign the remembered key and this value (as an object)
// as the next key-value pair in the current mapping
loader.putInMap(this,
(Map<Object, Object>)
pendingObjects.peek(),
entryKey,
new Boolean(value));
break;
}
default: {
throw new IllegalStateException("Unknown state");
}
}
}
else if (qName.equalsIgnoreCase(STR_ELT)) {
// String values are not kept as an attribute value but rather as
// text in a CDATA section between the XML start and end elements.
// Thus, the value must be assigned once it has been accumulated.
// If the current object is non-aggregate, then remember the
// instance variable for this string.
if (objectState == IN_OBJECT) {
pendingVariable = getVariable(attrs);
}
// The SAX parser incorrectly assumes a ']' at the end of our
// string value introduces end-of-CDATA; thus, ClassSaver
// always appends '@' to prevent the bug; add one to the length!
// Clear the accumulator for collecting string fragments that
// arise because the parser apparently buffers its reads.
stringLength = getIntAttribute(attrs, SIZE_ATTR) + 1;
assignmentState = IN_STRING;
accumulator.delete(0, accumulator.length());
}
else if (qName.equalsIgnoreCase(CHAR_ELT)) {
// A character value is not kept as an attribute value since
// it may not be a legal attribute value. Instead, it is placed
// in a CDATA section between the XML start and end elements.
// Thus, the value must be assigned once it has been accumulated.
// If the current object is non-aggregate, then remember the
// instance variable for this character value.
if (objectState == IN_OBJECT) {
pendingVariable = getVariable(attrs);
}
// The SAX parser incorrectly assumes a ']' as the
// character value introduces end-of-CDATA; thus, ClassSaver
// always appends '@' to prevent the bug! We may need to
// use the accumulator if buffering splits the two characters;
// clear the accumulator!
assignmentState = IN_CHAR;
accumulator.delete(0, accumulator.length());
}
else if (qName.equalsIgnoreCase(BYTES_ELT)) {
// An array of byte[] is treated differently from all other arrays.
// Its value is stored as a base64 encoded string between the XML
// start and end elements.
// Thus, the value must be assigned once it has been accumulated.
// If the current object is non-aggregate, then remember the
// instance variable for this byte[] value.
if (objectState == IN_OBJECT) {
pendingVariable = getVariable(attrs);
}
// Get the number of base64 characters and clear the accumulator
assignmentState = IN_BYTES;
idref = getIntAttribute(attrs, ID_ATTR);
stringLength = getIntAttribute(attrs, SIZE_ATTR);
accumulator.delete(0, accumulator.length());
}
else if (qName.equalsIgnoreCase(ENUM_ELT)) {
// At this point, we're trying to fetch the unique (singleton)
// value for an enumerated type. An IEnumLoader knows how to
// translate the persisted enumeration code into the appropriate
// value.
IEnumLoader loader =
loaderRegistry.getEnumLoader(getStringAttribute(attrs,
CLASS_ATTR),
getIntAttribute(attrs,
VERSION_ATTR));
// The loader is necessary.
if (loader == null) {
throw new IllegalStateException("enum class loader not found");
}
// Ask the loader to fetch the appropriate enumeration value
// for the persisted enumeration code.
Object newEnum =
loader.createEnum(this, getStringAttribute(attrs, VALUE_ATTR));
// Currently, we insist that a non-null enumeration value
// be fetched
if (newEnum == null) {
throw new IllegalStateException("enum create was not successful");
}
// Add the enumeration object to the current object as appropriate
// (object, mapping, collection, or array).
addObject(attrs, newEnum);
}
} // startElement
// We use this method to reset state.
@Override
public void endElement(String uri,
String localName,
String qName)
throws SAXException
{
// use qName
// System.out.println("endElement-- qName: " + qName);
if (qName.equalsIgnoreCase(ARRAY_ELT) ||
qName.equalsIgnoreCase(COLLECTION_ELT) ||
qName.equalsIgnoreCase(MAP_ELT) ||
qName.equalsIgnoreCase(OBJ_ELT))
{
// If we were constructing an object, we can pop the stacks:
// - for loaders associated with objects being constructed,
// - for objects being constructed
// - for the natures of the objects being constructed
// - and, if the current object is an array, the current
// index for placing array elements.
ILoader<Object> loader = activeLoaders.pop();
Object obj = pendingObjects.pop();
popObjectState();
if (qName.equalsIgnoreCase(ARRAY_ELT)) {
popArrayIndex();
}
// Make any type evolution changes to the object
loader.evolve(this, obj);
}
else if (qName.equalsIgnoreCase(SUPER_ELT)) {
// If we were dealing with reconstructing the data for one of the
// current object's superclasses, only a loader was pushed.
ILoader<Object> loader = activeLoaders.pop();
Object obj = pendingObjects.peek();
// Make any type evolution changes to the object
loader.evolve(this, obj);
}
else if (qName.equalsIgnoreCase(KEY_ELT)) {
// If we were dealing with the key value for the next entry
// of a current map object, only the state (IN_KEY) was pushed.
popObjectState();
}
else if (qName.equalsIgnoreCase(STR_ELT)) {
// If we were dealing with a string, reset the assignment state.
assignmentState = NORMAL;
}
else if (qName.equalsIgnoreCase(CHAR_ELT)) {
// If we were dealing with a char, reset the assignment state.
assignmentState = NORMAL;
}
else if (qName.equalsIgnoreCase(BYTES_ELT)) {
// If we were dealing with an array of bytes,
// reset the assignment state.
assignmentState = NORMAL;
}
} // endElement
@Override
@SuppressWarnings("unchecked")
public void characters(char[] str, int start, int length)
throws SAXException
{
// System.out.println("characters-- str: "
// + String.valueOf(str).substring(start,
// start + length)
// + " state: " + this.assignmentState
// + " start: " + start
// + " length: " + length);
// System.out.flush();
// If we are in the ASSIGNED state, we presumably have seen all
// of the characters needed for either STRING, BYTES, or CHAR.
// Something is wrong!
if (assignmentState == ASSIGNED) {
throw new IllegalStateException("incorrect state for assigning char(s)");
}
// Assignment can only occur if we have seen all of the characters
// expected for the given STRING, BYTES, or CHAR value.
// Apparently, the SAX parser buffers input, so we may see
// several calls to "characters" in a row with substrings.
boolean okToAssign = true;
if ((assignmentState == IN_STRING) ||
(assignmentState == IN_BYTES))
{
Object value = null; // stupid Java compiler
// Accumulate the value
accumulator.append(str, start, length);
if (assignmentState == IN_STRING) {
// SAX parser incorrectly assumes a ']' at the end of our
// string value introduces end-of-CDATA; thus, ClassSaver
// always appends '@' to prevent the bug; subtract one!
if (accumulator.length() == stringLength) {
value =
accumulator.substring(0, stringLength - 1);
}
else {
// We haven't seen enough yet; wait for a subsequent call
okToAssign = false;
}
}
else if (assignmentState == IN_BYTES) {
// A byte[] array was stored using base64 encoding; decode!
if (accumulator.length() == stringLength) {
value = Base64.decode(accumulator.toString());
loadedObjects.put(new Integer(idref), value);
}
else {
// We haven't seen enough yet; wait for a subsequent call
okToAssign = false;
}
}
// If we have seen all of the characters, assign
if (okToAssign) {
if (objectState == IN_OBJECT) {
// Need the loader for the current object being constructed
IObjectLoader<Object> loader =
(IObjectLoader<Object>) activeLoaders.peek();
// We don't use the clause in addObject because we have
// squirreled away the variable to assign to (and don't
// have attributes from which to [potentially] get it!)
loader.set(this,
pendingObjects.peek(),
pendingVariable,
value);
}
else {
// None of the other states require attributes.
addObject(null, value);
}
// We should see no additional characters before the endElement
assignmentState = ASSIGNED;
}
}
else if (assignmentState == IN_CHAR) {
// SAX parser incorrectly assumes a character value of ']'
// introduces end-of-CDATA; thus, ClassSaver always appends '@'
// to prevent the bug. Check for length of 2, but use only first!
char value = '\0'; // Java requires some value (ugh!)
// Check if buffering has split between the two characters!
if (length < 2) {
accumulator.append(str, start, length);
// If this is the second (i.e., the '@'), we can assign!
if (accumulator.length() == 2) {
value = accumulator.charAt(0);
}
else {
// Sure enough, buffering has split the two; wait!
okToAssign = false;
}
}
else if (length == 2) {
// We got both characters, so we can assign!
value = str[0];
}
else {
throw new IllegalStateException("char length incorrect");
}
// If we have seen the character and the trailing '@'; assign the
// value depending on the nature of the current object being
// reconstituted.
if (okToAssign) {
switch (objectState) {
case IN_OBJECT: {
IObjectLoader<Object> loader =
(IObjectLoader<Object>) activeLoaders.peek();
// Assign to the appropriate (saved) instance variable
loader.set(this,
pendingObjects.peek(),
pendingVariable,
value);
break;
}
case IN_ARRAY: {
// Assign to the next index in the current array
Array.setChar(pendingObjects.peek(),
nextArrayIndex(),
value);
break;
}
case IN_COLLECTION: {
IAggregateLoader loader =
(IAggregateLoader) activeLoaders.peek();
// Add the value as an object to the current collection
loader.addToCollection(this,
(Collection<Object>)
pendingObjects.peek(),
new Character(value));
break;
}
case IN_KEY: {
// Remember the value (as an object) as the key
// for the next key-value pair in the current mapping
entryKey = new Character(value);
break;
}
case IN_MAP: {
IAggregateLoader loader =
(IAggregateLoader) activeLoaders.peek();
// Assign the remembered key and this value (as an
// object) as the next key-value pair in the current
// mapping
loader.putInMap(this,
(Map<Object, Object>)
pendingObjects.peek(),
entryKey,
new Character(value));
break;
}
default: {
throw new IllegalStateException("Unknown state");
}
}
// We should see no additional characters before the endElement
assignmentState = ASSIGNED;
}
}
// else, we are in the NORMAL state and can ignore this call
// (it should be all white space!)
} // characters
@Override
public void endDocument()
throws SAXException
{
// System.out.println("endDocument");
}
@Override
public void error(SAXParseException e)
throws SAXException
{
System.out.println("error");
}
@Override
public void fatalError(SAXParseException e)
throws SAXException
{
System.out.println("fatalError");
}
@Override
public void warning(SAXParseException e)
throws SAXException
{
System.out.println("warning");
}
}