/*
 * Copyright (C) 2003, 2004, 2005, 2006 Joe Walnes.
 * Copyright (C) 2006, 2007, 2008 XStream Committers.
 * All rights reserved.
 *
 * The software in this package is published under the terms of the BSD
 * style license a copy of which has been included with this distribution in
 * the LICENSE.txt file.
 * 
 * Created on 26. September 2003 by Joe Walnes
 */
package com.thoughtworks.xstream;

import com.thoughtworks.xstream.alias.ClassMapper;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.ConverterLookup;
import com.thoughtworks.xstream.converters.ConverterRegistry;
import com.thoughtworks.xstream.converters.DataHolder;
import com.thoughtworks.xstream.converters.SingleValueConverter;
import com.thoughtworks.xstream.converters.SingleValueConverterWrapper;
import com.thoughtworks.xstream.converters.basic.BigDecimalConverter;
import com.thoughtworks.xstream.converters.basic.BigIntegerConverter;
import com.thoughtworks.xstream.converters.basic.BooleanConverter;
import com.thoughtworks.xstream.converters.basic.ByteConverter;
import com.thoughtworks.xstream.converters.basic.CharConverter;
import com.thoughtworks.xstream.converters.basic.DateConverter;
import com.thoughtworks.xstream.converters.basic.DoubleConverter;
import com.thoughtworks.xstream.converters.basic.FloatConverter;
import com.thoughtworks.xstream.converters.basic.IntConverter;
import com.thoughtworks.xstream.converters.basic.LongConverter;
import com.thoughtworks.xstream.converters.basic.NullConverter;
import com.thoughtworks.xstream.converters.basic.ShortConverter;
import com.thoughtworks.xstream.converters.basic.StringBufferConverter;
import com.thoughtworks.xstream.converters.basic.StringConverter;
import com.thoughtworks.xstream.converters.basic.URLConverter;
import com.thoughtworks.xstream.converters.collections.ArrayConverter;
import com.thoughtworks.xstream.converters.collections.BitSetConverter;
import com.thoughtworks.xstream.converters.collections.CharArrayConverter;
import com.thoughtworks.xstream.converters.collections.CollectionConverter;
import com.thoughtworks.xstream.converters.collections.MapConverter;
import com.thoughtworks.xstream.converters.collections.PropertiesConverter;
import com.thoughtworks.xstream.converters.collections.TreeMapConverter;
import com.thoughtworks.xstream.converters.collections.TreeSetConverter;
import com.thoughtworks.xstream.converters.extended.ColorConverter;
import com.thoughtworks.xstream.converters.extended.DynamicProxyConverter;
import com.thoughtworks.xstream.converters.extended.EncodedByteArrayConverter;
import com.thoughtworks.xstream.converters.extended.FileConverter;
import com.thoughtworks.xstream.converters.extended.FontConverter;
import com.thoughtworks.xstream.converters.extended.GregorianCalendarConverter;
import com.thoughtworks.xstream.converters.extended.JavaClassConverter;
import com.thoughtworks.xstream.converters.extended.JavaMethodConverter;
import com.thoughtworks.xstream.converters.extended.LocaleConverter;
import com.thoughtworks.xstream.converters.extended.LookAndFeelConverter;
import com.thoughtworks.xstream.converters.extended.SqlDateConverter;
import com.thoughtworks.xstream.converters.extended.SqlTimeConverter;
import com.thoughtworks.xstream.converters.extended.SqlTimestampConverter;
import com.thoughtworks.xstream.converters.extended.TextAttributeConverter;
import com.thoughtworks.xstream.converters.reflection.ExternalizableConverter;
import com.thoughtworks.xstream.converters.reflection.ReflectionConverter;
import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
import com.thoughtworks.xstream.converters.reflection.SelfStreamingInstanceChecker;
import com.thoughtworks.xstream.converters.reflection.SerializableConverter;
import com.thoughtworks.xstream.core.DefaultConverterLookup;
import com.thoughtworks.xstream.core.JVM;
import com.thoughtworks.xstream.core.MapBackedDataHolder;
import com.thoughtworks.xstream.core.ReferenceByIdMarshallingStrategy;
import com.thoughtworks.xstream.core.ReferenceByXPathMarshallingStrategy;
import com.thoughtworks.xstream.core.TreeMarshallingStrategy;
import com.thoughtworks.xstream.core.util.ClassLoaderReference;
import com.thoughtworks.xstream.core.util.CompositeClassLoader;
import com.thoughtworks.xstream.core.util.CustomObjectInputStream;
import com.thoughtworks.xstream.core.util.CustomObjectOutputStream;
import com.thoughtworks.xstream.io.HierarchicalStreamDriver;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.StatefulWriter;
import com.thoughtworks.xstream.io.xml.XppDriver;
import com.thoughtworks.xstream.mapper.AnnotationConfiguration;
import com.thoughtworks.xstream.mapper.ArrayMapper;
import com.thoughtworks.xstream.mapper.AttributeAliasingMapper;
import com.thoughtworks.xstream.mapper.AttributeMapper;
import com.thoughtworks.xstream.mapper.CachingMapper;
import com.thoughtworks.xstream.mapper.ClassAliasingMapper;
import com.thoughtworks.xstream.mapper.DefaultImplementationsMapper;
import com.thoughtworks.xstream.mapper.DefaultMapper;
import com.thoughtworks.xstream.mapper.DynamicProxyMapper;
import com.thoughtworks.xstream.mapper.FieldAliasingMapper;
import com.thoughtworks.xstream.mapper.ImmutableTypesMapper;
import com.thoughtworks.xstream.mapper.ImplicitCollectionMapper;
import com.thoughtworks.xstream.mapper.LocalConversionMapper;
import com.thoughtworks.xstream.mapper.Mapper;
import com.thoughtworks.xstream.mapper.MapperWrapper;
import com.thoughtworks.xstream.mapper.OuterClassMapper;
import com.thoughtworks.xstream.mapper.PackageAliasingMapper;
import com.thoughtworks.xstream.mapper.SystemAttributeAliasingMapper;
import com.thoughtworks.xstream.mapper.XStream11XmlFriendlyMapper;

import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.NotActiveException;
import java.io.ObjectInputStream;
import java.io.ObjectInputValidation;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Vector;


/**
 * Simple facade to XStream library, a Java-XML serialization tool. <p/>
 * <p>
 * <hr>
 * <b>Example</b><blockquote>
 *
 * <pre>
 * XStream xstream = new XStream();
 * String xml = xstream.toXML(myObject); // serialize to XML
 * Object myObject2 = xstream.fromXML(xml); // deserialize from XML
 * </pre>
 *
 * </blockquote>
 * <hr>
 * <p/>
 * <h3>Aliasing classes</h3>
 * <p/>
 * <p>
 * To create shorter XML, you can specify aliases for classes using the <code>alias()</code>
 * method. For example, you can shorten all occurrences of element
 * <code>&lt;com.blah.MyThing&gt;</code> to <code>&lt;my-thing&gt;</code> by registering an
 * alias for the class.
 * <p>
 * <hr>
 * <blockquote>
 *
 * <pre>
 * xstream.alias(&quot;my-thing&quot;, MyThing.class);
 * </pre>
 *
 * </blockquote>
 * <hr>
 * <p/>
 * <h3>Converters</h3>
 * <p/>
 * <p>
 * XStream contains a map of {@link com.thoughtworks.xstream.converters.Converter} instances, each
 * of which acts as a strategy for converting a particular type of class to XML and back again. Out
 * of the box, XStream contains converters for most basic types (String, Date, int, boolean, etc)
 * and collections (Map, List, Set, Properties, etc). For other objects reflection is used to
 * serialize each field recursively.
 * </p>
 * <p/>
 * <p>
 * Extra converters can be registered using the <code>registerConverter()</code> method. Some
 * non-standard converters are supplied in the {@link com.thoughtworks.xstream.converters.extended}
 * package and you can create your own by implementing the
 * {@link com.thoughtworks.xstream.converters.Converter} interface.
 * </p>
 * <p/>
 * <p>
 * <hr>
 * <b>Example</b><blockquote>
 *
 * <pre>
 * xstream.registerConverter(new SqlTimestampConverter());
 * xstream.registerConverter(new DynamicProxyConverter());
 * </pre>
 *
 * </blockquote>
 * <hr>
 * <p>
 * The converters can be registered with an explicit priority. By default they are registered with 
 * XStream.PRIORITY_NORMAL. Converters of same priority will be used in the reverse sequence
 * they have been registered. The default converter, i.e. the converter which will be used if
 * no other registered converter is suitable, can be registered with priority
 * XStream.PRIORITY_VERY_LOW. XStream uses by default the
 * {@link com.thoughtworks.xstream.converters.reflection.ReflectionConverter} as the fallback
 * converter.
 * </p>
 * <p/>
 * <p>
 * <hr>
 * <b>Example</b><blockquote>
 *
 * <pre>
 * xstream.registerConverter(new CustomDefaultConverter(), XStream.PRIORITY_VERY_LOW);
 * </pre>
 *
 * </blockquote>
 * <hr>
 * <p/>
 * <h3>Object graphs</h3>
 * <p/>
 * <p>
 * XStream has support for object graphs; a deserialized object graph will keep references intact,
 * including circular references.
 * </p>
 * <p/>
 * <p>
 * XStream can signify references in XML using either relative/absolute XPath or IDs. The mode can be changed using
 * <code>setMode()</code>:
 * </p>
 * <p/>
 * <table border='1'>
 * <tr>
 * <td><code>xstream.setMode(XStream.XPATH_RELATIVE_REFERENCES);</code></td>
 * <td><i>(Default)</i> Uses XPath relative references to signify duplicate references. This produces XML
 * with the least clutter.</td>
 * </tr>
 * <tr>
 * <td><code>xstream.setMode(XStream.XPATH_ABSOLUTE_REFERENCES);</code></td>
 * <td>Uses XPath absolute references to signify duplicate
 * references. This produces XML with the least clutter.</td>
 * </tr>
 * <tr>
 * <td><code>xstream.setMode(XStream.ID_REFERENCES);</code></td>
 * <td>Uses ID references to signify duplicate references. In some scenarios, such as when using
 * hand-written XML, this is easier to work with.</td>
 * </tr>
 * <tr>
 * <td><code>xstream.setMode(XStream.NO_REFERENCES);</code></td>
 * <td>This disables object graph support and treats the object structure like a tree. Duplicate
 * references are treated as two separate objects and circular references cause an exception. This
 * is slightly faster and uses less memory than the other two modes.</td>
 * </tr>
 * </table>
 * <h3>Thread safety</h3>
 * <p>
 * The XStream instance is thread-safe. That is, once the XStream instance has been created and
 * configured, it may be shared across multiple threads allowing objects to be
 * serialized/deserialized concurrently. <em>Note, that this only applies if annotations are not 
 * auto-detected on -the-fly.</em>
 * </p>
 * <h3>Implicit collections</h3>
 * <p/>
 * <p>
 * To avoid the need for special tags for collections, you can define implicit collections using one
 * of the <code>addImplicitCollection</code> methods.
 * </p>
 *
 * @author Joe Walnes
 * @author J&ouml;rg Schaible
 * @author Mauro Talevi
 * @author Guilherme Silveira
 */
public class XStream {

    // CAUTION: The sequence of the fields is intentional for an optimal XML output of a
    // self-serialization!
    private ReflectionProvider reflectionProvider;
    private HierarchicalStreamDriver hierarchicalStreamDriver;
    private ClassLoaderReference classLoaderReference;
    private MarshallingStrategy marshallingStrategy;
    private ConverterLookup converterLookup;
    private ConverterRegistry converterRegistry;
    private Mapper mapper;

    private PackageAliasingMapper packageAliasingMapper;
    private ClassAliasingMapper classAliasingMapper;
    private FieldAliasingMapper fieldAliasingMapper;
    private AttributeAliasingMapper attributeAliasingMapper;
    private SystemAttributeAliasingMapper systemAttributeAliasingMapper;
    private AttributeMapper attributeMapper;
    private DefaultImplementationsMapper defaultImplementationsMapper;
    private ImmutableTypesMapper immutableTypesMapper;
    private ImplicitCollectionMapper implicitCollectionMapper;
    private LocalConversionMapper localConversionMapper;
    private AnnotationConfiguration annotationConfiguration;

    private transient JVM jvm = new JVM();

    public static final int NO_REFERENCES = 1001;
    public static final int ID_REFERENCES = 1002;
    public static final int XPATH_RELATIVE_REFERENCES = 1003;
    public static final int XPATH_ABSOLUTE_REFERENCES = 1004;
    /**
     * @deprecated since 1.2, use {@link #XPATH_RELATIVE_REFERENCES} or
     *             {@link #XPATH_ABSOLUTE_REFERENCES} instead.
     */
    public static final int XPATH_REFERENCES = XPATH_RELATIVE_REFERENCES;

    public static final int PRIORITY_VERY_HIGH = 10000;
    public static final int PRIORITY_NORMAL = 0;
    public static final int PRIORITY_LOW = -10;
    public static final int PRIORITY_VERY_LOW = -20;

    private static final String ANNOTATION_MAPPER_TYPE = "com.thoughtworks.xstream.mapper.AnnotationMapper";

    /**
     * Constructs a default XStream. The instance will use the {@link XppDriver} as default and tries to determine the best
     * match for the {@link ReflectionProvider} on its own.
     *
     * @throws InitializationException in case of an initialization problem
     */
    public XStream() {
        this(null, (Mapper)null, new XppDriver());
    }

    /**
     * Constructs an XStream with a special {@link ReflectionProvider}. The instance will use the {@link XppDriver} as default.
     *
     * @throws InitializationException in case of an initialization problem
     */
    public XStream(ReflectionProvider reflectionProvider) {
        this(reflectionProvider, (Mapper)null, new XppDriver());
    }

    /**
     * Constructs an XStream with a special {@link HierarchicalStreamDriver}. The instance will tries to determine the best
     * match for the {@link ReflectionProvider} on its own.
     *
     * @throws InitializationException in case of an initialization problem
     */
    public XStream(HierarchicalStreamDriver hierarchicalStreamDriver) {
        this(null, (Mapper)null, hierarchicalStreamDriver);
    }

    /**
     * Constructs an XStream with a special {@link HierarchicalStreamDriver} and {@link ReflectionProvider}.
     *
     * @throws InitializationException in case of an initialization problem
     */
    public XStream(
            ReflectionProvider reflectionProvider, HierarchicalStreamDriver hierarchicalStreamDriver) {
        this(reflectionProvider, (Mapper)null, hierarchicalStreamDriver);
    }

    /**
     * @deprecated As of 1.2, use
     *             {@link #XStream(ReflectionProvider, Mapper, HierarchicalStreamDriver)}
     */
    public XStream(
            ReflectionProvider reflectionProvider, ClassMapper classMapper,
            HierarchicalStreamDriver driver) {
        this(reflectionProvider, (Mapper)classMapper, driver);
    }

    /**
     * @deprecated As of 1.2, use
     *             {@link #XStream(ReflectionProvider, Mapper, HierarchicalStreamDriver)} and
     *             register classAttributeIdentifier as alias
     */
    public XStream(
            ReflectionProvider reflectionProvider, ClassMapper classMapper,
            HierarchicalStreamDriver driver, String classAttributeIdentifier) {
        this(reflectionProvider, (Mapper)classMapper, driver);
        aliasAttribute(classAttributeIdentifier, "class");
    }

    /**
     * Constructs an XStream with a special {@link HierarchicalStreamDriver} and {@link ReflectionProvider} and additionally with a prepared {@link Mapper}.
     *
     * @throws InitializationException in case of an initialization problem
     * @deprecated since 1.3, use {@link #XStream(ReflectionProvider, HierarchicalStreamDriver, Mapper, ClassLoader)} instead
     */
    public XStream(
            ReflectionProvider reflectionProvider, Mapper mapper, HierarchicalStreamDriver driver) {
        this(reflectionProvider, driver, new ClassLoaderReference(new CompositeClassLoader()), mapper, new DefaultConverterLookup(), null);
    }

    /**
     * Constructs an XStream with a special {@link HierarchicalStreamDriver} and {@link ReflectionProvider} and additionally with a prepared 
     * {@link ClassLoader} to use.
     *
     * @throws InitializationException in case of an initialization problem
     * @since 1.3
     */
    public XStream(
            ReflectionProvider reflectionProvider, HierarchicalStreamDriver driver, ClassLoader classLoader) {
        this(reflectionProvider, driver, classLoader, null);
    }

    /**
     * Constructs an XStream with a special {@link HierarchicalStreamDriver} and {@link ReflectionProvider} and additionally with a prepared {@link Mapper}
     * and the {@link ClassLoader} in use.
     * 
     * <p>Note, if the class loader should be changed later again, you should provide a {@link ClassLoaderReference} as {@link ClassLoader} that is also
     * use in the {@link Mapper} chain.</p>
     *
     * @throws InitializationException in case of an initialization problem
     * @since 1.3
     */
    public XStream(
            ReflectionProvider reflectionProvider, HierarchicalStreamDriver driver, ClassLoader classLoader, Mapper mapper) {
        this(reflectionProvider, driver, classLoader, mapper, new DefaultConverterLookup(), null);
    }

    /**
     * Constructs an XStream with a special {@link HierarchicalStreamDriver}, {@link ReflectionProvider}, a prepared {@link Mapper}
     * and the {@link ClassLoader} in use and an own {@link ConverterRegistry}.
     * 
     * <p>Note, if the class loader should be changed later again, you should provide a {@link ClassLoaderReference} as {@link ClassLoader} that is also
     * use in the {@link Mapper} chain.</p>
     *
     * @throws InitializationException in case of an initialization problem
     * @since 1.3
     */
    public XStream(
            ReflectionProvider reflectionProvider, HierarchicalStreamDriver driver,   
            ClassLoader classLoader, Mapper mapper, ConverterLookup converterLookup, 
            ConverterRegistry converterRegistry) {
        jvm = new JVM();
        if (reflectionProvider == null) {
            reflectionProvider = jvm.bestReflectionProvider();
        }
        this.reflectionProvider = reflectionProvider;
        this.hierarchicalStreamDriver = driver;
        this.classLoaderReference = classLoader instanceof ClassLoaderReference ? (ClassLoaderReference)classLoader : new ClassLoaderReference(classLoader);
        this.converterLookup = converterLookup;
        this.converterRegistry = converterRegistry != null 
            ? converterRegistry 
            : (converterLookup instanceof ConverterRegistry ? (ConverterRegistry)converterLookup : null);
        this.mapper = mapper == null ? buildMapper() : mapper;

        setupMappers();
        setupAliases();
        setupDefaultImplementations();
        setupConverters();
        setupImmutableTypes();
        setMode(XPATH_RELATIVE_REFERENCES);
    }

    private Mapper buildMapper() {
        Mapper mapper = new DefaultMapper(classLoaderReference);
        if ( useXStream11XmlFriendlyMapper() ){
            mapper = new XStream11XmlFriendlyMapper(mapper);
        }
        mapper = new DynamicProxyMapper(mapper);
        mapper = new PackageAliasingMapper(mapper);
        mapper = new ClassAliasingMapper(mapper);
        mapper = new FieldAliasingMapper(mapper);
        mapper = new AttributeAliasingMapper(mapper);
        mapper = new SystemAttributeAliasingMapper(mapper);
        mapper = new ImplicitCollectionMapper(mapper);
        mapper = new OuterClassMapper(mapper);
        mapper = new ArrayMapper(mapper);
        mapper = new DefaultImplementationsMapper(mapper);
        mapper = new AttributeMapper(mapper, converterLookup);
        if (JVM.is15()) {
            mapper = buildMapperDynamically(
                "com.thoughtworks.xstream.mapper.EnumMapper", new Class[]{Mapper.class},
                new Object[]{mapper});
        }
        mapper = new LocalConversionMapper(mapper);
        mapper = new ImmutableTypesMapper(mapper);
        if (JVM.is15()) {
            mapper = buildMapperDynamically(
                ANNOTATION_MAPPER_TYPE,
                new Class[]{Mapper.class, ConverterRegistry.class, ClassLoader.class, ReflectionProvider.class, JVM.class}, 
                new Object[]{mapper, converterLookup, classLoaderReference, reflectionProvider, jvm});
        }
        mapper = wrapMapper((MapperWrapper)mapper);
        mapper = new CachingMapper(mapper);
        return mapper;
    }

    private Mapper buildMapperDynamically(
            String className, Class[] constructorParamTypes,
            Object[] constructorParamValues) {
        try {
            Class type = Class.forName(className, false, classLoaderReference.getReference());
            Constructor constructor = type.getConstructor(constructorParamTypes);
            return (Mapper)constructor.newInstance(constructorParamValues);
        } catch (Exception e) {
            throw new InitializationException("Could not instantiate mapper : " + className, e);
        }
    }

    protected MapperWrapper wrapMapper(MapperWrapper next) {
        return next;
    }

    protected boolean useXStream11XmlFriendlyMapper() {
        return false;
    }

    private void setupMappers() {
        packageAliasingMapper = (PackageAliasingMapper)this.mapper
                .lookupMapperOfType(PackageAliasingMapper.class);
        classAliasingMapper = (ClassAliasingMapper)this.mapper
                .lookupMapperOfType(ClassAliasingMapper.class);
        fieldAliasingMapper = (FieldAliasingMapper)this.mapper
                .lookupMapperOfType(FieldAliasingMapper.class);
        attributeMapper = (AttributeMapper)this.mapper.lookupMapperOfType(AttributeMapper.class);
        attributeAliasingMapper = (AttributeAliasingMapper)this.mapper
                .lookupMapperOfType(AttributeAliasingMapper.class);
        systemAttributeAliasingMapper = (SystemAttributeAliasingMapper)this.mapper
                .lookupMapperOfType(SystemAttributeAliasingMapper.class);
        implicitCollectionMapper = (ImplicitCollectionMapper)this.mapper
                .lookupMapperOfType(ImplicitCollectionMapper.class);
        defaultImplementationsMapper = (DefaultImplementationsMapper)this.mapper
                .lookupMapperOfType(DefaultImplementationsMapper.class);
        immutableTypesMapper = (ImmutableTypesMapper)this.mapper
                .lookupMapperOfType(ImmutableTypesMapper.class);
        localConversionMapper = (LocalConversionMapper)this.mapper
                .lookupMapperOfType(LocalConversionMapper.class);
        annotationConfiguration = (AnnotationConfiguration)this.mapper
                .lookupMapperOfType(AnnotationConfiguration.class);
    }

    protected void setupAliases() {
        if (classAliasingMapper == null) {
            return;
        }

        alias("null", Mapper.Null.class);
        alias("int", Integer.class);
        alias("float", Float.class);
        alias("double", Double.class);
        alias("long", Long.class);
        alias("short", Short.class);
        alias("char", Character.class);
        alias("byte", Byte.class);
        alias("boolean", Boolean.class);
        alias("number", Number.class);
        alias("object", Object.class);
        alias("big-int", BigInteger.class);
        alias("big-decimal", BigDecimal.class);

        alias("string-buffer", StringBuffer.class);
        alias("string", String.class);
        alias("java-class", Class.class);
        alias("method", Method.class);
        alias("constructor", Constructor.class);
        alias("date", Date.class);
        alias("url", URL.class);
        alias("bit-set", BitSet.class);

        alias("map", Map.class);
        alias("entry", Map.Entry.class);
        alias("properties", Properties.class);
        alias("list", List.class);
        alias("set", Set.class);

        alias("linked-list", LinkedList.class);
        alias("vector", Vector.class);
        alias("tree-map", TreeMap.class);
        alias("tree-set", TreeSet.class);
        alias("hashtable", Hashtable.class);

        if (jvm.supportsAWT()) {
            // Instantiating these two classes starts the AWT system, which is undesirable.
            // Calling loadClass ensures a reference to the class is found but they are not
            // instantiated.
            alias("awt-color", jvm.loadClass("java.awt.Color"));
            alias("awt-font", jvm.loadClass("java.awt.Font"));
            alias("awt-text-attribute", jvm.loadClass("java.awt.font.TextAttribute"));
        }

        if (jvm.supportsSQL()) {
            alias("sql-timestamp", jvm.loadClass("java.sql.Timestamp"));
            alias("sql-time", jvm.loadClass("java.sql.Time"));
            alias("sql-date", jvm.loadClass("java.sql.Date"));
        }

        alias("file", File.class);
        alias("locale", Locale.class);
        alias("gregorian-calendar", Calendar.class);

        if (JVM.is14()) {
            alias("auth-subject", jvm.loadClass("javax.security.auth.Subject"));
            alias("linked-hash-map", jvm.loadClass("java.util.LinkedHashMap"));
            alias("linked-hash-set", jvm.loadClass("java.util.LinkedHashSet"));
            alias("trace", jvm.loadClass("java.lang.StackTraceElement"));
            alias("currency", jvm.loadClass("java.util.Currency"));
            aliasType("charset", jvm.loadClass("java.nio.charset.Charset"));
        }

        if (JVM.is15()) {
            alias("duration", jvm.loadClass("javax.xml.datatype.Duration"));
            alias("enum-set", jvm.loadClass("java.util.EnumSet"));
            alias("enum-map", jvm.loadClass("java.util.EnumMap"));
            alias("string-builder", jvm.loadClass("java.lang.StringBuilder"));
            alias("uuid", jvm.loadClass("java.util.UUID"));
        }
    }

    protected void setupDefaultImplementations() {
        if (defaultImplementationsMapper == null) {
            return;
        }
        addDefaultImplementation(HashMap.class, Map.class);
        addDefaultImplementation(ArrayList.class, List.class);
        addDefaultImplementation(HashSet.class, Set.class);
        addDefaultImplementation(GregorianCalendar.class, Calendar.class);
    }

    protected void setupConverters() {
        final ReflectionConverter reflectionConverter = 
            new ReflectionConverter(mapper, reflectionProvider);
        registerConverter(reflectionConverter, PRIORITY_VERY_LOW);

        registerConverter(new SerializableConverter(mapper, reflectionProvider), PRIORITY_LOW);
        registerConverter(new ExternalizableConverter(mapper), PRIORITY_LOW);

        registerConverter(new NullConverter(), PRIORITY_VERY_HIGH);
        registerConverter(new IntConverter(), PRIORITY_NORMAL);
        registerConverter(new FloatConverter(), PRIORITY_NORMAL);
        registerConverter(new DoubleConverter(), PRIORITY_NORMAL);
        registerConverter(new LongConverter(), PRIORITY_NORMAL);
        registerConverter(new ShortConverter(), PRIORITY_NORMAL);
        registerConverter((Converter)new CharConverter(), PRIORITY_NORMAL);
        registerConverter(new BooleanConverter(), PRIORITY_NORMAL);
        registerConverter(new ByteConverter(), PRIORITY_NORMAL);

        registerConverter(new StringConverter(), PRIORITY_NORMAL);
        registerConverter(new StringBufferConverter(), PRIORITY_NORMAL);
        registerConverter(new DateConverter(), PRIORITY_NORMAL);
        registerConverter(new BitSetConverter(), PRIORITY_NORMAL);
        registerConverter(new URLConverter(), PRIORITY_NORMAL);
        registerConverter(new BigIntegerConverter(), PRIORITY_NORMAL);
        registerConverter(new BigDecimalConverter(), PRIORITY_NORMAL);

        registerConverter(new ArrayConverter(mapper), PRIORITY_NORMAL);
        registerConverter(new CharArrayConverter(), PRIORITY_NORMAL);
        registerConverter(new CollectionConverter(mapper), PRIORITY_NORMAL);
        registerConverter(new MapConverter(mapper), PRIORITY_NORMAL);
        registerConverter(new TreeMapConverter(mapper), PRIORITY_NORMAL);
        registerConverter(new TreeSetConverter(mapper), PRIORITY_NORMAL);
        registerConverter(new PropertiesConverter(), PRIORITY_NORMAL);
        registerConverter(new EncodedByteArrayConverter(), PRIORITY_NORMAL);

        registerConverter(new FileConverter(), PRIORITY_NORMAL);
        if(jvm.supportsSQL()) {
	        registerConverter(new SqlTimestampConverter(), PRIORITY_NORMAL);
	        registerConverter(new SqlTimeConverter(), PRIORITY_NORMAL);
	        registerConverter(new SqlDateConverter(), PRIORITY_NORMAL);
        }
        registerConverter(new DynamicProxyConverter(mapper, classLoaderReference), PRIORITY_NORMAL);
        registerConverter(new JavaClassConverter(classLoaderReference), PRIORITY_NORMAL);
        registerConverter(new JavaMethodConverter(classLoaderReference), PRIORITY_NORMAL);
        if(jvm.supportsAWT()) {
	        registerConverter(new FontConverter(), PRIORITY_NORMAL);
	        registerConverter(new ColorConverter(), PRIORITY_NORMAL);
	        registerConverter(new TextAttributeConverter(), PRIORITY_NORMAL);
        }
        if(jvm.supportsSwing()) {
            registerConverter(new LookAndFeelConverter(mapper, reflectionProvider), PRIORITY_NORMAL);
        }
        registerConverter(new LocaleConverter(), PRIORITY_NORMAL);
        registerConverter(new GregorianCalendarConverter(), PRIORITY_NORMAL);

        if (JVM.is14()) {
            // late bound converters - allows XStream to be compiled on earlier JDKs
            dynamicallyRegisterConverter(
                    "com.thoughtworks.xstream.converters.extended.SubjectConverter",
                    PRIORITY_NORMAL, new Class[]{Mapper.class}, new Object[]{mapper});
            dynamicallyRegisterConverter(
                    "com.thoughtworks.xstream.converters.extended.ThrowableConverter",
                    PRIORITY_NORMAL, new Class[]{Converter.class},
                    new Object[]{reflectionConverter});
            dynamicallyRegisterConverter(
                    "com.thoughtworks.xstream.converters.extended.StackTraceElementConverter",
                    PRIORITY_NORMAL, null, null);
            dynamicallyRegisterConverter(
                    "com.thoughtworks.xstream.converters.extended.CurrencyConverter",
                    PRIORITY_NORMAL, null, null);
            dynamicallyRegisterConverter(
                    "com.thoughtworks.xstream.converters.extended.RegexPatternConverter",
                    PRIORITY_NORMAL, new Class[]{Converter.class},
                    new Object[]{reflectionConverter});
            dynamicallyRegisterConverter(
                    "com.thoughtworks.xstream.converters.extended.CharsetConverter",
                    PRIORITY_NORMAL, null, null);
        }

        if (JVM.is15()) {
            // late bound converters - allows XStream to be compiled on earlier JDKs
            dynamicallyRegisterConverter(
                "com.thoughtworks.xstream.converters.extended.DurationConverter",
                PRIORITY_NORMAL, null, null);
            dynamicallyRegisterConverter(
                    "com.thoughtworks.xstream.converters.enums.EnumConverter", PRIORITY_NORMAL,
                    null, null);
            dynamicallyRegisterConverter(
                    "com.thoughtworks.xstream.converters.enums.EnumSetConverter", PRIORITY_NORMAL,
                    new Class[]{Mapper.class}, new Object[]{mapper});
            dynamicallyRegisterConverter(
                    "com.thoughtworks.xstream.converters.enums.EnumMapConverter", PRIORITY_NORMAL,
                    new Class[]{Mapper.class}, new Object[]{mapper});
            dynamicallyRegisterConverter(
                "com.thoughtworks.xstream.converters.basic.StringBuilderConverter", PRIORITY_NORMAL,
                null, null);
            dynamicallyRegisterConverter(
                "com.thoughtworks.xstream.converters.basic.UUIDConverter", PRIORITY_NORMAL,
                null, null);
        }

        registerConverter(new SelfStreamingInstanceChecker(reflectionConverter, this), PRIORITY_NORMAL);
    }

    private void dynamicallyRegisterConverter(
            String className, int priority, Class[] constructorParamTypes,
            Object[] constructorParamValues) {
        try {
            Class type = Class.forName(className, false, classLoaderReference.getReference());
            Constructor constructor = type.getConstructor(constructorParamTypes);
            Object instance = constructor.newInstance(constructorParamValues);
            if (instance instanceof Converter) {
                registerConverter((Converter)instance, priority);
            } else if (instance instanceof SingleValueConverter) {
                registerConverter((SingleValueConverter)instance, priority);
            }
        } catch (Exception e) {
            throw new InitializationException("Could not instantiate converter : " + className, e);
        }
    }

    protected void setupImmutableTypes() {
        if (immutableTypesMapper == null) {
            return;
        }

        // primitives are always immutable
        addImmutableType(boolean.class);
        addImmutableType(Boolean.class);
        addImmutableType(byte.class);
        addImmutableType(Byte.class);
        addImmutableType(char.class);
        addImmutableType(Character.class);
        addImmutableType(double.class);
        addImmutableType(Double.class);
        addImmutableType(float.class);
        addImmutableType(Float.class);
        addImmutableType(int.class);
        addImmutableType(Integer.class);
        addImmutableType(long.class);
        addImmutableType(Long.class);
        addImmutableType(short.class);
        addImmutableType(Short.class);

        // additional types
        addImmutableType(Mapper.Null.class);
        addImmutableType(BigDecimal.class);
        addImmutableType(BigInteger.class);
        addImmutableType(String.class);
        addImmutableType(URL.class);
        addImmutableType(File.class);
        addImmutableType(Class.class);

        if(jvm.supportsAWT()) {
            addImmutableType(jvm.loadClass("java.awt.font.TextAttribute"));
        }

        if (JVM.is14()) {
            // late bound types - allows XStream to be compiled on earlier JDKs
            Class type = jvm.loadClass("com.thoughtworks.xstream.converters.extended.CharsetConverter");
            addImmutableType(type);
        }
    }

    public void setMarshallingStrategy(MarshallingStrategy marshallingStrategy) {
        this.marshallingStrategy = marshallingStrategy;
    }

    /**
     * Serialize an object to a pretty-printed XML String.
     * @throws XStreamException if the object cannot be serialized
     */
    public String toXML(Object obj) {
        Writer writer = new StringWriter();
        toXML(obj, writer);
        return writer.toString();
    }

    /**
     * Serialize an object to the given Writer as pretty-printed XML.
     * The Writer will be flushed afterwards and in case of an exception. 
     * @throws XStreamException if the object cannot be serialized
     */
    public void toXML(Object obj, Writer out) {
        HierarchicalStreamWriter writer = hierarchicalStreamDriver.createWriter(out);
        try {
            marshal(obj, writer);
        } finally {
            writer.flush();
        }
    }

    /**
     * Serialize an object to the given OutputStream as pretty-printed XML.
     * The OutputStream will be flushed afterwards and in case of an exception. 
     * @throws XStreamException if the object cannot be serialized
     */
    public void toXML(Object obj, OutputStream out) {
        HierarchicalStreamWriter writer = hierarchicalStreamDriver.createWriter(out);
        try {
            marshal(obj, writer);
        } finally {
            writer.flush();
        }
    }

    /**
     * Serialize and object to a hierarchical data structure (such as XML).
     * @throws XStreamException if the object cannot be serialized
     */
    public void marshal(Object obj, HierarchicalStreamWriter writer) {
        marshal(obj, writer, null);
    }

    /**
     * Serialize and object to a hierarchical data structure (such as XML).
     *
     * @param dataHolder Extra data you can use to pass to your converters. Use this as you want. If
     *            not present, XStream shall create one lazily as needed.
     * @throws XStreamException if the object cannot be serialized
     */
    public void marshal(Object obj, HierarchicalStreamWriter writer, DataHolder dataHolder) {
        marshallingStrategy.marshal(writer, obj, converterLookup, mapper, dataHolder);
    }

    /**
     * Deserialize an object from an XML String.
     * @throws XStreamException if the object cannot be deserialized
     */
    public Object fromXML(String xml) {
        return fromXML(new StringReader(xml));
    }

    /**
     * Deserialize an object from an XML Reader.
     * @throws XStreamException if the object cannot be deserialized
     */
    public Object fromXML(Reader xml) {
        return unmarshal(hierarchicalStreamDriver.createReader(xml), null);
    }

    /**
     * Deserialize an object from an XML InputStream.
     * @throws XStreamException if the object cannot be deserialized
     */
    public Object fromXML(InputStream input) {
        return unmarshal(hierarchicalStreamDriver.createReader(input), null);
    }

    /**
     * Deserialize an object from an XML String, populating the fields of the given root object
     * instead of instantiating a new one. Note, that this is a special use case! With the ReflectionConverter 
     * XStream will write directly into the raw memory area of the existing object. Use with care!
     * @throws XStreamException if the object cannot be deserialized
     */
    public Object fromXML(String xml, Object root) {
        return fromXML(new StringReader(xml), root);
    }

    /**
     * Deserialize an object from an XML Reader, populating the fields of the given root object
     * instead of instantiating a new one. Note, that this is a special use case! With the ReflectionConverter 
     * XStream will write directly into the raw memory area of the existing object. Use with care!
     * @throws XStreamException if the object cannot be deserialized
     */
    public Object fromXML(Reader xml, Object root) {
        return unmarshal(hierarchicalStreamDriver.createReader(xml), root);
    }

    /**
     * Deserialize an object from an XML InputStream, populating the fields of the given root object
     * instead of instantiating a new one. Note, that this is a special use case! With the ReflectionConverter 
     * XStream will write directly into the raw memory area of the existing object. Use with care!
     * @throws XStreamException if the object cannot be deserialized
     */
    public Object fromXML(InputStream xml, Object root) {
        return unmarshal(hierarchicalStreamDriver.createReader(xml), root);
    }

    /**
     * Deserialize an object from a hierarchical data structure (such as XML).
     * @throws XStreamException if the object cannot be deserialized
     */
    public Object unmarshal(HierarchicalStreamReader reader) {
        return unmarshal(reader, null, null);
    }

    /**
     * Deserialize an object from a hierarchical data structure (such as XML), populating the fields
     * of the given root object instead of instantiating a new one. Note, that this is a special use case! With the ReflectionConverter 
     * XStream will write directly into the raw memory area of the existing object. Use with care!
     * @throws XStreamException if the object cannot be deserialized
     */
    public Object unmarshal(HierarchicalStreamReader reader, Object root) {
        return unmarshal(reader, root, null);
    }

    /**
     * Deserialize an object from a hierarchical data structure (such as XML).
     *
     * @param root If present, the passed in object will have its fields populated, as opposed to
     *            XStream creating a new instance. Note, that this is a special use case! With the ReflectionConverter 
     *            XStream will write directly into the raw memory area of the existing object. Use with care!
     * @param dataHolder Extra data you can use to pass to your converters. Use this as you want. If
     *            not present, XStream shall create one lazily as needed.
     * @throws XStreamException if the object cannot be deserialized
     */
    public Object unmarshal(HierarchicalStreamReader reader, Object root, DataHolder dataHolder) {
        return marshallingStrategy.unmarshal(root, reader, dataHolder, converterLookup, mapper);
    }

    /**
     * Alias a Class to a shorter name to be used in XML elements.
     *
     * @param name Short name
     * @param type Type to be aliased
     * @throws InitializationException if no {@link ClassAliasingMapper} is available
     */
    public void alias(String name, Class type) {
        if (classAliasingMapper == null) {
            throw new InitializationException("No "
                    + ClassAliasingMapper.class.getName()
                    + " available");
        }
        classAliasingMapper.addClassAlias(name, type);
    }

    /**
     * Alias a type to a shorter name to be used in XML elements.
     * Any class that is assignable to this type will be aliased to the same name.
     *
     * @param name Short name
     * @param type Type to be aliased
     * @since 1.2
     * @throws InitializationException if no {@link ClassAliasingMapper} is available
     */
    public void aliasType(String name, Class type) {
        if (classAliasingMapper == null) {
            throw new InitializationException("No "
                    + ClassAliasingMapper.class.getName()
                    + " available");
        }
        classAliasingMapper.addTypeAlias(name, type);
    }

    /**
     * Alias a Class to a shorter name to be used in XML elements.
     *
     * @param name Short name
     * @param type Type to be aliased
     * @param defaultImplementation Default implementation of type to use if no other specified.
     * @throws InitializationException if no {@link DefaultImplementationsMapper} or no {@link ClassAliasingMapper} is available
     */
    public void alias(String name, Class type, Class defaultImplementation) {
        alias(name, type);
        addDefaultImplementation(defaultImplementation, type);
    }

    /**
     * Alias a package to a shorter name to be used in XML elements.
     *
     * @param name Short name
     * @param pkgName package to be aliased
     * @throws InitializationException if no {@link DefaultImplementationsMapper} or no {@link PackageAliasingMapper} is available
     * @since 1.3.1
     */
    public void aliasPackage(String name, String pkgName) {
        if (packageAliasingMapper == null) {
            throw new InitializationException("No "
                    + PackageAliasingMapper.class.getName()
                    + " available");
        }
        packageAliasingMapper.addPackageAlias(name, pkgName);
    }

    /**
     * Create an alias for a field name.
     *
     * @param alias the alias itself
     * @param definedIn the type that declares the field
     * @param fieldName the name of the field
     * @throws InitializationException if no {@link FieldAliasingMapper} is available
     */
    public void aliasField(String alias, Class definedIn, String fieldName) {
        if (fieldAliasingMapper == null) {
            throw new InitializationException("No "
                    + FieldAliasingMapper.class.getName()
                    + " available");
        }
        fieldAliasingMapper.addFieldAlias(alias, definedIn, fieldName);
    }

    /**
     * Create an alias for an attribute
     *
     * @param alias the alias itself
     * @param attributeName the name of the attribute
     * @throws InitializationException if no {@link AttributeAliasingMapper} is available
     */
    public void aliasAttribute(String alias, String attributeName) {
        if (attributeAliasingMapper == null) {
            throw new InitializationException("No "
                    + AttributeAliasingMapper.class.getName()
                    + " available");
        }
        attributeAliasingMapper.addAliasFor(attributeName, alias);
    }

    /**
     * Create an alias for a system attribute.
     * 
     * XStream will not write a system attribute if its alias is set to <code>null</code>. However, 
     * this is not reversible, i.e. deserialization of the result is likely to fail afterwards and will not 
     * produce an object equal to the originally written one.
     *
     * @param alias the alias itself (may be <code>null</code>)
     * @param systemAttributeName the name of the system attribute
     * @throws InitializationException if no {@link SystemAttributeAliasingMapper} is available
     * @since 1.3.1
     */
    public void aliasSystemAttribute(String alias, String systemAttributeName) {
        if (systemAttributeAliasingMapper == null) {
            throw new InitializationException("No "
                    + SystemAttributeAliasingMapper.class.getName()
                    + " available");
        }
        systemAttributeAliasingMapper.addAliasFor(systemAttributeName, alias);
    }

    /**
     * Create an alias for an attribute.
     * 
     * @param definedIn the type where the attribute is defined
     * @param attributeName the name of the attribute
     * @param alias the alias itself
     * @throws InitializationException if no {@link AttributeAliasingMapper} is available
     * @since 1.2.2
     */
    public void aliasAttribute(Class definedIn, String attributeName, String alias) {
        aliasField(alias, definedIn, attributeName);
        useAttributeFor(definedIn, attributeName);
    }

    /**
     * Use an attribute for a field or a specific type.
     *
     * @param fieldName the name of the field
     * @param type the Class of the type to be rendered as XML attribute
     * @throws InitializationException if no {@link AttributeMapper} is available
     * @since 1.2
     */
    public void useAttributeFor(String fieldName, Class type) {
        if (attributeMapper == null) {
            throw new InitializationException("No " + AttributeMapper.class.getName() + " available");
        }
        attributeMapper.addAttributeFor(fieldName, type);
    }

    /**
     * Use an attribute for a field declared in a specific type.
     *
     * @param fieldName the name of the field
     * @param definedIn the Class containing such field
     * @throws InitializationException if no {@link AttributeMapper} is available
     * @since 1.2.2
     */
    public void useAttributeFor(Class definedIn, String fieldName) {
        if (attributeMapper == null) {
            throw new InitializationException("No " + AttributeMapper.class.getName() + " available");
        }
        attributeMapper.addAttributeFor(definedIn, fieldName);
    }

    /**
     * Use an attribute for an arbitrary type.
     *
     * @param type the Class of the type to be rendered as XML attribute
     * @throws InitializationException if no {@link AttributeMapper} is available
     * @since 1.2
     */
    public void useAttributeFor(Class type) {
        if (attributeMapper == null) {
            throw new InitializationException("No " + AttributeMapper.class.getName() + " available");
        }
        attributeMapper.addAttributeFor(type);
    }

    /**
     * Associate a default implementation of a class with an object. Whenever XStream encounters an
     * instance of this type, it will use the default implementation instead. For example,
     * java.util.ArrayList is the default implementation of java.util.List.
     *
     * @param defaultImplementation
     * @param ofType
     * @throws InitializationException if no {@link DefaultImplementationsMapper} is available
     */
    public void addDefaultImplementation(Class defaultImplementation, Class ofType) {
        if (defaultImplementationsMapper == null) {
            throw new InitializationException("No "
                    + DefaultImplementationsMapper.class.getName()
                    + " available");
        }
        defaultImplementationsMapper.addDefaultImplementation(defaultImplementation, ofType);
    }

    /**
     * Add immutable types. The value of the instances of these types will always be written into
     * the stream even if they appear multiple times.
     * @throws InitializationException if no {@link ImmutableTypesMapper} is available
     */
    public void addImmutableType(Class type) {
        if (immutableTypesMapper == null) {
            throw new InitializationException("No "
                    + ImmutableTypesMapper.class.getName()
                    + " available");
        }
        immutableTypesMapper.addImmutableType(type);
    }

    public void registerConverter(Converter converter) {
        registerConverter(converter, PRIORITY_NORMAL);
    }

    public void registerConverter(Converter converter, int priority) {
        if (converterRegistry != null) {
            converterRegistry.registerConverter(converter, priority);
        }
    }

    public void registerConverter(SingleValueConverter converter) {
        registerConverter(converter, PRIORITY_NORMAL);
    }

    public void registerConverter(SingleValueConverter converter, int priority) {
        if (converterRegistry != null) {
            converterRegistry.registerConverter(new SingleValueConverterWrapper(converter), priority);
        }
    }

    /**
     * Register a local {@link Converter} for a field.
     * 
     * @param definedIn the class type the field is defined in
     * @param fieldName the field name
     * @param converter the converter to use
     * @since 1.3
     */
    public void registerLocalConverter(Class definedIn, String fieldName, Converter converter) {
        if (localConversionMapper == null) {
            throw new InitializationException("No "
                    + LocalConversionMapper.class.getName()
                    + " available");
        }
        localConversionMapper.registerLocalConverter(definedIn, fieldName, converter);
    }

    /**
     * Register a local {@link SingleValueConverter} for a field.
     * 
     * @param definedIn the class type the field is defined in
     * @param fieldName the field name
     * @param converter the converter to use
     * @since 1.3
     */
    public void registerLocalConverter(Class definedIn, String fieldName, SingleValueConverter converter) {
        registerLocalConverter(definedIn, fieldName, (Converter)new SingleValueConverterWrapper(converter));
    }

    /**
     * @throws ClassCastException if mapper is not really a deprecated {@link ClassMapper} instance
     * @deprecated As of 1.2, use {@link #getMapper}
     */
    public ClassMapper getClassMapper() {
        if (mapper instanceof ClassMapper) {
            return (ClassMapper)mapper;
        } else {
            return (ClassMapper)Proxy.newProxyInstance(getClassLoader(), new Class[]{ClassMapper.class},
                    new InvocationHandler() {
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            return method.invoke(mapper, args);
                        }
                    });
        }
    }

    /**
     * Retrieve the {@link Mapper}. This is by default a chain of {@link MapperWrapper MapperWrappers}.
     *
     * @return the mapper
     * @since 1.2
     */
    public Mapper getMapper() {
        return mapper;
    }

    /**
     * Retrieve the {@link ReflectionProvider} in use.
     *
     * @return the mapper
     * @since 1.2.1
     */
    public ReflectionProvider getReflectionProvider() {
        return reflectionProvider;
    }

    public ConverterLookup getConverterLookup() {
        return converterLookup;
    }

    /**
     * Change mode for dealing with duplicate references. Valid values are
     * <code>XPATH_ABSOLUTE_REFERENCES</code>, <code>XPATH_RELATIVE_REFERENCES</code>,
     * <code>XStream.ID_REFERENCES</code> and <code>XStream.NO_REFERENCES</code>.
     *
     * @throws IllegalArgumentException if the mode is not one of the declared types
     * @see #XPATH_ABSOLUTE_REFERENCES
     * @see #XPATH_RELATIVE_REFERENCES
     * @see #ID_REFERENCES
     * @see #NO_REFERENCES
     */
    public void setMode(int mode) {
        switch (mode) {
        case NO_REFERENCES:
            setMarshallingStrategy(new TreeMarshallingStrategy());
            break;
        case ID_REFERENCES:
            setMarshallingStrategy(new ReferenceByIdMarshallingStrategy());
            break;
        case XPATH_RELATIVE_REFERENCES:
            setMarshallingStrategy(new ReferenceByXPathMarshallingStrategy(
                    ReferenceByXPathMarshallingStrategy.RELATIVE));
            break;
        case XPATH_ABSOLUTE_REFERENCES:
            setMarshallingStrategy(new ReferenceByXPathMarshallingStrategy(
                    ReferenceByXPathMarshallingStrategy.ABSOLUTE));
            break;
        default:
            throw new IllegalArgumentException("Unknown mode : " + mode);
        }
    }

    /**
     * Adds a default implicit collection which is used for any unmapped XML tag.
     *
     * @param ownerType class owning the implicit collection
     * @param fieldName name of the field in the ownerType. This field must be a
     *            concrete collection type or matching the default implementation type of the 
     *            collection type.
     */
    public void addImplicitCollection(Class ownerType, String fieldName) {
        if (implicitCollectionMapper == null) {
            throw new InitializationException("No "
                    + ImplicitCollectionMapper.class.getName()
                    + " available");
        }
        implicitCollectionMapper.add(ownerType, fieldName, null, null);
    }

    /**
     * Adds implicit collection which is used for all items of the given itemType.
     *
     * @param ownerType class owning the implicit collection
     * @param fieldName name of the field in the ownerType. This field must be a
     *            concrete collection type or matching the default implementation type of the 
     *            collection type.
     * @param itemType type of the items to be part of this collection.
     * @throws InitializationException if no {@link ImplicitCollectionMapper} is available
     */
    public void addImplicitCollection(Class ownerType, String fieldName, Class itemType) {
        if (implicitCollectionMapper == null) {
            throw new InitializationException("No "
                    + ImplicitCollectionMapper.class.getName()
                    + " available");
        }
        implicitCollectionMapper.add(ownerType, fieldName, null, itemType);
    }

    /**
     * Adds implicit collection which is used for all items of the given element name defined by
     * itemFieldName.
     *
     * @param ownerType class owning the implicit collection
     * @param fieldName name of the field in the ownerType. This field must be a
     *            concrete collection type or matching the default implementation type of the 
     *            collection type.
     * @param itemFieldName element name of the implicit collection
     * @param itemType item type to be aliases be the itemFieldName
     * @throws InitializationException if no {@link ImplicitCollectionMapper} is available
     */
    public void addImplicitCollection(
            Class ownerType, String fieldName, String itemFieldName, Class itemType) {
        if (implicitCollectionMapper == null) {
            throw new InitializationException("No "
                    + ImplicitCollectionMapper.class.getName()
                    + " available");
        }
        implicitCollectionMapper.add(ownerType, fieldName, itemFieldName, itemType);
    }

    /**
     * Create a DataHolder that can be used to pass data to the converters. The DataHolder is provided with a 
     * call to {@link #marshal(Object, HierarchicalStreamWriter, DataHolder)} or 
     * {@link #unmarshal(HierarchicalStreamReader, Object, DataHolder)}.
     * 
     * @return a new {@link DataHolder}
     */
    public DataHolder newDataHolder() {
        return new MapBackedDataHolder();
    }

    /**
     * Creates an ObjectOutputStream that serializes a stream of objects to the writer using
     * XStream.
     * <p>
     * To change the name of the root element (from &lt;object-stream&gt;), use
     * {@link #createObjectOutputStream(java.io.Writer, String)}.
     * </p>
     *
     * @see #createObjectOutputStream(com.thoughtworks.xstream.io.HierarchicalStreamWriter, String)
     * @see #createObjectInputStream(com.thoughtworks.xstream.io.HierarchicalStreamReader)
     * @since 1.0.3
     */
    public ObjectOutputStream createObjectOutputStream(Writer writer) throws IOException {
        return createObjectOutputStream(hierarchicalStreamDriver.createWriter(writer), "object-stream");
    }

    /**
     * Creates an ObjectOutputStream that serializes a stream of objects to the writer using
     * XStream.
     * <p>
     * To change the name of the root element (from &lt;object-stream&gt;), use
     * {@link #createObjectOutputStream(java.io.Writer, String)}.
     * </p>
     *
     * @see #createObjectOutputStream(com.thoughtworks.xstream.io.HierarchicalStreamWriter, String)
     * @see #createObjectInputStream(com.thoughtworks.xstream.io.HierarchicalStreamReader)
     * @since 1.0.3
     */
    public ObjectOutputStream createObjectOutputStream(HierarchicalStreamWriter writer)
            throws IOException {
        return createObjectOutputStream(writer, "object-stream");
    }

    /**
     * Creates an ObjectOutputStream that serializes a stream of objects to the writer using
     * XStream.
     *
     * @see #createObjectOutputStream(com.thoughtworks.xstream.io.HierarchicalStreamWriter, String)
     * @see #createObjectInputStream(com.thoughtworks.xstream.io.HierarchicalStreamReader)
     * @since 1.0.3
     */
    public ObjectOutputStream createObjectOutputStream(Writer writer, String rootNodeName)
            throws IOException {
        return createObjectOutputStream(hierarchicalStreamDriver.createWriter(writer), rootNodeName);
    }

    /**
     * Creates an ObjectOutputStream that serializes a stream of objects to the OutputStream using
     * XStream.
     * <p>
     * To change the name of the root element (from &lt;object-stream&gt;), use
     * {@link #createObjectOutputStream(java.io.Writer, String)}.
     * </p>
     *
     * @see #createObjectOutputStream(com.thoughtworks.xstream.io.HierarchicalStreamWriter, String)
     * @see #createObjectInputStream(com.thoughtworks.xstream.io.HierarchicalStreamReader)
     * @since 1.3
     */
    public ObjectOutputStream createObjectOutputStream(OutputStream out) throws IOException {
        return createObjectOutputStream(hierarchicalStreamDriver.createWriter(out), "object-stream");
    }

    /**
     * Creates an ObjectOutputStream that serializes a stream of objects to the OutputStream using
     * XStream.
     *
     * @see #createObjectOutputStream(com.thoughtworks.xstream.io.HierarchicalStreamWriter, String)
     * @see #createObjectInputStream(com.thoughtworks.xstream.io.HierarchicalStreamReader)
     * @since 1.3
     */
    public ObjectOutputStream createObjectOutputStream(OutputStream out, String rootNodeName)
            throws IOException {
        return createObjectOutputStream(hierarchicalStreamDriver.createWriter(out), rootNodeName);
    }

    /**
     * Creates an ObjectOutputStream that serializes a stream of objects to the writer using
     * XStream.
     * <p>
     * Because an ObjectOutputStream can contain multiple items and XML only allows a single root
     * node, the stream must be written inside an enclosing node.
     * </p>
     * <p>
     * It is necessary to call ObjectOutputStream.close() when done, otherwise the stream will be
     * incomplete.
     * </p>
     * <h3>Example</h3>
     *
     * <pre>
     *  ObjectOutputStream out = xstream.createObjectOutputStream(aWriter, &quot;things&quot;);
     *   out.writeInt(123);
     *   out.writeObject(&quot;Hello&quot;);
     *   out.writeObject(someObject)
     *   out.close();
     * </pre>
     *
     * @param writer The writer to serialize the objects to.
     * @param rootNodeName The name of the root node enclosing the stream of objects.
     * @see #createObjectInputStream(com.thoughtworks.xstream.io.HierarchicalStreamReader)
     * @since 1.0.3
     */
    public ObjectOutputStream createObjectOutputStream(
            final HierarchicalStreamWriter writer, String rootNodeName) throws IOException {
        final StatefulWriter statefulWriter = new StatefulWriter(writer);
        statefulWriter.startNode(rootNodeName, null);
        return new CustomObjectOutputStream(new CustomObjectOutputStream.StreamCallback() {
            public void writeToStream(Object object) {
                marshal(object, statefulWriter);
            }

            public void writeFieldsToStream(Map fields) throws NotActiveException {
                throw new NotActiveException("not in call to writeObject");
            }

            public void defaultWriteObject() throws NotActiveException {
                throw new NotActiveException("not in call to writeObject");
            }

            public void flush() {
                statefulWriter.flush();
            }

            public void close() {
                if (statefulWriter.state() != StatefulWriter.STATE_CLOSED) {
                    statefulWriter.endNode();
                    statefulWriter.close();
                }
            }
        });
    }

    /**
     * Creates an ObjectInputStream that deserializes a stream of objects from a reader using
     * XStream.
     *
     * @see #createObjectInputStream(com.thoughtworks.xstream.io.HierarchicalStreamReader)
     * @see #createObjectOutputStream(com.thoughtworks.xstream.io.HierarchicalStreamWriter, String)
     * @since 1.0.3
     */
    public ObjectInputStream createObjectInputStream(Reader xmlReader) throws IOException {
        return createObjectInputStream(hierarchicalStreamDriver.createReader(xmlReader));
    }

    /**
     * Creates an ObjectInputStream that deserializes a stream of objects from an InputStream using
     * XStream.
     *
     * @see #createObjectInputStream(com.thoughtworks.xstream.io.HierarchicalStreamReader)
     * @see #createObjectOutputStream(com.thoughtworks.xstream.io.HierarchicalStreamWriter, String)
     * @since 1.3
     */
    public ObjectInputStream createObjectInputStream(InputStream in) throws IOException {
        return createObjectInputStream(hierarchicalStreamDriver.createReader(in));
    }

    /**
     * Creates an ObjectInputStream that deserializes a stream of objects from a reader using
     * XStream.
     * <h3>Example</h3>
     *
     * <pre>
     * ObjectInputStream in = xstream.createObjectOutputStream(aReader);
     * int a = out.readInt();
     * Object b = out.readObject();
     * Object c = out.readObject();
     * </pre>
     *
     * @see #createObjectOutputStream(com.thoughtworks.xstream.io.HierarchicalStreamWriter, String)
     * @since 1.0.3
     */
    public ObjectInputStream createObjectInputStream(final HierarchicalStreamReader reader)
            throws IOException {
        return new CustomObjectInputStream(new CustomObjectInputStream.StreamCallback() {
            public Object readFromStream() throws EOFException {
                if (!reader.hasMoreChildren()) {
                    throw new EOFException();
                }
                reader.moveDown();
                Object result = unmarshal(reader);
                reader.moveUp();
                return result;
            }

            public Map readFieldsFromStream() throws IOException {
                throw new NotActiveException("not in call to readObject");
            }

            public void defaultReadObject() throws NotActiveException {
                throw new NotActiveException("not in call to readObject");
            }

            public void registerValidation(ObjectInputValidation validation, int priority)
                    throws NotActiveException {
                throw new NotActiveException("stream inactive");
            }

            public void close() {
                reader.close();
            }
        });
    }

    /**
     * Change the ClassLoader XStream uses to load classes.
     * 
     * Creating an XStream instance it will register for all kind of classes and types of the current JDK,
     * but not for any 3rd party type. To ensure that all other types are loaded with your classloader,
     * you should call this method as early as possible - or consider to provide the classloader directly
     * in the constructor.
     *
     * @since 1.1.1
     */
    public void setClassLoader(ClassLoader classLoader) {
        classLoaderReference.setReference(classLoader);
    }

    /**
     * Retrieve the ClassLoader XStream uses to load classes.
     *
     * @since 1.1.1
     */
    public ClassLoader getClassLoader() {
        return classLoaderReference.getReference();
    }

    /**
     * Prevents a field from being serialized. To omit a field you must always provide the declaring
     * type and not necessarily the type that is converted.
     *
     * @since 1.1.3
     * @throws InitializationException if no {@link FieldAliasingMapper} is available
     */
    public void omitField(Class definedIn, String fieldName) {
        if (fieldAliasingMapper == null) {
            throw new InitializationException("No "
                    + FieldAliasingMapper.class.getName()
                    + " available");
        }
        fieldAliasingMapper.omitField(definedIn, fieldName);
    }

    /**
     * Process the annotations of the given types and configure the XStream.
     * 
     * @param types the types with XStream annotations
     * @since 1.3
     */
    public void processAnnotations(final Class[] types) {
        if (annotationConfiguration == null) {
            throw new InitializationException("No " + ANNOTATION_MAPPER_TYPE + " available");
        }
        annotationConfiguration.processAnnotations(types);
    }

    /**
     * Process the annotations of the given type and configure the XStream. A call of this method
     * will automatically turn the auto-detection mode for annotations off.
     * 
     * @param type the type with XStream annotations
     * @since 1.3
     */
    public void processAnnotations(final Class type) {
        processAnnotations(new Class[]{type});
    }

    /**
     * Set the auto-detection mode of the AnnotationMapper. Note that auto-detection implies that 
     * the XStream is configured while it is processing the XML steams. This is a potential concurrency
     * problem. Also is it technically not possible to detect all class aliases at deserialization. You have 
     * been warned! 
     * 
     * @param mode <code>true</code> if annotations are auto-detected
     * @since 1.3
     */
    public void autodetectAnnotations(boolean mode) {
        if (annotationConfiguration != null) {
            annotationConfiguration.autodetectAnnotations(mode);
        }
    }
    
    /**
     * @deprecated since 1.3, use {@link InitializationException} instead
     */
    public static class InitializationException extends XStreamException {
        public InitializationException(String message, Throwable cause) {
            super(message, cause);
        }

        public InitializationException(String message) {
            super(message);
        }
    }

    private Object readResolve() {
        jvm = new JVM();
        return this;
    }

}
