diff --git a/BUILD.txt b/BUILD.txt
new file mode 100644
index 0000000..7ccabf6
--- /dev/null
+++ b/BUILD.txt
@@ -0,0 +1,15 @@
+For Java 5 or higher build with Maven 2.2 or 3
+For Java 1.4 build with Maven 2.0.11
+
+Before building:
+
+To build:
+
+mvn clean install
+
+Before deploying:
+
+copy settings-template.xml to ~/.m2/settings.xml adding your Codehaus DAV username and passwords.
+
+To deploy (optionally adding sources and javadoc jars):
+mvn deploy
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..fa11e38
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,28 @@
+(BSD Style License)
+
+Copyright (c) 2003-2006, Joe Walnes
+Copyright (c) 2006-2011, XStream Committers
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list of
+conditions and the following disclaimer. Redistributions in binary form must reproduce
+the above copyright notice, this list of conditions and the following disclaimer in
+the documentation and/or other materials provided with the distribution.
+
+Neither the name of XStream nor the names of its contributors may be used to endorse
+or promote products derived from this software without specific prior written
+permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..0639a48
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,37 @@
+
+ ***********
+********************** XStream **********************
+ ***********
+
+ "Java to XML Serialization, and back again"
+
+
+--[ Binaries ]-----------------------------------------------
+
+All binary artifacts are in the 'lib' directory. These include the
+xstream jars and any other library used at build time,
+or optional runtime extras. kXML2 is recommend for use as it will
+greatly improve the performance of XStream.
+
+--[ Documentation ]------------------------------------------
+
+Documentation can be found in docs/index.html. This includes:
+ * Introduction and tutorial
+ * JavaDoc
+ * Change log
+ * Frequently asked questions
+
+--[ Source ]-------------------------------------------------
+
+The complete source for XStream is bundled. This includes:
+ * Main API [src/java]
+ * Unit tests [src/test]
+ * Maven build files [pom.xml]
+ * Dependencies [lib]
+
+-------------------------------------------------------------
+
+-XStream Ccommitters
+
+ http://xstream.codehaus.org/
+
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..e77fd16
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,822 @@
+
+
+ 4.0.0
+
+ org.codehaus
+ codehaus-parent
+ 3
+
+
+ com.thoughtworks.xstream
+ xstream-parent
+ pom
+ 1.4.8
+ XStream Parent
+
+ XStream is a serialization library from Java objects to XML and back.
+
+
+ 2004
+
+ XStream
+ http://xstream.codehaus.org
+
+
+
+
+ jdk19
+
+ 1.9
+
+
+ 1.6
+ 1.6
+
+
+
+ jdk18ge
+
+ [1.8,)
+
+
+ -Xdoclint:-missing
+
+
+
+ jdk15-ge
+
+ [1.5,)
+
+
+
+
+ org.apache.felix
+ maven-bundle-plugin
+
+
+ bundle-manifest
+ process-classes
+
+ manifest
+
+
+
+
+
+
+
+
+
+
+
+ jdk15
+
+ 1.5
+
+
+ 1.8.0.10
+ 3.6.6.Final
+
+
+
+
+ jdk14
+
+ 1.4
+
+
+ 1.3
+ 1.3
+ http://docs.oracle.com/javase/1.4.2/docs/api/
+
+ 1.8.0.10
+ 3.3.2.GA
+ 3.6.6.Final
+
+
+
+ java
+
+
+ src/java
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
+
+ ${version.plugin.maven.enforcer}
+
+
+ enforce-java-version
+
+ enforce
+
+
+
+
+ ${version.java.enforced}
+
+
+
+
+
+
+
+
+
+
+ xstream-release
+
+ [1.8,1.9)
+
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+
+
+
+
+
+
+
+
+ xstream
+ xstream-hibernate
+ xstream-benchmark
+ xstream-distribution
+
+
+
+
+ BSD style
+ http://xstream.codehaus.org/license.html
+ repo
+
+
+
+
+
+
+ com.thoughtworks.xstream
+ xstream
+ 1.4.8
+
+
+ com.thoughtworks.xstream
+ xstream
+ 1.4.8
+ tests
+ test-jar
+ test
+
+
+ com.thoughtworks.xstream
+ xstream
+ 1.4.8
+ javadoc
+ provided
+
+
+ com.thoughtworks.xstream
+ xstream-hibernate
+ 1.4.8
+
+
+ com.thoughtworks.xstream
+ xstream-hibernate
+ 1.4.8
+ javadoc
+ provided
+
+
+ com.thoughtworks.xstream
+ xstream-benchmark
+ 1.4.8
+
+
+ com.thoughtworks.xstream
+ xstream-benchmark
+ 1.4.8
+ javadoc
+ provided
+
+
+
+ commons-io
+ commons-io
+ ${version.commons.io}
+
+
+
+ commons-cli
+ commons-cli
+ ${version.commons.cli}
+
+
+
+ commons-lang
+ commons-lang
+ ${version.commons.lang}
+
+
+
+ cglib
+ cglib-nodep
+ ${version.cglib.nodep}
+
+
+ javassist
+ javassist
+ ${version.javaassist}
+
+
+
+ dom4j
+ dom4j
+ ${version.dom4j}
+
+
+ xml-apis
+ xml-apis
+
+
+
+
+
+ org.jdom
+ jdom
+ ${version.org.jdom}
+
+
+ org.jdom
+ jdom2
+ ${version.org.jdom2}
+
+
+
+ joda-time
+ joda-time
+ ${version.joda-time}
+
+
+
+ com.megginson.sax
+ xml-writer
+ ${version.com.megginson.sax.xml-writer}
+
+
+ xml-apis
+ xml-apis
+
+
+
+
+
+ stax
+ stax
+ ${version.stax}
+
+
+ stax
+ stax-api
+ ${version.stax.api}
+
+
+
+ org.codehaus.woodstox
+ wstx-asl
+ ${version.org.codehaus.woodstox.asl}
+
+
+
+ xom
+ xom
+ ${version.xom}
+
+
+ xerces
+ xmlParserAPIs
+
+
+ xerces
+ xercesImpl
+
+
+ xalan
+ xalan
+
+
+ jaxen
+ jaxen
+
+
+
+
+
+ xpp3
+ xpp3_min
+ ${version.xpp3}
+
+
+ net.sf.kxml
+ kxml2-min
+ ${version.net.sf.kxml.kxml2}
+
+
+ net.sf.kxml
+ kxml2
+ ${version.net.sf.kxml.kxml2}
+
+
+ xmlpull
+ xmlpull
+ ${version.xmlpull}
+
+
+
+ oro
+ oro
+ ${version.oro}
+
+
+
+ org.json
+ json
+ ${version.org.json}
+
+
+
+ org.codehaus.jettison
+ jettison
+ ${version.org.codehaus.jettison}
+
+
+
+ xml-apis
+ xml-apis
+ ${version.xml-apis}
+
+
+
+ xerces
+ xercesImpl
+ ${version.xerces.impl}
+
+
+
+ org.hibernate
+ hibernate-core
+ ${version.org.hibernate.core}
+
+
+ org.hibernate
+ hibernate-envers
+ ${version.org.hibernate.envers}
+
+
+ cglib
+ cglib
+
+
+
+
+ org.hsqldb
+ hsqldb
+ ${version.hsqldb}
+
+
+ org.slf4j
+ slf4j-api
+ ${version.org.slf4j}
+ runtime
+
+
+ org.slf4j
+ slf4j-simple
+ ${version.org.slf4j}
+ runtime
+
+
+
+
+ junit
+ junit
+ ${version.junit}
+ test
+
+
+
+ jmock
+ jmock
+ ${version.jmock}
+ test
+
+
+
+
+
+
+ ${basedir}/src/java
+
+
+ ${basedir}/src/java
+
+ **/*.properties
+ **/*.xml
+
+
+
+ ${basedir}/src/test
+
+
+ ${basedir}/src/test
+
+ **/*.xml
+ **/*.xsl
+ **/*.txt
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-antrun-plugin
+ ${version.plugin.maven.antrun}
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ ${version.plugin.maven.assembly}
+
+
+ org.apache.maven.plugins
+ maven-clean-plugin
+ ${version.plugin.maven.clean}
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${version.plugin.maven.compiler}
+
+
+ ${version.java.target}
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ ${version.plugin.maven.dependency}
+
+
+ org.apache.maven.plugins
+ maven-deploy-plugin
+ ${version.plugin.maven.deploy}
+
+
+ org.apache.maven.plugins
+ maven-eclipse-plugin
+
+ true
+ [artifactId]-1.4.x
+
+
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
+ ${version.plugin.maven.enforcer}
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+ ${version.plugin.maven.gpg}
+
+
+ org.apache.maven.plugins
+ maven-install-plugin
+ ${version.plugin.maven.install}
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ ${version.plugin.maven.jar}
+
+
+
+ true
+ true
+
+
+ ${project.info.majorVersion}.${project.info.minorVersion}
+ ${version.java.source}
+ ${version.java.target}
+ Maven ${maven.version}
+ ${maven.build.timestamp}
+ ${os.name}
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ ${version.plugin.maven.javadoc}
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+ false
+ ${javadoc.xdoclint}
+
+
+ ${link.javadoc.javase}
+
+
+
+ true
+ true
+
+
+ ${project.info.majorVersion}.${project.info.minorVersion}
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-release-plugin
+ ${version.plugin.maven.release}
+
+ deploy
+ true
+
+ -Pxstream-release
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ ${version.plugin.maven.resources}
+
+
+ org.apache.maven.plugins
+ maven-site-plugin
+ ${version.plugin.maven.site}
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ ${version.plugin.maven.source}
+
+
+ attach-sources
+ package
+
+ jar-no-fork
+
+
+
+
+
+
+ true
+ true
+
+
+ ${project.info.majorVersion}.${project.info.minorVersion}
+ 2
+ ${project.name} Sources
+ ${project.artifactId}.sources
+ ${project.organization.name} Sources
+ ${project.info.osgiVersion} Sources
+ ${project.artifactId};version=${project.info.osgiVersion}
+ ${version.java.source}
+ ${version.java.target}
+ Maven ${maven.version}
+ ${maven.build.timestamp}
+ ${os.name}
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${version.plugin.maven.surefire}
+
+ once
+ true
+ false
+
+ **/*Test.java
+ **/*TestSuite.java
+
+
+ **/Abstract*Test.java
+ **/*$*.java
+
+
+
+ java.awt.headless
+ true
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-report-plugin
+ ${version.plugin.maven.surefire}
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ ${version.plugin.mojo.build-helper}
+
+
+ org.codehaus.mojo
+ cobertura-maven-plugin
+ ${version.plugin.mojo.cobertura}
+
+
+
+ clean
+
+
+
+
+
+ org.codehaus.mojo
+ jxr-maven-plugin
+ ${version.plugin.mojo.jxr}
+
+
+ org.codehaus.xsite
+ xsite-maven-plugin
+ ${version.plugin.codehaus.xsite}
+
+
+ org.apache.felix
+ maven-bundle-plugin
+ ${version.plugin.felix.bundle}
+
+ ${project.build.directory}/OSGi
+
+ <_nouses>true
+ ${project.artifactId}
+ ${project.info.majorVersion}.${project.info.minorVersion}
+
+
+
+ false
+
+
+
+
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+
+
+ versions
+ initialize
+
+ maven-version
+ parse-version
+
+
+ project.info
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-release-plugin
+
+ https://svn.codehaus.org/xstream/tags
+
+
+
+
+
+ org.apache.maven.wagon
+ wagon-webdav
+ ${version.org.apache.maven.wagon.webdev}
+
+
+
+
+
+
+
+ codehaus.org
+ Codehaus XStream Site
+ dav:https://dav.codehaus.org/xstream
+
+
+
+
+ scm:svn:http://svn.codehaus.org/xstream/tags/XSTREAM_1_4_8
+ scm:svn:https://svn.codehaus.org/xstream/tags/XSTREAM_1_4_8
+ http://fisheye.codehaus.org/browse/xstream/tags/XSTREAM_1_4_8
+
+
+
+ 1.5
+ 1.5
+ [1.4,)
+
+ 1.2.1
+ 2.3.7
+ 1.1
+ 2.1
+ 2.2
+ 2.1
+ 2.1
+ 2.3
+ 1.4
+ 1.4
+ 2.2
+ 2.2
+ 2.10
+ 2.1
+ 2.2
+ 2.0-beta-6
+ 2.1.2
+ 2.4.3
+ 1.5
+ 2.0
+ 2.0-beta-1
+
+ 1.0-beta-2
+
+ 2.2
+ 0.2
+ 1.1
+ 1.4
+ 2.4
+ 1.6.1
+ 2.2.8
+ 3.12.1.GA
+ 1.0.1
+ 1.6
+ 3.8.1
+ 2.3.0
+ 1.2
+ 3.2.7
+ 4.2.5.Final
+ ${version.org.hibernate.core}
+ 1.1.3
+ 2.0.5
+ 20080701
+ 1.6.1
+ 2.0.8
+ 1.2.0
+ 1.0.1
+ 2.8.1
+ 1.3.04
+ 1.1.3.1
+ 1.1
+ 1.1.4c
+
+ http://docs.oracle.com/javase/8/docs/api/
+
+
+
+
diff --git a/settings-template.xml b/settings-template.xml
new file mode 100644
index 0000000..b67ae92
--- /dev/null
+++ b/settings-template.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+ codehaus-nexus-snapshots
+ your-xircles-id
+ your-xircles-pwd
+
+
+ codehaus-nexus-staging
+ your-xircles-id
+ your-xircles-pwd
+
+
+ codehaus.org
+
+
+
+
+
diff --git a/svn-autoprops.config b/svn-autoprops.config
new file mode 100644
index 0000000..b0fcf67
--- /dev/null
+++ b/svn-autoprops.config
@@ -0,0 +1,84 @@
+[auto-props]
+*.apt = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.c = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.c++ = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.cpp = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.cs = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.css = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.dtd = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.ent = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.fml = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.groovy = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.h = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.h++ = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.hpp = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.html = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.idl = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.include = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.java = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.js = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.jsp = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.ldf = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.ldif = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.mak = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.mdo = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.php = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.rb = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.rtf = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.sql = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.svg = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.t2t = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.vm = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.xhtml = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.xml = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.xsd = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.xsl = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+*.xslt = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+Makefile = svn:eol-style=native;svn:keywords=Author Date Id HeadURL Revision
+
+*.launch = svn:eol-style=native
+*.MF = svn:eol-style=native
+*.properties = svn:eol-style=native
+*.script = svn:eol-style=native
+*.txt = svn:eol-style=native
+
+*.dsp = svn:eol-style=CRLF
+*.dsw = svn:eol-style=CRLF
+
+*.iml = svn:eol-style=LF
+
+*.bat = svn:eol-style=CRLF;svn:executable;svn:keywords=Author Date Id HeadURL Revision
+*.cmd = svn:eol-style=CRLF;svn:executable;svn:keywords=Author Date Id HeadURL Revision
+
+*.ksh = svn:eol-style=LF;svn:executable;svn:keywords=Author Date Id HeadURL Revision
+*.sh = svn:eol-style=LF;svn:executable;svn:keywords=Author Date Id HeadURL Revision
+
+*.pl = svn:eol-style=native;svn:executable;svn:keywords=Author Date Id HeadURL Revision
+*.py = svn:eol-style=native;svn:executable;svn:keywords=Author Date Id HeadURL Revision
+
+*.bmp = svn:mime-type=image/bmp;svn:needs-lock=*
+*.gif = svn:mime-type=image/gif;svn:needs-lock=*
+*.ico = svn:mime-type=image/x-icon;svn:needs-lock=*
+*.jpeg = svn:mime-type=image/jpeg;svn:needs-lock=*
+*.jpg = svn:mime-type=image/jpeg;svn:needs-lock=*
+*.png = svn:mime-type=image/png;svn:needs-lock=*
+*.tif = svn:mime-type=image/tiff;svn:needs-lock=*
+*.tiff = svn:mime-type=image/tiff;svn:needs-lock=*
+
+*.doc = svn:mime-type=application/msword;svn:needs-lock=*
+*.jar = svn:mime-type=application/octet-stream;svn:needs-lock=*
+*.odc = svn:mime-type=application/vnd.oasis.opendocument.chart;svn:needs-lock=*
+*.odf = svn:mime-type=application/vnd.oasis.opendocument.formula;svn:needs-lock=*
+*.odg = svn:mime-type=application/vnd.oasis.opendocument.graphics;svn:needs-lock=*
+*.odi = svn:mime-type=application/vnd.oasis.opendocument.image;svn:needs-lock=*
+*.odp = svn:mime-type=application/vnd.oasis.opendocument.presentation;svn:needs-lock=*
+*.ods = svn:mime-type=application/vnd.oasis.opendocument.spreadsheet;svn:needs-lock=*
+*.odt = svn:mime-type=application/vnd.oasis.opendocument.text;svn:needs-lock=*
+*.pdf = svn:mime-type=application/pdf;svn:needs-lock=*
+*.ppt = svn:mime-type=application/vnd.ms-powerpoint;svn:needs-lock=*
+*.ser = svn:mime-type=application/octet-stream;svn:needs-lock=*
+*.swf = svn:mime-type=application/x-shockwave-flash;svn:needs-lock=*
+*.vsd = svn:mime-type=application/x-visio;svn:needs-lock=*
+*.xls = svn:mime-type=application/vnd.ms-excel;svn:needs-lock=*
+*.zip = svn:mime-type=application/zip;svn:needs-lock=*
+
diff --git a/xstream/pom.xml b/xstream/pom.xml
new file mode 100644
index 0000000..779df61
--- /dev/null
+++ b/xstream/pom.xml
@@ -0,0 +1,432 @@
+
+
+ 4.0.0
+
+ com.thoughtworks.xstream
+ xstream-parent
+ 1.4.8
+
+ xstream
+ jar
+ XStream Core
+
+
+
+ dom4j
+ dom4j
+ true
+
+
+
+ org.jdom
+ jdom
+ true
+
+
+ org.jdom
+ jdom2
+ true
+
+
+
+ joda-time
+ joda-time
+ true
+
+
+
+ stax
+ stax
+ true
+
+
+
+ org.codehaus.woodstox
+ wstx-asl
+ true
+
+
+
+ stax
+ stax-api
+ true
+
+
+
+ xom
+ xom
+ true
+
+
+
+ xmlpull
+ xmlpull
+
+
+
+ net.sf.kxml
+ kxml2-min
+ true
+
+
+
+ net.sf.kxml
+ kxml2
+ true
+
+
+
+ xpp3
+ xpp3_min
+
+
+
+ cglib
+ cglib-nodep
+ true
+
+
+
+ org.codehaus.jettison
+ jettison
+ true
+
+
+
+
+ junit
+ junit
+
+
+
+ jmock
+ jmock
+
+
+
+ org.json
+ json
+ test
+
+
+
+ com.megginson.sax
+ xml-writer
+ test
+
+
+
+ oro
+ oro
+ test
+
+
+
+ commons-lang
+ commons-lang
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ complete-test-classpath
+ process-test-resources
+
+ copy
+
+
+ target/lib
+
+
+ proxytoys
+ proxytoys
+ 0.2.1
+
+
+
+
+
+ collect-dependencies
+ package
+
+ copy-dependencies
+
+
+ target/dependencies
+ runtime
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+
+ test-jar
+
+
+
+ **/AbstractAcceptanceTest.*
+
+
+
+ ${project.name} Test
+ ${project.name} Test
+
+
+
+
+
+
+
+
+
+
+
+ jdk18ge
+
+ [1.8,)
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ -XDignore.symbol.file
+
+
+ **/Lambda**
+
+
+ **/Lambda**
+
+
+
+
+ compile-jdk18
+
+
+ 1.8
+
+ foo
+
+
+ foo
+
+
+
+ compile
+ testCompile
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+
+ com.thoughtworks.xstream.core.util
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ ${version.plugin.maven.javadoc}
+
+ com.thoughtworks.xstream.core.util
+ ${javadoc.xdoclint}
+ false
+
+
+ ${link.javadoc.javase}
+
+
+
+
+
+
+
+ jdk17
+
+ 1.7
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ -XDignore.symbol.file
+
+
+ **/Lambda**
+
+
+ **/Lambda**
+
+
+
+
+
+
+
+ jdk15-16
+
+ [1.5,1.7)
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ -XDignore.symbol.file
+
+
+ **/Lambda**
+
+
+ **/Lambda**
+ **/extended/*17Test*
+
+
+
+
+
+
+
+ jdk15-ge
+
+ [1.5,)
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+ default-jar
+
+ jar
+
+
+
+ ${project.build.directory}/OSGi/MANIFEST.MF
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+
+
+ org.apache.felix
+ maven-bundle-plugin
+
+
+ !com.thoughtworks.xstream.core.util,com.thoughtworks.xstream.*;-noimport:=true
+
+
+
+
+
+
+
+ jdk14
+
+ 1.4
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ **/Lambda**
+ **/annotations/*
+ **/AnnotationMapper*
+ **/EnumMapper*
+ **/enums/*
+ **/basic/StringBuilder*
+ **/basic/UUID*
+ **/core/util/Types*
+ **/extended/*15*
+ **/io/xml/JDom2*
+
+
+ **/Lambda**
+ **/annotations/*
+ **/enums/*
+ **/extended/*17Test*
+ **/reflection/SunLimitedUnsafeReflectionProviderTest*
+ **/reflection/PureJavaReflectionProvider15Test*
+ **/io/xml/JDom2*Test*
+ **/acceptance/Basic15TypesTest*
+ **/acceptance/Concurrent15TypesTest*
+
+
+
+
+
+
+
+ xml-apis
+ xml-apis
+
+
+ xerces
+ xercesImpl
+
+
+
+ 1.0.1
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-report-plugin
+ ${version.plugin.maven.surefire}
+
+
+ org.codehaus.mojo
+ cobertura-maven-plugin
+ ${version.plugin.mojo.cobertura}
+
+
+
+
+
diff --git a/xstream/src/java/com/thoughtworks/xstream/InitializationException.java b/xstream/src/java/com/thoughtworks/xstream/InitializationException.java
new file mode 100644
index 0000000..ffb5c7a
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/InitializationException.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 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 22. October 2007 by Joerg Schaible
+ */
+package com.thoughtworks.xstream;
+
+/**
+ * Exception thrown configuring an XStream instance.
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ * @since 1.3
+ */
+public class InitializationException extends XStream.InitializationException {
+ public InitializationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public InitializationException(String message) {
+ super(message);
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/MarshallingStrategy.java b/xstream/src/java/com/thoughtworks/xstream/MarshallingStrategy.java
new file mode 100644
index 0000000..d8165d2
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/MarshallingStrategy.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2004, 2006 Joe Walnes.
+ * Copyright (C) 2007, 2009 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 16. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream;
+
+import com.thoughtworks.xstream.converters.ConverterLookup;
+import com.thoughtworks.xstream.converters.DataHolder;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+public interface MarshallingStrategy {
+
+ Object unmarshal(Object root, HierarchicalStreamReader reader, DataHolder dataHolder, ConverterLookup converterLookup, Mapper mapper);
+ void marshal(HierarchicalStreamWriter writer, Object obj, ConverterLookup converterLookup, Mapper mapper, DataHolder dataHolder);
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/XStream.java b/xstream/src/java/com/thoughtworks/xstream/XStream.java
new file mode 100644
index 0000000..40f83d2
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/XStream.java
@@ -0,0 +1,2207 @@
+/*
+ * Copyright (C) 2003, 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 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 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.Field;
+import java.lang.reflect.Method;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.net.URI;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Calendar;
+import java.util.Collections;
+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.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.Vector;
+import java.util.regex.Pattern;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+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.URIConverter;
+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.SingletonCollectionConverter;
+import com.thoughtworks.xstream.converters.collections.SingletonMapConverter;
+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.JavaFieldConverter;
+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.SerializableConverter;
+import com.thoughtworks.xstream.core.ClassLoaderReference;
+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.CompositeClassLoader;
+import com.thoughtworks.xstream.core.util.CustomObjectInputStream;
+import com.thoughtworks.xstream.core.util.CustomObjectOutputStream;
+import com.thoughtworks.xstream.core.util.SelfStreamingInstanceChecker;
+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.SecurityMapper;
+import com.thoughtworks.xstream.mapper.SystemAttributeAliasingMapper;
+import com.thoughtworks.xstream.mapper.XStream11XmlFriendlyMapper;
+import com.thoughtworks.xstream.security.AnyTypePermission;
+import com.thoughtworks.xstream.security.ExplicitTypePermission;
+import com.thoughtworks.xstream.security.NoPermission;
+import com.thoughtworks.xstream.security.NoTypePermission;
+import com.thoughtworks.xstream.security.RegExpTypePermission;
+import com.thoughtworks.xstream.security.TypeHierarchyPermission;
+import com.thoughtworks.xstream.security.TypePermission;
+import com.thoughtworks.xstream.security.WildcardTypePermission;
+
+
+/**
+ * Simple facade to XStream library, a Java-XML serialization tool.
+ *
+ *
+ * Example
+ *
+ *
+ * XStream xstream = new XStream();
+ * String xml = xstream.toXML(myObject); // serialize to XML
+ * Object myObject2 = xstream.fromXML(xml); // deserialize from XML
+ *
+ *
+ *
+ *
+ *
+ *
Aliasing classes
+ *
+ *
+ * To create shorter XML, you can specify aliases for classes using the alias()
+ * method. For example, you can shorten all occurrences of element
+ * <com.blah.MyThing> to <my-thing> by registering an
+ * alias for the class.
+ *
+ *
+ *
+ *
+ *
+ * xstream.alias("my-thing", MyThing.class);
+ *
+ *
+ *
+ *
+ *
+ *
Converters
+ *
+ *
+ * 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.
+ *
+ *
+ *
+ * Extra converters can be registered using the registerConverter() 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.
+ *
+ * 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.
+ *
Uses XPath absolute references to signify duplicate references. The XPath expression ensures that
+ * a single node only is selected always.
+ *
+ *
+ *
xstream.setMode(XStream.ID_REFERENCES);
+ *
Uses ID references to signify duplicate references. In some scenarios, such as when using
+ * hand-written XML, this is easier to work with.
+ *
+ *
+ *
xstream.setMode(XStream.NO_REFERENCES);
+ *
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.
+ *
+ *
+ *
Thread safety
+ *
+ * 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. Note, that this only applies if annotations are not
+ * auto-detected on-the-fly.
+ *
+ *
Implicit collections
+ *
+ *
+ * To avoid the need for special tags for collections, you can define implicit collections using one
+ * of the addImplicitCollection methods.
+ *
+ *
+ * @author Joe Walnes
+ * @author Jö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 SecurityMapper securityMapper;
+ private AnnotationConfiguration annotationConfiguration;
+
+ 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;
+ public static final int SINGLE_NODE_XPATH_RELATIVE_REFERENCES = 1005;
+ public static final int SINGLE_NODE_XPATH_ABSOLUTE_REFERENCES = 1006;
+
+ 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";
+ private static final Pattern IGNORE_ALL = Pattern.compile(".*");
+
+ /**
+ * 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.
+ *
+ *
+ * @param reflectionProvider the reflection provider to use or null for best
+ * matching reflection provider
+ * @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.
+ *
+ *
+ * @param hierarchicalStreamDriver the driver instance
+ * @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}.
+ *
+ * @param reflectionProvider the reflection provider to use or null for best
+ * matching Provider
+ * @param hierarchicalStreamDriver the driver instance
+ * @throws InitializationException in case of an initialization problem
+ */
+ public XStream(
+ ReflectionProvider reflectionProvider, HierarchicalStreamDriver hierarchicalStreamDriver) {
+ this(reflectionProvider, (Mapper)null, hierarchicalStreamDriver);
+ }
+
+ /**
+ * Constructs an XStream with a special {@link HierarchicalStreamDriver},
+ * {@link ReflectionProvider} and a prepared {@link Mapper} chain.
+ *
+ * @param reflectionProvider the reflection provider to use or null for best
+ * matching Provider
+ * @param mapper the instance with the {@link Mapper} chain or null for the default
+ * chain
+ * @param driver the driver instance
+ * @throws InitializationException in case of an initialization problem
+ * @deprecated As of 1.3, use
+ * {@link #XStream(ReflectionProvider, HierarchicalStreamDriver, ClassLoader, Mapper)}
+ * instead
+ */
+ public XStream(
+ ReflectionProvider reflectionProvider, Mapper mapper, HierarchicalStreamDriver driver) {
+ this(reflectionProvider, driver, new CompositeClassLoader(), mapper);
+ }
+
+ /**
+ * Constructs an XStream with a special {@link HierarchicalStreamDriver},
+ * {@link ReflectionProvider} and a {@link ClassLoaderReference}.
+ *
+ * @param reflectionProvider the reflection provider to use or null for best
+ * matching Provider
+ * @param driver the driver instance
+ * @param classLoaderReference the reference to the {@link ClassLoader} to use
+ * @throws InitializationException in case of an initialization problem
+ * @since 1.4.5
+ */
+ public XStream(
+ ReflectionProvider reflectionProvider, HierarchicalStreamDriver driver,
+ ClassLoaderReference classLoaderReference) {
+ this(reflectionProvider, driver, classLoaderReference, null);
+ }
+
+ /**
+ * Constructs an XStream with a special {@link HierarchicalStreamDriver},
+ * {@link ReflectionProvider} and the {@link ClassLoader} to use.
+ *
+ * @throws InitializationException in case of an initialization problem
+ * @since 1.3
+ * @deprecated As of 1.4.5 use
+ * {@link #XStream(ReflectionProvider, HierarchicalStreamDriver, ClassLoaderReference)}
+ */
+ public XStream(
+ ReflectionProvider reflectionProvider, HierarchicalStreamDriver driver,
+ ClassLoader classLoader) {
+ this(reflectionProvider, driver, classLoader, null);
+ }
+
+ /**
+ * Constructs an XStream with a special {@link HierarchicalStreamDriver},
+ * {@link ReflectionProvider}, a prepared {@link Mapper} chain and the {@link ClassLoader}
+ * to use.
+ *
+ * @param reflectionProvider the reflection provider to use or null for best
+ * matching Provider
+ * @param driver the driver instance
+ * @param classLoader the {@link ClassLoader} to use
+ * @param mapper the instance with the {@link Mapper} chain or null for the default
+ * chain
+ * @throws InitializationException in case of an initialization problem
+ * @since 1.3
+ * @deprecated As of 1.4.5 use
+ * {@link #XStream(ReflectionProvider, HierarchicalStreamDriver, ClassLoaderReference, Mapper)}
+ */
+ public XStream(
+ ReflectionProvider reflectionProvider, HierarchicalStreamDriver driver,
+ ClassLoader classLoader, Mapper mapper) {
+ this(
+ reflectionProvider, driver, new ClassLoaderReference(classLoader), mapper, new DefaultConverterLookup());
+ }
+
+ /**
+ * Constructs an XStream with a special {@link HierarchicalStreamDriver},
+ * {@link ReflectionProvider}, a prepared {@link Mapper} chain and the
+ * {@link ClassLoaderReference}.
+ *
+ * The {@link ClassLoaderReference} should also be used for the {@link Mapper} chain.
+ *
+ *
+ * @param reflectionProvider the reflection provider to use or null for best
+ * matching Provider
+ * @param driver the driver instance
+ * @param classLoaderReference the reference to the {@link ClassLoader} to use
+ * @param mapper the instance with the {@link Mapper} chain or null for the default
+ * chain
+ * @throws InitializationException in case of an initialization problem
+ * @since 1.4.5
+ */
+ public XStream(
+ ReflectionProvider reflectionProvider, HierarchicalStreamDriver driver,
+ ClassLoaderReference classLoaderReference, Mapper mapper) {
+ this(
+ reflectionProvider, driver, classLoaderReference, mapper, new DefaultConverterLookup());
+ }
+
+ private XStream(
+ ReflectionProvider reflectionProvider, HierarchicalStreamDriver driver, ClassLoaderReference classLoader,
+ Mapper mapper, final DefaultConverterLookup defaultConverterLookup) {
+ this(reflectionProvider, driver, classLoader, mapper, new ConverterLookup() {
+ public Converter lookupConverterForType(Class type) {
+ return defaultConverterLookup.lookupConverterForType(type);
+ }
+ }, new ConverterRegistry() {
+ public void registerConverter(Converter converter, int priority) {
+ defaultConverterLookup.registerConverter(converter, priority);
+ }
+ });
+ }
+
+ /**
+ * Constructs an XStream with a special {@link HierarchicalStreamDriver},
+ * {@link ReflectionProvider}, a prepared {@link Mapper} chain, the
+ * {@link ClassLoaderReference} and an own {@link ConverterLookup} and
+ * {@link ConverterRegistry}.
+ *
+ * @param reflectionProvider the reflection provider to use or null for best
+ * matching Provider
+ * @param driver the driver instance
+ * @param classLoader the {@link ClassLoader} to use
+ * @param mapper the instance with the {@link Mapper} chain or null for the default
+ * chain
+ * @param converterLookup the instance that is used to lookup the converters
+ * @param converterRegistry an instance to manage the converter instances
+ * @throws InitializationException in case of an initialization problem
+ * @since 1.3
+ * @deprecated As of 1.4.5 use
+ * {@link #XStream(ReflectionProvider, HierarchicalStreamDriver, ClassLoaderReference, Mapper, ConverterLookup, ConverterRegistry)}
+ */
+ public XStream(
+ ReflectionProvider reflectionProvider, HierarchicalStreamDriver driver,
+ ClassLoader classLoader, Mapper mapper, ConverterLookup converterLookup,
+ ConverterRegistry converterRegistry) {
+ this(reflectionProvider, driver, new ClassLoaderReference(classLoader), mapper, converterLookup, converterRegistry);
+ }
+
+ /**
+ * Constructs an XStream with a special {@link HierarchicalStreamDriver},
+ * {@link ReflectionProvider}, a prepared {@link Mapper} chain, the
+ * {@link ClassLoaderReference} and an own {@link ConverterLookup} and
+ * {@link ConverterRegistry}.
+ *
+ * The ClassLoaderReference should also be used for the Mapper chain. The ConverterLookup
+ * should access the ConverterRegistry if you intent to register {@link Converter} instances
+ * with XStream facade or you are using annotations.
+ *
+ *
+ * @param reflectionProvider the reflection provider to use or null for best
+ * matching Provider
+ * @param driver the driver instance
+ * @param classLoaderReference the reference to the {@link ClassLoader} to use
+ * @param mapper the instance with the {@link Mapper} chain or null for the default
+ * chain
+ * @param converterLookup the instance that is used to lookup the converters
+ * @param converterRegistry an instance to manage the converter instances or null
+ * to prevent any further registry (including annotations)
+ * @throws InitializationException in case of an initialization problem
+ * @since 1.4.5
+ */
+ public XStream(
+ ReflectionProvider reflectionProvider, HierarchicalStreamDriver driver,
+ ClassLoaderReference classLoaderReference, Mapper mapper, ConverterLookup converterLookup,
+ ConverterRegistry converterRegistry) {
+ if (reflectionProvider == null) {
+ reflectionProvider = JVM.newReflectionProvider();
+ }
+ this.reflectionProvider = reflectionProvider;
+ this.hierarchicalStreamDriver = driver;
+ this.classLoaderReference = classLoaderReference;
+ this.converterLookup = converterLookup;
+ this.converterRegistry = converterRegistry;
+ this.mapper = mapper == null ? buildMapper() : mapper;
+
+ setupMappers();
+ setupSecurity();
+ 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, reflectionProvider);
+ 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.is18()) {
+ mapper = buildMapperDynamically("com.thoughtworks.xstream.mapper.LambdaMapper", new Class[]{Mapper.class},
+ new Object[]{mapper});
+ }
+ mapper = new SecurityMapper(mapper);
+ if (JVM.is15()) {
+ mapper = buildMapperDynamically(ANNOTATION_MAPPER_TYPE, new Class[]{
+ Mapper.class, ConverterRegistry.class, ConverterLookup.class,
+ ClassLoaderReference.class, ReflectionProvider.class}, new Object[]{
+ mapper, converterRegistry, converterLookup, classLoaderReference,
+ reflectionProvider});
+ }
+ 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 com.thoughtworks.xstream.InitializationException(
+ "Could not instantiate mapper : " + className, e);
+ } catch (LinkageError e) {
+ throw new com.thoughtworks.xstream.InitializationException(
+ "Could not instantiate mapper : " + className, e);
+ }
+ }
+
+ protected MapperWrapper wrapMapper(MapperWrapper next) {
+ return next;
+ }
+
+ /**
+ * @deprecated As of 1.4.8
+ */
+ 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);
+ securityMapper = (SecurityMapper)this.mapper
+ .lookupMapperOfType(SecurityMapper.class);
+ annotationConfiguration = (AnnotationConfiguration)this.mapper
+ .lookupMapperOfType(AnnotationConfiguration.class);
+ }
+
+ protected void setupSecurity() {
+ if (securityMapper == null) {
+ return;
+ }
+
+ addPermission(AnyTypePermission.ANY);
+ }
+
+ 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("field", Field.class);
+ alias("date", Date.class);
+ alias("uri", URI.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("sorted-set", SortedSet.class);
+
+ alias("linked-list", LinkedList.class);
+ alias("vector", Vector.class);
+ alias("tree-map", TreeMap.class);
+ alias("tree-set", TreeSet.class);
+ alias("hashtable", Hashtable.class);
+
+ alias("empty-list", Collections.EMPTY_LIST.getClass());
+ alias("empty-map", Collections.EMPTY_MAP.getClass());
+ alias("empty-set", Collections.EMPTY_SET.getClass());
+ alias("singleton-list", Collections.singletonList(this).getClass());
+ alias("singleton-map", Collections.singletonMap(this, null).getClass());
+ alias("singleton-set", Collections.singleton(this).getClass());
+
+ if (JVM.isAWTAvailable()) {
+ // 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.loadClassForName("java.awt.Color", false));
+ alias("awt-font", JVM.loadClassForName("java.awt.Font", false));
+ alias("awt-text-attribute", JVM.loadClassForName("java.awt.font.TextAttribute"));
+ }
+
+ if (JVM.isSQLAvailable()) {
+ alias("sql-timestamp", JVM.loadClassForName("java.sql.Timestamp"));
+ alias("sql-time", JVM.loadClassForName("java.sql.Time"));
+ alias("sql-date", JVM.loadClassForName("java.sql.Date"));
+ }
+
+ alias("file", File.class);
+ alias("locale", Locale.class);
+ alias("gregorian-calendar", Calendar.class);
+
+ if (JVM.is14()) {
+ aliasDynamically("auth-subject", "javax.security.auth.Subject");
+ alias("linked-hash-map", JVM.loadClassForName("java.util.LinkedHashMap"));
+ alias("linked-hash-set", JVM.loadClassForName("java.util.LinkedHashSet"));
+ alias("trace", JVM.loadClassForName("java.lang.StackTraceElement"));
+ alias("currency", JVM.loadClassForName("java.util.Currency"));
+ aliasType("charset", JVM.loadClassForName("java.nio.charset.Charset"));
+ }
+
+ if (JVM.is15()) {
+ aliasDynamically("duration", "javax.xml.datatype.Duration");
+ alias("concurrent-hash-map", JVM.loadClassForName("java.util.concurrent.ConcurrentHashMap"));
+ alias("enum-set", JVM.loadClassForName("java.util.EnumSet"));
+ alias("enum-map", JVM.loadClassForName("java.util.EnumMap"));
+ alias("string-builder", JVM.loadClassForName("java.lang.StringBuilder"));
+ alias("uuid", JVM.loadClassForName("java.util.UUID"));
+ }
+ if (JVM.loadClassForName("java.lang.invoke.SerializedLambda") != null) {
+ aliasDynamically("serialized-lambda", "java.lang.invoke.SerializedLambda");
+ }
+ }
+
+ private void aliasDynamically(String alias, String className) {
+ Class type = JVM.loadClassForName(className);
+ if (type != null) {
+ alias(alias, type);
+ }
+ }
+
+ protected void setupDefaultImplementations() {
+ if (defaultImplementationsMapper == null) {
+ return;
+ }
+ addDefaultImplementation(HashMap.class, Map.class);
+ addDefaultImplementation(ArrayList.class, List.class);
+ addDefaultImplementation(HashSet.class, Set.class);
+ addDefaultImplementation(TreeSet.class, SortedSet.class);
+ addDefaultImplementation(GregorianCalendar.class, Calendar.class);
+ }
+
+ protected void setupConverters() {
+ registerConverter(
+ new ReflectionConverter(mapper, reflectionProvider), PRIORITY_VERY_LOW);
+
+ registerConverter(
+ new SerializableConverter(mapper, reflectionProvider, classLoaderReference), PRIORITY_LOW);
+ registerConverter(new ExternalizableConverter(mapper, classLoaderReference), 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 URIConverter(), 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 SingletonCollectionConverter(mapper), PRIORITY_NORMAL);
+ registerConverter(new SingletonMapConverter(mapper), PRIORITY_NORMAL);
+ registerConverter(new PropertiesConverter(), PRIORITY_NORMAL);
+ registerConverter((Converter)new EncodedByteArrayConverter(), PRIORITY_NORMAL);
+
+ registerConverter(new FileConverter(), PRIORITY_NORMAL);
+ if (JVM.isSQLAvailable()) {
+ 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);
+ registerConverter(new JavaFieldConverter(classLoaderReference), PRIORITY_NORMAL);
+ if (JVM.isAWTAvailable()) {
+ registerConverter(new FontConverter(mapper), PRIORITY_NORMAL);
+ registerConverter(new ColorConverter(), PRIORITY_NORMAL);
+ registerConverter(new TextAttributeConverter(), PRIORITY_NORMAL);
+ }
+ if (JVM.isSwingAvailable()) {
+ 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
+ registerConverterDynamically(
+ "com.thoughtworks.xstream.converters.extended.SubjectConverter",
+ PRIORITY_NORMAL, new Class[]{Mapper.class}, new Object[]{mapper});
+ registerConverterDynamically(
+ "com.thoughtworks.xstream.converters.extended.ThrowableConverter",
+ PRIORITY_NORMAL, new Class[]{ConverterLookup.class},
+ new Object[]{converterLookup});
+ registerConverterDynamically(
+ "com.thoughtworks.xstream.converters.extended.StackTraceElementConverter",
+ PRIORITY_NORMAL, null, null);
+ registerConverterDynamically(
+ "com.thoughtworks.xstream.converters.extended.CurrencyConverter",
+ PRIORITY_NORMAL, null, null);
+ registerConverterDynamically(
+ "com.thoughtworks.xstream.converters.extended.RegexPatternConverter",
+ PRIORITY_NORMAL, null, null);
+ registerConverterDynamically(
+ "com.thoughtworks.xstream.converters.extended.CharsetConverter",
+ PRIORITY_NORMAL, null, null);
+ }
+
+ if (JVM.is15()) {
+ // late bound converters - allows XStream to be compiled on earlier JDKs
+ if (JVM.loadClassForName("javax.xml.datatype.Duration") != null) {
+ registerConverterDynamically(
+ "com.thoughtworks.xstream.converters.extended.DurationConverter",
+ PRIORITY_NORMAL, null, null);
+ }
+ registerConverterDynamically(
+ "com.thoughtworks.xstream.converters.enums.EnumConverter", PRIORITY_NORMAL,
+ null, null);
+ registerConverterDynamically(
+ "com.thoughtworks.xstream.converters.enums.EnumSetConverter", PRIORITY_NORMAL,
+ new Class[]{Mapper.class}, new Object[]{mapper});
+ registerConverterDynamically(
+ "com.thoughtworks.xstream.converters.enums.EnumMapConverter", PRIORITY_NORMAL,
+ new Class[]{Mapper.class}, new Object[]{mapper});
+ registerConverterDynamically(
+ "com.thoughtworks.xstream.converters.basic.StringBuilderConverter",
+ PRIORITY_NORMAL, null, null);
+ registerConverterDynamically(
+ "com.thoughtworks.xstream.converters.basic.UUIDConverter", PRIORITY_NORMAL,
+ null, null);
+ }
+ if (JVM.is18()) {
+ registerConverterDynamically("com.thoughtworks.xstream.converters.reflection.LambdaConverter",
+ PRIORITY_NORMAL, new Class[]{Mapper.class, ReflectionProvider.class, ClassLoaderReference.class},
+ new Object[]{mapper, reflectionProvider, classLoaderReference});
+ }
+
+ registerConverter(
+ new SelfStreamingInstanceChecker(converterLookup, this), PRIORITY_NORMAL);
+ }
+
+ private void registerConverterDynamically(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 com.thoughtworks.xstream.InitializationException(
+ "Could not instantiate converter : " + className, e);
+ } catch (LinkageError e) {
+ throw new com.thoughtworks.xstream.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(URI.class);
+ addImmutableType(URL.class);
+ addImmutableType(File.class);
+ addImmutableType(Class.class);
+
+ addImmutableType(Collections.EMPTY_LIST.getClass());
+ addImmutableType(Collections.EMPTY_SET.getClass());
+ addImmutableType(Collections.EMPTY_MAP.getClass());
+
+ if (JVM.isAWTAvailable()) {
+ addImmutableTypeDynamically("java.awt.font.TextAttribute");
+ }
+
+ if (JVM.is14()) {
+ // late bound types - allows XStream to be compiled on earlier JDKs
+ addImmutableTypeDynamically("java.nio.charset.Charset");
+ addImmutableTypeDynamically("java.util.Currency");
+ }
+ }
+
+ private void addImmutableTypeDynamically(String className) {
+ Class type = JVM.loadClassForName(className);
+ if (type != null) {
+ 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 reader) {
+ return unmarshal(hierarchicalStreamDriver.createReader(reader), 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 a URL.
+ *
+ * Depending on the parser implementation, some might take the file path as SystemId to
+ * resolve additional references.
+ *
+ * @throws XStreamException if the object cannot be deserialized
+ * @since 1.4
+ */
+ public Object fromXML(URL url) {
+ return fromXML(url, null);
+ }
+
+ /**
+ * Deserialize an object from a file.
+ *
+ * Depending on the parser implementation, some might take the file path as SystemId to
+ * resolve additional references.
+ *
+ * @throws XStreamException if the object cannot be deserialized
+ * @since 1.4
+ */
+ public Object fromXML(File file) {
+ return fromXML(file, 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 a URL, 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!
+ *
+ * Depending on the parser implementation, some might take the file path as SystemId to
+ * resolve additional references.
+ *
+ * @throws XStreamException if the object cannot be deserialized
+ * @since 1.4
+ */
+ public Object fromXML(URL url, Object root) {
+ return unmarshal(hierarchicalStreamDriver.createReader(url), root);
+ }
+
+ /**
+ * Deserialize an object from a file, 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!
+ *
+ * Depending on the parser implementation, some might take the file path as SystemId to
+ * resolve additional references.
+ *
+ * @throws XStreamException if the object cannot be deserialized
+ * @since 1.4
+ */
+ public Object fromXML(File file, Object root) {
+ HierarchicalStreamReader reader = hierarchicalStreamDriver.createReader(file);
+ try {
+ return unmarshal(reader, root);
+ } finally {
+ reader.close();
+ }
+ }
+
+ /**
+ * 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 input, Object root) {
+ return unmarshal(hierarchicalStreamDriver.createReader(input), 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) {
+ try {
+ return marshallingStrategy.unmarshal(
+ root, reader, dataHolder, converterLookup, mapper);
+
+ } catch (ConversionException e) {
+ Package pkg = getClass().getPackage();
+ String version = pkg != null ? pkg.getImplementationVersion() : null;
+ e.add("version", version != null ? version : "not available");
+ throw e;
+ }
+ }
+
+ /**
+ * 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 com.thoughtworks.xstream.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 com.thoughtworks.xstream.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 com.thoughtworks.xstream.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 com.thoughtworks.xstream.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 com.thoughtworks.xstream.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 null. 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 null)
+ * @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 com.thoughtworks.xstream.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 com.thoughtworks.xstream.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 com.thoughtworks.xstream.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 com.thoughtworks.xstream.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 com.thoughtworks.xstream.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 com.thoughtworks.xstream.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 com.thoughtworks.xstream.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));
+ }
+
+ /**
+ * 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
+ * XPATH_ABSOLUTE_REFERENCES, XPATH_RELATIVE_REFERENCES,
+ * XStream.ID_REFERENCES and XStream.NO_REFERENCES.
+ *
+ * @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;
+ case SINGLE_NODE_XPATH_RELATIVE_REFERENCES:
+ setMarshallingStrategy(new ReferenceByXPathMarshallingStrategy(
+ ReferenceByXPathMarshallingStrategy.RELATIVE
+ | ReferenceByXPathMarshallingStrategy.SINGLE_NODE));
+ break;
+ case SINGLE_NODE_XPATH_ABSOLUTE_REFERENCES:
+ setMarshallingStrategy(new ReferenceByXPathMarshallingStrategy(
+ ReferenceByXPathMarshallingStrategy.ABSOLUTE
+ | ReferenceByXPathMarshallingStrategy.SINGLE_NODE));
+ 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) {
+ addImplicitCollection(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) {
+ addImplicitCollection(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) {
+ addImplicitMap(ownerType, fieldName, itemFieldName, itemType, null);
+ }
+
+ /**
+ * Adds an implicit array.
+ *
+ * @param ownerType class owning the implicit array
+ * @param fieldName name of the array field
+ * @since 1.4
+ */
+ public void addImplicitArray(Class ownerType, String fieldName) {
+ addImplicitCollection(ownerType, fieldName);
+ }
+
+ /**
+ * Adds an implicit array which is used for all items of the given itemType when the array
+ * type matches.
+ *
+ * @param ownerType class owning the implicit array
+ * @param fieldName name of the array field in the ownerType
+ * @param itemType type of the items to be part of this array
+ * @throws InitializationException if no {@link ImplicitCollectionMapper} is available or the
+ * array type does not match the itemType
+ * @since 1.4
+ */
+ public void addImplicitArray(Class ownerType, String fieldName, Class itemType) {
+ addImplicitCollection(ownerType, fieldName, itemType);
+ }
+
+ /**
+ * Adds an implicit array which is used for all items of the given element name defined by
+ * itemName.
+ *
+ * @param ownerType class owning the implicit array
+ * @param fieldName name of the array field in the ownerType
+ * @param itemName alias name of the items
+ * @throws InitializationException if no {@link ImplicitCollectionMapper} is available
+ * @since 1.4
+ */
+ public void addImplicitArray(Class ownerType, String fieldName, String itemName) {
+ addImplicitCollection(ownerType, fieldName, itemName, null);
+ }
+
+ /**
+ * Adds an implicit map.
+ *
+ * @param ownerType class owning the implicit map
+ * @param fieldName name of the field in the ownerType. This field must be a concrete
+ * map type or matching the default implementation type of the map
+ * type.
+ * @param itemType type of the items to be part of this map as value
+ * @param keyFieldName the name of the field of the itemType that is used for the key in the map
+ * @since 1.4
+ */
+ public void addImplicitMap(Class ownerType, String fieldName, Class itemType, String keyFieldName) {
+ addImplicitMap(ownerType, fieldName, null, itemType, keyFieldName);
+ }
+
+ /**
+ * Adds an implicit map.
+ *
+ * @param ownerType class owning the implicit map
+ * @param fieldName name of the field in the ownerType. This field must be a concrete
+ * map type or matching the default implementation type of the map
+ * type.
+ * @param itemName alias name of the items
+ * @param itemType type of the items to be part of this map as value
+ * @param keyFieldName the name of the field of the itemType that is used for the key in the map
+ * @since 1.4
+ */
+ public void addImplicitMap(Class ownerType, String fieldName, String itemName,
+ Class itemType, String keyFieldName) {
+ if (implicitCollectionMapper == null) {
+ throw new com.thoughtworks.xstream.InitializationException("No "
+ + ImplicitCollectionMapper.class.getName()
+ + " available");
+ }
+ implicitCollectionMapper.add(ownerType, fieldName, itemName, itemType, keyFieldName);
+ }
+
+ /**
+ * 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.
+ *
+ * To change the name of the root element (from <object-stream>), use
+ * {@link #createObjectOutputStream(java.io.Writer, String)}.
+ *
+ *
+ * @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.
+ *
+ * To change the name of the root element (from <object-stream>), use
+ * {@link #createObjectOutputStream(java.io.Writer, String)}.
+ *
+ *
+ * @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.
+ *
+ * To change the name of the root element (from <object-stream>), use
+ * {@link #createObjectOutputStream(java.io.Writer, String)}.
+ *
+ *
+ * @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.
+ *
+ * Because an ObjectOutputStream can contain multiple items and XML only allows a single
+ * root node, the stream must be written inside an enclosing node.
+ *
+ *
+ * It is necessary to call ObjectOutputStream.close() when done, otherwise the stream will
+ * be incomplete.
+ *
+ *
+ * @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.
Example
+ *
+ *
+ * ObjectInputStream in = xstream.createObjectOutputStream(aReader);
+ * int a = out.readInt();
+ * Object b = out.readObject();
+ * Object c = out.readObject();
+ *
+ *
+ * @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();
+ }
+ }, classLoaderReference);
+ }
+
+ /**
+ * 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 class loader, you should call
+ * this method as early as possible - or consider to provide the class loader 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();
+ }
+
+ /**
+ * Retrieve the reference to this instance' ClassLoader. Use this reference for other
+ * XStream components (like converters) to ensure that they will use a changed ClassLoader
+ * instance automatically.
+ *
+ * @return the reference
+ * @since 1.4.5
+ */
+ public ClassLoaderReference getClassLoaderReference() {
+ return classLoaderReference;
+ }
+
+ /**
+ * 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 com.thoughtworks.xstream.InitializationException("No "
+ + FieldAliasingMapper.class.getName()
+ + " available");
+ }
+ fieldAliasingMapper.omitField(definedIn, fieldName);
+ }
+
+ /**
+ * Ignore all unknown elements.
+ *
+ * @since 1.4.5
+ */
+ public void ignoreUnknownElements() {
+ ignoreUnknownElements(IGNORE_ALL);
+ }
+
+ /**
+ * Add pattern for unknown element names to ignore.
+ *
+ * @param pattern the name pattern as regular expression
+ * @since 1.4.5
+ */
+ public void ignoreUnknownElements(String pattern) {
+ ignoreUnknownElements(Pattern.compile(pattern));
+ }
+
+ /**
+ * Add pattern for unknown element names to ignore.
+ *
+ * @param pattern the name pattern as regular expression
+ * @since 1.4.5
+ */
+ public void ignoreUnknownElements(final Pattern pattern) {
+ if (fieldAliasingMapper == null) {
+ throw new com.thoughtworks.xstream.InitializationException("No "
+ + FieldAliasingMapper.class.getName()
+ + " available");
+ }
+ fieldAliasingMapper.addFieldsToIgnore(pattern);
+ }
+
+ /**
+ * 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 com.thoughtworks.xstream.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 true if annotations are auto-detected
+ * @since 1.3
+ */
+ public void autodetectAnnotations(boolean mode) {
+ if (annotationConfiguration != null) {
+ annotationConfiguration.autodetectAnnotations(mode);
+ }
+ }
+
+ /**
+ * Add a new security permission.
+ *
+ *
+ * Permissions are evaluated in the added sequence. An instance of {@link NoTypePermission} or
+ * {@link AnyTypePermission} will implicitly wipe any existing permission.
+ *
+ *
+ * @param permission the permission to add
+ * @since 1.4.7
+ */
+ public void addPermission(TypePermission permission) {
+ if (securityMapper != null) {
+ securityMapper.addPermission(permission);
+ }
+ }
+
+ /**
+ * Add security permission for explicit types by name.
+ *
+ * @param names the type names to allow
+ * @since 1.4.7
+ */
+ public void allowTypes(String[] names) {
+ addPermission(new ExplicitTypePermission(names));
+ }
+
+ /**
+ * Add security permission for explicit types.
+ *
+ * @param types the types to allow
+ * @since 1.4.7
+ */
+ public void allowTypes(Class[] types) {
+ addPermission(new ExplicitTypePermission(types));
+ }
+
+ /**
+ * Add security permission for a type hierarchy.
+ *
+ * @param type the base type to allow
+ * @since 1.4.7
+ */
+ public void allowTypeHierarchy(Class type) {
+ addPermission(new TypeHierarchyPermission(type));
+ }
+
+ /**
+ * Add security permission for types matching one of the specified regular expressions.
+ *
+ * @param regexps the regular expressions to allow type names
+ * @since 1.4.7
+ */
+ public void allowTypesByRegExp(String[] regexps) {
+ addPermission(new RegExpTypePermission(regexps));
+ }
+
+ /**
+ * Add security permission for types matching one of the specified regular expressions.
+ *
+ * @param regexps the regular expressions to allow type names
+ * @since 1.4.7
+ */
+ public void allowTypesByRegExp(Pattern[] regexps) {
+ addPermission(new RegExpTypePermission(regexps));
+ }
+
+ /**
+ * Add security permission for types matching one of the specified wildcard patterns.
+ *
+ * Supported are patterns with path expressions using dot as separator:
+ *
+ *
+ *
?: one non-control character except separator, e.g. for 'java.net.Inet?Address'
+ *
*: arbitrary number of non-control characters except separator, e.g. for types in a package like 'java.lang.*'
+ *
**: arbitrary number of non-control characters including separator, e.g. for types in a package and subpackages like 'java.lang.**'
+ *
+ *
+ * @param patterns the patterns to allow type names
+ * @since 1.4.7
+ */
+ public void allowTypesByWildcard(String[] patterns) {
+ addPermission(new WildcardTypePermission(patterns));
+ }
+
+ /**
+ * Add security permission denying another one.
+ *
+ * @param permission the permission to deny
+ * @since 1.4.7
+ */
+ public void denyPermission(TypePermission permission) {
+ addPermission(new NoPermission(permission));
+ }
+
+ /**
+ * Add security permission forbidding explicit types by name.
+ *
+ * @param names the type names to forbid
+ * @since 1.4.7
+ */
+ public void denyTypes(String[] names) {
+ denyPermission(new ExplicitTypePermission(names));
+ }
+
+ /**
+ * Add security permission forbidding explicit types.
+ *
+ * @param types the types to forbid
+ * @since 1.4.7
+ */
+ public void denyTypes(Class[] types) {
+ denyPermission(new ExplicitTypePermission(types));
+ }
+
+ /**
+ * Add security permission forbidding a type hierarchy.
+ *
+ * @param type the base type to forbid
+ * @since 1.4.7
+ */
+ public void denyTypeHierarchy(Class type) {
+ denyPermission(new TypeHierarchyPermission(type));
+ }
+
+ /**
+ * Add security permission forbidding types matching one of the specified regular expressions.
+ *
+ * @param regexps the regular expressions to forbid type names
+ * @since 1.4.7
+ */
+ public void denyTypesByRegExp(String[] regexps) {
+ denyPermission(new RegExpTypePermission(regexps));
+ }
+
+ /**
+ * Add security permission forbidding types matching one of the specified regular expressions.
+ *
+ * @param regexps the regular expressions to forbid type names
+ * @since 1.4.7
+ */
+ public void denyTypesByRegExp(Pattern[] regexps) {
+ denyPermission(new RegExpTypePermission(regexps));
+ }
+
+ /**
+ * Add security permission forbidding types matching one of the specified wildcard patterns.
+ *
+ * Supported are patterns with path expressions using dot as separator:
+ *
+ *
+ *
?: one non-control character except separator, e.g. for 'java.net.Inet?Address'
+ *
*: arbitrary number of non-control characters except separator, e.g. for types in a package like 'java.lang.*'
+ *
**: arbitrary number of non-control characters including separator, e.g. for types in a package and subpackages like 'java.lang.**'
+ *
+ *
+ * @param patterns the patterns to forbid names
+ * @since 1.4.7
+ */
+ public void denyTypesByWildcard(String[] patterns) {
+ denyPermission(new WildcardTypePermission(patterns));
+ }
+
+ /**
+ * @deprecated As of 1.3, use {@link com.thoughtworks.xstream.InitializationException}
+ * instead
+ */
+ public static class InitializationException extends XStreamException {
+ /**
+ * @deprecated As of 1.3, use
+ * {@link com.thoughtworks.xstream.InitializationException#InitializationException(String, Throwable)}
+ * instead
+ */
+ public InitializationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * @deprecated As of 1.3, use
+ * {@link com.thoughtworks.xstream.InitializationException#InitializationException(String)}
+ * instead
+ */
+ public InitializationException(String message) {
+ super(message);
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/XStreamException.java b/xstream/src/java/com/thoughtworks/xstream/XStreamException.java
new file mode 100644
index 0000000..002a605
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/XStreamException.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 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 22. October 2007 by Joerg Schaible
+ */
+package com.thoughtworks.xstream;
+
+import com.thoughtworks.xstream.core.BaseException;
+
+
+/**
+ * Base exception for all thrown exceptions with XStream. JDK 1.3 friendly cause handling.
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ * @since 1.3
+ */
+public class XStreamException extends BaseException {
+
+ private Throwable cause;
+
+ /**
+ * Default constructor.
+ *
+ * @since 1.3
+ */
+ protected XStreamException() {
+ this("", null);
+ }
+
+ /**
+ * Constructs an XStreamException with a message.
+ *
+ * @param message
+ * @since 1.3
+ */
+ public XStreamException(String message) {
+ this(message, null);
+ }
+
+ /**
+ * Constructs an XStreamException as wrapper for a different causing {@link Throwable}.
+ *
+ * @param cause
+ * @since 1.3
+ */
+ public XStreamException(Throwable cause) {
+ this("", cause);
+ }
+
+ /**
+ * Constructs an XStreamException with a message as wrapper for a different causing
+ * {@link Throwable}.
+ *
+ * @param message
+ * @param cause
+ * @since 1.3
+ */
+ public XStreamException(String message, Throwable cause) {
+ super(message + (cause == null ? "" : " : " + cause.getMessage()));
+ this.cause = cause;
+ }
+
+ public Throwable getCause() {
+ return cause;
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/XStreamer.java b/xstream/src/java/com/thoughtworks/xstream/XStreamer.java
new file mode 100644
index 0000000..6d40890
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/XStreamer.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2006, 2007, 2014 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 13. April 2006 by Joerg Schaible
+ */
+package com.thoughtworks.xstream;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+
+import javax.xml.datatype.DatatypeFactory;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.ConverterLookup;
+import com.thoughtworks.xstream.converters.ConverterMatcher;
+import com.thoughtworks.xstream.converters.ConverterRegistry;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.converters.javabean.JavaBeanProvider;
+import com.thoughtworks.xstream.converters.reflection.FieldKeySorter;
+import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
+import com.thoughtworks.xstream.core.JVM;
+import com.thoughtworks.xstream.io.HierarchicalStreamDriver;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+import com.thoughtworks.xstream.io.xml.XppDriver;
+import com.thoughtworks.xstream.mapper.Mapper;
+import com.thoughtworks.xstream.security.AnyTypePermission;
+import com.thoughtworks.xstream.security.TypeHierarchyPermission;
+import com.thoughtworks.xstream.security.TypePermission;
+import com.thoughtworks.xstream.security.WildcardTypePermission;
+
+/**
+ * Self-contained XStream generator. The class is a utility to write XML streams that contain
+ * additionally the XStream that was used to serialize the object graph. Such a stream can
+ * be unmarshalled using this embedded XStream instance, that kept any settings.
+ *
+ * @author Jörg Schaible
+ * @since 1.2
+ */
+public class XStreamer {
+
+ private final static TypePermission[] PERMISSIONS = {
+ new TypeHierarchyPermission(ConverterMatcher.class),
+ new TypeHierarchyPermission(Mapper.class),
+ new TypeHierarchyPermission(XStream.class),
+ new TypeHierarchyPermission(ReflectionProvider.class),
+ new TypeHierarchyPermission(JavaBeanProvider.class),
+ new TypeHierarchyPermission(FieldKeySorter.class),
+ new TypeHierarchyPermission(ConverterLookup.class),
+ new TypeHierarchyPermission(ConverterRegistry.class),
+ new TypeHierarchyPermission(HierarchicalStreamDriver.class),
+ new TypeHierarchyPermission(MarshallingStrategy.class),
+ new TypeHierarchyPermission(MarshallingContext.class),
+ new TypeHierarchyPermission(UnmarshallingContext.class),
+ new TypeHierarchyPermission(NameCoder.class),
+ new TypeHierarchyPermission(TypePermission.class),
+ new WildcardTypePermission(new String[]{JVM.class.getPackage().getName()+".**"}),
+ new TypeHierarchyPermission(DatatypeFactory.class) // required by DurationConverter
+ };
+
+ /**
+ * Serialize an object including the XStream to a pretty-printed XML String.
+ *
+ * @throws ObjectStreamException if the XML contains non-serializable elements
+ * @throws com.thoughtworks.xstream.XStreamException if the object cannot be serialized
+ * @since 1.2
+ * @see #toXML(XStream, Object, Writer)
+ */
+ public String toXML(final XStream xstream, final Object obj) throws ObjectStreamException {
+ final Writer writer = new StringWriter();
+ try {
+ toXML(xstream, obj, writer);
+ } catch (final ObjectStreamException e) {
+ throw e;
+ } catch (final IOException e) {
+ throw new ConversionException("Unexpected IO error from a StringWriter", e);
+ }
+ return writer.toString();
+ }
+
+ /**
+ * Serialize an object including the XStream to the given Writer as pretty-printed XML.
+ *
+ * Warning: XStream will serialize itself into this XML stream. To read such an XML code, you
+ * should use {@link XStreamer#fromXML(Reader)} or one of the other overloaded
+ * methods. Since a lot of internals are written into the stream, you cannot expect to use such
+ * an XML to work with another XStream version or with XStream running on different JDKs and/or
+ * versions. We have currently no JDK 1.3 support, nor will the PureReflectionConverter work
+ * with a JDK less than 1.5.
+ *
+ *
+ * @throws IOException if an error occurs reading from the Writer.
+ * @throws com.thoughtworks.xstream.XStreamException if the object cannot be serialized
+ * @since 1.2
+ */
+ public void toXML(final XStream xstream, final Object obj, final Writer out)
+ throws IOException {
+ final XStream outer = new XStream();
+ final ObjectOutputStream oos = outer.createObjectOutputStream(out);
+ try {
+ oos.writeObject(xstream);
+ oos.flush();
+ xstream.toXML(obj, out);
+ } finally {
+ oos.close();
+ }
+ }
+
+ /**
+ * Deserialize a self-contained XStream with object from a String. The method will use
+ * internally an XppDriver to load the contained XStream instance with default permissions.
+ *
+ * @param xml the XML data
+ * @throws ClassNotFoundException if a class in the XML stream cannot be found
+ * @throws ObjectStreamException if the XML contains non-deserializable elements
+ * @throws com.thoughtworks.xstream.XStreamException if the object cannot be deserialized
+ * @since 1.2
+ * @see #toXML(XStream, Object, Writer)
+ */
+ public Object fromXML(final String xml) throws ClassNotFoundException, ObjectStreamException {
+ try {
+ return fromXML(new StringReader(xml));
+ } catch (final ObjectStreamException e) {
+ throw e;
+ } catch (final IOException e) {
+ throw new ConversionException("Unexpected IO error from a StringReader", e);
+ }
+ }
+
+ /**
+ * Deserialize a self-contained XStream with object from a String. The method will use
+ * internally an XppDriver to load the contained XStream instance.
+ *
+ * @param xml the XML data
+ * @param permissions the permissions to use (ensure that they include the defaults)
+ * @throws ClassNotFoundException if a class in the XML stream cannot be found
+ * @throws ObjectStreamException if the XML contains non-deserializable elements
+ * @throws com.thoughtworks.xstream.XStreamException if the object cannot be deserialized
+ * @since 1.4.7
+ * @see #toXML(XStream, Object, Writer)
+ */
+ public Object fromXML(final String xml, final TypePermission[] permissions) throws ClassNotFoundException, ObjectStreamException {
+ try {
+ return fromXML(new StringReader(xml), permissions);
+ } catch (final ObjectStreamException e) {
+ throw e;
+ } catch (final IOException e) {
+ throw new ConversionException("Unexpected IO error from a StringReader", e);
+ }
+ }
+
+ /**
+ * Deserialize a self-contained XStream with object from a String.
+ *
+ * @param driver the implementation to use
+ * @param xml the XML data
+ * @throws ClassNotFoundException if a class in the XML stream cannot be found
+ * @throws ObjectStreamException if the XML contains non-deserializable elements
+ * @throws com.thoughtworks.xstream.XStreamException if the object cannot be deserialized
+ * @since 1.2
+ * @see #toXML(XStream, Object, Writer)
+ */
+ public Object fromXML(final HierarchicalStreamDriver driver, final String xml)
+ throws ClassNotFoundException, ObjectStreamException {
+ try {
+ return fromXML(driver, new StringReader(xml));
+ } catch (final ObjectStreamException e) {
+ throw e;
+ } catch (final IOException e) {
+ throw new ConversionException("Unexpected IO error from a StringReader", e);
+ }
+ }
+
+ /**
+ * Deserialize a self-contained XStream with object from a String.
+ *
+ * @param driver the implementation to use
+ * @param xml the XML data
+ * @param permissions the permissions to use (ensure that they include the defaults)
+ * @throws ClassNotFoundException if a class in the XML stream cannot be found
+ * @throws ObjectStreamException if the XML contains non-deserializable elements
+ * @throws com.thoughtworks.xstream.XStreamException if the object cannot be deserialized
+ * @since 1.4.7
+ * @see #toXML(XStream, Object, Writer)
+ */
+ public Object fromXML(final HierarchicalStreamDriver driver, final String xml, final TypePermission[] permissions)
+ throws ClassNotFoundException, ObjectStreamException {
+ try {
+ return fromXML(driver, new StringReader(xml), permissions);
+ } catch (final ObjectStreamException e) {
+ throw e;
+ } catch (final IOException e) {
+ throw new ConversionException("Unexpected IO error from a StringReader", e);
+ }
+ }
+
+ /**
+ * Deserialize a self-contained XStream with object from an XML Reader. The method will use
+ * internally an XppDriver to load the contained XStream instance with default permissions.
+ *
+ * @param xml the {@link Reader} providing the XML data
+ * @throws IOException if an error occurs reading from the Reader.
+ * @throws ClassNotFoundException if a class in the XML stream cannot be found
+ * @throws com.thoughtworks.xstream.XStreamException if the object cannot be deserialized
+ * @since 1.2
+ * @see #toXML(XStream, Object, Writer)
+ */
+ public Object fromXML(final Reader xml)
+ throws IOException, ClassNotFoundException {
+ return fromXML(new XppDriver(), xml);
+ }
+
+ /**
+ * Deserialize a self-contained XStream with object from an XML Reader. The method will use
+ * internally an XppDriver to load the contained XStream instance.
+ *
+ * @param xml the {@link Reader} providing the XML data
+ * @param permissions the permissions to use (ensure that they include the defaults)
+ * @throws IOException if an error occurs reading from the Reader.
+ * @throws ClassNotFoundException if a class in the XML stream cannot be found
+ * @throws com.thoughtworks.xstream.XStreamException if the object cannot be deserialized
+ * @since 1.4.7
+ * @see #toXML(XStream, Object, Writer)
+ */
+ public Object fromXML(final Reader xml, final TypePermission[] permissions)
+ throws IOException, ClassNotFoundException {
+ return fromXML(new XppDriver(), xml, permissions);
+ }
+
+ /**
+ * Deserialize a self-contained XStream with object from an XML Reader.
+ *
+ * @param driver the implementation to use
+ * @param xml the {@link Reader} providing the XML data
+ * @throws IOException if an error occurs reading from the Reader.
+ * @throws ClassNotFoundException if a class in the XML stream cannot be found
+ * @throws com.thoughtworks.xstream.XStreamException if the object cannot be deserialized
+ * @since 1.2
+ */
+ public Object fromXML(final HierarchicalStreamDriver driver, final Reader xml)
+ throws IOException, ClassNotFoundException {
+ return fromXML(driver, xml, new TypePermission[]{AnyTypePermission.ANY});
+ }
+
+ /**
+ * Deserialize a self-contained XStream with object from an XML Reader.
+ *
+ * @param driver the implementation to use
+ * @param xml the {@link Reader} providing the XML data
+ * @param permissions the permissions to use (ensure that they include the defaults)
+ * @throws IOException if an error occurs reading from the Reader.
+ * @throws ClassNotFoundException if a class in the XML stream cannot be found
+ * @throws com.thoughtworks.xstream.XStreamException if the object cannot be deserialized
+ * @since 1.4.7
+ */
+ public Object fromXML(final HierarchicalStreamDriver driver, final Reader xml, final TypePermission[] permissions)
+ throws IOException, ClassNotFoundException {
+ final XStream outer = new XStream(driver);
+ for(int i = 0; i < permissions.length; ++i) {
+ outer.addPermission(permissions[i]);
+ }
+ final HierarchicalStreamReader reader = driver.createReader(xml);
+ final ObjectInputStream configIn = outer.createObjectInputStream(reader);
+ try {
+ final XStream configured = (XStream)configIn.readObject();
+ final ObjectInputStream in = configured.createObjectInputStream(reader);
+ try {
+ return in.readObject();
+ } finally {
+ in.close();
+ }
+ } finally {
+ configIn.close();
+ }
+ }
+
+ /**
+ * Retrieve the default permissions to unmarshal an XStream instance.
+ *
+ * The returned list will only cover permissions for XStream's own types. If your custom converters or mappers keep
+ * references to other types, you will have to add permission for those types on your own.
+ *
+ *
+ * @since 1.4.7
+ */
+ public static TypePermission[] getDefaultPermissions() {
+ return (TypePermission[])PERMISSIONS.clone();
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/annotations/AnnotationProvider.java b/xstream/src/java/com/thoughtworks/xstream/annotations/AnnotationProvider.java
new file mode 100644
index 0000000..0da315b
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/annotations/AnnotationProvider.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2006, 2007, 2008, 2009 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 02. March 2006 by Mauro Talevi
+ */
+package com.thoughtworks.xstream.annotations;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+
+
+/**
+ * An utility class to provide annotations from different sources
+ *
+ * @author Guilherme Silveira
+ * @deprecated As of 1.3
+ */
+@Deprecated
+public class AnnotationProvider {
+
+ /**
+ * Returns a field annotation based on an annotation type
+ *
+ * @param field the annotation Field
+ * @param annotationClass the annotation Class
+ * @return The Annotation type
+ * @deprecated As of 1.3
+ */
+ @Deprecated
+ public T getAnnotation(Field field, Class annotationClass) {
+ return field.getAnnotation(annotationClass);
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/annotations/AnnotationReflectionConverter.java b/xstream/src/java/com/thoughtworks/xstream/annotations/AnnotationReflectionConverter.java
new file mode 100644
index 0000000..88cd5a9
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/annotations/AnnotationReflectionConverter.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2006, 2007, 2008, 2009 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 02. March 2006 by Mauro Talevi
+ */
+package com.thoughtworks.xstream.annotations;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.ConverterMatcher;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.SingleValueConverter;
+import com.thoughtworks.xstream.converters.SingleValueConverterWrapper;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.converters.reflection.ObjectAccessException;
+import com.thoughtworks.xstream.converters.reflection.ReflectionConverter;
+import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+
+/**
+ * ReflectionConverter which uses an AnnotationProvider to marshall and unmarshall fields based
+ * on the annotated converters.
+ *
+ * @author Guilherme Silveira
+ * @author Mauro Talevi
+ * @deprecated As of 1.3, build into {@link ReflectionConverter}
+ */
+@Deprecated
+public class AnnotationReflectionConverter extends ReflectionConverter {
+
+ private final AnnotationProvider annotationProvider;
+
+ private final Map, Converter> cachedConverters;
+
+ @Deprecated
+ public AnnotationReflectionConverter(
+ Mapper mapper, ReflectionProvider reflectionProvider,
+ AnnotationProvider annotationProvider) {
+ super(mapper, reflectionProvider);
+ this.annotationProvider = annotationProvider;
+ this.cachedConverters = new HashMap, Converter>();
+ }
+
+ protected void marshallField(final MarshallingContext context, Object newObj, Field field) {
+ XStreamConverter annotation = annotationProvider.getAnnotation(
+ field, XStreamConverter.class);
+ if (annotation != null) {
+ Class extends ConverterMatcher> type = annotation.value();
+ ensureCache(type);
+ context.convertAnother(newObj, cachedConverters.get(type));
+ } else {
+ context.convertAnother(newObj);
+ }
+ }
+
+ private void ensureCache(Class extends ConverterMatcher> type) {
+ if (!this.cachedConverters.containsKey(type)) {
+ cachedConverters.put(type, newInstance(type));
+ }
+ }
+
+ protected Object unmarshallField(
+ final UnmarshallingContext context, final Object result,
+ Class type, Field field) {
+ XStreamConverter annotation = annotationProvider.getAnnotation(
+ field, XStreamConverter.class);
+ if (annotation != null) {
+ Class extends Converter> converterType = (Class extends Converter>)annotation.value();
+ ensureCache(converterType);
+ return context.convertAnother(result, type, cachedConverters.get(converterType));
+ } else {
+ return context.convertAnother(result, type);
+ }
+ }
+
+ /**
+ * Instantiates a converter using its default constructor.
+ *
+ * @param type the converter type to instantiate
+ * @return the new instance
+ */
+ private Converter newInstance(Class extends ConverterMatcher> type) {
+ Converter converter;
+ // TODO: We need a separate exception for runtime initialization.
+ try {
+ if (SingleValueConverter.class.isAssignableFrom(type)) {
+ final SingleValueConverter svc = (SingleValueConverter)type.getConstructor().newInstance();
+ converter = new SingleValueConverterWrapper(svc);
+ } else {
+ converter = (Converter)type.getConstructor().newInstance();
+ }
+ } catch (InvocationTargetException e) {
+ throw new ObjectAccessException("Cannot construct " + type.getName(), e
+ .getCause());
+ } catch (InstantiationException e) {
+ throw new ObjectAccessException("Cannot construct " + type.getName(), e);
+ } catch (IllegalAccessException e) {
+ throw new ObjectAccessException("Cannot construct " + type.getName(), e);
+ } catch (NoSuchMethodException e) {
+ throw new ObjectAccessException("Cannot construct " + type.getName(), e);
+ }
+ return converter;
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/annotations/Annotations.java b/xstream/src/java/com/thoughtworks/xstream/annotations/Annotations.java
new file mode 100644
index 0000000..1e48132
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/annotations/Annotations.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009 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 11. August 2005 by Mauro Talevi
+ */
+package com.thoughtworks.xstream.annotations;
+
+import com.thoughtworks.xstream.XStream;
+
+
+/**
+ * Contains utility methods that enable to configure an XStream instance with class and field
+ * aliases, based on a class decorated with annotations defined in this package.
+ *
+ * @author Emil Kirschner
+ * @author Chung-Onn Cheong
+ * @author Guilherme Silveira
+ * @author Jörg Schaible
+ * @deprecated As of 1.3, use {@link XStream#processAnnotations(Class[])}
+ */
+@Deprecated
+public class Annotations {
+ /**
+ * This class is not instantiable
+ */
+ private Annotations() {
+ }
+
+ /**
+ * Configures aliases on the specified XStream object based on annotations that decorate the
+ * specified class. It will recursively invoke itself. If a field is parameterized, a
+ * recursive call for each of its parameters type will be made.
+ *
+ * @param topLevelClasses the class for which the XStream object is configured. This class
+ * is expected to be decorated with annotations defined in this package.
+ * @param xstream the XStream object that will be configured
+ * @deprecated As of 1.3, use {@link XStream#processAnnotations(Class[])}
+ */
+ @Deprecated
+ public static synchronized void configureAliases(XStream xstream,
+ Class> ... topLevelClasses) {
+ xstream.processAnnotations(topLevelClasses);
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/annotations/XStreamAlias.java b/xstream/src/java/com/thoughtworks/xstream/annotations/XStreamAlias.java
new file mode 100644
index 0000000..88d3c4c
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/annotations/XStreamAlias.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2013 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 11. August 2005 by Mauro Talevi
+ */
+package com.thoughtworks.xstream.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation used to define an XStream class or field alias.
+ *
+ * @author Emil Kirschner
+ * @author Chung-Onn Cheong
+ * @see com.thoughtworks.xstream.XStream#alias(String, Class)
+ * @see com.thoughtworks.xstream.XStream#alias(String, Class, Class)
+ * @see com.thoughtworks.xstream.XStream#addDefaultImplementation(Class, Class)
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.FIELD})
+public @interface XStreamAlias {
+ /**
+ * The name of the class or field alias.
+ */
+ public String value();
+ /**
+ * A possible default implementation if the annotated type is an interface.
+ */
+ public Class> impl() default Void.class; //Use Void to denote as Null
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/annotations/XStreamAliasType.java b/xstream/src/java/com/thoughtworks/xstream/annotations/XStreamAliasType.java
new file mode 100644
index 0000000..2019e47
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/annotations/XStreamAliasType.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2013 XStream Committers.
+ * All rights reserved.
+ *
+ * Created on 12.07.2013 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+/**
+ * Annotation used to define an XStream type alias.
+ *
+ * @author Jörg Schaible
+ * @since 1.4.5
+ * @see com.thoughtworks.xstream.XStream#aliasType(String, Class)
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface XStreamAliasType {
+ /**
+ * The name of the type alias.
+ */
+ public String value();
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/annotations/XStreamAsAttribute.java b/xstream/src/java/com/thoughtworks/xstream/annotations/XStreamAsAttribute.java
new file mode 100644
index 0000000..a39e7ab
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/annotations/XStreamAsAttribute.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2006, 2007 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 24. December 2006 by Guilherme Silveira
+ */
+package com.thoughtworks.xstream.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Defines that a field should be serialized as an attribute.
+ *
+ * @author Guilherme Silveira
+ * @since 1.2.2
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+@Documented
+public @interface XStreamAsAttribute {
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/annotations/XStreamContainedType.java b/xstream/src/java/com/thoughtworks/xstream/annotations/XStreamContainedType.java
new file mode 100644
index 0000000..6f2bc9e
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/annotations/XStreamContainedType.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009 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 11. August 2005 by Mauro Talevi
+ */
+package com.thoughtworks.xstream.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation used to notify Annotations.configureAliases that it should recursively invoke itself for
+ * all parameterized types of this field.
+ *
+ * @author Emil Kirschner
+ * @author Chung-Onn Cheong
+ * @deprecated As of 1.3, recursive behaviour is now always used and the annotation is therefore superfluous
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+@Deprecated
+public @interface XStreamContainedType {
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/annotations/XStreamConverter.java b/xstream/src/java/com/thoughtworks/xstream/annotations/XStreamConverter.java
new file mode 100644
index 0000000..c0ef961
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/annotations/XStreamConverter.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009, 2011, 2012, 2013, 2014 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 16. September 2005 by Mauro Talevi
+ */
+package com.thoughtworks.xstream.annotations;
+
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.converters.ConverterMatcher;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+/**
+ * Annotation to declare a converter. The annotation supports additionally the injection of
+ * various constructor arguments provided by XStream:
+ *
+ *
{@link com.thoughtworks.xstream.mapper.Mapper}: The current mapper chain of the XStream
+ * instance.
+ *
{@link com.thoughtworks.xstream.core.ClassLoaderReference}: The reference to the class
+ * loader used by the XStream instance to deserialize the objects.
+ *
{@link com.thoughtworks.xstream.converters.reflection.ReflectionProvider}: The reflection
+ * provider used by the reflection based converters of the current XStream instance.
+ *
{@link com.thoughtworks.xstream.converters.ConverterLookup}: The lookup for converters
+ * handling a special type.
+ *
All elements provided with the individual arrays of this annotation. The provided values
+ * follow the declaration sequence if a constructor requires multiple arguments of the same
+ * type.
+ *
{@link Class}: The type of the element where the annotation is declared. Note, that this
+ * argument is not supported when using
+ * {@link com.thoughtworks.xstream.annotations.XStreamConverters} or {@link #useImplicitType()}
+ * == false.
+ *
{@link com.thoughtworks.xstream.core.JVM}: Utility e.g. to load classes.
+ *
{@link ClassLoader} (deprecated since 1.4.5): The class loader used by the XStream
+ * instance to deserialize the objects. Use ClassLoaderReference as argument
+ *
+ *
+ * The algorithm will try the converter's constructor with the most arguments first.
+ *
+ *
+ * Note, the annotation matches a {@link ConverterMatcher}.
+ * {@link com.thoughtworks.xstream.converters.Converter} as well as
+ * {@link com.thoughtworks.xstream.converters.SingleValueConverter} extend this interface. The
+ * {@link com.thoughtworks.xstream.mapper.AnnotationMapper} can only handle these two
+ * known types.
+ *
+ *
+ * @author Chung-Onn Cheong
+ * @author Jörg Schaible
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.FIELD})
+@Documented
+public @interface XStreamConverter {
+ Class extends ConverterMatcher> value();
+
+ int priority() default XStream.PRIORITY_NORMAL;
+
+ /**
+ * Flag to provide the current type as implicit first Class argument to a converter's
+ * constructor.
+ *
+ * @return true if the current type is provided
+ * @since 1.4.5
+ */
+ boolean useImplicitType() default true;
+
+ /**
+ * Provide class types as arguments for the converter's constructor arguments.
+ *
+ * Note, that XStream itself provides the current class type as first Class argument to a
+ * constructor, if the annotation is added directly to a class type (and not as part of a
+ * parameter declaration of a {@link XStreamConverters} annotation). The current type has
+ * precedence over any type provided with this method. This behavior can be overridden
+ * setting {@link #useImplicitType()} to false.
+ *
+ * @return the types
+ * @since 1.4.2
+ */
+ Class>[] types() default {};
+
+ String[] strings() default {};
+
+ byte[] bytes() default {};
+
+ char[] chars() default {};
+
+ short[] shorts() default {};
+
+ int[] ints() default {};
+
+ long[] longs() default {};
+
+ float[] floats() default {};
+
+ double[] doubles() default {};
+
+ boolean[] booleans() default {};
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/annotations/XStreamConverters.java b/xstream/src/java/com/thoughtworks/xstream/annotations/XStreamConverters.java
new file mode 100644
index 0000000..a515b70
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/annotations/XStreamConverters.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 16. September 2005 by Mauro Talevi
+ */
+package com.thoughtworks.xstream.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ *
+ * @author Chung-Onn Cheong
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface XStreamConverters {
+ XStreamConverter[] value();
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/annotations/XStreamImplicit.java b/xstream/src/java/com/thoughtworks/xstream/annotations/XStreamImplicit.java
new file mode 100644
index 0000000..e589add
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/annotations/XStreamImplicit.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2006, 2007, 2011 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 01. December 2006 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * An annotation for marking a field as an implicit collection or array.
+ *
+ * @author Lucio Benfante
+ * @author Jörg Schaible
+ * @since 1.2.2
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface XStreamImplicit {
+ /**
+ * Element name of the implicit collection.
+ */
+ String itemFieldName() default "";
+ /**
+ * Field name of map entries that are used as key for the element in the implicit map.
+ * @since 1.4
+ */
+ String keyFieldName() default "";
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/annotations/XStreamImplicitCollection.java b/xstream/src/java/com/thoughtworks/xstream/annotations/XStreamImplicitCollection.java
new file mode 100644
index 0000000..b54f45f
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/annotations/XStreamImplicitCollection.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009 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 16. September 2005 by Mauro Talevi
+ */
+package com.thoughtworks.xstream.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ *
+ * @author Chung-Onn Cheong
+ * @deprecated As of 1.3, use @XStreamImplicit at field level
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface XStreamImplicitCollection {
+ String value(); //fieldName
+ String item() default ""; //itemfieldName
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/annotations/XStreamInclude.java b/xstream/src/java/com/thoughtworks/xstream/annotations/XStreamInclude.java
new file mode 100644
index 0000000..44e9fe4
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/annotations/XStreamInclude.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 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 13. November 2008 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to force automated processing of further classes.
+ *
+ * @author Steven Sparling
+ * @since 1.3.1
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface XStreamInclude
+{
+ public Class>[] value();
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/annotations/XStreamOmitField.java b/xstream/src/java/com/thoughtworks/xstream/annotations/XStreamOmitField.java
new file mode 100644
index 0000000..9e3ab20
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/annotations/XStreamOmitField.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2007 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 06. May 2005 by Guilherme Silveira
+ */
+package com.thoughtworks.xstream.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.annotation.ElementType;
+
+/**
+ * Declares a field to be omitted. The result is the same as invoking the method
+ * omitField in a XStream instance.
+ *
+ * @author Chung-Onn Cheong
+ * @author Guilherme Silveira
+ * @since 1.2.2
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @ interface XStreamOmitField {
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/ConversionException.java b/xstream/src/java/com/thoughtworks/xstream/converters/ConversionException.java
new file mode 100644
index 0000000..cd920be
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/ConversionException.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2003, 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009, 2011 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.converters;
+
+import com.thoughtworks.xstream.XStreamException;
+import com.thoughtworks.xstream.core.util.OrderRetainingMap;
+
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Thrown by {@link Converter} implementations when they cannot convert an object
+ * to/from textual data.
+ *
+ * When this exception is thrown it can be passed around to things that accept an
+ * {@link ErrorWriter}, allowing them to add diagnostics to the stack trace.
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ *
+ * @see ErrorWriter
+ */
+public class ConversionException extends XStreamException implements ErrorWriter {
+
+ private static final String SEPARATOR = "\n-------------------------------";
+ private Map stuff = new OrderRetainingMap();
+
+ public ConversionException(String msg, Throwable cause) {
+ super(msg, cause);
+ if (msg != null) {
+ add("message", msg);
+ }
+ if (cause != null) {
+ add("cause-exception", cause.getClass().getName());
+ add("cause-message", cause instanceof ConversionException ? ((ConversionException)cause).getShortMessage() : cause.getMessage());
+ }
+ }
+
+ public ConversionException(String msg) {
+ super(msg);
+ }
+
+ public ConversionException(Throwable cause) {
+ this(cause.getMessage(), cause);
+ }
+
+ public String get(String errorKey) {
+ return (String) stuff.get(errorKey);
+ }
+
+ public void add(String name, String information) {
+ String key = name;
+ int i = 0;
+ while (stuff.containsKey(key)) {
+ String value = (String)stuff.get(key);
+ if (information.equals(value))
+ return;
+ key = name + "[" + ++i +"]";
+ }
+ stuff.put(key, information);
+ }
+
+ public void set(String name, String information) {
+ String key = name;
+ int i = 0;
+ stuff.put(key, information); // keep order
+ while (stuff.containsKey(key)) {
+ if (i != 0) {
+ stuff.remove(key);
+ }
+ key = name + "[" + ++i +"]";
+ }
+ }
+
+ public Iterator keys() {
+ return stuff.keySet().iterator();
+ }
+
+ public String getMessage() {
+ StringBuffer result = new StringBuffer();
+ if (super.getMessage() != null) {
+ result.append(super.getMessage());
+ }
+ if (!result.toString().endsWith(SEPARATOR)) {
+ result.append("\n---- Debugging information ----");
+ }
+ for (Iterator iterator = keys(); iterator.hasNext();) {
+ String k = (String) iterator.next();
+ String v = get(k);
+ result.append('\n').append(k);
+ result.append(" ".substring(Math.min(20, k.length())));
+ result.append(": ").append(v);
+ }
+ result.append(SEPARATOR);
+ return result.toString();
+ }
+
+ public String getShortMessage() {
+ return super.getMessage();
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/Converter.java b/xstream/src/java/com/thoughtworks/xstream/converters/Converter.java
new file mode 100644
index 0000000..3d20ca9
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/Converter.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2003, 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2013 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.converters;
+
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+
+/**
+ * Converter implementations are responsible marshalling Java objects
+ * to/from textual data.
+ *
+ *
If an exception occurs during processing, a {@link ConversionException}
+ * should be thrown.
+ *
+ *
If working with the high level {@link com.thoughtworks.xstream.XStream} facade,
+ * you can register new converters using the XStream.registerConverter() method.
+ *
+ *
If working with the lower level API, the
+ * {@link com.thoughtworks.xstream.converters.ConverterLookup} implementation is
+ * responsible for looking up the appropriate converter.
+ *
+ *
Converters for object that can store all information in a single value
+ * should implement {@link com.thoughtworks.xstream.converters.SingleValueConverter}.
+ * {@link com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter}
+ * provides a starting point.
+ *
+ *
{@link com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter}
+ * provides a starting point for objects that hold a collection of other objects
+ * (such as Lists and Maps).
+ *
+ * @author Joe Walnes
+ * @see com.thoughtworks.xstream.XStream
+ * @see com.thoughtworks.xstream.converters.ConverterLookup
+ * @see com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter
+ * @see com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter
+ */
+public interface Converter extends ConverterMatcher {
+
+ /**
+ * Convert an object to textual data.
+ *
+ * @param source The object to be marshalled.
+ * @param writer A stream to write to.
+ * @param context A context that allows nested objects to be processed by XStream.
+ */
+ void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context);
+
+ /**
+ * Convert textual data back into an object.
+ *
+ * @param reader The stream to read the text from.
+ * @param context
+ * @return The resulting object.
+ */
+ Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context);
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/ConverterLookup.java b/xstream/src/java/com/thoughtworks/xstream/converters/ConverterLookup.java
new file mode 100644
index 0000000..818702c
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/ConverterLookup.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2003, 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2013 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.converters;
+
+/**
+ * Responsible for looking up the correct Converter implementation for a specific type.
+ *
+ * @author Joe Walnes
+ * @see Converter
+ */
+public interface ConverterLookup {
+
+ /**
+ * Lookup a converter for a specific type.
+ *
+ * This type may be any Class, including primitive and array types. It may also be null, signifying
+ * the value to be converted is a null type.
+ *
+ */
+ Converter lookupConverterForType(Class type);
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/ConverterMatcher.java b/xstream/src/java/com/thoughtworks/xstream/converters/ConverterMatcher.java
new file mode 100644
index 0000000..4400727
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/ConverterMatcher.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2006, 2007 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 15. February 2006 by Mauro Talevi
+ */
+package com.thoughtworks.xstream.converters;
+
+/**
+ * ConverterMatcher allows to match converters to classes by
+ * determining if a given type can be converted by the converter instance.
+ * ConverterMatcher is the base interface of any converter.
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ * @author Mauro Talevi
+ * @see com.thoughtworks.xstream.converters.Converter
+ * @see com.thoughtworks.xstream.converters.SingleValueConverter
+ * @since 1.2
+ */
+public interface ConverterMatcher {
+
+ /**
+ * Determines whether the converter can marshall a particular type.
+ * @param type the Class representing the object type to be converted
+ */
+ boolean canConvert(Class type);
+
+}
\ No newline at end of file
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/ConverterRegistry.java b/xstream/src/java/com/thoughtworks/xstream/converters/ConverterRegistry.java
new file mode 100644
index 0000000..d3ec037
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/ConverterRegistry.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 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 01. January 2008 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters;
+
+/**
+ * An interface for the converter management.
+ *
+ * @author Jörg Schaible
+ * @since 1.3
+ */
+public interface ConverterRegistry {
+
+ void registerConverter(Converter converter, int priority);
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/DataHolder.java b/xstream/src/java/com/thoughtworks/xstream/converters/DataHolder.java
new file mode 100644
index 0000000..05d45bf
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/DataHolder.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 16. August 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters;
+
+import java.util.Iterator;
+
+/**
+ * Holds generic data, to be used as seen fit by the user.
+ *
+ * @author Joe Walnes
+ */
+public interface DataHolder {
+
+ Object get(Object key);
+ void put(Object key, Object value);
+ Iterator keys();
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/ErrorReporter.java b/xstream/src/java/com/thoughtworks/xstream/converters/ErrorReporter.java
new file mode 100644
index 0000000..83fa22f
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/ErrorReporter.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2011 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 15.02.2011 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters;
+
+/**
+ * To aid debugging, some components expose themselves as ErrorReporter
+ * indicating that they can add information in case of an error..
+ *
+ * @author Joerg Schaible
+ *
+ * @since 1.4
+ */
+public interface ErrorReporter {
+ /**
+ * Append context information to an {@link ErrorWriter}.
+ *
+ * @param errorWriter the error writer
+ * @since 1.4
+ */
+ void appendErrors(ErrorWriter errorWriter);
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/ErrorWriter.java b/xstream/src/java/com/thoughtworks/xstream/converters/ErrorWriter.java
new file mode 100644
index 0000000..27e0d61
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/ErrorWriter.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009 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 08. May 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters;
+
+import java.util.Iterator;
+
+/**
+ * To aid debugging, some components are passed an ErrorWriter
+ * when things go wrong, allowing them to add information
+ * to the error message that may be helpful to diagnose problems.
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ */
+public interface ErrorWriter {
+
+ /**
+ * Add some information to the error message. The information will be added even
+ * if the identifier is already in use.
+ *
+ * @param name something to identify the type of information (e.g. 'XPath').
+ * @param information detail of the message (e.g. '/blah/moo[3]'
+ */
+ void add(String name, String information);
+
+ /**
+ * Set some information to the error message. If the identifier is already in use, the
+ * new information will replace the old one.
+ *
+ * @param name something to identify the type of information (e.g. 'XPath').
+ * @param information detail of the message (e.g. '/blah/moo[3]'
+ * @since 1.4
+ */
+ void set(String name, String information);
+
+ /**
+ * Retrieve information of the error message.
+ *
+ * @param errorKey the key of the message
+ * @return the value
+ * @since 1.3
+ */
+ String get(String errorKey);
+
+ /**
+ * Retrieve an iterator over all keys of the error message.
+ *
+ * @return an Iterator
+ * @since 1.3
+ */
+ Iterator keys();
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/MarshallingContext.java b/xstream/src/java/com/thoughtworks/xstream/converters/MarshallingContext.java
new file mode 100644
index 0000000..bdca456
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/MarshallingContext.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 06. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters;
+
+public interface MarshallingContext extends DataHolder {
+
+ /**
+ * Converts another object searching for the default converter
+ * @param nextItem the next item to convert
+ */
+ void convertAnother(Object nextItem);
+
+ /**
+ * Converts another object using the specified converter
+ * @param nextItem the next item to convert
+ * @param converter the Converter to use
+ * @since 1.2
+ */
+ void convertAnother(Object nextItem, Converter converter);
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/SingleValueConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/SingleValueConverter.java
new file mode 100644
index 0000000..6884bf0
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/SingleValueConverter.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2006, 2007, 2013 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 15. February 2006 by Mauro Talevi
+ */
+package com.thoughtworks.xstream.converters;
+
+/**
+ * SingleValueConverter implementations are marshallable to/from a single value String representation.
+ *
+ *
{@link com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter}
+ * provides a starting point for objects that can store all information in a single value String.
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ * @author Mauro Talevi
+ * @see com.thoughtworks.xstream.converters.Converter
+ * @see com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter
+ * @since 1.2
+ */
+public interface SingleValueConverter extends ConverterMatcher {
+
+ /**
+ * Marshals an Object into a single value representation.
+ * @param obj the Object to be converted
+ * @return a String with the single value of the Object or null
+ */
+ public String toString(Object obj);
+
+ /**
+ * Unmarshals an Object from its single value representation.
+ * @param str the String with the single value of the Object
+ * @return the Object
+ */
+ public Object fromString(String str);
+
+}
\ No newline at end of file
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/SingleValueConverterWrapper.java b/xstream/src/java/com/thoughtworks/xstream/converters/SingleValueConverterWrapper.java
new file mode 100644
index 0000000..795448d
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/SingleValueConverterWrapper.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2006, 2007, 2011, 2014 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 20. February 2006 by Mauro Talevi
+ */
+package com.thoughtworks.xstream.converters;
+
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+
+/**
+ * Wrapper to convert a {@link com.thoughtworks.xstream.converters.SingleValueConverter} into a
+ * {@link com.thoughtworks.xstream.converters.Converter}.
+ *
+ * @author Jörg Schaible
+ * @see com.thoughtworks.xstream.converters.Converter
+ * @see com.thoughtworks.xstream.converters.SingleValueConverter
+ */
+public class SingleValueConverterWrapper implements Converter, SingleValueConverter, ErrorReporter {
+
+ private final SingleValueConverter wrapped;
+
+ public SingleValueConverterWrapper(SingleValueConverter wrapped) {
+ this.wrapped = wrapped;
+ }
+
+ public boolean canConvert(Class type) {
+ return wrapped.canConvert(type);
+ }
+
+ public String toString(Object obj) {
+ return wrapped.toString(obj);
+ }
+
+ public Object fromString(String str) {
+ return wrapped.fromString(str);
+ }
+
+ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+ writer.setValue(toString(source));
+ }
+
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ return fromString(reader.getValue());
+ }
+
+ public void appendErrors(ErrorWriter errorWriter) {
+ errorWriter.add("wrapped-converter", wrapped == null ? "(null)" : wrapped.getClass().getName());
+ if (wrapped instanceof ErrorReporter) {
+ ((ErrorReporter)wrapped).appendErrors(errorWriter);
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/UnmarshallingContext.java b/xstream/src/java/com/thoughtworks/xstream/converters/UnmarshallingContext.java
new file mode 100644
index 0000000..dff988e
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/UnmarshallingContext.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 06. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters;
+
+public interface UnmarshallingContext extends DataHolder {
+
+ Object convertAnother(Object current, Class type);
+
+ /**
+ * @since 1.2
+ */
+ Object convertAnother(Object current, Class type, Converter converter);
+
+ Object currentObject();
+
+ Class getRequiredType();
+
+ void addCompletionCallback(Runnable work, int priority);
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/basic/AbstractSingleValueConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/basic/AbstractSingleValueConverter.java
new file mode 100644
index 0000000..467412d
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/basic/AbstractSingleValueConverter.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2006, 2007, 2013 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 15. February 2006 by Mauro Talevi
+ */
+package com.thoughtworks.xstream.converters.basic;
+
+import com.thoughtworks.xstream.converters.SingleValueConverter;
+
+/**
+ * Base abstract implementation of {@link com.thoughtworks.xstream.converters.SingleValueConverter}.
+ *
+ *
Subclasses should implement methods canConvert(Class) and fromString(String) for the conversion.
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ * @author Mauro Talevi
+ * @see com.thoughtworks.xstream.converters.SingleValueConverter
+ */
+public abstract class AbstractSingleValueConverter implements SingleValueConverter {
+
+ public abstract boolean canConvert(Class type);
+
+ public String toString(Object obj) {
+ return obj == null ? null : obj.toString();
+ }
+
+ public abstract Object fromString(String str);
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/basic/BigDecimalConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/basic/BigDecimalConverter.java
new file mode 100644
index 0000000..57a2014
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/basic/BigDecimalConverter.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 07. May 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.basic;
+
+import java.math.BigDecimal;
+
+/**
+ * Converts a java.math.BigDecimal to a String, retaining
+ * its precision.
+ *
+ * @author Joe Walnes
+ */
+public class BigDecimalConverter extends AbstractSingleValueConverter {
+
+ public boolean canConvert(Class type) {
+ return type.equals(BigDecimal.class);
+ }
+
+ public Object fromString(String str) {
+ return new BigDecimal(str);
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/basic/BigIntegerConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/basic/BigIntegerConverter.java
new file mode 100644
index 0000000..e3113ed
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/basic/BigIntegerConverter.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 07. May 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.basic;
+
+import java.math.BigInteger;
+
+/**
+ * Converts a java.math.BigInteger to a String.
+ *
+ * @author Joe Walnes
+ */
+public class BigIntegerConverter extends AbstractSingleValueConverter {
+
+ public boolean canConvert(Class type) {
+ return type.equals(BigInteger.class);
+ }
+
+ public Object fromString(String str) {
+ return new BigInteger(str);
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/basic/BooleanConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/basic/BooleanConverter.java
new file mode 100644
index 0000000..7afb03b
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/basic/BooleanConverter.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2003, 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2014 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.converters.basic;
+
+
+/**
+ * Converts a boolean primitive or java.lang.Boolean wrapper to
+ * a String.
+ *
+ * @author Joe Walnes
+ * @author David Blevins
+ */
+public class BooleanConverter extends AbstractSingleValueConverter {
+
+ public static final BooleanConverter TRUE_FALSE = new BooleanConverter("true", "false", false);
+
+ public static final BooleanConverter YES_NO = new BooleanConverter("yes", "no", false);
+
+ public static final BooleanConverter BINARY = new BooleanConverter("1", "0", true);
+
+ private final String positive;
+ private final String negative;
+ private final boolean caseSensitive;
+
+ public BooleanConverter(final String positive, final String negative, final boolean caseSensitive) {
+ this.positive = positive;
+ this.negative = negative;
+ this.caseSensitive = caseSensitive;
+ }
+
+ public BooleanConverter() {
+ this("true", "false", false);
+ }
+
+ /**
+ * @deprecated As of 1.4.8 use {@link #canConvert(Class)}
+ */
+ public boolean shouldConvert(final Class type, final Object value) {
+ return true;
+ }
+
+ public boolean canConvert(final Class type) {
+ return type.equals(boolean.class) || type.equals(Boolean.class);
+ }
+
+ public Object fromString(final String str) {
+ if (caseSensitive) {
+ return positive.equals(str) ? Boolean.TRUE : Boolean.FALSE;
+ } else {
+ return positive.equalsIgnoreCase(str) ? Boolean.TRUE : Boolean.FALSE;
+ }
+ }
+
+ public String toString(final Object obj) {
+ final Boolean value = (Boolean) obj;
+ return obj == null ? null : value.booleanValue() ? positive : negative;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/basic/ByteConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/basic/ByteConverter.java
new file mode 100644
index 0000000..46b168a
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/basic/ByteConverter.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2003, 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007 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.converters.basic;
+
+/**
+ * Converts a byte primitive or java.lang.Byte wrapper to
+ * a String.
+ *
+ * @author Joe Walnes
+ */
+public class ByteConverter extends AbstractSingleValueConverter {
+
+ public boolean canConvert(Class type) {
+ return type.equals(byte.class) || type.equals(Byte.class);
+ }
+
+ public Object fromString(String str) {
+ int value = Integer.decode(str).intValue();
+ if(value < Byte.MIN_VALUE || value > 0xFF) {
+ throw new NumberFormatException("For input string: \"" + str + '"');
+ }
+ return new Byte((byte)value);
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/basic/CharConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/basic/CharConverter.java
new file mode 100644
index 0000000..4f93310
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/basic/CharConverter.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2003, 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007 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.converters.basic;
+
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.SingleValueConverter;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+
+/**
+ * Converts a char primitive or java.lang.Character wrapper to
+ * a String. If char is \0 the representing String is empty.
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ */
+public class CharConverter implements Converter, SingleValueConverter {
+
+ public boolean canConvert(Class type) {
+ return type.equals(char.class) || type.equals(Character.class);
+ }
+
+ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+ writer.setValue(toString(source));
+ }
+
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ String nullAttribute = reader.getAttribute("null");
+ if (nullAttribute != null && nullAttribute.equals("true")) {
+ return new Character('\0');
+ } else {
+ return fromString(reader.getValue());
+ }
+ }
+
+ public Object fromString(String str) {
+ if (str.length() == 0) {
+ return new Character('\0');
+ } else {
+ return new Character(str.charAt(0));
+ }
+ }
+
+ public String toString(Object obj) {
+ char ch = ((Character)obj).charValue();
+ return ch == '\0' ? "" : obj.toString();
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/basic/DateConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/basic/DateConverter.java
new file mode 100644
index 0000000..ad111ed
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/basic/DateConverter.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2003, 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009, 2011, 2012, 2013, 2014, 2015 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.converters.basic;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.ErrorReporter;
+import com.thoughtworks.xstream.converters.ErrorWriter;
+import com.thoughtworks.xstream.core.JVM;
+import com.thoughtworks.xstream.core.util.ThreadSafeSimpleDateFormat;
+
+
+/**
+ * Converts a {@link Date} to a string as a date format, retaining precision down to milliseconds.
+ *
+ * The formatted string is by default in UTC and English locale. You can provide a different {@link Locale} and
+ * {@link TimeZone} that are used for serialization or null to use always the current TimeZone. Note, that
+ * the default format uses 3-letter time zones that can be ambiguous and may cause wrong results at deserialization and
+ * is localized since Java 6.
+ *
+ *
+ * Using a Java 7 runtime or higher, the converter supports the datetime
+ * format defined by W3C (a subset of ISO 8601) at deserialization. Only the formats that also contain the time
+ * information.
+ *
+ *
+ * Dates in a different era are using a special default pattern that contains the era itself.
+ *
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ */
+public class DateConverter extends AbstractSingleValueConverter implements ErrorReporter {
+
+ private static final String[] DEFAULT_ACCEPTABLE_FORMATS;
+ private static final String DEFAULT_PATTERN;
+ private static final String DEFAULT_ERA_PATTERN;
+ private static final TimeZone UTC;
+ private static final long ERA_START;
+ static {
+ UTC = TimeZone.getTimeZone("UTC");
+
+ final String defaultPattern = "yyyy-MM-dd HH:mm:ss.S z";
+ final String defaultEraPattern = "yyyy-MM-dd G HH:mm:ss.S z";
+ final List acceptablePatterns = new ArrayList();
+ final boolean utcSupported = JVM.canParseUTCDateFormat();
+ DEFAULT_PATTERN = utcSupported ? defaultPattern : "yyyy-MM-dd HH:mm:ss.S 'UTC'";
+ DEFAULT_ERA_PATTERN = utcSupported ? defaultEraPattern : "yyyy-MM-dd G HH:mm:ss.S 'UTC'";
+ acceptablePatterns.add("yyyy-MM-dd HH:mm:ss.S z");
+ if (!utcSupported) {
+ acceptablePatterns.add(defaultPattern);
+ }
+ acceptablePatterns.add("yyyy-MM-dd HH:mm:ss.S a");
+ // JDK 1.3 needs both versions
+ acceptablePatterns.add("yyyy-MM-dd HH:mm:ssz");
+ acceptablePatterns.add("yyyy-MM-dd HH:mm:ss z");
+ if (!utcSupported) {
+ acceptablePatterns.add("yyyy-MM-dd HH:mm:ss 'UTC'");
+ }
+ if (JVM.canParseISO8601TimeZoneInDateFormat()) {
+ acceptablePatterns.add("yyyy-MM-dd'T'HH:mm:ss.SX");
+ acceptablePatterns.add("yyyy-MM-dd'T'HH:mm:ssX");
+ acceptablePatterns.add("yyyy-MM-dd'T'HH:mmX");
+ }
+ // backwards compatibility
+ acceptablePatterns.add("yyyy-MM-dd HH:mm:ssa");
+ DEFAULT_ACCEPTABLE_FORMATS = (String[]) acceptablePatterns.toArray(new String[acceptablePatterns.size()]);
+
+ final Calendar cal = Calendar.getInstance();
+ cal.setTimeZone(UTC);
+ cal.clear();
+ cal.set(1, Calendar.JANUARY, 1);
+ ERA_START = cal.getTime().getTime(); // calendar.getTimeInMillis() not available under JDK 1.3
+ }
+ private final ThreadSafeSimpleDateFormat defaultFormat;
+ private final ThreadSafeSimpleDateFormat defaultEraFormat;
+ private final ThreadSafeSimpleDateFormat[] acceptableFormats;
+
+ /**
+ * Construct a DateConverter with standard formats and lenient set off.
+ */
+ public DateConverter() {
+ this(false);
+ }
+
+ /**
+ * Construct a DateConverter with standard formats, lenient set off and uses a given
+ * TimeZone for serialization.
+ *
+ * @param timeZone the TimeZone used to serialize the Date
+ * @since 1.4
+ */
+ public DateConverter(final TimeZone timeZone) {
+ this(DEFAULT_PATTERN, DEFAULT_ACCEPTABLE_FORMATS, timeZone);
+ }
+
+ /**
+ * Construct a DateConverter with standard formats and using UTC.
+ *
+ * @param lenient the lenient setting of {@link SimpleDateFormat#setLenient(boolean)}
+ * @since 1.3
+ */
+ public DateConverter(final boolean lenient) {
+ this(DEFAULT_PATTERN, DEFAULT_ACCEPTABLE_FORMATS, lenient);
+ }
+
+ /**
+ * Construct a DateConverter with lenient set off using UTC.
+ *
+ * @param defaultFormat the default format
+ * @param acceptableFormats fallback formats
+ */
+ public DateConverter(final String defaultFormat, final String[] acceptableFormats) {
+ this(defaultFormat, acceptableFormats, false);
+ }
+
+ /**
+ * Construct a DateConverter with a given TimeZone and lenient set off.
+ *
+ * @param defaultFormat the default format
+ * @param acceptableFormats fallback formats
+ * @since 1.4
+ */
+ public DateConverter(final String defaultFormat, final String[] acceptableFormats, final TimeZone timeZone) {
+ this(defaultFormat, acceptableFormats, timeZone, false);
+ }
+
+ /**
+ * Construct a DateConverter.
+ *
+ * @param defaultFormat the default format
+ * @param acceptableFormats fallback formats
+ * @param lenient the lenient setting of {@link SimpleDateFormat#setLenient(boolean)}
+ * @since 1.3
+ */
+ public DateConverter(final String defaultFormat, final String[] acceptableFormats, final boolean lenient) {
+ this(defaultFormat, acceptableFormats, UTC, lenient);
+ }
+
+ /**
+ * Construct a DateConverter.
+ *
+ * @param defaultFormat the default format
+ * @param acceptableFormats fallback formats
+ * @param timeZone the TimeZone used to serialize the Date
+ * @param lenient the lenient setting of {@link SimpleDateFormat#setLenient(boolean)}
+ * @since 1.4
+ */
+ public DateConverter(
+ final String defaultFormat, final String[] acceptableFormats, final TimeZone timeZone, final boolean lenient) {
+ this(DEFAULT_ERA_PATTERN, defaultFormat, acceptableFormats, Locale.ENGLISH, timeZone, lenient);
+ }
+
+ /**
+ * Construct a DateConverter.
+ *
+ * @param defaultEraFormat the default format for dates in a different era (may be
+ * null to drop era support)
+ * @param defaultFormat the default format
+ * @param acceptableFormats fallback formats
+ * @param locale locale to use for the format
+ * @param timeZone the TimeZone used to serialize the Date
+ * @param lenient the lenient setting of {@link SimpleDateFormat#setLenient(boolean)}
+ * @since 1.4.4
+ */
+ public DateConverter(
+ final String defaultEraFormat, final String defaultFormat, final String[] acceptableFormats,
+ final Locale locale, final TimeZone timeZone, final boolean lenient) {
+ if (defaultEraFormat != null) {
+ this.defaultEraFormat = new ThreadSafeSimpleDateFormat(
+ defaultEraFormat, timeZone, locale, 4, 20, lenient);
+ } else {
+ this.defaultEraFormat = null;
+ }
+ this.defaultFormat = new ThreadSafeSimpleDateFormat(
+ defaultFormat, timeZone, locale, 4, 20, lenient);
+ this.acceptableFormats = acceptableFormats != null
+ ? new ThreadSafeSimpleDateFormat[acceptableFormats.length]
+ : new ThreadSafeSimpleDateFormat[0];
+ for (int i = 0; i < this.acceptableFormats.length; i++ ) {
+ this.acceptableFormats[i] = new ThreadSafeSimpleDateFormat(
+ acceptableFormats[i], timeZone, locale, 1, 20, lenient);
+ }
+ }
+
+ public boolean canConvert(Class type) {
+ return type.equals(Date.class);
+ }
+
+ public Object fromString(String str) {
+ if (defaultEraFormat != null) {
+ try {
+ return defaultEraFormat.parse(str);
+ } catch (ParseException e) {
+ // try next ...
+ }
+ }
+ if (defaultEraFormat != defaultFormat) {
+ try {
+ return defaultFormat.parse(str);
+ } catch (ParseException e) {
+ // try next ...
+ }
+ }
+ for (int i = 0; i < acceptableFormats.length; i++ ) {
+ try {
+ return acceptableFormats[i].parse(str);
+ } catch (ParseException e3) {
+ // no worries, let's try the next format.
+ }
+ }
+ // no dateFormats left to try
+ throw new ConversionException("Cannot parse date " + str);
+ }
+
+ public String toString(Object obj) {
+ final Date date = (Date)obj;
+ if (date.getTime() < ERA_START && defaultEraFormat != null) {
+ return defaultEraFormat.format(date);
+ } else {
+ return defaultFormat.format(date);
+ }
+ }
+
+ public void appendErrors(ErrorWriter errorWriter) {
+ errorWriter.add("Default date pattern", defaultFormat.toString());
+ if (defaultEraFormat != null) {
+ errorWriter.add("Default era date pattern", defaultEraFormat.toString());
+ }
+ for (int i = 0; i < acceptableFormats.length; i++ ) {
+ errorWriter.add("Alternative date pattern", acceptableFormats[i].toString());
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/basic/DoubleConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/basic/DoubleConverter.java
new file mode 100644
index 0000000..1c5b6e3
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/basic/DoubleConverter.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2003, 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007 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.converters.basic;
+
+/**
+ * Converts a double primitive or java.lang.Double wrapper to
+ * a String.
+ *
+ * @author Joe Walnes
+ */
+public class DoubleConverter extends AbstractSingleValueConverter {
+
+ public boolean canConvert(Class type) {
+ return type.equals(double.class) || type.equals(Double.class);
+ }
+
+ public Object fromString(String str) {
+ return Double.valueOf(str);
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/basic/FloatConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/basic/FloatConverter.java
new file mode 100644
index 0000000..66a486f
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/basic/FloatConverter.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2003, 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007 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.converters.basic;
+
+/**
+ * Converts a float primitive or java.lang.Float wrapper to
+ * a String.
+ *
+ * @author Joe Walnes
+ */
+public class FloatConverter extends AbstractSingleValueConverter {
+
+ public boolean canConvert(Class type) {
+ return type.equals(float.class) || type.equals(Float.class);
+ }
+
+ public Object fromString(String str) {
+ return Float.valueOf(str);
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/basic/IntConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/basic/IntConverter.java
new file mode 100644
index 0000000..cc28fcd
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/basic/IntConverter.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2003, 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007 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.converters.basic;
+
+/**
+ * Converts an int primitive or java.lang.Integer wrapper to
+ * a String.
+ *
+ * @author Joe Walnes
+ */
+public class IntConverter extends AbstractSingleValueConverter {
+
+ public boolean canConvert(Class type) {
+ return type.equals(int.class) || type.equals(Integer.class);
+ }
+
+ public Object fromString(String str) {
+ long value = Long.decode(str).longValue();
+ if(value < Integer.MIN_VALUE || value > 0xFFFFFFFFl) {
+ throw new NumberFormatException("For input string: \"" + str + '"');
+ }
+ return new Integer((int)value);
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/basic/LongConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/basic/LongConverter.java
new file mode 100644
index 0000000..88771f1
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/basic/LongConverter.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2003, 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2013 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.converters.basic;
+
+/**
+ * Converts a long primitive or java.lang.Long wrapper to a String.
+ *
+ * @author Joe Walnes
+ */
+public class LongConverter extends AbstractSingleValueConverter {
+
+ public boolean canConvert(Class type) {
+ return type.equals(long.class) || type.equals(Long.class);
+ }
+
+ public Object fromString(String str) {
+ int len = str.length();
+ if (len == 0) {
+ throw new NumberFormatException("For input string: \"\"");
+ }
+ if (len < 17) {
+ return Long.decode(str);
+ }
+ char c0 = str.charAt(0);
+ if (c0 != '0' && c0 != '#') {
+ return Long.decode(str);
+ }
+ char c1 = str.charAt(1);
+ final long high;
+ final long low;
+ if (c0 == '#' && len == 17) {
+ high = Long.parseLong(str.substring(1, 9), 16) << 32;
+ low = Long.parseLong(str.substring(9, 17), 16);
+ } else if ((c1 == 'x' || c1 == 'X') && len == 18) {
+ high = Long.parseLong(str.substring(2, 10), 16) << 32;
+ low = Long.parseLong(str.substring(10, 18), 16);
+ } else if (len == 23 && c1 == '1') {
+ high = Long.parseLong(str.substring(1, 12), 8) << 33;
+ low = Long.parseLong(str.substring(12, 23), 8);
+ } else {
+ return Long.decode(str);
+ }
+ final long num = high | low;
+ return new Long(num);
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/basic/NullConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/basic/NullConverter.java
new file mode 100644
index 0000000..7b2fe53
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/basic/NullConverter.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2003, 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2012 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 03. October 2003 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.basic;
+
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+/**
+ * Special converter to signify nulls at the root level.
+ *
+ * @author Joe Walnes
+ */
+public class NullConverter implements Converter {
+
+ public boolean canConvert(Class type) {
+ return type == null || Mapper.Null.class.isAssignableFrom(type);
+ }
+
+ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+ ExtendedHierarchicalStreamWriterHelper.startNode(writer, "null", Mapper.Null.class);
+ writer.endNode();
+ }
+
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ return null;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/basic/ShortConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/basic/ShortConverter.java
new file mode 100644
index 0000000..df9c851
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/basic/ShortConverter.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2003, 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007 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.converters.basic;
+
+/**
+ * Converts a short primitive or java.lang.Short wrapper to
+ * a String.
+ *
+ * @author Joe Walnes
+ */
+public class ShortConverter extends AbstractSingleValueConverter {
+
+ public boolean canConvert(Class type) {
+ return type.equals(short.class) || type.equals(Short.class);
+ }
+
+ public Object fromString(String str) {
+ int value = Integer.decode(str).intValue();
+ if(value < Short.MIN_VALUE || value > 0xFFFF) {
+ throw new NumberFormatException("For input string: \"" + str + '"');
+ }
+ return new Short((short)value);
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/basic/StringBufferConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/basic/StringBufferConverter.java
new file mode 100644
index 0000000..4bdaaab
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/basic/StringBufferConverter.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2003, 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 29. September 2003 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.basic;
+
+/**
+ * Converts the contents of a StringBuffer to XML.
+ *
+ * @author Joe Walnes
+ */
+public class StringBufferConverter extends AbstractSingleValueConverter {
+
+ public Object fromString(String str) {
+ return new StringBuffer(str);
+ }
+
+ public boolean canConvert(Class type) {
+ return type.equals(StringBuffer.class);
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/basic/StringBuilderConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/basic/StringBuilderConverter.java
new file mode 100644
index 0000000..d1557af
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/basic/StringBuilderConverter.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 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 04. January 2008 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.basic;
+
+/**
+ * Converts the contents of a StringBuilder to XML.
+ *
+ * @author Jörg Schaible
+ */
+public class StringBuilderConverter extends AbstractSingleValueConverter {
+
+ public Object fromString(String str) {
+ return new StringBuilder(str);
+ }
+
+ public boolean canConvert(Class type) {
+ return type.equals(StringBuilder.class);
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/basic/StringConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/basic/StringConverter.java
new file mode 100644
index 0000000..7ee0437
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/basic/StringConverter.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2003, 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2011 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.converters.basic;
+
+import java.util.Collections;
+import java.util.Map;
+
+import com.thoughtworks.xstream.core.util.WeakCache;
+
+
+/**
+ * Converts a String to a String ;).
+ *
+ * Well ok, it doesn't actually do any conversion. The converter uses by default a map
+ * with weak references to reuse instances of strings that do not exceed a length limit. This
+ * limit is by default 38 characters to cache typical strings containing UUIDs. Only shorter
+ * strings are typically repeated more often in XML values.
+ *
+ *
+ * @author Joe Walnes
+ * @author Rene Schwietzke
+ * @author Jörg Schaible
+ */
+public class StringConverter extends AbstractSingleValueConverter {
+
+ private static final int LENGTH_LIMIT = 38;
+
+ /**
+ * A Map to store strings as long as needed to map similar strings onto the same instance
+ * and conserve memory. The map can be set from the outside during construction, so it can
+ * be a LRU map or a weak map, synchronised or not.
+ */
+ private final Map cache;
+ private final int lengthLimit;
+
+ /**
+ * Construct a StringConverter using a map-based cache for strings not exceeding the length limit.
+ *
+ * @param map the map to use for the instances to reuse (may be null to not cache at all)
+ * @param lengthLimit maximum string length of a cached string, -1 to cache all, 0 to turn off the cache
+ * @since 1.4.2
+ */
+ public StringConverter(final Map map, int lengthLimit) {
+ cache = map;
+ this.lengthLimit = lengthLimit;
+ }
+
+ /**
+ * Construct a StringConverter using a map-based cache for strings not exceeding 38 characters.
+ *
+ * @param map the map to use for the instances to reuse (may be null to not cache at all)
+ */
+ public StringConverter(final Map map) {
+ this(map, LENGTH_LIMIT);
+ }
+
+ /**
+ * Construct a StringConverter using a cache with weak references for strings not exceeding the length limit.
+ *
+ * @param lengthLimit maximum string length of a cached string, -1 to cache all, 0 to turn off the cache
+ * @since 1.4.2
+ */
+ public StringConverter(int lengthLimit) {
+ this(Collections.synchronizedMap(new WeakCache()), lengthLimit);
+ }
+
+ /**
+ * Construct a StringConverter using a cache with weak references for strings not exceeding 38 characters.
+ */
+ public StringConverter() {
+ this(LENGTH_LIMIT);
+ }
+
+ public boolean canConvert(final Class type) {
+ return type.equals(String.class);
+ }
+
+ public Object fromString(final String str) {
+ if (cache != null && str != null && (lengthLimit < 0 || str.length() <= lengthLimit)) {
+ String s = (String)cache.get(str);
+
+ if (s == null) {
+ // fill cache
+ cache.put(str, str);
+
+ s = str;
+ }
+
+ return s;
+ } else {
+ return str;
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/basic/URIConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/basic/URIConverter.java
new file mode 100644
index 0000000..0661efd
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/basic/URIConverter.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2010 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 3. August 2010 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.basic;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+
+
+/**
+ * Converts a java.net.URI to a string.
+ *
+ * @author Carlos Roman
+ */
+public class URIConverter extends AbstractSingleValueConverter {
+
+ public boolean canConvert(Class type) {
+ return type.equals(URI.class);
+ }
+
+ public Object fromString(String str) {
+ try {
+ return new URI(str);
+ } catch (URISyntaxException e) {
+ throw new ConversionException(e);
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/basic/URLConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/basic/URLConverter.java
new file mode 100644
index 0000000..8aee562
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/basic/URLConverter.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 25. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.basic;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * Converts a java.net.URL to a string.
+ *
+ * @author J. Matthew Pryor
+ */
+public class URLConverter extends AbstractSingleValueConverter {
+
+ public boolean canConvert(Class type) {
+ return type.equals(URL.class);
+ }
+
+ public Object fromString(String str) {
+ try {
+ return new URL(str);
+ } catch (MalformedURLException e) {
+ throw new ConversionException(e);
+ }
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/basic/UUIDConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/basic/UUIDConverter.java
new file mode 100644
index 0000000..a1d69d8
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/basic/UUIDConverter.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 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 04. January 2008 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.basic;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+
+import java.util.UUID;
+
+
+/**
+ * Converts a java.util.UUID to a string.
+ *
+ * @author Jörg Schaible
+ */
+public class UUIDConverter extends AbstractSingleValueConverter {
+
+ public boolean canConvert(Class type) {
+ return type.equals(UUID.class);
+ }
+
+ public Object fromString(String str) {
+ try {
+ return UUID.fromString(str);
+ } catch(IllegalArgumentException e) {
+ throw new ConversionException("Cannot create UUID instance", e);
+ }
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/basic/package.html b/xstream/src/java/com/thoughtworks/xstream/converters/basic/package.html
new file mode 100644
index 0000000..6440f47
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/basic/package.html
@@ -0,0 +1,22 @@
+
+
+
Converters for common basic types in Java. These include
+primitives (int, boolean, etc), wrapper objects (Integer, Boolean, etc),
+String, StringBuffer, java.util.Date, null, java.net.URL,
+java.math.BigInteger and java.math.BigDecimal.
+
+
These converters are enabled by default.
+
+
All of the basic converters will convert the item
+into a single String, with no nested elements.
+
\ No newline at end of file
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/collections/AbstractCollectionConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/collections/AbstractCollectionConverter.java
new file mode 100644
index 0000000..0909417
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/collections/AbstractCollectionConverter.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2003, 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009, 2013 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.converters.collections;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.core.util.HierarchicalStreams;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+/**
+ * Base helper class for converters that need to handle
+ * collections of items (arrays, Lists, Maps, etc).
+ *
+ *
Typically, subclasses of this will converter the outer
+ * structure of the collection, loop through the contents and
+ * call readItem() or writeItem() for each item.
+ *
+ * @author Joe Walnes
+ */
+public abstract class AbstractCollectionConverter implements Converter {
+
+ private final Mapper mapper;
+
+ public abstract boolean canConvert(Class type);
+
+ public AbstractCollectionConverter(Mapper mapper) {
+ this.mapper = mapper;
+ }
+
+ protected Mapper mapper() {
+ return mapper;
+ }
+
+ public abstract void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context);
+
+ public abstract Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context);
+
+
+
+ protected void writeItem(Object item, MarshallingContext context, HierarchicalStreamWriter writer) {
+ // PUBLISHED API METHOD! If changing signature, ensure backwards compatibility.
+ if (item == null) {
+ // todo: this is duplicated in TreeMarshaller.start()
+ String name = mapper().serializedClass(null);
+ ExtendedHierarchicalStreamWriterHelper.startNode(writer, name, Mapper.Null.class);
+ writer.endNode();
+ } else {
+ String name = mapper().serializedClass(item.getClass());
+ ExtendedHierarchicalStreamWriterHelper.startNode(writer, name, item.getClass());
+ context.convertAnother(item);
+ writer.endNode();
+ }
+ }
+
+ protected Object readItem(HierarchicalStreamReader reader, UnmarshallingContext context, Object current) {
+ Class type = HierarchicalStreams.readClassType(reader, mapper());
+ return context.convertAnother(current, type);
+ }
+
+ protected Object createCollection(Class type) {
+ Class defaultType = mapper().defaultImplementationOf(type);
+ try {
+ return defaultType.newInstance();
+ } catch (InstantiationException e) {
+ throw new ConversionException("Cannot instantiate " + defaultType.getName(), e);
+ } catch (IllegalAccessException e) {
+ throw new ConversionException("Cannot instantiate " + defaultType.getName(), e);
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/collections/ArrayConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/collections/ArrayConverter.java
new file mode 100644
index 0000000..25da0ce
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/collections/ArrayConverter.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2003, 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 03. October 2003 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.collections;
+
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Converts an array of objects or primitives to XML, using
+ * a nested child element for each item.
+ *
+ * @author Joe Walnes
+ */
+public class ArrayConverter extends AbstractCollectionConverter {
+
+ public ArrayConverter(Mapper mapper) {
+ super(mapper);
+ }
+
+ public boolean canConvert(Class type) {
+ return type.isArray();
+ }
+
+ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+ int length = Array.getLength(source);
+ for (int i = 0; i < length; i++) {
+ Object item = Array.get(source, i);
+ writeItem(item, context, writer);
+ }
+
+ }
+
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ // read the items from xml into a list
+ List items = new ArrayList();
+ while (reader.hasMoreChildren()) {
+ reader.moveDown();
+ Object item = readItem(reader, context, null); // TODO: arg, what should replace null?
+ items.add(item);
+ reader.moveUp();
+ }
+ // now convertAnother the list into an array
+ // (this has to be done as a separate list as the array size is not
+ // known until all items have been read)
+ Object array = Array.newInstance(context.getRequiredType().getComponentType(), items.size());
+ int i = 0;
+ for (Iterator iterator = items.iterator(); iterator.hasNext();) {
+ Array.set(array, i++, iterator.next());
+ }
+ return array;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/collections/BitSetConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/collections/BitSetConverter.java
new file mode 100644
index 0000000..627ba8c
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/collections/BitSetConverter.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 07. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.collections;
+
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+
+import java.util.BitSet;
+import java.util.StringTokenizer;
+
+/**
+ * Converts a java.util.BitSet to XML, as a compact
+ * comma delimited list of ones and zeros.
+ *
+ * @author Joe Walnes
+ */
+public class BitSetConverter implements Converter {
+
+ public boolean canConvert(Class type) {
+ return type.equals(BitSet.class);
+ }
+
+ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+ BitSet bitSet = (BitSet) source;
+ StringBuffer buffer = new StringBuffer();
+ boolean seenFirst = false;
+ for (int i = 0; i < bitSet.length(); i++) {
+ if (bitSet.get(i)) {
+ if (seenFirst) {
+ buffer.append(',');
+ } else {
+ seenFirst = true;
+ }
+ buffer.append(i);
+ }
+ }
+ writer.setValue(buffer.toString());
+ }
+
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ BitSet result = new BitSet();
+ StringTokenizer tokenizer = new StringTokenizer(reader.getValue(), ",", false);
+ while (tokenizer.hasMoreTokens()) {
+ int index = Integer.parseInt(tokenizer.nextToken());
+ result.set(index);
+ }
+ return result;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/collections/CharArrayConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/collections/CharArrayConverter.java
new file mode 100644
index 0000000..62e8332
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/collections/CharArrayConverter.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 06. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.collections;
+
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+
+/**
+ * Converts a char[] to XML, storing the contents as a single
+ * String.
+ *
+ * @author Joe Walnes
+ */
+public class CharArrayConverter implements Converter {
+
+ public boolean canConvert(Class type) {
+ return type.isArray() && type.getComponentType().equals(char.class);
+ }
+
+ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+ char[] chars = (char[]) source;
+ writer.setValue(new String(chars));
+ }
+
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ return reader.getValue().toCharArray();
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/collections/CollectionConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/collections/CollectionConverter.java
new file mode 100644
index 0000000..4c7e90d
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/collections/CollectionConverter.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2003, 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2010, 2011, 2013 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 01. October 2003 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.collections;
+
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.core.JVM;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Vector;
+
+/**
+ * Converts most common Collections (Lists and Sets) to XML, specifying a nested
+ * element for each item.
+ *
+ *
Supports java.util.ArrayList, java.util.HashSet,
+ * java.util.LinkedList, java.util.Vector and java.util.LinkedHashSet.
+ *
+ * @author Joe Walnes
+ */
+public class CollectionConverter extends AbstractCollectionConverter {
+
+ private final Class type;
+
+ public CollectionConverter(Mapper mapper) {
+ this(mapper, null);
+ }
+
+ /**
+ * Construct a CollectionConverter for a special Collection type.
+ * @param mapper the mapper
+ * @param type the Collection type to handle
+ * @since 1.4.5
+ */
+ public CollectionConverter(Mapper mapper, Class type) {
+ super(mapper);
+ this.type = type;
+ if (type != null && !Collection.class.isAssignableFrom(type)) {
+ throw new IllegalArgumentException(type + " not of type " + Collection.class);
+ }
+ }
+
+ public boolean canConvert(Class type) {
+ if (this.type != null) {
+ return type.equals(this.type);
+ }
+ return type.equals(ArrayList.class)
+ || type.equals(HashSet.class)
+ || type.equals(LinkedList.class)
+ || type.equals(Vector.class)
+ || (JVM.is14() && type.getName().equals("java.util.LinkedHashSet"));
+ }
+
+ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+ Collection collection = (Collection) source;
+ for (Iterator iterator = collection.iterator(); iterator.hasNext();) {
+ Object item = iterator.next();
+ writeItem(item, context, writer);
+ }
+ }
+
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ Collection collection = (Collection) createCollection(context.getRequiredType());
+ populateCollection(reader, context, collection);
+ return collection;
+ }
+
+ protected void populateCollection(HierarchicalStreamReader reader, UnmarshallingContext context, Collection collection) {
+ populateCollection(reader, context, collection, collection);
+ }
+
+ protected void populateCollection(HierarchicalStreamReader reader, UnmarshallingContext context, Collection collection, Collection target) {
+ while (reader.hasMoreChildren()) {
+ reader.moveDown();
+ addCurrentElementToCollection(reader, context, collection, target);
+ reader.moveUp();
+ }
+ }
+
+ protected void addCurrentElementToCollection(HierarchicalStreamReader reader, UnmarshallingContext context,
+ Collection collection, Collection target) {
+ Object item = readItem(reader, context, collection);
+ target.add(item);
+ }
+
+ protected Object createCollection(Class type) {
+ return super.createCollection(this.type != null ? this.type : type);
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/collections/MapConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/collections/MapConverter.java
new file mode 100644
index 0000000..dccaca7
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/collections/MapConverter.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2003, 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2010, 2011, 2012, 2013 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.converters.collections;
+
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Converts a java.util.Map to XML, specifying an 'entry'
+ * element with 'key' and 'value' children.
+ *
Note: 'key' and 'value' is not the name of the generated tag. The
+ * children are serialized as normal elements and the implementation expects
+ * them in the order 'key'/'value'.
+ *
Supports java.util.HashMap, java.util.Hashtable,
+ * java.util.LinkedHashMap and java.util.concurrent.ConcurrentHashMap.
+ *
+ * @author Joe Walnes
+ */
+public class MapConverter extends AbstractCollectionConverter {
+
+ private final Class type;
+
+ public MapConverter(Mapper mapper) {
+ this(mapper, null);
+ }
+
+ /**
+ * Construct a MapConverter for a special Map type.
+ * @param mapper the mapper
+ * @param type the type to handle
+ * @since 1.4.5
+ */
+ public MapConverter(Mapper mapper, Class type) {
+ super(mapper);
+ this.type = type;
+ if (type != null && !Map.class.isAssignableFrom(type)) {
+ throw new IllegalArgumentException(type + " not of type " + Map.class);
+ }
+ }
+
+ public boolean canConvert(Class type) {
+ if (this.type != null) {
+ return type.equals(this.type);
+ }
+ return type.equals(HashMap.class)
+ || type.equals(Hashtable.class)
+ || type.getName().equals("java.util.LinkedHashMap")
+ || type.getName().equals("java.util.concurrent.ConcurrentHashMap")
+ || type.getName().equals("sun.font.AttributeMap") // Used by java.awt.Font in JDK 6
+ ;
+ }
+
+ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+ Map map = (Map) source;
+ String entryName = mapper().serializedClass(Map.Entry.class);
+ for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) {
+ Map.Entry entry = (Map.Entry) iterator.next();
+ ExtendedHierarchicalStreamWriterHelper.startNode(writer, entryName, entry.getClass());
+
+ writeItem(entry.getKey(), context, writer);
+ writeItem(entry.getValue(), context, writer);
+
+ writer.endNode();
+ }
+ }
+
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ Map map = (Map) createCollection(context.getRequiredType());
+ populateMap(reader, context, map);
+ return map;
+ }
+
+ protected void populateMap(HierarchicalStreamReader reader, UnmarshallingContext context, Map map) {
+ populateMap(reader, context, map, map);
+ }
+
+ protected void populateMap(HierarchicalStreamReader reader, UnmarshallingContext context, Map map, Map target) {
+ while (reader.hasMoreChildren()) {
+ reader.moveDown();
+ putCurrentEntryIntoMap(reader, context, map, target);
+ reader.moveUp();
+ }
+ }
+
+ protected void putCurrentEntryIntoMap(HierarchicalStreamReader reader, UnmarshallingContext context,
+ Map map, Map target) {
+ reader.moveDown();
+ Object key = readItem(reader, context, map);
+ reader.moveUp();
+
+ reader.moveDown();
+ Object value = readItem(reader, context, map);
+ reader.moveUp();
+
+ target.put(key, value);
+ }
+
+ protected Object createCollection(Class type) {
+ return super.createCollection(this.type != null ? this.type : type);
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/collections/PropertiesConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/collections/PropertiesConverter.java
new file mode 100644
index 0000000..0f3c8b4
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/collections/PropertiesConverter.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009, 2013 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 23. February 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.collections;
+
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.core.util.Fields;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+
+import java.lang.reflect.Field;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.TreeMap;
+
+
+/**
+ * Special converter for java.util.Properties that stores properties in a more compact form than
+ * java.util.Map.
+ *
+ * Because all entries of a Properties instance are Strings, a single element is used for each
+ * property with two attributes; one for key and one for value.
+ *
+ *
+ * Additionally, default properties are also serialized, if they are present or if a
+ * SecurityManager is set, and it has permissions for SecurityManager.checkPackageAccess,
+ * SecurityManager.checkMemberAccess(this, EnumSet.MEMBER) and
+ * ReflectPermission("suppressAccessChecks").
+ *
+ *
+ * @author Joe Walnes
+ * @author Kevin Ring
+ */
+public class PropertiesConverter implements Converter {
+
+ private final static Field defaultsField = Fields.locate(Properties.class, Properties.class, false);
+ private final boolean sort;
+
+ public PropertiesConverter() {
+ this(false);
+ }
+
+ public PropertiesConverter(boolean sort) {
+ this.sort = sort;
+ }
+
+ public boolean canConvert(Class type) {
+ return Properties.class == type;
+ }
+
+ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+ final Properties properties = (Properties) source;
+ Map map = sort ? (Map)new TreeMap(properties) : (Map)properties;
+ for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) {
+ Map.Entry entry = (Map.Entry) iterator.next();
+ writer.startNode("property");
+ writer.addAttribute("name", entry.getKey().toString());
+ writer.addAttribute("value", entry.getValue().toString());
+ writer.endNode();
+ }
+ if (defaultsField != null) {
+ Properties defaults = (Properties)Fields.read(defaultsField, properties);
+ if (defaults != null) {
+ writer.startNode("defaults");
+ marshal(defaults, writer, context);
+ writer.endNode();
+ }
+ }
+ }
+
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ Properties properties = new Properties();
+ Properties defaults = null;
+ while (reader.hasMoreChildren()) {
+ reader.moveDown();
+ if (reader.getNodeName().equals("defaults")) {
+ defaults = (Properties) unmarshal(reader, context);
+ } else {
+ String name = reader.getAttribute("name");
+ String value = reader.getAttribute("value");
+ properties.setProperty(name, value);
+ }
+ reader.moveUp();
+ }
+ if (defaults == null) {
+ return properties;
+ } else {
+ Properties propertiesWithDefaults = new Properties(defaults);
+ propertiesWithDefaults.putAll(properties);
+ return propertiesWithDefaults;
+ }
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/collections/SingletonCollectionConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/collections/SingletonCollectionConverter.java
new file mode 100644
index 0000000..c56bc90
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/collections/SingletonCollectionConverter.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2011 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 11. October 2011 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.collections;
+
+import java.util.Collections;
+
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+
+/**
+ * Converts singleton collections (list and set) to XML, specifying a nested element for the
+ * item.
+ *
+ * Supports Collections.singleton(Object) and Collections.singletonList(Object).
+ *
+ *
+ * @author Jörg Schaible
+ * @since 1.4.2
+ */
+public class SingletonCollectionConverter extends CollectionConverter {
+
+ private static final Class LIST = Collections.singletonList(Boolean.TRUE).getClass();
+ private static final Class SET = Collections.singleton(Boolean.TRUE).getClass();
+
+ /**
+ * Construct a SingletonCollectionConverter.
+ *
+ * @param mapper the mapper
+ * @since 1.4.2
+ */
+ public SingletonCollectionConverter(Mapper mapper) {
+ super(mapper);
+ }
+
+ public boolean canConvert(Class type) {
+ return LIST == type || SET == type;
+ }
+
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ reader.moveDown();
+ Object item = readItem(reader, context, null);
+ reader.moveUp();
+ return context.getRequiredType() == LIST
+ ? (Object)Collections.singletonList(item)
+ : (Object)Collections.singleton(item);
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/collections/SingletonMapConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/collections/SingletonMapConverter.java
new file mode 100644
index 0000000..5656374
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/collections/SingletonMapConverter.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2011 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 11. October 2011 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.collections;
+
+import java.util.Collections;
+
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+/**
+ * Converts a singleton map to XML, specifying an 'entry'
+ * element with 'key' and 'value' children.
+ *
Note: 'key' and 'value' is not the name of the generated tag. The
+ * children are serialized as normal elements and the implementation expects
+ * them in the order 'key'/'value'.
+ *
Supports Collections.singletonMap.
+ *
+ * @author Jörg Schaible
+ * @since 1.4.2
+ */
+public class SingletonMapConverter extends MapConverter {
+
+ private static final Class MAP = Collections.singletonMap(Boolean.TRUE, null).getClass();
+
+ /**
+ * Construct a SingletonMapConverter.
+ * @param mapper
+ * @since 1.4.2
+ */
+ public SingletonMapConverter(Mapper mapper) {
+ super(mapper);
+ }
+
+ public boolean canConvert(Class type) {
+ return MAP == type;
+ }
+
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ reader.moveDown();
+ reader.moveDown();
+ Object key = readItem(reader, context, null);
+ reader.moveUp();
+
+ reader.moveDown();
+ Object value = readItem(reader, context, null);
+ reader.moveUp();
+ reader.moveUp();
+
+ return Collections.singletonMap(key, value);
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/collections/TreeMapConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/collections/TreeMapConverter.java
new file mode 100644
index 0000000..a79f4f5
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/collections/TreeMapConverter.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2010, 2011, 2013 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 08. May 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.collections;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.core.JVM;
+import com.thoughtworks.xstream.core.util.Fields;
+import com.thoughtworks.xstream.core.util.HierarchicalStreams;
+import com.thoughtworks.xstream.core.util.PresortedMap;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+import java.lang.reflect.Field;
+import java.util.Comparator;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+/**
+ * Converts a java.util.TreeMap to XML, and serializes
+ * the associated java.util.Comparator. The converter
+ * assumes that the entries in the XML are already sorted
+ * according the comparator.
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ */
+public class TreeMapConverter extends MapConverter {
+
+ private static final class NullComparator extends Mapper.Null implements Comparator {
+ public int compare(Object o1, Object o2) {
+ Comparable c1 = (Comparable)o1;
+ Comparable c2 = (Comparable)o2;
+ return c1.compareTo(o2);
+ }
+ }
+
+ private final static Comparator NULL_MARKER = new NullComparator();
+ private final static Field comparatorField = Fields.locate(TreeMap.class, Comparator.class, false);
+
+ public TreeMapConverter(Mapper mapper) {
+ super(mapper, TreeMap.class);
+ }
+
+ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+ SortedMap sortedMap = (SortedMap) source;
+ marshalComparator(sortedMap.comparator(), writer, context);
+ super.marshal(source, writer, context);
+ }
+
+ protected void marshalComparator(Comparator comparator, HierarchicalStreamWriter writer,
+ MarshallingContext context) {
+ if (comparator != null) {
+ writer.startNode("comparator");
+ writer.addAttribute(mapper().aliasForSystemAttribute("class"),
+ mapper().serializedClass(comparator.getClass()));
+ context.convertAnother(comparator);
+ writer.endNode();
+ }
+ }
+
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ TreeMap result = comparatorField != null ? new TreeMap() : null;
+ final Comparator comparator = unmarshalComparator(reader, context, result);
+ if (result == null) {
+ result = comparator == null ? new TreeMap() : new TreeMap(comparator);
+ }
+ populateTreeMap(reader, context, result, comparator);
+ return result;
+ }
+
+ protected Comparator unmarshalComparator(HierarchicalStreamReader reader,
+ UnmarshallingContext context, TreeMap result) {
+ final Comparator comparator;
+ if (reader.hasMoreChildren()) {
+ reader.moveDown();
+ if (reader.getNodeName().equals("comparator")) {
+ Class comparatorClass = HierarchicalStreams.readClassType(reader, mapper());
+ comparator = (Comparator) context.convertAnother(result, comparatorClass);
+ } else if (reader.getNodeName().equals("no-comparator")) { // pre 1.4 format
+ comparator = null;
+ } else {
+ // we are already within the first entry
+ return NULL_MARKER;
+ }
+ reader.moveUp();
+ } else {
+ comparator = null;
+ }
+ return comparator;
+ }
+
+ protected void populateTreeMap(HierarchicalStreamReader reader, UnmarshallingContext context,
+ TreeMap result, Comparator comparator) {
+ boolean inFirstElement = comparator == NULL_MARKER;
+ if (inFirstElement) {
+ comparator = null;
+ }
+ SortedMap sortedMap = new PresortedMap(comparator != null && JVM.hasOptimizedTreeMapPutAll() ? comparator : null);
+ if (inFirstElement) {
+ // we are already within the first entry
+ putCurrentEntryIntoMap(reader, context, result, sortedMap);
+ reader.moveUp();
+ }
+ populateMap(reader, context, result, sortedMap);
+ try {
+ if (JVM.hasOptimizedTreeMapPutAll()) {
+ if (comparator != null && comparatorField != null) {
+ comparatorField.set(result, comparator);
+ }
+ result.putAll(sortedMap); // internal optimization will not call comparator
+ } else if (comparatorField != null) {
+ comparatorField.set(result, sortedMap.comparator());
+ result.putAll(sortedMap); // "sort" by index
+ comparatorField.set(result, comparator);
+ } else {
+ result.putAll(sortedMap); // will use comparator for already sorted map
+ }
+ } catch (final IllegalAccessException e) {
+ throw new ConversionException("Cannot set comparator of TreeMap", e);
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/collections/TreeSetConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/collections/TreeSetConverter.java
new file mode 100644
index 0000000..7e9e630
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/collections/TreeSetConverter.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2010, 2011, 2013, 2014 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 08. May 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.collections;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.core.JVM;
+import com.thoughtworks.xstream.core.util.Fields;
+import com.thoughtworks.xstream.core.util.PresortedSet;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+import java.lang.reflect.Field;
+import java.util.AbstractList;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+/**
+ * Converts a java.util.TreeSet to XML, and serializes
+ * the associated java.util.Comparator. The converter
+ * assumes that the elements in the XML are already sorted
+ * according the comparator.
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ */
+public class TreeSetConverter extends CollectionConverter {
+ private transient TreeMapConverter treeMapConverter;
+ private final static Field sortedMapField;
+ private final static Object constantValue;
+ static {
+ Object value = null;
+ sortedMapField = JVM.hasOptimizedTreeSetAddAll() ? Fields.locate(TreeSet.class, SortedMap.class, false) : null;
+ if (sortedMapField != null) {
+ TreeSet set = new TreeSet();
+ set.add("1");
+ set.add("2");
+
+ Map backingMap = null;
+ try {
+ backingMap = (Map)sortedMapField.get(set);
+ } catch (final IllegalAccessException e) {
+ // give up;
+ }
+ if (backingMap != null) {
+ Object[] values = backingMap.values().toArray();
+ if (values[0] == values[1]) {
+ value = values[0];
+ }
+ }
+ } else {
+ Field valueField = Fields.locate(TreeSet.class, Object.class, true);
+ if (valueField != null) {
+ try {
+ value = valueField.get(null);
+ } catch (final IllegalAccessException e) {
+ // give up;
+ }
+ }
+ }
+ constantValue = value;
+ }
+
+ public TreeSetConverter(Mapper mapper) {
+ super(mapper, TreeSet.class);
+ readResolve();
+ }
+
+ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+ SortedSet sortedSet = (SortedSet) source;
+ treeMapConverter.marshalComparator(sortedSet.comparator(), writer, context);
+ super.marshal(source, writer, context);
+ }
+
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ TreeSet result = null;
+ final TreeMap treeMap;
+ Comparator unmarshalledComparator = treeMapConverter.unmarshalComparator(reader, context, null);
+ boolean inFirstElement = unmarshalledComparator instanceof Mapper.Null;
+ Comparator comparator = inFirstElement ? null : unmarshalledComparator;
+ if (sortedMapField != null) {
+ TreeSet possibleResult = comparator == null ? new TreeSet() : new TreeSet(comparator);
+ Object backingMap = null;
+ try {
+ backingMap = sortedMapField.get(possibleResult);
+ } catch (IllegalAccessException e) {
+ throw new ConversionException("Cannot get backing map of TreeSet", e);
+ }
+ if (backingMap instanceof TreeMap) {
+ treeMap = (TreeMap)backingMap;
+ result = possibleResult;
+ } else {
+ treeMap = null;
+ }
+ } else {
+ treeMap = null;
+ }
+ if (treeMap == null) {
+ final PresortedSet set = new PresortedSet(comparator);
+ result = comparator == null ? new TreeSet() : new TreeSet(comparator);
+ if (inFirstElement) {
+ // we are already within the first element
+ addCurrentElementToCollection(reader, context, result, set);
+ reader.moveUp();
+ }
+ populateCollection(reader, context, result, set);
+ if (set.size() > 0) {
+ result.addAll(set); // comparator will not be called if internally optimized
+ }
+ } else {
+ treeMapConverter.populateTreeMap(reader, context, treeMap, unmarshalledComparator);
+ }
+ return result;
+ }
+
+ private Object readResolve() {
+ treeMapConverter = new TreeMapConverter(mapper()) {
+
+ protected void populateMap(HierarchicalStreamReader reader,
+ UnmarshallingContext context, Map map, final Map target) {
+ populateCollection(reader, context, new AbstractList() {
+ public boolean add(Object object) {
+ return target.put(object, constantValue != null ? constantValue : object) != null;
+ }
+
+ public Object get(int location) {
+ return null;
+ }
+
+ public int size() {
+ return target.size();
+ }
+ });
+ }
+
+ protected void putCurrentEntryIntoMap(HierarchicalStreamReader reader, UnmarshallingContext context,
+ Map map, Map target) {
+ Object key = readItem(reader, context, map);
+ target.put(key, key);
+ }
+ };
+ return this;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/collections/package.html b/xstream/src/java/com/thoughtworks/xstream/converters/collections/package.html
new file mode 100644
index 0000000..9d59acd
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/collections/package.html
@@ -0,0 +1,17 @@
+
+
+
Converters for collection objects that write their items as
+nested elements, such as arrays, Lists, Sets and Maps.
+
+
All the converters in this package are enabled by default.
+
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/enums/EnumConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/enums/EnumConverter.java
new file mode 100644
index 0000000..4b4a2ba
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/enums/EnumConverter.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2013 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 18. March 2005 by Joe Walnes
+ */
+
+// ***** READ THIS *****
+// This class will only compile with JDK 1.5.0 or above as it test Java enums.
+// If you are using an earlier version of Java, just don't try to build this class. XStream should work fine without it.
+
+package com.thoughtworks.xstream.converters.enums;
+
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+
+/**
+ * Converter for JDK 1.5 enums. Combined with EnumMapper this also deals with polymorphic enums.
+ *
+ * @author Eric Snell
+ * @author Bryan Coleman
+ */
+public class EnumConverter implements Converter {
+
+ public boolean canConvert(Class type) {
+ return type.isEnum() || Enum.class.isAssignableFrom(type);
+ }
+
+ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+ writer.setValue(((Enum) source).name());
+ }
+
+ @SuppressWarnings("unchecked")
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ Class type = context.getRequiredType();
+ if (type.getSuperclass() != Enum.class) {
+ type = type.getSuperclass(); // polymorphic enums
+ }
+ String name = reader.getValue();
+ try {
+ return Enum.valueOf(type, name);
+ } catch (IllegalArgumentException e) {
+ // failed to find it, do a case insensitive match
+ for (Enum c : (Enum[])type.getEnumConstants())
+ if (c.name().equalsIgnoreCase(name))
+ return c;
+ // all else failed
+ throw e;
+ }
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/enums/EnumMapConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/enums/EnumMapConverter.java
new file mode 100644
index 0000000..aaed8a3
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/enums/EnumMapConverter.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009, 2013 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 06. April 2005 by Joe Walnes
+ */
+
+// ***** READ THIS *****
+// This class will only compile with JDK 1.5.0 or above as it test Java enums.
+// If you are using an earlier version of Java, just don't try to build this class. XStream should work fine without it.
+
+package com.thoughtworks.xstream.converters.enums;
+
+import com.thoughtworks.xstream.converters.collections.MapConverter;
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.mapper.Mapper;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.core.util.Fields;
+
+import java.util.EnumMap;
+import java.lang.reflect.Field;
+
+/**
+ * Serializes an Java 5 EnumMap, including the type of Enum it's for. If a SecurityManager is set, the converter will only work with permissions
+ * for SecurityManager.checkPackageAccess, SecurityManager.checkMemberAccess(this, EnumSet.MEMBER)
+ * and ReflectPermission("suppressAccessChecks").
+ *
+ * @author Joe Walnes
+ */
+public class EnumMapConverter extends MapConverter {
+
+ private final static Field typeField = Fields.locate(EnumMap.class, Class.class, false);
+
+ public EnumMapConverter(Mapper mapper) {
+ super(mapper);
+ }
+
+ public boolean canConvert(Class type) {
+ return typeField != null && type == EnumMap.class;
+ }
+
+ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+ Class type = (Class) Fields.read(typeField, source);
+ String attributeName = mapper().aliasForSystemAttribute("enum-type");
+ if (attributeName != null) {
+ writer.addAttribute(attributeName, mapper().serializedClass(type));
+ }
+ super.marshal(source, writer, context);
+ }
+
+ @SuppressWarnings("unchecked")
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ String attributeName = mapper().aliasForSystemAttribute("enum-type");
+ if (attributeName == null) {
+ throw new ConversionException("No EnumType specified for EnumMap");
+ }
+ Class type = mapper().realClass(reader.getAttribute(attributeName));
+ EnumMap map = new EnumMap(type);
+ populateMap(reader, context, map);
+ return map;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/enums/EnumSetConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/enums/EnumSetConverter.java
new file mode 100644
index 0000000..2d39908
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/enums/EnumSetConverter.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009 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 06. April 2005 by Joe Walnes
+ */
+
+// ***** READ THIS *****
+// This class will only compile with JDK 1.5.0 or above as it test Java enums.
+// If you are using an earlier version of Java, just don't try to build this class. XStream should work fine without it.
+
+package com.thoughtworks.xstream.converters.enums;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.mapper.Mapper;
+import com.thoughtworks.xstream.core.util.Fields;
+
+import java.lang.reflect.Field;
+import java.util.EnumSet;
+import java.util.Iterator;
+
+/**
+ * Serializes a Java 5 EnumSet. If a SecurityManager is set, the converter will only work with permissions
+ * for SecurityManager.checkPackageAccess, SecurityManager.checkMemberAccess(this, EnumSet.MEMBER)
+ * and ReflectPermission("suppressAccessChecks").
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ */
+public class EnumSetConverter implements Converter {
+
+ private final static Field typeField = Fields.locate(EnumSet.class, Class.class, false);
+ private final Mapper mapper;
+
+ public EnumSetConverter(Mapper mapper) {
+ this.mapper = mapper;
+ }
+
+ public boolean canConvert(Class type) {
+ return typeField != null && EnumSet.class.isAssignableFrom(type);
+ }
+
+ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+ EnumSet set = (EnumSet) source;
+ Class enumTypeForSet = (Class) Fields.read(typeField, set);
+ String attributeName = mapper.aliasForSystemAttribute("enum-type");
+ if (attributeName != null) {
+ writer.addAttribute(attributeName, mapper.serializedClass(enumTypeForSet));
+ }
+ writer.setValue(joinEnumValues(set));
+ }
+
+ private String joinEnumValues(EnumSet set) {
+ boolean seenFirst = false;
+ StringBuffer result = new StringBuffer();
+ for (Iterator iterator = set.iterator(); iterator.hasNext();) {
+ Enum value = (Enum) iterator.next();
+ if (seenFirst) {
+ result.append(',');
+ } else {
+ seenFirst = true;
+ }
+ result.append(value.name());
+ }
+ return result.toString();
+ }
+
+ @SuppressWarnings("unchecked")
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ String attributeName = mapper.aliasForSystemAttribute("enum-type");
+ if (attributeName == null) {
+ throw new ConversionException("No EnumType specified for EnumSet");
+ }
+ Class enumTypeForSet = mapper.realClass(reader.getAttribute(attributeName));
+ EnumSet set = EnumSet.noneOf(enumTypeForSet);
+ String[] enumValues = reader.getValue().split(",");
+ for (int i = 0; i < enumValues.length; i++) {
+ String enumValue = enumValues[i];
+ if(enumValue.length() > 0) {
+ set.add(Enum.valueOf(enumTypeForSet, enumValue));
+ }
+ }
+ return set;
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/enums/EnumSingleValueConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/enums/EnumSingleValueConverter.java
new file mode 100644
index 0000000..eb668a2
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/enums/EnumSingleValueConverter.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2008, 2009, 2010, 2013 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 12. February 2008 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.enums;
+
+import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter;
+
+
+/**
+ * A single value converter for a special enum type. Converter is internally automatically
+ * instantiated for enum types.
+ *
+ * @author Jörg Schaible
+ * @since 1.3
+ */
+public class EnumSingleValueConverter extends AbstractSingleValueConverter {
+
+ private final Class extends Enum> enumType;
+
+ public EnumSingleValueConverter(Class extends Enum> type) {
+ if (!Enum.class.isAssignableFrom(type) && type != Enum.class) {
+ throw new IllegalArgumentException("Converter can only handle defined enums");
+ }
+ enumType = type;
+ }
+
+ @Override
+ public boolean canConvert(Class type) {
+ return enumType.isAssignableFrom(type);
+ }
+
+ @Override
+ public String toString(Object obj) {
+ return Enum.class.cast(obj).name();
+ }
+
+ @Override
+ public Object fromString(String str) {
+ @SuppressWarnings("unchecked")
+ Enum result = Enum.valueOf(enumType, str);
+ return result;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/enums/EnumToStringConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/enums/EnumToStringConverter.java
new file mode 100644
index 0000000..90fea84
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/enums/EnumToStringConverter.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2013 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 14. March 2013 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.enums;
+
+import java.util.EnumMap;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter;
+
+
+/**
+ * A single value converter for a special enum type using its string representation.
+ *
+ * @author Jörg Schaible
+ * @since 1.4.5
+ */
+public class EnumToStringConverter> extends AbstractSingleValueConverter {
+
+ private final Class enumType;
+ private final Map strings;
+ private final EnumMap values;
+
+ public EnumToStringConverter(Class type) {
+ this(type, extractStringMap(type), null);
+ }
+
+ public EnumToStringConverter(Class type, Map strings) {
+ this(type, strings, buildValueMap(type, strings));
+ }
+
+ private EnumToStringConverter(
+ Class type, Map strings, EnumMap values) {
+ enumType = type;
+ this.strings = strings;
+ this.values = values;
+ }
+
+ private static > Map extractStringMap(Class type) {
+ checkType(type);
+ EnumSet values = EnumSet.allOf(type);
+ Map strings = new HashMap(values.size());
+ for (T value : values) {
+ if (strings.put(value.toString(), value) != null) {
+ throw new IllegalArgumentException("Enum type "
+ + type.getName()
+ + " does not have unique string representations for its values");
+ }
+ }
+ return strings;
+ }
+
+ private static void checkType(Class type) {
+ if (!Enum.class.isAssignableFrom(type) && type != Enum.class) {
+ throw new IllegalArgumentException("Converter can only handle enum types");
+ }
+ }
+
+ private static > EnumMap buildValueMap(Class type,
+ Map strings) {
+ EnumMap values = new EnumMap(type);
+ for (Map.Entry entry : strings.entrySet()) {
+ values.put(entry.getValue(), entry.getKey());
+ }
+ return values;
+ }
+
+ @Override
+ public boolean canConvert(Class type) {
+ return enumType.isAssignableFrom(type);
+ }
+
+ @Override
+ public String toString(Object obj) {
+ Enum value = Enum.class.cast(obj);
+ return values == null ? value.toString() : values.get(value);
+ }
+
+ @Override
+ public Object fromString(String str) {
+ if (str == null) {
+ return null;
+ }
+ T result = strings.get(str);
+ if (result == null) {
+ throw new ConversionException("Invalid string representation for enum type "
+ + enumType.getName()
+ + ": <"
+ + str
+ + ">");
+ }
+ return result;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/CharsetConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/CharsetConverter.java
new file mode 100644
index 0000000..ca90bd1
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/CharsetConverter.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2006, 2007 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 07. April 2006 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter;
+
+import java.nio.charset.Charset;
+
+/**
+ * Converts a java.nio.charset.Carset to a string.
+ *
+ * @author Jörg Schaible
+ * @since 1.2
+ */
+public class CharsetConverter extends AbstractSingleValueConverter {
+
+ public boolean canConvert(Class type) {
+ return Charset.class.isAssignableFrom(type);
+ }
+
+ public String toString(Object obj) {
+ return obj == null ? null : ((Charset)obj).name();
+ }
+
+
+ public Object fromString(String str) {
+ return Charset.forName(str);
+ }
+}
\ No newline at end of file
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/ColorConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/ColorConverter.java
new file mode 100644
index 0000000..f7de926
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/ColorConverter.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2003, 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 01. October 2003 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+
+import java.awt.Color;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Converts a java.awt.Color to XML, using four nested elements:
+ * red, green, blue, alpha.
+ *
+ * @author Joe Walnes
+ */
+public class ColorConverter implements Converter {
+
+ public boolean canConvert(Class type) {
+ // String comparison is used here because Color.class loads the class which in turns instantiates AWT,
+ // which is nasty if you don't want it.
+ return type.getName().equals("java.awt.Color");
+ }
+
+ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+ Color color = (Color) source;
+ write("red", color.getRed(), writer);
+ write("green", color.getGreen(), writer);
+ write("blue", color.getBlue(), writer);
+ write("alpha", color.getAlpha(), writer);
+ }
+
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ Map elements = new HashMap();
+ while (reader.hasMoreChildren()) {
+ reader.moveDown();
+ elements.put(reader.getNodeName(), Integer.valueOf(reader.getValue()));
+ reader.moveUp();
+ }
+ return new Color(((Integer) elements.get("red")).intValue(),
+ ((Integer) elements.get("green")).intValue(),
+ ((Integer) elements.get("blue")).intValue(),
+ ((Integer) elements.get("alpha")).intValue());
+ }
+
+ private void write(String fieldName, int value, HierarchicalStreamWriter writer) {
+ ExtendedHierarchicalStreamWriterHelper.startNode(writer, fieldName, int.class);
+ writer.setValue(String.valueOf(value));
+ writer.endNode();
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/CurrencyConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/CurrencyConverter.java
new file mode 100644
index 0000000..1609f49
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/CurrencyConverter.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 24. July 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import java.util.Currency;
+
+import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter;
+
+/**
+ * Converts a java.util.Currency to String. Despite the name of this class, it has nothing to do with converting
+ * currencies between exchange rates! It makes sense in the context of XStream.
+ *
+ * @author Jose A. Illescas
+ * @author Joe Walnes
+ */
+public class CurrencyConverter extends AbstractSingleValueConverter {
+
+ public boolean canConvert(Class type) {
+ return type.equals(Currency.class);
+ }
+
+ public Object fromString(String str) {
+ return Currency.getInstance(str);
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/DurationConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/DurationConverter.java
new file mode 100644
index 0000000..e7f906d
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/DurationConverter.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2007, 2008, 2011 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 21.09.2007 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter;
+
+import javax.xml.datatype.DatatypeConfigurationException;
+import javax.xml.datatype.DatatypeFactory;
+import javax.xml.datatype.Duration;
+
+
+/**
+ * A Converter for the XML Schema datatype duration and the Java type
+ * {@link Duration}. The implementation uses a {@link DatatypeFactory} to create Duration
+ * objects. If no factory is provided and the instantiation of the internal factory fails with a
+ * {@link DatatypeConfigurationException}, the converter will not claim the responsibility for
+ * Duration objects.
+ *
+ * @author John Kristian
+ * @author Jörg Schaible
+ * @since 1.3
+ */
+public class DurationConverter extends AbstractSingleValueConverter {
+ private final DatatypeFactory factory;
+
+ public DurationConverter() {
+ this(new Object() {
+ DatatypeFactory getFactory() {
+ try {
+ return DatatypeFactory.newInstance();
+ } catch (final DatatypeConfigurationException e) {
+ return null;
+ }
+ }
+ }.getFactory());
+ }
+
+ public DurationConverter(DatatypeFactory factory) {
+ this.factory = factory;
+ }
+
+ public boolean canConvert(Class c) {
+ return factory != null && Duration.class.isAssignableFrom(c);
+ }
+
+ public Object fromString(String s) {
+ return factory.newDuration(s);
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/DynamicProxyConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/DynamicProxyConverter.java
new file mode 100644
index 0000000..9700c9a
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/DynamicProxyConverter.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2010, 2013 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 25. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.core.ClassLoaderReference;
+import com.thoughtworks.xstream.core.util.Fields;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.mapper.DynamicProxyMapper;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Converts a dynamic proxy to XML, storing the implemented
+ * interfaces and handler.
+ *
+ * @author Joe Walnes
+ */
+public class DynamicProxyConverter implements Converter {
+
+ private ClassLoaderReference classLoaderReference;
+ private Mapper mapper;
+ private static final Field HANDLER = Fields.locate(Proxy.class, InvocationHandler.class, false);
+ private static final InvocationHandler DUMMY = new InvocationHandler() {
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ return null;
+ }
+ };
+
+ /**
+ * @deprecated As of 1.4.5 use {@link #DynamicProxyConverter(Mapper, ClassLoaderReference)}
+ */
+ public DynamicProxyConverter(Mapper mapper) {
+ this(mapper, DynamicProxyConverter.class.getClassLoader());
+ }
+
+ /**
+ * Construct a DynamicProxyConverter.
+ * @param mapper the Mapper chain
+ * @param classLoaderReference the reference to the {@link ClassLoader} of the XStream instance
+ * @since 1.4.5
+ */
+ public DynamicProxyConverter(Mapper mapper, ClassLoaderReference classLoaderReference) {
+ this.classLoaderReference = classLoaderReference;
+ this.mapper = mapper;
+ }
+
+ /**
+ * @deprecated As of 1.4.5 use {@link #DynamicProxyConverter(Mapper, ClassLoaderReference)}
+ */
+ public DynamicProxyConverter(Mapper mapper, ClassLoader classLoader) {
+ this(mapper,new ClassLoaderReference(classLoader));
+ }
+
+ public boolean canConvert(Class type) {
+ return type.equals(DynamicProxyMapper.DynamicProxy.class) || Proxy.isProxyClass(type);
+ }
+
+ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+ InvocationHandler invocationHandler = Proxy.getInvocationHandler(source);
+ addInterfacesToXml(source, writer);
+ writer.startNode("handler");
+ String attributeName = mapper.aliasForSystemAttribute("class");
+ if (attributeName != null) {
+ writer.addAttribute(attributeName, mapper.serializedClass(invocationHandler.getClass()));
+ }
+ context.convertAnother(invocationHandler);
+ writer.endNode();
+ }
+
+ private void addInterfacesToXml(Object source, HierarchicalStreamWriter writer) {
+ Class[] interfaces = source.getClass().getInterfaces();
+ for (int i = 0; i < interfaces.length; i++) {
+ Class currentInterface = interfaces[i];
+ writer.startNode("interface");
+ writer.setValue(mapper.serializedClass(currentInterface));
+ writer.endNode();
+ }
+ }
+
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ List interfaces = new ArrayList();
+ InvocationHandler handler = null;
+ Class handlerType = null;
+ while (reader.hasMoreChildren()) {
+ reader.moveDown();
+ String elementName = reader.getNodeName();
+ if (elementName.equals("interface")) {
+ interfaces.add(mapper.realClass(reader.getValue()));
+ } else if (elementName.equals("handler")) {
+ String attributeName = mapper.aliasForSystemAttribute("class");
+ if (attributeName != null) {
+ handlerType = mapper.realClass(reader.getAttribute(attributeName));
+ break;
+ }
+ }
+ reader.moveUp();
+ }
+ if (handlerType == null) {
+ throw new ConversionException("No InvocationHandler specified for dynamic proxy");
+ }
+ Class[] interfacesAsArray = new Class[interfaces.size()];
+ interfaces.toArray(interfacesAsArray);
+ Object proxy = null;
+ if (HANDLER != null) { // we will not be able to resolve references to the proxy
+ proxy = Proxy.newProxyInstance(classLoaderReference.getReference(), interfacesAsArray, DUMMY);
+ }
+ handler = (InvocationHandler) context.convertAnother(proxy, handlerType);
+ reader.moveUp();
+ if (HANDLER != null) {
+ Fields.write(HANDLER, proxy, handler);
+ } else {
+ proxy = Proxy.newProxyInstance(classLoaderReference.getReference(), interfacesAsArray, handler);
+ }
+ return proxy;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/EncodedByteArrayConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/EncodedByteArrayConverter.java
new file mode 100644
index 0000000..6eb815d
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/EncodedByteArrayConverter.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2010 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 03. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.SingleValueConverter;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.converters.basic.ByteConverter;
+import com.thoughtworks.xstream.core.util.Base64Encoder;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Converts a byte array to a single Base64 encoding string.
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ */
+public class EncodedByteArrayConverter implements Converter, SingleValueConverter {
+
+ private static final Base64Encoder base64 = new Base64Encoder();
+ private static final ByteConverter byteConverter = new ByteConverter();
+
+ public boolean canConvert(Class type) {
+ return type.isArray() && type.getComponentType().equals(byte.class);
+ }
+
+ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+ writer.setValue(toString(source));
+ }
+
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ String data = reader.getValue(); // needs to be called before hasMoreChildren.
+ if (!reader.hasMoreChildren()) {
+ return fromString(data);
+ } else {
+ // backwards compatibility ... try to unmarshal byte arrays that haven't been encoded
+ return unmarshalIndividualByteElements(reader, context);
+ }
+ }
+
+ private Object unmarshalIndividualByteElements(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ List bytes = new ArrayList(); // have to create a temporary list because don't know the size of the array
+ boolean firstIteration = true;
+ while (firstIteration || reader.hasMoreChildren()) { // hangover from previous hasMoreChildren
+ reader.moveDown();
+ bytes.add(byteConverter.fromString(reader.getValue()));
+ reader.moveUp();
+ firstIteration = false;
+ }
+ // copy into real array
+ byte[] result = new byte[bytes.size()];
+ int i = 0;
+ for (Iterator iterator = bytes.iterator(); iterator.hasNext();) {
+ Byte b = (Byte) iterator.next();
+ result[i] = b.byteValue();
+ i++;
+ }
+ return result;
+ }
+
+ public String toString(Object obj) {
+ return base64.encode((byte[]) obj);
+ }
+
+ public Object fromString(String str) {
+ return base64.decode(str);
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/FileConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/FileConverter.java
new file mode 100644
index 0000000..6003465
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/FileConverter.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 13. January 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter;
+
+import java.io.File;
+
+/**
+ * This converter will take care of storing and retrieving File with either
+ * an absolute path OR a relative path depending on how they were created.
+ *
+ * @author Joe Walnes
+ */
+public class FileConverter extends AbstractSingleValueConverter {
+
+ public boolean canConvert(Class type) {
+ return type.equals(File.class);
+ }
+
+ public Object fromString(String str) {
+ return new File(str);
+ }
+
+ public String toString(Object obj) {
+ return ((File) obj).getPath();
+ }
+
+}
\ No newline at end of file
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/FontConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/FontConverter.java
new file mode 100644
index 0000000..c1703d8
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/FontConverter.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2013 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 08. July 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.SingleValueConverter;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.core.JVM;
+import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+import javax.swing.plaf.FontUIResource;
+
+import java.awt.Font;
+import java.awt.font.TextAttribute;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+public class FontConverter implements Converter {
+
+ private final SingleValueConverter textAttributeConverter;
+ private final Mapper mapper;
+
+ /**
+ * Constructs a FontConverter.
+ * @deprecated As of 1.4.5
+ */
+ public FontConverter() {
+ this(null);
+ }
+
+ /**
+ * Constructs a FontConverter.
+ * @param mapper
+ * @since 1.4.5
+ */
+ public FontConverter(Mapper mapper) {
+ this.mapper = mapper;
+ if (mapper == null) {
+ textAttributeConverter = null;
+ } else {
+ textAttributeConverter = new TextAttributeConverter();
+ }
+ }
+
+ public boolean canConvert(Class type) {
+ // String comparison is used here because Font.class loads the class which in turns instantiates AWT,
+ // which is nasty if you don't want it.
+ return type.getName().equals("java.awt.Font") || type.getName().equals("javax.swing.plaf.FontUIResource");
+ }
+
+ public void marshal(Object source, HierarchicalStreamWriter writer,
+ MarshallingContext context) {
+ Font font = (Font)source;
+ Map attributes = font.getAttributes();
+ if (mapper != null) {
+ String classAlias = mapper.aliasForSystemAttribute("class");
+ for (Iterator iter = attributes.entrySet().iterator(); iter.hasNext();) {
+ Map.Entry entry = (Map.Entry)iter.next();
+ String name = textAttributeConverter.toString(entry.getKey());
+ Object value = entry.getValue();
+ Class type = value != null ? value.getClass() : Mapper.Null.class;
+ ExtendedHierarchicalStreamWriterHelper.startNode(writer, name, type);
+ writer.addAttribute(classAlias, mapper.serializedClass(type));
+ if (value != null) {
+ context.convertAnother(value);
+ }
+ writer.endNode();
+ }
+ } else {
+ writer.startNode("attributes"); //
+ context.convertAnother(attributes);
+ writer.endNode(); //
+ }
+ }
+
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ final Map attributes;
+ if (reader.hasMoreChildren()) {
+ reader.moveDown();
+ if (!reader.getNodeName().equals("attributes")) {
+ String classAlias = mapper.aliasForSystemAttribute("class");
+ attributes = new HashMap();
+ do {
+ if (!attributes.isEmpty()) {
+ reader.moveDown();
+ }
+ Class type = mapper.realClass(reader.getAttribute(classAlias));
+ TextAttribute attribute = (TextAttribute)textAttributeConverter.fromString(reader.getNodeName());
+ Object value = type == Mapper.Null.class ? null : context.convertAnother(null, type);
+ attributes.put(attribute, value);
+ reader.moveUp();
+ } while(reader.hasMoreChildren());
+ } else {
+ // in
+ attributes = (Map)context.convertAnother(null, Map.class);
+ reader.moveUp(); // out of
+ }
+ } else {
+ attributes = Collections.EMPTY_MAP;
+ }
+ if (!JVM.is16()) {
+ for (Iterator iter = attributes.values().iterator(); iter.hasNext(); ) {
+ if (iter.next() == null) {
+ iter.remove();
+ }
+ }
+ }
+ Font font = Font.getFont(attributes);
+ if (context.getRequiredType() == FontUIResource.class) {
+ return new FontUIResource(font);
+ } else {
+ return font;
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/GregorianCalendarConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/GregorianCalendarConverter.java
new file mode 100644
index 0000000..f8ed612
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/GregorianCalendarConverter.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2004, 2005 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 24. July 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+/**
+ * Converts a java.util.GregorianCalendar to XML. Note that although it currently only contains one field, it nests
+ * it inside a child element, to allow for other fields to be stored in the future.
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ */
+public class GregorianCalendarConverter implements Converter {
+
+ public boolean canConvert(Class type) {
+ return type.equals(GregorianCalendar.class);
+ }
+
+ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+ GregorianCalendar calendar = (GregorianCalendar) source;
+ ExtendedHierarchicalStreamWriterHelper.startNode(writer, "time", long.class);
+ long timeInMillis = calendar.getTime().getTime(); // calendar.getTimeInMillis() not available under JDK 1.3
+ writer.setValue(String.valueOf(timeInMillis));
+ writer.endNode();
+ ExtendedHierarchicalStreamWriterHelper.startNode(writer, "timezone", String.class);
+ writer.setValue(calendar.getTimeZone().getID());
+ writer.endNode();
+ }
+
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ reader.moveDown();
+ long timeInMillis = Long.parseLong(reader.getValue());
+ reader.moveUp();
+ final String timeZone;
+ if (reader.hasMoreChildren()) {
+ reader.moveDown();
+ timeZone = reader.getValue();
+ reader.moveUp();
+ } else { // backward compatibility to XStream 1.1.2 and below
+ timeZone = TimeZone.getDefault().getID();
+ }
+
+ GregorianCalendar result = new GregorianCalendar();
+ result.setTimeZone(TimeZone.getTimeZone(timeZone));
+ result.setTime(new Date(timeInMillis)); // calendar.setTimeInMillis() not available under JDK 1.3
+
+ return result;
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/ISO8601DateConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/ISO8601DateConverter.java
new file mode 100644
index 0000000..67d03e2
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/ISO8601DateConverter.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 22. November 2004 by Mauro Talevi
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import java.util.Calendar;
+import java.util.Date;
+
+
+/**
+ * A DateConverter conforming to the ISO8601 standard.
+ * http://www.iso.ch/iso/en/CatalogueDetailPage.CatalogueDetail?CSNUMBER=26780
+ *
+ * @author Mauro Talevi
+ * @author Jörg Schaible
+ */
+public class ISO8601DateConverter extends ISO8601GregorianCalendarConverter {
+
+ public boolean canConvert(Class type) {
+ return type.equals(Date.class);
+ }
+
+ public Object fromString(String str) {
+ return ((Calendar)super.fromString(str)).getTime();
+ }
+
+ public String toString(Object obj) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime((Date)obj);
+ return super.toString(calendar);
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/ISO8601GregorianCalendarConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/ISO8601GregorianCalendarConverter.java
new file mode 100644
index 0000000..1d0da42
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/ISO8601GregorianCalendarConverter.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2011, 2013, 2014, 2015 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 03. October 2005 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.format.DateTimeFormatter;
+import org.joda.time.format.ISODateTimeFormat;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+
+/**
+ * A GregorianCalendarConverter conforming to the ISO8601 standard. The converter will always
+ * serialize the calendar value in UTC and deserialize it to a value in the current default time
+ * zone.
+ *
+ * @author Mauro Talevi
+ * @author Jörg Schaible
+ * @see ISO 8601
+ * @since 1.1.3
+ */
+public class ISO8601GregorianCalendarConverter extends AbstractSingleValueConverter {
+ private static final DateTimeFormatter[] formattersUTC = new DateTimeFormatter[]{
+ ISODateTimeFormat.dateTime(),
+ ISODateTimeFormat.dateTimeNoMillis(),
+ ISODateTimeFormat.basicDateTime(),
+ ISODateTimeFormat.basicOrdinalDateTime(),
+ ISODateTimeFormat.basicOrdinalDateTimeNoMillis(),
+ ISODateTimeFormat.basicTime(),
+ ISODateTimeFormat.basicTimeNoMillis(),
+ ISODateTimeFormat.basicTTime(),
+ ISODateTimeFormat.basicTTimeNoMillis(),
+ ISODateTimeFormat.basicWeekDateTime(),
+ ISODateTimeFormat.basicWeekDateTimeNoMillis(),
+ ISODateTimeFormat.ordinalDateTime(),
+ ISODateTimeFormat.ordinalDateTimeNoMillis(),
+ ISODateTimeFormat.time(),
+ ISODateTimeFormat.timeNoMillis(),
+ ISODateTimeFormat.tTime(),
+ ISODateTimeFormat.tTimeNoMillis(),
+ ISODateTimeFormat.weekDateTime(),
+ ISODateTimeFormat.weekDateTimeNoMillis()
+ };
+ private static final DateTimeFormatter[] formattersNoUTC = new DateTimeFormatter[]{
+ ISODateTimeFormat.basicDate(),
+ ISODateTimeFormat.basicOrdinalDate(),
+ ISODateTimeFormat.basicWeekDate(),
+ ISODateTimeFormat.date(),
+ ISODateTimeFormat.dateHour(),
+ ISODateTimeFormat.dateHourMinute(),
+ ISODateTimeFormat.dateHourMinuteSecond(),
+ ISODateTimeFormat.dateHourMinuteSecondFraction(),
+ ISODateTimeFormat.dateHourMinuteSecondMillis(),
+ ISODateTimeFormat.hour(),
+ ISODateTimeFormat.hourMinute(),
+ ISODateTimeFormat.hourMinuteSecond(),
+ ISODateTimeFormat.hourMinuteSecondFraction(),
+ ISODateTimeFormat.hourMinuteSecondMillis(),
+ ISODateTimeFormat.ordinalDate(),
+ ISODateTimeFormat.weekDate(),
+ ISODateTimeFormat.year(),
+ ISODateTimeFormat.yearMonth(),
+ ISODateTimeFormat.yearMonthDay(),
+ ISODateTimeFormat.weekyear(),
+ ISODateTimeFormat.weekyearWeek(),
+ ISODateTimeFormat.weekyearWeekDay()
+ };
+
+ public boolean canConvert(Class type) {
+ return type.equals(GregorianCalendar.class);
+ }
+
+ public Object fromString(String str) {
+ for (int i = 0; i < formattersUTC.length; i++ ) {
+ DateTimeFormatter formatter = formattersUTC[i];
+ try {
+ DateTime dt = formatter.parseDateTime(str);
+ Calendar calendar = dt.toGregorianCalendar();
+ calendar.setTimeZone(TimeZone.getDefault());
+ return calendar;
+ } catch (IllegalArgumentException e) {
+ // try with next formatter
+ }
+ }
+ final DateTimeZone dateTimeZone = DateTimeZone.forTimeZone(TimeZone.getDefault());
+ for (int i = 0; i < formattersNoUTC.length; i++ ) {
+ try {
+ final DateTimeFormatter formatter = formattersNoUTC[i].withZone(dateTimeZone);
+ final DateTime dt = formatter.parseDateTime(str);
+ final Calendar calendar = dt.toGregorianCalendar();
+ calendar.setTimeZone(TimeZone.getDefault());
+ return calendar;
+ } catch (IllegalArgumentException e) {
+ // try with next formatter
+ }
+ }
+ throw new ConversionException("Cannot parse date " + str);
+ }
+
+ public String toString(Object obj) {
+ DateTime dt = new DateTime(obj);
+ return dt.toString(formattersUTC[0]);
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/ISO8601SqlTimestampConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/ISO8601SqlTimestampConverter.java
new file mode 100644
index 0000000..fbecf39
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/ISO8601SqlTimestampConverter.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 03. October 2005 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import java.sql.Timestamp;
+import java.util.Date;
+
+
+/**
+ * A SqlTimestampConverter conforming to the ISO8601 standard.
+ * http://www.iso.ch/iso/en/CatalogueDetailPage.CatalogueDetail?CSNUMBER=26780
+ *
+ * @author Jörg Schaible
+ * @since 1.1.3
+ */
+public class ISO8601SqlTimestampConverter extends ISO8601DateConverter {
+
+ private final static String PADDING = "000000000";
+
+ public boolean canConvert(Class type) {
+ return type.equals(Timestamp.class);
+ }
+
+ public Object fromString(String str) {
+ final int idxFraction = str.lastIndexOf('.');
+ int nanos = 0;
+ if (idxFraction > 0) {
+ int idx;
+ for (idx = idxFraction + 1; Character.isDigit(str.charAt(idx)); ++idx)
+ ;
+ nanos = Integer.parseInt(str.substring(idxFraction + 1, idx));
+ str = str.substring(0, idxFraction) + str.substring(idx);
+ }
+ final Date date = (Date)super.fromString(str);
+ final Timestamp timestamp = new Timestamp(date.getTime());
+ timestamp.setNanos(nanos);
+ return timestamp;
+ }
+
+ public String toString(Object obj) {
+ final Timestamp timestamp = (Timestamp)obj;
+ String str = super.toString(new Date((timestamp.getTime() / 1000) * 1000));
+ final String nanos = String.valueOf(timestamp.getNanos());
+ final int idxFraction = str.lastIndexOf('.');
+ str = str.substring(0, idxFraction + 1)
+ + PADDING.substring(nanos.length())
+ + nanos
+ + str.substring(idxFraction + 4);
+ return str;
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/JavaClassConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/JavaClassConverter.java
new file mode 100644
index 0000000..ed77196
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/JavaClassConverter.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2011, 2013 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 04. April 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter;
+import com.thoughtworks.xstream.core.ClassLoaderReference;
+import com.thoughtworks.xstream.mapper.CannotResolveClassException;
+import com.thoughtworks.xstream.mapper.DefaultMapper;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+/**
+ * Converts a java.lang.Class to XML.
+ *
+ * @author Aslak Hellesøy
+ * @author Joe Walnes
+ * @author Matthew Sandoz
+ * @author Jörg Schaible
+ */
+public class JavaClassConverter extends AbstractSingleValueConverter {
+
+ private Mapper mapper;
+
+ /**
+ * Construct a JavaClassConverter.
+ * @param classLoaderReference the reference to the {@link ClassLoader} of the XStream instance
+ * @since 1.4.5
+ */
+ public JavaClassConverter(ClassLoaderReference classLoaderReference) {
+ this(new DefaultMapper(classLoaderReference));
+ }
+
+ /**
+ * @deprecated As of 1.4.5 use {@link #JavaClassConverter(ClassLoaderReference)}
+ */
+ public JavaClassConverter(ClassLoader classLoader) {
+ this(new ClassLoaderReference(classLoader));
+ }
+
+ /**
+ * Construct a JavaClassConverter that uses a provided mapper. Depending on the mapper
+ * chain it will not only be used to load classes, but also to support type aliases.
+ * @param mapper to use
+ * @since 1.4.5
+ */
+ protected JavaClassConverter(Mapper mapper) {
+ this.mapper = mapper;
+ }
+
+ public boolean canConvert(Class clazz) {
+ return Class.class.equals(clazz); // :)
+ }
+
+ public String toString(Object obj) {
+ return mapper.serializedClass(((Class) obj));
+ }
+
+ public Object fromString(String str) {
+ try {
+ return mapper.realClass(str);
+ } catch (CannotResolveClassException e) {
+ throw new ConversionException("Cannot load java class " + str, e.getCause());
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/JavaFieldConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/JavaFieldConverter.java
new file mode 100644
index 0000000..793a872
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/JavaFieldConverter.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2009, 2013 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 17. April 2009 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.SingleValueConverter;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.core.ClassLoaderReference;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.mapper.DefaultMapper;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+import java.lang.reflect.Field;
+
+/**
+ * Converts a java.lang.reflect.Field to XML.
+ *
+ * @author Jörg Schaible
+ */
+public class JavaFieldConverter implements Converter {
+
+ private final SingleValueConverter javaClassConverter;
+ private final Mapper mapper;
+
+ /**
+ * Construct a JavaFieldConverter.
+ * @param classLoaderReference the reference to the {@link ClassLoader} of the XStream instance
+ * @since 1.4.5
+ */
+ public JavaFieldConverter(ClassLoaderReference classLoaderReference) {
+ this(new JavaClassConverter(classLoaderReference), new DefaultMapper(classLoaderReference));
+ }
+
+ /**
+ * @deprecated As of 1.4.5 use {@link #JavaFieldConverter(ClassLoaderReference)}
+ */
+ public JavaFieldConverter(ClassLoader classLoader) {
+ this(new ClassLoaderReference(classLoader));
+ }
+
+ /**
+ * Construct a JavaFieldConverter. Depending on the mapper chain the converter will also respect aliases.
+ * @param javaClassConverter the converter to use
+ * @param mapper to use
+ * @since 1.4.5
+ */
+ protected JavaFieldConverter(SingleValueConverter javaClassConverter, Mapper mapper) {
+ this.javaClassConverter = javaClassConverter;
+ this.mapper = mapper;
+ }
+
+ public boolean canConvert(Class type) {
+ return type.equals(Field.class);
+ }
+
+ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+ Field field = (Field) source;
+ Class type = field.getDeclaringClass();
+
+ writer.startNode("name");
+ writer.setValue(mapper.serializedMember(type, field.getName()));
+ writer.endNode();
+
+ writer.startNode("clazz");
+ writer.setValue(javaClassConverter.toString(type));
+ writer.endNode();
+ }
+
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ String methodName = null;
+ String declaringClassName = null;
+
+ while((methodName == null || declaringClassName == null) && reader.hasMoreChildren()) {
+ reader.moveDown();
+
+ if (reader.getNodeName().equals("name")) {
+ methodName = reader.getValue();
+ } else if (reader.getNodeName().equals("clazz")) {
+ declaringClassName = reader.getValue();
+ }
+ reader.moveUp();
+ }
+
+ Class declaringClass = (Class)javaClassConverter.fromString(declaringClassName);
+ try {
+ return declaringClass.getDeclaredField(mapper.realMember(declaringClass, methodName));
+ } catch (NoSuchFieldException e) {
+ throw new ConversionException(e);
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/JavaMethodConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/JavaMethodConverter.java
new file mode 100644
index 0000000..b91d2e9
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/JavaMethodConverter.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2013 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 04. April 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.SingleValueConverter;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.core.ClassLoaderReference;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Converts a java.lang.reflect.Method to XML.
+ *
+ * @author Aslak Hellesøy
+ * @author Jörg Schaible
+ */
+public class JavaMethodConverter implements Converter {
+
+ private final SingleValueConverter javaClassConverter;
+
+ /**
+ * Construct a JavaMethodConverter.
+ * @param classLoaderReference the reference to the {@link ClassLoader} of the XStream instance
+ * @since 1.4.5
+ */
+ public JavaMethodConverter(ClassLoaderReference classLoaderReference) {
+ this(new JavaClassConverter(classLoaderReference));
+ }
+
+ /**
+ * @deprecated As of 1.4.5 use {@link #JavaMethodConverter(ClassLoaderReference)}
+ */
+ public JavaMethodConverter(ClassLoader classLoader) {
+ this(new ClassLoaderReference(classLoader));
+ }
+
+ /**
+ * Construct a JavaMethodConverter.
+ * @param javaClassConverter the converter to use
+ * @since 1.4.5
+ */
+ protected JavaMethodConverter(SingleValueConverter javaClassConverter) {
+ this.javaClassConverter = javaClassConverter;
+ }
+
+ public boolean canConvert(Class type) {
+ return type.equals(Method.class) || type.equals(Constructor.class);
+ }
+
+ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+ if (source instanceof Method) {
+ Method method = (Method) source;
+ String declaringClassName = javaClassConverter.toString(method.getDeclaringClass());
+ marshalMethod(writer, declaringClassName, method.getName(), method.getParameterTypes());
+ } else {
+ Constructor method = (Constructor) source;
+ String declaringClassName = javaClassConverter.toString(method.getDeclaringClass());
+ marshalMethod(writer, declaringClassName, null, method.getParameterTypes());
+ }
+ }
+
+ private void marshalMethod(HierarchicalStreamWriter writer, String declaringClassName, String methodName, Class[] parameterTypes) {
+
+ writer.startNode("class");
+ writer.setValue(declaringClassName);
+ writer.endNode();
+
+ if (methodName != null) {
+ // it's a method and not a ctor
+ writer.startNode("name");
+ writer.setValue(methodName);
+ writer.endNode();
+ }
+
+ writer.startNode("parameter-types");
+ for (int i = 0; i < parameterTypes.length; i++) {
+ writer.startNode("class");
+ writer.setValue(javaClassConverter.toString(parameterTypes[i]));
+ writer.endNode();
+ }
+ writer.endNode();
+ }
+
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ try {
+ boolean isMethodNotConstructor = context.getRequiredType().equals(Method.class);
+
+ reader.moveDown();
+ String declaringClassName = reader.getValue();
+ Class declaringClass = (Class)javaClassConverter.fromString(declaringClassName);
+ reader.moveUp();
+
+ String methodName = null;
+ if (isMethodNotConstructor) {
+ reader.moveDown();
+ methodName = reader.getValue();
+ reader.moveUp();
+ }
+
+ reader.moveDown();
+ List parameterTypeList = new ArrayList();
+ while (reader.hasMoreChildren()) {
+ reader.moveDown();
+ String parameterTypeName = reader.getValue();
+ parameterTypeList.add(javaClassConverter.fromString(parameterTypeName));
+ reader.moveUp();
+ }
+ Class[] parameterTypes = (Class[]) parameterTypeList.toArray(new Class[parameterTypeList.size()]);
+ reader.moveUp();
+
+ if (isMethodNotConstructor) {
+ return declaringClass.getDeclaredMethod(methodName, parameterTypes);
+ } else {
+ return declaringClass.getDeclaredConstructor(parameterTypes);
+ }
+ } catch (NoSuchMethodException e) {
+ throw new ConversionException(e);
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/LocaleConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/LocaleConverter.java
new file mode 100644
index 0000000..bcd9f83
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/LocaleConverter.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 24. Julyl 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter;
+
+import java.util.Locale;
+
+/**
+ * Converts a java.util.Locale to a string.
+ *
+ * @author Jose A. Illescas
+ * @author Joe Walnes
+ */
+public class LocaleConverter extends AbstractSingleValueConverter {
+
+ public boolean canConvert(Class type) {
+ return type.equals(Locale.class);
+ }
+
+ public Object fromString(String str) {
+ int[] underscorePositions = underscorePositions(str);
+ String language, country, variant;
+ if (underscorePositions[0] == -1) { // "language"
+ language = str;
+ country = "";
+ variant = "";
+ } else if (underscorePositions[1] == -1) { // "language_country"
+ language = str.substring(0, underscorePositions[0]);
+ country = str.substring(underscorePositions[0] + 1);
+ variant = "";
+ } else { // "language_country_variant"
+ language = str.substring(0, underscorePositions[0]);
+ country = str.substring(underscorePositions[0] + 1, underscorePositions[1]);
+ variant = str.substring(underscorePositions[1] + 1);
+ }
+ return new Locale(language, country, variant);
+ }
+
+ private int[] underscorePositions(String in) {
+ int[] result = new int[2];
+ for (int i = 0; i < result.length; i++) {
+ int last = i == 0 ? 0 : result[i - 1];
+ result[i] = in.indexOf('_', last + 1);
+ }
+ return result;
+ }
+
+}
\ No newline at end of file
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/LookAndFeelConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/LookAndFeelConverter.java
new file mode 100644
index 0000000..2315a33
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/LookAndFeelConverter.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2007, 2008, 2013 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 08.12.2007 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import com.thoughtworks.xstream.converters.reflection.ReflectionConverter;
+import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+import javax.swing.LookAndFeel;
+
+import java.io.NotSerializableException;
+
+
+/**
+ * A converter for Swing LookAndFeel implementations. The JDK's implementations are serializable
+ * for historical reasons but will throw a {@link NotSerializableException} in their writeObject
+ * method. Therefore XStream will use an implementation based on the ReflectionConverter.
+ *
+ * @author Jörg Schaible
+ * @since 1.3
+ */
+public class LookAndFeelConverter extends ReflectionConverter {
+
+ /**
+ * Constructs a LookAndFeelConverter.
+ *
+ * @param mapper the mapper
+ * @param reflectionProvider the reflection provider
+ * @since 1.3
+ */
+ public LookAndFeelConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
+ super(mapper, reflectionProvider);
+ }
+
+ public boolean canConvert(Class type) {
+ return LookAndFeel.class.isAssignableFrom(type) && canAccess(type);
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/NamedArrayConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/NamedArrayConverter.java
new file mode 100644
index 0000000..3312330
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/NamedArrayConverter.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2013 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 03. December 2013 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.core.util.HierarchicalStreams;
+import com.thoughtworks.xstream.core.util.Primitives;
+import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+/**
+ * An array converter that uses predefined names for its items.
+ *
+ * To be used as local converter.
+ *
+ *
+ * @author Jörg Schaible
+ * @since 1.4.6
+ */
+public class NamedArrayConverter implements Converter {
+
+ private final Class arrayType;
+ private final String itemName;
+ private final Mapper mapper;
+
+ /**
+ * Construct a NamedArrayConverter.
+ * @param arrayType
+ * @param mapper
+ * @param itemName
+ * @since 1.4.6
+ */
+ public NamedArrayConverter(final Class arrayType, final Mapper mapper, final String itemName) {
+ if (!arrayType.isArray()) {
+ throw new IllegalArgumentException(arrayType.getName() + " is not an array");
+ }
+ this.arrayType = arrayType;
+ this.mapper = mapper;
+ this.itemName = itemName;
+ }
+
+ public boolean canConvert(final Class type) {
+ return type == arrayType;
+ }
+
+ public void marshal(final Object source, final HierarchicalStreamWriter writer, final MarshallingContext context) {
+ final int length = Array.getLength(source);
+ for (int i = 0; i < length; ++i) {
+ final Object item = Array.get(source, i);
+ final Class itemType = item == null
+ ? Mapper.Null.class
+ : arrayType.getComponentType().isPrimitive()
+ ? Primitives.unbox(item.getClass())
+ : item.getClass();
+ ExtendedHierarchicalStreamWriterHelper.startNode(writer, itemName, itemType);
+ if (!itemType.equals(arrayType.getComponentType())) {
+ final String attributeName = mapper.aliasForSystemAttribute("class");
+ if (attributeName != null) {
+ writer.addAttribute(attributeName, mapper.serializedClass(itemType));
+ }
+ }
+ if (item != null) {
+ context.convertAnother(item);
+ }
+ writer.endNode();
+ }
+ }
+
+ public Object unmarshal(final HierarchicalStreamReader reader, final UnmarshallingContext context) {
+ final List list = new ArrayList();
+ while (reader.hasMoreChildren()) {
+ reader.moveDown();
+ final Object item;
+ final String className = HierarchicalStreams.readClassAttribute(reader, mapper);
+ final Class itemType = className == null ? arrayType.getComponentType() : mapper.realClass(className);
+ if (Mapper.Null.class.equals(itemType)) {
+ item = null;
+ } else {
+ item = context.convertAnother(null, itemType);
+ }
+ list.add(item);
+ reader.moveUp();
+ }
+ final Object array = Array.newInstance(arrayType.getComponentType(), list.size());
+ for (int i = 0; i < list.size(); ++i) {
+ Array.set(array, i, list.get(i));
+ }
+ return array;
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/NamedCollectionConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/NamedCollectionConverter.java
new file mode 100644
index 0000000..3e5b798
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/NamedCollectionConverter.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2013 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 19. September 2013 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.converters.collections.CollectionConverter;
+import com.thoughtworks.xstream.core.util.HierarchicalStreams;
+import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+
+/**
+ * A collection converter that uses predefined names for its items.
+ *
+ * To be used as local converter. Note, suppress the usage of the implicit type argument, if
+ * registered with annotation.
+ *
+ *
+ * @author Jörg Schaible
+ * @since 1.4.5
+ */
+public class NamedCollectionConverter extends CollectionConverter {
+
+ private final String name;
+ private final Class type;
+
+ /**
+ * Constructs a NamedCollectionConverter.
+ *
+ * @param mapper the mapper
+ * @param itemName the name of the items
+ * @param itemType the base type of the items
+ * @since 1.4.5
+ */
+ public NamedCollectionConverter(Mapper mapper, String itemName, Class itemType) {
+ this(null, mapper, itemName, itemType);
+ }
+
+ /**
+ * Constructs a NamedCollectionConverter handling an explicit Collection type.
+ *
+ * @param type the Collection type to handle
+ * @param mapper the mapper
+ * @param itemName the name of the items
+ * @param itemType the base type of the items
+ * @since 1.4.5
+ */
+ public NamedCollectionConverter(Class type, Mapper mapper, String itemName, Class itemType) {
+ super(mapper, type);
+ this.name = itemName;
+ this.type = itemType;
+ }
+
+ protected void writeItem(Object item, MarshallingContext context, HierarchicalStreamWriter writer) {
+ Class itemType = item == null ? Mapper.Null.class : item.getClass();
+ ExtendedHierarchicalStreamWriterHelper.startNode(writer, name, itemType);
+ if (!itemType.equals(type)) {
+ String attributeName = mapper().aliasForSystemAttribute("class");
+ if (attributeName != null) {
+ writer.addAttribute(attributeName, mapper().serializedClass(itemType));
+ }
+ }
+ if (item != null) {
+ context.convertAnother(item);
+ }
+ writer.endNode();
+ }
+
+ protected Object readItem(HierarchicalStreamReader reader, UnmarshallingContext context, Object current) {
+ String className = HierarchicalStreams.readClassAttribute(reader, mapper());
+ Class itemType = className == null ? type : mapper().realClass(className);
+ if (Mapper.Null.class.equals(itemType)) {
+ return null;
+ } else {
+ return context.convertAnother(current, itemType);
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/NamedMapConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/NamedMapConverter.java
new file mode 100644
index 0000000..24f6e0e
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/NamedMapConverter.java
@@ -0,0 +1,391 @@
+/*
+ * Copyright (C) 2013 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 20. September 2013 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.ConverterLookup;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.SingleValueConverter;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.converters.collections.MapConverter;
+import com.thoughtworks.xstream.core.JVM;
+import com.thoughtworks.xstream.core.util.HierarchicalStreams;
+import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+
+/**
+ * A map converter that uses predefined names for its elements.
+ *
+ * To be used as local converter. Note, suppress the usage of the implicit type argument, if
+ * registered with annotation. Depending on the constructor arguments it is possible to support
+ * various formats:
+ *
+ *
+ * @author Jörg Schaible
+ * @since 1.4.5
+ */
+public class NamedMapConverter extends MapConverter {
+
+ private final String entryName;
+ private final String keyName;
+ private final Class keyType;
+ private final String valueName;
+ private final Class valueType;
+ private final boolean keyAsAttribute;
+ private final boolean valueAsAttribute;
+ private final ConverterLookup lookup;
+ private final Mapper enumMapper;
+
+ /**
+ * Constructs a NamedMapConverter.
+ *
+ * @param mapper the mapper
+ * @param entryName the name of the entry elements
+ * @param keyName the name of the key elements
+ * @param keyType the base type of key elements
+ * @param valueName the name of the value elements
+ * @param valueType the base type of value elements
+ * @since 1.4.5
+ */
+ public NamedMapConverter(
+ Mapper mapper, String entryName, String keyName, Class keyType, String valueName,
+ Class valueType) {
+ this(mapper, entryName, keyName, keyType, valueName, valueType, false, false, null);
+ }
+
+ /**
+ * Constructs a NamedMapConverter handling an explicit Map type.
+ *
+ * @param type the Map type this instance will handle
+ * @param mapper the mapper
+ * @param entryName the name of the entry elements
+ * @param keyName the name of the key elements
+ * @param keyType the base type of key elements
+ * @param valueName the name of the value elements
+ * @param valueType the base type of value elements
+ * @since 1.4.5
+ */
+ public NamedMapConverter(
+ Class type, Mapper mapper, String entryName, String keyName, Class keyType,
+ String valueName, Class valueType) {
+ this(
+ type, mapper, entryName, keyName, keyType, valueName, valueType, false, false, null);
+ }
+
+ /**
+ * Constructs a NamedMapConverter with attribute support.
+ *
+ * @param mapper the mapper
+ * @param entryName the name of the entry elements
+ * @param keyName the name of the key elements
+ * @param keyType the base type of key elements
+ * @param valueName the name of the value elements
+ * @param valueType the base type of value elements
+ * @param keyAsAttribute flag to write key as attribute of entry element
+ * @param valueAsAttribute flag to write value as attribute of entry element
+ * @param lookup used to lookup SingleValueConverter for attributes
+ * @since 1.4.5
+ */
+ public NamedMapConverter(
+ Mapper mapper, String entryName, String keyName, Class keyType, String valueName,
+ Class valueType, boolean keyAsAttribute, boolean valueAsAttribute,
+ ConverterLookup lookup) {
+ this(
+ null, mapper, entryName, keyName, keyType, valueName, valueType, keyAsAttribute,
+ valueAsAttribute, lookup);
+ }
+
+ /**
+ * Constructs a NamedMapConverter with attribute support handling an explicit Map type.
+ *
+ * @param type the Map type this instance will handle
+ * @param mapper the mapper
+ * @param entryName the name of the entry elements
+ * @param keyName the name of the key elements
+ * @param keyType the base type of key elements
+ * @param valueName the name of the value elements
+ * @param valueType the base type of value elements
+ * @param keyAsAttribute flag to write key as attribute of entry element
+ * @param valueAsAttribute flag to write value as attribute of entry element
+ * @param lookup used to lookup SingleValueConverter for attributes
+ * @since 1.4.5
+ */
+ public NamedMapConverter(
+ Class type, Mapper mapper, String entryName, String keyName, Class keyType,
+ String valueName, Class valueType, boolean keyAsAttribute, boolean valueAsAttribute,
+ ConverterLookup lookup) {
+ super(mapper, type);
+ this.entryName = entryName != null && entryName.length() == 0 ? null : entryName;
+ this.keyName = keyName != null && keyName.length() == 0 ? null : keyName;
+ this.keyType = keyType;
+ this.valueName = valueName != null && valueName.length() == 0 ? null : valueName;
+ this.valueType = valueType;
+ this.keyAsAttribute = keyAsAttribute;
+ this.valueAsAttribute = valueAsAttribute;
+ this.lookup = lookup;
+ enumMapper = JVM.is15() ? UseAttributeForEnumMapper.createEnumMapper(mapper) : null;
+
+ if (keyType == null || valueType == null) {
+ throw new IllegalArgumentException("Class types of key and value are mandatory");
+ }
+ if (entryName == null) {
+ if (keyAsAttribute || valueAsAttribute) {
+ throw new IllegalArgumentException(
+ "Cannot write attributes to map entry, if map entry must be omitted");
+ }
+ if (valueName == null) {
+ throw new IllegalArgumentException(
+ "Cannot write value as text of entry, if entry must be omitted");
+ }
+ }
+ if (keyName == null) {
+ throw new IllegalArgumentException("Cannot write key without name");
+ }
+ if (valueName == null) {
+ if (valueAsAttribute) {
+ throw new IllegalArgumentException(
+ "Cannot write value as attribute without name");
+ } else if (!keyAsAttribute) {
+ throw new IllegalArgumentException(
+ "Cannot write value as text of entry, if key is also child element");
+ }
+ }
+ if (keyAsAttribute && valueAsAttribute && keyName.equals(valueName)) {
+ throw new IllegalArgumentException(
+ "Cannot write key and value with same attribute name");
+ }
+ }
+
+ public void marshal(Object source, HierarchicalStreamWriter writer,
+ MarshallingContext context) {
+ Map map = (Map)source;
+ SingleValueConverter keyConverter = null;
+ SingleValueConverter valueConverter = null;
+ if (keyAsAttribute) {
+ keyConverter = getSingleValueConverter(keyType);
+ }
+ if (valueAsAttribute || valueName == null) {
+ valueConverter = getSingleValueConverter(valueType);
+ }
+ for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) {
+ Map.Entry entry = (Map.Entry)iterator.next();
+ Object key = entry.getKey();
+ Object value = entry.getValue();
+ if (entryName != null) {
+ ExtendedHierarchicalStreamWriterHelper.startNode(
+ writer, entryName, entry.getClass());
+ if (keyConverter != null && key != null) {
+ writer.addAttribute(keyName, keyConverter.toString(key));
+ }
+ if (valueName != null && valueConverter != null && value != null) {
+ writer.addAttribute(valueName, valueConverter.toString(value));
+ }
+ }
+
+ if (keyConverter == null) {
+ writeItem(keyName, keyType, key, context, writer);
+ }
+ if (valueConverter == null) {
+ writeItem(valueName, valueType, value, context, writer);
+ } else if (valueName == null) {
+ writer.setValue(valueConverter.toString(value));
+ }
+
+ if (entryName != null) {
+ writer.endNode();
+ }
+ }
+ }
+
+ protected void populateMap(HierarchicalStreamReader reader, UnmarshallingContext context,
+ Map map, Map target) {
+ SingleValueConverter keyConverter = null;
+ SingleValueConverter valueConverter = null;
+ if (keyAsAttribute) {
+ keyConverter = getSingleValueConverter(keyType);
+ }
+ if (valueAsAttribute || valueName == null) {
+ valueConverter = getSingleValueConverter(valueType);
+ }
+
+ while (reader.hasMoreChildren()) {
+ Object key = null;
+ Object value = null;
+
+ if (entryName != null) {
+ reader.moveDown();
+
+ if (keyConverter != null) {
+ String attribute = reader.getAttribute(keyName);
+ if (attribute != null) {
+ key = keyConverter.fromString(attribute);
+ }
+ }
+
+ if (valueAsAttribute && valueConverter != null) {
+ String attribute = reader.getAttribute(valueName);
+ if (attribute != null) {
+ value = valueConverter.fromString(attribute);
+ }
+ }
+ }
+
+ if (keyConverter == null) {
+ reader.moveDown();
+ if (valueConverter == null
+ && !keyName.equals(valueName)
+ && reader.getNodeName().equals(valueName)) {
+ value = readItem(valueType, reader, context, map);
+ } else {
+ key = readItem(keyType, reader, context, map);
+ }
+ reader.moveUp();
+ }
+
+ if (valueConverter == null) {
+ reader.moveDown();
+ if (keyConverter == null && key == null && value != null) {
+ key = readItem(keyType, reader, context, map);
+ } else {
+ value = readItem(valueType, reader, context, map);
+ }
+ reader.moveUp();
+ } else if (!valueAsAttribute) {
+ value = reader.getValue();
+ }
+
+ target.put(key, value);
+
+ if (entryName != null) {
+ reader.moveUp();
+ }
+ }
+ }
+
+ private SingleValueConverter getSingleValueConverter(Class type) {
+ SingleValueConverter conv = UseAttributeForEnumMapper.isEnum(type) ? enumMapper
+ .getConverterFromItemType(null, type, null) : mapper().getConverterFromItemType(
+ null, type, null);
+ if (conv == null) {
+ Converter converter = lookup.lookupConverterForType(type);
+ if (converter instanceof SingleValueConverter) {
+ conv = (SingleValueConverter)converter;
+ } else {
+ throw new ConversionException("No SingleValueConverter for key available");
+ }
+ }
+ return conv;
+ }
+
+ protected void writeItem(String name, Class type, Object item, MarshallingContext context,
+ HierarchicalStreamWriter writer) {
+ Class itemType = item == null ? Mapper.Null.class : item.getClass();
+ ExtendedHierarchicalStreamWriterHelper.startNode(writer, name, itemType);
+ if (!itemType.equals(type)) {
+ String attributeName = mapper().aliasForSystemAttribute("class");
+ if (attributeName != null) {
+ writer.addAttribute(attributeName, mapper().serializedClass(itemType));
+ }
+ }
+ if (item != null) {
+ context.convertAnother(item);
+ }
+ writer.endNode();
+ }
+
+ protected Object readItem(Class type, HierarchicalStreamReader reader,
+ UnmarshallingContext context, Object current) {
+ String className = HierarchicalStreams.readClassAttribute(reader, mapper());
+ Class itemType = className == null ? type : mapper().realClass(className);
+ if (Mapper.Null.class.equals(itemType)) {
+ return null;
+ } else {
+ return context.convertAnother(current, itemType);
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/PropertyEditorCapableConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/PropertyEditorCapableConverter.java
new file mode 100644
index 0000000..8c5a111
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/PropertyEditorCapableConverter.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 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 20.09.2007 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import com.thoughtworks.xstream.converters.SingleValueConverter;
+import com.thoughtworks.xstream.core.util.ThreadSafePropertyEditor;
+
+import java.beans.PropertyEditor;
+
+
+/**
+ * A SingleValueConverter that can utilize a {@link PropertyEditor} implementation used for a
+ * specific type. The converter ensures that the editors can be used concurrently.
+ *
+ * @author Jukka Lindström
+ * @author Jörg Schaible
+ * @since 1.3
+ */
+public class PropertyEditorCapableConverter implements SingleValueConverter {
+
+ private final ThreadSafePropertyEditor editor;
+ private final Class type;
+
+ public PropertyEditorCapableConverter(final Class propertyEditorType, final Class type) {
+ this.type = type;
+ editor = new ThreadSafePropertyEditor(propertyEditorType, 2, 5);
+ }
+
+ public boolean canConvert(final Class type) {
+ return this.type == type;
+ }
+
+ public Object fromString(final String str) {
+ return editor.setAsText(str);
+ }
+
+ public String toString(final Object obj) {
+ return editor.getAsText(obj);
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/RegexPatternConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/RegexPatternConverter.java
new file mode 100644
index 0000000..3bed0f6
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/RegexPatternConverter.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2013 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 31. July 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+
+import java.util.regex.Pattern;
+
+/**
+ * Ensures java.util.regex.Pattern is compiled upon deserialization.
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ */
+public class RegexPatternConverter implements Converter {
+
+ /**
+ * @since 1.4.5
+ */
+ public RegexPatternConverter() {
+ }
+
+ /**
+ * @deprecated As of 1.4.5, use {@link #RegexPatternConverter()} instead
+ */
+ public RegexPatternConverter(Converter defaultConverter) {
+ }
+
+ public boolean canConvert(final Class type) {
+ return type.equals(Pattern.class);
+ }
+
+ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+ Pattern pattern = (Pattern)source;
+ writer.startNode("pattern");
+ writer.setValue(pattern.pattern());
+ writer.endNode();
+ writer.startNode("flags");
+ writer.setValue(String.valueOf(pattern.flags()));
+ writer.endNode();
+ }
+
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ reader.moveDown();
+ String pattern = reader.getValue();
+ reader.moveUp();
+ reader.moveDown();
+ int flags = Integer.parseInt(reader.getValue());
+ reader.moveUp();
+ return Pattern.compile(pattern, flags);
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/SqlDateConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/SqlDateConverter.java
new file mode 100644
index 0000000..f5d8e7f
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/SqlDateConverter.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 24. July 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import java.sql.Date;
+
+import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter;
+
+/**
+ * Converts a java.sql.Date to text.
+ *
+ * @author Jose A. Illescas
+ */
+public class SqlDateConverter extends AbstractSingleValueConverter {
+
+ public boolean canConvert(Class type) {
+ return type.equals(Date.class);
+ }
+
+ public Object fromString(String str) {
+ return Date.valueOf(str);
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/SqlTimeConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/SqlTimeConverter.java
new file mode 100644
index 0000000..3315f23
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/SqlTimeConverter.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 24. July 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter;
+
+import java.sql.Time;
+
+/**
+ * Converts a java.sql.Time to text. Warning: Any granularity smaller than seconds is lost.
+ *
+ * @author Jose A. Illescas
+ */
+public class SqlTimeConverter extends AbstractSingleValueConverter {
+
+ public boolean canConvert(Class type) {
+ return type.equals(Time.class);
+ }
+
+ public Object fromString(String str) {
+ return Time.valueOf(str);
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/SqlTimestampConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/SqlTimestampConverter.java
new file mode 100644
index 0000000..d5f9c45
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/SqlTimestampConverter.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2003, 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2012, 2014 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 01. October 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter;
+import com.thoughtworks.xstream.core.util.ThreadSafeSimpleDateFormat;
+
+import java.sql.Timestamp;
+import java.text.ParseException;
+import java.util.TimeZone;
+
+
+/**
+ * Converts a java.sql.Timestamp to text.
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ */
+public class SqlTimestampConverter extends AbstractSingleValueConverter {
+
+ private final ThreadSafeSimpleDateFormat format = new ThreadSafeSimpleDateFormat(
+ "yyyy-MM-dd HH:mm:ss", TimeZone.getTimeZone("UTC"), 0, 5, false);
+
+ public boolean canConvert(Class type) {
+ return type.equals(Timestamp.class);
+ }
+
+ public String toString(Object obj) {
+ Timestamp timestamp = (Timestamp)obj;
+ StringBuffer buffer = new StringBuffer(format.format(timestamp)).append('.');
+ if (timestamp.getNanos() == 0) {
+ buffer.append('0');
+ } else {
+ String nanos = String.valueOf(timestamp.getNanos() + 1000000000);
+ int last = 10;
+ while (last > 2 && nanos.charAt(last-1) == '0')
+ --last;
+ buffer.append(nanos.subSequence(1, last));
+ }
+ return buffer.toString();
+ }
+
+ public Object fromString(String str) {
+ int idx = str.lastIndexOf('.');
+ if (idx < 0 || str.length() - idx < 2 || str.length() - idx > 10) {
+ throw new ConversionException(
+ "Timestamp format must be yyyy-mm-dd hh:mm:ss[.fffffffff]");
+ }
+ try {
+ Timestamp timestamp = new Timestamp(format.parse(str.substring(0, idx)).getTime());
+ StringBuffer buffer = new StringBuffer(str.substring(idx + 1));
+ while(buffer.length() != 9) {
+ buffer.append('0');
+ }
+ timestamp.setNanos(Integer.parseInt(buffer.toString()));
+ return timestamp;
+ } catch (NumberFormatException e) {
+ throw new ConversionException(
+ "Timestamp format must be yyyy-mm-dd hh:mm:ss[.fffffffff]", e);
+ } catch (ParseException e) {
+ throw new ConversionException(
+ "Timestamp format must be yyyy-mm-dd hh:mm:ss[.fffffffff]", e);
+ }
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/StackTraceElementConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/StackTraceElementConverter.java
new file mode 100644
index 0000000..c56b9e3
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/StackTraceElementConverter.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 29. May 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter;
+import com.thoughtworks.xstream.core.JVM;
+
+/**
+ * Converter for StackTraceElement (the lines of a stack trace) - JDK 1.4+ only.
+ *
+ * @author B. K. Oxley (binkley)
+ * @author Joe Walnes
+ */
+public class StackTraceElementConverter extends AbstractSingleValueConverter {
+
+ // Regular expression to parse a line of a stack trace. Returns 4 groups.
+ //
+ // Example: com.blah.MyClass.doStuff(MyClass.java:123)
+ // |-------1------| |--2--| |----3-----| |4|
+ // (Note group 4 is optional is optional and only present if a colon char exists.)
+
+ private static final Pattern PATTERN = Pattern.compile("^(.+)\\.([^\\(]+)\\(([^:]*)(:(\\d+))?\\)$");
+ private static final StackTraceElementFactory FACTORY;
+ static {
+ StackTraceElementFactory factory = null;
+ if (JVM.is15()) {
+ Class factoryType = JVM.loadClassForName(
+ "com.thoughtworks.xstream.converters.extended.StackTraceElementFactory15",
+ false);
+ try {
+ factory = (StackTraceElementFactory)factoryType.newInstance();
+ } catch (Exception e) {
+ // N/A
+ } catch (LinkageError e) {
+ // N/A
+ }
+ }
+ if (factory == null) {
+ factory = new StackTraceElementFactory();
+ }
+ try {
+ factory.unknownSourceElement("a", "b");
+ } catch (Exception e) {
+ factory = null;
+ } catch (NoClassDefFoundError e) { // GAE
+ factory = null;
+ }
+ FACTORY = factory;
+ }
+
+ public boolean canConvert(Class type) {
+ return StackTraceElement.class.equals(type) && FACTORY != null;
+ }
+
+ public String toString(Object obj) {
+ String s = super.toString(obj);
+ // JRockit adds ":???" for invalid line number
+ return s.replaceFirst(":\\?\\?\\?", "");
+ }
+
+ public Object fromString(String str) {
+ Matcher matcher = PATTERN.matcher(str);
+ if (matcher.matches()) {
+ String declaringClass = matcher.group(1);
+ String methodName = matcher.group(2);
+ String fileName = matcher.group(3);
+ if (fileName.equals("Unknown Source")) {
+ return FACTORY.unknownSourceElement(declaringClass, methodName);
+ } else if (fileName.equals("Native Method")) {
+ return FACTORY.nativeMethodElement(declaringClass, methodName);
+ } else {
+ if (matcher.group(4) != null) {
+ int lineNumber = Integer.parseInt(matcher.group(5));
+ return FACTORY.element(declaringClass, methodName, fileName, lineNumber);
+ } else {
+ return FACTORY.element(declaringClass, methodName, fileName);
+ }
+ }
+ } else {
+ throw new ConversionException("Could not parse StackTraceElement : " + str);
+ }
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/StackTraceElementFactory.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/StackTraceElementFactory.java
new file mode 100644
index 0000000..7154c8a
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/StackTraceElementFactory.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2014 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 30. May 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+
+import java.lang.reflect.Field;
+
+/**
+ * Factory for creating StackTraceElements.
+ * Factory for creating StackTraceElements.
+ *
+ * @author B. K. Oxley (binkley)
+ * @author Joe Walnes
+ * @deprecated As of 1.4.8, it is an internal helper class
+ */
+public class StackTraceElementFactory {
+
+ public StackTraceElement nativeMethodElement(String declaringClass, String methodName) {
+ return create(declaringClass, methodName, "Native Method", -2);
+ }
+
+ public StackTraceElement unknownSourceElement(String declaringClass, String methodName) {
+ return create(declaringClass, methodName, "Unknown Source", -1);
+ }
+
+ public StackTraceElement element(String declaringClass, String methodName, String fileName) {
+ return create(declaringClass, methodName, fileName, -1);
+ }
+
+ public StackTraceElement element(String declaringClass, String methodName, String fileName, int lineNumber) {
+ return create(declaringClass, methodName, fileName, lineNumber);
+ }
+
+ protected StackTraceElement create(String declaringClass, String methodName, String fileName, int lineNumber) {
+ StackTraceElement result = new Throwable().getStackTrace()[0];
+ setField(result, "declaringClass", declaringClass);
+ setField(result, "methodName", methodName);
+ setField(result, "fileName", fileName);
+ setField(result, "lineNumber", new Integer(lineNumber));
+ return result;
+ }
+
+ private void setField(StackTraceElement element, String fieldName, Object value) {
+ try {
+ final Field field = StackTraceElement.class.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ field.set(element, value);
+ } catch (Exception e) {
+ throw new ConversionException(e);
+ }
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/StackTraceElementFactory15.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/StackTraceElementFactory15.java
new file mode 100644
index 0000000..f34024c
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/StackTraceElementFactory15.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2013 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 03. December 2013 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+/**
+ * @author Jörg Schaible
+ *
+ * @since 1.4.6
+ */
+class StackTraceElementFactory15 extends StackTraceElementFactory {
+
+ @Override
+ protected StackTraceElement create(final String declaringClass, final String methodName,
+ final String fileName, final int lineNumber) {
+ return new StackTraceElement(declaringClass, methodName, fileName, lineNumber);
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/SubjectConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/SubjectConverter.java
new file mode 100644
index 0000000..bbb5890
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/SubjectConverter.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2006, 2007 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 12. January 2006 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+import javax.security.auth.Subject;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * Converts a {@link Subject} instance. Note, that this Converter does only convert the contained Principals as
+ * it is done by JDK serialization, but not any credentials. For other behaviour you can derive your own converter,
+ * overload the appropriate methods and register it in the {@link com.thoughtworks.xstream.XStream}.
+ *
+ * @author Jörg Schaible
+ * @since 1.1.3
+ */
+public class SubjectConverter extends AbstractCollectionConverter {
+
+ public SubjectConverter(Mapper mapper) {
+ super(mapper);
+ }
+
+ public boolean canConvert(Class type) {
+ return type.equals(Subject.class);
+ }
+
+ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+ Subject subject = (Subject) source;
+ marshalPrincipals(subject.getPrincipals(), writer, context);
+ marshalPublicCredentials(subject.getPublicCredentials(), writer, context);
+ marshalPrivateCredentials(subject.getPrivateCredentials(), writer, context);
+ marshalReadOnly(subject.isReadOnly(), writer);
+ }
+
+ protected void marshalPrincipals(Set principals, HierarchicalStreamWriter writer, MarshallingContext context) {
+ writer.startNode("principals");
+ for (final Iterator iter = principals.iterator(); iter.hasNext();) {
+ final Object principal = iter.next(); // pre jdk 1.4 a Principal was also in javax.security
+ writeItem(principal, context, writer);
+ }
+ writer.endNode();
+ };
+
+ protected void marshalPublicCredentials(Set pubCredentials, HierarchicalStreamWriter writer, MarshallingContext context) {
+ };
+
+ protected void marshalPrivateCredentials(Set privCredentials, HierarchicalStreamWriter writer, MarshallingContext context) {
+ };
+
+ protected void marshalReadOnly(boolean readOnly, HierarchicalStreamWriter writer) {
+ writer.startNode("readOnly");
+ writer.setValue(String.valueOf(readOnly));
+ writer.endNode();
+ };
+
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ Set principals = unmarshalPrincipals(reader, context);
+ Set publicCredentials = unmarshalPublicCredentials(reader, context);
+ Set privateCredentials = unmarshalPrivateCredentials(reader, context);
+ boolean readOnly = unmarshalReadOnly(reader);
+ return new Subject(readOnly, principals, publicCredentials, privateCredentials);
+ }
+
+ protected Set unmarshalPrincipals(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ return populateSet(reader, context);
+ };
+
+ protected Set unmarshalPublicCredentials(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ return Collections.EMPTY_SET;
+ };
+
+ protected Set unmarshalPrivateCredentials(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ return Collections.EMPTY_SET;
+ };
+
+ protected boolean unmarshalReadOnly(HierarchicalStreamReader reader) {
+ reader.moveDown();
+ boolean readOnly = Boolean.getBoolean(reader.getValue());
+ reader.moveUp();
+ return readOnly;
+ };
+
+ protected Set populateSet(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ Set set = new HashSet();
+ reader.moveDown();
+ while (reader.hasMoreChildren()) {
+ reader.moveDown();
+ Object elementl = readItem(reader, context, set);
+ reader.moveUp();
+ set.add(elementl);
+ }
+ reader.moveUp();
+ return set;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/TextAttributeConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/TextAttributeConverter.java
new file mode 100644
index 0000000..0c5fdb5
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/TextAttributeConverter.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2006, 2007 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 25. March 2006 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import com.thoughtworks.xstream.converters.reflection.AbstractAttributedCharacterIteratorAttributeConverter;
+
+import java.awt.font.TextAttribute;
+
+
+/**
+ * A converter for {@link TextAttribute} constants.
+ *
+ * @author Jörg Schaible
+ * @since 1.2
+ */
+public class TextAttributeConverter extends
+ AbstractAttributedCharacterIteratorAttributeConverter {
+
+ /**
+ * Constructs a TextAttributeConverter.
+ *
+ * @since 1.2.2
+ */
+ public TextAttributeConverter() {
+ super(TextAttribute.class);
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/ThrowableConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/ThrowableConverter.java
new file mode 100644
index 0000000..a0c8d3b
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/ThrowableConverter.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2013 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 29. May 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.ConverterLookup;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+
+/**
+ * Converter for Throwable (and Exception) that retains stack trace.
+ *
+ * @author B. K. Oxley (binkley)
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ */
+public class ThrowableConverter implements Converter {
+
+ private Converter defaultConverter;
+ private final ConverterLookup lookup;
+
+ /**
+ * @deprecated As of 1.4.5 use {@link #ThrowableConverter(ConverterLookup)}
+ */
+ public ThrowableConverter(Converter defaultConverter) {
+ this.defaultConverter = defaultConverter;
+ lookup = null;
+ }
+
+ /**
+ * @since 1.4.5
+ */
+ public ThrowableConverter(ConverterLookup lookup) {
+ this.lookup = lookup;
+ }
+
+ public boolean canConvert(final Class type) {
+ return Throwable.class.isAssignableFrom(type);
+ }
+
+ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+ Throwable throwable = (Throwable) source;
+ if (throwable.getCause() == null) {
+ try {
+ throwable.initCause(null);
+ } catch (IllegalStateException e) {
+ // ignore, initCause failed, cause was already set
+ }
+ }
+ throwable.getStackTrace(); // Force stackTrace field to be lazy loaded by special JVM native witchcraft (outside our control).
+ getConverter().marshal(throwable, writer, context);
+ }
+
+ private Converter getConverter() {
+ return defaultConverter != null ? defaultConverter : lookup.lookupConverterForType(Object.class);
+ }
+
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ return getConverter().unmarshal(reader, context);
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/ToAttributedValueConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/ToAttributedValueConverter.java
new file mode 100644
index 0000000..df5f483
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/ToAttributedValueConverter.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2011, 2013 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 30. July 2011 by Joerg Schaible
+ */
+
+package com.thoughtworks.xstream.converters.extended;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.ConverterLookup;
+import com.thoughtworks.xstream.converters.ConverterMatcher;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.SingleValueConverter;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.DuplicateFieldException;
+import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
+import com.thoughtworks.xstream.core.JVM;
+import com.thoughtworks.xstream.core.util.FastField;
+import com.thoughtworks.xstream.core.util.HierarchicalStreams;
+import com.thoughtworks.xstream.core.util.Primitives;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+
+/**
+ * Converter that supports the definition of one field member that will be written as value and
+ * all other field members are written as attributes. The converter requires that all the field
+ * types (expect the one with the value) are handled by a {@link SingleValueConverter}. The
+ * value field is defined using the name of the type that declares the field and the field name
+ * itself. Therefore it is possible to define an inherited field as value. It is also possible
+ * to provide no value field at all, so that all fields are written as attributes.
+ *
+ * @author Jörg Schaible
+ * @since 1.4
+ */
+public class ToAttributedValueConverter implements Converter {
+ private static final String STRUCTURE_MARKER = "";
+ private final Class type;
+ private final Mapper mapper;
+ private final Mapper enumMapper;
+ private final ReflectionProvider reflectionProvider;
+ private final ConverterLookup lookup;
+ private final Field valueField;
+
+ /**
+ * Creates a new ToAttributedValueConverter instance.
+ *
+ * @param type the type that is handled by this converter instance
+ * @param mapper the mapper in use
+ * @param reflectionProvider the reflection provider in use
+ * @param lookup the converter lookup in use
+ * @param valueFieldName the field defining the tag's value (may be null)
+ */
+ public ToAttributedValueConverter(
+ final Class type, final Mapper mapper, final ReflectionProvider reflectionProvider,
+ final ConverterLookup lookup, final String valueFieldName) {
+ this(type, mapper, reflectionProvider, lookup, valueFieldName, null);
+ }
+
+ /**
+ * Creates a new ToAttributedValueConverter instance.
+ *
+ * @param type the type that is handled by this converter instance
+ * @param mapper the mapper in use
+ * @param reflectionProvider the reflection provider in use
+ * @param lookup the converter lookup in use
+ * @param valueFieldName the field defining the tag's value (may be null)
+ * @param valueDefinedIn the type defining the field
+ */
+ public ToAttributedValueConverter(
+ final Class type, final Mapper mapper, final ReflectionProvider reflectionProvider,
+ final ConverterLookup lookup, final String valueFieldName, Class valueDefinedIn) {
+ this.type = type;
+ this.mapper = mapper;
+ this.reflectionProvider = reflectionProvider;
+ this.lookup = lookup;
+
+ if (valueFieldName == null) {
+ valueField = null;
+ } else {
+ Field field = null;
+ try {
+ field = (valueDefinedIn != null ? valueDefinedIn : type)
+ .getDeclaredField(valueFieldName);
+ if (!field.isAccessible()) {
+ field.setAccessible(true);
+ }
+ } catch (NoSuchFieldException e) {
+ throw new IllegalArgumentException(e.getMessage() + ": " + valueFieldName);
+ }
+ this.valueField = field;
+ }
+ enumMapper = JVM.is15() ? UseAttributeForEnumMapper.createEnumMapper(mapper) : null;
+ }
+
+ public boolean canConvert(final Class type) {
+ return this.type == type;
+ }
+
+ public void marshal(final Object source, final HierarchicalStreamWriter writer,
+ final MarshallingContext context) {
+ final Class sourceType = source.getClass();
+ final Map defaultFieldDefinition = new HashMap();
+ final String[] tagValue = new String[1];
+ final Object[] realValue = new Object[1];
+ final Class[] fieldType = new Class[1];
+ final Class[] definingType = new Class[1];
+ reflectionProvider.visitSerializableFields(source, new ReflectionProvider.Visitor() {
+ public void visit(final String fieldName, final Class type, final Class definedIn,
+ final Object value) {
+ if (!mapper.shouldSerializeMember(definedIn, fieldName)) {
+ return;
+ }
+
+ final FastField field = new FastField(definedIn, fieldName);
+ final String alias = mapper.serializedMember(definedIn, fieldName);
+ if (!defaultFieldDefinition.containsKey(alias)) {
+ final Class lookupType = sourceType;
+ defaultFieldDefinition.put(
+ alias, reflectionProvider.getField(lookupType, fieldName));
+ } else if (!fieldIsEqual(field)) {
+ final ConversionException exception = new ConversionException(
+ "Cannot write attribute twice for object");
+ exception.add("alias", alias);
+ exception.add("type", sourceType.getName());
+ throw exception;
+ }
+
+ ConverterMatcher converter = UseAttributeForEnumMapper.isEnum(type)
+ ? (ConverterMatcher)enumMapper.getConverterFromItemType(null, type, null)
+ : (ConverterMatcher)mapper.getLocalConverter(definedIn, fieldName);
+ if (converter == null) {
+ converter = lookup.lookupConverterForType(type);
+ }
+
+ if (value != null) {
+ boolean isValueField = valueField != null && fieldIsEqual(field);
+ if (isValueField) {
+ definingType[0] = definedIn;
+ fieldType[0] = type;
+ realValue[0] = value;
+ tagValue[0] = STRUCTURE_MARKER;
+ }
+ if (converter instanceof SingleValueConverter) {
+ final String str = ((SingleValueConverter)converter).toString(value);
+
+ if (isValueField) {
+ tagValue[0] = str;
+ } else {
+ if (str != null) {
+ writer.addAttribute(alias, str);
+ }
+ }
+ } else {
+ if (!isValueField) {
+ final ConversionException exception = new ConversionException(
+ "Cannot write element as attribute");
+ exception.add("alias", alias);
+ exception.add("type", sourceType.getName());
+ throw exception;
+ }
+ }
+ }
+ }
+ });
+
+ if (tagValue[0] != null) {
+ final Class actualType = realValue[0].getClass();
+ final Class defaultType = mapper.defaultImplementationOf(fieldType[0]);
+ if (!actualType.equals(defaultType)) {
+ final String serializedClassName = mapper.serializedClass(actualType);
+ if (!serializedClassName.equals(mapper.serializedClass(defaultType))) {
+ final String attributeName = mapper.aliasForSystemAttribute("class");
+ if (attributeName != null) {
+ writer.addAttribute(attributeName, serializedClassName);
+ }
+ }
+ }
+
+ if (tagValue[0] == STRUCTURE_MARKER) {
+ context.convertAnother(realValue[0]);
+ } else {
+ writer.setValue(tagValue[0]);
+ }
+ }
+ }
+
+ public Object unmarshal(final HierarchicalStreamReader reader,
+ final UnmarshallingContext context) {
+ final Object result = reflectionProvider.newInstance(context.getRequiredType());
+ final Class resultType = result.getClass();
+
+ final Set seenFields = new HashSet();
+ final Iterator it = reader.getAttributeNames();
+
+ final Set systemAttributes = new HashSet();
+ systemAttributes.add(mapper.aliasForSystemAttribute("class"));
+
+ // Process attributes before recursing into child elements.
+ while (it.hasNext()) {
+ final String attrName = (String)it.next();
+ if (systemAttributes.contains(attrName)) {
+ continue;
+ }
+
+ final String fieldName = mapper.realMember(resultType, attrName);
+ final Field field = reflectionProvider.getFieldOrNull(resultType, fieldName);
+ if (field != null) {
+ if (Modifier.isTransient(field.getModifiers())) {
+ continue;
+ }
+
+ Class type = field.getType();
+ final Class declaringClass = field.getDeclaringClass();
+ ConverterMatcher converter = UseAttributeForEnumMapper.isEnum(type)
+ ? (ConverterMatcher)enumMapper.getConverterFromItemType(null, type, null)
+ : (ConverterMatcher)mapper.getLocalConverter(declaringClass, fieldName);
+ if (converter == null) {
+ converter = lookup.lookupConverterForType(type);
+ }
+
+ if (!(converter instanceof SingleValueConverter)) {
+ final ConversionException exception = new ConversionException(
+ "Cannot read field as a single value for object");
+ exception.add("field", fieldName);
+ exception.add("type", resultType.getName());
+ throw exception;
+ }
+
+ if (converter != null) {
+ final Object value = ((SingleValueConverter)converter).fromString(reader
+ .getAttribute(attrName));
+ if (type.isPrimitive()) {
+ type = Primitives.box(type);
+ }
+
+ if (value != null && !type.isAssignableFrom(value.getClass())) {
+ final ConversionException exception = new ConversionException(
+ "Cannot assign object to type");
+ exception.add("object type", value.getClass().getName());
+ exception.add("target type", type.getName());
+ throw exception;
+ }
+
+ reflectionProvider.writeField(result, fieldName, value, declaringClass);
+ if (!seenFields.add(new FastField(declaringClass, fieldName))) {
+ throw new DuplicateFieldException(fieldName
+ + " ["
+ + declaringClass.getName()
+ + "]");
+ }
+ }
+ }
+ }
+
+ if (valueField != null) {
+ final Class classDefiningField = valueField.getDeclaringClass();
+ final String fieldName = valueField.getName();
+ final Field field = fieldName == null ? null : reflectionProvider.getField(
+ classDefiningField, fieldName);
+ if (fieldName == null || field == null) {
+ final ConversionException exception = new ConversionException(
+ "Cannot assign value to field of type");
+ exception.add("element", reader.getNodeName());
+ exception.add("field", fieldName);
+ exception.add("target type", context.getRequiredType().getName());
+ throw exception;
+ }
+
+ Class type;
+ final String classAttribute = HierarchicalStreams
+ .readClassAttribute(reader, mapper);
+ if (classAttribute != null) {
+ type = mapper.realClass(classAttribute);
+ } else {
+ type = mapper.defaultImplementationOf(reflectionProvider.getFieldType(
+ result, fieldName, classDefiningField));
+ }
+
+ final Object value = context.convertAnother(
+ result, type,
+ mapper.getLocalConverter(field.getDeclaringClass(), field.getName()));
+
+ final Class definedType = reflectionProvider.getFieldType(
+ result, fieldName, classDefiningField);
+ if (!definedType.isPrimitive()) {
+ type = definedType;
+ }
+
+ if (value != null && !type.isAssignableFrom(value.getClass())) {
+ final ConversionException exception = new ConversionException(
+ "Cannot assign object to type");
+ exception.add("object type", value.getClass().getName());
+ exception.add("target type", type.getName());
+ throw exception;
+ }
+
+ reflectionProvider.writeField(result, fieldName, value, classDefiningField);
+ if (!seenFields.add(new FastField(classDefiningField, fieldName))) {
+ throw new DuplicateFieldException(fieldName
+ + " ["
+ + classDefiningField.getName()
+ + "]");
+ }
+ }
+ return result;
+ }
+
+ private boolean fieldIsEqual(FastField field) {
+ return valueField.getName().equals(field.getName())
+ && valueField.getDeclaringClass().getName().equals(field.getDeclaringClass());
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/ToStringConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/ToStringConverter.java
new file mode 100644
index 0000000..852f264
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/ToStringConverter.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2006, 2007 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 07. July 2006 by Mauro Talevi
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Convenient converter for classes with natural string representation.
+ *
+ * Converter for classes that adopt the following convention:
+ * - a constructor that takes a single string parameter
+ * - a toString() that is overloaded to issue a string that is meaningful
+ *
+ * @author Paul Hammant
+ */
+public class ToStringConverter extends AbstractSingleValueConverter {
+ private final Class clazz;
+ private final Constructor ctor;
+
+ public ToStringConverter(Class clazz) throws NoSuchMethodException {
+ this.clazz = clazz;
+ ctor = clazz.getConstructor(new Class[] {String.class});
+ }
+ public boolean canConvert(Class type) {
+ return type.equals(clazz);
+ }
+ public String toString(Object obj) {
+ return obj == null ? null : obj.toString();
+ }
+
+ public Object fromString(String str) {
+ try {
+ return ctor.newInstance(new Object[] {str});
+ } catch (InstantiationException e) {
+ throw new ConversionException("Unable to instantiate single String param constructor", e);
+ } catch (IllegalAccessException e) {
+ throw new ConversionException("Unable to access single String param constructor", e);
+ } catch (InvocationTargetException e) {
+ throw new ConversionException("Unable to target single String param constructor", e.getTargetException());
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/UseAttributeForEnumMapper.java b/xstream/src/java/com/thoughtworks/xstream/converters/extended/UseAttributeForEnumMapper.java
new file mode 100644
index 0000000..2b2e22d
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/UseAttributeForEnumMapper.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2013 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 25. September 2013 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.extended;
+
+import com.thoughtworks.xstream.converters.SingleValueConverter;
+import com.thoughtworks.xstream.core.util.DependencyInjectionFactory;
+import com.thoughtworks.xstream.mapper.AttributeMapper;
+import com.thoughtworks.xstream.mapper.DefaultMapper;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+class UseAttributeForEnumMapper extends AttributeMapper {
+
+ public UseAttributeForEnumMapper(Mapper wrapped) {
+ super(wrapped, null, null);
+ }
+
+ /**
+ * @deprecated only used for Java 1.4 support
+ */
+ public static boolean isEnum(Class type) {
+ while(type != null && type != Object.class) {
+ if (type.getName().equals("java.lang.Enum")) {
+ return true;
+ }
+ type = type.getSuperclass();
+ }
+ return false;
+ }
+
+ public boolean shouldLookForSingleValueConverter(String fieldName, Class type,
+ Class definedIn) {
+ return isEnum(type);
+ }
+
+ public SingleValueConverter getConverterFromItemType(String fieldName, Class type,
+ Class definedIn) {
+ return null;
+ }
+
+ public SingleValueConverter getConverterFromAttribute(Class definedIn,
+ String attribute, Class type) {
+ return null;
+ }
+
+ static Mapper createEnumMapper(final Mapper mapper) {
+ try {
+ Class enumMapperClass = Class.forName(
+ "com.thoughtworks.xstream.mapper.EnumMapper", true,
+ Mapper.class.getClassLoader());
+ return (Mapper)DependencyInjectionFactory.newInstance(
+ enumMapperClass,
+ new Object[]{new UseAttributeForEnumMapper(mapper
+ .lookupMapperOfType(DefaultMapper.class))});
+ } catch (Exception e) {
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/extended/package.html b/xstream/src/java/com/thoughtworks/xstream/converters/extended/package.html
new file mode 100644
index 0000000..10e58df
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/extended/package.html
@@ -0,0 +1,14 @@
+
+
+
Extra converters that may not be enabled in XStream by default.
+
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/javabean/BeanProperty.java b/xstream/src/java/com/thoughtworks/xstream/converters/javabean/BeanProperty.java
new file mode 100644
index 0000000..5a8c445
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/javabean/BeanProperty.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009 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 12. April 2005 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.javabean;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.UndeclaredThrowableException;
+
+/**
+ * Provide access to a bean property.
+ *
+ * @author Andrea Aime
+ * @deprecated As of 1.3.1, no longer in use
+ */
+public class BeanProperty {
+
+ /** the target class */
+ private Class memberClass;
+
+ /** the property name */
+ private String propertyName;
+
+ /** the property type */
+ private Class type;
+
+ /** the getter */
+ protected Method getter;
+
+ /** the setter */
+ private Method setter;
+
+ private static final Object[] EMPTY_ARGS = new Object[0];
+
+ /**
+ * Creates a new {@link BeanProperty}that gets the specified property from
+ * the specified class.
+ */
+ public BeanProperty(Class memberClass, String propertyName, Class propertyType) {
+ this.memberClass = memberClass;
+ this.propertyName = propertyName;
+ this.type = propertyType;
+ }
+
+ /**
+ * Gets the base class that this getter accesses.
+ */
+ public Class getBeanClass() {
+ return memberClass;
+ }
+
+ /**
+ * Returns the property type
+ */
+ public Class getType() {
+ return type;
+ }
+
+ /**
+ * Gets the name of the property that this getter extracts.
+ */
+ public String getName() {
+ return propertyName;
+ }
+
+ /**
+ * Gets whether this property can get get.
+ */
+ public boolean isReadable() {
+ return (getter != null);
+ }
+
+ /**
+ * Gets whether this property can be set.
+ */
+ public boolean isWritable() {
+ return (setter != null);
+ }
+
+ /**
+ * Gets the value of this property for the specified Object.
+ * @throws IllegalAccessException
+ * @throws IllegalArgumentException
+ */
+ public Object get(Object member) throws IllegalArgumentException, IllegalAccessException {
+ if (!isReadable())
+ throw new IllegalStateException("Property " + propertyName + " of " + memberClass
+ + " not readable");
+
+ try {
+ return getter.invoke(member, EMPTY_ARGS);
+ } catch (InvocationTargetException e) {
+ throw new UndeclaredThrowableException(e.getTargetException());
+ }
+ }
+
+ /**
+ * Sets the value of this property for the specified Object.
+ * @throws IllegalAccessException
+ * @throws IllegalArgumentException
+ */
+ public Object set(Object member, Object newValue) throws IllegalArgumentException, IllegalAccessException {
+ if (!isWritable())
+ throw new IllegalStateException("Property " + propertyName + " of " + memberClass
+ + " not writable");
+
+ try {
+ return setter.invoke(member, new Object[] { newValue });
+ } catch (InvocationTargetException e) {
+ throw new UndeclaredThrowableException(e.getTargetException());
+ }
+ }
+
+ /**
+ * @param method
+ */
+ public void setGetterMethod(Method method) {
+ this.getter = method;
+
+ }
+
+ /**
+ * @param method
+ */
+ public void setSetterMethod(Method method) {
+ this.setter = method;
+ }
+}
\ No newline at end of file
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/javabean/BeanProvider.java b/xstream/src/java/com/thoughtworks/xstream/converters/javabean/BeanProvider.java
new file mode 100644
index 0000000..82fd9c1
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/javabean/BeanProvider.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2010, 2011, 2013 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 12. April 2005 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.javabean;
+
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+import com.thoughtworks.xstream.converters.reflection.ObjectAccessException;
+
+
+public class BeanProvider implements JavaBeanProvider {
+
+ /**
+ * @deprecated As of 1.4.6
+ */
+ protected static final Object[] NO_PARAMS = new Object[0];
+ protected PropertyDictionary propertyDictionary;
+
+ /**
+ * Construct a BeanProvider that will process the bean properties in their natural order.
+ */
+ public BeanProvider() {
+ this(new PropertyDictionary(new NativePropertySorter()));
+ }
+
+ /**
+ * Construct a BeanProvider with a comparator to sort the bean properties by name in the
+ * dictionary.
+ *
+ * @param propertyNameComparator the comparator
+ */
+ public BeanProvider(final Comparator propertyNameComparator) {
+ this(new PropertyDictionary(new ComparingPropertySorter(propertyNameComparator)));
+ }
+
+ /**
+ * Construct a BeanProvider with a provided property dictionary.
+ *
+ * @param propertyDictionary the property dictionary to use
+ * @since 1.4
+ */
+ public BeanProvider(final PropertyDictionary propertyDictionary) {
+ this.propertyDictionary = propertyDictionary;
+ }
+
+ public Object newInstance(Class type) {
+ try {
+ return type.newInstance();
+ } catch (InstantiationException e) {
+ throw new ObjectAccessException("Cannot construct " + type.getName(), e);
+ } catch (IllegalAccessException e) {
+ throw new ObjectAccessException("Cannot construct " + type.getName(), e);
+ } catch (SecurityException e) {
+ throw new ObjectAccessException("Cannot construct " + type.getName(), e);
+ } catch (ExceptionInInitializerError e) {
+ throw new ObjectAccessException("Cannot construct " + type.getName(), e);
+ }
+ }
+
+ public void visitSerializableProperties(Object object, JavaBeanProvider.Visitor visitor) {
+ PropertyDescriptor[] propertyDescriptors = getSerializableProperties(object);
+ for (int i = 0; i < propertyDescriptors.length; i++ ) {
+ PropertyDescriptor property = propertyDescriptors[i];
+ try {
+ Method readMethod = property.getReadMethod();
+ String name = property.getName();
+ Class definedIn = readMethod.getDeclaringClass();
+ if (visitor.shouldVisit(name, definedIn)) {
+ Object value = readMethod.invoke(object, new Object[0]);
+ visitor.visit(name, property.getPropertyType(), definedIn, value);
+ }
+ } catch (IllegalArgumentException e) {
+ throw new ObjectAccessException("Could not get property "
+ + object.getClass()
+ + "."
+ + property.getName(), e);
+ } catch (IllegalAccessException e) {
+ throw new ObjectAccessException("Could not get property "
+ + object.getClass()
+ + "."
+ + property.getName(), e);
+ } catch (InvocationTargetException e) {
+ throw new ObjectAccessException("Could not get property "
+ + object.getClass()
+ + "."
+ + property.getName(), e);
+ }
+ }
+ }
+
+ public void writeProperty(Object object, String propertyName, Object value) {
+ PropertyDescriptor property = getProperty(propertyName, object.getClass());
+ try {
+ property.getWriteMethod().invoke(object, new Object[]{value});
+ } catch (IllegalArgumentException e) {
+ throw new ObjectAccessException("Could not set property "
+ + object.getClass()
+ + "."
+ + property.getName(), e);
+ } catch (IllegalAccessException e) {
+ throw new ObjectAccessException("Could not set property "
+ + object.getClass()
+ + "."
+ + property.getName(), e);
+ } catch (InvocationTargetException e) {
+ throw new ObjectAccessException("Could not set property "
+ + object.getClass()
+ + "."
+ + property.getName(), e);
+ }
+ }
+
+ public Class getPropertyType(Object object, String name) {
+ return getProperty(name, object.getClass()).getPropertyType();
+ }
+
+ public boolean propertyDefinedInClass(String name, Class type) {
+ return getProperty(name, type) != null;
+ }
+
+ /**
+ * Returns true if the Bean provider can instantiate the specified class
+ */
+ public boolean canInstantiate(Class type) {
+ try {
+ return newInstance(type) != null;
+ } catch (ObjectAccessException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Returns the default constructor, or null if none is found
+ *
+ * @param type
+ * @deprecated As of 1.4.6 use {@link #newInstance(Class)} or {@link #canInstantiate(Class)} directly.
+ */
+ protected Constructor getDefaultConstrutor(Class type) {
+
+ Constructor[] constructors = type.getConstructors();
+ for (int i = 0; i < constructors.length; i++ ) {
+ Constructor c = constructors[i];
+ if (c.getParameterTypes().length == 0 && Modifier.isPublic(c.getModifiers()))
+ return c;
+ }
+ return null;
+ }
+
+ protected PropertyDescriptor[] getSerializableProperties(Object object) {
+ List result = new ArrayList();
+ for (final Iterator iter = propertyDictionary.propertiesFor(object.getClass()); iter.hasNext();) {
+ final PropertyDescriptor descriptor = (PropertyDescriptor)iter.next();
+ if (canStreamProperty(descriptor)) {
+ result.add(descriptor);
+ }
+ }
+ return (PropertyDescriptor[])result.toArray(new PropertyDescriptor[result.size()]);
+ }
+
+ protected boolean canStreamProperty(PropertyDescriptor descriptor) {
+ return descriptor.getReadMethod() != null && descriptor.getWriteMethod() != null;
+ }
+
+ public boolean propertyWriteable(String name, Class type) {
+ PropertyDescriptor property = getProperty(name, type);
+ return property.getWriteMethod() != null;
+ }
+
+ protected PropertyDescriptor getProperty(String name, Class type) {
+ return (PropertyDescriptor)propertyDictionary.propertyDescriptor(type, name);
+ }
+
+ /**
+ * @deprecated As of 1.4 use {@link JavaBeanProvider.Visitor}
+ */
+ public interface Visitor extends JavaBeanProvider.Visitor {
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/javabean/ComparingPropertySorter.java b/xstream/src/java/com/thoughtworks/xstream/converters/javabean/ComparingPropertySorter.java
new file mode 100644
index 0000000..a66d238
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/javabean/ComparingPropertySorter.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2011 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 16. July 2011 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.javabean;
+
+import java.util.Comparator;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * A sorter that uses a comparator to determine the order of the bean properties.
+ *
+ * @author Jörg Schaible
+ * @since 1.4
+ */
+public class ComparingPropertySorter implements PropertySorter {
+
+ private final Comparator comparator;
+
+ public ComparingPropertySorter(final Comparator propertyNameComparator) {
+ this.comparator = propertyNameComparator;
+ }
+
+ public Map sort(final Class type, final Map nameMap) {
+ TreeMap map = new TreeMap(comparator);
+ map.putAll(nameMap);
+ return map;
+ }
+
+}
\ No newline at end of file
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/javabean/JavaBeanConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/javabean/JavaBeanConverter.java
new file mode 100644
index 0000000..9109597
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/javabean/JavaBeanConverter.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 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 12. April 2005 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.javabean;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.converters.reflection.MissingFieldException;
+import com.thoughtworks.xstream.core.util.FastField;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+/**
+ * Can convert any bean with a public default constructor. The {@link BeanProvider} used as
+ * default is based on {@link java.beans.BeanInfo}. Indexed properties are currently not supported.
+ */
+public class JavaBeanConverter implements Converter {
+
+ /*
+ * TODO:
+ * - support indexed properties
+ * - support attributes (XSTR-620)
+ * - support local converters (XSTR-601)
+ * Problem: Mappers take definitions based on reflection, they don't know about bean info
+ */
+ protected final Mapper mapper;
+ protected final JavaBeanProvider beanProvider;
+ private final Class type;
+
+ /**
+ * @deprecated As of 1.3, no necessity for field anymore.
+ */
+ private String classAttributeIdentifier;
+
+ public JavaBeanConverter(Mapper mapper) {
+ this(mapper, (Class)null);
+ }
+
+ public JavaBeanConverter(Mapper mapper, Class type) {
+ this(mapper, new BeanProvider(), type);
+ }
+
+ public JavaBeanConverter(Mapper mapper, JavaBeanProvider beanProvider) {
+ this(mapper,beanProvider, null);
+ }
+
+ public JavaBeanConverter(Mapper mapper, JavaBeanProvider beanProvider, Class type) {
+ this.mapper = mapper;
+ this.beanProvider = beanProvider;
+ this.type = type;
+ }
+
+ /**
+ * @deprecated As of 1.3, use {@link #JavaBeanConverter(Mapper)} and {@link com.thoughtworks.xstream.XStream#aliasAttribute(String, String)}
+ */
+ public JavaBeanConverter(Mapper mapper, String classAttributeIdentifier) {
+ this(mapper, new BeanProvider());
+ this.classAttributeIdentifier = classAttributeIdentifier;
+ }
+
+ /**
+ * Checks if the bean provider can instantiate this type.
+ * If you need less strict checks, subclass JavaBeanConverter
+ */
+ public boolean canConvert(Class type) {
+ return (this.type == null || this.type == type) && beanProvider.canInstantiate(type);
+ }
+
+ public void marshal(final Object source, final HierarchicalStreamWriter writer, final MarshallingContext context) {
+ final String classAttributeName = classAttributeIdentifier != null ? classAttributeIdentifier : mapper.aliasForSystemAttribute("class");
+ beanProvider.visitSerializableProperties(source, new JavaBeanProvider.Visitor() {
+ public boolean shouldVisit(String name, Class definedIn) {
+ return mapper.shouldSerializeMember(definedIn, name);
+ }
+
+ public void visit(String propertyName, Class fieldType, Class definedIn, Object newObj) {
+ if (newObj != null) {
+ writeField(propertyName, fieldType, newObj, definedIn);
+ }
+ }
+
+ private void writeField(String propertyName, Class fieldType, Object newObj, Class definedIn) {
+ Class actualType = newObj.getClass();
+ Class defaultType = mapper.defaultImplementationOf(fieldType);
+ String serializedMember = mapper.serializedMember(source.getClass(), propertyName);
+ ExtendedHierarchicalStreamWriterHelper.startNode(writer, serializedMember, actualType);
+ if (!actualType.equals(defaultType) && classAttributeName != null) {
+ writer.addAttribute(classAttributeName, mapper.serializedClass(actualType));
+ }
+ context.convertAnother(newObj);
+
+ writer.endNode();
+ }
+ });
+ }
+
+ public Object unmarshal(final HierarchicalStreamReader reader, final UnmarshallingContext context) {
+ final Object result = instantiateNewInstance(context);
+ final Set seenProperties = new HashSet() {
+ public boolean add(Object e) {
+ if (!super.add(e)) {
+ throw new DuplicatePropertyException(((FastField)e).getName());
+ }
+ return true;
+ }
+ };
+
+ Class resultType = result.getClass();
+ while (reader.hasMoreChildren()) {
+ reader.moveDown();
+
+ String propertyName = mapper.realMember(resultType, reader.getNodeName());
+
+ if (mapper.shouldSerializeMember(resultType, propertyName)) {
+ boolean propertyExistsInClass = beanProvider.propertyDefinedInClass(propertyName, resultType);
+
+ if (propertyExistsInClass) {
+ Class type = determineType(reader, result, propertyName);
+ Object value = context.convertAnother(result, type);
+ beanProvider.writeProperty(result, propertyName, value);
+ seenProperties.add(new FastField(resultType, propertyName));
+ } else {
+ throw new MissingFieldException(resultType.getName(), propertyName);
+ }
+ }
+ reader.moveUp();
+ }
+
+ return result;
+ }
+
+ private Object instantiateNewInstance(UnmarshallingContext context) {
+ Object result = context.currentObject();
+ if (result == null) {
+ result = beanProvider.newInstance(context.getRequiredType());
+ }
+ return result;
+ }
+
+ private Class determineType(HierarchicalStreamReader reader, Object result, String fieldName) {
+ final String classAttributeName = classAttributeIdentifier != null ? classAttributeIdentifier : mapper.aliasForSystemAttribute("class");
+ String classAttribute = classAttributeName == null ? null : reader.getAttribute(classAttributeName);
+ if (classAttribute != null) {
+ return mapper.realClass(classAttribute);
+ } else {
+ return mapper.defaultImplementationOf(beanProvider.getPropertyType(result, fieldName));
+ }
+ }
+
+ /**
+ * @deprecated As of 1.3
+ */
+ public static class DuplicateFieldException extends ConversionException {
+ public DuplicateFieldException(String msg) {
+ super(msg);
+ }
+ }
+
+ /**
+ * Exception to indicate double processing of a property to avoid silent clobbering.
+ *
+ * @author Jörg Schaible
+ * @since 1.4.2
+ */
+ public static class DuplicatePropertyException extends ConversionException {
+ public DuplicatePropertyException(String msg) {
+ super("Duplicate property " + msg);
+ add("property", msg);
+ }
+ }
+}
\ No newline at end of file
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/javabean/JavaBeanProvider.java b/xstream/src/java/com/thoughtworks/xstream/converters/javabean/JavaBeanProvider.java
new file mode 100644
index 0000000..fb63b50
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/javabean/JavaBeanProvider.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2011 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 07. July 2011 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.javabean;
+
+
+/**
+ * @author Jörg Schaible
+ *
+ * @since 1.4
+ */
+public interface JavaBeanProvider {
+
+ Object newInstance(Class type);
+
+ void visitSerializableProperties(Object object, Visitor visitor);
+
+ void writeProperty(Object object, String propertyName, Object value);
+
+ Class getPropertyType(Object object, String name);
+
+ boolean propertyDefinedInClass(String name, Class type);
+
+ /**
+ * Returns true if the Bean provider can instantiate the specified class
+ */
+ boolean canInstantiate(Class type);
+
+ public interface Visitor {
+ boolean shouldVisit(String name, Class definedIn);
+ void visit(String name, Class type, Class definedIn, Object value);
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/javabean/NativePropertySorter.java b/xstream/src/java/com/thoughtworks/xstream/converters/javabean/NativePropertySorter.java
new file mode 100644
index 0000000..fea1143
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/javabean/NativePropertySorter.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2011 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 16. July 2011 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.javabean;
+
+import java.util.Map;
+
+
+/**
+ * A sorter that keeps the natural order of the bean properties as they are returned by the
+ * JavaBean introspection.
+ *
+ * @author Jörg Schaible
+ * @since 1.4
+ */
+public class NativePropertySorter implements PropertySorter {
+
+ public Map sort(final Class type, final Map nameMap) {
+ return nameMap;
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/javabean/PropertyDictionary.java b/xstream/src/java/com/thoughtworks/xstream/converters/javabean/PropertyDictionary.java
new file mode 100644
index 0000000..c5c6b19
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/javabean/PropertyDictionary.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009, 2011 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 12. April 2005 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.javabean;
+
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import com.thoughtworks.xstream.converters.reflection.MissingFieldException;
+import com.thoughtworks.xstream.converters.reflection.ObjectAccessException;
+import com.thoughtworks.xstream.core.Caching;
+import com.thoughtworks.xstream.core.util.OrderRetainingMap;
+
+
+/**
+ * Builds the properties maps for each bean and caches them.
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ */
+public class PropertyDictionary implements Caching {
+ private transient Map propertyNameCache = Collections.synchronizedMap(new HashMap());
+ private final PropertySorter sorter;
+
+ public PropertyDictionary() {
+ this(new NativePropertySorter());
+ }
+
+ public PropertyDictionary(PropertySorter sorter) {
+ this.sorter = sorter;
+ }
+
+ /**
+ * @deprecated As of 1.3.1, use {@link #propertiesFor(Class)} instead
+ */
+ public Iterator serializablePropertiesFor(Class type) {
+ Collection beanProperties = new ArrayList();
+ Collection descriptors = buildMap(type).values();
+ for (Iterator iter = descriptors.iterator(); iter.hasNext();) {
+ PropertyDescriptor descriptor = (PropertyDescriptor)iter.next();
+ if (descriptor.getReadMethod() != null && descriptor.getWriteMethod() != null) {
+ beanProperties.add(new BeanProperty(type, descriptor.getName(), descriptor
+ .getPropertyType()));
+ }
+ }
+ return beanProperties.iterator();
+ }
+
+ /**
+ * Locates a serializable property.
+ *
+ * @param cls
+ * @param name
+ * @deprecated As of 1.3.1, use {@link #propertyDescriptor(Class, String)} instead
+ */
+ public BeanProperty property(Class cls, String name) {
+ BeanProperty beanProperty = null;
+ PropertyDescriptor descriptor = (PropertyDescriptor)buildMap(cls).get(name);
+ if (descriptor == null) {
+ throw new MissingFieldException(cls.getName(), name);
+ }
+ if (descriptor.getReadMethod() != null && descriptor.getWriteMethod() != null) {
+ beanProperty = new BeanProperty(
+ cls, descriptor.getName(), descriptor.getPropertyType());
+ }
+ return beanProperty;
+ }
+
+ public Iterator propertiesFor(Class type) {
+ return buildMap(type).values().iterator();
+ }
+
+ /**
+ * Locates a property descriptor.
+ *
+ * @param type
+ * @param name
+ */
+ public PropertyDescriptor propertyDescriptor(Class type, String name) {
+ PropertyDescriptor descriptor = (PropertyDescriptor)buildMap(type).get(name);
+ if (descriptor == null) {
+ throw new MissingFieldException(type.getName(), name);
+ }
+ return descriptor;
+ }
+
+ private Map buildMap(Class type) {
+ Map nameMap = (Map)propertyNameCache.get(type);
+ if (nameMap == null) {
+ BeanInfo beanInfo;
+ try {
+ beanInfo = Introspector.getBeanInfo(type, Object.class);
+ } catch (IntrospectionException e) {
+ throw new ObjectAccessException(
+ "Cannot get BeanInfo of type " + type.getName(), e);
+ }
+ nameMap = new OrderRetainingMap();
+ PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
+ for (int i = 0; i < propertyDescriptors.length; i++ ) {
+ PropertyDescriptor descriptor = propertyDescriptors[i];
+ nameMap.put(descriptor.getName(), descriptor);
+ }
+ nameMap = sorter.sort(type, nameMap);
+ propertyNameCache.put(type, nameMap);
+ }
+ return nameMap;
+ }
+
+ public void flushCache() {
+ propertyNameCache.clear();
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/javabean/PropertySorter.java b/xstream/src/java/com/thoughtworks/xstream/converters/javabean/PropertySorter.java
new file mode 100644
index 0000000..29819bb
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/javabean/PropertySorter.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2011 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 16. July 2011 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.javabean;
+
+import java.beans.PropertyDescriptor;
+import java.util.Map;
+
+/**
+ * An interface capable of sorting Java bean properties. Implement this interface if you
+ * want to customize the order in which XStream serializes the properties of a bean.
+ *
+ * @author Jörg Schaible
+ * @since 1.4
+ */
+public interface PropertySorter {
+
+ /**
+ * Sort the properties of a bean type. The method will be called with the class type
+ * that contains all the properties and a Map that retains the order in which the
+ * elements have been added. The sequence in which elements are returned by an iterator
+ * defines the processing order of the properties. An implementation may create a
+ * different Map with similar semantic, add all elements of the original map and return
+ * the new one.
+ *
+ * @param type the bean class that contains all the properties
+ * @param nameMap the map to sort, key is the property name, value the
+ * {@link PropertyDescriptor}
+ * @return the sorted nameMap
+ * @since 1.4
+ */
+ Map sort(Class type, Map nameMap);
+
+}
\ No newline at end of file
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/reflection/AbstractAttributedCharacterIteratorAttributeConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/AbstractAttributedCharacterIteratorAttributeConverter.java
new file mode 100644
index 0000000..ac65fab
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/AbstractAttributedCharacterIteratorAttributeConverter.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2007, 2013 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 01. February 2007 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.reflection;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter;
+import com.thoughtworks.xstream.core.util.Fields;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.text.AttributedCharacterIterator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+
+/**
+ * An abstract converter implementation for constants of
+ * {@link java.text.AttributedCharacterIterator.Attribute} and derived types.
+ *
+ * @author Jörg Schaible
+ * @since 1.2.2
+ */
+public class AbstractAttributedCharacterIteratorAttributeConverter extends
+ AbstractSingleValueConverter {
+
+ private static final Map instanceMaps = new HashMap();
+ private static final Method getName;
+ static {
+ Method method = null;
+ try {
+ method = AttributedCharacterIterator.Attribute.class.getDeclaredMethod(
+ "getName", (Class[])null);
+ if (!method.isAccessible()) {
+ method.setAccessible(true);
+ }
+ } catch (SecurityException e) {
+ // ignore for now
+ } catch (NoSuchMethodException e) {
+ // ignore for now
+ }
+ getName = method;
+ }
+
+ private final Class type;
+ private transient Map attributeMap;
+
+ public AbstractAttributedCharacterIteratorAttributeConverter(final Class type) {
+ super();
+ if (!AttributedCharacterIterator.Attribute.class.isAssignableFrom(type)) {
+ throw new IllegalArgumentException(type.getName()
+ + " is not a " + AttributedCharacterIterator.Attribute.class.getName());
+ }
+ this.type = type;
+ readResolve();
+ }
+
+ public boolean canConvert(final Class type) {
+ return type == this.type && !attributeMap.isEmpty();
+ }
+
+ public String toString(final Object source) {
+ return getName((AttributedCharacterIterator.Attribute)source);
+ }
+
+ private String getName(AttributedCharacterIterator.Attribute attribute) {
+ Exception ex = null;
+ if (getName != null) {
+ try {
+ return (String)getName.invoke(attribute, (Object[])null);
+ } catch (IllegalAccessException e) {
+ ex = e;
+ } catch (InvocationTargetException e) {
+ ex = e;
+ }
+ }
+ String s = attribute.toString();
+ String className = attribute.getClass().getName();
+ if (s.startsWith(className)) {
+ return s.substring(className.length()+1, s.length()-1);
+ }
+ throw new ConversionException("Cannot find name of attribute of type " + className, ex);
+ }
+
+ public Object fromString(final String str) {
+ if (attributeMap.containsKey(str)) {
+ return attributeMap.get(str);
+ }
+ throw new ConversionException("Cannot find attribute of type " + type.getName() + " with name " + str);
+ }
+
+ private Object readResolve() {
+ attributeMap = (Map)instanceMaps.get(type.getName());
+ if (attributeMap == null) {
+ attributeMap = new HashMap();
+ Field instanceMap = Fields.locate(type, Map.class, true);
+ if (instanceMap != null) {
+ try {
+ Map map = (Map)Fields.read(instanceMap, null);
+ if (map != null) {
+ boolean valid = true;
+ for (Iterator iter = map.entrySet().iterator(); valid && iter.hasNext(); ) {
+ Map.Entry entry = (Map.Entry)iter.next();
+ valid = entry.getKey().getClass() == String.class && entry.getValue().getClass() == type;
+ }
+ if (valid) {
+ attributeMap.putAll(map);
+ }
+ }
+ } catch (ObjectAccessException e) {
+ }
+ }
+ if (attributeMap.isEmpty()) {
+ try {
+ Field[] fields = type.getDeclaredFields();
+ for(int i = 0; i < fields.length; ++i) {
+ if(fields[i].getType() == type == Modifier.isStatic(fields[i].getModifiers())) {
+ AttributedCharacterIterator.Attribute attribute =
+ (AttributedCharacterIterator.Attribute)Fields.read(fields[i], null);
+ attributeMap.put(toString(attribute), attribute);
+ }
+ }
+ } catch (SecurityException e) {
+ attributeMap.clear();
+ } catch (ObjectAccessException e) {
+ attributeMap.clear();
+ } catch (NoClassDefFoundError e) {
+ attributeMap.clear();
+ }
+ }
+ instanceMaps.put(type.getName(), attributeMap);
+ }
+ return this;
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/reflection/AbstractReflectionConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/AbstractReflectionConverter.java
new file mode 100644
index 0000000..ffde863
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/AbstractReflectionConverter.java
@@ -0,0 +1,683 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 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 02. March 2006 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.reflection;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.SingleValueConverter;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.core.Caching;
+import com.thoughtworks.xstream.core.ReferencingMarshallingContext;
+import com.thoughtworks.xstream.core.util.ArrayIterator;
+import com.thoughtworks.xstream.core.util.FastField;
+import com.thoughtworks.xstream.core.util.HierarchicalStreams;
+import com.thoughtworks.xstream.core.util.Primitives;
+import com.thoughtworks.xstream.core.util.SerializationMembers;
+import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.mapper.CannotResolveClassException;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+public abstract class AbstractReflectionConverter implements Converter, Caching {
+
+ protected final ReflectionProvider reflectionProvider;
+ protected final Mapper mapper;
+ /**
+ * @deprecated As of 1.4.8, use {@link #serializationMembers}.
+ */
+ protected transient SerializationMethodInvoker serializationMethodInvoker;
+ protected transient SerializationMembers serializationMembers;
+ private transient ReflectionProvider pureJavaReflectionProvider;
+
+ public AbstractReflectionConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
+ this.mapper = mapper;
+ this.reflectionProvider = reflectionProvider;
+ serializationMethodInvoker = new SerializationMethodInvoker();
+ serializationMembers = serializationMethodInvoker.serializationMembers;
+ }
+
+ protected boolean canAccess(Class type) {
+ try {
+ reflectionProvider.getFieldOrNull(type, "%");
+ return true;
+ } catch (NoClassDefFoundError e) {
+ // restricted type in GAE
+ }
+ return false;
+ }
+
+ public void marshal(Object original, final HierarchicalStreamWriter writer,
+ final MarshallingContext context) {
+ final Object source = serializationMembers.callWriteReplace(original);
+
+ if (source != original && context instanceof ReferencingMarshallingContext) {
+ ((ReferencingMarshallingContext)context).replace(original, source);
+ }
+ if (source.getClass() != original.getClass()) {
+ String attributeName = mapper.aliasForSystemAttribute("resolves-to");
+ if (attributeName != null) {
+ writer.addAttribute(attributeName, mapper.serializedClass(source.getClass()));
+ }
+ context.convertAnother(source);
+ } else {
+ doMarshal(source, writer, context);
+ }
+ }
+
+ protected void doMarshal(final Object source, final HierarchicalStreamWriter writer,
+ final MarshallingContext context) {
+ final List fields = new ArrayList();
+ final Map defaultFieldDefinition = new HashMap();
+
+ // Attributes might be preferred to child elements ...
+ reflectionProvider.visitSerializableFields(source, new ReflectionProvider.Visitor() {
+ final Set writtenAttributes = new HashSet();
+
+ public void visit(String fieldName, Class type, Class definedIn, Object value) {
+ if (!mapper.shouldSerializeMember(definedIn, fieldName)) {
+ return;
+ }
+ if (!defaultFieldDefinition.containsKey(fieldName)) {
+ Class lookupType = source.getClass();
+ // See XSTR-457 and OmitFieldsTest
+ if (definedIn != source.getClass()
+ && !mapper.shouldSerializeMember(lookupType, fieldName)) {
+ lookupType = definedIn;
+ }
+ defaultFieldDefinition.put(
+ fieldName, reflectionProvider.getField(lookupType, fieldName));
+ }
+
+ SingleValueConverter converter = mapper.getConverterFromItemType(
+ fieldName, type, definedIn);
+ if (converter != null) {
+ final String attribute = mapper.aliasForAttribute(mapper.serializedMember(
+ definedIn, fieldName));
+ if (value != null) {
+ if (writtenAttributes.contains(fieldName)) { // TODO: use attribute
+ throw new ConversionException("Cannot write field with name '"
+ + fieldName
+ + "' twice as attribute for object of type "
+ + source.getClass().getName());
+ }
+ final String str = converter.toString(value);
+ if (str != null) {
+ writer.addAttribute(attribute, str);
+ }
+ }
+ writtenAttributes.add(fieldName); // TODO: use attribute
+ } else {
+ fields.add(new FieldInfo(fieldName, type, definedIn, value));
+ }
+ }
+ });
+
+ new Object() {
+ {
+ for (Iterator fieldIter = fields.iterator(); fieldIter.hasNext();) {
+ FieldInfo info = (FieldInfo)fieldIter.next();
+ if (info.value != null) {
+ Mapper.ImplicitCollectionMapping mapping = mapper
+ .getImplicitCollectionDefForFieldName(
+ source.getClass(), info.fieldName);
+ if (mapping != null) {
+ if (context instanceof ReferencingMarshallingContext) {
+ if (info.value != Collections.EMPTY_LIST
+ && info.value != Collections.EMPTY_SET
+ && info.value != Collections.EMPTY_MAP) {
+ ReferencingMarshallingContext refContext = (ReferencingMarshallingContext)context;
+ refContext.registerImplicit(info.value);
+ }
+ }
+ final boolean isCollection = info.value instanceof Collection;
+ final boolean isMap = info.value instanceof Map;
+ final boolean isEntry = isMap && mapping.getKeyFieldName() == null;
+ final boolean isArray = info.value.getClass().isArray();
+ for (Iterator iter = isArray
+ ? new ArrayIterator(info.value)
+ : isCollection ? ((Collection)info.value).iterator() : isEntry
+ ? ((Map)info.value).entrySet().iterator()
+ : ((Map)info.value).values().iterator(); iter.hasNext();) {
+ Object obj = iter.next();
+ final String itemName;
+ final Class itemType;
+ if (obj == null) {
+ itemType = Object.class;
+ itemName = mapper.serializedClass(null);
+ } else if (isEntry) {
+ final String entryName = mapping.getItemFieldName() != null
+ ? mapping.getItemFieldName()
+ : mapper.serializedClass(Map.Entry.class);
+ Map.Entry entry = (Map.Entry)obj;
+ ExtendedHierarchicalStreamWriterHelper.startNode(
+ writer, entryName, entry.getClass());
+ writeItem(entry.getKey(), context, writer);
+ writeItem(entry.getValue(), context, writer);
+ writer.endNode();
+ continue;
+ } else if (mapping.getItemFieldName() != null) {
+ itemType = mapping.getItemType();
+ itemName = mapping.getItemFieldName();
+ } else {
+ itemType = obj.getClass();
+ itemName = mapper.serializedClass(itemType);
+ }
+ writeField(
+ info.fieldName, itemName, itemType, info.definedIn, obj);
+ }
+ } else {
+ writeField(
+ info.fieldName, null, info.type, info.definedIn, info.value);
+ }
+ }
+ }
+
+ }
+
+ void writeField(String fieldName, String aliasName, Class fieldType,
+ Class definedIn, Object newObj) {
+ Class actualType = newObj != null ? newObj.getClass() : fieldType;
+ ExtendedHierarchicalStreamWriterHelper.startNode(writer, aliasName != null
+ ? aliasName
+ : mapper.serializedMember(source.getClass(), fieldName), actualType);
+
+ if (newObj != null) {
+ Class defaultType = mapper.defaultImplementationOf(fieldType);
+ if (!actualType.equals(defaultType)) {
+ String serializedClassName = mapper.serializedClass(actualType);
+ if (!serializedClassName.equals(mapper.serializedClass(defaultType))) {
+ String attributeName = mapper.aliasForSystemAttribute("class");
+ if (attributeName != null) {
+ writer.addAttribute(attributeName, serializedClassName);
+ }
+ }
+ }
+
+ final Field defaultField = (Field)defaultFieldDefinition.get(fieldName);
+ if (defaultField.getDeclaringClass() != definedIn) {
+ String attributeName = mapper.aliasForSystemAttribute("defined-in");
+ if (attributeName != null) {
+ writer.addAttribute(
+ attributeName, mapper.serializedClass(definedIn));
+ }
+ }
+
+ Field field = reflectionProvider.getField(definedIn, fieldName);
+ marshallField(context, newObj, field);
+ }
+ writer.endNode();
+ }
+
+ void writeItem(Object item, MarshallingContext context,
+ HierarchicalStreamWriter writer) {
+ if (item == null) {
+ String name = mapper.serializedClass(null);
+ ExtendedHierarchicalStreamWriterHelper.startNode(
+ writer, name, Mapper.Null.class);
+ writer.endNode();
+ } else {
+ String name = mapper.serializedClass(item.getClass());
+ ExtendedHierarchicalStreamWriterHelper.startNode(
+ writer, name, item.getClass());
+ context.convertAnother(item);
+ writer.endNode();
+ }
+ }
+ };
+ }
+
+ protected void marshallField(final MarshallingContext context, Object newObj, Field field) {
+ context.convertAnother(
+ newObj, mapper.getLocalConverter(field.getDeclaringClass(), field.getName()));
+ }
+
+ public Object unmarshal(final HierarchicalStreamReader reader,
+ final UnmarshallingContext context) {
+ Object result = instantiateNewInstance(reader, context);
+ result = doUnmarshal(result, reader, context);
+ return serializationMembers.callReadResolve(result);
+ }
+
+ public Object doUnmarshal(final Object result, final HierarchicalStreamReader reader,
+ final UnmarshallingContext context) {
+ final Class resultType = result.getClass();
+ final Set seenFields = new HashSet() {
+ public boolean add(Object e) {
+ if (!super.add(e)) {
+ throw new DuplicateFieldException(((FastField)e).getName());
+ }
+ return true;
+ }
+ };
+
+ // process attributes before recursing into child elements.
+ Iterator it = reader.getAttributeNames();
+ while (it.hasNext()) {
+ String attrAlias = (String)it.next();
+ // TODO: realMember should return FastField
+ String attrName = mapper
+ .realMember(resultType, mapper.attributeForAlias(attrAlias));
+ Field field = reflectionProvider.getFieldOrNull(resultType, attrName);
+ if (field != null && shouldUnmarshalField(field)) {
+ Class classDefiningField = field.getDeclaringClass();
+ if (!mapper.shouldSerializeMember(classDefiningField, attrName)) {
+ continue;
+ }
+
+ // we need a converter that produces a string representation only
+ SingleValueConverter converter = mapper.getConverterFromAttribute(
+ classDefiningField, attrName, field.getType());
+ Class type = field.getType();
+ if (converter != null) {
+ Object value = converter.fromString(reader.getAttribute(attrAlias));
+ if (type.isPrimitive()) {
+ type = Primitives.box(type);
+ }
+ if (value != null && !type.isAssignableFrom(value.getClass())) {
+ throw new ConversionException("Cannot convert type "
+ + value.getClass().getName()
+ + " to type "
+ + type.getName());
+ }
+ seenFields.add(new FastField(classDefiningField, attrName));
+ reflectionProvider.writeField(result, attrName, value, classDefiningField);
+ }
+ }
+ }
+
+ Map implicitCollectionsForCurrentObject = null;
+ while (reader.hasMoreChildren()) {
+ reader.moveDown();
+
+ String originalNodeName = reader.getNodeName();
+ Class explicitDeclaringClass = readDeclaringClass(reader);
+ Class fieldDeclaringClass = explicitDeclaringClass == null
+ ? resultType
+ : explicitDeclaringClass;
+ String fieldName = mapper.realMember(fieldDeclaringClass, originalNodeName);
+ Mapper.ImplicitCollectionMapping implicitCollectionMapping = mapper
+ .getImplicitCollectionDefForFieldName(fieldDeclaringClass, fieldName);
+ final Object value;
+ String implicitFieldName = null;
+ Field field = null;
+ Class type = null;
+ if (implicitCollectionMapping == null) {
+ // no item of an implicit collection for this name ... do we have a field?
+ field = reflectionProvider.getFieldOrNull(fieldDeclaringClass, fieldName);
+ if (field == null) {
+ // it is not a field ... do we have a field alias?
+ Class itemType = mapper.getItemTypeForItemFieldName(resultType, fieldName);
+ if (itemType != null) {
+ String classAttribute = HierarchicalStreams.readClassAttribute(
+ reader, mapper);
+ if (classAttribute != null) {
+ type = mapper.realClass(classAttribute);
+ } else {
+ type = itemType;
+ }
+ } else {
+ // it is not an alias ... do we have an element of an implicit
+ // collection based on type only?
+ try {
+ type = mapper.realClass(originalNodeName);
+ implicitFieldName = mapper.getFieldNameForItemTypeAndName(
+ context.getRequiredType(), type, originalNodeName);
+ } catch (CannotResolveClassException e) {
+ // type stays null ...
+ }
+ if (type == null || (type != null && implicitFieldName == null)) {
+ // either not a type or element is a type alias, but does not
+ // belong to an implicit field
+ handleUnknownField(
+ explicitDeclaringClass, fieldName, resultType, originalNodeName);
+
+ // element is unknown in declaring class, ignore it now
+ type = null;
+ }
+ }
+ if (type == null) {
+ // no type, no value
+ value = null;
+ } else {
+ if (Map.Entry.class.equals(type)) {
+ // it is an element of an implicit map with two elements now for
+ // key and value
+ reader.moveDown();
+ final Object key = context.convertAnother(
+ result, HierarchicalStreams.readClassType(reader, mapper));
+ reader.moveUp();
+ reader.moveDown();
+ final Object v = context.convertAnother(
+ result, HierarchicalStreams.readClassType(reader, mapper));
+ reader.moveUp();
+ value = Collections.singletonMap(key, v)
+ .entrySet().iterator().next();
+ } else {
+ // recurse info hierarchy
+ value = context.convertAnother(result, type);
+ }
+ }
+ } else {
+ boolean fieldAlreadyChecked = false;
+
+ // we have a field, but do we have to address a hidden one?
+ if (explicitDeclaringClass == null) {
+ while (field != null
+ && !(fieldAlreadyChecked = shouldUnmarshalField(field)
+ && mapper.shouldSerializeMember(
+ field.getDeclaringClass(), fieldName))) {
+ field = reflectionProvider.getFieldOrNull(field
+ .getDeclaringClass()
+ .getSuperclass(), fieldName);
+ }
+ }
+ if (field != null
+ && (fieldAlreadyChecked || (shouldUnmarshalField(field) && mapper
+ .shouldSerializeMember(field.getDeclaringClass(), fieldName)))) {
+
+ String classAttribute = HierarchicalStreams.readClassAttribute(
+ reader, mapper);
+ if (classAttribute != null) {
+ type = mapper.realClass(classAttribute);
+ } else {
+ type = mapper.defaultImplementationOf(field.getType());
+ }
+ // TODO the reflection provider should already return the proper field
+ value = unmarshallField(context, result, type, field);
+ Class definedType = field.getType();
+ if (!definedType.isPrimitive()) {
+ type = definedType;
+ }
+ } else {
+ value = null;
+ }
+ }
+ } else {
+ // we have an implicit collection with defined names
+ implicitFieldName = implicitCollectionMapping.getFieldName();
+ type = implicitCollectionMapping.getItemType();
+ if (type == null) {
+ String classAttribute = HierarchicalStreams.readClassAttribute(
+ reader, mapper);
+ type = mapper.realClass(classAttribute != null
+ ? classAttribute
+ : originalNodeName);
+ }
+ value = context.convertAnother(result, type);
+ }
+
+ if (value != null && !type.isAssignableFrom(value.getClass())) {
+ throw new ConversionException("Cannot convert type "
+ + value.getClass().getName()
+ + " to type "
+ + type.getName());
+ }
+
+ if (field != null) {
+ reflectionProvider.writeField(result, fieldName, value, field.getDeclaringClass());
+ seenFields.add(new FastField(field.getDeclaringClass(), fieldName));
+ } else if (type != null) {
+ if (implicitFieldName == null) {
+ // look for implicit field
+ implicitFieldName = mapper.getFieldNameForItemTypeAndName(
+ context.getRequiredType(),
+ value != null ? value.getClass() : Mapper.Null.class,
+ originalNodeName);
+ }
+ if (implicitCollectionsForCurrentObject == null) {
+ implicitCollectionsForCurrentObject = new HashMap();
+ }
+ writeValueToImplicitCollection(
+ value, implicitCollectionsForCurrentObject, result, implicitFieldName);
+ }
+
+ reader.moveUp();
+ }
+
+ if (implicitCollectionsForCurrentObject != null) {
+ for (Iterator iter = implicitCollectionsForCurrentObject.entrySet().iterator(); iter
+ .hasNext();) {
+ Map.Entry entry = (Map.Entry)iter.next();
+ Object value = entry.getValue();
+ if (value instanceof ArraysList) {
+ Object array = ((ArraysList)value).toPhysicalArray();
+ reflectionProvider.writeField(result, (String)entry.getKey(), array, null);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ protected Object unmarshallField(final UnmarshallingContext context, final Object result,
+ Class type, Field field) {
+ return context.convertAnother(
+ result, type, mapper.getLocalConverter(field.getDeclaringClass(), field.getName()));
+ }
+
+ protected boolean shouldUnmarshalTransientFields() {
+ return false;
+ }
+
+ protected boolean shouldUnmarshalField(Field field) {
+ return !(Modifier.isTransient(field.getModifiers()) && !shouldUnmarshalTransientFields());
+ }
+
+ private void handleUnknownField(Class classDefiningField, String fieldName,
+ Class resultType, String originalNodeName) {
+ if (classDefiningField == null) {
+ for (Class cls = resultType; cls != null; cls = cls.getSuperclass()) {
+ if (!mapper.shouldSerializeMember(cls, originalNodeName)) {
+ return;
+ }
+ }
+ }
+ throw new UnknownFieldException(resultType.getName(), fieldName);
+ }
+
+ private void writeValueToImplicitCollection(Object value, Map implicitCollections, Object result, String implicitFieldName) {
+ Collection collection = (Collection)implicitCollections.get(implicitFieldName);
+ if (collection == null) {
+ Class physicalFieldType = reflectionProvider.getFieldType(
+ result, implicitFieldName, null);
+ if (physicalFieldType.isArray()) {
+ collection = new ArraysList(physicalFieldType);
+ } else {
+ Class fieldType = mapper.defaultImplementationOf(physicalFieldType);
+ if (!(Collection.class.isAssignableFrom(fieldType) || Map.class
+ .isAssignableFrom(fieldType))) {
+ throw new ObjectAccessException(
+ "Field "
+ + implicitFieldName
+ + " of "
+ + result.getClass().getName()
+ + " is configured for an implicit Collection or Map, but field is of type "
+ + fieldType.getName());
+ }
+ if (pureJavaReflectionProvider == null) {
+ pureJavaReflectionProvider = new PureJavaReflectionProvider();
+ }
+ Object instance = pureJavaReflectionProvider.newInstance(fieldType);
+ if (instance instanceof Collection) {
+ collection = (Collection)instance;
+ } else {
+ Mapper.ImplicitCollectionMapping implicitCollectionMapping = mapper
+ .getImplicitCollectionDefForFieldName(result.getClass(), implicitFieldName);
+ collection = new MappingList(
+ (Map)instance, implicitCollectionMapping.getKeyFieldName());
+ }
+ reflectionProvider.writeField(result, implicitFieldName, instance, null);
+ }
+ implicitCollections.put(implicitFieldName, collection);
+ }
+ collection.add(value);
+ }
+
+ private Class readDeclaringClass(HierarchicalStreamReader reader) {
+ String attributeName = mapper.aliasForSystemAttribute("defined-in");
+ String definedIn = attributeName == null ? null : reader.getAttribute(attributeName);
+ return definedIn == null ? null : mapper.realClass(definedIn);
+ }
+
+ protected Object instantiateNewInstance(HierarchicalStreamReader reader,
+ UnmarshallingContext context) {
+ String attributeName = mapper.aliasForSystemAttribute("resolves-to");
+ String readResolveValue = attributeName == null ? null : reader
+ .getAttribute(attributeName);
+ Object currentObject = context.currentObject();
+ if (currentObject != null) {
+ return currentObject;
+ } else if (readResolveValue != null) {
+ return reflectionProvider.newInstance(mapper.realClass(readResolveValue));
+ } else {
+ return reflectionProvider.newInstance(context.getRequiredType());
+ }
+ }
+
+ public void flushCache() {
+ serializationMethodInvoker.flushCache();
+ }
+
+ protected Object readResolve() {
+ serializationMethodInvoker = new SerializationMethodInvoker();
+ serializationMembers = serializationMethodInvoker.serializationMembers;
+ return this;
+ }
+
+ public static class DuplicateFieldException extends ConversionException {
+ public DuplicateFieldException(String msg) {
+ super("Duplicate field " + msg);
+ add("field", msg);
+ }
+ }
+
+ public static class UnknownFieldException extends ConversionException {
+ public UnknownFieldException(String type, String field) {
+ super("No such field " + type + "." + field);
+ add("field", field);
+ }
+ }
+
+ private static class FieldInfo {
+ final String fieldName;
+ final Class type;
+ final Class definedIn;
+ final Object value;
+
+ FieldInfo(String fieldName, Class type, Class definedIn, Object value) {
+ this.fieldName = fieldName;
+ this.type = type;
+ this.definedIn = definedIn;
+ this.value = value;
+ }
+ }
+
+ private static class ArraysList extends ArrayList {
+ final Class physicalFieldType;
+
+ ArraysList(Class physicalFieldType) {
+ this.physicalFieldType = physicalFieldType;
+ }
+
+ Object toPhysicalArray() {
+ Object[] objects = toArray();
+ Object array = Array.newInstance(
+ physicalFieldType.getComponentType(), objects.length);
+ if (physicalFieldType.getComponentType().isPrimitive()) {
+ for (int i = 0; i < objects.length; ++i) {
+ Array.set(array, i, Array.get(objects, i));
+ }
+ } else {
+ System.arraycopy(objects, 0, array, 0, objects.length);
+ }
+ return array;
+ }
+ }
+
+ private class MappingList extends AbstractList {
+
+ private final Map map;
+ private final String keyFieldName;
+ private final Map fieldCache = new HashMap();
+
+ public MappingList(Map map, String keyFieldName) {
+ this.map = map;
+ this.keyFieldName = keyFieldName;
+ }
+
+ public boolean add(Object object) {
+ if (object == null) {
+ boolean containsNull = !map.containsKey(null);
+ map.put(null, null);
+ return containsNull;
+ }
+ Class itemType = object.getClass();
+
+ if (keyFieldName != null) {
+ Field field = (Field)fieldCache.get(itemType);
+ if (field == null) {
+ field = reflectionProvider.getField(itemType, keyFieldName);
+ fieldCache.put(itemType, field);
+ }
+ if (field != null) {
+ try {
+ Object key = field.get(object);
+ return map.put(key, object) == null;
+ } catch (IllegalArgumentException e) {
+ throw new ObjectAccessException("Could not get field "
+ + field.getClass()
+ + "."
+ + field.getName(), e);
+ } catch (IllegalAccessException e) {
+ throw new ObjectAccessException("Could not get field "
+ + field.getClass()
+ + "."
+ + field.getName(), e);
+ }
+ }
+ } else if (object instanceof Map.Entry) {
+ final Map.Entry entry = (Map.Entry)object;
+ return map.put(entry.getKey(), entry.getValue()) == null;
+ }
+
+ throw new ConversionException("Element of type "
+ + object.getClass().getName()
+ + " is not defined as entry for map of type "
+ + map.getClass().getName());
+ }
+
+ public Object get(int index) {
+ throw new UnsupportedOperationException();
+ }
+
+ public int size() {
+ return map.size();
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/reflection/CGLIBEnhancedConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/CGLIBEnhancedConverter.java
new file mode 100644
index 0000000..6ba08cc
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/CGLIBEnhancedConverter.java
@@ -0,0 +1,498 @@
+/*
+ * Copyright (C) 2006, 2007, 2008, 2010, 2011, 2013, 2014, 2015 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 13. April 2006 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.reflection;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.core.ClassLoaderReference;
+import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.mapper.CGLIBMapper;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+import net.sf.cglib.proxy.Callback;
+import net.sf.cglib.proxy.CallbackFilter;
+import net.sf.cglib.proxy.Enhancer;
+import net.sf.cglib.proxy.Factory;
+import net.sf.cglib.proxy.MethodInterceptor;
+import net.sf.cglib.proxy.NoOp;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * Converts a proxy created by the CGLIB {@link Enhancer}. Such a proxy is recreated while
+ * deserializing the proxy. The converter does only work, if
+ *
+ *
the DefaultNamingPolicy is used for the proxy's name
+ *
the proxy uses a factory or only one Callback is registered
+ *
a possible super class has at least a protected default constructor
+ *
+ * Note, that the this converter relies on the CGLIBMapper.
+ *
+ * @author Jörg Schaible
+ * @since 1.2
+ */
+public class CGLIBEnhancedConverter extends SerializableConverter {
+ private static String DEFAULT_NAMING_MARKER = "$$EnhancerByCGLIB$$";
+ private static String CALLBACK_MARKER = "CGLIB$CALLBACK_";
+ private transient Map fieldCache;
+
+ /**
+ * Construct a CGLIBEnhancedConverter.
+ * @param mapper the mapper chain instance
+ * @param reflectionProvider the reflection provider
+ * @param classLoaderReference the reference to the {@link ClassLoader} of the XStream instance
+ * @since 1.4.5
+ */
+ public CGLIBEnhancedConverter(Mapper mapper, ReflectionProvider reflectionProvider, ClassLoaderReference classLoaderReference) {
+ super(mapper, new CGLIBFilteringReflectionProvider(reflectionProvider), classLoaderReference);
+ this.fieldCache = new HashMap();
+ }
+
+ /**
+ * @deprecated As of 1.4.5 use {@link #CGLIBEnhancedConverter(Mapper, ReflectionProvider, ClassLoaderReference)}
+ */
+ public CGLIBEnhancedConverter(Mapper mapper, ReflectionProvider reflectionProvider, ClassLoader classLoader) {
+ super(mapper, new CGLIBFilteringReflectionProvider(reflectionProvider), classLoader);
+ this.fieldCache = new HashMap();
+ }
+
+ /**
+ * @deprecated As of 1.4 use {@link #CGLIBEnhancedConverter(Mapper, ReflectionProvider, ClassLoaderReference)}
+ */
+ public CGLIBEnhancedConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
+ this(mapper, new CGLIBFilteringReflectionProvider(reflectionProvider), CGLIBEnhancedConverter.class.getClassLoader());
+ }
+
+ public boolean canConvert(Class type) {
+ return (Enhancer.isEnhanced(type) && type.getName().indexOf(DEFAULT_NAMING_MARKER) > 0)
+ || type == CGLIBMapper.Marker.class;
+ }
+
+ public void marshal(Object source, HierarchicalStreamWriter writer,
+ MarshallingContext context) {
+ Class type = source.getClass();
+ boolean hasFactory = Factory.class.isAssignableFrom(type);
+ ExtendedHierarchicalStreamWriterHelper.startNode(writer, "type", type);
+ context.convertAnother(type.getSuperclass());
+ writer.endNode();
+ writer.startNode("interfaces");
+ Class[] interfaces = type.getInterfaces();
+ for (int i = 0; i < interfaces.length; i++ ) {
+ if (interfaces[i] == Factory.class) {
+ continue;
+ }
+ ExtendedHierarchicalStreamWriterHelper.startNode(writer, mapper
+ .serializedClass(interfaces[i].getClass()), interfaces[i].getClass());
+ context.convertAnother(interfaces[i]);
+ writer.endNode();
+ }
+ writer.endNode();
+ writer.startNode("hasFactory");
+ writer.setValue(String.valueOf(hasFactory));
+ writer.endNode();
+ Map callbackIndexMap = null;
+ Callback[] callbacks = hasFactory
+ ? ((Factory)source).getCallbacks()
+ : getCallbacks(source);
+ if (callbacks.length > 1) {
+ if (hasFactory) {
+ callbackIndexMap = createCallbackIndexMap((Factory)source);
+ } else {
+ ConversionException exception = new ConversionException(
+ "Cannot handle CGLIB enhanced proxies without factory that have multiple callbacks");
+ exception.add("proxy superclass", type.getSuperclass().getName());
+ exception.add("number of callbacks", String.valueOf(callbacks.length));
+ throw exception;
+ }
+ writer.startNode("callbacks");
+ writer.startNode("mapping");
+ context.convertAnother(callbackIndexMap);
+ writer.endNode();
+ }
+ boolean hasInterceptor = false;
+ for (int i = 0; i < callbacks.length; i++ ) {
+ final Callback callback = callbacks[i];
+ if (callback == null) {
+ String name = mapper.serializedClass(null);
+ writer.startNode(name);
+ writer.endNode();
+ } else {
+ hasInterceptor = hasInterceptor
+ || MethodInterceptor.class.isAssignableFrom(callback.getClass());
+ ExtendedHierarchicalStreamWriterHelper.startNode(writer, mapper
+ .serializedClass(callback.getClass()), callback.getClass());
+ context.convertAnother(callback);
+ writer.endNode();
+ }
+ }
+ if (callbacks.length > 1) {
+ writer.endNode();
+ }
+ try {
+ final Field field = type.getDeclaredField("serialVersionUID");
+ if (!field.isAccessible()) {
+ field.setAccessible(true);
+ }
+ long serialVersionUID = field.getLong(null);
+ ExtendedHierarchicalStreamWriterHelper.startNode(
+ writer, "serialVersionUID", String.class);
+ writer.setValue(String.valueOf(serialVersionUID));
+ writer.endNode();
+ } catch (NoSuchFieldException e) {
+ // OK, ignore
+ } catch (IllegalAccessException e) {
+ throw new ObjectAccessException("Access to serialVersionUID of "
+ + type.getName()
+ + " not allowed", e);
+ }
+ if (hasInterceptor) {
+ writer.startNode("instance");
+ super.doMarshalConditionally(source, writer, context);
+ writer.endNode();
+ }
+ }
+
+ private Callback[] getCallbacks(Object source) {
+ Class type = source.getClass();
+ List fields = (List)fieldCache.get(type.getName());
+ if (fields == null) {
+ fields = new ArrayList();
+ fieldCache.put(type.getName(), fields);
+ for (int i = 0; true; ++i) {
+ try {
+ Field field = type.getDeclaredField(CALLBACK_MARKER + i);
+ if (!field.isAccessible()) {
+ field.setAccessible(true);
+ }
+ fields.add(field);
+ } catch (NoSuchFieldException e) {
+ break;
+ }
+ }
+ }
+ List list = new ArrayList();
+ for (int i = 0; i < fields.size(); ++i) {
+ try {
+ Field field = (Field)fields.get(i);
+ Object callback = field.get(source);
+ list.add(callback);
+ } catch (IllegalAccessException e) {
+ throw new ObjectAccessException("Access to "
+ + type.getName()
+ + "."
+ + CALLBACK_MARKER
+ + i
+ + " not allowed", e);
+ }
+ }
+ return (Callback[])list.toArray(new Callback[list.size()]);
+ }
+
+ private Map createCallbackIndexMap(Factory source) {
+ Callback[] originalCallbacks = source.getCallbacks();
+ Callback[] reverseEngineeringCallbacks = new Callback[originalCallbacks.length];
+ Map callbackIndexMap = new HashMap();
+ int idxNoOp = -1;
+ for (int i = 0; i < originalCallbacks.length; i++ ) {
+ Callback callback = originalCallbacks[i];
+ if (callback == null) {
+ reverseEngineeringCallbacks[i] = null;
+ } else if (NoOp.class.isAssignableFrom(callback.getClass())) {
+ reverseEngineeringCallbacks[i] = NoOp.INSTANCE;
+ idxNoOp = i;
+ } else {
+ reverseEngineeringCallbacks[i] = createReverseEngineeredCallbackOfProperType(
+ callback, i, callbackIndexMap);
+ }
+ }
+
+ try {
+ source.setCallbacks(reverseEngineeringCallbacks);
+ final Set interfaces = new HashSet();
+ final Set methods = new HashSet();
+ Class type = source.getClass();
+ do {
+ methods.addAll(Arrays.asList(type.getDeclaredMethods()));
+ methods.addAll(Arrays.asList(type.getMethods()));
+ Class[] implementedInterfaces = type.getInterfaces();
+ interfaces.addAll(Arrays.asList(implementedInterfaces));
+ type = type.getSuperclass();
+ } while (type != null);
+ for (final Iterator iterator = interfaces.iterator(); iterator.hasNext();) {
+ type = (Class)iterator.next();
+ methods.addAll(Arrays.asList(type.getDeclaredMethods()));
+ }
+ for (final Iterator iter = methods.iterator(); iter.hasNext();) {
+ final Method method = (Method)iter.next();
+ if (!method.isAccessible()) {
+ method.setAccessible(true);
+ }
+ if (Factory.class.isAssignableFrom(method.getDeclaringClass())
+ || (method.getModifiers() & (Modifier.FINAL | Modifier.STATIC)) > 0) {
+ iter.remove();
+ continue;
+ }
+ Class[] parameterTypes = method.getParameterTypes();
+ Method calledMethod = method;
+ try {
+ if ((method.getModifiers() & Modifier.ABSTRACT) > 0) {
+ calledMethod = source.getClass().getMethod(
+ method.getName(), method.getParameterTypes());
+ }
+ callbackIndexMap.put(null, method);
+ calledMethod.invoke(source, parameterTypes == null
+ ? (Object[])null
+ : createNullArguments(parameterTypes));
+ } catch (IllegalAccessException e) {
+ throw new ObjectAccessException("Access to "
+ + calledMethod
+ + " not allowed", e);
+ } catch (InvocationTargetException e) {
+ // OK, ignore
+ } catch (NoSuchMethodException e) {
+ ConversionException exception = new ConversionException(
+ "CGLIB enhanced proxies wit abstract nethod that has not been implemented");
+ exception.add("proxy superclass", type.getSuperclass().getName());
+ exception.add("method", method.toString());
+ throw exception;
+ }
+ if (callbackIndexMap.containsKey(method)) {
+ iter.remove();
+ }
+ }
+ if (idxNoOp >= 0) {
+ Integer idx = new Integer(idxNoOp);
+ for (final Iterator iter = methods.iterator(); iter.hasNext();) {
+ callbackIndexMap.put(iter.next(), idx);
+ }
+ }
+ } finally {
+ source.setCallbacks(originalCallbacks);
+ }
+
+ callbackIndexMap.remove(null);
+ return callbackIndexMap;
+ }
+
+ private Object[] createNullArguments(Class[] parameterTypes) {
+ Object[] arguments = new Object[parameterTypes.length];
+ for (int i = 0; i < arguments.length; i++ ) {
+ Class type = parameterTypes[i];
+ if (type.isPrimitive()) {
+ if (type == byte.class) {
+ arguments[i] = new Byte((byte)0);
+ } else if (type == short.class) {
+ arguments[i] = new Short((short)0);
+ } else if (type == int.class) {
+ arguments[i] = new Integer(0);
+ } else if (type == long.class) {
+ arguments[i] = new Long(0);
+ } else if (type == float.class) {
+ arguments[i] = new Float(0);
+ } else if (type == double.class) {
+ arguments[i] = new Double(0);
+ } else if (type == char.class) {
+ arguments[i] = new Character('\0');
+ } else {
+ arguments[i] = Boolean.FALSE;
+ }
+ }
+ }
+ return arguments;
+ }
+
+ private Callback createReverseEngineeredCallbackOfProperType(Callback callback, int index,
+ Map callbackIndexMap) {
+ Class iface = null;
+ Class[] interfaces = callback.getClass().getInterfaces();
+ for (int i = 0; i < interfaces.length; i++ ) {
+ if (Callback.class.isAssignableFrom(interfaces[i])) {
+ iface = interfaces[i];
+ if (iface == Callback.class) {
+ ConversionException exception = new ConversionException(
+ "Cannot handle CGLIB callback");
+ exception.add("CGLIB callback type", callback.getClass().getName());
+ throw exception;
+ }
+ interfaces = iface.getInterfaces();
+ if (Arrays.asList(interfaces).contains(Callback.class)) {
+ break;
+ }
+ i = -1;
+ }
+ }
+ return (Callback)Proxy.newProxyInstance(
+ iface.getClassLoader(), new Class[]{iface},
+ new ReverseEngineeringInvocationHandler(index, callbackIndexMap));
+ }
+
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ final Enhancer enhancer = new Enhancer();
+ reader.moveDown();
+ enhancer.setSuperclass((Class)context.convertAnother(null, Class.class));
+ reader.moveUp();
+ reader.moveDown();
+ List interfaces = new ArrayList();
+ while (reader.hasMoreChildren()) {
+ reader.moveDown();
+ interfaces
+ .add(context.convertAnother(null, mapper.realClass(reader.getNodeName())));
+ reader.moveUp();
+ }
+ enhancer.setInterfaces((Class[])interfaces.toArray(new Class[interfaces.size()]));
+ reader.moveUp();
+ reader.moveDown();
+ boolean useFactory = Boolean.valueOf(reader.getValue()).booleanValue();
+ enhancer.setUseFactory(useFactory);
+ reader.moveUp();
+
+ List callbacksToEnhance = new ArrayList();
+ List callbacks = new ArrayList();
+ Map callbackIndexMap = null;
+ reader.moveDown();
+ if ("callbacks".equals(reader.getNodeName())) {
+ reader.moveDown();
+ callbackIndexMap = (Map)context.convertAnother(null, HashMap.class);
+ reader.moveUp();
+ while (reader.hasMoreChildren()) {
+ reader.moveDown();
+ readCallback(reader, context, callbacksToEnhance, callbacks);
+ reader.moveUp();
+ }
+ } else {
+ readCallback(reader, context, callbacksToEnhance, callbacks);
+ }
+ enhancer.setCallbacks((Callback[])callbacksToEnhance
+ .toArray(new Callback[callbacksToEnhance.size()]));
+ if (callbackIndexMap != null) {
+ enhancer.setCallbackFilter(new ReverseEngineeredCallbackFilter(callbackIndexMap));
+ }
+ reader.moveUp();
+ Object result = null;
+ while (reader.hasMoreChildren()) {
+ reader.moveDown();
+ if (reader.getNodeName().equals("serialVersionUID")) {
+ enhancer.setSerialVersionUID(Long.valueOf(reader.getValue()));
+ } else if (reader.getNodeName().equals("instance")) {
+ result = create(enhancer, callbacks, useFactory);
+ super.doUnmarshalConditionally(result, reader, context);
+ }
+ reader.moveUp();
+ }
+ if (result == null) {
+ result = create(enhancer, callbacks, useFactory);
+ }
+ return serializationMembers.callReadResolve(result);
+ }
+
+ private void readCallback(HierarchicalStreamReader reader, UnmarshallingContext context,
+ List callbacksToEnhance, List callbacks) {
+ Callback callback = (Callback)context.convertAnother(null, mapper.realClass(reader
+ .getNodeName()));
+ callbacks.add(callback);
+ if (callback == null) {
+ callbacksToEnhance.add(NoOp.INSTANCE);
+ } else {
+ callbacksToEnhance.add(callback);
+ }
+ }
+
+ private Object create(final Enhancer enhancer, List callbacks, boolean useFactory) {
+ Object result = enhancer.create();
+ if (useFactory) {
+ ((Factory)result).setCallbacks((Callback[])callbacks.toArray(new Callback[callbacks
+ .size()]));
+ }
+ return result;
+ }
+
+ protected List hierarchyFor(Class type) {
+ List typeHierarchy = super.hierarchyFor(type);
+ // drop the CGLIB proxy
+ typeHierarchy.remove(typeHierarchy.size() - 1);
+ return typeHierarchy;
+ }
+
+ protected Object readResolve() {
+ super.readResolve();
+ fieldCache = new HashMap();
+ return this;
+ }
+
+ private static class CGLIBFilteringReflectionProvider extends ReflectionProviderWrapper {
+
+ public CGLIBFilteringReflectionProvider(final ReflectionProvider reflectionProvider) {
+ super(reflectionProvider);
+ }
+
+ public void visitSerializableFields(final Object object, final Visitor visitor) {
+ wrapped.visitSerializableFields(object, new Visitor() {
+ public void visit(String name, Class type, Class definedIn, Object value) {
+ if (!name.startsWith("CGLIB$")) {
+ visitor.visit(name, type, definedIn, value);
+ }
+ }
+ });
+ }
+ }
+
+ private static final class ReverseEngineeringInvocationHandler implements InvocationHandler {
+ private final Integer index;
+ private final Map indexMap;
+
+ public ReverseEngineeringInvocationHandler(int index, Map indexMap) {
+ this.indexMap = indexMap;
+ this.index = new Integer(index);
+ }
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ indexMap.put(indexMap.get(null), index);
+ return null;
+ }
+ }
+
+ private static class ReverseEngineeredCallbackFilter implements CallbackFilter {
+
+ private final Map callbackIndexMap;
+
+ public ReverseEngineeredCallbackFilter(Map callbackIndexMap) {
+ this.callbackIndexMap = callbackIndexMap;
+ }
+
+ public int accept(Method method) {
+ if (!callbackIndexMap.containsKey(method)) {
+ ConversionException exception = new ConversionException(
+ "CGLIB callback not detected in reverse engineering");
+ exception.add("CGLIB callback", method.toString());
+ throw exception;
+ }
+ return ((Integer)callbackIndexMap.get(method)).intValue();
+ }
+
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/reflection/ExternalizableConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/ExternalizableConverter.java
new file mode 100644
index 0000000..cd8a585
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/ExternalizableConverter.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2010, 2011, 2013, 2014, 2015 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 24. August 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.reflection;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.core.ClassLoaderReference;
+import com.thoughtworks.xstream.core.JVM;
+import com.thoughtworks.xstream.core.ReferencingMarshallingContext;
+import com.thoughtworks.xstream.core.util.CustomObjectInputStream;
+import com.thoughtworks.xstream.core.util.CustomObjectOutputStream;
+import com.thoughtworks.xstream.core.util.HierarchicalStreams;
+import com.thoughtworks.xstream.core.util.SerializationMembers;
+import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.NotActiveException;
+import java.io.ObjectInputValidation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Map;
+
+/**
+ * Converts any object that implements the java.io.Externalizable interface, allowing compatibility with native Java
+ * serialization.
+ *
+ * @author Joe Walnes
+ */
+public class ExternalizableConverter implements Converter {
+
+ private Mapper mapper;
+ private final ClassLoaderReference classLoaderReference;
+ private transient SerializationMembers serializationMembers;
+
+ /**
+ * Construct an ExternalizableConverter.
+ *
+ * @param mapper the Mapper chain
+ * @param classLoaderReference the reference to XStream's {@link ClassLoader} instance
+ * @since 1.4.5
+ */
+ public ExternalizableConverter(Mapper mapper, ClassLoaderReference classLoaderReference) {
+ this.mapper = mapper;
+ this.classLoaderReference = classLoaderReference;
+ serializationMembers = new SerializationMembers();
+ }
+
+ /**
+ * @deprecated As of 1.4.5 use {@link #ExternalizableConverter(Mapper, ClassLoaderReference)}
+ */
+ public ExternalizableConverter(Mapper mapper, ClassLoader classLoader) {
+ this(mapper, new ClassLoaderReference(classLoader));
+ }
+
+ /**
+ * @deprecated As of 1.4 use {@link #ExternalizableConverter(Mapper, ClassLoader)}
+ */
+ public ExternalizableConverter(Mapper mapper) {
+ this(mapper, ExternalizableConverter.class.getClassLoader());
+ }
+
+ public boolean canConvert(Class type) {
+ return JVM.canCreateDerivedObjectOutputStream() && Externalizable.class.isAssignableFrom(type);
+ }
+
+ public void marshal(final Object original, final HierarchicalStreamWriter writer, final MarshallingContext context) {
+ final Object source = serializationMembers.callWriteReplace(original);
+ if (source != original && context instanceof ReferencingMarshallingContext) {
+ ((ReferencingMarshallingContext)context).replace(original, source);
+ }
+ if (source.getClass() != original.getClass()) {
+ final String attributeName = mapper.aliasForSystemAttribute("resolves-to");
+ if (attributeName != null) {
+ writer.addAttribute(attributeName, mapper.serializedClass(source.getClass()));
+ }
+ context.convertAnother(source);
+ } else {
+ try {
+ Externalizable externalizable = (Externalizable)source;
+ CustomObjectOutputStream.StreamCallback callback = new CustomObjectOutputStream.StreamCallback() {
+ public void writeToStream(final Object object) {
+ if (object == null) {
+ writer.startNode("null");
+ writer.endNode();
+ } else {
+ ExtendedHierarchicalStreamWriterHelper.startNode(writer, mapper.serializedClass(object.getClass()), object.getClass());
+ context.convertAnother(object);
+ writer.endNode();
+ }
+ }
+
+ public void writeFieldsToStream(final Map fields) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void defaultWriteObject() {
+ throw new UnsupportedOperationException();
+ }
+
+ public void flush() {
+ writer.flush();
+ }
+
+ public void close() {
+ throw new UnsupportedOperationException("Objects are not allowed to call ObjectOutput.close() from writeExternal()");
+ }
+ };
+ final CustomObjectOutputStream objectOutput = CustomObjectOutputStream.getInstance(context, callback);
+ externalizable.writeExternal(objectOutput);
+ objectOutput.popCallback();
+ } catch (IOException e) {
+ throw new ConversionException("Cannot serialize " + source.getClass().getName() + " using Externalization", e);
+ }
+ }
+ }
+
+ public Object unmarshal(final HierarchicalStreamReader reader, final UnmarshallingContext context) {
+ final Class type = context.getRequiredType();
+ final Constructor defaultConstructor;
+ try {
+ defaultConstructor = type.getDeclaredConstructor((Class[]) null);
+ if (!defaultConstructor.isAccessible()) {
+ defaultConstructor.setAccessible(true);
+ }
+ final Externalizable externalizable = (Externalizable) defaultConstructor.newInstance((Object[]) null);
+ CustomObjectInputStream.StreamCallback callback = new CustomObjectInputStream.StreamCallback() {
+ public Object readFromStream() {
+ reader.moveDown();
+ Class type = HierarchicalStreams.readClassType(reader, mapper);
+ Object streamItem = context.convertAnother(externalizable, type);
+ reader.moveUp();
+ return streamItem;
+ }
+
+ public Map readFieldsFromStream() {
+ throw new UnsupportedOperationException();
+ }
+
+ public void defaultReadObject() {
+ throw new UnsupportedOperationException();
+ }
+
+ public void registerValidation(ObjectInputValidation validation, int priority) throws NotActiveException {
+ throw new NotActiveException("stream inactive");
+ }
+
+ public void close() {
+ throw new UnsupportedOperationException("Objects are not allowed to call ObjectInput.close() from readExternal()");
+ }
+ };
+ CustomObjectInputStream objectInput = CustomObjectInputStream.getInstance(context, callback, classLoaderReference);
+ externalizable.readExternal(objectInput);
+ objectInput.popCallback();
+ return serializationMembers.callReadResolve(externalizable);
+ } catch (NoSuchMethodException e) {
+ throw new ConversionException("Cannot construct " + type.getClass() + ", missing default constructor", e);
+ } catch (InvocationTargetException e) {
+ throw new ConversionException("Cannot construct " + type.getClass(), e);
+ } catch (InstantiationException e) {
+ throw new ConversionException("Cannot construct " + type.getClass(), e);
+ } catch (IllegalAccessException e) {
+ throw new ConversionException("Cannot construct " + type.getClass(), e);
+ } catch (IOException e) {
+ throw new ConversionException("Cannot externalize " + type.getClass(), e);
+ } catch (ClassNotFoundException e) {
+ throw new ConversionException("Cannot externalize " + type.getClass(), e);
+ }
+ }
+
+ private Object readResolve() {
+ serializationMembers = new SerializationMembers();
+ return this;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/reflection/FieldDictionary.java b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/FieldDictionary.java
new file mode 100644
index 0000000..a450e8f
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/FieldDictionary.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 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 14. May 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.reflection;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.thoughtworks.xstream.core.Caching;
+import com.thoughtworks.xstream.core.JVM;
+import com.thoughtworks.xstream.core.util.OrderRetainingMap;
+
+
+/**
+ * A field dictionary instance caches information about classes fields.
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ * @author Guilherme Silveira
+ */
+public class FieldDictionary implements Caching {
+
+ private transient Map keyedByFieldNameCache;
+ private transient Map keyedByFieldKeyCache;
+ private final FieldKeySorter sorter;
+
+ public FieldDictionary() {
+ this(new ImmutableFieldKeySorter());
+ }
+
+ public FieldDictionary(FieldKeySorter sorter) {
+ this.sorter = sorter;
+ init();
+ }
+
+ private void init() {
+ keyedByFieldNameCache = new HashMap();
+ keyedByFieldKeyCache = new HashMap();
+ keyedByFieldNameCache.put(Object.class, Collections.EMPTY_MAP);
+ keyedByFieldKeyCache.put(Object.class, Collections.EMPTY_MAP);
+ }
+
+ /**
+ * Returns an iterator for all fields for some class
+ *
+ * @param cls the class you are interested on
+ * @return an iterator for its fields
+ * @deprecated As of 1.3, use {@link #fieldsFor(Class)} instead
+ */
+ public Iterator serializableFieldsFor(Class cls) {
+ return fieldsFor(cls);
+ }
+
+ /**
+ * Returns an iterator for all fields for some class
+ *
+ * @param cls the class you are interested on
+ * @return an iterator for its fields
+ */
+ public Iterator fieldsFor(final Class cls) {
+ return buildMap(cls, true).values().iterator();
+ }
+
+ /**
+ * Returns an specific field of some class. If definedIn is null, it searches for the field
+ * named 'name' inside the class cls. If definedIn is different than null, tries to find the
+ * specified field name in the specified class cls which should be defined in class
+ * definedIn (either equals cls or a one of it's superclasses)
+ *
+ * @param cls the class where the field is to be searched
+ * @param name the field name
+ * @param definedIn the superclass (or the class itself) of cls where the field was defined
+ * @return the field itself
+ * @throws ObjectAccessException if no field can be found
+ */
+ public Field field(Class cls, String name, Class definedIn) {
+ Field field = fieldOrNull(cls, name, definedIn);
+ if (field == null) {
+ throw new MissingFieldException(cls.getName(), name);
+ } else {
+ return field;
+ }
+ }
+
+ /**
+ * Returns an specific field of some class. If definedIn is null, it searches for the field
+ * named 'name' inside the class cls. If definedIn is different than null, tries to find the
+ * specified field name in the specified class cls which should be defined in class
+ * definedIn (either equals cls or a one of it's superclasses)
+ *
+ * @param cls the class where the field is to be searched
+ * @param name the field name
+ * @param definedIn the superclass (or the class itself) of cls where the field was defined
+ * @return the field itself or null
+ * @since 1.4
+ */
+ public Field fieldOrNull(Class cls, String name, Class definedIn) {
+ Map fields = buildMap(cls, definedIn != null);
+ Field field = (Field)fields.get(definedIn != null
+ ? (Object)new FieldKey(name, definedIn, -1)
+ : (Object)name);
+ return field;
+ }
+
+ private Map buildMap(final Class type, boolean tupleKeyed) {
+ Class cls = type;
+ synchronized (this) {
+ if (!keyedByFieldNameCache.containsKey(type)) {
+ final List superClasses = new ArrayList();
+ while (!Object.class.equals(cls) && cls != null) {
+ superClasses.add(0, cls);
+ cls = cls.getSuperclass();
+ }
+ Map lastKeyedByFieldName = Collections.EMPTY_MAP;
+ Map lastKeyedByFieldKey = Collections.EMPTY_MAP;
+ for (final Iterator iter = superClasses.iterator(); iter.hasNext();) {
+ cls = (Class)iter.next();
+ if (!keyedByFieldNameCache.containsKey(cls)) {
+ final Map keyedByFieldName = new HashMap(lastKeyedByFieldName);
+ final Map keyedByFieldKey = new OrderRetainingMap(lastKeyedByFieldKey);
+ Field[] fields = cls.getDeclaredFields();
+ if (JVM.reverseFieldDefinition()) {
+ for (int i = fields.length >> 1; i-- > 0;) {
+ final int idx = fields.length - i - 1;
+ final Field field = fields[i];
+ fields[i] = fields[idx];
+ fields[idx] = field;
+ }
+ }
+ for (int i = 0; i < fields.length; i++ ) {
+ Field field = fields[i];
+ if (!field.isAccessible()) {
+ field.setAccessible(true);
+ }
+ FieldKey fieldKey = new FieldKey(
+ field.getName(), field.getDeclaringClass(), i);
+ Field existent = (Field)keyedByFieldName.get(field.getName());
+ if (existent == null
+ // do overwrite statics
+ || ((existent.getModifiers() & Modifier.STATIC) != 0)
+ // overwrite non-statics with non-statics only
+ || (existent != null && ((field.getModifiers() & Modifier.STATIC) == 0))) {
+ keyedByFieldName.put(field.getName(), field);
+ }
+ keyedByFieldKey.put(fieldKey, field);
+ }
+ final Map sortedFieldKeys = sorter.sort(cls, keyedByFieldKey);
+ keyedByFieldNameCache.put(cls, keyedByFieldName);
+ keyedByFieldKeyCache.put(cls, sortedFieldKeys);
+ lastKeyedByFieldName = keyedByFieldName;
+ lastKeyedByFieldKey = sortedFieldKeys;
+ } else {
+ lastKeyedByFieldName = (Map)keyedByFieldNameCache.get(cls);
+ lastKeyedByFieldKey = (Map)keyedByFieldKeyCache.get(cls);
+ }
+ }
+ return tupleKeyed ? lastKeyedByFieldKey : lastKeyedByFieldName;
+ }
+ }
+ return (Map)(tupleKeyed
+ ? keyedByFieldKeyCache.get(type)
+ : keyedByFieldNameCache.get(type));
+ }
+
+ public synchronized void flushCache() {
+ Set objectTypeSet = Collections.singleton(Object.class);
+ keyedByFieldNameCache.keySet().retainAll(objectTypeSet);
+ keyedByFieldKeyCache.keySet().retainAll(objectTypeSet);
+ if (sorter instanceof Caching) {
+ ((Caching)sorter).flushCache();
+ }
+ }
+
+ protected Object readResolve() {
+ init();
+ return this;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/reflection/FieldKey.java b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/FieldKey.java
new file mode 100644
index 0000000..8e5a6d7
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/FieldKey.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2007 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 10. April 2007 by Guilherme Silveira
+ */
+package com.thoughtworks.xstream.converters.reflection;
+
+/**
+ * A field key.
+ *
+ * @author Guilherme Silveira
+ * @author Jörg Schaible
+ */
+public class FieldKey {
+ final private String fieldName;
+ final private Class declaringClass;
+ final private int depth;
+ final private int order;
+
+ public FieldKey(String fieldName, Class declaringClass, int order) {
+ if (fieldName == null || declaringClass == null) {
+ throw new IllegalArgumentException("fieldName or declaringClass is null");
+ }
+ this.fieldName = fieldName;
+ this.declaringClass = declaringClass;
+ this.order = order;
+ Class c = declaringClass;
+ int i = 0;
+ while (c.getSuperclass() != null) {
+ i++;
+ c = c.getSuperclass();
+ }
+ depth = i;
+ }
+
+ public String getFieldName() {
+ return this.fieldName;
+ }
+
+ public Class getDeclaringClass() {
+ return this.declaringClass;
+ }
+
+ public int getDepth() {
+ return this.depth;
+ }
+
+ public int getOrder() {
+ return this.order;
+ }
+
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof FieldKey)) return false;
+
+ final FieldKey fieldKey = (FieldKey)o;
+
+ if (!declaringClass.equals(fieldKey.declaringClass))
+ return false;
+ if (!fieldName.equals(fieldKey.fieldName))
+ return false;
+
+ return true;
+ }
+
+ public int hashCode() {
+ int result;
+ result = fieldName.hashCode();
+ result = 29 * result +declaringClass.hashCode();
+ return result;
+ }
+
+ public String toString() {
+ return "FieldKey{"
+ + "order="
+ + order
+ + ", writer="
+ + depth
+ + ", declaringClass="
+ + declaringClass
+ + ", fieldName='"
+ + fieldName
+ + "'"
+ + "}";
+ }
+
+}
\ No newline at end of file
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/reflection/FieldKeySorter.java b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/FieldKeySorter.java
new file mode 100644
index 0000000..774a088
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/FieldKeySorter.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2007 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 10. April 2007 by Guilherme Silveira
+ */
+package com.thoughtworks.xstream.converters.reflection;
+
+import java.util.Map;
+
+
+/**
+ * An interface capable of sorting fields. Implement this interface if you want to customize the
+ * field order in which XStream serializes objects.
+ *
+ * @author Guilherme Silveira
+ * @since 1.2.2
+ */
+public interface FieldKeySorter {
+
+ /**
+ * Sort the fields of a type. The method will be called with the class type that contains
+ * all the fields and a Map that retains the order in which the elements have been added.
+ * The sequence in which elements are returned by an iterator defines the processing order
+ * of the fields. An implementation may create a different Map with similar semantic, add
+ * all elements of the original map and return the new one.
+ *
+ * @param type the class that contains all the fields
+ * @param keyedByFieldKey a Map containing a {@link FieldKey} as key element and a
+ * {@link java.lang.reflect.Field} as value.
+ * @return a Map with all the entries of the original Map
+ * @since 1.2.2
+ */
+ Map sort(Class type, Map keyedByFieldKey);
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/reflection/ImmutableFieldKeySorter.java b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/ImmutableFieldKeySorter.java
new file mode 100644
index 0000000..db7b48e
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/ImmutableFieldKeySorter.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2007 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 10. April 2007 by Guilherme Silveira
+ */
+package com.thoughtworks.xstream.converters.reflection;
+
+import java.util.Map;
+
+/**
+ * Does not change the order of the fields.
+ *
+ * @author Guilherme Silveira
+ * @since 1.2.2
+ */
+public class ImmutableFieldKeySorter implements FieldKeySorter {
+
+ public Map sort(Class type, Map keyedByFieldKey) {
+ return keyedByFieldKey;
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/reflection/LambdaConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/LambdaConverter.java
new file mode 100644
index 0000000..8f2f7ff
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/LambdaConverter.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 XStream Committer.
+ * All rights reserved.
+ *
+ * Created on 17. January 2015 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.reflection;
+
+import java.io.Serializable;
+
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.core.ClassLoaderReference;
+import com.thoughtworks.xstream.core.JVM;
+import com.thoughtworks.xstream.core.util.Types;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+
+/**
+ * Converts a lambda type.
+ *
+ * The implementation maps any non-serializable lambda instance to {@code null}.
+ *
+ * @author Jörg Schaible
+ * @since 1.4.8
+ */
+public class LambdaConverter extends SerializableConverter {
+
+ /**
+ * Constructs a LambdaConverter.
+ *
+ * @param mapper
+ * @param reflectionProvider
+ * @param classLoaderReference
+ * @since 1.4.8
+ */
+ public LambdaConverter(
+ final Mapper mapper, final ReflectionProvider reflectionProvider,
+ final ClassLoaderReference classLoaderReference) {
+ super(mapper, reflectionProvider, classLoaderReference);
+ }
+
+ @Override
+ public boolean canConvert(final Class type) {
+ return Types.isLambdaType(type)
+ && (JVM.canCreateDerivedObjectOutputStream() || !Serializable.class.isAssignableFrom(type));
+ }
+
+ @Override
+ public void marshal(final Object original, final HierarchicalStreamWriter writer, final MarshallingContext context) {
+ if (original instanceof Serializable) {
+ super.marshal(original, writer, context);
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/reflection/MissingFieldException.java b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/MissingFieldException.java
new file mode 100644
index 0000000..102fa92
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/MissingFieldException.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2011 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 01. October 2011 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.reflection;
+
+/**
+ * Indicates a missing field or property creating an object.
+ *
+ * @author Nikita Levyankov
+ * @author Joerg Schaible
+ * @since 1.4.2
+ */
+public class MissingFieldException extends ObjectAccessException {
+
+ private final String fieldName;
+ private final String className;
+
+ /**
+ * Construct a MissingFieldException.
+ * @param className the name of the class missing the field
+ * @param fieldName the name of the missed field
+ * @since 1.4.2
+ */
+ public MissingFieldException(final String className, final String fieldName) {
+ super("No field '" + fieldName + "' found in class '" + className + "'");
+ this.className = className;
+ this.fieldName = fieldName;
+ }
+
+ /**
+ * Retrieve the name of the missing field.
+ * @return the field name
+ * @since 1.4.2
+ */
+ public String getFieldName() {
+ return fieldName;
+ }
+
+ /**
+ * Retrieve the name of the class with the missing field.
+ * @return the class name
+ * @since 1.4.2
+ */
+ protected String getClassName() {
+ return className;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/reflection/NativeFieldKeySorter.java b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/NativeFieldKeySorter.java
new file mode 100644
index 0000000..4220a3b
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/NativeFieldKeySorter.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2007 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 17.05.2007 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.reflection;
+
+import java.util.Comparator;
+import java.util.Map;
+import java.util.TreeMap;
+
+
+/**
+ * Sort the fields in their natural order. Fields are returned in their declaration order,
+ * fields of base classes first.
+ *
+ * @author Jörg Schaible
+ * @since 1.2.2
+ */
+public class NativeFieldKeySorter implements FieldKeySorter {
+
+ public Map sort(final Class type, final Map keyedByFieldKey) {
+ final Map map = new TreeMap(new Comparator() {
+
+ public int compare(final Object o1, final Object o2) {
+ final FieldKey fieldKey1 = (FieldKey)o1;
+ final FieldKey fieldKey2 = (FieldKey)o2;
+ int i = fieldKey1.getDepth() - fieldKey2.getDepth();
+ if (i == 0) {
+ i = fieldKey1.getOrder() - fieldKey2.getOrder();
+ }
+ return i;
+ }
+ });
+ map.putAll(keyedByFieldKey);
+ return map;
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/reflection/ObjectAccessException.java b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/ObjectAccessException.java
new file mode 100644
index 0000000..c3ac302
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/ObjectAccessException.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 07. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.reflection;
+
+import com.thoughtworks.xstream.XStreamException;
+
+public class ObjectAccessException extends XStreamException {
+ public ObjectAccessException(String message) {
+ super(message);
+ }
+
+ public ObjectAccessException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/reflection/PureJavaReflectionProvider.java b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/PureJavaReflectionProvider.java
new file mode 100644
index 0000000..8158e37
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/PureJavaReflectionProvider.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2011, 2013 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 07. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.reflection;
+
+import com.thoughtworks.xstream.core.JVM;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectStreamClass;
+import java.io.ObjectStreamConstants;
+import java.io.Serializable;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+/**
+ * Pure Java ObjectFactory that instantiates objects using standard Java reflection, however the types of objects
+ * that can be constructed are limited.
+ *
+ * Can newInstance: classes with public visibility, outer classes, static inner classes, classes with default constructors
+ * and any class that implements java.io.Serializable.
+ *
+ *
+ * Cannot newInstance: classes without public visibility, non-static inner classes, classes without default constructors.
+ * Note that any code in the constructor of a class will be executed when the ObjectFactory instantiates the object.
+ *
+ * @author Joe Walnes
+ */
+public class PureJavaReflectionProvider implements ReflectionProvider {
+
+ private transient Map serializedDataCache;
+ protected FieldDictionary fieldDictionary;
+
+ public PureJavaReflectionProvider() {
+ this(new FieldDictionary(new ImmutableFieldKeySorter()));
+ }
+
+ public PureJavaReflectionProvider(FieldDictionary fieldDictionary) {
+ this.fieldDictionary = fieldDictionary;
+ init();
+ }
+
+ public Object newInstance(Class type) {
+ try {
+ Constructor[] constructors = type.getDeclaredConstructors();
+ for (int i = 0; i < constructors.length; i++) {
+ final Constructor constructor = constructors[i];
+ if (constructor.getParameterTypes().length == 0) {
+ if (!constructor.isAccessible()) {
+ constructor.setAccessible(true);
+ }
+ return constructor.newInstance(new Object[0]);
+ }
+ }
+ if (Serializable.class.isAssignableFrom(type)) {
+ return instantiateUsingSerialization(type);
+ } else {
+ throw new ObjectAccessException("Cannot construct " + type.getName()
+ + " as it does not have a no-args constructor");
+ }
+ } catch (InstantiationException e) {
+ throw new ObjectAccessException("Cannot construct " + type.getName(), e);
+ } catch (IllegalAccessException e) {
+ throw new ObjectAccessException("Cannot construct " + type.getName(), e);
+ } catch (InvocationTargetException e) {
+ if (e.getTargetException() instanceof RuntimeException) {
+ throw (RuntimeException)e.getTargetException();
+ } else if (e.getTargetException() instanceof Error) {
+ throw (Error)e.getTargetException();
+ } else {
+ throw new ObjectAccessException("Constructor for " + type.getName() + " threw an exception", e.getTargetException());
+ }
+ }
+ }
+
+ private Object instantiateUsingSerialization(final Class type) {
+ try {
+ synchronized (serializedDataCache) {
+ byte[] data = (byte[]) serializedDataCache.get(type);
+ if (data == null) {
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ DataOutputStream stream = new DataOutputStream(bytes);
+ stream.writeShort(ObjectStreamConstants.STREAM_MAGIC);
+ stream.writeShort(ObjectStreamConstants.STREAM_VERSION);
+ stream.writeByte(ObjectStreamConstants.TC_OBJECT);
+ stream.writeByte(ObjectStreamConstants.TC_CLASSDESC);
+ stream.writeUTF(type.getName());
+ stream.writeLong(ObjectStreamClass.lookup(type).getSerialVersionUID());
+ stream.writeByte(2); // classDescFlags (2 = Serializable)
+ stream.writeShort(0); // field count
+ stream.writeByte(ObjectStreamConstants.TC_ENDBLOCKDATA);
+ stream.writeByte(ObjectStreamConstants.TC_NULL);
+ data = bytes.toByteArray();
+ serializedDataCache.put(type, data);
+ }
+
+ ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(data)) {
+ protected Class resolveClass(ObjectStreamClass desc)
+ throws IOException, ClassNotFoundException {
+ return Class.forName(desc.getName(), false, type.getClassLoader());
+ }
+ };
+ return in.readObject();
+ }
+ } catch (IOException e) {
+ throw new ObjectAccessException("Cannot create " + type.getName() + " by JDK serialization", e);
+ } catch (ClassNotFoundException e) {
+ throw new ObjectAccessException("Cannot find class " + e.getMessage(), e);
+ }
+ }
+
+ public void visitSerializableFields(Object object, ReflectionProvider.Visitor visitor) {
+ for (Iterator iterator = fieldDictionary.fieldsFor(object.getClass()); iterator.hasNext();) {
+ Field field = (Field) iterator.next();
+ if (!fieldModifiersSupported(field)) {
+ continue;
+ }
+ validateFieldAccess(field);
+ try {
+ Object value = field.get(object);
+ visitor.visit(field.getName(), field.getType(), field.getDeclaringClass(), value);
+ } catch (IllegalArgumentException e) {
+ throw new ObjectAccessException("Could not get field " + field.getClass() + "." + field.getName(), e);
+ } catch (IllegalAccessException e) {
+ throw new ObjectAccessException("Could not get field " + field.getClass() + "." + field.getName(), e);
+ }
+ }
+ }
+
+ public void writeField(Object object, String fieldName, Object value, Class definedIn) {
+ Field field = fieldDictionary.field(object.getClass(), fieldName, definedIn);
+ validateFieldAccess(field);
+ try {
+ field.set(object, value);
+ } catch (IllegalArgumentException e) {
+ throw new ObjectAccessException("Could not set field " + object.getClass() + "." + field.getName(), e);
+ } catch (IllegalAccessException e) {
+ throw new ObjectAccessException("Could not set field " + object.getClass() + "." + field.getName(), e);
+ }
+ }
+
+ public Class getFieldType(Object object, String fieldName, Class definedIn) {
+ return fieldDictionary.field(object.getClass(), fieldName, definedIn).getType();
+ }
+
+ /**
+ * @deprecated As of 1.4.5, use {@link #getFieldOrNull(Class, String)} instead
+ */
+ public boolean fieldDefinedInClass(String fieldName, Class type) {
+ Field field = fieldDictionary.fieldOrNull(type, fieldName, null);
+ return field != null && fieldModifiersSupported(field);
+ }
+
+ protected boolean fieldModifiersSupported(Field field) {
+ int modifiers = field.getModifiers();
+ return !(Modifier.isStatic(modifiers) || Modifier.isTransient(modifiers));
+ }
+
+ protected void validateFieldAccess(Field field) {
+ if (Modifier.isFinal(field.getModifiers())) {
+ if (JVM.is15()) {
+ if (!field.isAccessible()) {
+ field.setAccessible(true);
+ }
+ } else {
+ throw new ObjectAccessException("Invalid final field "
+ + field.getDeclaringClass().getName() + "." + field.getName());
+ }
+ }
+ }
+
+ public Field getField(Class definedIn, String fieldName) {
+ return fieldDictionary.field(definedIn, fieldName, null);
+ }
+
+ public Field getFieldOrNull(Class definedIn, String fieldName) {
+ return fieldDictionary.fieldOrNull(definedIn, fieldName, null);
+ }
+
+ public void setFieldDictionary(FieldDictionary dictionary) {
+ this.fieldDictionary = dictionary;
+ }
+
+ private Object readResolve() {
+ init();
+ return this;
+ }
+
+ protected void init() {
+ serializedDataCache = new WeakHashMap();
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/reflection/ReflectionConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/ReflectionConverter.java
new file mode 100644
index 0000000..aa7c3eb
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/ReflectionConverter.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2013, 2014 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 07. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.reflection;
+
+import com.thoughtworks.xstream.core.JVM;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+public class ReflectionConverter extends AbstractReflectionConverter {
+
+ // Might be missing in Android
+ private final static Class eventHandlerType = JVM.loadClassForName("java.beans.EventHandler");
+ private Class type;
+
+ public ReflectionConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
+ super(mapper, reflectionProvider);
+ }
+
+ /**
+ * Construct a ReflectionConverter for an explicit type.
+ *
+ * @param mapper the mapper in use
+ * @param reflectionProvider the reflection provider in use
+ * @param type the explicit type to handle
+ * @since 1.4.7
+ */
+ public ReflectionConverter(Mapper mapper, ReflectionProvider reflectionProvider, Class type) {
+ this(mapper, reflectionProvider);
+ this.type = type;
+ }
+
+ public boolean canConvert(Class type) {
+ return ((this.type != null && this.type == type) || (this.type == null && type != null && type != eventHandlerType))
+ && canAccess(type);
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/reflection/ReflectionProvider.java b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/ReflectionProvider.java
new file mode 100644
index 0000000..286d9db
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/ReflectionProvider.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2004, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2013 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 07. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.reflection;
+
+import java.lang.reflect.Field;
+
+
+/**
+ * Provides core reflection services.
+ *
+ * @author Joe Walnes
+ */
+public interface ReflectionProvider {
+
+ /**
+ * Creates a new instance of the specified type. It is in the responsibility of the
+ * implementation how such an instance is created.
+ *
+ * @param type the type to instantiate
+ * @return a new instance of this type
+ */
+ Object newInstance(Class type);
+
+ void visitSerializableFields(Object object, Visitor visitor);
+
+ void writeField(Object object, String fieldName, Object value, Class definedIn);
+
+ Class getFieldType(Object object, String fieldName, Class definedIn);
+
+ /**
+ * @deprecated As of 1.4.5, use {@link #getFieldOrNull(Class, String)} instead
+ */
+ boolean fieldDefinedInClass(String fieldName, Class type);
+
+ /**
+ * A visitor interface for serializable fields defined in a class.
+ */
+ interface Visitor {
+
+ /**
+ * Callback for each visit
+ *
+ * @param name field name
+ * @param type field type
+ * @param definedIn where the field was defined
+ * @param value field value
+ */
+ void visit(String name, Class type, Class definedIn, Object value);
+ }
+
+ /**
+ * Returns a field defined in some class.
+ *
+ * @param definedIn class where the field was defined
+ * @param fieldName field name
+ * @return the field itself
+ * @throws ObjectAccessException if field does not exist
+ */
+ Field getField(Class definedIn, String fieldName);
+
+ /**
+ * Returns a field defined in some class.
+ *
+ * @param definedIn class where the field was defined
+ * @param fieldName field name
+ * @return the field itself or null
+ * @since 1.4.5
+ */
+ Field getFieldOrNull(Class definedIn, String fieldName);
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/reflection/ReflectionProviderWrapper.java b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/ReflectionProviderWrapper.java
new file mode 100644
index 0000000..e97da5a
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/ReflectionProviderWrapper.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2006, 2007, 2013 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 13. April 2006 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.reflection;
+
+import java.lang.reflect.Field;
+
+/**
+ * A wrapper implementation for the ReflectionProvider.
+ *
+ * @author Jörg Schaible
+ * @since 1.2
+ */
+public class ReflectionProviderWrapper implements ReflectionProvider {
+
+ final protected ReflectionProvider wrapped;
+
+ public ReflectionProviderWrapper(ReflectionProvider wrapper) {
+ this.wrapped = wrapper;
+ }
+
+ /**
+ * @deprecated As of 1.4.5, use {@link #getFieldOrNull(Class, String)} instead
+ */
+ public boolean fieldDefinedInClass(String fieldName, Class type) {
+ return this.wrapped.fieldDefinedInClass(fieldName, type);
+ }
+
+ public Field getField(Class definedIn, String fieldName) {
+ return this.wrapped.getField(definedIn, fieldName);
+ }
+
+ public Field getFieldOrNull(Class definedIn, String fieldName) {
+ return this.wrapped.getFieldOrNull(definedIn, fieldName);
+ }
+
+ public Class getFieldType(Object object, String fieldName, Class definedIn) {
+ return this.wrapped.getFieldType(object, fieldName, definedIn);
+ }
+
+ public Object newInstance(Class type) {
+ return this.wrapped.newInstance(type);
+ }
+
+ public void visitSerializableFields(Object object, Visitor visitor) {
+ this.wrapped.visitSerializableFields(object, visitor);
+ }
+
+ public void writeField(Object object, String fieldName, Object value, Class definedIn) {
+ this.wrapped.writeField(object, fieldName, value, definedIn);
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/reflection/SelfStreamingInstanceChecker.java b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/SelfStreamingInstanceChecker.java
new file mode 100644
index 0000000..6a45fe7
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/SelfStreamingInstanceChecker.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2006, 2007, 2013 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 03. April 2006 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.reflection;
+
+import com.thoughtworks.xstream.converters.Converter;
+
+/**
+ * A special converter that prevents self-serialization. The serializing XStream instance
+ * adds a converter of this type to prevent self-serialization and will throw an
+ * exception instead.
+ *
+ * @author Jörg Schaible
+ * @since 1.2
+ * @deprecated As of 1.4.5 use {@link com.thoughtworks.xstream.core.util.SelfStreamingInstanceChecker}
+ */
+public class SelfStreamingInstanceChecker extends com.thoughtworks.xstream.core.util.SelfStreamingInstanceChecker {
+
+ public SelfStreamingInstanceChecker(Converter defaultConverter, Object xstream) {
+ super(defaultConverter, xstream);
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/reflection/SerializableConverter.java b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/SerializableConverter.java
new file mode 100644
index 0000000..2825980
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/SerializableConverter.java
@@ -0,0 +1,499 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2010, 2011, 2012, 2013, 2014, 2015 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 21. December 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.reflection;
+
+import java.io.IOException;
+import java.io.InvalidObjectException;
+import java.io.ObjectInputValidation;
+import java.io.ObjectStreamClass;
+import java.io.ObjectStreamField;
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.core.ClassLoaderReference;
+import com.thoughtworks.xstream.core.JVM;
+import com.thoughtworks.xstream.core.util.CustomObjectInputStream;
+import com.thoughtworks.xstream.core.util.CustomObjectOutputStream;
+import com.thoughtworks.xstream.core.util.HierarchicalStreams;
+import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+/**
+ * Emulates the mechanism used by standard Java Serialization for classes that implement java.io.Serializable AND
+ * implement or inherit a custom readObject()/writeObject() method.
+ *
+ *
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ */
+public class SerializableConverter extends AbstractReflectionConverter {
+
+ private static final String ELEMENT_NULL = "null";
+ private static final String ELEMENT_DEFAULT = "default";
+ private static final String ELEMENT_UNSERIALIZABLE_PARENTS = "unserializable-parents";
+ private static final String ATTRIBUTE_CLASS = "class";
+ private static final String ATTRIBUTE_SERIALIZATION = "serialization";
+ private static final String ATTRIBUTE_VALUE_CUSTOM = "custom";
+ private static final String ELEMENT_FIELDS = "fields";
+ private static final String ELEMENT_FIELD = "field";
+ private static final String ATTRIBUTE_NAME = "name";
+
+ private final ClassLoaderReference classLoaderReference;
+
+ /**
+ * Construct a SerializableConverter.
+ *
+ * @param mapper the mapper chain instance
+ * @param reflectionProvider the reflection provider
+ * @param classLoaderReference the reference to the {@link ClassLoader} of the XStream instance
+ * @since 1.4.5
+ */
+ public SerializableConverter(Mapper mapper, ReflectionProvider reflectionProvider, ClassLoaderReference classLoaderReference) {
+ super(mapper, new UnserializableParentsReflectionProvider(reflectionProvider));
+ this.classLoaderReference = classLoaderReference;
+ }
+
+ /**
+ * @deprecated As of 1.4.5 use {@link #SerializableConverter(Mapper, ReflectionProvider, ClassLoaderReference)}
+ */
+ public SerializableConverter(Mapper mapper, ReflectionProvider reflectionProvider, ClassLoader classLoader) {
+ this(mapper, reflectionProvider, new ClassLoaderReference(classLoader));
+ }
+
+ /**
+ * @deprecated As of 1.4 use {@link #SerializableConverter(Mapper, ReflectionProvider, ClassLoaderReference)}
+ */
+ public SerializableConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
+ this(mapper, new UnserializableParentsReflectionProvider(reflectionProvider), new ClassLoaderReference(null));
+ }
+
+ public boolean canConvert(Class type) {
+ return JVM.canCreateDerivedObjectOutputStream() && isSerializable(type);
+ }
+
+ private boolean isSerializable(Class type) {
+ if (type != null
+ && Serializable.class.isAssignableFrom(type)
+ && !type.isInterface()
+ && (serializationMembers.supportsReadObject(type, true) || serializationMembers.supportsWriteObject(type,
+ true))) {
+ for (Iterator iter = hierarchyFor(type).iterator(); iter.hasNext();) {
+ if (!Serializable.class.isAssignableFrom((Class)iter.next())) {
+ return canAccess(type);
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public void doMarshal(final Object source, final HierarchicalStreamWriter writer, final MarshallingContext context) {
+ String attributeName = mapper.aliasForSystemAttribute(ATTRIBUTE_SERIALIZATION);
+ if (attributeName != null) {
+ writer.addAttribute(attributeName, ATTRIBUTE_VALUE_CUSTOM);
+ }
+
+ // this is an array as it's a non final value that's accessed from an anonymous inner class.
+ final Class[] currentType = new Class[1];
+ final boolean[] writtenClassWrapper = {false};
+
+ CustomObjectOutputStream.StreamCallback callback = new CustomObjectOutputStream.StreamCallback() {
+
+ public void writeToStream(Object object) {
+ if (object == null) {
+ writer.startNode(ELEMENT_NULL);
+ writer.endNode();
+ } else {
+ ExtendedHierarchicalStreamWriterHelper.startNode(writer, mapper.serializedClass(object.getClass()), object.getClass());
+ context.convertAnother(object);
+ writer.endNode();
+ }
+ }
+
+ public void writeFieldsToStream(Map fields) {
+ ObjectStreamClass objectStreamClass = ObjectStreamClass.lookup(currentType[0]);
+
+ writer.startNode(ELEMENT_DEFAULT);
+ for (Iterator iterator = fields.keySet().iterator(); iterator.hasNext();) {
+ String name = (String) iterator.next();
+ if (!mapper.shouldSerializeMember(currentType[0], name)) {
+ continue;
+ }
+ ObjectStreamField field = objectStreamClass.getField(name);
+ Object value = fields.get(name);
+ if (field == null) {
+ throw new ObjectAccessException("Class " + value.getClass().getName()
+ + " may not write a field named '" + name + "'");
+ }
+ if (value != null) {
+ ExtendedHierarchicalStreamWriterHelper.startNode(
+ writer, mapper.serializedMember(source.getClass(), name),
+ value.getClass());
+ if (field.getType() != value.getClass() && !field.getType().isPrimitive()) {
+ String attributeName = mapper.aliasForSystemAttribute(ATTRIBUTE_CLASS);
+ if (attributeName != null) {
+ writer.addAttribute(attributeName, mapper.serializedClass(value.getClass()));
+ }
+ }
+ context.convertAnother(value);
+ writer.endNode();
+ }
+ }
+ writer.endNode();
+ }
+
+ public void defaultWriteObject() {
+ boolean writtenDefaultFields = false;
+
+ ObjectStreamClass objectStreamClass = ObjectStreamClass.lookup(currentType[0]);
+
+ if (objectStreamClass == null) {
+ return;
+ }
+
+ ObjectStreamField[] fields = objectStreamClass.getFields();
+ for (int i = 0; i < fields.length; i++) {
+ ObjectStreamField field = fields[i];
+ Object value = readField(field, currentType[0], source);
+ if (value != null) {
+ if (!writtenClassWrapper[0]) {
+ writer.startNode(mapper.serializedClass(currentType[0]));
+ writtenClassWrapper[0] = true;
+ }
+ if (!writtenDefaultFields) {
+ writer.startNode(ELEMENT_DEFAULT);
+ writtenDefaultFields = true;
+ }
+ if (!mapper.shouldSerializeMember(currentType[0], field.getName())) {
+ continue;
+ }
+
+ Class actualType = value.getClass();
+ ExtendedHierarchicalStreamWriterHelper.startNode(
+ writer, mapper.serializedMember(source.getClass(), field.getName()), actualType);
+ Class defaultType = mapper.defaultImplementationOf(field.getType());
+ if (!actualType.equals(defaultType)) {
+ String attributeName = mapper.aliasForSystemAttribute(ATTRIBUTE_CLASS);
+ if (attributeName != null) {
+ writer.addAttribute(attributeName, mapper.serializedClass(actualType));
+ }
+ }
+
+ context.convertAnother(value);
+
+ writer.endNode();
+ }
+ }
+ if (writtenClassWrapper[0] && !writtenDefaultFields) {
+ writer.startNode(ELEMENT_DEFAULT);
+ writer.endNode();
+ } else if (writtenDefaultFields) {
+ writer.endNode();
+ }
+ }
+
+ public void flush() {
+ writer.flush();
+ }
+
+ public void close() {
+ throw new UnsupportedOperationException("Objects are not allowed to call ObjectOutputStream.close() from writeObject()");
+ }
+ };
+
+ try {
+ boolean mustHandleUnserializableParent = false;
+ Iterator classHieararchy = hierarchyFor(source.getClass()).iterator();
+ while (classHieararchy.hasNext()) {
+ currentType[0] = (Class) classHieararchy.next();
+ if (!Serializable.class.isAssignableFrom(currentType[0])) {
+ mustHandleUnserializableParent = true;
+ continue;
+ } else {
+ if (mustHandleUnserializableParent) {
+ marshalUnserializableParent(writer, context, source);
+ mustHandleUnserializableParent = false;
+ }
+ if (serializationMembers.supportsWriteObject(currentType[0], false)) {
+ writtenClassWrapper[0] = true;
+ writer.startNode(mapper.serializedClass(currentType[0]));
+ if (currentType[0] != mapper.defaultImplementationOf(currentType[0])) {
+ String classAttributeName = mapper.aliasForSystemAttribute(ATTRIBUTE_CLASS);
+ if (classAttributeName != null) {
+ writer.addAttribute(classAttributeName, currentType[0].getName());
+ }
+ }
+ CustomObjectOutputStream objectOutputStream = CustomObjectOutputStream.getInstance(context, callback);
+ serializationMembers.callWriteObject(currentType[0], source, objectOutputStream);
+ objectOutputStream.popCallback();
+ writer.endNode();
+ } else if (serializationMembers.supportsReadObject(currentType[0], false)) {
+ // Special case for objects that have readObject(), but not writeObject().
+ // The class wrapper is always written, whether or not this class in the hierarchy has
+ // serializable fields. This guarantees that readObject() will be called upon deserialization.
+ writtenClassWrapper[0] = true;
+ writer.startNode(mapper.serializedClass(currentType[0]));
+ if (currentType[0] != mapper.defaultImplementationOf(currentType[0])) {
+ String classAttributeName = mapper.aliasForSystemAttribute(ATTRIBUTE_CLASS);
+ if (classAttributeName != null) {
+ writer.addAttribute(classAttributeName, currentType[0].getName());
+ }
+ }
+ callback.defaultWriteObject();
+ writer.endNode();
+ } else {
+ writtenClassWrapper[0] = false;
+ callback.defaultWriteObject();
+ if (writtenClassWrapper[0]) {
+ writer.endNode();
+ }
+ }
+ }
+ }
+ } catch (IOException e) {
+ throw new ObjectAccessException("Could not call defaultWriteObject()", e);
+ }
+ }
+
+ protected void marshalUnserializableParent(final HierarchicalStreamWriter writer, final MarshallingContext context, final Object replacedSource) {
+ writer.startNode(ELEMENT_UNSERIALIZABLE_PARENTS);
+ super.doMarshal(replacedSource, writer, context);
+ writer.endNode();
+ }
+
+ private Object readField(ObjectStreamField field, Class type, Object instance) {
+ try {
+ Field javaField = type.getDeclaredField(field.getName());
+ if (!javaField.isAccessible()) {
+ javaField.setAccessible(true);
+ }
+ return javaField.get(instance);
+ } catch (IllegalArgumentException e) {
+ throw new ObjectAccessException("Could not get field " + field.getClass() + "." + field.getName(), e);
+ } catch (IllegalAccessException e) {
+ throw new ObjectAccessException("Could not get field " + field.getClass() + "." + field.getName(), e);
+ } catch (NoSuchFieldException e) {
+ throw new ObjectAccessException("Could not get field " + field.getClass() + "." + field.getName(), e);
+ } catch (SecurityException e) {
+ throw new ObjectAccessException("Could not get field " + field.getClass() + "." + field.getName(), e);
+ }
+ }
+
+ protected List hierarchyFor(Class type) {
+ List result = new ArrayList();
+ while(type != Object.class && type != null) {
+ result.add(type);
+ type = type.getSuperclass();
+ }
+
+ // In Java Object Serialization, the classes are deserialized starting from parent class and moving down.
+ Collections.reverse(result);
+
+ return result;
+ }
+
+ public Object doUnmarshal(final Object result, final HierarchicalStreamReader reader, final UnmarshallingContext context) {
+ // this is an array as it's a non final value that's accessed from an anonymous inner class.
+ final Class[] currentType = new Class[1];
+
+ String attributeName = mapper.aliasForSystemAttribute(ATTRIBUTE_SERIALIZATION);
+ if (attributeName != null && !ATTRIBUTE_VALUE_CUSTOM.equals(reader.getAttribute(attributeName))) {
+ throw new ConversionException("Cannot deserialize object with new readObject()/writeObject() methods");
+ }
+
+ CustomObjectInputStream.StreamCallback callback = new CustomObjectInputStream.StreamCallback() {
+ public Object readFromStream() {
+ reader.moveDown();
+ Class type = HierarchicalStreams.readClassType(reader, mapper);
+ Object value = context.convertAnother(result, type);
+ reader.moveUp();
+ return value;
+ }
+
+ public Map readFieldsFromStream() {
+ final Map fields = new HashMap();
+ reader.moveDown();
+ if (reader.getNodeName().equals(ELEMENT_FIELDS)) {
+ // Maintain compatibility with XStream 1.1.0
+ while (reader.hasMoreChildren()) {
+ reader.moveDown();
+ if (!reader.getNodeName().equals(ELEMENT_FIELD)) {
+ throw new ConversionException("Expected <" + ELEMENT_FIELD + "/> element inside <" + ELEMENT_FIELD + "/>");
+ }
+ String name = reader.getAttribute(ATTRIBUTE_NAME);
+ Class type = mapper.realClass(reader.getAttribute(ATTRIBUTE_CLASS));
+ Object value = context.convertAnother(result, type);
+ fields.put(name, value);
+ reader.moveUp();
+ }
+ } else if (reader.getNodeName().equals(ELEMENT_DEFAULT)) {
+ // New format introduced in XStream 1.1.1
+ ObjectStreamClass objectStreamClass = ObjectStreamClass.lookup(currentType[0]);
+ while (reader.hasMoreChildren()) {
+ reader.moveDown();
+ String name = mapper.realMember(currentType[0], reader.getNodeName());
+ if (mapper.shouldSerializeMember(currentType[0], name)) {
+ String classAttribute = HierarchicalStreams.readClassAttribute(reader, mapper);
+ Class type;
+ if (classAttribute != null) {
+ type = mapper.realClass(classAttribute);
+ } else {
+ ObjectStreamField field = objectStreamClass.getField(name);
+ if (field == null) {
+ throw new MissingFieldException(currentType[0].getName(), name);
+ }
+ type = field.getType();
+ }
+ Object value = context.convertAnother(result, type);
+ fields.put(name, value);
+ }
+ reader.moveUp();
+ }
+ } else {
+ throw new ConversionException("Expected <" + ELEMENT_FIELDS + "/> or <" +
+ ELEMENT_DEFAULT + "/> element when calling ObjectInputStream.readFields()");
+ }
+ reader.moveUp();
+ return fields;
+ }
+
+ public void defaultReadObject() {
+ if (serializationMembers.getSerializablePersistentFields(currentType[0]) != null) {
+ readFieldsFromStream();
+ return;
+ }
+ if (!reader.hasMoreChildren()) {
+ return;
+ }
+ reader.moveDown();
+ if (!reader.getNodeName().equals(ELEMENT_DEFAULT)) {
+ throw new ConversionException("Expected <" + ELEMENT_DEFAULT + "/> element in readObject() stream");
+ }
+ while (reader.hasMoreChildren()) {
+ reader.moveDown();
+
+ String fieldName = mapper.realMember(currentType[0], reader.getNodeName());
+ if (mapper.shouldSerializeMember(currentType[0], fieldName)) {
+ String classAttribute = HierarchicalStreams.readClassAttribute(reader, mapper);
+ final Class type;
+ if (classAttribute != null) {
+ type = mapper.realClass(classAttribute);
+ } else {
+ type = mapper.defaultImplementationOf(reflectionProvider.getFieldType(result, fieldName, currentType[0]));
+ }
+
+ Object value = context.convertAnother(result, type);
+ reflectionProvider.writeField(result, fieldName, value, currentType[0]);
+ }
+
+ reader.moveUp();
+ }
+ reader.moveUp();
+ }
+
+ public void registerValidation(final ObjectInputValidation validation, int priority) {
+ context.addCompletionCallback(new Runnable() {
+ public void run() {
+ try {
+ validation.validateObject();
+ } catch (InvalidObjectException e) {
+ throw new ObjectAccessException("Cannot validate object : " + e.getMessage(), e);
+ }
+ }
+ }, priority);
+ }
+
+ public void close() {
+ throw new UnsupportedOperationException("Objects are not allowed to call ObjectInputStream.close() from readObject()");
+ }
+ };
+
+ while (reader.hasMoreChildren()) {
+ reader.moveDown();
+ String nodeName = reader.getNodeName();
+ if (nodeName.equals(ELEMENT_UNSERIALIZABLE_PARENTS)) {
+ super.doUnmarshal(result, reader, context);
+ } else {
+ String classAttribute = HierarchicalStreams.readClassAttribute(reader, mapper);
+ if (classAttribute == null) {
+ currentType[0] = mapper.defaultImplementationOf(mapper.realClass(nodeName));
+ } else {
+ currentType[0] = mapper.realClass(classAttribute);
+ }
+ if (serializationMembers.supportsReadObject(currentType[0], false)) {
+ CustomObjectInputStream objectInputStream =
+ CustomObjectInputStream.getInstance(context, callback, classLoaderReference);
+ serializationMembers.callReadObject(currentType[0], result, objectInputStream);
+ objectInputStream.popCallback();
+ } else {
+ try {
+ callback.defaultReadObject();
+ } catch (IOException e) {
+ throw new ObjectAccessException("Could not call defaultWriteObject()", e);
+ }
+ }
+ }
+ reader.moveUp();
+ }
+
+ return result;
+ }
+
+ protected void doMarshalConditionally(final Object source, final HierarchicalStreamWriter writer, final MarshallingContext context) {
+ if(isSerializable(source.getClass())) {
+ doMarshal(source, writer, context);
+ } else {
+ super.doMarshal(source, writer, context);
+ }
+ }
+
+ protected Object doUnmarshalConditionally(final Object result, final HierarchicalStreamReader reader, final UnmarshallingContext context) {
+ return isSerializable(result.getClass()) ? doUnmarshal(result, reader, context) : super.doUnmarshal(result, reader, context);
+ }
+
+ private static class UnserializableParentsReflectionProvider extends ReflectionProviderWrapper {
+
+ public UnserializableParentsReflectionProvider(final ReflectionProvider reflectionProvider) {
+ super(reflectionProvider);
+ }
+
+ public void visitSerializableFields(final Object object, final Visitor visitor) {
+ wrapped.visitSerializableFields(object, new Visitor() {
+ public void visit(String name, Class type, Class definedIn, Object value) {
+ if (!Serializable.class.isAssignableFrom(definedIn)) {
+ visitor.visit(name, type, definedIn, value);
+ }
+ }
+ });
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/reflection/SerializationMethodInvoker.java b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/SerializationMethodInvoker.java
new file mode 100644
index 0000000..e8b3193
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/SerializationMethodInvoker.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2010, 2011, 2014, 2015 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 23. August 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.reflection;
+
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import com.thoughtworks.xstream.core.Caching;
+import com.thoughtworks.xstream.core.util.SerializationMembers;
+
+/**
+ * Convenience wrapper to invoke special serialization methods on objects (and perform
+ * reflection caching).
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ * @deprecated As of 1.4.8, moved into internal util package.
+ */
+public class SerializationMethodInvoker implements Caching {
+
+ SerializationMembers serializationMembers = new SerializationMembers();
+
+ /**
+ * Resolves an object as native serialization does by calling readResolve(), if available.
+ *
+ * @deprecated As of 1.4.8, moved into internal util package.
+ */
+ public Object callReadResolve(Object result) {
+ return serializationMembers.callReadResolve(result);
+ }
+
+ /**
+ * @deprecated As of 1.4.8, moved into internal util package.
+ */
+ public Object callWriteReplace(Object object) {
+ return serializationMembers.callWriteReplace(object);
+ }
+
+ /**
+ * @deprecated As of 1.4.8, moved into internal util package.
+ */
+ public boolean supportsReadObject(Class type, boolean includeBaseClasses) {
+ return serializationMembers.supportsReadObject(type, includeBaseClasses);
+ }
+
+ /**
+ * @deprecated As of 1.4.8, moved into internal util package.
+ */
+ public void callReadObject(Class type, Object object, ObjectInputStream stream) {
+ serializationMembers.callReadObject(type, object, stream);
+ }
+
+ /**
+ * @deprecated As of 1.4.8, moved into internal util package.
+ */
+ public boolean supportsWriteObject(Class type, boolean includeBaseClasses) {
+ return serializationMembers.supportsWriteObject(type, includeBaseClasses);
+ }
+
+ /**
+ * @deprecated As of 1.4.8, moved into internal util package.
+ */
+ public void callWriteObject(Class type, Object instance, ObjectOutputStream stream) {
+ serializationMembers.callWriteObject(type, instance, stream);
+ }
+
+ /**
+ * @deprecated As of 1.4.8, moved into internal util package.
+ */
+ public void flushCache() {
+ serializationMembers.flushCache();
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/reflection/SortableFieldKeySorter.java b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/SortableFieldKeySorter.java
new file mode 100644
index 0000000..f6bee5e
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/SortableFieldKeySorter.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2007, 2009, 2011 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 10. April 2007 by Guilherme Silveira
+ */
+package com.thoughtworks.xstream.converters.reflection;
+
+import com.thoughtworks.xstream.core.Caching;
+import com.thoughtworks.xstream.core.util.OrderRetainingMap;
+import com.thoughtworks.xstream.io.StreamException;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * The default implementation for sorting fields. Invoke registerFieldOrder in order to set the
+ * field order for an specific type.
+ *
+ * @author Guilherme Silveira
+ * @since 1.2.2
+ */
+public class SortableFieldKeySorter implements FieldKeySorter, Caching {
+
+ private final Map map = new HashMap();
+
+ public Map sort(Class type, Map keyedByFieldKey) {
+ if (map.containsKey(type)) {
+ Map result = new OrderRetainingMap();
+ FieldKey[] fieldKeys = (FieldKey[])keyedByFieldKey.keySet().toArray(
+ new FieldKey[keyedByFieldKey.size()]);
+ Arrays.sort(fieldKeys, (Comparator)map.get(type));
+ for (int i = 0; i < fieldKeys.length; i++ ) {
+ result.put(fieldKeys[i], keyedByFieldKey.get(fieldKeys[i]));
+ }
+ return result;
+ } else {
+ return keyedByFieldKey;
+ }
+ }
+
+ /**
+ * Registers the field order to use for a specific type. This will not affect any of the
+ * type's super or sub classes. If you skip a field which will be serialized, XStream will
+ * thrown an StreamException during the serialization process.
+ *
+ * @param type the type
+ * @param fields the field order
+ */
+ public void registerFieldOrder(Class type, String[] fields) {
+ map.put(type, new FieldComparator(fields));
+ }
+
+ private class FieldComparator implements Comparator {
+
+ private final String[] fieldOrder;
+
+ public FieldComparator(String[] fields) {
+ this.fieldOrder = fields;
+ }
+
+ public int compare(String first, String second) {
+ int firstPosition = -1, secondPosition = -1;
+ for (int i = 0; i < fieldOrder.length; i++ ) {
+ if (fieldOrder[i].equals(first)) {
+ firstPosition = i;
+ }
+ if (fieldOrder[i].equals(second)) {
+ secondPosition = i;
+ }
+ }
+ if (firstPosition == -1 || secondPosition == -1) {
+ // field not defined!!!
+ throw new StreamException(
+ "You have not given XStream a list of all fields to be serialized.");
+ }
+ return firstPosition - secondPosition;
+ }
+
+ public int compare(Object firstObject, Object secondObject) {
+ FieldKey first = (FieldKey)firstObject, second = (FieldKey)secondObject;
+ return compare(first.getFieldName(), second.getFieldName());
+ }
+
+ }
+
+ public void flushCache() {
+ map.clear();
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/reflection/Sun14ReflectionProvider.java b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/Sun14ReflectionProvider.java
new file mode 100644
index 0000000..3c1be88
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/Sun14ReflectionProvider.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2011, 2013, 2014 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 07. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.converters.reflection;
+
+/**
+ * Instantiates a new object on the Sun JVM by bypassing the constructor (meaning code in the constructor will never be
+ * executed and parameters do not have to be known). This is the same method used by the internals of standard Java
+ * serialization, but relies on internal Sun code that may not be present on all JVMs.
+ *
+ * @author Joe Walnes
+ * @author Brian Slesinsky
+ * @deprecated As of 1.4.7 use {@link SunUnsafeReflectionProvider}
+ */
+public class Sun14ReflectionProvider extends SunUnsafeReflectionProvider {
+ /**
+ * @deprecated As of 1.4.7 use {@link SunUnsafeReflectionProvider#SunUnsafeReflectionProvider()}
+ */
+ public Sun14ReflectionProvider() {
+ super();
+ }
+
+ /**
+ * @deprecated As of 1.4.7 use {@link SunUnsafeReflectionProvider#SunUnsafeReflectionProvider(FieldDictionary)}
+ */
+ public Sun14ReflectionProvider(FieldDictionary dic) {
+ super(dic);
+ }
+
+ private Object readResolve() {
+ init();
+ return this;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/reflection/SunLimitedUnsafeReflectionProvider.java b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/SunLimitedUnsafeReflectionProvider.java
new file mode 100644
index 0000000..4faef33
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/SunLimitedUnsafeReflectionProvider.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2011, 2013, 2014 XStream Committers.
+ * All rights reserved.
+ *
+ * Created on 08. January 2014 by Joerg Schaible, factored out from SunUnsafeReflectionProvider
+ */
+package com.thoughtworks.xstream.converters.reflection;
+
+import java.lang.reflect.Field;
+
+import sun.misc.Unsafe;
+
+
+/**
+ * Instantiates a new object bypassing the constructor using undocumented internal JDK features.
+ *
+ * The code in the constructor will never be executed and parameters do not have to be known. This is the same method
+ * used by the internals of standard Java serialization, but relies on internal code (sun.misc.Unsafe) that may not be
+ * present on all JVMs.
+ *
+ *
+ * The implementation will use standard Java functionality to write any fields. This requires Java 5 as minimum runtime
+ * and is used as fallback on platforms that do not provide the complete implementation level for the internals (like
+ * Dalvik).
+ *
+ *
+ * @author Jörg Schaible
+ * @author Joe Walnes
+ * @author Brian Slesinsky
+ * @since 1.4.7
+ */
+public class SunLimitedUnsafeReflectionProvider extends PureJavaReflectionProvider {
+
+ protected static final Unsafe unsafe;
+ protected static final Exception exception;
+ static {
+ Unsafe u = null;
+ Exception ex = null;
+ try {
+ Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
+ unsafeField.setAccessible(true);
+ u = (Unsafe)unsafeField.get(null);
+ } catch (SecurityException e) {
+ ex = e;
+ } catch (NoSuchFieldException e) {
+ ex = e;
+ } catch (IllegalArgumentException e) {
+ ex = e;
+ } catch (IllegalAccessException e) {
+ ex = e;
+ }
+ exception = ex;
+ unsafe = u;
+ }
+
+ /**
+ * @since 1.4.7
+ */
+ public SunLimitedUnsafeReflectionProvider() {
+ super();
+ }
+
+ /**
+ * @since 1.4.7
+ */
+ public SunLimitedUnsafeReflectionProvider(FieldDictionary fieldDictionary) {
+ super(fieldDictionary);
+ }
+
+ public Object newInstance(Class type) {
+ if (exception != null) {
+ throw new ObjectAccessException("Cannot construct " + type.getName(), exception);
+ }
+ try {
+ return unsafe.allocateInstance(type);
+ } catch (SecurityException e) {
+ throw new ObjectAccessException("Cannot construct " + type.getName(), e);
+ } catch (InstantiationException e) {
+ throw new ObjectAccessException("Cannot construct " + type.getName(), e);
+ } catch (IllegalArgumentException e) {
+ throw new ObjectAccessException("Cannot construct " + type.getName(), e);
+ }
+ }
+
+ protected void validateFieldAccess(Field field) {
+ // (overriden) don't mind final fields.
+ }
+
+ private Object readResolve() {
+ init();
+ return this;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/reflection/SunUnsafeReflectionProvider.java b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/SunUnsafeReflectionProvider.java
new file mode 100644
index 0000000..c97c0c8
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/SunUnsafeReflectionProvider.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2011, 2013, 2014 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 08. January 2014 by Joerg Schaible, renamed from Sun14ReflectionProvider
+ */
+package com.thoughtworks.xstream.converters.reflection;
+
+import java.lang.reflect.Field;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+
+/**
+ * Instantiates a new object bypassing the constructor using undocumented internal JDK features.
+ *
+ * The code in the constructor will never be executed and parameters do not have to be known. This is the same method
+ * used by the internals of standard Java serialization, but relies on internal code (sun.misc.Unsafe) that may not be
+ * present on all JVMs.
+ *
+ *
+ * The implementation will use the same internals to write into fields. This is a lot faster and was additionally the
+ * only possibility to set final fields prior to Java 5.
+ *
+ *
+ * @author Joe Walnes
+ * @author Brian Slesinsky
+ * @author Jörg Schaible
+ * @since 1.4.7
+ */
+public class SunUnsafeReflectionProvider extends SunLimitedUnsafeReflectionProvider {
+
+ // references to the Field key are kept in the FieldDictionary
+ private transient Map fieldOffsetCache;
+
+ /**
+ * @since 1.4.7
+ */
+ public SunUnsafeReflectionProvider() {
+ super();
+ }
+
+ /**
+ * @since 1.4.7
+ */
+ public SunUnsafeReflectionProvider(FieldDictionary dic) {
+ super(dic);
+ }
+
+ public void writeField(Object object, String fieldName, Object value, Class definedIn) {
+ write(fieldDictionary.field(object.getClass(), fieldName, definedIn), object, value);
+ }
+
+ private void write(Field field, Object object, Object value) {
+ if (exception != null) {
+ throw new ObjectAccessException("Could not set field " + object.getClass() + "." + field.getName(),
+ exception);
+ }
+ try {
+ long offset = getFieldOffset(field);
+ Class type = field.getType();
+ if (type.isPrimitive()) {
+ if (type.equals(Integer.TYPE)) {
+ unsafe.putInt(object, offset, ((Integer)value).intValue());
+ } else if (type.equals(Long.TYPE)) {
+ unsafe.putLong(object, offset, ((Long)value).longValue());
+ } else if (type.equals(Short.TYPE)) {
+ unsafe.putShort(object, offset, ((Short)value).shortValue());
+ } else if (type.equals(Character.TYPE)) {
+ unsafe.putChar(object, offset, ((Character)value).charValue());
+ } else if (type.equals(Byte.TYPE)) {
+ unsafe.putByte(object, offset, ((Byte)value).byteValue());
+ } else if (type.equals(Float.TYPE)) {
+ unsafe.putFloat(object, offset, ((Float)value).floatValue());
+ } else if (type.equals(Double.TYPE)) {
+ unsafe.putDouble(object, offset, ((Double)value).doubleValue());
+ } else if (type.equals(Boolean.TYPE)) {
+ unsafe.putBoolean(object, offset, ((Boolean)value).booleanValue());
+ } else {
+ throw new ObjectAccessException("Could not set field "
+ + object.getClass()
+ + "."
+ + field.getName()
+ + ": Unknown type "
+ + type);
+ }
+ } else {
+ unsafe.putObject(object, offset, value);
+ }
+
+ } catch (IllegalArgumentException e) {
+ throw new ObjectAccessException("Could not set field " + object.getClass() + "." + field.getName(), e);
+ }
+ }
+
+ private synchronized long getFieldOffset(Field f) {
+ Long l = (Long)fieldOffsetCache.get(f);
+ if (l == null) {
+ l = new Long(unsafe.objectFieldOffset(f));
+ fieldOffsetCache.put(f, l);
+ }
+
+ return l.longValue();
+ }
+
+ private Object readResolve() {
+ init();
+ return this;
+ }
+
+ protected void init() {
+ super.init();
+ fieldOffsetCache = new WeakHashMap();
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/converters/reflection/XStream12FieldKeySorter.java b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/XStream12FieldKeySorter.java
new file mode 100644
index 0000000..121d9e0
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/converters/reflection/XStream12FieldKeySorter.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2007, 2008, 2013 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 19.09.2007 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.converters.reflection;
+
+import java.util.Comparator;
+import java.util.Map;
+import java.util.TreeMap;
+
+
+/**
+ * Sort the fields in the order of XStream 1.2.x. Fields are returned in their declaration order,
+ * fields of base classes last.
+ *
+ * @author Jörg Schaible
+ * @since 1.3
+ */
+public class XStream12FieldKeySorter implements FieldKeySorter {
+
+ public Map sort(final Class type, final Map keyedByFieldKey) {
+ final Map map = new TreeMap(new Comparator() {
+
+ public int compare(final Object o1, final Object o2) {
+ final FieldKey fieldKey1 = (FieldKey)o1;
+ final FieldKey fieldKey2 = (FieldKey)o2;
+ int i = fieldKey2.getDepth() - fieldKey1.getDepth();
+ if (i == 0) {
+ i = fieldKey1.getOrder() - fieldKey2.getOrder();
+ }
+ return i;
+ }
+ });
+ map.putAll(keyedByFieldKey);
+ keyedByFieldKey.clear();
+ keyedByFieldKey.putAll(map);
+ return keyedByFieldKey;
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/AbstractReferenceMarshaller.java b/xstream/src/java/com/thoughtworks/xstream/core/AbstractReferenceMarshaller.java
new file mode 100644
index 0000000..cbd9417
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/AbstractReferenceMarshaller.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 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 15. March 2007 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.core;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.ConverterLookup;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.core.util.ObjectIdDictionary;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.io.path.Path;
+import com.thoughtworks.xstream.io.path.PathTracker;
+import com.thoughtworks.xstream.io.path.PathTrackingWriter;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+import java.util.Iterator;
+
+/**
+ * Abstract base class for a TreeMarshaller, that can build references.
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ * @author Mauro Talevi
+ * @since 1.2
+ */
+public abstract class AbstractReferenceMarshaller extends TreeMarshaller implements MarshallingContext {
+
+ private ObjectIdDictionary references = new ObjectIdDictionary();
+ private ObjectIdDictionary implicitElements = new ObjectIdDictionary();
+ private PathTracker pathTracker = new PathTracker();
+ private Path lastPath;
+
+ public AbstractReferenceMarshaller(HierarchicalStreamWriter writer,
+ ConverterLookup converterLookup,
+ Mapper mapper) {
+ super(writer, converterLookup, mapper);
+ this.writer = new PathTrackingWriter(writer, pathTracker);
+ }
+
+ public void convert(Object item, Converter converter) {
+ if (getMapper().isImmutableValueType(item.getClass())) {
+ // strings, ints, dates, etc... don't bother using references.
+ converter.marshal(item, writer, this);
+ } else {
+ final Path currentPath = pathTracker.getPath();
+ Id existingReference = (Id)references.lookupId(item);
+ if (existingReference != null && existingReference.getPath() != currentPath) {
+ String attributeName = getMapper().aliasForSystemAttribute("reference");
+ if (attributeName != null) {
+ writer.addAttribute(attributeName, createReference(currentPath, existingReference.getItem()));
+ }
+ } else {
+ final Object newReferenceKey = existingReference == null
+ ? createReferenceKey(currentPath, item)
+ : existingReference.getItem();
+ if (lastPath == null || !currentPath.isAncestor(lastPath)) {
+ fireValidReference(newReferenceKey);
+ lastPath = currentPath;
+ references.associateId(item, new Id(newReferenceKey, currentPath));
+ }
+ converter.marshal(item, writer, new ReferencingMarshallingContext() {
+
+ public void put(Object key, Object value) {
+ AbstractReferenceMarshaller.this.put(key, value);
+ }
+
+ public Iterator keys() {
+ return AbstractReferenceMarshaller.this.keys();
+ }
+
+ public Object get(Object key) {
+ return AbstractReferenceMarshaller.this.get(key);
+ }
+
+ public void convertAnother(Object nextItem, Converter converter) {
+ AbstractReferenceMarshaller.this.convertAnother(nextItem, converter);
+ }
+
+ public void convertAnother(Object nextItem) {
+ AbstractReferenceMarshaller.this.convertAnother(nextItem);
+ }
+
+ public void replace(Object original, Object replacement) {
+ references.associateId(replacement, new Id(newReferenceKey, currentPath));
+ }
+
+ public Object lookupReference(Object item) {
+ Id id = (Id)references.lookupId(item);
+ return id.getItem();
+ }
+
+ /**
+ * @deprecated As of 1.4.2
+ */
+ public Path currentPath() {
+ return pathTracker.getPath();
+ }
+
+ public void registerImplicit(Object item) {
+ if (implicitElements.containsId(item)) {
+ throw new ReferencedImplicitElementException(item, currentPath);
+ }
+ implicitElements.associateId(item, newReferenceKey);
+ }
+ });
+ }
+ }
+ }
+
+ protected abstract String createReference(Path currentPath, Object existingReferenceKey);
+ protected abstract Object createReferenceKey(Path currentPath, Object item);
+ protected abstract void fireValidReference(Object referenceKey);
+
+ private static class Id {
+ private Object item;
+ private Path path;
+ public Id(Object item, Path path) {
+ this.item = item;
+ this.path = path;
+ }
+ protected Object getItem() {
+ return this.item;
+ }
+ protected Path getPath() {
+ return this.path;
+ }
+ }
+
+ public static class ReferencedImplicitElementException extends ConversionException {
+ public ReferencedImplicitElementException(final Object item, final Path path) {
+ super("Cannot reference implicit element");
+ add("implicit-element", item.toString());
+ add("referencing-element", path.toString());
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/AbstractReferenceUnmarshaller.java b/xstream/src/java/com/thoughtworks/xstream/core/AbstractReferenceUnmarshaller.java
new file mode 100644
index 0000000..d8218fd
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/AbstractReferenceUnmarshaller.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2006, 2007, 2008, 2011 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 15. March 2007 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.core;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.ConverterLookup;
+import com.thoughtworks.xstream.core.util.FastStack;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+/**
+ * Abstract base class for a TreeUnmarshaller, that resolves references.
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ * @author Mauro Talevi
+ * @since 1.2
+ */
+public abstract class AbstractReferenceUnmarshaller extends TreeUnmarshaller {
+
+ private static final Object NULL = new Object();
+ private Map values = new HashMap();
+ private FastStack parentStack = new FastStack(16);
+
+ public AbstractReferenceUnmarshaller(Object root, HierarchicalStreamReader reader,
+ ConverterLookup converterLookup, Mapper mapper) {
+ super(root, reader, converterLookup, mapper);
+ }
+
+ protected Object convert(Object parent, Class type, Converter converter) {
+ if (parentStack.size() > 0) { // handles circular references
+ Object parentReferenceKey = parentStack.peek();
+ if (parentReferenceKey != null) {
+ if (!values.containsKey(parentReferenceKey)) { // see AbstractCircularReferenceTest.testWeirdCircularReference()
+ values.put(parentReferenceKey, parent);
+ }
+ }
+ }
+ final Object result;
+ String attributeName = getMapper().aliasForSystemAttribute("reference");
+ String reference = attributeName == null ? null : reader.getAttribute(attributeName);
+ if (reference != null) {
+ Object cache = values.get(getReferenceKey(reference));
+ if (cache == null) {
+ final ConversionException ex = new ConversionException("Invalid reference");
+ ex.add("reference", reference);
+ throw ex;
+ }
+ result = cache == NULL ? null : cache;
+ } else {
+ Object currentReferenceKey = getCurrentReferenceKey();
+ parentStack.push(currentReferenceKey);
+ result = super.convert(parent, type, converter);
+ if (currentReferenceKey != null) {
+ values.put(currentReferenceKey, result == null ? NULL : result);
+ }
+ parentStack.popSilently();
+ }
+ return result;
+ }
+
+ protected abstract Object getReferenceKey(String reference);
+ protected abstract Object getCurrentReferenceKey();
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/AbstractTreeMarshallingStrategy.java b/xstream/src/java/com/thoughtworks/xstream/core/AbstractTreeMarshallingStrategy.java
new file mode 100644
index 0000000..46d09c3
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/AbstractTreeMarshallingStrategy.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2006, 2007, 2008, 2009 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.09.2007 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.core;
+
+import com.thoughtworks.xstream.MarshallingStrategy;
+import com.thoughtworks.xstream.converters.ConverterLookup;
+import com.thoughtworks.xstream.converters.DataHolder;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+
+/**
+ * Basic functionality of a tree based marshalling strategy.
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ * @since 1.3
+ */
+public abstract class AbstractTreeMarshallingStrategy implements MarshallingStrategy {
+
+ public Object unmarshal(Object root, HierarchicalStreamReader reader, DataHolder dataHolder, ConverterLookup converterLookup, Mapper mapper) {
+ TreeUnmarshaller context = createUnmarshallingContext(root, reader, converterLookup, mapper);
+ return context.start(dataHolder);
+ }
+
+ public void marshal(HierarchicalStreamWriter writer, Object obj, ConverterLookup converterLookup, Mapper mapper, DataHolder dataHolder) {
+ TreeMarshaller context = createMarshallingContext(writer, converterLookup, mapper);
+ context.start(obj, dataHolder);
+ }
+
+ protected abstract TreeUnmarshaller createUnmarshallingContext(Object root,
+ HierarchicalStreamReader reader, ConverterLookup converterLookup, Mapper mapper);
+
+ protected abstract TreeMarshaller createMarshallingContext(
+ HierarchicalStreamWriter writer, ConverterLookup converterLookup, Mapper mapper);
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/BaseException.java b/xstream/src/java/com/thoughtworks/xstream/core/BaseException.java
new file mode 100644
index 0000000..a1bdb51
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/BaseException.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009 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 14. September 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.core;
+
+import com.thoughtworks.xstream.XStreamException;
+
+/**
+ * JDK1.3 friendly exception that retains cause.
+ * @deprecated As of 1.3, use {@link XStreamException} instead
+ */
+public abstract class BaseException extends RuntimeException {
+
+ protected BaseException(String message) {
+ super(message);
+ }
+
+ public abstract Throwable getCause();
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/Caching.java b/xstream/src/java/com/thoughtworks/xstream/core/Caching.java
new file mode 100644
index 0000000..ef329da
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/Caching.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2011 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 19. July 2011 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.core;
+
+/**
+ * Marker interface for caching implementations.
+ *
+ * @author Jörg Schaible
+ * @since 1.4
+ */
+public interface Caching {
+ void flushCache();
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/ClassLoaderReference.java b/xstream/src/java/com/thoughtworks/xstream/core/ClassLoaderReference.java
new file mode 100644
index 0000000..d6701ec
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/ClassLoaderReference.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2013 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. June 2013 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.core;
+
+import com.thoughtworks.xstream.core.util.CompositeClassLoader;
+
+/**
+ * Reference to a ClassLoader, allowing a single instance to be passed around the codebase that
+ * can later have its destination changed.
+ *
+ * @author Jörg Schaible
+ * @since 1.4.5
+ */
+public final class ClassLoaderReference {
+
+ private transient ClassLoader reference;
+
+ public ClassLoaderReference(ClassLoader reference) {
+ setReference(reference);
+ }
+
+ public ClassLoader getReference() {
+ return reference;
+ }
+
+ public void setReference(ClassLoader reference) {
+ this.reference = reference instanceof com.thoughtworks.xstream.core.util.ClassLoaderReference
+ ? ((com.thoughtworks.xstream.core.util.ClassLoaderReference)reference)
+ .getReference() : reference;
+ }
+
+ private Object readResolve() {
+ this.reference = new CompositeClassLoader();
+ return this;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/DefaultConverterLookup.java b/xstream/src/java/com/thoughtworks/xstream/core/DefaultConverterLookup.java
new file mode 100644
index 0000000..b4b955f
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/DefaultConverterLookup.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009, 2011, 2013 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 07. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.core;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.ConverterLookup;
+import com.thoughtworks.xstream.converters.ConverterRegistry;
+import com.thoughtworks.xstream.core.util.PrioritizedList;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+/**
+ * The default implementation of converters lookup.
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ * @author Guilherme Silveira
+ */
+public class DefaultConverterLookup implements ConverterLookup, ConverterRegistry, Caching {
+
+ private final PrioritizedList converters = new PrioritizedList();
+ private transient Map typeToConverterMap;
+
+ public DefaultConverterLookup() {
+ readResolve();
+ }
+
+ /**
+ * @deprecated As of 1.3, use {@link #DefaultConverterLookup()}
+ */
+ public DefaultConverterLookup(Mapper mapper) {
+ }
+
+ public Converter lookupConverterForType(Class type) {
+ Converter cachedConverter = (Converter) typeToConverterMap.get(type);
+ if (cachedConverter != null) {
+ return cachedConverter;
+ }
+ Iterator iterator = converters.iterator();
+ while (iterator.hasNext()) {
+ Converter converter = (Converter) iterator.next();
+ if (converter.canConvert(type)) {
+ typeToConverterMap.put(type, converter);
+ return converter;
+ }
+ }
+ throw new ConversionException("No converter specified for " + type);
+ }
+
+ public void registerConverter(Converter converter, int priority) {
+ converters.add(converter, priority);
+ for (Iterator iter = typeToConverterMap.keySet().iterator(); iter.hasNext();) {
+ Class type = (Class) iter.next();
+ if (converter.canConvert(type)) {
+ iter.remove();
+ }
+ }
+ }
+
+ public void flushCache() {
+ typeToConverterMap.clear();
+ Iterator iterator = converters.iterator();
+ while (iterator.hasNext()) {
+ Converter converter = (Converter) iterator.next();
+ if (converter instanceof Caching) {
+ ((Caching)converter).flushCache();
+ }
+ }
+ }
+
+ private Object readResolve() {
+ typeToConverterMap = Collections.synchronizedMap(new WeakHashMap());
+ return this;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/JVM.java b/xstream/src/java/com/thoughtworks/xstream/core/JVM.java
new file mode 100644
index 0000000..056a4fc
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/JVM.java
@@ -0,0 +1,540 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2010, 2011, 2012, 2013, 2014, 2015 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 09. May 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.core;
+
+import com.thoughtworks.xstream.converters.reflection.FieldDictionary;
+import com.thoughtworks.xstream.converters.reflection.ObjectAccessException;
+import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider;
+import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
+import com.thoughtworks.xstream.core.util.CustomObjectOutputStream;
+import com.thoughtworks.xstream.core.util.DependencyInjectionFactory;
+import com.thoughtworks.xstream.core.util.PresortedMap;
+import com.thoughtworks.xstream.core.util.PresortedSet;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.text.AttributedString;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Comparator;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+public class JVM implements Caching {
+
+ private ReflectionProvider reflectionProvider;
+
+ private static final boolean isAWTAvailable;
+ private static final boolean isSwingAvailable;
+ private static final boolean isSQLAvailable;
+ private static final boolean canAllocateWithUnsafe;
+ private static final boolean canWriteWithUnsafe;
+ private static final boolean optimizedTreeSetAddAll;
+ private static final boolean optimizedTreeMapPutAll;
+ private static final boolean canParseUTCDateFormat;
+ private static final boolean canParseISO8601TimeZoneInDateFormat;
+ private static final boolean canCreateDerivedObjectOutputStream;
+
+ private static final String vendor = System.getProperty("java.vm.vendor");
+ private static final float majorJavaVersion = getMajorJavaVersion();
+ private static final float DEFAULT_JAVA_VERSION = 1.4f;
+ private static final boolean reverseFieldOrder = false;
+ private static final Class reflectionProviderType;
+
+ static class Test {
+ private Object o;
+ private char c;
+ private byte b;
+ private short s;
+ private int i;
+ private long l;
+ private float f;
+ private double d;
+ private boolean bool;
+ Test() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ static {
+ boolean test = true;
+ Object unsafe = null;
+ try {
+ Class unsafeClass = Class.forName("sun.misc.Unsafe");
+ Field unsafeField = unsafeClass.getDeclaredField("theUnsafe");
+ unsafeField.setAccessible(true);
+ unsafe = unsafeField.get(null);
+ Method allocateInstance = unsafeClass.getDeclaredMethod("allocateInstance", new Class[]{Class.class});
+ allocateInstance.setAccessible(true);
+ test = allocateInstance.invoke(unsafe, new Object[]{Test.class}) != null;
+ } catch (Exception e) {
+ test = false;
+ } catch (Error e) {
+ test = false;
+ }
+ canAllocateWithUnsafe = test;
+ test = false;
+ Class type = PureJavaReflectionProvider.class;
+ if (canUseSunUnsafeReflectionProvider()) {
+ Class cls = loadClassForName("com.thoughtworks.xstream.converters.reflection.SunUnsafeReflectionProvider");
+ if (cls != null) {
+ try {
+ ReflectionProvider provider = (ReflectionProvider)DependencyInjectionFactory.newInstance(cls, null);
+ Test t = (Test)provider.newInstance(Test.class);
+ try {
+ provider.writeField(t, "o", "object", Test.class);
+ provider.writeField(t, "c", new Character('c'), Test.class);
+ provider.writeField(t, "b", new Byte((byte)1), Test.class);
+ provider.writeField(t, "s", new Short((short)1), Test.class);
+ provider.writeField(t, "i", new Integer(1), Test.class);
+ provider.writeField(t, "l", new Long(1), Test.class);
+ provider.writeField(t, "f", new Float(1), Test.class);
+ provider.writeField(t, "d", new Double(1), Test.class);
+ provider.writeField(t, "bool", Boolean.TRUE, Test.class);
+ test = true;
+ } catch(IncompatibleClassChangeError e) {
+ cls = null;
+ } catch (ObjectAccessException e) {
+ cls = null;
+ }
+ if (cls == null) {
+ cls = loadClassForName("com.thoughtworks.xstream.converters.reflection.SunLimitedUnsafeReflectionProvider");
+ }
+ type = cls;
+ } catch (ObjectAccessException e) {
+ }
+ }
+ }
+ reflectionProviderType = type;
+ canWriteWithUnsafe = test;
+ Comparator comparator = new Comparator() {
+ public int compare(Object o1, Object o2) {
+ throw new RuntimeException();
+ }
+ };
+ SortedMap map = new PresortedMap(comparator);
+ map.put("one", null);
+ map.put("two", null);
+ try {
+ new TreeMap(comparator).putAll(map);
+ test = true;
+ } catch (RuntimeException e) {
+ test = false;
+ }
+ optimizedTreeMapPutAll = test;
+ SortedSet set = new PresortedSet(comparator);
+ set.addAll(map.keySet());
+ try {
+ new TreeSet(comparator).addAll(set);
+ test = true;
+ } catch (RuntimeException e) {
+ test = false;
+ }
+ optimizedTreeSetAddAll = test;
+ try {
+ new SimpleDateFormat("z").parse("UTC");
+ test = true;
+ } catch (ParseException e) {
+ test = false;
+ }
+ canParseUTCDateFormat = test;
+ try {
+ new SimpleDateFormat("X").parse("Z");
+ test = true;
+ } catch (final ParseException e) {
+ test = false;
+ } catch (final IllegalArgumentException e) {
+ test = false;
+ }
+ canParseISO8601TimeZoneInDateFormat = test;
+ try {
+ test = new CustomObjectOutputStream(null) != null;
+ } catch (RuntimeException e) {
+ test = false;
+ } catch (IOException e) {
+ test = false;
+ }
+ canCreateDerivedObjectOutputStream = test;
+
+ isAWTAvailable = loadClassForName("java.awt.Color", false) != null;
+ isSwingAvailable = loadClassForName("javax.swing.LookAndFeel", false) != null;
+ isSQLAvailable = loadClassForName("java.sql.Date") != null;
+ }
+
+ /**
+ * @deprecated As of 1.4.5 use the static methods of JVM.
+ */
+ public JVM() {
+ }
+
+ /**
+ * Parses the java version system property to determine the major java version,
+ * i.e. 1.x
+ *
+ * @return A float of the form 1.x
+ */
+ private static final float getMajorJavaVersion() {
+ try {
+ return isAndroid() ? 1.5f : Float.parseFloat(System.getProperty("java.specification.version"));
+ } catch ( NumberFormatException e ){
+ // Some JVMs may not conform to the x.y.z java.version format
+ return DEFAULT_JAVA_VERSION;
+ }
+ }
+
+ /**
+ * @deprecated As of 1.4.4, minimal JDK version is 1.4 already
+ */
+ public static boolean is14() {
+ return majorJavaVersion >= 1.4f;
+ }
+
+ /**
+ * @deprecated As of 1.4.4, minimal JDK version will be 1.6 for next major release
+ */
+ public static boolean is15() {
+ return majorJavaVersion >= 1.5f;
+ }
+
+ /**
+ * @deprecated As of 1.4.4, minimal JDK version will be 1.6 for next major release
+ */
+ public static boolean is16() {
+ return majorJavaVersion >= 1.6f;
+ }
+
+ /**
+ * @since 1.4
+ */
+ public static boolean is17() {
+ return majorJavaVersion >= 1.7f;
+ }
+
+ /**
+ * @since 1.4
+ */
+ public static boolean is18() {
+ return majorJavaVersion >= 1.8f;
+ }
+
+ /**
+ * @since 1.4.8
+ */
+ public static boolean is19() {
+ return majorJavaVersion >= 1.9f;
+ }
+
+ private static boolean isIBM() {
+ return vendor.indexOf("IBM") != -1;
+ }
+
+ /**
+ * @since 1.4
+ */
+ private static boolean isAndroid() {
+ return vendor.indexOf("Android") != -1;
+ }
+
+ /**
+ * Load a XStream class for the given name.
+ *
+ *
This method is not meant to use loading arbitrary classes. It is used by XStream bootstrap
+ * until it is able to use the user provided or the default {@link ClassLoader}.
+ *
+ * @since 1.4.5
+ */
+ public static Class loadClassForName(String name) {
+ return loadClassForName(name, true);
+ }
+
+ /**
+ * @deprecated As of 1.4.5 use {@link #loadClassForName(String)}
+ */
+ public Class loadClass(String name) {
+ return loadClassForName(name, true);
+ }
+
+ /**
+ * Load a XStream class for the given name.
+ *
+ *
This method is not meant to use loading arbitrary classes. It is used by XStream bootstrap
+ * until it is able to use the user provided or the default {@link ClassLoader}.
+ *
+ * @since 1.4.5
+ */
+ public static Class loadClassForName(String name, boolean initialize) {
+ try {
+ Class clazz = Class.forName(name, initialize, JVM.class.getClassLoader());
+ return clazz;
+ } catch (LinkageError e) {
+ return null;
+ } catch (ClassNotFoundException e) {
+ return null;
+ }
+ }
+
+ /**
+ * @since 1.4.4
+ * @deprecated As of 1.4.5 use {@link #loadClassForName(String, boolean)}
+ */
+ public Class loadClass(String name, boolean initialize) {
+ return loadClassForName(name, initialize);
+ }
+
+ /**
+ * Create the best matching ReflectionProvider.
+ *
+ * @return a new instance
+ * @since 1.4.5
+ */
+ public static ReflectionProvider newReflectionProvider() {
+ return (ReflectionProvider)DependencyInjectionFactory.newInstance(reflectionProviderType, null);
+ }
+
+ /**
+ * Create the best matching ReflectionProvider.
+ *
+ * @param dictionary the FieldDictionary to use by the ReflectionProvider
+ * @return a new instance
+ * @since 1.4.5
+ */
+ public static ReflectionProvider newReflectionProvider(FieldDictionary dictionary) {
+ return (ReflectionProvider)DependencyInjectionFactory.newInstance(reflectionProviderType, new Object[]{ dictionary });
+ }
+
+ /**
+ * Get the XMLInputFactory implementation used normally by the current Java runtime as
+ * standard.
+ *
+ * In contrast to XMLInputFactory.newFactory() this method will ignore any implementations
+ * provided with the system property javax.xml.stream.XMLInputFactory,
+ * implementations configured in lib/stax.properties or registered with the Service
+ * API.
+ *
+ *
+ * @return the XMLInputFactory implementation or null
+ * @throws ClassNotFoundException if the standard class cannot be found
+ * @since 1.4.5
+ */
+ public static Class getStaxInputFactory() throws ClassNotFoundException {
+ if (is16()) {
+ if (isIBM()) {
+ return Class.forName("com.ibm.xml.xlxp.api.stax.XMLInputFactoryImpl");
+ } else {
+ return Class.forName("com.sun.xml.internal.stream.XMLInputFactoryImpl");
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get the XMLOutputFactory implementation used normally by the current Java runtime as
+ * standard.
+ *
+ * In contrast to XMLOutputFactory.newFactory() this method will ignore any implementations
+ * provided with the system property javax.xml.stream.XMLOutputFactory,
+ * implementations configured in lib/stax.properties or registered with the Service
+ * API.
+ *
+ *
+ * @return the XMLOutputFactory implementation or null
+ * @throws ClassNotFoundException if the standard class cannot be found
+ * @since 1.4.5
+ */
+ public static Class getStaxOutputFactory() throws ClassNotFoundException {
+ if (is16()) {
+ if (isIBM()) {
+ return Class.forName("com.ibm.xml.xlxp.api.stax.XMLOutputFactoryImpl");
+ } else {
+ return Class.forName("com.sun.xml.internal.stream.XMLOutputFactoryImpl");
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @deprecated As of 1.4.5 use {@link #newReflectionProvider()}
+ */
+ public synchronized ReflectionProvider bestReflectionProvider() {
+ if (reflectionProvider == null) {
+ reflectionProvider = newReflectionProvider();
+ }
+ return reflectionProvider;
+ }
+
+ private static boolean canUseSunUnsafeReflectionProvider() {
+ return canAllocateWithUnsafe && is14();
+ }
+
+ private static boolean canUseSunLimitedUnsafeReflectionProvider() {
+ return canWriteWithUnsafe;
+ }
+
+ /**
+ * @deprecated As of 1.4.5
+ */
+ public static boolean reverseFieldDefinition() {
+ return reverseFieldOrder;
+ }
+
+ /**
+ * Checks if AWT is available.
+ * @since 1.4.5
+ */
+ public static boolean isAWTAvailable() {
+ return isAWTAvailable;
+ }
+
+ /**
+ * Checks if the jvm supports awt.
+ * @deprecated As of 1.4.5 use {@link #isAWTAvailable()}
+ */
+ public boolean supportsAWT() {
+ return this.isAWTAvailable;
+ }
+
+ /**
+ * Checks if Swing is available.
+ * @since 1.4.5
+ */
+ public static boolean isSwingAvailable() {
+ return isSwingAvailable;
+ }
+
+ /**
+ * Checks if the jvm supports swing.
+ * @deprecated As of 1.4.5 use {@link #isSwingAvailable()}
+ */
+ public boolean supportsSwing() {
+ return this.isSwingAvailable;
+ }
+
+ /**
+ * Checks if SQL is available.
+ * @since 1.4.5
+ */
+ public static boolean isSQLAvailable() {
+ return isSQLAvailable;
+ }
+
+ /**
+ * Checks if the jvm supports sql.
+ * @deprecated As of 1.4.5 use {@link #isSQLAvailable()}
+ */
+ public boolean supportsSQL() {
+ return this.isSQLAvailable;
+ }
+
+ /**
+ * Checks if TreeSet.addAll is optimized for SortedSet argument.
+ *
+ * @since 1.4
+ */
+ public static boolean hasOptimizedTreeSetAddAll() {
+ return optimizedTreeSetAddAll;
+ }
+
+ /**
+ * Checks if TreeMap.putAll is optimized for SortedMap argument.
+ *
+ * @since 1.4
+ */
+ public static boolean hasOptimizedTreeMapPutAll() {
+ return optimizedTreeMapPutAll;
+ }
+
+ public static boolean canParseUTCDateFormat() {
+ return canParseUTCDateFormat;
+ }
+
+ /**
+ * @since 1.4.8
+ */
+ public static boolean canParseISO8601TimeZoneInDateFormat() {
+ return canParseISO8601TimeZoneInDateFormat;
+ }
+
+ /**
+ * @since 1.4.6
+ */
+ public static boolean canCreateDerivedObjectOutputStream() {
+ return canCreateDerivedObjectOutputStream;
+ }
+
+ /**
+ * @deprecated As of 1.4.5 no functionality
+ */
+ public void flushCache() {
+ }
+
+ public static void main(String[] args) {
+ boolean reverseJDK = false;
+ Field[] fields = AttributedString.class.getDeclaredFields();
+ for (int i = 0; i < fields.length; i++) {
+ if (fields[i].getName().equals("text")) {
+ reverseJDK = i > 3;
+ break;
+ }
+ }
+
+ boolean reverseLocal = false;
+ fields = Test.class.getDeclaredFields();
+ for (int i = 0; i < fields.length; i++) {
+ if (fields[i].getName().equals("o")) {
+ reverseLocal = i > 3;
+ break;
+ }
+ }
+
+ String staxInputFactory = null;
+ try {
+ staxInputFactory = getStaxInputFactory().getName();
+ } catch (ClassNotFoundException e) {
+ staxInputFactory = e.getMessage();
+ } catch (NullPointerException e) {
+ }
+
+ String staxOutputFactory = null;
+ try {
+ staxOutputFactory = getStaxOutputFactory().getName();
+ } catch (ClassNotFoundException e) {
+ staxOutputFactory = e.getMessage();
+ } catch (NullPointerException e) {
+ }
+
+ System.out.println("XStream JVM diagnostics");
+ System.out.println("java.specification.version: " + System.getProperty("java.specification.version"));
+ System.out.println("java.specification.vendor: " + System.getProperty("java.specification.vendor"));
+ System.out.println("java.specification.name: " + System.getProperty("java.specification.name"));
+ System.out.println("java.vm.vendor: " + vendor);
+ System.out.println("java.vendor: " + System.getProperty("java.vendor"));
+ System.out.println("java.vm.name: " + System.getProperty("java.vm.name"));
+ System.out.println("Version: " + majorJavaVersion);
+ System.out.println("XStream support for enhanced Mode: " + canUseSunUnsafeReflectionProvider());
+ System.out.println("XStream support for reduced Mode: " + canUseSunLimitedUnsafeReflectionProvider());
+ System.out.println("Supports AWT: " + isAWTAvailable());
+ System.out.println("Supports Swing: " + isSwingAvailable());
+ System.out.println("Supports SQL: " + isSQLAvailable());
+ System.out.println("Java Beans EventHandler present: " + (loadClassForName("java.beans.EventHandler") != null));
+ System.out.println("Standard StAX XMLInputFactory: " + staxInputFactory);
+ System.out.println("Standard StAX XMLOutputFactory: " + staxOutputFactory);
+ System.out.println("Optimized TreeSet.addAll: " + hasOptimizedTreeSetAddAll());
+ System.out.println("Optimized TreeMap.putAll: " + hasOptimizedTreeMapPutAll());
+ System.out.println("Can parse UTC date format: " + canParseUTCDateFormat());
+ System.out.println("Can create derive ObjectOutputStream: " + canCreateDerivedObjectOutputStream());
+ System.out.println("Reverse field order detected for JDK: " + reverseJDK);
+ System.out.println("Reverse field order detected (only if JVM class itself has been compiled): " + reverseLocal);
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/MapBackedDataHolder.java b/xstream/src/java/com/thoughtworks/xstream/core/MapBackedDataHolder.java
new file mode 100644
index 0000000..09f02f1
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/MapBackedDataHolder.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 04. October 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.core;
+
+import com.thoughtworks.xstream.converters.DataHolder;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+public class MapBackedDataHolder implements DataHolder {
+ private final Map map;
+
+ public MapBackedDataHolder() {
+ this(new HashMap());
+ }
+
+ public MapBackedDataHolder(Map map) {
+ this.map = map;
+ }
+
+ public Object get(Object key) {
+ return map.get(key);
+ }
+
+ public void put(Object key, Object value) {
+ map.put(key, value);
+ }
+
+ public Iterator keys() {
+ return Collections.unmodifiableCollection(map.keySet()).iterator();
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/ReferenceByIdMarshaller.java b/xstream/src/java/com/thoughtworks/xstream/core/ReferenceByIdMarshaller.java
new file mode 100644
index 0000000..05802b1
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/ReferenceByIdMarshaller.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009 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 15. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.core;
+
+import com.thoughtworks.xstream.converters.ConverterLookup;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.io.path.Path;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+public class ReferenceByIdMarshaller extends AbstractReferenceMarshaller {
+
+ private final IDGenerator idGenerator;
+
+ public static interface IDGenerator {
+ String next(Object item);
+ }
+
+ public ReferenceByIdMarshaller(HierarchicalStreamWriter writer,
+ ConverterLookup converterLookup,
+ Mapper mapper,
+ IDGenerator idGenerator) {
+ super(writer, converterLookup, mapper);
+ this.idGenerator = idGenerator;
+ }
+
+ public ReferenceByIdMarshaller(HierarchicalStreamWriter writer,
+ ConverterLookup converterLookup,
+ Mapper mapper) {
+ this(writer, converterLookup, mapper, new SequenceGenerator(1));
+ }
+
+ protected String createReference(Path currentPath, Object existingReferenceKey) {
+ return existingReferenceKey.toString();
+ }
+
+ protected Object createReferenceKey(Path currentPath, Object item) {
+ return idGenerator.next(item);
+ }
+
+ protected void fireValidReference(Object referenceKey) {
+ String attributeName = getMapper().aliasForSystemAttribute("id");
+ if (attributeName != null) {
+ writer.addAttribute(attributeName, referenceKey.toString());
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/ReferenceByIdMarshallingStrategy.java b/xstream/src/java/com/thoughtworks/xstream/core/ReferenceByIdMarshallingStrategy.java
new file mode 100644
index 0000000..fc7e0dd
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/ReferenceByIdMarshallingStrategy.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 16. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.core;
+
+import com.thoughtworks.xstream.converters.ConverterLookup;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+public class ReferenceByIdMarshallingStrategy extends AbstractTreeMarshallingStrategy {
+
+ protected TreeUnmarshaller createUnmarshallingContext(Object root,
+ HierarchicalStreamReader reader, ConverterLookup converterLookup, Mapper mapper) {
+ return new ReferenceByIdUnmarshaller(root, reader, converterLookup, mapper);
+ }
+
+ protected TreeMarshaller createMarshallingContext(
+ HierarchicalStreamWriter writer, ConverterLookup converterLookup, Mapper mapper) {
+ return new ReferenceByIdMarshaller(writer, converterLookup, mapper);
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/ReferenceByIdUnmarshaller.java b/xstream/src/java/com/thoughtworks/xstream/core/ReferenceByIdUnmarshaller.java
new file mode 100644
index 0000000..aa53323
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/ReferenceByIdUnmarshaller.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009 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 15. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.core;
+
+import com.thoughtworks.xstream.converters.ConverterLookup;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+public class ReferenceByIdUnmarshaller extends AbstractReferenceUnmarshaller {
+
+ public ReferenceByIdUnmarshaller(Object root, HierarchicalStreamReader reader,
+ ConverterLookup converterLookup, Mapper mapper) {
+ super(root, reader, converterLookup, mapper);
+ }
+
+ protected Object getReferenceKey(String reference) {
+ return reference;
+ }
+
+ protected Object getCurrentReferenceKey() {
+ String attributeName = getMapper().aliasForSystemAttribute("id");
+ return attributeName == null ? null : reader.getAttribute(attributeName);
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/ReferenceByXPathMarshaller.java b/xstream/src/java/com/thoughtworks/xstream/core/ReferenceByXPathMarshaller.java
new file mode 100644
index 0000000..954d4df
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/ReferenceByXPathMarshaller.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2004, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009 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 03. April 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.core;
+
+import com.thoughtworks.xstream.converters.ConverterLookup;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.io.path.Path;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+public class ReferenceByXPathMarshaller extends AbstractReferenceMarshaller {
+
+ private final int mode;
+
+ public ReferenceByXPathMarshaller(HierarchicalStreamWriter writer, ConverterLookup converterLookup, Mapper mapper, int mode) {
+ super(writer, converterLookup, mapper);
+ this.mode = mode;
+ }
+
+ protected String createReference(Path currentPath, Object existingReferenceKey) {
+ Path existingPath = (Path)existingReferenceKey;
+ Path referencePath = (mode & ReferenceByXPathMarshallingStrategy.ABSOLUTE) > 0 ? existingPath : currentPath.relativeTo(existingPath);
+ return (mode & ReferenceByXPathMarshallingStrategy.SINGLE_NODE) > 0 ? referencePath.explicit() : referencePath.toString();
+ }
+
+ protected Object createReferenceKey(Path currentPath, Object item) {
+ return currentPath;
+ }
+
+ protected void fireValidReference(Object referenceKey) {
+ // nothing to do
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/ReferenceByXPathMarshallingStrategy.java b/xstream/src/java/com/thoughtworks/xstream/core/ReferenceByXPathMarshallingStrategy.java
new file mode 100644
index 0000000..75bb705
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/ReferenceByXPathMarshallingStrategy.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009 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 03. April 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.core;
+
+import com.thoughtworks.xstream.converters.ConverterLookup;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+public class ReferenceByXPathMarshallingStrategy extends AbstractTreeMarshallingStrategy {
+
+ public static int RELATIVE = 0;
+ public static int ABSOLUTE = 1;
+ public static int SINGLE_NODE = 2;
+ private final int mode;
+
+ public ReferenceByXPathMarshallingStrategy(int mode) {
+ this.mode = mode;
+ }
+
+ protected TreeUnmarshaller createUnmarshallingContext(Object root,
+ HierarchicalStreamReader reader, ConverterLookup converterLookup, Mapper mapper) {
+ return new ReferenceByXPathUnmarshaller(root, reader, converterLookup, mapper);
+ }
+
+ protected TreeMarshaller createMarshallingContext(
+ HierarchicalStreamWriter writer, ConverterLookup converterLookup, Mapper mapper) {
+ return new ReferenceByXPathMarshaller(writer, converterLookup, mapper, mode);
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/ReferenceByXPathUnmarshaller.java b/xstream/src/java/com/thoughtworks/xstream/core/ReferenceByXPathUnmarshaller.java
new file mode 100644
index 0000000..8e9731b
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/ReferenceByXPathUnmarshaller.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2011 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 03. April 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.core;
+
+import com.thoughtworks.xstream.converters.ConverterLookup;
+import com.thoughtworks.xstream.io.AbstractReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.path.Path;
+import com.thoughtworks.xstream.io.path.PathTracker;
+import com.thoughtworks.xstream.io.path.PathTrackingReader;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+public class ReferenceByXPathUnmarshaller extends AbstractReferenceUnmarshaller {
+
+ private PathTracker pathTracker = new PathTracker();
+ protected boolean isNameEncoding;
+
+ public ReferenceByXPathUnmarshaller(Object root, HierarchicalStreamReader reader,
+ ConverterLookup converterLookup, Mapper mapper) {
+ super(root, reader, converterLookup, mapper);
+ this.reader = new PathTrackingReader(reader, pathTracker);
+ isNameEncoding = reader.underlyingReader() instanceof AbstractReader;
+ }
+
+ protected Object getReferenceKey(String reference) {
+ final Path path = new Path(isNameEncoding ? ((AbstractReader)reader.underlyingReader()).decodeNode(reference) : reference);
+ // We have absolute references, if path starts with '/'
+ return reference.charAt(0) != '/' ? pathTracker.getPath().apply(path) : path;
+ }
+
+ protected Object getCurrentReferenceKey() {
+ return pathTracker.getPath();
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/ReferencingMarshallingContext.java b/xstream/src/java/com/thoughtworks/xstream/core/ReferencingMarshallingContext.java
new file mode 100644
index 0000000..44fd349
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/ReferencingMarshallingContext.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2009, 2011 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 06. May 2009 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.core;
+
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.io.path.Path;
+
+/**
+ * A {@link MarshallingContext} that manages references.
+ *
+ * @author Jörg Schaible
+ * @since 1.4
+ */
+public interface ReferencingMarshallingContext extends MarshallingContext {
+
+ /**
+ * Retrieve the current path.
+ *
+ * @return the current path
+ * @since 1.4
+ * @deprecated As of 1.4.2
+ */
+ Path currentPath();
+
+ /**
+ * Request the reference key for the given item
+ *
+ * @param item the item to lookup
+ * @return the reference key or null
+ * @since 1.4
+ */
+ Object lookupReference(Object item);
+
+ /**
+ * Replace the currently marshalled item.
+ *
+ *
Use this method only, if you know exactly what you do! It is a special solution for
+ * Serializable types that make usage of the writeReplace method where the replacing object itself is referenced.
+ *
+ * @param original the original item to convert
+ * @param replacement the replacement item that is converted instead
+ * @since 1.4
+ */
+ void replace(Object original, Object replacement);
+
+ /**
+ * Register an implicit element. This is typically some kind of collection. Note, that this object may not be
+ * referenced anywhere else in the object stream.
+ *
+ * @param item the object that is implicit
+ * @since 1.4
+ */
+ void registerImplicit(Object item);
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/SequenceGenerator.java b/xstream/src/java/com/thoughtworks/xstream/core/SequenceGenerator.java
new file mode 100644
index 0000000..690b5f5
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/SequenceGenerator.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2004 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 16. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.core;
+
+public class SequenceGenerator implements ReferenceByIdMarshaller.IDGenerator {
+
+ private int counter;
+
+ public SequenceGenerator(int startsAt) {
+ this.counter = startsAt;
+ }
+
+ public String next(Object item) {
+ return String.valueOf(counter++);
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/TreeMarshaller.java b/xstream/src/java/com/thoughtworks/xstream/core/TreeMarshaller.java
new file mode 100644
index 0000000..771f195
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/TreeMarshaller.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2011 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 15. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.core;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.ConverterLookup;
+import com.thoughtworks.xstream.converters.DataHolder;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.core.util.ObjectIdDictionary;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+import java.util.Iterator;
+
+
+public class TreeMarshaller implements MarshallingContext {
+
+ protected HierarchicalStreamWriter writer;
+ protected ConverterLookup converterLookup;
+ private Mapper mapper;
+ private ObjectIdDictionary parentObjects = new ObjectIdDictionary();
+ private DataHolder dataHolder;
+
+ public TreeMarshaller(
+ HierarchicalStreamWriter writer, ConverterLookup converterLookup, Mapper mapper) {
+ this.writer = writer;
+ this.converterLookup = converterLookup;
+ this.mapper = mapper;
+ }
+
+ public void convertAnother(Object item) {
+ convertAnother(item, null);
+ }
+
+ public void convertAnother(Object item, Converter converter) {
+ if (converter == null) {
+ converter = converterLookup.lookupConverterForType(item.getClass());
+ } else {
+ if (!converter.canConvert(item.getClass())) {
+ ConversionException e = new ConversionException(
+ "Explicit selected converter cannot handle item");
+ e.add("item-type", item.getClass().getName());
+ e.add("converter-type", converter.getClass().getName());
+ throw e;
+ }
+ }
+ convert(item, converter);
+ }
+
+ protected void convert(Object item, Converter converter) {
+ if (parentObjects.containsId(item)) {
+ ConversionException e = new CircularReferenceException(
+ "Recursive reference to parent object");
+ e.add("item-type", item.getClass().getName());
+ e.add("converter-type", converter.getClass().getName());
+ throw e;
+ }
+ parentObjects.associateId(item, "");
+ converter.marshal(item, writer, this);
+ parentObjects.removeId(item);
+ }
+
+ public void start(Object item, DataHolder dataHolder) {
+ this.dataHolder = dataHolder;
+ if (item == null) {
+ writer.startNode(mapper.serializedClass(null));
+ writer.endNode();
+ } else {
+ ExtendedHierarchicalStreamWriterHelper.startNode(writer, mapper
+ .serializedClass(item.getClass()), item.getClass());
+ convertAnother(item);
+ writer.endNode();
+ }
+ }
+
+ public Object get(Object key) {
+ lazilyCreateDataHolder();
+ return dataHolder.get(key);
+ }
+
+ public void put(Object key, Object value) {
+ lazilyCreateDataHolder();
+ dataHolder.put(key, value);
+ }
+
+ public Iterator keys() {
+ lazilyCreateDataHolder();
+ return dataHolder.keys();
+ }
+
+ private void lazilyCreateDataHolder() {
+ if (dataHolder == null) {
+ dataHolder = new MapBackedDataHolder();
+ }
+ }
+
+ protected Mapper getMapper() {
+ return this.mapper;
+ }
+
+ public static class CircularReferenceException extends ConversionException {
+
+ public CircularReferenceException(String msg) {
+ super(msg);
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/TreeMarshallingStrategy.java b/xstream/src/java/com/thoughtworks/xstream/core/TreeMarshallingStrategy.java
new file mode 100644
index 0000000..8e1635b
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/TreeMarshallingStrategy.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 16. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.core;
+
+import com.thoughtworks.xstream.converters.ConverterLookup;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+public class TreeMarshallingStrategy extends AbstractTreeMarshallingStrategy {
+
+ protected TreeUnmarshaller createUnmarshallingContext(Object root,
+ HierarchicalStreamReader reader, ConverterLookup converterLookup, Mapper mapper) {
+ return new TreeUnmarshaller(root, reader, converterLookup, mapper);
+ }
+
+ protected TreeMarshaller createMarshallingContext(
+ HierarchicalStreamWriter writer, ConverterLookup converterLookup, Mapper mapper) {
+ return new TreeMarshaller(writer, converterLookup, mapper);
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/TreeUnmarshaller.java b/xstream/src/java/com/thoughtworks/xstream/core/TreeUnmarshaller.java
new file mode 100644
index 0000000..e40b105
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/TreeUnmarshaller.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009, 2011 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 15. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.core;
+
+import java.util.Iterator;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.ConverterLookup;
+import com.thoughtworks.xstream.converters.DataHolder;
+import com.thoughtworks.xstream.converters.ErrorReporter;
+import com.thoughtworks.xstream.converters.ErrorWriter;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.core.util.FastStack;
+import com.thoughtworks.xstream.core.util.HierarchicalStreams;
+import com.thoughtworks.xstream.core.util.PrioritizedList;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+
+public class TreeUnmarshaller implements UnmarshallingContext {
+
+ private Object root;
+ protected HierarchicalStreamReader reader;
+ private ConverterLookup converterLookup;
+ private Mapper mapper;
+ private FastStack types = new FastStack(16);
+ private DataHolder dataHolder;
+ private final PrioritizedList validationList = new PrioritizedList();
+
+ public TreeUnmarshaller(
+ Object root, HierarchicalStreamReader reader, ConverterLookup converterLookup,
+ Mapper mapper) {
+ this.root = root;
+ this.reader = reader;
+ this.converterLookup = converterLookup;
+ this.mapper = mapper;
+ }
+
+ public Object convertAnother(Object parent, Class type) {
+ return convertAnother(parent, type, null);
+ }
+
+ public Object convertAnother(Object parent, Class type, Converter converter) {
+ type = mapper.defaultImplementationOf(type);
+ if (converter == null) {
+ converter = converterLookup.lookupConverterForType(type);
+ } else {
+ if (!converter.canConvert(type)) {
+ ConversionException e = new ConversionException(
+ "Explicit selected converter cannot handle type");
+ e.add("item-type", type.getName());
+ e.add("converter-type", converter.getClass().getName());
+ throw e;
+ }
+ }
+ return convert(parent, type, converter);
+ }
+
+ protected Object convert(Object parent, Class type, Converter converter) {
+ try {
+ types.push(type);
+ Object result = converter.unmarshal(reader, this);
+ types.popSilently();
+ return result;
+ } catch (ConversionException conversionException) {
+ addInformationTo(conversionException, type, converter, parent);
+ throw conversionException;
+ } catch (RuntimeException e) {
+ ConversionException conversionException = new ConversionException(e);
+ addInformationTo(conversionException, type, converter, parent);
+ throw conversionException;
+ }
+ }
+
+ private void addInformationTo(ErrorWriter errorWriter, Class type, Converter converter, Object parent) {
+ errorWriter.add("class", type.getName());
+ errorWriter.add("required-type", getRequiredType().getName());
+ errorWriter.add("converter-type", converter.getClass().getName());
+ if (converter instanceof ErrorReporter) {
+ ((ErrorReporter)converter).appendErrors(errorWriter);
+ }
+ if (parent instanceof ErrorReporter) {
+ ((ErrorReporter)parent).appendErrors(errorWriter);
+ }
+ reader.appendErrors(errorWriter);
+ }
+
+ public void addCompletionCallback(Runnable work, int priority) {
+ validationList.add(work, priority);
+ }
+
+ public Object currentObject() {
+ return types.size() == 1 ? root : null;
+ }
+
+ public Class getRequiredType() {
+ return (Class)types.peek();
+ }
+
+ public Object get(Object key) {
+ lazilyCreateDataHolder();
+ return dataHolder.get(key);
+ }
+
+ public void put(Object key, Object value) {
+ lazilyCreateDataHolder();
+ dataHolder.put(key, value);
+ }
+
+ public Iterator keys() {
+ lazilyCreateDataHolder();
+ return dataHolder.keys();
+ }
+
+ private void lazilyCreateDataHolder() {
+ if (dataHolder == null) {
+ dataHolder = new MapBackedDataHolder();
+ }
+ }
+
+ public Object start(DataHolder dataHolder) {
+ this.dataHolder = dataHolder;
+ Class type = HierarchicalStreams.readClassType(reader, mapper);
+ Object result = convertAnother(null, type);
+ Iterator validations = validationList.iterator();
+ while (validations.hasNext()) {
+ Runnable runnable = (Runnable)validations.next();
+ runnable.run();
+ }
+ return result;
+ }
+
+ protected Mapper getMapper() {
+ return this.mapper;
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/util/ArrayIterator.java b/xstream/src/java/com/thoughtworks/xstream/core/util/ArrayIterator.java
new file mode 100644
index 0000000..782756d
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/util/ArrayIterator.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2011 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 29.07.2011 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.core.util;
+
+import java.lang.reflect.Array;
+import java.util.Iterator;
+
+/**
+ * @author Jörg Schaible
+ *
+ * @since 1.4
+ */
+public class ArrayIterator implements Iterator {
+ private final Object array;
+ private int idx;
+ private int length;
+ public ArrayIterator(Object array) {
+ this.array = array;
+ length = Array.getLength(array);
+ }
+
+ public boolean hasNext() {
+ return idx < length;
+ }
+
+ public Object next() {
+ return Array.get(array, idx++);
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException("Remove from array");
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/util/Base64Encoder.java b/xstream/src/java/com/thoughtworks/xstream/core/util/Base64Encoder.java
new file mode 100644
index 0000000..f28d991
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/util/Base64Encoder.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 06. August 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.core.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+
+/**
+ * Encodes binary data to plain text as Base64.
+ *
+ *
Despite there being a gazillion other Base64 implementations out there, this has been written as part of XStream as
+ * it forms a core part but is too trivial to warrant an extra dependency.
+ *
+ *
This meets the standard as described in RFC 1521, section 5.2 , allowing
+ * other Base64 tools to manipulate the data.
+ *
+ * @author Joe Walnes
+ */
+public class Base64Encoder {
+
+ // Here's how encoding works:
+ //
+ // 1) Incoming bytes are broken up into groups of 3 (each byte having 8 bits).
+ //
+ // 2) The combined 24 bits (3 * 8) are split into 4 groups of 6 bits.
+ //
+ // input |------||------||------| (3 values each with 8 bits)
+ // 101010101010101010101010
+ // output |----||----||----||----| (4 values each with 6 bits)
+ //
+ // 3) Each of these 4 groups of 6 bits are converted back to a number, which will fall in the range of 0 - 63.
+ //
+ // 4) Each of these 4 numbers are converted to an alphanumeric char in a specified mapping table, to create
+ // a 4 character string.
+ //
+ // 5) This is repeated for all groups of three bytes.
+ //
+ // 6) Special padding is done at the end of the stream using the '=' char.
+
+ private static final char[] SIXTY_FOUR_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
+ private static final int[] REVERSE_MAPPING = new int[123];
+
+ static {
+ for (int i = 0; i < SIXTY_FOUR_CHARS.length; i++) REVERSE_MAPPING[SIXTY_FOUR_CHARS[i]] = i + 1;
+ }
+
+ public String encode(byte[] input) {
+ StringBuffer result = new StringBuffer();
+ int outputCharCount = 0;
+ for (int i = 0; i < input.length; i += 3) {
+ int remaining = Math.min(3, input.length - i);
+ int oneBigNumber = (input[i] & 0xff) << 16 | (remaining <= 1 ? 0 : input[i + 1] & 0xff) << 8 | (remaining <= 2 ? 0 : input[i + 2] & 0xff);
+ for (int j = 0; j < 4; j++) result.append(remaining + 1 > j ? SIXTY_FOUR_CHARS[0x3f & oneBigNumber >> 6 * (3 - j)] : '=');
+ if ((outputCharCount += 4) % 76 == 0) result.append('\n');
+ }
+ return result.toString();
+ }
+
+ public byte[] decode(String input) {
+ try {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ StringReader in = new StringReader(input);
+ for (int i = 0; i < input.length(); i += 4) {
+ int a[] = {mapCharToInt(in), mapCharToInt(in), mapCharToInt(in), mapCharToInt(in)};
+ int oneBigNumber = (a[0] & 0x3f) << 18 | (a[1] & 0x3f) << 12 | (a[2] & 0x3f) << 6 | (a[3] & 0x3f);
+ for (int j = 0; j < 3; j++) if (a[j + 1] >= 0) out.write(0xff & oneBigNumber >> 8 * (2 - j));
+ }
+ return out.toByteArray();
+ } catch (IOException e) {
+ throw new Error(e + ": " + e.getMessage());
+ }
+ }
+
+ private int mapCharToInt(Reader input) throws IOException {
+ int c;
+ while ((c = input.read()) != -1) {
+ int result = REVERSE_MAPPING[c];
+ if (result != 0) return result -1;
+ if (c == '=') return -1;
+ }
+ return -1;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/util/ClassLoaderReference.java b/xstream/src/java/com/thoughtworks/xstream/core/util/ClassLoaderReference.java
new file mode 100644
index 0000000..d79a5b9
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/util/ClassLoaderReference.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2013 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 07. March 2005 by Joe Walnes
+ */
+package com.thoughtworks.xstream.core.util;
+
+/**
+ * ClassLoader that refers to another ClassLoader, allowing a single instance to be passed around the codebase that
+ * can later have its destination changed.
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ * @since 1.1.1
+ * @deprecated As of 1.4.5 use {@link com.thoughtworks.xstream.core.ClassLoaderReference} instead
+ */
+public class ClassLoaderReference extends ClassLoader {
+
+ private transient ClassLoader reference;
+
+ /**
+ * @deprecated As of 1.4.5 use
+ * {@link com.thoughtworks.xstream.core.ClassLoaderReference#ClassLoaderReference(ClassLoader)}
+ * instead
+ */
+ public ClassLoaderReference(ClassLoader reference) {
+ this.reference = reference;
+ }
+
+ /**
+ * @deprecated As of 1.4.5 use
+ * {@link com.thoughtworks.xstream.core.ClassLoaderReference#getReference()}
+ * .loadClass(String) instead
+ */
+ public Class loadClass(String name) throws ClassNotFoundException {
+ return reference.loadClass(name);
+ }
+
+ /**
+ * @deprecated As of 1.4.5 use
+ * {@link com.thoughtworks.xstream.core.ClassLoaderReference#getReference()}
+ * instead
+ */
+ public ClassLoader getReference() {
+ return reference;
+ }
+
+ /**
+ * @deprecated As of 1.4.5 use
+ * {@link com.thoughtworks.xstream.core.ClassLoaderReference#setReference(ClassLoader)}
+ * instead
+ */
+ public void setReference(ClassLoader reference) {
+ this.reference = reference;
+ }
+
+ private Object writeReplace() {
+ return new Replacement();
+ }
+
+ static class Replacement {
+
+ private Object readResolve() {
+ return new ClassLoaderReference(new CompositeClassLoader());
+ }
+
+ };
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/util/Cloneables.java b/xstream/src/java/com/thoughtworks/xstream/core/util/Cloneables.java
new file mode 100644
index 0000000..5079097
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/util/Cloneables.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2009, 2010, 2011 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 29. August 2009 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.core.util;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import com.thoughtworks.xstream.converters.reflection.ObjectAccessException;
+
+/**
+ * Utility functions for {@link Cloneable} objects.
+ *
+ * @author Jörg Schaible
+ * @since 1.4
+ */
+public class Cloneables {
+
+ public static Object clone(Object o) {
+ if (o instanceof Cloneable) {
+ if (o.getClass().isArray()) {
+ final Class componentType = o.getClass().getComponentType();
+ if (!componentType.isPrimitive()) {
+ return ((Object[])o).clone();
+ } else {
+ int length = Array.getLength(o);
+ final Object clone = Array.newInstance(componentType, length);
+ while (length-- > 0) {
+ Array.set(clone, length, Array.get(o, length));
+ }
+
+ return clone;
+ }
+ } else {
+ try {
+ Method clone = o.getClass().getMethod("clone", (Class[])null);
+ return clone.invoke(o, (Object[])null);
+ } catch (NoSuchMethodException e) {
+ throw new ObjectAccessException("Cloneable type has no clone method", e);
+ } catch (IllegalAccessException e) {
+ throw new ObjectAccessException("Cannot clone Cloneable type", e);
+ } catch (InvocationTargetException e) {
+ throw new ObjectAccessException("Exception cloning Cloneable type", e.getCause());
+ }
+ }
+ }
+ return null;
+ }
+
+ public static Object cloneIfPossible(Object o) {
+ Object clone = clone(o);
+ return clone == null ? o : clone;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/util/CompositeClassLoader.java b/xstream/src/java/com/thoughtworks/xstream/core/util/CompositeClassLoader.java
new file mode 100644
index 0000000..bdfc626
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/util/CompositeClassLoader.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2011, 2013 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 16. November 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.core.util;
+
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import com.thoughtworks.xstream.core.JVM;
+
+/**
+ * ClassLoader that is composed of other classloaders. Each loader will be used to try to load the particular class, until
+ * one of them succeeds. Note: The loaders will always be called in the REVERSE order they were added in.
+ *
+ *
The Composite class loader also has registered the classloader that loaded xstream.jar
+ * and (if available) the thread's context classloader.
The above code will attempt to load a class from the following classloaders (in order):
+ *
+ *
+ *
AnotherClassLoader (and all its parents)
+ *
The classloader for MyClas (and all its parents)
+ *
The thread's context classloader (and all its parents)
+ *
The classloader for XStream (and all its parents)
+ *
+ *
+ *
The added classloaders are kept with weak references to allow an application container to reload classes.
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ * @since 1.0.3
+ */
+public class CompositeClassLoader extends ClassLoader {
+ static {
+ if (JVM.is17()) {
+ // see http://www.cs.duke.edu/csed/java/jdk1.7/technotes/guides/lang/cl-mt.html
+ try {
+ Method m = ClassLoader.class.getDeclaredMethod("registerAsParallelCapable", (Class[])null);
+ if (!m.isAccessible()) {
+ m.setAccessible(true);
+ }
+ m.invoke(null, (Object[])null);
+ } catch (Exception e) {
+ // ignore errors, JVM will synchronize class for Java 7 or higher
+ }
+ }
+ }
+
+ private final ReferenceQueue queue = new ReferenceQueue();
+ private final List classLoaders = new ArrayList();
+
+ public CompositeClassLoader() {
+ addInternal(Object.class.getClassLoader()); // bootstrap loader.
+ addInternal(getClass().getClassLoader()); // whichever classloader loaded this jar.
+ }
+
+ /**
+ * Add a loader to the n
+ * @param classLoader
+ */
+ public synchronized void add(ClassLoader classLoader) {
+ cleanup();
+ if (classLoader != null) {
+ addInternal(classLoader);
+ }
+ }
+
+ private void addInternal(ClassLoader classLoader) {
+ WeakReference refClassLoader = null;
+ for (Iterator iterator = classLoaders.iterator(); iterator.hasNext();) {
+ WeakReference ref = (WeakReference) iterator.next();
+ ClassLoader cl = (ClassLoader)ref.get();
+ if (cl == null) {
+ iterator.remove();
+ } else if (cl == classLoader) {
+ iterator.remove();
+ refClassLoader = ref;
+ }
+ }
+ classLoaders.add(0, refClassLoader != null ? refClassLoader : new WeakReference(classLoader, queue));
+ }
+
+ public Class loadClass(String name) throws ClassNotFoundException {
+ List copy = new ArrayList(classLoaders.size()) {
+
+ public boolean addAll(Collection c) {
+ boolean result = false;
+ for(Iterator iter = c.iterator(); iter.hasNext(); ) {
+ result |= add(iter.next());
+ }
+ return result;
+ }
+
+ public boolean add(Object ref) {
+ Object classLoader = ((WeakReference)ref).get();
+ if (classLoader != null) {
+ return super.add(classLoader);
+ }
+ return false;
+ }
+
+ };
+ synchronized(this) {
+ cleanup();
+ copy.addAll(classLoaders);
+ }
+
+ ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+ for (Iterator iterator = copy.iterator(); iterator.hasNext();) {
+ ClassLoader classLoader = (ClassLoader) iterator.next();
+ if (classLoader == contextClassLoader) {
+ contextClassLoader = null;
+ }
+ try {
+ return classLoader.loadClass(name);
+ } catch (ClassNotFoundException notFound) {
+ // ok.. try another one
+ }
+ }
+
+ // One last try - the context class loader associated with the current thread. Often used in j2ee servers.
+ // Note: The contextClassLoader cannot be added to the classLoaders list up front as the thread that constructs
+ // XStream is potentially different to thread that uses it.
+ if (contextClassLoader != null) {
+ return contextClassLoader.loadClass(name);
+ } else {
+ throw new ClassNotFoundException(name);
+ }
+ }
+
+ private void cleanup() {
+ WeakReference ref;
+ while ((ref = (WeakReference)queue.poll()) != null)
+ {
+ classLoaders.remove(ref);
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/util/CustomObjectInputStream.java b/xstream/src/java/com/thoughtworks/xstream/core/util/CustomObjectInputStream.java
new file mode 100644
index 0000000..5b47e5e
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/util/CustomObjectInputStream.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2010, 2011, 2013 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 23. August 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.core.util;
+
+import java.io.IOException;
+import java.io.InvalidObjectException;
+import java.io.NotActiveException;
+import java.io.ObjectInputStream;
+import java.io.ObjectInputValidation;
+import java.io.ObjectStreamClass;
+import java.io.StreamCorruptedException;
+import java.util.Map;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.DataHolder;
+import com.thoughtworks.xstream.core.ClassLoaderReference;
+
+public class CustomObjectInputStream extends ObjectInputStream {
+
+ private FastStack callbacks = new FastStack(1);
+ private final ClassLoaderReference classLoaderReference;
+
+ private static final String DATA_HOLDER_KEY = CustomObjectInputStream.class.getName();
+
+ public static interface StreamCallback {
+ Object readFromStream() throws IOException;
+ Map readFieldsFromStream() throws IOException;
+ void defaultReadObject() throws IOException;
+ void registerValidation(ObjectInputValidation validation, int priority) throws NotActiveException, InvalidObjectException;
+ void close() throws IOException;
+ }
+
+ /**
+ * @deprecated As of 1.4 use {@link #getInstance(DataHolder, StreamCallback, ClassLoader)}
+ */
+ public static CustomObjectInputStream getInstance(DataHolder whereFrom, CustomObjectInputStream.StreamCallback callback) {
+ return getInstance(whereFrom, callback, (ClassLoader)null);
+ }
+
+ /**
+ * @deprecated As of 1.4.5 use {@link #getInstance(DataHolder, StreamCallback, ClassLoaderReference)}
+ */
+ public static synchronized CustomObjectInputStream getInstance(DataHolder whereFrom, CustomObjectInputStream.StreamCallback callback, ClassLoader classLoader) {
+ return getInstance(whereFrom, callback, new ClassLoaderReference(classLoader));
+ }
+
+ public static synchronized CustomObjectInputStream getInstance(DataHolder whereFrom, CustomObjectInputStream.StreamCallback callback, ClassLoaderReference classLoaderReference) {
+ try {
+ CustomObjectInputStream result = (CustomObjectInputStream) whereFrom.get(DATA_HOLDER_KEY);
+ if (result == null) {
+ result = new CustomObjectInputStream(callback, classLoaderReference);
+ whereFrom.put(DATA_HOLDER_KEY, result);
+ } else {
+ result.pushCallback(callback);
+ }
+ return result;
+ } catch (IOException e) {
+ throw new ConversionException("Cannot create CustomObjectStream", e);
+ }
+ }
+
+ /**
+ * Warning, this object is expensive to create (due to functionality inherited from superclass).
+ * Use the static fetch() method instead, wherever possible.
+ *
+ * @see #getInstance(DataHolder, StreamCallback, ClassLoaderReference)
+ */
+ public CustomObjectInputStream(StreamCallback callback, ClassLoaderReference classLoaderReference) throws IOException, SecurityException {
+ super();
+ this.callbacks.push(callback);
+ this.classLoaderReference = classLoaderReference;
+ }
+
+ /**
+ * @deprecated As of 1.4.5 use {@link #CustomObjectInputStream(StreamCallback, ClassLoaderReference)}
+ */
+ public CustomObjectInputStream(StreamCallback callback, ClassLoader classLoader) throws IOException, SecurityException {
+ this(callback, new ClassLoaderReference(classLoader));
+ }
+
+ /**
+ * Allows the CustomObjectInputStream (which is expensive to create) to be reused.
+ */
+ public void pushCallback(StreamCallback callback) {
+ this.callbacks.push(callback);
+ }
+
+ public StreamCallback popCallback(){
+ return (StreamCallback) this.callbacks.pop();
+ }
+
+ public StreamCallback peekCallback(){
+ return (StreamCallback) this.callbacks.peek();
+ }
+
+ protected Class resolveClass(ObjectStreamClass desc)
+ throws IOException, ClassNotFoundException {
+ ClassLoader classLoader = classLoaderReference.getReference();
+ if (classLoader == null) {
+ return super.resolveClass(desc);
+ } else {
+ return Class.forName(desc.getName(), false, classLoader);
+ }
+ }
+
+ public void defaultReadObject() throws IOException {
+ peekCallback().defaultReadObject();
+ }
+
+ protected Object readObjectOverride() throws IOException {
+ return peekCallback().readFromStream();
+ }
+
+ public Object readUnshared() throws IOException, ClassNotFoundException {
+ return readObject();
+ }
+
+ public boolean readBoolean() throws IOException {
+ return ((Boolean)peekCallback().readFromStream()).booleanValue();
+ }
+
+ public byte readByte() throws IOException {
+ return ((Byte)peekCallback().readFromStream()).byteValue();
+ }
+
+ public int readUnsignedByte() throws IOException {
+ int b = ((Byte)peekCallback().readFromStream()).byteValue();
+ if (b < 0) {
+ b += Byte.MAX_VALUE;
+ }
+ return b;
+ }
+
+ public int readInt() throws IOException {
+ return ((Integer)peekCallback().readFromStream()).intValue();
+ }
+
+ public char readChar() throws IOException {
+ return ((Character)peekCallback().readFromStream()).charValue();
+ }
+
+ public float readFloat() throws IOException {
+ return ((Float)peekCallback().readFromStream()).floatValue();
+ }
+
+ public double readDouble() throws IOException {
+ return ((Double)peekCallback().readFromStream()).doubleValue();
+ }
+
+ public long readLong() throws IOException {
+ return ((Long)peekCallback().readFromStream()).longValue();
+ }
+
+ public short readShort() throws IOException {
+ return ((Short)peekCallback().readFromStream()).shortValue();
+ }
+
+ public int readUnsignedShort() throws IOException {
+ int b = ((Short)peekCallback().readFromStream()).shortValue();
+ if (b < 0) {
+ b += Short.MAX_VALUE;
+ }
+ return b;
+ }
+
+ public String readUTF() throws IOException {
+ return (String)peekCallback().readFromStream();
+ }
+
+ public void readFully(byte[] buf) throws IOException {
+ readFully(buf, 0, buf.length);
+ }
+
+ public void readFully(byte[] buf, int off, int len) throws IOException {
+ byte[] b = (byte[])peekCallback().readFromStream();
+ System.arraycopy(b, 0, buf, off, len);
+ }
+
+ public int read() throws IOException {
+ return readUnsignedByte();
+ }
+
+ public int read(byte[] buf, int off, int len) throws IOException {
+ byte[] b = (byte[])peekCallback().readFromStream();
+ if (b.length != len) {
+ throw new StreamCorruptedException("Expected " + len + " bytes from stream, got " + b.length);
+ }
+ System.arraycopy(b, 0, buf, off, len);
+ return len;
+ }
+
+ public int read(byte b[]) throws IOException {
+ return read(b, 0, b.length);
+ }
+
+ public GetField readFields() throws IOException {
+ return new CustomGetField(peekCallback().readFieldsFromStream());
+ }
+
+ private class CustomGetField extends GetField {
+
+ private Map fields;
+
+ public CustomGetField(Map fields) {
+ this.fields = fields;
+ }
+
+ public ObjectStreamClass getObjectStreamClass() {
+ throw new UnsupportedOperationException();
+ }
+
+ private Object get(String name) {
+ return fields.get(name);
+ }
+
+ public boolean defaulted(String name) {
+ return !fields.containsKey(name);
+ }
+
+ public byte get(String name, byte val) {
+ return defaulted(name) ? val : ((Byte)get(name)).byteValue();
+ }
+
+ public char get(String name, char val) {
+ return defaulted(name) ? val : ((Character)get(name)).charValue();
+ }
+
+ public double get(String name, double val) {
+ return defaulted(name) ? val : ((Double)get(name)).doubleValue();
+ }
+
+ public float get(String name, float val) {
+ return defaulted(name) ? val : ((Float)get(name)).floatValue();
+ }
+
+ public int get(String name, int val) {
+ return defaulted(name) ? val : ((Integer)get(name)).intValue();
+ }
+
+ public long get(String name, long val) {
+ return defaulted(name) ? val : ((Long)get(name)).longValue();
+ }
+
+ public short get(String name, short val) {
+ return defaulted(name) ? val : ((Short)get(name)).shortValue();
+ }
+
+ public boolean get(String name, boolean val) {
+ return defaulted(name) ? val : ((Boolean)get(name)).booleanValue();
+ }
+
+ public Object get(String name, Object val) {
+ return defaulted(name) ? val : get(name);
+ }
+
+ }
+
+ public void registerValidation(ObjectInputValidation validation, int priority) throws NotActiveException, InvalidObjectException {
+ peekCallback().registerValidation(validation, priority);
+ }
+
+ public void close() throws IOException {
+ peekCallback().close();
+ }
+
+ /****** Unsupported methods ******/
+
+ public int available() {
+ throw new UnsupportedOperationException();
+ }
+
+ public String readLine() {
+ throw new UnsupportedOperationException();
+ }
+
+ public int skipBytes(int len) {
+ throw new UnsupportedOperationException();
+ }
+
+ public long skip(long n) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void mark(int readlimit) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void reset() {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean markSupported() {
+ return false;
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/util/CustomObjectOutputStream.java b/xstream/src/java/com/thoughtworks/xstream/core/util/CustomObjectOutputStream.java
new file mode 100644
index 0000000..09a2cdf
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/util/CustomObjectOutputStream.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 23. August 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.core.util;
+
+import java.io.IOException;
+import java.io.ObjectOutput;
+import java.io.ObjectOutputStream;
+import java.util.Map;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.DataHolder;
+
+public class CustomObjectOutputStream extends ObjectOutputStream {
+
+ private FastStack callbacks = new FastStack(1);
+ private FastStack customFields = new FastStack(1);
+
+ private static final String DATA_HOLDER_KEY = CustomObjectOutputStream.class.getName();
+
+ public static synchronized CustomObjectOutputStream getInstance(DataHolder whereFrom, StreamCallback callback) {
+ try {
+ CustomObjectOutputStream result = (CustomObjectOutputStream) whereFrom.get(DATA_HOLDER_KEY);
+ if (result == null) {
+ result = new CustomObjectOutputStream(callback);
+ whereFrom.put(DATA_HOLDER_KEY, result);
+ } else {
+ result.pushCallback(callback);
+ }
+ return result;
+ } catch (IOException e) {
+ throw new ConversionException("Cannot create CustomObjectStream", e);
+ }
+ }
+
+ public static interface StreamCallback {
+ void writeToStream(Object object) throws IOException;
+ void writeFieldsToStream(Map fields) throws IOException;
+ void defaultWriteObject() throws IOException;
+ void flush() throws IOException;
+ void close() throws IOException;
+ }
+
+ /**
+ * Warning, this object is expensive to create (due to functionality inherited from superclass).
+ * Use the static fetch() method instead, wherever possible.
+ *
+ * @see #getInstance(com.thoughtworks.xstream.converters.DataHolder, com.thoughtworks.xstream.core.util.CustomObjectOutputStream.StreamCallback)
+ */
+ public CustomObjectOutputStream(StreamCallback callback) throws IOException, SecurityException {
+ this.callbacks.push(callback);
+ }
+
+ /**
+ * Allows the CustomObjectOutputStream (which is expensive to create) to be reused.
+ */
+ public void pushCallback(StreamCallback callback) {
+ this.callbacks.push(callback);
+ }
+
+ public StreamCallback popCallback(){
+ return (StreamCallback) this.callbacks.pop();
+ }
+
+ public StreamCallback peekCallback(){
+ return (StreamCallback) this.callbacks.peek();
+ }
+
+ /*** Methods to delegate to callback ***/
+
+ public void defaultWriteObject() throws IOException {
+ peekCallback().defaultWriteObject();
+ }
+
+ protected void writeObjectOverride(Object obj) throws IOException {
+ peekCallback().writeToStream(obj);
+ }
+
+ public void writeBoolean(boolean val) throws IOException {
+ peekCallback().writeToStream(val ? Boolean.TRUE : Boolean.FALSE); // JDK 1.3 friendly
+ }
+
+ public void writeByte(int val) throws IOException {
+ peekCallback().writeToStream(new Byte((byte) val));
+ }
+
+ public void writeInt(int val) throws IOException {
+ peekCallback().writeToStream(new Integer(val));
+ }
+
+ public void writeChar(int val) throws IOException {
+ peekCallback().writeToStream(new Character((char)val));
+ }
+
+ public void writeDouble(double val) throws IOException {
+ peekCallback().writeToStream(new Double(val));
+ }
+
+ public void writeFloat(float val) throws IOException {
+ peekCallback().writeToStream(new Float(val));
+ }
+
+ public void writeLong(long val) throws IOException {
+ peekCallback().writeToStream(new Long(val));
+ }
+
+ public void writeShort(int val) throws IOException {
+ peekCallback().writeToStream(new Short((short) val));
+ }
+
+ public void write(byte[] buf) throws IOException {
+ peekCallback().writeToStream(buf);
+ }
+
+ public void writeChars(String str) throws IOException {
+ peekCallback().writeToStream(str.toCharArray());
+ }
+
+ public void writeUTF(String str) throws IOException {
+ peekCallback().writeToStream(str);
+ }
+
+ public void write(int val) throws IOException {
+ peekCallback().writeToStream(new Byte((byte) val));
+ }
+
+ public void write(byte[] buf, int off, int len) throws IOException {
+ byte[] b = new byte[len];
+ System.arraycopy(buf, off, b, 0, len);
+ peekCallback().writeToStream(b);
+ }
+
+ public void flush() throws IOException {
+ peekCallback().flush();
+ }
+
+ public void close() throws IOException {
+ peekCallback().close();
+ }
+
+ public PutField putFields() {
+ CustomPutField result = new CustomPutField();
+ customFields.push(result);
+ return result;
+ }
+
+ public void writeFields() throws IOException {
+ CustomPutField customPutField = (CustomPutField) customFields.pop();
+ peekCallback().writeFieldsToStream(customPutField.asMap());
+ }
+
+ private class CustomPutField extends PutField {
+
+ private final Map fields = new OrderRetainingMap();
+
+ public Map asMap() {
+ return fields;
+ }
+
+ public void write(ObjectOutput out) throws IOException {
+ peekCallback().writeToStream(asMap());
+ }
+
+ public void put(String name, Object val) {
+ fields.put(name, val);
+ }
+
+ public void put(String name, byte val) {
+ put(name, new Byte(val));
+ }
+
+ public void put(String name, char val) {
+ put(name, new Character(val));
+ }
+
+ public void put(String name, double val) {
+ put(name, new Double(val));
+ }
+
+ public void put(String name, float val) {
+ put(name, new Float(val));
+ }
+
+ public void put(String name, int val) {
+ put(name, new Integer(val));
+ }
+
+ public void put(String name, long val) {
+ put(name, new Long(val));
+ }
+
+ public void put(String name, short val) {
+ put(name, new Short(val));
+ }
+
+ public void put(String name, boolean val) {
+ put(name, val ? Boolean.TRUE : Boolean.FALSE); // JDK 1.3 friendly
+ }
+
+ }
+
+ /****** Unsupported methods ******/
+
+ public void reset() {
+ throw new UnsupportedOperationException();
+ }
+
+ public void useProtocolVersion(int version) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void writeBytes(String str) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void writeUnshared(Object obj) {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/util/DependencyInjectionFactory.java b/xstream/src/java/com/thoughtworks/xstream/core/util/DependencyInjectionFactory.java
new file mode 100644
index 0000000..b7cb672
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/util/DependencyInjectionFactory.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) 2007, 2009, 2010, 2011, 2012, 2013 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 30. March 2007 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.core.util;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Comparator;
+import java.util.List;
+
+import com.thoughtworks.xstream.converters.reflection.ObjectAccessException;
+
+
+/**
+ * A dependency injection factory.
+ *
+ * @author Jörg Schaible
+ * @since 1.2.2
+ */
+public class DependencyInjectionFactory {
+
+ /**
+ * Create an instance with dependency injection. The given dependencies are used to match the parameters of the
+ * constructors of the type. Constructors with most parameters are examined first. A parameter type sequence
+ * matching the sequence of the dependencies' types match first. Otherwise all the types of the dependencies must
+ * match one of the the parameters although no dependency is used twice. Use a {@link TypedNull} instance to inject
+ * null as parameter.
+ *
+ * @param type the type to create an instance of
+ * @param dependencies the possible dependencies
+ * @return the instantiated object
+ * @throws ObjectAccessException if no instance can be generated
+ * @throws IllegalArgumentException if more than 63 dependencies have been provided
+ * @since 1.2.2
+ */
+ public static Object newInstance(final Class type, final Object[] dependencies) {
+ return newInstance(type, dependencies, null);
+ }
+
+ /**
+ * Create an instance with dependency injection. The given dependencies are used to match the parameters of the
+ * constructors of the type. Constructors with most parameters are examined first. A parameter type sequence
+ * matching the sequence of the dependencies' types match first. Otherwise all the types of the dependencies must
+ * match one of the the parameters although no dependency is used twice. Use a {@link TypedNull} instance to inject
+ * null as parameter.
+ *
+ * @param type the type to create an instance of
+ * @param dependencies the possible dependencies
+ * @param usedDependencies bit mask set by the method for all used dependencies (may be null)
+ * @return the instantiated object
+ * @throws ObjectAccessException if no instance can be generated
+ * @throws IllegalArgumentException if more than 63 dependencies have been provided
+ * @since 1.4
+ */
+ public static Object newInstance(final Class type, final Object[] dependencies, final BitSet usedDependencies) {
+ if (dependencies != null && dependencies.length > 63) {
+ throw new IllegalArgumentException("More than 63 arguments are not supported");
+ }
+ Constructor bestMatchingCtor = null;
+ final ArrayList matchingDependencies = new ArrayList();
+ List possibleMatchingDependencies = null;
+ long usedDeps = 0;
+ long possibleUsedDeps = 0;
+
+ if (dependencies != null && dependencies.length > 0) {
+ // sort available ctors according their arity
+ final Constructor[] ctors = type.getConstructors();
+ if (ctors.length > 1) {
+ Arrays.sort(ctors, new Comparator() {
+ public int compare(final Object o1, final Object o2) {
+ return ((Constructor)o2).getParameterTypes().length
+ - ((Constructor)o1).getParameterTypes().length;
+ }
+ });
+ }
+
+ final TypedValue[] typedDependencies = new TypedValue[dependencies.length];
+ for (int i = 0; i < dependencies.length; i++ ) {
+ Object dependency = dependencies[i];
+ Class depType = dependency.getClass();
+ if (depType.isPrimitive()) {
+ depType = Primitives.box(depType);
+ } else if (depType == TypedNull.class) {
+ depType = ((TypedNull)dependency).getType();
+ dependency = null;
+ }
+
+ typedDependencies[i] = new TypedValue(depType, dependency);
+ }
+
+ Constructor possibleCtor = null;
+ int arity = Integer.MAX_VALUE;
+ for (int i = 0; bestMatchingCtor == null && i < ctors.length; i++ ) {
+ final Constructor constructor = ctors[i];
+ final Class[] parameterTypes = constructor.getParameterTypes();
+ if (parameterTypes.length > dependencies.length) {
+ continue;
+ } else if (parameterTypes.length == 0) {
+ if (possibleCtor == null) {
+ bestMatchingCtor = constructor;
+ }
+ break;
+ }
+ if (arity > parameterTypes.length) {
+ if (possibleCtor != null) {
+ continue;
+ }
+ arity = parameterTypes.length;
+ }
+
+ for (int j = 0; j < parameterTypes.length; j++ ) {
+ if (parameterTypes[j].isPrimitive()) {
+ parameterTypes[j] = Primitives.box(parameterTypes[j]);
+ }
+ }
+
+ // first approach: test the ctor params against the dependencies in the sequence
+ // of the parameter declaration
+ matchingDependencies.clear();
+ usedDeps = 0;
+ for (int j = 0, k = 0; j < parameterTypes.length
+ && parameterTypes.length + k - j <= typedDependencies.length; k++ ) {
+ if (parameterTypes[j].isAssignableFrom(typedDependencies[k].type)) {
+ matchingDependencies.add(typedDependencies[k].value);
+ usedDeps |= 1L << k;
+ if ( ++j == parameterTypes.length) {
+ bestMatchingCtor = constructor;
+ break;
+ }
+ }
+ }
+
+ if (bestMatchingCtor == null) {
+ boolean possible = true; // assumption
+
+ // try to match all dependencies in the sequence of the parameter
+ // declaration
+ final TypedValue[] deps = new TypedValue[typedDependencies.length];
+ System.arraycopy(typedDependencies, 0, deps, 0, deps.length);
+ matchingDependencies.clear();
+ usedDeps = 0;
+ for (int j = 0; j < parameterTypes.length; j++ ) {
+ int assignable = -1;
+ for (int k = 0; k < deps.length; k++ ) {
+ if (deps[k] == null) {
+ continue;
+ }
+ if (deps[k].type == parameterTypes[j]) {
+ assignable = k;
+ // optimal match
+ break;
+ } else if (parameterTypes[j].isAssignableFrom(deps[k].type)) {
+ // use most specific type
+ if (assignable < 0
+ || (deps[assignable].type != deps[k].type && deps[assignable].type
+ .isAssignableFrom(deps[k].type))) {
+ assignable = k;
+ }
+ }
+ }
+
+ if (assignable >= 0) {
+ matchingDependencies.add(deps[assignable].value);
+ usedDeps |= 1L << assignable;
+ deps[assignable] = null; // do not match same dep twice
+ } else {
+ possible = false;
+ break;
+ }
+ }
+
+ if (possible) {
+ // the smaller the value, the smaller the indices in the deps array
+ if (possibleCtor != null && usedDeps >= possibleUsedDeps) {
+ continue;
+ }
+ possibleCtor = constructor;
+ possibleMatchingDependencies = (List)matchingDependencies.clone();
+ possibleUsedDeps = usedDeps;
+ }
+ }
+ }
+
+ if (bestMatchingCtor == null) {
+ if (possibleCtor == null) {
+ usedDeps = 0;
+ throw new ObjectAccessException("Cannot construct "
+ + type.getName()
+ + ", none of the dependencies match any constructor's parameters");
+ } else {
+ bestMatchingCtor = possibleCtor;
+ matchingDependencies.clear();
+ matchingDependencies.addAll(possibleMatchingDependencies);
+ usedDeps = possibleUsedDeps;
+ }
+ }
+ }
+
+ try {
+ final Object instance;
+ if (bestMatchingCtor == null) {
+ instance = type.newInstance();
+ } else {
+ instance = bestMatchingCtor.newInstance(matchingDependencies.toArray());
+ }
+ if (usedDependencies != null) {
+ usedDependencies.clear();
+ int i = 0;
+ for(long l = 1; l < usedDeps; l <<= 1, ++i) {
+ if ((usedDeps & l) > 0) {
+ usedDependencies.set(i);
+ }
+ }
+ }
+ return instance;
+ } catch (final InstantiationException e) {
+ throw new ObjectAccessException("Cannot construct " + type.getName(), e);
+ } catch (final IllegalAccessException e) {
+ throw new ObjectAccessException("Cannot construct " + type.getName(), e);
+ } catch (final InvocationTargetException e) {
+ throw new ObjectAccessException("Cannot construct " + type.getName(), e);
+ } catch (final SecurityException e) {
+ throw new ObjectAccessException("Cannot construct " + type.getName(), e);
+ } catch (final ExceptionInInitializerError e) {
+ throw new ObjectAccessException("Cannot construct " + type.getName(), e);
+ }
+ }
+
+ private static class TypedValue {
+ final Class type;
+ final Object value;
+
+ public TypedValue(final Class type, final Object value) {
+ super();
+ this.type = type;
+ this.value = value;
+ }
+
+ public String toString()
+ {
+ return type.getName() + ":" + value;
+ }
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/util/FastField.java b/xstream/src/java/com/thoughtworks/xstream/core/util/FastField.java
new file mode 100644
index 0000000..3e4363f
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/util/FastField.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2008, 2010 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 13. October 2008 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.core.util;
+
+public final class FastField {
+ private final String name;
+ private final String declaringClass;
+
+ public FastField(String definedIn, String name) {
+ this.name = name;
+ this.declaringClass = definedIn;
+ }
+
+ public FastField(Class definedIn, String name) {
+ this(definedIn == null ? null : definedIn.getName(), name);
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public String getDeclaringClass() {
+ return this.declaringClass;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (obj instanceof FastField) {
+ final FastField field = (FastField)obj;
+ if ((declaringClass == null && field.declaringClass != null)
+ || (declaringClass != null && field.declaringClass == null)) {
+ return false;
+ }
+ return name.equals(field.getName())
+ && (declaringClass == null || declaringClass.equals(field.getDeclaringClass()));
+ }
+ return false;
+ }
+
+ public int hashCode() {
+ return name.hashCode() ^ (declaringClass == null ? 0 : declaringClass.hashCode());
+ }
+
+ public String toString() {
+ return (declaringClass == null ? "" : declaringClass + ".") + name;
+ }
+}
\ No newline at end of file
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/util/FastStack.java b/xstream/src/java/com/thoughtworks/xstream/core/util/FastStack.java
new file mode 100644
index 0000000..92f6068
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/util/FastStack.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009 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 02. September 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.core.util;
+
+/**
+ * An array-based stack implementation.
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ */
+public final class FastStack {
+
+ private Object[] stack;
+ private int pointer;
+
+ public FastStack(int initialCapacity) {
+ stack = new Object[initialCapacity];
+ }
+
+ public Object push(Object value) {
+ if (pointer + 1 >= stack.length) {
+ resizeStack(stack.length * 2);
+ }
+ stack[pointer++] = value;
+ return value;
+ }
+
+ public void popSilently() {
+ stack[--pointer] = null;
+ }
+
+ public Object pop() {
+ final Object result = stack[--pointer];
+ stack[pointer] = null;
+ return result;
+ }
+
+ public Object peek() {
+ return pointer == 0 ? null : stack[pointer - 1];
+ }
+
+ public Object replace(Object value) {
+ final Object result = stack[pointer - 1];
+ stack[pointer - 1] = value;
+ return result;
+ }
+
+ public void replaceSilently(Object value) {
+ stack[pointer - 1] = value;
+ }
+
+ public int size() {
+ return pointer;
+ }
+
+ public boolean hasStuff() {
+ return pointer > 0;
+ }
+
+ public Object get(int i) {
+ return stack[i];
+ }
+
+ private void resizeStack(int newCapacity) {
+ Object[] newStack = new Object[newCapacity];
+ System.arraycopy(stack, 0, newStack, 0, Math.min(pointer, newCapacity));
+ stack = newStack;
+ }
+
+ public String toString() {
+ StringBuffer result = new StringBuffer("[");
+ for (int i = 0; i < pointer; i++) {
+ if (i > 0) {
+ result.append(", ");
+ }
+ result.append(stack[i]);
+ }
+ result.append(']');
+ return result.toString();
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/util/Fields.java b/xstream/src/java/com/thoughtworks/xstream/core/util/Fields.java
new file mode 100644
index 0000000..368353a
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/util/Fields.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009, 2011, 2013 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 06. April 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.core.util;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+import com.thoughtworks.xstream.converters.reflection.ObjectAccessException;
+
+/**
+ * Slightly nicer way to find, get and set fields in classes. Wraps standard java.lang.reflect.Field calls but wraps
+ * wraps exception in XStreamExceptions.
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ */
+public class Fields {
+ public static Field locate(Class definedIn, Class fieldType, boolean isStatic) {
+ Field field = null;
+ try {
+ Field[] fields = definedIn.getDeclaredFields();
+ for(int i = 0; i < fields.length; ++i) {
+ if (Modifier.isStatic(fields[i].getModifiers()) == isStatic) {
+ if (fieldType.isAssignableFrom(fields[i].getType())) {
+ field = fields[i];
+ }
+ }
+ }
+ if (field != null && !field.isAccessible()) {
+ field.setAccessible(true);
+ }
+ } catch (SecurityException e) {
+ // active SecurityManager
+ } catch (NoClassDefFoundError e) {
+ // restricted type in GAE
+ }
+ return field;
+ }
+
+ public static Field find(Class type, String name) {
+ try {
+ Field result = type.getDeclaredField(name);
+ if (!result.isAccessible()) {
+ result.setAccessible(true);
+ }
+ return result;
+ } catch (NoSuchFieldException e) {
+ throw new IllegalArgumentException("Could not access " + type.getName() + "." + name + " field: " + e.getMessage());
+ } catch (NoClassDefFoundError e) {
+ throw new ObjectAccessException("Could not access " + type.getName() + "." + name + " field: " + e.getMessage());
+ }
+ }
+
+ public static void write(Field field, Object instance, Object value) {
+ try {
+ field.set(instance, value);
+ } catch (IllegalAccessException e) {
+ throw new ObjectAccessException("Could not write " + field.getType().getName() + "." + field.getName() + " field", e);
+ } catch (NoClassDefFoundError e) {
+ throw new ObjectAccessException("Could not write " + field.getType().getName() + "." + field.getName() + " field", e);
+ }
+ }
+
+ public static Object read(Field field, Object instance) {
+ try {
+ return field.get(instance);
+ } catch (IllegalAccessException e) {
+ throw new ObjectAccessException("Could not read " + field.getType().getName() + "." + field.getName() + " field", e);
+ } catch (NoClassDefFoundError e) {
+ throw new ObjectAccessException("Could not read " + field.getType().getName() + "." + field.getName() + " field", e);
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/util/HierarchicalStreams.java b/xstream/src/java/com/thoughtworks/xstream/core/util/HierarchicalStreams.java
new file mode 100644
index 0000000..85ae1ed
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/util/HierarchicalStreams.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 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 09. October 2008 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.core.util;
+
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+/**
+ * Helper methods for {@link HierarchicalStreamReader} and {@link HierarchicalStreamWriter}.
+ *
+ * @author Jörg Schaible
+ * @since 1.3.1
+ */
+public class HierarchicalStreams {
+
+ public static Class readClassType(HierarchicalStreamReader reader, Mapper mapper) {
+ String classAttribute = readClassAttribute(reader, mapper);
+ Class type;
+ if (classAttribute == null) {
+ type = mapper.realClass(reader.getNodeName());
+ } else {
+ type = mapper.realClass(classAttribute);
+ }
+ return type;
+ }
+
+ public static String readClassAttribute(HierarchicalStreamReader reader, Mapper mapper) {
+ String attributeName = mapper.aliasForSystemAttribute("resolves-to");
+ String classAttribute = attributeName == null ? null : reader.getAttribute(attributeName);
+ if (classAttribute == null) {
+ attributeName = mapper.aliasForSystemAttribute("class");
+ if (attributeName != null) {
+ classAttribute = reader.getAttribute(attributeName);
+ }
+ }
+ return classAttribute;
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/util/ObjectIdDictionary.java b/xstream/src/java/com/thoughtworks/xstream/core/util/ObjectIdDictionary.java
new file mode 100644
index 0000000..4c42d4d
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/util/ObjectIdDictionary.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2010 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 09. May 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.core.util;
+
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * Store IDs against given object references.
+ *
+ * Behaves similar to java.util.IdentityHashMap, but in JDK1.3 as well. Additionally the
+ * implementation keeps track of orphaned IDs by using a WeakReference to store the reference
+ * object.
+ *
+ */
+public class ObjectIdDictionary {
+
+ private final Map map = new HashMap();
+ private final ReferenceQueue queue = new ReferenceQueue();
+
+ private static interface Wrapper {
+ int hashCode();
+ boolean equals(Object obj);
+ String toString();
+ Object get();
+ }
+
+ private static class IdWrapper implements Wrapper {
+
+ private final Object obj;
+ private final int hashCode;
+
+ public IdWrapper(Object obj) {
+ hashCode = System.identityHashCode(obj);
+ this.obj = obj;
+ }
+
+ public int hashCode() {
+ return hashCode;
+ }
+
+ public boolean equals(Object other) {
+ return obj == ((Wrapper)other).get();
+ }
+
+ public String toString() {
+ return obj.toString();
+ }
+
+ public Object get() {
+ return obj;
+ }
+ }
+
+ private class WeakIdWrapper extends WeakReference implements Wrapper {
+
+ private final int hashCode;
+
+ public WeakIdWrapper(Object obj) {
+ super(obj, queue);
+ hashCode = System.identityHashCode(obj);
+ }
+
+ public int hashCode() {
+ return hashCode;
+ }
+
+ public boolean equals(Object other) {
+ return get() == ((Wrapper)other).get();
+ }
+
+ public String toString() {
+ Object obj = get();
+ return obj == null ? "(null)" : obj.toString();
+ }
+ }
+
+ public void associateId(Object obj, Object id) {
+ map.put(new WeakIdWrapper(obj), id);
+ cleanup();
+ }
+
+ public Object lookupId(Object obj) {
+ Object id = map.get(new IdWrapper(obj));
+ return id;
+ }
+
+ public boolean containsId(Object item) {
+ boolean b = map.containsKey(new IdWrapper(item));
+ return b;
+ }
+
+ public void removeId(Object item) {
+ map.remove(new IdWrapper(item));
+ cleanup();
+ }
+
+ public int size() {
+ cleanup();
+ return map.size();
+ }
+
+ private void cleanup() {
+ WeakIdWrapper wrapper;
+ while ((wrapper = (WeakIdWrapper)queue.poll()) != null)
+ {
+ map.remove(wrapper);
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/util/OrderRetainingMap.java b/xstream/src/java/com/thoughtworks/xstream/core/util/OrderRetainingMap.java
new file mode 100644
index 0000000..f62062f
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/util/OrderRetainingMap.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2013, 2014 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 06. February 2005 by Joe Walnes
+ */
+package com.thoughtworks.xstream.core.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * @deprecated As of 1.4.8 use {@link java.util.LinkedHashMap}
+ */
+public class OrderRetainingMap extends HashMap {
+
+ private ArraySet keyOrder = new ArraySet();
+ private List valueOrder = new ArrayList();
+
+ public OrderRetainingMap() {
+ super();
+ }
+
+ public OrderRetainingMap(Map m) {
+ super();
+ putAll(m);
+ }
+
+ public void putAll(Map m) {
+ for(Iterator iter = m.entrySet().iterator(); iter.hasNext(); ) {
+ Map.Entry entry = (Map.Entry)iter.next();
+ put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ public Object put(Object key, Object value) {
+ int idx = keyOrder.lastIndexOf(key);
+ if (idx < 0) {
+ keyOrder.add(key);
+ valueOrder.add(value);
+ } else {
+ valueOrder.set(idx, value);
+ }
+ return super.put(key, value);
+ }
+
+ public Object remove(Object key) {
+ int idx = keyOrder.lastIndexOf(key);
+ if (idx != 0) {
+ keyOrder.remove(idx);
+ valueOrder.remove(idx);
+ }
+ return super.remove(key);
+ }
+
+ public void clear() {
+ keyOrder.clear();
+ valueOrder.clear();
+ super.clear();
+ }
+
+ public Collection values() {
+ return Collections.unmodifiableList(valueOrder);
+ }
+
+ public Set keySet() {
+ return Collections.unmodifiableSet(keyOrder);
+ }
+
+ public Set entrySet() {
+ Map.Entry[] entries = new Map.Entry[size()];
+ for (Iterator iter = super.entrySet().iterator(); iter.hasNext();) {
+ Map.Entry entry = (Map.Entry)iter.next();
+ entries[keyOrder.indexOf(entry.getKey())] = entry;
+ }
+ Set set = new ArraySet();
+ set.addAll(Arrays.asList(entries));
+ return Collections.unmodifiableSet(set);
+ }
+
+ private static class ArraySet extends ArrayList implements Set {
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/util/Pool.java b/xstream/src/java/com/thoughtworks/xstream/core/util/Pool.java
new file mode 100644
index 0000000..1829377
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/util/Pool.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2007 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 10. May 2007 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.core.util;
+
+/**
+ * A simple pool implementation.
+ *
+ * @author Jörg Schaible
+ * @author Joe Walnes
+ */
+public class Pool {
+
+ public interface Factory {
+ public Object newInstance();
+ }
+
+ private final int initialPoolSize;
+ private final int maxPoolSize;
+ private final Factory factory;
+ private transient Object[] pool;
+ private transient int nextAvailable;
+ private transient Object mutex = new Object();
+
+ public Pool(int initialPoolSize, int maxPoolSize, Factory factory) {
+ this.initialPoolSize = initialPoolSize;
+ this.maxPoolSize = maxPoolSize;
+ this.factory = factory;
+ }
+
+ public Object fetchFromPool() {
+ Object result;
+ synchronized (mutex) {
+ if (pool == null) {
+ pool = new Object[maxPoolSize];
+ for (nextAvailable = initialPoolSize; nextAvailable > 0; ) {
+ putInPool(factory.newInstance());
+ }
+ }
+ while (nextAvailable == maxPoolSize) {
+ try {
+ mutex.wait();
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Interrupted whilst waiting " +
+ "for a free item in the pool : " + e.getMessage());
+ }
+ }
+ result = pool[nextAvailable++];
+ if (result == null) {
+ result = factory.newInstance();
+ putInPool(result);
+ ++nextAvailable;
+ }
+ }
+ return result;
+ }
+
+ protected void putInPool(Object object) {
+ synchronized (mutex) {
+ pool[--nextAvailable] = object;
+ mutex.notify();
+ }
+ }
+
+ private Object readResolve() {
+ mutex = new Object();
+ return this;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/util/PresortedMap.java b/xstream/src/java/com/thoughtworks/xstream/core/util/PresortedMap.java
new file mode 100644
index 0000000..13b3800
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/util/PresortedMap.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2006, 2007, 2010 XStream Committers.
+ * All rights reserved.
+ *
+ * Created on 12.10.2010 by Joerg Schaible, extracted from TreeMapConverter.
+ */
+package com.thoughtworks.xstream.core.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+
+/**
+ * @author Jörg Schaible
+ */
+public class PresortedMap implements SortedMap {
+
+ private static class ArraySet extends ArrayList implements Set {
+ }
+
+ private final PresortedMap.ArraySet set;
+ private final Comparator comparator;
+
+ public PresortedMap() {
+ this(null, new ArraySet());
+ }
+
+ public PresortedMap(Comparator comparator) {
+ this(comparator, new ArraySet());
+ }
+
+ private PresortedMap(Comparator comparator, PresortedMap.ArraySet set) {
+ this.comparator = comparator != null ? comparator : new ArraySetComparator(set);
+ this.set = set;
+ }
+
+ public Comparator comparator() {
+ return comparator;
+ }
+
+ public Set entrySet() {
+ return set;
+ }
+
+ public Object firstKey() {
+ throw new UnsupportedOperationException();
+ }
+
+ public SortedMap headMap(Object toKey) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Set keySet() {
+ Set keySet = new ArraySet();
+ for (final Iterator iterator = set.iterator(); iterator.hasNext();) {
+ final Entry entry = (Entry)iterator.next();
+ keySet.add(entry.getKey());
+ }
+ return keySet;
+ }
+
+ public Object lastKey() {
+ throw new UnsupportedOperationException();
+ }
+
+ public SortedMap subMap(Object fromKey, Object toKey) {
+ throw new UnsupportedOperationException();
+ }
+
+ public SortedMap tailMap(Object fromKey) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Collection values() {
+ Set values = new ArraySet();
+ for (final Iterator iterator = set.iterator(); iterator.hasNext();) {
+ final Entry entry = (Entry)iterator.next();
+ values.add(entry.getValue());
+ }
+ return values;
+ }
+
+ public void clear() {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean containsKey(Object key) {
+ return false;
+ }
+
+ public boolean containsValue(Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object get(Object key) {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean isEmpty() {
+ return set.isEmpty();
+ }
+
+ public Object put(final Object key, final Object value) {
+ set.add(new Entry(){
+
+ public Object getKey() {
+ return key;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+
+ public Object setValue(Object value) {
+ throw new UnsupportedOperationException();
+ }});
+ return null;
+ }
+
+ public void putAll(Map m) {
+ for (final Iterator iter = m.entrySet().iterator(); iter.hasNext();) {
+ set.add(iter.next());
+ }
+ }
+
+ public Object remove(Object key) {
+ throw new UnsupportedOperationException();
+ }
+
+ public int size() {
+ return set.size();
+ }
+
+ private static class ArraySetComparator implements Comparator {
+
+ private final ArrayList list;
+ private Map.Entry[] array;
+
+ ArraySetComparator(ArrayList list) {
+ this.list = list;
+ }
+
+ public int compare(Object object1, Object object2) {
+ if (array == null || list.size() != array.length) {
+ Map.Entry[] a = new Map.Entry[list.size()];
+ if (array != null) {
+ System.arraycopy(array, 0, a, 0, array.length);
+ }
+ for (int i = array == null ? 0 : array.length; i < list.size(); ++i) {
+ a[i] = (Map.Entry)list.get(i);
+ }
+ array = a;
+ }
+ int idx1 = Integer.MAX_VALUE, idx2 = Integer.MAX_VALUE;
+ for(int i = 0; i < array.length && !(idx1 < Integer.MAX_VALUE && idx2 < Integer.MAX_VALUE); ++i) {
+ if (idx1 == Integer.MAX_VALUE && object1 == array[i].getKey()) {
+ idx1 = i;
+ }
+ if (idx2 == Integer.MAX_VALUE && object2 == array[i].getKey()) {
+ idx2 = i;
+ }
+ }
+ return idx1 - idx2;
+ }
+ }
+}
\ No newline at end of file
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/util/PresortedSet.java b/xstream/src/java/com/thoughtworks/xstream/core/util/PresortedSet.java
new file mode 100644
index 0000000..bc252d3
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/util/PresortedSet.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2006, 2007, 2010 XStream Committers.
+ * All rights reserved.
+ *
+ * Created on 12.10.2010 by Joerg Schaible, extracted from TreeSetConverter.
+ */
+package com.thoughtworks.xstream.core.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.SortedSet;
+
+/**
+ * @author Jörg Schaible
+ */
+public class PresortedSet implements SortedSet {
+ private final List list = new ArrayList();
+ private final Comparator comparator;
+
+ public PresortedSet() {
+ this(null);
+ }
+
+ public PresortedSet(Comparator comparator) {
+ this(comparator, null);
+ }
+
+ public PresortedSet(Comparator comparator, Collection c) {
+ this.comparator = comparator;
+ if (c != null) {
+ addAll(c);
+ }
+ }
+
+ public boolean add(Object e) {
+ return this.list.add(e);
+ }
+
+ public boolean addAll(Collection c) {
+ return this.list.addAll(c);
+ }
+
+ public void clear() {
+ this.list.clear();
+ }
+
+ public boolean contains(Object o) {
+ return this.list.contains(o);
+ }
+
+ public boolean containsAll(Collection c) {
+ return this.list.containsAll(c);
+ }
+
+ public boolean equals(Object o) {
+ return this.list.equals(o);
+ }
+
+ public int hashCode() {
+ return this.list.hashCode();
+ }
+
+ public boolean isEmpty() {
+ return this.list.isEmpty();
+ }
+
+ public Iterator iterator() {
+ return this.list.iterator();
+ }
+
+ public boolean remove(Object o) {
+ return this.list.remove(o);
+ }
+
+ public boolean removeAll(Collection c) {
+ return this.list.removeAll(c);
+ }
+
+ public boolean retainAll(Collection c) {
+ return this.list.retainAll(c);
+ }
+
+ public int size() {
+ return this.list.size();
+ }
+
+ public List subList(int fromIndex, int toIndex) {
+ return this.list.subList(fromIndex, toIndex);
+ }
+
+ public Object[] toArray() {
+ return this.list.toArray();
+ }
+
+ public Object[] toArray(Object[] a) {
+ return this.list.toArray(a);
+ }
+
+ public Comparator comparator() {
+ return comparator;
+ }
+
+ public Object first() {
+ return list.isEmpty() ? null : list.get(0);
+ }
+
+ public SortedSet headSet(Object toElement) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object last() {
+ return list.isEmpty() ? null : list.get(list.size() - 1);
+ }
+
+ public SortedSet subSet(Object fromElement, Object toElement) {
+ throw new UnsupportedOperationException();
+ }
+
+ public SortedSet tailSet(Object fromElement) {
+ throw new UnsupportedOperationException();
+ }
+}
\ No newline at end of file
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/util/Primitives.java b/xstream/src/java/com/thoughtworks/xstream/core/util/Primitives.java
new file mode 100644
index 0000000..7a95282
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/util/Primitives.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2006, 2007, 2011 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 11. October 2006 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.core.util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Utility class for primitives.
+ *
+ * @author Jörg Schaible
+ * @since 1.2.1
+ */
+public final class Primitives {
+ private final static Map BOX = new HashMap();
+ private final static Map UNBOX = new HashMap();
+ private final static Map NAMED_PRIMITIVE = new HashMap();
+ private final static Map REPRESENTING_CHAR = new HashMap();
+
+ static {
+ final Class[][] boxing = new Class[][]{
+ { Byte.TYPE, Byte.class},
+ { Character.TYPE, Character.class},
+ { Short.TYPE, Short.class},
+ { Integer.TYPE, Integer.class},
+ { Long.TYPE, Long.class},
+ { Float.TYPE, Float.class},
+ { Double.TYPE, Double.class},
+ { Boolean.TYPE, Boolean.class},
+ { Void.TYPE, Void.class},
+ };
+ final Character[] representingChars = {
+ new Character('B'),
+ new Character('C'),
+ new Character('S'),
+ new Character('I'),
+ new Character('J'),
+ new Character('F'),
+ new Character('D'),
+ new Character('Z'),
+ null
+ };
+ for (int i = 0; i < boxing.length; i++) {
+ final Class primitiveType = boxing[i][0];
+ final Class boxedType = boxing[i][1];
+ BOX.put(primitiveType, boxedType);
+ UNBOX.put(boxedType, primitiveType);
+ NAMED_PRIMITIVE.put(primitiveType.getName(), primitiveType);
+ REPRESENTING_CHAR.put(primitiveType, representingChars[i]);
+ }
+ }
+
+ /**
+ * Get the boxed type for a primitive.
+ *
+ * @param type the primitive type
+ * @return the boxed type or null
+ */
+ static public Class box(final Class type) {
+ return (Class)BOX.get(type);
+ }
+
+ /**
+ * Get the primitive type for a boxed one.
+ *
+ * @param type the boxed type
+ * @return the primitive type or null
+ */
+ static public Class unbox(final Class type) {
+ return (Class)UNBOX.get(type);
+ }
+
+ /**
+ * Check for a boxed type.
+ *
+ * @param type the type to check
+ * @return true if the type is boxed
+ * @since 1.4
+ */
+ static public boolean isBoxed(final Class type) {
+ return UNBOX.containsKey(type);
+ }
+
+ /**
+ * Get the primitive type by name.
+ *
+ * @param name the name of the type
+ * @return the Java type or null
+ * @since 1.4
+ */
+ static public Class primitiveType(final String name) {
+ return (Class)NAMED_PRIMITIVE.get(name);
+ }
+
+ /**
+ * Get the representing character of a primitive type.
+ *
+ * @param type the primitive type
+ * @return the representing character or 0
+ * @since 1.4
+ */
+ static public char representingChar(final Class type) {
+ Character ch = (Character)REPRESENTING_CHAR.get(type);
+ return ch == null ? 0 : ch.charValue();
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/util/PrioritizedList.java b/xstream/src/java/com/thoughtworks/xstream/core/util/PrioritizedList.java
new file mode 100644
index 0000000..f6546b7
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/util/PrioritizedList.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2011 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 06. February 2005 by Joe Walnes
+ */
+package com.thoughtworks.xstream.core.util;
+
+import java.util.Iterator;
+import java.util.Set;
+import java.util.TreeSet;
+
+
+/**
+ * List that allows items to be added with a priority that will affect the order in which they are later iterated over.
+ * Objects with a high priority will appear before objects with a low priority in the list. If two objects of the same
+ * priority are added to the list, the most recently added one will be iterated over first. Implementation uses a
+ * TreeSet, which has a guaranteed add time of O(log(n)).
+ *
+ * @author Joe Walnes
+ * @author Guilherme Silveira
+ */
+public class PrioritizedList {
+
+ private final Set set = new TreeSet();
+
+ private int lowestPriority = Integer.MAX_VALUE;
+
+ private int lastId = 0;
+
+ public void add(Object item, int priority) {
+ if (this.lowestPriority > priority) {
+ this.lowestPriority = priority;
+ }
+ this.set.add(new PrioritizedItem(item, priority, ++lastId));
+ }
+
+ public Iterator iterator() {
+ return new PrioritizedItemIterator(this.set.iterator());
+ }
+
+ private static class PrioritizedItem implements Comparable {
+
+ final Object value;
+ final int priority;
+ final int id;
+
+ public PrioritizedItem(Object value, int priority, int id) {
+ this.value = value;
+ this.priority = priority;
+ this.id = id;
+ }
+
+ public int compareTo(Object o) {
+ PrioritizedItem other = (PrioritizedItem)o;
+ if (this.priority != other.priority) {
+ return (other.priority - this.priority);
+ }
+ return (other.id - this.id);
+ }
+
+ public boolean equals(Object obj) {
+ return this.id == ((PrioritizedItem)obj).id;
+ }
+
+ }
+
+ private static class PrioritizedItemIterator implements Iterator {
+
+ private Iterator iterator;
+
+ public PrioritizedItemIterator(Iterator iterator) {
+ this.iterator = iterator;
+ }
+
+ public void remove() {
+ // call iterator.remove()?
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean hasNext() {
+ return iterator.hasNext();
+ }
+
+ public Object next() {
+ return ((PrioritizedItem)iterator.next()).value;
+ }
+
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/util/QuickWriter.java b/xstream/src/java/com/thoughtworks/xstream/core/util/QuickWriter.java
new file mode 100644
index 0000000..019ed36
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/util/QuickWriter.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009 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 07. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.core.util;
+
+import com.thoughtworks.xstream.io.StreamException;
+
+import java.io.IOException;
+import java.io.Writer;
+
+public class QuickWriter {
+
+ private final Writer writer;
+ private char[] buffer;
+ private int pointer;
+
+ public QuickWriter(Writer writer) {
+ this(writer, 1024);
+ }
+
+ public QuickWriter(Writer writer, int bufferSize) {
+ this.writer = writer;
+ buffer = new char[bufferSize];
+ }
+
+ public void write(String str) {
+ int len = str.length();
+ if (pointer + len >= buffer.length) {
+ flush();
+ if (len > buffer.length) {
+ raw(str.toCharArray());
+ return;
+ }
+ }
+ str.getChars(0, len, buffer, pointer);
+ pointer += len;
+ }
+
+ public void write(char c) {
+ if (pointer + 1 >= buffer.length) {
+ flush();
+ if (buffer.length == 0) {
+ raw(c);
+ return;
+ }
+ }
+ buffer[pointer++] = c;
+ }
+
+ public void write(char[] c) {
+ int len = c.length;
+ if (pointer + len >= buffer.length) {
+ flush();
+ if (len > buffer.length) {
+ raw(c);
+ return;
+ }
+ }
+ System.arraycopy(c, 0, buffer, pointer, len);
+ pointer += len;
+ }
+
+ public void flush() {
+ try {
+ writer.write(buffer, 0, pointer);
+ pointer = 0;
+ writer.flush();
+ } catch (IOException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public void close() {
+ try {
+ writer.write(buffer, 0, pointer);
+ pointer = 0;
+ writer.close();
+ } catch (IOException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ private void raw(char[] c) {
+ try {
+ writer.write(c);
+ writer.flush();
+ } catch (IOException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ private void raw(char c) {
+ try {
+ writer.write(c);
+ writer.flush();
+ } catch (IOException e) {
+ throw new StreamException(e);
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/util/SelfStreamingInstanceChecker.java b/xstream/src/java/com/thoughtworks/xstream/core/util/SelfStreamingInstanceChecker.java
new file mode 100644
index 0000000..d4ee0f5
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/util/SelfStreamingInstanceChecker.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2006, 2007, 2013 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 01. March 2013 by Joerg Schaible, moved from package
+ * com.thoughtworks.xstream.converters.reflection.
+ */
+package com.thoughtworks.xstream.core.util;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.ConverterLookup;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+
+/**
+ * A special converter that prevents self-serialization. The serializing XStream instance
+ * adds a converter of this type to prevent self-serialization and will throw an
+ * exception instead.
+ *
+ * @author Jörg Schaible
+ * @since 1.2
+ */
+public class SelfStreamingInstanceChecker implements Converter {
+
+ private final Object self;
+ private Converter defaultConverter;
+ private final ConverterLookup lookup;
+
+ /**
+ * @since 1.4.5
+ */
+ public SelfStreamingInstanceChecker(ConverterLookup lookup, Object xstream) {
+ this.lookup = lookup;
+ this.self = xstream;
+ }
+
+ /**
+ * @deprecated As of 1.4.5 use {@link #SelfStreamingInstanceChecker(ConverterLookup, Object)}
+ */
+ public SelfStreamingInstanceChecker(Converter defaultConverter, Object xstream) {
+ this.defaultConverter = defaultConverter;
+ this.self = xstream;
+ lookup = null;
+ }
+
+ public boolean canConvert(Class type) {
+ return type == self.getClass();
+ }
+
+ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+ if (source == self) {
+ throw new ConversionException("Cannot marshal the XStream instance in action");
+ }
+ getConverter().marshal(source, writer, context);
+ }
+
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ return getConverter().unmarshal(reader, context);
+ }
+
+ private Converter getConverter() {
+ return defaultConverter != null ? defaultConverter : lookup.lookupConverterForType(Object.class);
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/util/SerializationMembers.java b/xstream/src/java/com/thoughtworks/xstream/core/util/SerializationMembers.java
new file mode 100644
index 0000000..505df5d
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/util/SerializationMembers.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2010, 2011, 2014, 2015 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 12. February 2015 by Joerg Schaible, copied from c.t.x.converters.reflection.SerializationMemberInvoker.
+ */
+package com.thoughtworks.xstream.core.util;
+
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamField;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.converters.reflection.ObjectAccessException;
+import com.thoughtworks.xstream.core.Caching;
+
+
+/**
+ * Convenience wrapper to invoke special serialization methods on objects (and perform reflection caching).
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ */
+public class SerializationMembers implements Caching {
+
+ private static final Method NO_METHOD = (new Object() {
+ private void noMethod() {
+ }
+ }).getClass().getDeclaredMethods()[0];
+ private static final Object[] EMPTY_ARGS = new Object[0];
+ private static final Class[] EMPTY_CLASSES = new Class[0];
+ private static final Map NO_FIELDS = Collections.EMPTY_MAP;
+ private static final int PERSISTENT_FIELDS_MODIFIER = Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL;
+ private static final FastField[] OBJECT_TYPE_FIELDS = {
+ new FastField(Object.class, "readResolve"),
+ new FastField(Object.class, "writeReplace"),
+ new FastField(Object.class, "readObject"),
+ new FastField(Object.class, "writeObject")
+ };
+ private Map declaredCache = Collections.synchronizedMap(new HashMap());
+ private Map resRepCache = Collections.synchronizedMap(new HashMap());
+ private final Map fieldCache = Collections.synchronizedMap(new HashMap());
+ {
+ for(int i = 0; i < OBJECT_TYPE_FIELDS.length; ++i) {
+ declaredCache.put(OBJECT_TYPE_FIELDS[i], NO_METHOD);
+ }
+ for(int i = 0; i < 2; ++i) {
+ resRepCache.put(OBJECT_TYPE_FIELDS[i], NO_METHOD);
+ }
+ }
+
+ /**
+ * Resolves an object as native serialization does by calling readResolve(), if available.
+ */
+ public Object callReadResolve(final Object result) {
+ if (result == null) {
+ return null;
+ } else {
+ final Class resultType = result.getClass();
+ final Method readResolveMethod = getRRMethod(resultType, "readResolve");
+ if (readResolveMethod != null) {
+ try {
+ return readResolveMethod.invoke(result, EMPTY_ARGS);
+ } catch (IllegalAccessException e) {
+ throw new ObjectAccessException("Could not call "
+ + resultType.getName()
+ + ".readResolve()", e);
+ } catch (InvocationTargetException e) {
+ throw new ObjectAccessException("Could not call "
+ + resultType.getName()
+ + ".readResolve()", e.getTargetException());
+ }
+ } else {
+ return result;
+ }
+ }
+ }
+
+ public Object callWriteReplace(final Object object) {
+ if (object == null) {
+ return null;
+ } else {
+ final Class objectType = object.getClass();
+ final Method writeReplaceMethod = getRRMethod(objectType, "writeReplace");
+ if (writeReplaceMethod != null) {
+ try {
+ return writeReplaceMethod.invoke(object, EMPTY_ARGS);
+ } catch (IllegalAccessException e) {
+ throw new ObjectAccessException("Could not call "
+ + objectType.getName()
+ + ".writeReplace()", e);
+ } catch (InvocationTargetException e) {
+ throw new ObjectAccessException("Could not call "
+ + objectType.getName()
+ + ".writeReplace()", e.getTargetException());
+ }
+ } else {
+ return object;
+ }
+ }
+ }
+
+ public boolean supportsReadObject(final Class type, final boolean includeBaseClasses) {
+ return getMethod(
+ type, "readObject", new Class[]{ObjectInputStream.class}, includeBaseClasses) != null;
+ }
+
+ public void callReadObject(final Class type, final Object object, final ObjectInputStream stream) {
+ try {
+ Method readObjectMethod = getMethod(
+ type, "readObject", new Class[]{ObjectInputStream.class}, false);
+ readObjectMethod.invoke(object, new Object[]{stream});
+ } catch (IllegalAccessException e) {
+ throw new ConversionException("Could not call "
+ + object.getClass().getName()
+ + ".readObject()", e);
+ } catch (InvocationTargetException e) {
+ throw new ConversionException("Could not call "
+ + object.getClass().getName()
+ + ".readObject()", e.getTargetException());
+ }
+ }
+
+ public boolean supportsWriteObject(final Class type, final boolean includeBaseClasses) {
+ return getMethod(
+ type, "writeObject", new Class[]{ObjectOutputStream.class}, includeBaseClasses) != null;
+ }
+
+ public void callWriteObject(final Class type, final Object instance, final ObjectOutputStream stream) {
+ try {
+ Method readObjectMethod = getMethod(
+ type, "writeObject", new Class[]{ObjectOutputStream.class}, false);
+ readObjectMethod.invoke(instance, new Object[]{stream});
+ } catch (IllegalAccessException e) {
+ throw new ConversionException("Could not call "
+ + instance.getClass().getName()
+ + ".writeObject()", e);
+ } catch (InvocationTargetException e) {
+ throw new ConversionException("Could not call "
+ + instance.getClass().getName()
+ + ".writeObject()", e.getTargetException());
+ }
+ }
+
+ private Method getMethod(Class type, String name, Class[] parameterTypes,
+ boolean includeBaseclasses) {
+ Method method = getMethod(type, name, parameterTypes);
+ return method == NO_METHOD
+ || (!includeBaseclasses && !method.getDeclaringClass().equals(type))
+ ? null
+ : method;
+ }
+
+ private Method getMethod(Class type, String name, Class[] parameterTypes) {
+ if (type == null) {
+ return null;
+ }
+ FastField method = new FastField(type, name);
+ Method result = (Method)declaredCache.get(method);
+ if (result == null) {
+ try {
+ result = type.getDeclaredMethod(name, parameterTypes);
+ if (!result.isAccessible()) {
+ result.setAccessible(true);
+ }
+ } catch (NoSuchMethodException e) {
+ result = getMethod(type.getSuperclass(), name, parameterTypes);
+ }
+ declaredCache.put(method, result);
+ }
+ return result;
+ }
+
+ private Method getRRMethod(final Class type, final String name) {
+ final FastField method = new FastField(type, name);
+ Method result = (Method)resRepCache.get(method);
+ if (result == null) {
+ result = getMethod(type, name, EMPTY_CLASSES, true);
+ if (result != null && result.getDeclaringClass() != type) {
+ if ((result.getModifiers() & (Modifier.PUBLIC | Modifier.PROTECTED)) == 0) {
+ if ((result.getModifiers() & Modifier.PRIVATE) > 0
+ || type.getPackage() != result.getDeclaringClass().getPackage()) {
+ result = NO_METHOD;
+ }
+ }
+ } else if (result == null) {
+ result = NO_METHOD;
+ }
+ resRepCache.put(method, result);
+ }
+ return result == NO_METHOD ? null : result;
+ }
+
+ public Map getSerializablePersistentFields(final Class type) {
+ if (type == null) {
+ return null;
+ }
+ Map result = (Map)fieldCache.get(type.getName());
+ if (result == null) {
+ try {
+ final Field field = type.getDeclaredField("serialPersistentFields");
+ if ((field.getModifiers() & PERSISTENT_FIELDS_MODIFIER) == PERSISTENT_FIELDS_MODIFIER) {
+ field.setAccessible(true);
+ final ObjectStreamField[] fields = (ObjectStreamField[])field.get(null);
+ if (fields != null) {
+ result = new HashMap();
+ for (int i = 0; i < fields.length; ++i) {
+ result.put(fields[i].getName(), fields[i]);
+ }
+ }
+ }
+ } catch (final NoSuchFieldException e) {
+ } catch (final IllegalAccessException e) {
+ throw new ObjectAccessException("Cannot get " + type.getName() + ".serialPersistentFields.", e);
+ } catch (final ClassCastException e) {
+ throw new ObjectAccessException("Cannot get " + type.getName() + ".serialPersistentFields.", e);
+ }
+ if (result == null) {
+ result = NO_FIELDS;
+ }
+ fieldCache.put(type.getName(), result);
+ }
+ return result == NO_FIELDS ? null : result;
+ }
+
+ public void flushCache() {
+ declaredCache.keySet().retainAll(Arrays.asList(OBJECT_TYPE_FIELDS));
+ resRepCache.keySet().retainAll(Arrays.asList(OBJECT_TYPE_FIELDS));
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/util/ThreadSafePropertyEditor.java b/xstream/src/java/com/thoughtworks/xstream/core/util/ThreadSafePropertyEditor.java
new file mode 100644
index 0000000..34c9953
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/util/ThreadSafePropertyEditor.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 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 20. September 2007 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.core.util;
+
+import com.thoughtworks.xstream.converters.reflection.ObjectAccessException;
+
+import java.beans.PropertyEditor;
+
+
+/**
+ * Wrapper around {@link PropertyEditor} that can be called by multiple threads concurrently.
+ *
+ * A PropertyEditor is not thread safe. To make best use of resources, the PropertyEditor
+ * provides a dynamically sizing pool of instances, each of which will only be called by a
+ * single thread at a time.
+ *
+ *
+ * The pool has a maximum capacity, to limit overhead. If all instances in the pool are in use
+ * and another is required, it shall block until one becomes available.
+ *
+ *
+ * @author Jörg Schaible
+ * @since 1.3
+ */
+public class ThreadSafePropertyEditor {
+
+ private final Class editorType;
+ private final Pool pool;
+
+ public ThreadSafePropertyEditor(Class type, int initialPoolSize, int maxPoolSize) {
+ if (!PropertyEditor.class.isAssignableFrom(type)) {
+ throw new IllegalArgumentException(type.getName()
+ + " is not a "
+ + PropertyEditor.class.getName());
+ }
+ editorType = type;
+ pool = new Pool(initialPoolSize, maxPoolSize, new Pool.Factory() {
+ public Object newInstance() {
+ try {
+ return editorType.newInstance();
+ } catch (InstantiationException e) {
+ throw new ObjectAccessException("Could not call default constructor of "
+ + editorType.getName(), e);
+ } catch (IllegalAccessException e) {
+ throw new ObjectAccessException("Could not call default constructor of "
+ + editorType.getName(), e);
+ }
+ }
+
+ });
+ }
+
+ public String getAsText(Object object) {
+ PropertyEditor editor = fetchFromPool();
+ try {
+ editor.setValue(object);
+ return editor.getAsText();
+ } finally {
+ pool.putInPool(editor);
+ }
+ }
+
+ public Object setAsText(String str) {
+ PropertyEditor editor = fetchFromPool();
+ try {
+ editor.setAsText(str);
+ return editor.getValue();
+ } finally {
+ pool.putInPool(editor);
+ }
+ }
+
+ private PropertyEditor fetchFromPool() {
+ PropertyEditor editor = (PropertyEditor)pool.fetchFromPool();
+ return editor;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/util/ThreadSafeSimpleDateFormat.java b/xstream/src/java/com/thoughtworks/xstream/core/util/ThreadSafeSimpleDateFormat.java
new file mode 100644
index 0000000..dfd15a8
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/util/ThreadSafeSimpleDateFormat.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2011, 2012 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 06. May 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.core.util;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Wrapper around java.text.SimpleDateFormat that can
+ * be called by multiple threads concurrently.
+ *
SimpleDateFormat has a high overhead in creating
+ * and is not thread safe. To make best use of resources,
+ * the ThreadSafeSimpleDateFormat provides a dynamically
+ * sizing pool of instances, each of which will only
+ * be called by a single thread at a time.
+ *
The pool has a maximum capacity, to limit overhead.
+ * If all instances in the pool are in use and another is
+ * required, it shall block until one becomes available.
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ */
+public class ThreadSafeSimpleDateFormat {
+
+ private final String formatString;
+ private final Pool pool;
+ private final TimeZone timeZone;
+
+ public ThreadSafeSimpleDateFormat(
+ String format, TimeZone timeZone, int initialPoolSize, int maxPoolSize,
+ final boolean lenient) {
+ this(format, timeZone, Locale.ENGLISH, initialPoolSize, maxPoolSize, lenient);
+ }
+
+ public ThreadSafeSimpleDateFormat(
+ String format, TimeZone timeZone, final Locale locale, int initialPoolSize,
+ int maxPoolSize, final boolean lenient) {
+ formatString = format;
+ this.timeZone = timeZone;
+ pool = new Pool(initialPoolSize, maxPoolSize, new Pool.Factory() {
+ public Object newInstance() {
+ SimpleDateFormat dateFormat = new SimpleDateFormat(formatString, locale);
+ dateFormat.setLenient(lenient);
+ return dateFormat;
+ }
+
+ });
+ }
+
+ public String format(Date date) {
+ DateFormat format = fetchFromPool();
+ try {
+ return format.format(date);
+ } finally {
+ pool.putInPool(format);
+ }
+ }
+
+ public Date parse(String date) throws ParseException {
+ DateFormat format = fetchFromPool();
+ try {
+ return format.parse(date);
+ } finally {
+ pool.putInPool(format);
+ }
+ }
+
+ private DateFormat fetchFromPool() {
+ DateFormat format = (DateFormat)pool.fetchFromPool();
+ TimeZone tz = timeZone != null ? timeZone : TimeZone.getDefault();
+ if (!tz.equals(format.getTimeZone())) {
+ format.setTimeZone(tz);
+ }
+ return format;
+ }
+
+ public String toString() {
+ return formatString;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/util/TypedNull.java b/xstream/src/java/com/thoughtworks/xstream/core/util/TypedNull.java
new file mode 100644
index 0000000..a6458f8
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/util/TypedNull.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2007 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 30. March 2007 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.core.util;
+
+/**
+ * A placeholder for a null value of a specific type.
+ *
+ * @author Jörg Schaible
+ * @since 1.2.2
+ */
+public class TypedNull
+{
+ private final Class type;
+
+ public TypedNull(Class type)
+ {
+ super();
+ this.type = type;
+ }
+
+ public Class getType()
+ {
+ return this.type;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/util/Types.java b/xstream/src/java/com/thoughtworks/xstream/core/util/Types.java
new file mode 100644
index 0000000..5168be7
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/util/Types.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2015 XStream Committers.
+ * All rights reserved.
+ *
+ * Created on 17. January 2015 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.core.util;
+
+import java.util.regex.Pattern;
+
+
+/**
+ * Helper methods for class types.
+ *
+ * @author Jörg Schaible
+ * @since 1.4.8
+ */
+public class Types {
+ private static final Pattern lambdaPattern = Pattern.compile(".*\\$\\$Lambda\\$[0-9]+/.*");
+
+ public static final boolean isLambdaType(final Class> type) {
+ return type != null && type.isSynthetic() && lambdaPattern.matcher(type.getSimpleName()).matches();
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/util/WeakCache.java b/xstream/src/java/com/thoughtworks/xstream/core/util/WeakCache.java
new file mode 100644
index 0000000..c13d29e
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/util/WeakCache.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2011, 2013, 2014 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 12. July 2011 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.core.util;
+
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+
+/**
+ * A HashMap implementation with weak references values and by default for the key. When the
+ * value is garbage collected, the key will also vanish from the map.
+ *
+ * @author Jörg Schaible
+ * @since 1.4
+ */
+public class WeakCache extends AbstractMap {
+
+ private final Map map;
+
+ /**
+ * Construct a WeakCache with weak keys.
+ *
+ *
Note, that the internally used WeakHashMap is not thread-safe.
+ *
+ * @param map the map to use
+ * @since 1.4
+ */
+ public WeakCache() {
+ this(new WeakHashMap());
+ }
+
+ /**
+ * Construct a WeakCache.
+ *
+ * @param map the map to use
+ * @since 1.4
+ */
+ public WeakCache(Map map) {
+ this.map = map;
+ }
+
+ public Object get(Object key) {
+ Reference reference = (Reference)map.get(key);
+ return reference != null ? reference.get() : null;
+ }
+
+ public Object put(Object key, Object value) {
+ Reference ref = (Reference)map.put(key, createReference(value));
+ return ref == null ? null : ref.get();
+ }
+
+ public Object remove(Object key) {
+ Reference ref = (Reference)map.remove(key);
+ return ref == null ? null : ref.get();
+ }
+
+ protected Reference createReference(Object value) {
+ return new WeakReference(value);
+ }
+
+ public boolean containsValue(final Object value) {
+ Boolean result = (Boolean)iterate(new Visitor() {
+
+ public Object visit(Object element) {
+ return element.equals(value) ? Boolean.TRUE : null;
+ }
+
+ }, 0);
+ return result == Boolean.TRUE;
+ }
+
+ public int size() {
+ if (map.size() == 0) {
+ return 0;
+ }
+ final int i[] = new int[1];
+ i[0] = 0;
+ iterate(new Visitor() {
+
+ public Object visit(Object element) {
+ ++i[0];
+ return null;
+ }
+
+ }, 0);
+ return i[0];
+ }
+
+ public Collection values() {
+ final Collection collection = new ArrayList();
+ if (map.size() != 0) {
+ iterate(new Visitor() {
+
+ public Object visit(Object element) {
+ collection.add(element);
+ return null;
+ }
+
+ }, 0);
+ }
+ return collection;
+ }
+
+ public Set entrySet() {
+ final Set set = new HashSet();
+ if (map.size() != 0) {
+ iterate(new Visitor() {
+
+ public Object visit(Object element) {
+ final Map.Entry entry = (Map.Entry)element;
+ set.add(new Map.Entry() {
+
+ public Object getKey() {
+ return entry.getKey();
+ }
+
+ public Object getValue() {
+ return ((Reference)entry.getValue()).get();
+ }
+
+ public Object setValue(Object value) {
+ Reference reference = (Reference)entry.setValue(createReference(value));
+ return reference != null ? reference.get() : null;
+ }
+
+ });
+ return null;
+ }
+
+ }, 2);
+ }
+ return set;
+ }
+
+ private Object iterate(Visitor visitor, int type) {
+ Object result = null;
+ for (Iterator iter = map.entrySet().iterator(); result == null && iter.hasNext();) {
+ Map.Entry entry = (Map.Entry)iter.next();
+ Reference reference = (Reference)entry.getValue();
+ Object element = reference.get();
+ if (element == null) {
+ iter.remove();
+ continue;
+ }
+ switch (type) {
+ case 0:
+ result = visitor.visit(element);
+ break;
+ case 1:
+ result = visitor.visit(entry.getKey());
+ break;
+ case 2:
+ result = visitor.visit(entry);
+ break;
+ }
+
+ }
+ return result;
+ }
+
+ private interface Visitor {
+ Object visit(Object element);
+ }
+
+ public boolean containsKey(Object key) {
+ return map.containsKey(key);
+ }
+
+ public void clear() {
+ map.clear();
+ }
+
+ public Set keySet() {
+ return map.keySet();
+ }
+
+ public boolean equals(Object o) {
+ return map.equals(o);
+ }
+
+ public int hashCode() {
+ return map.hashCode();
+ }
+
+ public String toString() {
+ return map.toString();
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/core/util/XmlHeaderAwareReader.java b/xstream/src/java/com/thoughtworks/xstream/core/util/XmlHeaderAwareReader.java
new file mode 100644
index 0000000..b0364fc
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/core/util/XmlHeaderAwareReader.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2007, 2008, 2010 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 13. September 2007 by Joerg Schaible.
+ */
+
+package com.thoughtworks.xstream.core.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PushbackInputStream;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * A {@link Reader} that evaluates the XML header. It selects its encoding based on the encoding read with the XML
+ * header of the provided {@link InputStream}. The default encoding is UTF-8 and the version is 1.0 if the
+ * stream does not contain an XML header or the attributes are not set within the header.
+ *
+ * @author Jörg Schaible
+ * @since 1.3
+ */
+public final class XmlHeaderAwareReader extends Reader {
+
+ private final InputStreamReader reader;
+ private final double version;
+
+ private static final String KEY_ENCODING = "encoding";
+ private static final String KEY_VERSION = "version";
+
+ private static final String XML_TOKEN = "?xml";
+
+ private static final int STATE_BOM = 0;
+ private static final int STATE_START = 1;
+ private static final int STATE_AWAIT_XML_HEADER = 2;
+ private static final int STATE_ATTR_NAME = 3;
+ private static final int STATE_ATTR_VALUE = 4;
+
+ /**
+ * Constructs an XmlHeaderAwareReader.
+ *
+ * @param in the {@link InputStream}
+ * @throws UnsupportedEncodingException if the encoding is not supported
+ * @throws IOException occurred while reading the XML header
+ * @since 1.3
+ */
+ public XmlHeaderAwareReader(final InputStream in) throws UnsupportedEncodingException, IOException {
+ final PushbackInputStream[] pin = new PushbackInputStream[]{in instanceof PushbackInputStream
+ ? (PushbackInputStream)in
+ : new PushbackInputStream(in, 64)};
+ final Map header = getHeader(pin);
+ version = Double.parseDouble((String)header.get(KEY_VERSION));
+ reader = new InputStreamReader(pin[0], (String)header.get(KEY_ENCODING));
+ }
+
+ private Map getHeader(final PushbackInputStream[] in) throws IOException {
+ final Map header = new HashMap();
+ header.put(KEY_ENCODING, "utf-8");
+ header.put(KEY_VERSION, "1.0");
+
+ int state = STATE_BOM;
+ final ByteArrayOutputStream out = new ByteArrayOutputStream(64);
+ int i = 0;
+ char ch = 0;
+ char valueEnd = 0;
+ final StringBuffer name = new StringBuffer();
+ final StringBuffer value = new StringBuffer();
+ boolean escape = false;
+ while (i != -1 && (i = in[0].read()) != -1) {
+ out.write(i);
+ ch = (char)i;
+ switch (state) {
+ case STATE_BOM:
+ if ((ch == 0xEF && out.size() == 1)
+ || (ch == 0xBB && out.size() == 2)
+ || (ch == 0xBF && out.size() == 3)) {
+ if (ch == 0xBF) {
+ out.reset();
+ state = STATE_START;
+ }
+ break;
+ } else if (out.size() > 1) {
+ i = -1;
+ break;
+ } else {
+ state = STATE_START;
+ }
+ // fall through
+ case STATE_START:
+ if (!Character.isWhitespace(ch)) {
+ if (ch == '<') {
+ state = STATE_AWAIT_XML_HEADER;
+ } else {
+ i = -1;
+ }
+ }
+ break;
+ case STATE_AWAIT_XML_HEADER:
+ if (!Character.isWhitespace(ch)) {
+ name.append(Character.toLowerCase(ch));
+ if (!XML_TOKEN.startsWith(name.substring(0))) {
+ i = -1;
+ }
+ } else {
+ if (name.toString().equals(XML_TOKEN)) {
+ state = STATE_ATTR_NAME;
+ name.setLength(0);
+ } else {
+ i = -1;
+ }
+ }
+ break;
+ case STATE_ATTR_NAME:
+ if (!Character.isWhitespace(ch)) {
+ if (ch == '=') {
+ state = STATE_ATTR_VALUE;
+ } else {
+ ch = Character.toLowerCase(ch);
+ if (Character.isLetter(ch)) {
+ name.append(ch);
+ } else {
+ i = -1;
+ }
+ }
+ } else if (name.length() > 0) {
+ i = -1;
+ }
+ break;
+ case STATE_ATTR_VALUE:
+ if (valueEnd == 0) {
+ if (ch == '"' || ch == '\'') {
+ valueEnd = ch;
+ } else {
+ i = -1;
+ }
+ } else {
+ if (ch == '\\' && !escape) {
+ escape = true;
+ break;
+ }
+ if (ch == valueEnd && !escape) {
+ valueEnd = 0;
+ state = STATE_ATTR_NAME;
+ header.put(name.toString(), value.toString());
+ name.setLength(0);
+ value.setLength(0);
+ } else {
+ escape = false;
+ if (ch != '\n') {
+ value.append(ch);
+ } else {
+ i = -1;
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ byte[] pushbackData = out.toByteArray();
+ for (i = pushbackData.length; i-- > 0;) {
+ final byte b = pushbackData[i];
+ try {
+ in[0].unread(b);
+ } catch (IOException ex) {
+ in[0] = new PushbackInputStream(in[0], ++i);
+ }
+ }
+ return header;
+ }
+
+ /**
+ * @see InputStreamReader#getEncoding()
+ * @since 1.3
+ */
+ public String getEncoding() {
+ return reader.getEncoding();
+ }
+
+ /**
+ * @see InputStreamReader#getEncoding()
+ * @since 1.3
+ */
+ public double getVersion() {
+ return version;
+ }
+
+ /**
+ * @see java.io.Reader#mark(int)
+ */
+ public void mark(final int readAheadLimit) throws IOException {
+ reader.mark(readAheadLimit);
+ }
+
+ /**
+ * @see java.io.Reader#markSupported()
+ */
+ public boolean markSupported() {
+ return reader.markSupported();
+ }
+
+ /**
+ * @see java.io.Reader#read()
+ */
+ public int read() throws IOException {
+ return reader.read();
+ }
+
+ /**
+ * @see java.io.Reader#read(char[], int, int)
+ */
+ public int read(final char[] cbuf, final int offset, final int length) throws IOException {
+ return reader.read(cbuf, offset, length);
+ }
+
+ /**
+ * @see java.io.Reader#read(char[])
+ */
+ public int read(final char[] cbuf) throws IOException {
+ return reader.read(cbuf);
+ }
+
+// TODO: This is JDK 1.5
+// public int read(final CharBuffer target) throws IOException {
+// return reader.read(target);
+// }
+
+ /**
+ * @see java.io.Reader#ready()
+ */
+ public boolean ready() throws IOException {
+ return reader.ready();
+ }
+
+ /**
+ * @see java.io.Reader#reset()
+ */
+ public void reset() throws IOException {
+ reader.reset();
+ }
+
+ /**
+ * @see java.io.Reader#skip(long)
+ */
+ public long skip(final long n) throws IOException {
+ return reader.skip(n);
+ }
+
+ /**
+ * @see java.io.Reader#close()
+ */
+ public void close() throws IOException {
+ reader.close();
+ }
+
+ /**
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public boolean equals(final Object obj) {
+ return reader.equals(obj);
+ }
+
+ /**
+ * @see java.lang.Object#hashCode()
+ */
+ public int hashCode() {
+ return reader.hashCode();
+ }
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ return reader.toString();
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/AbstractDriver.java b/xstream/src/java/com/thoughtworks/xstream/io/AbstractDriver.java
new file mode 100644
index 0000000..85d29ac
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/AbstractDriver.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2009, 2011 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 15. August 2009 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import com.thoughtworks.xstream.io.naming.NameCoder;
+import com.thoughtworks.xstream.io.naming.NoNameCoder;
+
+
+/**
+ * Abstract base class for all HierarchicalStreamDriver implementations. Implementations of
+ * {@link HierarchicalStreamDriver} should rather be derived from this class then implementing
+ * the interface directly.
+ *
+ * @author Jörg Schaible
+ * @since 1.4
+ */
+public abstract class AbstractDriver implements HierarchicalStreamDriver {
+
+ private NameCoder replacer;
+
+ /**
+ * Creates an AbstractDriver with a NameCoder that does nothing.
+ */
+ public AbstractDriver() {
+ this(new NoNameCoder());
+ }
+
+ /**
+ * Creates an AbstractDriver with a provided {@link NameCoder}.
+ *
+ * @param nameCoder the name coder for the target format
+ */
+ public AbstractDriver(NameCoder nameCoder) {
+ this.replacer = nameCoder;
+ }
+
+ protected NameCoder getNameCoder() {
+ return replacer;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public HierarchicalStreamReader createReader(URL in) {
+ InputStream stream = null;
+ try {
+ stream = in.openStream();
+ } catch (IOException e) {
+ throw new StreamException(e);
+ }
+ return createReader(stream);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public HierarchicalStreamReader createReader(File in) {
+ try {
+ return createReader(new FileInputStream(in));
+ } catch (FileNotFoundException e) {
+ throw new StreamException(e);
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/AbstractReader.java b/xstream/src/java/com/thoughtworks/xstream/io/AbstractReader.java
new file mode 100644
index 0000000..ad5e041
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/AbstractReader.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2009, 2011 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 16. August 2009 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io;
+
+import com.thoughtworks.xstream.core.util.Cloneables;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+import com.thoughtworks.xstream.io.naming.NoNameCoder;
+
+
+/**
+ * Abstract base class for all HierarchicalStreamReader implementations. Implementations of
+ * {@link HierarchicalStreamReader} should rather be derived from this class then implementing
+ * the interface directly.
+ *
+ * @author Jörg Schaible
+ * @since 1.4
+ */
+public abstract class AbstractReader implements ExtendedHierarchicalStreamReader {
+
+ private NameCoder nameCoder;
+
+ /**
+ * Creates an AbstractReader with a NameCoder that does nothing.
+ *
+ * @since 1.4
+ */
+ protected AbstractReader() {
+ this(new NoNameCoder());
+ }
+
+ /**
+ * Creates an AbstractReader with a provided {@link NameCoder}.
+ *
+ * @param nameCoder the name coder used to read names from the incoming format
+ * @since 1.4
+ */
+ protected AbstractReader(NameCoder nameCoder) {
+ this.nameCoder = (NameCoder)Cloneables.cloneIfPossible(nameCoder);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public HierarchicalStreamReader underlyingReader() {
+ return this;
+ }
+
+ /**
+ * Decode a node name from the target format.
+ *
+ * @param name the name in the target format
+ * @return the original name
+ * @since 1.4
+ */
+ public String decodeNode(String name) {
+ return nameCoder.decodeNode(name);
+ }
+
+ /**
+ * Decode an attribute name from the target format.
+ *
+ * @param name the name in the target format
+ * @return the original name
+ * @since 1.4
+ */
+ public String decodeAttribute(String name) {
+ return nameCoder.decodeAttribute(name);
+ }
+
+ /**
+ * Encode the node name again into the name of the target format. Internally used.
+ *
+ * @param name the original name
+ * @return the name in the target format
+ * @since 1.4
+ */
+ protected String encodeNode(String name) {
+ return nameCoder.encodeNode(name);
+ }
+
+ /**
+ * Encode the attribute name again into the name of the target format. Internally used.
+ *
+ * @param name the original name
+ * @return the name in the target format
+ * @since 1.4
+ */
+ protected String encodeAttribute(String name) {
+ return nameCoder.encodeAttribute(name);
+ }
+
+ public String peekNextChild() {
+ throw new UnsupportedOperationException("peekNextChild");
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/AbstractWriter.java b/xstream/src/java/com/thoughtworks/xstream/io/AbstractWriter.java
new file mode 100644
index 0000000..165cfa0
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/AbstractWriter.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2009, 2011 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 17. August 2009 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io;
+
+import com.thoughtworks.xstream.core.util.Cloneables;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+import com.thoughtworks.xstream.io.naming.NoNameCoder;
+
+
+/**
+ * Abstract base class for all HierarchicalStreamWriter implementations. Implementations of
+ * {@link HierarchicalStreamWriter} should rather be derived from this class then implementing
+ * the interface directly.
+ *
+ * @author Jörg Schaible
+ * @since 1.4
+ */
+public abstract class AbstractWriter implements ExtendedHierarchicalStreamWriter {
+
+ private NameCoder nameCoder;
+
+ /**
+ * Creates an AbstractWriter with a NameCoder that does nothing.
+ *
+ * @since 1.4
+ */
+ protected AbstractWriter() {
+ this(new NoNameCoder());
+ }
+
+ /**
+ * Creates an AbstractWriter with a provided {@link NameCoder}.
+ *
+ * @param nameCoder the name coder used to write names in the target format
+ * @since 1.4
+ */
+ protected AbstractWriter(NameCoder nameCoder) {
+ this.nameCoder = (NameCoder)Cloneables.cloneIfPossible(nameCoder);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void startNode(String name, Class clazz) {
+ startNode(name);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public HierarchicalStreamWriter underlyingWriter() {
+ return this;
+ }
+
+ /**
+ * Encode the node name into the name of the target format.
+ *
+ * @param name the original name
+ * @return the name in the target format
+ * @since 1.4
+ */
+ public String encodeNode(String name) {
+ return nameCoder.encodeNode(name);
+ }
+
+ /**
+ * Encode the attribute name into the name of the target format.
+ *
+ * @param name the original name
+ * @return the name in the target format
+ * @since 1.4
+ */
+ public String encodeAttribute(String name) {
+ return nameCoder.encodeAttribute(name);
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/AttributeNameIterator.java b/xstream/src/java/com/thoughtworks/xstream/io/AttributeNameIterator.java
new file mode 100644
index 0000000..e71e3fb
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/AttributeNameIterator.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2014 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 24. April 2005 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io;
+
+import java.util.Iterator;
+
+/**
+ * Provide an iterator over the attribute names of the current node of a reader.
+ *
+ * @author Joe Walnes
+ * @deprecated As of 1.4.8, it is an internal helper class only
+ */
+public class AttributeNameIterator implements Iterator {
+
+ private int current;
+ private final int count;
+ private final HierarchicalStreamReader reader;
+
+ public AttributeNameIterator(HierarchicalStreamReader reader) {
+ this.reader = reader;
+ count = reader.getAttributeCount();
+ }
+
+ public boolean hasNext() {
+ return current < count;
+ }
+
+ public Object next() {
+ return reader.getAttributeName(current++);
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/ExtendedHierarchicalStreamReader.java b/xstream/src/java/com/thoughtworks/xstream/io/ExtendedHierarchicalStreamReader.java
new file mode 100644
index 0000000..27059fb
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/ExtendedHierarchicalStreamReader.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2011 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 13. October 2011 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io;
+
+/**
+ * @author Jörg Schaible
+ * @since 1.4.2
+ */
+public interface ExtendedHierarchicalStreamReader extends HierarchicalStreamReader {
+
+ /**
+ * Peek the name of the next child. In situation where {@link #hasMoreChildren()} returns
+ * true, peek the tag name of the child.
+ *
+ * @since 1.4.2
+ */
+ String peekNextChild();
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/ExtendedHierarchicalStreamWriter.java b/xstream/src/java/com/thoughtworks/xstream/io/ExtendedHierarchicalStreamWriter.java
new file mode 100644
index 0000000..1091da1
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/ExtendedHierarchicalStreamWriter.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 22. June 2006 by Mauro Talevi
+ */
+package com.thoughtworks.xstream.io;
+
+/**
+ * @author Paul Hammant
+ */
+public interface ExtendedHierarchicalStreamWriter extends HierarchicalStreamWriter {
+
+ void startNode(String name, Class clazz);
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/ExtendedHierarchicalStreamWriterHelper.java b/xstream/src/java/com/thoughtworks/xstream/io/ExtendedHierarchicalStreamWriterHelper.java
new file mode 100644
index 0000000..ac06624
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/ExtendedHierarchicalStreamWriterHelper.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 22. June 2006 by Mauro Talevi
+ */
+package com.thoughtworks.xstream.io;
+
+public class ExtendedHierarchicalStreamWriterHelper {
+ public static void startNode(HierarchicalStreamWriter writer, String name, Class clazz) {
+ if (writer instanceof ExtendedHierarchicalStreamWriter) {
+ ((ExtendedHierarchicalStreamWriter) writer).startNode(name, clazz);
+ } else {
+ writer.startNode(name);
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/HierarchicalStreamDriver.java b/xstream/src/java/com/thoughtworks/xstream/io/HierarchicalStreamDriver.java
new file mode 100644
index 0000000..bc5c686
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/HierarchicalStreamDriver.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2011 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 07. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.URL;
+
+/**
+ * Provides implementation of stream parsers and writers to XStream.
+ *
+ * @author Joe Walnes
+ * @author James Strachan
+ */
+public interface HierarchicalStreamDriver {
+
+ /**
+ * Create the HierarchicalStreamReader with the stream parser reading from the IO reader.
+ *
+ * @param in the {@link Reader} with the data to parse
+ * @return the HierarchicalStreamReader
+ */
+ HierarchicalStreamReader createReader(Reader in);
+
+ /**
+ * Create the HierarchicalStreamReader with the stream parser reading from the input stream.
+ *
+ * @param in the {@link InputStream} with the data to parse
+ * @since 1.1.3
+ */
+ HierarchicalStreamReader createReader(InputStream in);
+
+ /**
+ * Create the HierarchicalStreamReader with the stream parser reading from a URL.
+ *
+ * Depending on the parser implementation, some might take the URL as SystemId to resolve
+ * additional references.
+ *
+ * @param in the {@link URL} defining the location with the data to parse
+ * @return the HierarchicalStreamReader
+ * @since 1.4
+ */
+ HierarchicalStreamReader createReader(URL in);
+
+ /**
+ * Create the HierarchicalStreamReader with the stream parser reading from a File.
+ *
+ * Depending on the parser implementation, some might take the file path as SystemId to
+ * resolve additional references.
+ *
+ * @param in the {@link URL} defining the location with the data to parse
+ * @return the HierarchicalStreamReader
+ * @since 1.4
+ */
+ HierarchicalStreamReader createReader(File in);
+
+ /**
+ * Create the HierarchicalStreamWriter with the formatted writer.
+ *
+ * @param out the {@link Writer} to receive the formatted data
+ * @return the HierarchicalStreamWriter
+ */
+ HierarchicalStreamWriter createWriter(Writer out);
+ /**
+ * Create the HierarchicalStreamWriter with the formatted writer.
+ *
+ * @param out the {@link OutputStream} to receive the formatted data
+ * @return the HierarchicalStreamWriter
+ * @since 1.1.3
+ */
+ HierarchicalStreamWriter createWriter(OutputStream out);
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/HierarchicalStreamReader.java b/xstream/src/java/com/thoughtworks/xstream/io/HierarchicalStreamReader.java
new file mode 100644
index 0000000..8ee85c7
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/HierarchicalStreamReader.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2011 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 07. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io;
+
+import com.thoughtworks.xstream.converters.ErrorReporter;
+import com.thoughtworks.xstream.converters.ErrorWriter;
+
+import java.util.Iterator;
+
+/**
+ * @author Joe Walnes
+ */
+public interface HierarchicalStreamReader extends ErrorReporter {
+
+ /**
+ * Does the node have any more children remaining that have not yet been read?
+ */
+ boolean hasMoreChildren();
+
+ /**
+ * Select the current child as current node.
+ * A call to this function must be balanced with a call to {@link #moveUp()}.
+ */
+ void moveDown();
+
+ /**
+ * Select the parent node as current node.
+ */
+ void moveUp();
+
+ /**
+ * Get the name of the current node.
+ */
+ String getNodeName();
+
+ /**
+ * Get the value (text content) of the current node.
+ */
+ String getValue();
+
+ /**
+ * Get the value of an attribute of the current node.
+ */
+ String getAttribute(String name);
+
+ /**
+ * Get the value of an attribute of the current node, by index.
+ */
+ String getAttribute(int index);
+
+ /**
+ * Number of attributes in current node.
+ */
+ int getAttributeCount();
+
+ /**
+ * Name of attribute in current node.
+ */
+ String getAttributeName(int index);
+
+ /**
+ * Names of attributes (as Strings).
+ */
+ Iterator getAttributeNames();
+
+ /**
+ * If any errors are detected, allow the reader to add any additional information that can aid debugging
+ * (such as line numbers, XPath expressions, etc).
+ */
+ void appendErrors(ErrorWriter errorWriter);
+
+ /**
+ * Close the reader, if necessary.
+ */
+ void close();
+
+ /**
+ * Return the underlying HierarchicalStreamReader implementation.
+ *
+ *
If a Converter needs to access methods of a specific HierarchicalStreamReader implementation that are not
+ * defined in the HierarchicalStreamReader interface, it should call this method before casting. This is because
+ * the reader passed to the Converter is often wrapped/decorated by another implementation to provide additional
+ * functionality (such as XPath tracking).
Implementations of HierarchicalStreamReader should return 'this', unless they are a decorator, in which case
+ * they should delegate to whatever they are wrapping.
+ */
+ HierarchicalStreamReader underlyingReader();
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/HierarchicalStreamWriter.java b/xstream/src/java/com/thoughtworks/xstream/io/HierarchicalStreamWriter.java
new file mode 100644
index 0000000..4449928
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/HierarchicalStreamWriter.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 07. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io;
+
+/**
+ * @author Joe Walnes
+ */
+public interface HierarchicalStreamWriter {
+
+ void startNode(String name);
+
+ void addAttribute(String name, String value);
+
+ /**
+ * Write the value (text content) of the current node.
+ */
+ void setValue(String text);
+
+ void endNode();
+
+ /**
+ * Flush the writer, if necessary.
+ */
+ void flush();
+
+ /**
+ * Close the writer, if necessary.
+ */
+ void close();
+
+ /**
+ * Return the underlying HierarchicalStreamWriter implementation.
+ *
+ *
If a Converter needs to access methods of a specific HierarchicalStreamWriter implementation that are not
+ * defined in the HierarchicalStreamWriter interface, it should call this method before casting. This is because
+ * the writer passed to the Converter is often wrapped/decorated by another implementation to provide additional
+ * functionality (such as XPath tracking).
Implementations of HierarchicalStreamWriter should return 'this', unless they are a decorator, in which case
+ * they should delegate to whatever they are wrapping.
+ */
+ HierarchicalStreamWriter underlyingWriter();
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/ReaderWrapper.java b/xstream/src/java/com/thoughtworks/xstream/io/ReaderWrapper.java
new file mode 100644
index 0000000..38cbbf6
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/ReaderWrapper.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2011 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 10. April 2005 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io;
+
+import com.thoughtworks.xstream.converters.ErrorWriter;
+
+import java.util.Iterator;
+
+/**
+ * Base class to make it easy to create wrappers (decorators) for HierarchicalStreamReader.
+ *
+ * @author Joe Walnes
+ */
+public abstract class ReaderWrapper implements ExtendedHierarchicalStreamReader {
+
+ protected HierarchicalStreamReader wrapped;
+
+ protected ReaderWrapper(HierarchicalStreamReader reader) {
+ this.wrapped = reader;
+ }
+
+ public boolean hasMoreChildren() {
+ return wrapped.hasMoreChildren();
+ }
+
+ public void moveDown() {
+ wrapped.moveDown();
+ }
+
+ public void moveUp() {
+ wrapped.moveUp();
+ }
+
+ public String getNodeName() {
+ return wrapped.getNodeName();
+ }
+
+ public String getValue() {
+ return wrapped.getValue();
+ }
+
+ public String getAttribute(String name) {
+ return wrapped.getAttribute(name);
+ }
+
+ public String getAttribute(int index) {
+ return wrapped.getAttribute(index);
+ }
+
+ public int getAttributeCount() {
+ return wrapped.getAttributeCount();
+ }
+
+ public String getAttributeName(int index) {
+ return wrapped.getAttributeName(index);
+ }
+
+ public Iterator getAttributeNames() {
+ return wrapped.getAttributeNames();
+ }
+
+ public void appendErrors(ErrorWriter errorWriter) {
+ wrapped.appendErrors(errorWriter);
+ }
+
+ public void close() {
+ wrapped.close();
+ }
+
+ public String peekNextChild() {
+ if (! (wrapped instanceof ExtendedHierarchicalStreamReader)) {
+ throw new UnsupportedOperationException("peekNextChild");
+ }
+ return ((ExtendedHierarchicalStreamReader)wrapped).peekNextChild();
+ }
+
+ public HierarchicalStreamReader underlyingReader() {
+ return wrapped.underlyingReader();
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/StatefulWriter.java b/xstream/src/java/com/thoughtworks/xstream/io/StatefulWriter.java
new file mode 100644
index 0000000..23e0cab
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/StatefulWriter.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 15. March 2006 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io;
+
+import com.thoughtworks.xstream.core.util.FastStack;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+
+
+/**
+ * An wrapper for all {@link HierarchicalStreamWriter} implementations, that keeps the state.
+ * Writing in a wrong state will throw a {@link StreamException}, that wraps either an
+ * {@link IOException} (writing to a closed writer) or an {@link IllegalStateException}. The
+ * implementation will also track unbalanced nodes or multiple attributes with the same name.
+ *
+ * @author Jörg Schaible
+ * @since 1.2
+ */
+public class StatefulWriter extends WriterWrapper {
+
+ /**
+ * STATE_OPEN is the initial value of the writer.
+ *
+ * @since 1.2
+ */
+ public static int STATE_OPEN = 0;
+ /**
+ * STATE_NODE_START is the state of a new node has been started.
+ *
+ * @since 1.2
+ */
+ public static int STATE_NODE_START = 1;
+ /**
+ * STATE_VALUE is the state if the value of a node has been written.
+ *
+ * @since 1.2
+ */
+ public static int STATE_VALUE = 2;
+ /**
+ * STATE_NODE_END is the state if a node has ended
+ *
+ * @since 1.2
+ */
+ public static int STATE_NODE_END = 3;
+ /**
+ * STATE_CLOSED is the state if the writer has been closed.
+ *
+ * @since 1.2
+ */
+ public static int STATE_CLOSED = 4;
+
+ private transient int state = STATE_OPEN;
+ private transient int balance;
+ private transient FastStack attributes;
+
+ /**
+ * Constructs a StatefulWriter.
+ *
+ * @param wrapped the wrapped writer
+ * @since 1.2
+ */
+ public StatefulWriter(final HierarchicalStreamWriter wrapped) {
+ super(wrapped);
+ attributes = new FastStack(16);
+ }
+
+ public void startNode(final String name) {
+ startNodeCommon();
+ super.startNode(name);
+ }
+
+ public void startNode(final String name, final Class clazz) {
+ startNodeCommon();
+ super.startNode(name, clazz);
+ }
+
+ private void startNodeCommon() {
+ checkClosed();
+ if (state == STATE_VALUE) {
+ // legal XML, but not in XStream ... ?
+ throw new StreamException(new IllegalStateException("Opening node after writing text"));
+ }
+ state = STATE_NODE_START;
+ ++balance;
+ attributes.push(new HashSet());
+ }
+
+ public void addAttribute(String name, String value) {
+ checkClosed();
+ if (state != STATE_NODE_START) {
+ throw new StreamException(new IllegalStateException("Writing attribute '"
+ + name
+ + "' without an opened node"));
+ }
+ Set currentAttributes = (Set)attributes.peek();
+ if (currentAttributes.contains(name)) {
+ throw new StreamException(new IllegalStateException("Writing attribute '"
+ + name
+ + "' twice"));
+ }
+ currentAttributes.add(name);
+ super.addAttribute(name, value);
+ }
+
+ public void setValue(String text) {
+ checkClosed();
+ if (state != STATE_NODE_START) {
+ // STATE_NODE_END is legal XML, but not in XStream ... ?
+ throw new StreamException(new IllegalStateException(
+ "Writing text without an opened node"));
+ }
+ state = STATE_VALUE;
+ super.setValue(text);
+ }
+
+ public void endNode() {
+ checkClosed();
+ if (balance-- == 0) {
+ throw new StreamException(new IllegalStateException("Unbalanced node"));
+ }
+ attributes.popSilently();
+ state = STATE_NODE_END;
+ super.endNode();
+ }
+
+ public void flush() {
+ checkClosed();
+ super.flush();
+ }
+
+ public void close() {
+ if (state != STATE_NODE_END && state != STATE_OPEN) {
+ // calling close in a finally block should not throw again
+ // throw new StreamException(new IllegalStateException("Closing with unbalanced tag"));
+ }
+ state = STATE_CLOSED;
+ super.close();
+ }
+
+ private void checkClosed() {
+ if (state == STATE_CLOSED) {
+ throw new StreamException(new IOException("Writing on a closed stream"));
+ }
+ }
+
+ /**
+ * Retrieve the state of the writer.
+ *
+ * @return one of the states
+ * @see #STATE_OPEN
+ * @see #STATE_NODE_START
+ * @see #STATE_VALUE
+ * @see #STATE_NODE_END
+ * @see #STATE_CLOSED
+ * @since 1.2
+ */
+ public int state() {
+ return state;
+ }
+
+ private Object readResolve() {
+ attributes = new FastStack(16);
+ return this;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/StreamException.java b/xstream/src/java/com/thoughtworks/xstream/io/StreamException.java
new file mode 100644
index 0000000..4be7844
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/StreamException.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2004, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2011 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 07. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io;
+
+import com.thoughtworks.xstream.XStreamException;
+
+public class StreamException extends XStreamException {
+ public StreamException(Throwable e) {
+ super(e);
+ }
+
+ public StreamException(String message) {
+ super(message);
+ }
+
+ /**
+ * @since 1.4
+ */
+ public StreamException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/WriterWrapper.java b/xstream/src/java/com/thoughtworks/xstream/io/WriterWrapper.java
new file mode 100644
index 0000000..54d0007
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/WriterWrapper.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 10. April 2005 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io;
+
+/**
+ * Base class to make it easy to create wrappers (decorators) for HierarchicalStreamWriter.
+ *
+ * @author Joe Walnes
+ */
+public abstract class WriterWrapper implements ExtendedHierarchicalStreamWriter {
+
+ protected HierarchicalStreamWriter wrapped;
+
+ protected WriterWrapper(HierarchicalStreamWriter wrapped) {
+ this.wrapped = wrapped;
+ }
+
+ public void startNode(String name) {
+ wrapped.startNode(name);
+ }
+
+ public void startNode(String name, Class clazz) {
+
+ ((ExtendedHierarchicalStreamWriter) wrapped).startNode(name, clazz);
+ }
+
+ public void endNode() {
+ wrapped.endNode();
+ }
+
+ public void addAttribute(String key, String value) {
+ wrapped.addAttribute(key, value);
+ }
+
+ public void setValue(String text) {
+ wrapped.setValue(text);
+ }
+
+ public void flush() {
+ wrapped.flush();
+ }
+
+ public void close() {
+ wrapped.close();
+ }
+
+ public HierarchicalStreamWriter underlyingWriter() {
+ return wrapped.underlyingWriter();
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/binary/BinaryStreamDriver.java b/xstream/src/java/com/thoughtworks/xstream/io/binary/BinaryStreamDriver.java
new file mode 100644
index 0000000..ca7e6fb
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/binary/BinaryStreamDriver.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2011 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 14. October 2011 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io.binary;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+
+import com.thoughtworks.xstream.io.AbstractDriver;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+
+
+/**
+ * HierarchicalStreamDriver for binary input and output. The driver uses an optimized binary
+ * format to store an object graph. The format is not as compact as Java serialization, but a
+ * lot more than typical text-based formats like XML. However, due to its nature it cannot use a
+ * {@link Reader} for input or a {@link Writer} for output.
+ *
+ * @author Jörg Schaible
+ * @since 1.4.2
+ */
+public class BinaryStreamDriver extends AbstractDriver {
+
+ /**
+ * @throws UnsupportedOperationException if called
+ */
+ public HierarchicalStreamReader createReader(Reader in) {
+ throw new UnsupportedOperationException(
+ "The BinaryDriver cannot use character-oriented input streams.");
+ }
+
+ public HierarchicalStreamReader createReader(InputStream in) {
+ return new BinaryStreamReader(in);
+ }
+
+ /**
+ * @throws UnsupportedOperationException if called
+ */
+ public HierarchicalStreamWriter createWriter(Writer out) {
+ throw new UnsupportedOperationException(
+ "The BinaryDriver cannot use character-oriented output streams.");
+ }
+
+ public HierarchicalStreamWriter createWriter(OutputStream out) {
+ return new BinaryStreamWriter(out);
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/binary/BinaryStreamReader.java b/xstream/src/java/com/thoughtworks/xstream/io/binary/BinaryStreamReader.java
new file mode 100644
index 0000000..2839651
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/binary/BinaryStreamReader.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2011, 2013 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 04. June 2006 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.binary;
+
+import com.thoughtworks.xstream.converters.ErrorWriter;
+import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.StreamException;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * A HierarchicalStreamReader that reads from a binary stream created by
+ * {@link BinaryStreamWriter}.
+ *
+ * @author Joe Walnes
+ * @see BinaryStreamReader
+ * @since 1.2
+ */
+public class BinaryStreamReader implements ExtendedHierarchicalStreamReader {
+
+ private final DataInputStream in;
+ private final ReaderDepthState depthState = new ReaderDepthState();
+ private final IdRegistry idRegistry = new IdRegistry();
+
+ private Token pushback;
+ private final Token.Formatter tokenFormatter = new Token.Formatter();
+
+ public BinaryStreamReader(InputStream inputStream) {
+ in = new DataInputStream(inputStream);
+ moveDown();
+ }
+
+ public boolean hasMoreChildren() {
+ return depthState.hasMoreChildren();
+ }
+
+ public String getNodeName() {
+ return depthState.getName();
+ }
+
+ public String getValue() {
+ return depthState.getValue();
+ }
+
+ public String getAttribute(String name) {
+ return depthState.getAttribute(name);
+ }
+
+ public String getAttribute(int index) {
+ return depthState.getAttribute(index);
+ }
+
+ public int getAttributeCount() {
+ return depthState.getAttributeCount();
+ }
+
+ public String getAttributeName(int index) {
+ return depthState.getAttributeName(index);
+ }
+
+ public Iterator getAttributeNames() {
+ return depthState.getAttributeNames();
+ }
+
+ public void moveDown() {
+ depthState.push();
+ Token firstToken = readToken();
+ switch (firstToken.getType()) {
+ case Token.TYPE_START_NODE:
+ depthState.setName(idRegistry.get(firstToken.getId()));
+ break;
+ default:
+ throw new StreamException("Expected StartNode");
+ }
+ while (true) {
+ Token nextToken = readToken();
+ switch (nextToken.getType()) {
+ case Token.TYPE_ATTRIBUTE:
+ depthState.addAttribute(idRegistry.get(nextToken.getId()), nextToken.getValue());
+ break;
+ case Token.TYPE_VALUE:
+ depthState.setValue(nextToken.getValue());
+ break;
+ case Token.TYPE_END_NODE:
+ depthState.setHasMoreChildren(false);
+ pushBack(nextToken);
+ return;
+ case Token.TYPE_START_NODE:
+ depthState.setHasMoreChildren(true);
+ pushBack(nextToken);
+ return;
+ default:
+ throw new StreamException("Unexpected token " + nextToken);
+ }
+ }
+ }
+
+ public void moveUp() {
+ depthState.pop();
+ // We're done with this depth. Skip over all tokens until we get to the end.
+ int depth = 0;
+ slurp:
+ while (true) {
+ Token nextToken = readToken();
+ switch(nextToken.getType()) {
+ case Token.TYPE_END_NODE:
+ if (depth == 0) {
+ break slurp;
+ } else {
+ depth--;
+ }
+ break;
+ case Token.TYPE_START_NODE:
+ depth++;
+ break;
+ default:
+ // Ignore other tokens
+ }
+ }
+ // Peek ahead to determine if there are any more kids at this level.
+ Token nextToken = readToken();
+ switch(nextToken.getType()) {
+ case Token.TYPE_END_NODE:
+ depthState.setHasMoreChildren(false);
+ break;
+ case Token.TYPE_START_NODE:
+ depthState.setHasMoreChildren(true);
+ break;
+ default:
+ throw new StreamException("Unexpected token " + nextToken);
+ }
+ pushBack(nextToken);
+ }
+
+ private Token readToken() {
+ if (pushback == null) {
+ try {
+ Token token = tokenFormatter.read(in);
+ switch (token.getType()) {
+ case Token.TYPE_MAP_ID_TO_VALUE:
+ idRegistry.put(token.getId(), token.getValue());
+ return readToken(); // Next one please.
+ default:
+ return token;
+ }
+ } catch (IOException e) {
+ throw new StreamException(e);
+ }
+ } else {
+ Token result = pushback;
+ pushback = null;
+ return result;
+ }
+ }
+
+ public void pushBack(Token token) {
+ if (pushback == null) {
+ pushback = token;
+ } else {
+ // If this happens, I've messed up :( -joe.
+ throw new Error("Cannot push more than one token back");
+ }
+ }
+
+ public void close() {
+ try {
+ in.close();
+ } catch (IOException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public String peekNextChild() {
+ if (depthState.hasMoreChildren()) {
+ return idRegistry.get(pushback.getId());
+ }
+ return null;
+ }
+
+ public HierarchicalStreamReader underlyingReader() {
+ return this;
+ }
+
+ public void appendErrors(ErrorWriter errorWriter) {
+ // TODO: When things go bad, it would be good to know where!
+ }
+
+ private static class IdRegistry {
+
+ private Map map = new HashMap();
+
+ public void put(long id, String value) {
+ map.put(new Long(id), value);
+ }
+
+ public String get(long id) {
+ String result = (String) map.get(new Long(id));
+ if (result == null) {
+ throw new StreamException("Unknown ID : " + id);
+ } else {
+ return result;
+ }
+ }
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/binary/BinaryStreamWriter.java b/xstream/src/java/com/thoughtworks/xstream/io/binary/BinaryStreamWriter.java
new file mode 100644
index 0000000..e7f88a0
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/binary/BinaryStreamWriter.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 04. June 2006 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.binary;
+
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.io.StreamException;
+import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriter;
+
+import java.io.DataOutputStream;
+import java.io.OutputStream;
+import java.io.IOException;
+import java.util.Map;
+import java.util.HashMap;
+
+/**
+ * @since 1.2
+ */
+public class BinaryStreamWriter implements ExtendedHierarchicalStreamWriter {
+
+ private final IdRegistry idRegistry = new IdRegistry();
+ private final DataOutputStream out;
+ private final Token.Formatter tokenFormatter = new Token.Formatter();
+
+ public BinaryStreamWriter(OutputStream outputStream) {
+ out = new DataOutputStream(outputStream);
+ }
+
+ public void startNode(String name) {
+ write(new Token.StartNode(idRegistry.getId(name)));
+ }
+
+ public void startNode(String name, Class clazz) {
+ startNode(name);
+ }
+
+ public void addAttribute(String name, String value) {
+ write(new Token.Attribute(idRegistry.getId(name), value));
+ }
+
+ public void setValue(String text) {
+ write(new Token.Value(text));
+ }
+
+ public void endNode() {
+ write(new Token.EndNode());
+ }
+
+ public void flush() {
+ try {
+ out.flush();
+ } catch (IOException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public void close() {
+ try {
+ out.close();
+ } catch (IOException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public HierarchicalStreamWriter underlyingWriter() {
+ return this;
+ }
+
+ private void write(Token token) {
+ try {
+ tokenFormatter.write(out, token);
+ } catch (IOException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ private class IdRegistry {
+
+ private long nextId = 0;
+ private Map ids = new HashMap();
+
+ public long getId(String value) {
+ Long id = (Long) ids.get(value);
+ if (id == null) {
+ id = new Long(++nextId);
+ ids.put(value, id);
+ write(new Token.MapIdToValue(id.longValue(), value));
+ }
+ return id.longValue();
+ }
+
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/binary/ReaderDepthState.java b/xstream/src/java/com/thoughtworks/xstream/io/binary/ReaderDepthState.java
new file mode 100644
index 0000000..6a27ce8
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/binary/ReaderDepthState.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 04. June 2006 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.binary;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Collections;
+
+/**
+ * Maintains the state of a pull reader at various states in the document depth.
+ *
+ * Used by the {@link BinaryStreamReader}
+ *
+ * @author Joe Walnes
+ * @since 1.2
+ */
+class ReaderDepthState {
+
+ private static final String EMPTY_STRING = "";
+
+ private static class State {
+ String name;
+ String value;
+ List attributes;
+ boolean hasMoreChildren;
+ State parent;
+ }
+
+ private static class Attribute {
+ String name;
+ String value;
+ }
+
+ private State current;
+
+ public void push() {
+ State newState = new State();
+ newState.parent = current;
+ current = newState;
+ }
+
+ public void pop() {
+ current = current.parent;
+ }
+
+ public String getName() {
+ return current.name;
+ }
+
+ public void setName(String name) {
+ current.name = name;
+ }
+
+ public String getValue() {
+ return current.value == null ? EMPTY_STRING : current.value;
+ }
+
+ public void setValue(String value) {
+ current.value = value;
+ }
+
+ public boolean hasMoreChildren() {
+ return current.hasMoreChildren;
+ }
+
+ public void setHasMoreChildren(boolean hasMoreChildren) {
+ current.hasMoreChildren = hasMoreChildren;
+ }
+
+ public void addAttribute(String name, String value) {
+ Attribute attribute = new Attribute();
+ attribute.name = name;
+ attribute.value = value;
+ if (current.attributes == null) {
+ current.attributes = new ArrayList();
+ }
+ current.attributes.add(attribute);
+ }
+
+ public String getAttribute(String name) {
+ if (current.attributes == null) {
+ return null;
+ } else {
+ // For short maps, it's faster to iterate then do a hashlookup.
+ for (Iterator iterator = current.attributes.iterator(); iterator.hasNext();) {
+ Attribute attribute = (Attribute) iterator.next();
+ if (attribute.name.equals(name)) {
+ return attribute.value;
+ }
+ }
+ return null;
+ }
+ }
+
+ public String getAttribute(int index) {
+ if (current.attributes == null) {
+ return null;
+ } else {
+ Attribute attribute = (Attribute) current.attributes.get(index);
+ return attribute.value;
+ }
+ }
+
+ public String getAttributeName(int index) {
+ if (current.attributes == null) {
+ return null;
+ } else {
+ Attribute attribute = (Attribute) current.attributes.get(index);
+ return attribute.name;
+ }
+ }
+
+ public int getAttributeCount() {
+ return current.attributes == null ? 0 : current.attributes.size();
+ }
+
+ public Iterator getAttributeNames() {
+ if (current.attributes == null) {
+ return Collections.EMPTY_SET.iterator();
+ } else {
+ final Iterator attributeIterator = current.attributes.iterator();
+ return new Iterator() {
+ public boolean hasNext() {
+ return attributeIterator.hasNext();
+ }
+
+ public Object next() {
+ Attribute attribute = (Attribute) attributeIterator.next();
+ return attribute.name;
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/binary/Token.java b/xstream/src/java/com/thoughtworks/xstream/io/binary/Token.java
new file mode 100644
index 0000000..0dfef39
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/binary/Token.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2013 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 04. June 2006 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.binary;
+
+import com.thoughtworks.xstream.io.StreamException;
+
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.DataInput;
+
+/**
+ * Represents the Tokens stored in the binary stream used by
+ * {@link BinaryStreamReader} and {@link BinaryStreamWriter}.
+ *
+ * A token consists of a type and (depending on this type)
+ * it may additionally have an ID (positive long number)
+ * and/or a value (String).
+ *
+ * The first byte of the token represents how many subsequent
+ * bytes are used by the ID.
+ *
+ * @author Joe Walnes
+ * @see BinaryStreamReader
+ * @see BinaryStreamWriter
+ * @since 1.2
+ */
+public abstract class Token {
+
+ private static final byte TYPE_MASK = 0x7;
+ public static final byte TYPE_VERSION = 0x1;
+ public static final byte TYPE_MAP_ID_TO_VALUE = 0x2;
+ public static final byte TYPE_START_NODE = 0x3;
+ public static final byte TYPE_END_NODE = 0x4;
+ public static final byte TYPE_ATTRIBUTE = 0x5;
+ public static final byte TYPE_VALUE = 0x6;
+
+ private static final byte ID_MASK = 0x38;
+ private static final byte ID_ONE_BYTE = 0x08;
+ private static final byte ID_TWO_BYTES = 0x10;
+ private static final byte ID_FOUR_BYTES = 0x18;
+ private static final byte ID_EIGHT_BYTES = 0x20;
+
+ private static final String ID_SPLITTED = "\u0000\u2021\u0000";
+ private static final int MAX_UTF8_LENGTH = 0xffff;
+
+ private final byte type;
+
+ protected long id = -1;
+ protected String value;
+
+ public Token(byte type) {
+ this.type = type;
+ }
+
+ public byte getType() {
+ return type;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public String toString() {
+ return getClass().getName() + " [id=" + id + ", value='" + value + "']";
+ }
+
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ final Token token = (Token) o;
+
+ if (id != token.id) return false;
+ if (type != token.type) return false;
+ return !(value != null ? !value.equals(token.value) : token.value != null);
+ }
+
+ public int hashCode() {
+ int result;
+ result = type;
+ result = 29 * result + (int) (id ^ (id >>> 32));
+ result = 29 * result + (value != null ? value.hashCode() : 0);
+ return result;
+ }
+
+ public abstract void writeTo(DataOutput out, byte idType) throws IOException;
+
+ public abstract void readFrom(DataInput in, byte idType) throws IOException;
+
+ protected void writeId(DataOutput out, long id, byte idType) throws IOException {
+ if (id < 0) {
+ throw new IOException("id must not be negative " + id);
+ }
+ switch (idType) {
+ case ID_ONE_BYTE:
+ out.writeByte((byte) id + Byte.MIN_VALUE);
+ break;
+ case ID_TWO_BYTES:
+ out.writeShort((short) id + Short.MIN_VALUE);
+ break;
+ case ID_FOUR_BYTES:
+ out.writeInt((int) id + Integer.MIN_VALUE);
+ break;
+ case ID_EIGHT_BYTES:
+ out.writeLong(id + Long.MIN_VALUE);
+ break;
+ default:
+ throw new Error("Unknown idType " + idType);
+ }
+ }
+
+ protected void writeString(DataOutput out, String string) throws IOException {
+ final byte[] bytes = (string.length() > MAX_UTF8_LENGTH / 4) ? string.getBytes("utf-8") : new byte[0];
+ int length = bytes.length;
+ if (length <= MAX_UTF8_LENGTH) {
+ out.writeUTF(string);
+ } else {
+ out.writeUTF(ID_SPLITTED);
+ out.writeInt(bytes.length);
+ out.write(bytes);
+ }
+ }
+
+ protected long readId(DataInput in, byte idType) throws IOException {
+ switch (idType) {
+ case ID_ONE_BYTE:
+ return in.readByte() - Byte.MIN_VALUE;
+ case ID_TWO_BYTES:
+ return in.readShort() - Short.MIN_VALUE;
+ case ID_FOUR_BYTES:
+ return in.readInt() - Integer.MIN_VALUE;
+ case ID_EIGHT_BYTES:
+ return in.readLong() - Long.MIN_VALUE;
+ default:
+ throw new Error("Unknown idType " + idType);
+ }
+ }
+
+ protected String readString(DataInput in) throws IOException {
+ final String string = in.readUTF();
+ if (!ID_SPLITTED.equals(string)) {
+ return string;
+ }
+ final int size = in.readInt();
+ final byte[] bytes = new byte[size];
+ in.readFully(bytes);
+ return new String(bytes, "utf-8");
+ }
+
+ public static class Formatter {
+
+ public void write(DataOutput out, Token token) throws IOException {
+ long id = token.getId();
+ byte idType;
+ if (id <= Byte.MAX_VALUE - Byte.MIN_VALUE) {
+ idType = ID_ONE_BYTE;
+ } else if (id <= Short.MAX_VALUE - Short.MIN_VALUE) {
+ idType = ID_TWO_BYTES;
+ } else if (id <= (long) Integer.MAX_VALUE - (long) Integer.MIN_VALUE) { // cast to long to prevent overflow
+ idType = ID_FOUR_BYTES;
+ } else {
+ idType = ID_EIGHT_BYTES;
+ }
+ out.write(token.getType() + idType);
+ token.writeTo(out, idType);
+ }
+
+ public Token read(DataInput in) throws IOException {
+ byte nextByte = in.readByte();
+ byte type = (byte) (nextByte & TYPE_MASK);
+ byte idType = (byte) (nextByte & ID_MASK);
+ Token token = contructToken(type);
+ token.readFrom(in, idType);
+ return token;
+ }
+
+ private Token contructToken(byte type) {
+ switch (type) {
+ case Token.TYPE_START_NODE:
+ return new StartNode();
+ case Token.TYPE_MAP_ID_TO_VALUE:
+ return new MapIdToValue();
+ case Token.TYPE_ATTRIBUTE:
+ return new Attribute();
+ case Token.TYPE_END_NODE:
+ return new EndNode();
+ case Token.TYPE_VALUE:
+ return new Value();
+ default:
+ throw new StreamException("Unknown token type");
+ }
+ }
+ }
+
+ public static class MapIdToValue extends Token {
+
+ public MapIdToValue(long id, String value) {
+ super(TYPE_MAP_ID_TO_VALUE);
+ this.id = id;
+ this.value = value;
+ }
+
+ public MapIdToValue() {
+ super(TYPE_MAP_ID_TO_VALUE);
+ }
+
+ public void writeTo(DataOutput out, byte idType) throws IOException {
+ writeId(out, id, idType);
+ writeString(out, value);
+ }
+
+ public void readFrom(DataInput in, byte idType) throws IOException {
+ id = readId(in, idType);
+ value = readString(in);
+ }
+
+ }
+
+ public static class StartNode extends Token {
+
+ public StartNode(long id) {
+ super(TYPE_START_NODE);
+ this.id = id;
+ }
+
+ public StartNode() {
+ super(TYPE_START_NODE);
+ }
+
+ public void writeTo(DataOutput out, byte idType) throws IOException {
+ writeId(out, id, idType);
+ }
+
+ public void readFrom(DataInput in, byte idType) throws IOException {
+ id = readId(in, idType);
+ }
+
+ }
+
+ public static class EndNode extends Token {
+
+ public EndNode() {
+ super(TYPE_END_NODE);
+ }
+
+ public void writeTo(DataOutput out, byte idType) {
+ }
+
+ public void readFrom(DataInput in, byte idType) {
+ }
+
+ }
+
+ public static class Attribute extends Token {
+
+ public Attribute(long id, String value) {
+ super(TYPE_ATTRIBUTE);
+ this.id = id;
+ this.value = value;
+ }
+
+ public Attribute() {
+ super(TYPE_ATTRIBUTE);
+ }
+
+ public void writeTo(DataOutput out, byte idType) throws IOException {
+ writeId(out, id, idType);
+ writeString(out, value);
+ }
+
+ public void readFrom(DataInput in, byte idType) throws IOException {
+ this.id = readId(in, idType);
+ this.value = readString(in);
+ }
+
+ }
+
+ public static class Value extends Token {
+
+ public Value(String value) {
+ super(TYPE_VALUE);
+ this.value = value;
+ }
+
+ public Value() {
+ super(TYPE_VALUE);
+ }
+
+ public void writeTo(DataOutput out, byte idType) throws IOException {
+ writeString(out, value);
+ }
+
+ public void readFrom(DataInput in, byte idType) throws IOException {
+ value = readString(in);
+ }
+
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/copy/HierarchicalStreamCopier.java b/xstream/src/java/com/thoughtworks/xstream/io/copy/HierarchicalStreamCopier.java
new file mode 100644
index 0000000..3bb9f15
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/copy/HierarchicalStreamCopier.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2013 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 04. June 2006 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.copy;
+
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+
+/**
+ * Tool for copying the contents of one HierarichalStreamReader to a HierarichalStreamWriter.
+ *
+ * This is useful for transforming the output of one format to another (e.g. binary to XML)
+ * without needing to know details about the classes and avoiding the overhead of serialization.
+ *
+ *
Example
+ *
+ * HierarchicalStreamReader reader = new BinaryStreamReader(someBinaryInput);
+ * HierarchicalStreamWriter writer = new PrettyPrintWriter(someXmlOutput);
+ * HierarchicalStreamCopier copier = new HierarchicalStreamCopier();
+ * copier.copy(reader, writer);
+ *
+ *
+ * @author Joe Walnes
+ * @since 1.2
+ */
+public class HierarchicalStreamCopier {
+ public void copy(HierarchicalStreamReader source, HierarchicalStreamWriter destination) {
+ destination.startNode(source.getNodeName());
+ int attributeCount = source.getAttributeCount();
+ for (int i = 0; i < attributeCount; i++) {
+ destination.addAttribute(source.getAttributeName(i), source.getAttribute(i));
+ }
+ String value = source.getValue();
+ if (value != null && value.length() > 0) {
+ destination.setValue(value);
+ }
+ while (source.hasMoreChildren()) {
+ source.moveDown();
+ copy(source, destination);
+ source.moveUp();
+ }
+ destination.endNode();
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/json/AbstractJsonWriter.java b/xstream/src/java/com/thoughtworks/xstream/io/json/AbstractJsonWriter.java
new file mode 100644
index 0000000..ce5bb1e
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/json/AbstractJsonWriter.java
@@ -0,0 +1,661 @@
+/*
+ * Copyright (C) 2009, 2010, 2011, 2012, 2013 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 20. August 2009 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io.json;
+
+import java.io.Externalizable;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.core.util.FastStack;
+import com.thoughtworks.xstream.io.AbstractWriter;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+import com.thoughtworks.xstream.io.naming.NoNameCoder;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+
+/**
+ * An abstract implementation of a writer that calls abstract methods to build JSON structures.
+ * Note, that XStream's implicit collection feature is only compatible with the syntax in
+ * {@link #EXPLICIT_MODE}.
+ *
+ * @author Jörg Schaible
+ * @since 1.4
+ */
+public abstract class AbstractJsonWriter extends AbstractWriter {
+ /**
+ * DROP_ROOT_MODE drops the JSON root node.
+ *
+ * The root node is the first level of the JSON object i.e.
+ *
+ *
+ * { "person": {
+ * "name": "Joe"
+ * }}
+ *
+ *
+ *
will be written without root simply as
+ *
+ *
+ * {
+ * "name": "Joe"
+ * }
+ *
+ *
+ *
+ * Without a root node, the top level element might now also be an array. However, it is
+ * possible to generate invalid JSON unless {@link #STRICT_MODE} is also set.
+ *
+ * @since 1.3.1
+ */
+ public static final int DROP_ROOT_MODE = 1;
+ /**
+ * STRICT_MODE prevents invalid JSON for single value objects when dropping the root.
+ *
+ * The mode is only useful in combination with the {@link #DROP_ROOT_MODE}. An object with a
+ * single value as first node i.e.
+ *
+ *
+ * { "name": "Joe" }
+ *
+ *
+ *
is simply written as
+ *
+ *
+ * "Joe"
+ *
+ *
+ *
+ * However, this is no longer valid JSON. Therefore you can activate {@link #STRICT_MODE}
+ * and a {@link ConversionException} is thrown instead.
+ *
+ * @since 1.3.1
+ */
+ public static final int STRICT_MODE = 2;
+ /**
+ * EXPLICIT_MODE assures that all data has its explicit equivalent in the resulting JSON.
+ *
+ * XStream is normally using attributes in XML that have no real equivalent in JSON.
+ * Additionally it is essential in XML that the individual child elements of a tag keep
+ * order and may have the same tag name. XStream's model relies on both characteristics.
+ * However, properties of a JSON object do not have a defined order, but their names have to
+ * be unique. Only a JSON array defines the order of its elements.
+ *
+ *
+ * Therefore XStream uses in explicit mode a JSON format that supports the original
+ * requirements at the expense of the simplicity of the JSON objects and arrays. Each Java
+ * object will be represented by a JSON object with a single property representing the name
+ * of the object and an array as value that contains two more arrays. The first one contains
+ * a JSON object with all attributes, the second one the value of the Java object which can
+ * be null, a string or integer value or again a new JSON object representing a Java object.
+ * Here an example of an string array with one member, where the array and the string has an
+ * additional attribute 'id':
+ * This format can be used to always deserialize into Java again.
+ *
+ *
+ * This mode cannot combined with {@link #STRICT_MODE} or {@link #DROP_ROOT_MODE}.
+ *
+ *
+ * @since 1.4
+ */
+ public static final int EXPLICIT_MODE = 4;
+ /**
+ * IEEE_754_MODE keeps precision of 64-bit integer values.
+ *
+ * In JavaScript every number is expressed as 64-bit double value with a precision of 53
+ * bits following IEEE 754. Therefore it is not possible to represent the complete value
+ * range of 64-bit integer values. Any integer value > 253
+ * (9007199254740992) or < -253 (-9007199254740992) will therefore be
+ * written as string value.
+ *
+ *
+ * CAUTION: A client must be aware that the element may contain a number or a string value.
+ *
+ *
+ * @since 1.4.5
+ * @see ECMA Specification: The Number Type
+ */
+ public static final int IEEE_754_MODE = 8;
+
+ public static class Type {
+ public static Type NULL = new Type();
+ public static Type STRING = new Type();
+ public static Type NUMBER = new Type();
+ public static Type BOOLEAN = new Type();
+ }
+
+ private static class StackElement {
+ final Class type;
+ int status;
+ public StackElement(Class type, int status) {
+ this.type = type;
+ this.status = status;
+ }
+ }
+
+ private static class IllegalWriterStateException extends IllegalStateException {
+ public IllegalWriterStateException(int from, int to, String element) {
+ super("Cannot turn from state " + getState(from) + " into state " + getState(to)
+ + (element == null ? "" : " for property " + element));
+ }
+ private static String getState(int state) {
+ switch (state) {
+ case STATE_ROOT: return "ROOT";
+ case STATE_END_OBJECT: return "END_OBJECT";
+ case STATE_START_OBJECT: return "START_OBJECT";
+ case STATE_START_ATTRIBUTES: return "START_ATTRIBUTES";
+ case STATE_NEXT_ATTRIBUTE: return "NEXT_ATTRIBUTE";
+ case STATE_END_ATTRIBUTES: return "END_ATTRIBUTES";
+ case STATE_START_ELEMENTS: return "START_ELEMENTS";
+ case STATE_NEXT_ELEMENT: return "NEXT_ELEMENT";
+ case STATE_END_ELEMENTS: return "END_ELEMENTS";
+ case STATE_SET_VALUE: return "SET_VALUE";
+ default: throw new IllegalArgumentException("Unknown state provided: " + state
+ + ", cannot create message for IllegalWriterStateException");
+ }
+ }
+ }
+
+ private static final int STATE_ROOT = 1 << 0;
+ private static final int STATE_END_OBJECT = 1 << 1;
+ private static final int STATE_START_OBJECT = 1 << 2;
+ private static final int STATE_START_ATTRIBUTES = 1 << 3;
+ private static final int STATE_NEXT_ATTRIBUTE = 1 << 4;
+ private static final int STATE_END_ATTRIBUTES = 1 << 5;
+ private static final int STATE_START_ELEMENTS = 1 << 6;
+ private static final int STATE_NEXT_ELEMENT = 1 << 7;
+ private static final int STATE_END_ELEMENTS = 1 << 8;
+ private static final int STATE_SET_VALUE = 1 << 9;
+
+ private static final Set NUMBER_TYPES = new HashSet(Arrays.asList(new Class[]{
+ byte.class, Byte.class, short.class, Short.class, int.class, Integer.class, long.class,
+ Long.class, float.class, Float.class, double.class, Double.class, BigInteger.class,
+ BigDecimal.class}));
+ private int mode;
+ private FastStack stack = new FastStack(16);
+ private int expectedStates;
+
+ /**
+ * Construct a JSON writer.
+ *
+ * @since 1.4
+ */
+ public AbstractJsonWriter() {
+ this(new NoNameCoder());
+ }
+
+ /**
+ * Construct a JSON writer with a special mode.
+ *
+ * @param mode a bit mask of the mode constants
+ * @since 1.4
+ */
+ public AbstractJsonWriter(int mode) {
+ this(mode, new NoNameCoder());
+ }
+
+ /**
+ * Construct a JSON writer with a special name coder.
+ *
+ * @param nameCoder the name coder to use
+ * @since 1.4
+ */
+ public AbstractJsonWriter(NameCoder nameCoder) {
+ this(0, nameCoder);
+ }
+
+ /**
+ * Construct a JSON writer with a special mode and name coder.
+ *
+ * @param mode a bit mask of the mode constants
+ * @param nameCoder the name coder to use
+ * @since 1.4
+ */
+ public AbstractJsonWriter(int mode, NameCoder nameCoder) {
+ super(nameCoder);
+ this.mode = (mode & EXPLICIT_MODE) > 0 ? EXPLICIT_MODE : mode;
+ stack.push(new StackElement(null, STATE_ROOT));
+ expectedStates = STATE_START_OBJECT;
+ }
+
+ public void startNode(String name, Class clazz) {
+ if (name == null) {
+ throw new NullPointerException("name");
+ }
+ stack.push(new StackElement(clazz, ((StackElement)stack.peek()).status));
+ handleCheckedStateTransition(STATE_START_OBJECT, name, null);
+ expectedStates = STATE_SET_VALUE | STATE_NEXT_ATTRIBUTE | STATE_START_OBJECT | STATE_NEXT_ELEMENT | STATE_ROOT;
+ }
+
+ public void startNode(String name) {
+ startNode(name, null);
+ }
+
+ public void addAttribute(String name, String value) {
+ handleCheckedStateTransition(STATE_NEXT_ATTRIBUTE, name, value);
+ expectedStates = STATE_SET_VALUE | STATE_NEXT_ATTRIBUTE | STATE_START_OBJECT | STATE_NEXT_ELEMENT | STATE_ROOT;
+ }
+
+ public void setValue(String text) {
+ Class type = ((StackElement)stack.peek()).type;
+ if ((type == Character.class || type == Character.TYPE) && "".equals(text)) {
+ text = "\u0000";
+ }
+ handleCheckedStateTransition(STATE_SET_VALUE, null, text);
+ expectedStates = STATE_NEXT_ELEMENT | STATE_ROOT;
+ }
+
+ public void endNode() {
+ int size = stack.size();
+ int nextState = size > 2 ? STATE_NEXT_ELEMENT : STATE_ROOT;
+ handleCheckedStateTransition(nextState, null, null);
+ stack.pop();
+ ((StackElement)stack.peek()).status = nextState;
+ expectedStates = STATE_START_OBJECT;
+ if (size > 2) {
+ expectedStates |= STATE_NEXT_ELEMENT | STATE_ROOT;
+ }
+ }
+
+ private void handleCheckedStateTransition(final int requiredState, final String elementToAdd, final String valueToAdd)
+ {
+ final StackElement stackElement = (StackElement)stack.peek();
+ if ((expectedStates & requiredState) == 0) {
+ throw new IllegalWriterStateException(stackElement.status, requiredState, elementToAdd);
+ }
+ int currentState = handleStateTransition(stackElement.status, requiredState, elementToAdd, valueToAdd);
+ stackElement.status = currentState;
+ }
+
+ private int handleStateTransition(int currentState, final int requiredState, final String elementToAdd, final String valueToAdd)
+ {
+ int size = stack.size();
+ Class currentType = ((StackElement)stack.peek()).type;
+ boolean isArray = size > 1 && isArray(currentType);
+ boolean isArrayElement = size > 1 && isArray(((StackElement)stack.get(size-2)).type);
+ switch(currentState) {
+ case STATE_ROOT:
+ if (requiredState == STATE_START_OBJECT) {
+ currentState = handleStateTransition(STATE_START_ELEMENTS, STATE_START_OBJECT, elementToAdd, null);
+ return requiredState;
+ }
+ throw new IllegalWriterStateException(currentState, requiredState, elementToAdd);
+
+ case STATE_END_OBJECT:
+ switch(requiredState) {
+ case STATE_START_OBJECT:
+ currentState = handleStateTransition(currentState, STATE_NEXT_ELEMENT, null, null);
+ currentState = handleStateTransition(currentState, STATE_START_OBJECT, elementToAdd, null);
+ return requiredState;
+ case STATE_NEXT_ELEMENT:
+ nextElement();
+ return requiredState;
+ case STATE_ROOT:
+ if (((mode & DROP_ROOT_MODE) == 0 || size > 2) && (mode & EXPLICIT_MODE) == 0) {
+ endObject();
+ }
+ return requiredState;
+ default:
+ throw new IllegalWriterStateException(currentState, requiredState, elementToAdd);
+ }
+
+ case STATE_START_OBJECT:
+ switch(requiredState) {
+ case STATE_SET_VALUE:
+ case STATE_START_OBJECT:
+ case STATE_ROOT:
+ case STATE_NEXT_ELEMENT:
+ if (!isArrayElement || (mode & EXPLICIT_MODE) != 0) {
+ currentState = handleStateTransition(currentState, STATE_START_ATTRIBUTES, null, null);
+ currentState = handleStateTransition(currentState, STATE_END_ATTRIBUTES, null, null);
+ }
+ currentState = STATE_START_ELEMENTS;
+
+ switch(requiredState) {
+ case STATE_SET_VALUE:
+ currentState = handleStateTransition(currentState, STATE_SET_VALUE, null, valueToAdd);
+ break;
+ case STATE_START_OBJECT:
+ currentState = handleStateTransition(currentState, STATE_START_OBJECT, elementToAdd, null);
+ break;
+ case STATE_ROOT:
+ case STATE_NEXT_ELEMENT:
+ currentState = handleStateTransition(currentState, STATE_SET_VALUE, null, null);
+ currentState = handleStateTransition(currentState, requiredState, null, null);
+ break;
+ }
+ return requiredState;
+ case STATE_START_ATTRIBUTES:
+ if ((mode & EXPLICIT_MODE) != 0) {
+ startArray();
+ }
+ return requiredState;
+ case STATE_NEXT_ATTRIBUTE:
+ if ((mode & EXPLICIT_MODE) != 0 || !isArray) {
+ currentState = handleStateTransition(currentState, STATE_START_ATTRIBUTES, null, null);
+ currentState = handleStateTransition(currentState, STATE_NEXT_ATTRIBUTE, elementToAdd, valueToAdd);
+ return requiredState;
+ } else {
+ return STATE_START_OBJECT;
+ }
+ default:
+ throw new IllegalWriterStateException(currentState, requiredState, elementToAdd);
+ }
+
+ case STATE_NEXT_ELEMENT:
+ switch(requiredState) {
+ case STATE_START_OBJECT:
+ nextElement();
+ if (!isArrayElement && (mode & EXPLICIT_MODE) == 0) {
+ addLabel(encodeNode(elementToAdd));
+ if ((mode & EXPLICIT_MODE) == 0 && isArray) {
+ startArray();
+ }
+ return requiredState;
+ }
+ break;
+ case STATE_ROOT:
+ currentState = handleStateTransition(currentState, STATE_END_OBJECT, null, null);
+ currentState = handleStateTransition(currentState, STATE_ROOT, null, null);
+ return requiredState;
+ case STATE_NEXT_ELEMENT:
+ case STATE_END_OBJECT:
+ currentState = handleStateTransition(currentState, STATE_END_ELEMENTS, null, null);
+ currentState = handleStateTransition(currentState, STATE_END_OBJECT, null, null);
+ if ((mode & EXPLICIT_MODE) == 0 && !isArray) {
+ endObject();
+ }
+ return requiredState;
+ case STATE_END_ELEMENTS:
+ if ((mode & EXPLICIT_MODE) == 0 && isArray) {
+ endArray();
+ }
+ return requiredState;
+ default:
+ throw new IllegalWriterStateException(currentState, requiredState, elementToAdd);
+ }
+ // fall through
+ case STATE_START_ELEMENTS:
+ switch(requiredState) {
+ case STATE_START_OBJECT:
+ if ((mode & DROP_ROOT_MODE) == 0 || size > 2) {
+ if (!isArrayElement || (mode & EXPLICIT_MODE) != 0) {
+ if (!"".equals(valueToAdd)) {
+ startObject();
+ }
+ addLabel(encodeNode(elementToAdd));
+ }
+ if ((mode & EXPLICIT_MODE) != 0) {
+ startArray();
+ }
+ }
+ if ((mode & EXPLICIT_MODE) == 0) {
+ if (isArray) {
+ startArray();
+ }
+ }
+ return requiredState;
+ case STATE_SET_VALUE:
+ if ((mode & STRICT_MODE) != 0 && size == 2) {
+ throw new ConversionException("Single value cannot be root element");
+ }
+ if (valueToAdd == null) {
+ if (currentType == Mapper.Null.class) {
+ addValue("null", Type.NULL);
+ } else if ((mode & EXPLICIT_MODE) == 0 && !isArray) {
+ startObject();
+ endObject();
+ }
+ } else {
+ if (((mode & IEEE_754_MODE) != 0)
+ && (currentType == long.class || currentType == Long.class)) {
+ long longValue = Long.parseLong(valueToAdd);
+ // JavaScript supports a maximum of 2^53
+ if (longValue > 9007199254740992L || longValue < -9007199254740992L) {
+ addValue(valueToAdd, Type.STRING);
+ } else {
+ addValue(valueToAdd, getType(currentType));
+ }
+ } else {
+ addValue(valueToAdd, getType(currentType));
+ }
+ }
+ return requiredState;
+ case STATE_END_ELEMENTS:
+ case STATE_NEXT_ELEMENT:
+ if ((mode & EXPLICIT_MODE) == 0) {
+ if (isArray) {
+ endArray();
+ } else {
+ endObject();
+ }
+ }
+ return requiredState;
+ default:
+ throw new IllegalWriterStateException(currentState, requiredState, elementToAdd);
+ }
+
+ case STATE_END_ELEMENTS:
+ switch(requiredState) {
+ case STATE_END_OBJECT:
+ if ((mode & EXPLICIT_MODE) != 0) {
+ endArray();
+ endArray();
+ endObject();
+ }
+ return requiredState;
+ default:
+ throw new IllegalWriterStateException(currentState, requiredState, elementToAdd);
+ }
+
+ case STATE_START_ATTRIBUTES:
+ switch(requiredState) {
+ case STATE_NEXT_ATTRIBUTE:
+ if (elementToAdd != null) {
+ String name = ((mode & EXPLICIT_MODE) == 0 ? "@" : "" ) + elementToAdd;
+ startObject();
+ addLabel(encodeAttribute(name));
+ addValue(valueToAdd, Type.STRING);
+ }
+ return requiredState;
+ }
+ // fall through
+ case STATE_NEXT_ATTRIBUTE:
+ switch(requiredState) {
+ case STATE_END_ATTRIBUTES:
+ if ((mode & EXPLICIT_MODE) != 0) {
+ if (currentState == STATE_NEXT_ATTRIBUTE) {
+ endObject();
+ }
+ endArray();
+ nextElement();
+ startArray();
+ }
+ return requiredState;
+ case STATE_NEXT_ATTRIBUTE:
+ if (!isArray || (mode & EXPLICIT_MODE) != 0) {
+ nextElement();
+ String name = ((mode & EXPLICIT_MODE) == 0 ? "@" : "" ) + elementToAdd;
+ addLabel(encodeAttribute(name));
+ addValue(valueToAdd, Type.STRING);
+ }
+ return requiredState;
+ case STATE_SET_VALUE:
+ case STATE_START_OBJECT:
+ currentState = handleStateTransition(currentState, STATE_END_ATTRIBUTES, null, null);
+ currentState = handleStateTransition(currentState, STATE_START_ELEMENTS, null, null);
+ switch (requiredState) {
+ case STATE_SET_VALUE:
+ if ((mode & EXPLICIT_MODE) == 0) {
+ addLabel(encodeNode("$"));
+ }
+ currentState = handleStateTransition(currentState, STATE_SET_VALUE, null, valueToAdd);
+ if ((mode & EXPLICIT_MODE) == 0) {
+ endObject();
+ }
+ break;
+ case STATE_START_OBJECT:
+ currentState = handleStateTransition(currentState, STATE_START_OBJECT, elementToAdd, (mode & EXPLICIT_MODE) == 0 ? "" : null);
+ break;
+ case STATE_END_OBJECT:
+ currentState = handleStateTransition(currentState, STATE_SET_VALUE, null, null);
+ currentState = handleStateTransition(currentState, STATE_END_OBJECT, null, null);
+ break;
+ }
+ return requiredState;
+ case STATE_NEXT_ELEMENT:
+ currentState = handleStateTransition(currentState, STATE_END_ATTRIBUTES, null, null);
+ currentState = handleStateTransition(currentState, STATE_END_OBJECT, null, null);
+ return requiredState;
+ case STATE_ROOT:
+ currentState = handleStateTransition(currentState, STATE_END_ATTRIBUTES, null, null);
+ currentState = handleStateTransition(currentState, STATE_END_OBJECT, null, null);
+ currentState = handleStateTransition(currentState, STATE_ROOT, null, null);
+ return requiredState;
+ default:
+ throw new IllegalWriterStateException(currentState, requiredState, elementToAdd);
+ }
+
+ case STATE_END_ATTRIBUTES:
+ switch(requiredState) {
+ case STATE_START_ELEMENTS:
+ if ((mode & EXPLICIT_MODE) == 0) {
+ nextElement();
+ }
+ break;
+ case STATE_END_OBJECT:
+ currentState = handleStateTransition(STATE_START_ELEMENTS, STATE_END_ELEMENTS, null, null);
+ currentState = handleStateTransition(currentState, STATE_END_OBJECT, null, null);
+ break;
+ default:
+ throw new IllegalWriterStateException(currentState, requiredState, elementToAdd);
+ }
+ return requiredState;
+
+ case STATE_SET_VALUE:
+ switch(requiredState) {
+ case STATE_END_ELEMENTS:
+ if ((mode & EXPLICIT_MODE) == 0 && isArray) {
+ endArray();
+ }
+ return requiredState;
+ case STATE_NEXT_ELEMENT:
+ currentState = handleStateTransition(currentState, STATE_END_ELEMENTS, null, null);
+ currentState = handleStateTransition(currentState, STATE_END_OBJECT, null, null);
+ return requiredState;
+ case STATE_ROOT:
+ currentState = handleStateTransition(currentState, STATE_END_ELEMENTS, null, null);
+ currentState = handleStateTransition(currentState, STATE_END_OBJECT, null, null);
+ currentState = handleStateTransition(currentState, STATE_ROOT, null, null);
+ return requiredState;
+ default:
+ throw new IllegalWriterStateException(currentState, requiredState, elementToAdd);
+ }
+ }
+
+ throw new IllegalWriterStateException(currentState, requiredState, elementToAdd);
+ }
+
+ /**
+ * Method to return the appropriate JSON type for a Java type.
+ *
+ * @param clazz the type
+ * @return One of the {@link Type} instances
+ * @since 1.4.4
+ */
+ protected Type getType(Class clazz) {
+ return clazz == Mapper.Null.class
+ ? Type.NULL
+ : (clazz == Boolean.class || clazz == Boolean.TYPE)
+ ? Type.BOOLEAN
+ : NUMBER_TYPES.contains(clazz)
+ ? Type.NUMBER
+ : Type.STRING;
+ }
+
+ /**
+ * Method to declare various Java types to be handles as JSON array.
+ *
+ * @param clazz the type
+ * @return true if handles as array
+ * @since 1.4
+ */
+ protected boolean isArray(Class clazz) {
+ return clazz != null && (clazz.isArray()
+ || Collection.class.isAssignableFrom(clazz)
+ || Externalizable.class.isAssignableFrom(clazz)
+ || Map.class.isAssignableFrom(clazz)
+ || Map.Entry.class.isAssignableFrom(clazz));
+ }
+
+ /**
+ * Start a JSON object.
+ *
+ * @since 1.4
+ */
+ protected abstract void startObject();
+
+ /**
+ * Add a label to a JSON object.
+ *
+ * @param name the label's name
+ * @since 1.4
+ */
+ protected abstract void addLabel(String name);
+
+ /**
+ * Add a value to a JSON object's label or to an array.
+ *
+ * @param value the value itself
+ * @param type the JSON type
+ * @since 1.4
+ */
+ protected abstract void addValue(String value, Type type);
+
+ /**
+ * Start a JSON array.
+ *
+ * @since 1.4
+ */
+ protected abstract void startArray();
+
+ /**
+ * Prepare a JSON object or array for another element.
+ *
+ * @since 1.4
+ */
+ protected abstract void nextElement();
+
+ /**
+ * End the JSON array.
+ *
+ * @since 1.4
+ */
+ protected abstract void endArray();
+
+ /**
+ * End the JSON object.
+ *
+ * @since 1.4
+ */
+ protected abstract void endObject();
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/json/JettisonMappedXmlDriver.java b/xstream/src/java/com/thoughtworks/xstream/io/json/JettisonMappedXmlDriver.java
new file mode 100644
index 0000000..272ca11
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/json/JettisonMappedXmlDriver.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2007, 2008, 2009, 2010, 2011, 2013 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 30. March 2007 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io.json;
+
+import com.thoughtworks.xstream.io.AbstractDriver;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.io.StreamException;
+import com.thoughtworks.xstream.io.xml.QNameMap;
+import com.thoughtworks.xstream.io.xml.StaxReader;
+import com.thoughtworks.xstream.io.xml.StaxWriter;
+
+import org.codehaus.jettison.mapped.Configuration;
+import org.codehaus.jettison.mapped.MappedNamespaceConvention;
+import org.codehaus.jettison.mapped.MappedXMLInputFactory;
+import org.codehaus.jettison.mapped.MappedXMLOutputFactory;
+
+import javax.xml.stream.XMLStreamException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.URL;
+
+
+/**
+ * Simple XStream driver wrapping Jettison's Mapped reader and writer. Serializes object from
+ * and to JSON.
+ *
+ * @author Dejan Bosanac
+ */
+public class JettisonMappedXmlDriver extends AbstractDriver {
+
+ protected final MappedXMLOutputFactory mof;
+ protected final MappedXMLInputFactory mif;
+ protected final MappedNamespaceConvention convention;
+ protected final boolean useSerializeAsArray;
+
+ /**
+ * Construct a JettisonMappedXmlDriver.
+ */
+ public JettisonMappedXmlDriver() {
+ this(new Configuration());
+ }
+
+ /**
+ * Construct a JettisonMappedXmlDriver with configuration.
+ * @param config the Jettison configuration
+ */
+ public JettisonMappedXmlDriver(final Configuration config) {
+ this(config, true);
+ }
+
+ /**
+ * Construct a JettisonMappedXmlDriver with configuration. This constructor has been added
+ * by special request of Jettison users to support JSON generated by older Jettison
+ * versions. if the driver is setup to ignore the XStream hints for JSON arrays, there is
+ * neither support from XStream's side nor are there any tests to ensure this mode.
+ *
+ * @param config the Jettison configuration
+ * @param useSerializeAsArray flag to use XStream's hints for collections and arrays
+ * @since 1.4
+ */
+ public JettisonMappedXmlDriver(final Configuration config, final boolean useSerializeAsArray) {
+ mof = new MappedXMLOutputFactory(config);
+ mif = new MappedXMLInputFactory(config);
+ convention = new MappedNamespaceConvention(config);
+ this.useSerializeAsArray = useSerializeAsArray;
+ }
+
+ public HierarchicalStreamReader createReader(final Reader reader) {
+ try {
+ return new StaxReader(new QNameMap(), mif.createXMLStreamReader(reader), getNameCoder());
+ } catch (final XMLStreamException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public HierarchicalStreamReader createReader(final InputStream input) {
+ try {
+ return new StaxReader(new QNameMap(), mif.createXMLStreamReader(input), getNameCoder());
+ } catch (final XMLStreamException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public HierarchicalStreamReader createReader(URL in) {
+ InputStream instream = null;
+ try {
+ instream = in.openStream();
+ return new StaxReader(new QNameMap(), mif.createXMLStreamReader(
+ in.toExternalForm(), instream), getNameCoder());
+ } catch (final XMLStreamException e) {
+ throw new StreamException(e);
+ } catch (IOException e) {
+ throw new StreamException(e);
+ } finally {
+ if (instream != null) {
+ try {
+ instream.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+ }
+
+ public HierarchicalStreamReader createReader(File in) {
+ InputStream instream = null;
+ try {
+ instream = new FileInputStream(in);
+ return new StaxReader(new QNameMap(), mif.createXMLStreamReader(in
+ .toURI()
+ .toASCIIString(), instream), getNameCoder());
+ } catch (final XMLStreamException e) {
+ throw new StreamException(e);
+ } catch (IOException e) {
+ throw new StreamException(e);
+ } finally {
+ if (instream != null) {
+ try {
+ instream.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+ }
+
+ public HierarchicalStreamWriter createWriter(final Writer writer) {
+ try {
+ if (useSerializeAsArray) {
+ return new JettisonStaxWriter(new QNameMap(), mof.createXMLStreamWriter(writer), getNameCoder(), convention);
+ } else {
+ return new StaxWriter(new QNameMap(), mof.createXMLStreamWriter(writer), getNameCoder());
+ }
+ } catch (final XMLStreamException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public HierarchicalStreamWriter createWriter(final OutputStream output) {
+ try {
+ if (useSerializeAsArray) {
+ return new JettisonStaxWriter(new QNameMap(), mof.createXMLStreamWriter(output), getNameCoder(), convention);
+ } else {
+ return new StaxWriter(new QNameMap(), mof.createXMLStreamWriter(output), getNameCoder());
+ }
+ } catch (final XMLStreamException e) {
+ throw new StreamException(e);
+ }
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/json/JettisonStaxWriter.java b/xstream/src/java/com/thoughtworks/xstream/io/json/JettisonStaxWriter.java
new file mode 100644
index 0000000..bc2117e
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/json/JettisonStaxWriter.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 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 17.04.2008 by Joerg Schaible.
+ */
+package com.thoughtworks.xstream.io.json;
+
+import com.thoughtworks.xstream.io.naming.NameCoder;
+import com.thoughtworks.xstream.io.xml.QNameMap;
+import com.thoughtworks.xstream.io.xml.StaxWriter;
+import com.thoughtworks.xstream.io.xml.XmlFriendlyReplacer;
+
+import org.codehaus.jettison.AbstractXMLStreamWriter;
+import org.codehaus.jettison.mapped.MappedNamespaceConvention;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+
+import java.util.Collection;
+import java.util.Map;
+
+
+/**
+ * A specialized {@link StaxWriter} that makes usage of internal functionality of Jettison.
+ *
+ * @author Jörg Schaible
+ * @since 1.3.1
+ */
+public class JettisonStaxWriter extends StaxWriter {
+
+ private final MappedNamespaceConvention convention;
+
+ /**
+ * @since 1.4
+ */
+ public JettisonStaxWriter(
+ QNameMap qnameMap, XMLStreamWriter out, boolean writeEnclosingDocument,
+ boolean namespaceRepairingMode, NameCoder nameCoder,
+ MappedNamespaceConvention convention) throws XMLStreamException {
+ super(qnameMap, out, writeEnclosingDocument, namespaceRepairingMode, nameCoder);
+ this.convention = convention;
+ }
+
+ /**
+ * @deprecated As of 1.4 use
+ * {@link JettisonStaxWriter#JettisonStaxWriter(QNameMap, XMLStreamWriter, boolean, boolean, NameCoder, MappedNamespaceConvention)}
+ * instead
+ */
+ public JettisonStaxWriter(
+ QNameMap qnameMap, XMLStreamWriter out, boolean writeEnclosingDocument,
+ boolean namespaceRepairingMode, XmlFriendlyReplacer replacer,
+ MappedNamespaceConvention convention) throws XMLStreamException {
+ this(qnameMap, out, writeEnclosingDocument, namespaceRepairingMode, (NameCoder) replacer, convention);
+ }
+
+ public JettisonStaxWriter(
+ QNameMap qnameMap, XMLStreamWriter out, boolean writeEnclosingDocument,
+ boolean namespaceRepairingMode, MappedNamespaceConvention convention)
+ throws XMLStreamException {
+ super(qnameMap, out, writeEnclosingDocument, namespaceRepairingMode);
+ this.convention = convention;
+ }
+
+ public JettisonStaxWriter(
+ QNameMap qnameMap, XMLStreamWriter out, MappedNamespaceConvention convention)
+ throws XMLStreamException {
+ super(qnameMap, out);
+ this.convention = convention;
+ }
+
+ /**
+ * @since 1.4
+ */
+ public JettisonStaxWriter(
+ QNameMap qnameMap, XMLStreamWriter out, NameCoder nameCoder, MappedNamespaceConvention convention)
+ throws XMLStreamException {
+ super(qnameMap, out, nameCoder);
+ this.convention = convention;
+ }
+
+ public void startNode(String name, Class clazz) {
+ XMLStreamWriter out = getXMLStreamWriter();
+ if (clazz != null && out instanceof AbstractXMLStreamWriter) {
+ if (Collection.class.isAssignableFrom(clazz)
+ || Map.class.isAssignableFrom(clazz)
+ || clazz.isArray()) {
+ QName qname = getQNameMap().getQName(encodeNode(name));
+ String prefix = qname.getPrefix();
+ String uri = qname.getNamespaceURI();
+ String key = convention.createKey(prefix, uri, qname.getLocalPart());
+ if (!((AbstractXMLStreamWriter)out).getSerializedAsArrays().contains(key)) {
+ // Typo is in the API of Jettison ...
+ ((AbstractXMLStreamWriter)out).seriliazeAsArray(key);
+ }
+ }
+ }
+ startNode(name);
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/json/JsonHierarchicalStreamDriver.java b/xstream/src/java/com/thoughtworks/xstream/io/json/JsonHierarchicalStreamDriver.java
new file mode 100644
index 0000000..c0e607f
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/json/JsonHierarchicalStreamDriver.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2011 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 22. June 2006 by Mauro Talevi
+ */
+package com.thoughtworks.xstream.io.json;
+
+import com.thoughtworks.xstream.io.AbstractDriver;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.io.StreamException;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.net.URL;
+
+/**
+ * A driver for JSON that writes optimized JSON format, but is not able to deserialize the result.
+ *
+ * @author Paul Hammant
+ * @since 1.2
+ */
+public class JsonHierarchicalStreamDriver extends AbstractDriver {
+
+ /**
+ * Construct a JsonHierarchicalStreamDriver.
+ */
+ public JsonHierarchicalStreamDriver() {
+ super();
+ }
+
+ /**
+ * Construct a JsonHierarchicalStreamDriver with name coding.
+ *
+ * @param nameCoder the coder to encode and decode the JSON labels.
+ * @since 1.4.2
+ */
+ public JsonHierarchicalStreamDriver(NameCoder nameCoder) {
+ super(nameCoder);
+ }
+
+ public HierarchicalStreamReader createReader(Reader in) {
+ throw new UnsupportedOperationException("The JsonHierarchicalStreamDriver can only write JSON");
+ }
+
+ public HierarchicalStreamReader createReader(InputStream in) {
+ throw new UnsupportedOperationException("The JsonHierarchicalStreamDriver can only write JSON");
+ }
+
+ public HierarchicalStreamReader createReader(URL in) {
+ throw new UnsupportedOperationException("The JsonHierarchicalStreamDriver can only write JSON");
+ }
+
+ public HierarchicalStreamReader createReader(File in) {
+ throw new UnsupportedOperationException("The JsonHierarchicalStreamDriver can only write JSON");
+ }
+
+ /**
+ * Create a HierarchicalStreamWriter that writes JSON.
+ */
+ public HierarchicalStreamWriter createWriter(Writer out) {
+ return new JsonWriter(out);
+ }
+
+ public HierarchicalStreamWriter createWriter(OutputStream out) {
+ try {
+ // JSON spec requires UTF-8
+ return createWriter(new OutputStreamWriter(out, "UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ throw new StreamException(e);
+ }
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/json/JsonHierarchicalStreamWriter.java b/xstream/src/java/com/thoughtworks/xstream/io/json/JsonHierarchicalStreamWriter.java
new file mode 100644
index 0000000..4511a67
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/json/JsonHierarchicalStreamWriter.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009 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 22. June 2006 by Mauro Talevi
+ */
+package com.thoughtworks.xstream.io.json;
+
+import java.io.Writer;
+
+
+/**
+ * A simple writer that outputs JSON in a pretty-printed indented stream. Arrays, Lists and Sets
+ * rely on you NOT using XStream.addImplicitCollection(..)
+ *
+ * @author Paul Hammant
+ * @author Jörg Schaible
+ * @since 1.2
+ * @deprecated As of 1.3.1, use JsonWriter instead
+ */
+public class JsonHierarchicalStreamWriter extends JsonWriter {
+
+ /**
+ * @deprecated As of 1.3.1, use JsonWriter instead
+ */
+ public JsonHierarchicalStreamWriter(Writer writer, char[] lineIndenter, String newLine) {
+ super(writer, lineIndenter, newLine);
+ }
+
+ /**
+ * @deprecated As of 1.3.1, use JsonWriter instead
+ */
+ public JsonHierarchicalStreamWriter(Writer writer, char[] lineIndenter) {
+ this(writer, lineIndenter, "\n");
+ }
+
+ /**
+ * @deprecated As of 1.3.1, use JsonWriter instead
+ */
+ public JsonHierarchicalStreamWriter(Writer writer, String lineIndenter, String newLine) {
+ this(writer, lineIndenter.toCharArray(), newLine);
+ }
+
+ /**
+ * @deprecated As of 1.3.1, use JsonWriter instead
+ */
+ public JsonHierarchicalStreamWriter(Writer writer, String lineIndenter) {
+ this(writer, lineIndenter.toCharArray());
+ }
+
+ /**
+ * @deprecated As of 1.3.1, use JsonWriter instead
+ */
+ public JsonHierarchicalStreamWriter(Writer writer) {
+ this(writer, new char[]{' ', ' '});
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/json/JsonWriter.java b/xstream/src/java/com/thoughtworks/xstream/io/json/JsonWriter.java
new file mode 100644
index 0000000..b81b81b
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/json/JsonWriter.java
@@ -0,0 +1,406 @@
+/*
+ * Copyright (C) 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009, 2011, 2013 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 28. November 2008 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io.json;
+
+import java.io.Writer;
+
+import com.thoughtworks.xstream.converters.ConversionException;
+import com.thoughtworks.xstream.core.util.QuickWriter;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+import com.thoughtworks.xstream.io.naming.NoNameCoder;
+
+
+/**
+ * A simple writer that outputs JSON in a pretty-printed indented stream. Arrays, Lists and Sets
+ * rely on you NOT using XStream.addImplicitCollection(..).
+ *
+ * @author Paul Hammant
+ * @author Jörg Schaible
+ * @since 1.3.1
+ */
+public class JsonWriter extends AbstractJsonWriter {
+
+ protected final QuickWriter writer;
+ protected final Format format;
+ private int depth;
+ private boolean newLineProposed;
+
+ /**
+ * @deprecated As of 1.4 use {@link JsonWriter#JsonWriter(Writer, Format) instead}
+ */
+ public JsonWriter(Writer writer, char[] lineIndenter, String newLine) {
+ this(writer, 0, new Format(
+ lineIndenter, newLine.toCharArray(), Format.SPACE_AFTER_LABEL
+ | Format.COMPACT_EMPTY_ELEMENT));
+ }
+
+ /**
+ * @deprecated As of 1.4 use {@link JsonWriter#JsonWriter(Writer, Format) instead}
+ */
+ public JsonWriter(Writer writer, char[] lineIndenter) {
+ this(writer, 0, new Format(lineIndenter, new char[]{'\n'}, Format.SPACE_AFTER_LABEL
+ | Format.COMPACT_EMPTY_ELEMENT));
+ }
+
+ /**
+ * @deprecated As of 1.4 use {@link JsonWriter#JsonWriter(Writer, Format) instead}
+ */
+ public JsonWriter(Writer writer, String lineIndenter, String newLine) {
+ this(writer, 0, new Format(
+ lineIndenter.toCharArray(), newLine.toCharArray(), Format.SPACE_AFTER_LABEL
+ | Format.COMPACT_EMPTY_ELEMENT));
+ }
+
+ /**
+ * @deprecated As of 1.4 use {@link JsonWriter#JsonWriter(Writer, Format) instead}
+ */
+ public JsonWriter(Writer writer, String lineIndenter) {
+ this(writer, 0, new Format(
+ lineIndenter.toCharArray(), new char[]{'\n'}, Format.SPACE_AFTER_LABEL
+ | Format.COMPACT_EMPTY_ELEMENT));
+ }
+
+ public JsonWriter(Writer writer) {
+ this(writer, 0, new Format(
+ new char[]{' ', ' '}, new char[]{'\n'}, Format.SPACE_AFTER_LABEL
+ | Format.COMPACT_EMPTY_ELEMENT));
+ }
+
+ /**
+ * @since 1.3.1
+ * @deprecated As of 1.4 use {@link JsonWriter#JsonWriter(Writer, int, Format) instead}
+ */
+ public JsonWriter(Writer writer, char[] lineIndenter, String newLine, int mode) {
+ this(writer, mode, new Format(
+ lineIndenter, newLine.toCharArray(), Format.SPACE_AFTER_LABEL
+ | Format.COMPACT_EMPTY_ELEMENT));
+ }
+
+ /**
+ * Create a JsonWriter where the writer mode can be chosen.
+ *
+ * @param writer the {@link Writer} where the JSON is written to
+ * @param mode the JsonWriter mode
+ * @since 1.3.1
+ * @see #JsonWriter(Writer, int, Format)
+ */
+ public JsonWriter(Writer writer, int mode) {
+ this(writer, mode, new Format());
+ }
+
+ /**
+ * Create a JsonWriter where the format is provided.
+ *
+ * @param writer the {@link Writer} where the JSON is written to
+ * @param format the JSON format definition
+ * @since 1.4
+ * @see #JsonWriter(Writer, int, Format)
+ */
+ public JsonWriter(Writer writer, Format format) {
+ this(writer, 0, format);
+ }
+
+ /**
+ * Create a JsonWriter where the writer mode can be chosen and the format definition is
+ * provided.
+ *
+ * Following constants can be used as bit mask for the mode:
+ *
+ *
+ *
{@link #DROP_ROOT_MODE}: drop the root node
+ *
{@link #STRICT_MODE}: do not throw {@link ConversionException}, if writer should
+ * generate invalid JSON
+ *
{@link #EXPLICIT_MODE}: ensure that all available data is explicitly written even if
+ * addition objects must be added
+ *
+ *
+ * @param writer the {@link Writer} where the JSON is written to
+ * @param mode the JsonWriter mode
+ * @param format the JSON format definition
+ * @since 1.4
+ */
+ public JsonWriter(Writer writer, int mode, Format format) {
+ this(writer, mode, format, 1024);
+ }
+
+ /**
+ * Create a JsonWriter.
+ *
+ * @param writer the {@link Writer} where the JSON is written to
+ * @param mode the JsonWriter mode
+ * @param format the JSON format definition
+ * @param bufferSize the buffer size of the internally used QuickWriter
+ * @see JsonWriter#JsonWriter(Writer, int, Format)
+ * @since 1.4
+ */
+ public JsonWriter(Writer writer, int mode, Format format, int bufferSize) {
+ super(mode, format.getNameCoder());
+ this.writer = new QuickWriter(writer, bufferSize);
+ this.format = format;
+ depth = (mode & DROP_ROOT_MODE) == 0 ? -1 : 0;
+ }
+
+ public void flush() {
+ writer.flush();
+ }
+
+ public void close() {
+ writer.close();
+ }
+
+ public HierarchicalStreamWriter underlyingWriter() {
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected void startObject() {
+ if (newLineProposed) {
+ writeNewLine();
+ }
+ writer.write('{');
+ startNewLine();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected void addLabel(String name) {
+ if (newLineProposed) {
+ writeNewLine();
+ }
+ writer.write('"');
+ writeText(name);
+ writer.write("\":");
+ if ((format.mode() & Format.SPACE_AFTER_LABEL) != 0) {
+ writer.write(' ');
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected void addValue(String value, Type type) {
+ if (newLineProposed) {
+ writeNewLine();
+ }
+ if (type == Type.STRING) {
+ writer.write('"');
+ }
+ writeText(value);
+ if (type == Type.STRING) {
+ writer.write('"');
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected void startArray() {
+ if (newLineProposed) {
+ writeNewLine();
+ }
+ writer.write("[");
+ startNewLine();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected void nextElement() {
+ writer.write(",");
+ writeNewLine();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected void endArray() {
+ endNewLine();
+ writer.write("]");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected void endObject() {
+ endNewLine();
+ writer.write("}");
+ }
+
+ private void startNewLine() {
+ if ( ++depth > 0) {
+ newLineProposed = true;
+ }
+ }
+
+ private void endNewLine() {
+ if (depth-- > 0) {
+ if (((format.mode() & Format.COMPACT_EMPTY_ELEMENT) != 0) && newLineProposed) {
+ newLineProposed = false;
+ } else {
+ writeNewLine();
+ }
+ }
+ }
+
+ private void writeNewLine() {
+ int depth = this.depth;
+ writer.write(format.getNewLine());
+ while (depth-- > 0) {
+ writer.write(format.getLineIndenter());
+ }
+ newLineProposed = false;
+ }
+
+ private void writeText(String text) {
+ int length = text.length();
+ for (int i = 0; i < length; i++ ) {
+ char c = text.charAt(i);
+ switch (c) {
+ case '"':
+ this.writer.write("\\\"");
+ break;
+ case '\\':
+ this.writer.write("\\\\");
+ break;
+ // turn this off - it is no CTRL char anyway
+ // case '/':
+ // this.writer.write("\\/");
+ // break;
+ case '\b':
+ this.writer.write("\\b");
+ break;
+ case '\f':
+ this.writer.write("\\f");
+ break;
+ case '\n':
+ this.writer.write("\\n");
+ break;
+ case '\r':
+ this.writer.write("\\r");
+ break;
+ case '\t':
+ this.writer.write("\\t");
+ break;
+ default:
+ if (c > 0x1f) {
+ this.writer.write(c);
+ } else {
+ this.writer.write("\\u");
+ String hex = "000" + Integer.toHexString(c);
+ this.writer.write(hex.substring(hex.length() - 4));
+ }
+ }
+ }
+ }
+
+ /**
+ * Format definition for JSON.
+ *
+ * @author Jörg Schaible
+ * @since 1.4
+ */
+ public static class Format {
+
+ public static int SPACE_AFTER_LABEL = 1;
+ public static int COMPACT_EMPTY_ELEMENT = 2;
+
+ private char[] lineIndenter;
+ private char[] newLine;
+ private final int mode;
+ private final NameCoder nameCoder;
+
+ /**
+ * Create a new default Formatter. The formatter uses two spaces, normal line feed
+ * character, adds a space after the label and will try to compact the output.
+ *
+ * @since 1.4.2
+ */
+ public Format() {
+ this(new char[]{' ', ' '}, new char[]{'\n'}, Format.SPACE_AFTER_LABEL
+ | Format.COMPACT_EMPTY_ELEMENT);
+ }
+
+
+ /**
+ * Create a new Formatter.
+ *
+ * @param lineIndenter the characters used for indenting the line
+ * @param newLine the characters used to create a new line
+ * @param mode the flags for the format modes
+ * @since 1.4
+ */
+ public Format(char[] lineIndenter, char[] newLine, int mode) {
+ this(lineIndenter, newLine, mode, new NoNameCoder());
+ }
+
+ /**
+ * Create a new Formatter.
+ *
+ * @param lineIndenter the characters used for indenting the line
+ * @param newLine the characters used to create a new line
+ * @param mode the flags for the format modes
+ * @param nameCoder the name encoder and decoder
+ * @since 1.4.2
+ */
+ public Format(char[] lineIndenter, char[] newLine, int mode, NameCoder nameCoder) {
+ this.lineIndenter = lineIndenter;
+ this.newLine = newLine;
+ this.mode = mode;
+ this.nameCoder = nameCoder;
+ }
+
+ /**
+ * Retrieve the lineIndenter.
+ *
+ * @return the lineIndenter
+ * @since 1.4
+ */
+ public char[] getLineIndenter() {
+ return this.lineIndenter;
+ }
+
+ /**
+ * Retrieve the newLine.
+ *
+ * @return the newLine
+ * @since 1.4
+ */
+ public char[] getNewLine() {
+ return this.newLine;
+ }
+
+ /**
+ * Retrieve the mode flags of the formatter.
+ *
+ * @return the mode
+ * @since 1.4
+ */
+ public int mode() {
+ return this.mode;
+ }
+
+
+ /**
+ * Retrieve the NameCoder.
+ *
+ * @return the name coder
+ * @since 1.4.2
+ */
+ public NameCoder getNameCoder() {
+ return nameCoder;
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/naming/NameCoder.java b/xstream/src/java/com/thoughtworks/xstream/io/naming/NameCoder.java
new file mode 100644
index 0000000..c383bb0
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/naming/NameCoder.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2009, 2011 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 14. August 2009 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io.naming;
+
+/**
+ * Coder between names in the object graph and names of a target format.
+ *
+ * The names form the object graph are typically names generated from Java identifiers (Java
+ * types or field members), but some names may also contain a minus sign. However, the original
+ * name might have already been aliased, so a wider range of characters can occur.
+ *
+ *
+ * The target names should satisfy the syntax of the target format. Transforming Java objects to
+ * XML this affects names that contain or even start with a dollar sign. Such names violate the
+ * XML specification.
+ *
+ *
+ * By default all names from the object graph are used as node names in the target format.
+ * Meta-data that is necessary to unmarshal the object again is typically written as attribute.
+ * Since such attributes might be represented differently in the target format, the NameCoder
+ * distinguishes between the names used for meta-data elements and the ones for the object data.
+ * The names in the target format might even have to follow a different syntax. Remember, that
+ * XStream can be easily configured to write also object data as attributes.
+ *
+ *
+ * Note that the instance of a NameCoder should be either thread-safe or implement {@link Cloneable}.
+ *
+ *
+ * @author Jörg Schaible
+ * @since 1.4
+ */
+public interface NameCoder {
+ /**
+ * Encode an object name for a node in the target format.
+ *
+ * @param name the name of the object data
+ * @return the node name in the target format
+ * @since 1.4
+ */
+ String encodeNode(String name);
+
+ /**
+ * Encode a meta-data name for an attribute in the target format.
+ *
+ * @param name the name of the meta-data
+ * @return the attribute name in the target format
+ * @since 1.4
+ */
+ String encodeAttribute(String name);
+
+ /**
+ * Decode a node name to an object name.
+ *
+ * @param nodeName the name of the node
+ * @return the name of the object
+ * @since 1.4
+ */
+ String decodeNode(String nodeName);
+
+ /**
+ * Decode an attribute name to an object name.
+ *
+ * @param attributeName the name of the attribute
+ * @return the name of the meta-data
+ * @since 1.4
+ */
+ String decodeAttribute(String attributeName);
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/naming/NameCoderWrapper.java b/xstream/src/java/com/thoughtworks/xstream/io/naming/NameCoderWrapper.java
new file mode 100644
index 0000000..d18f3e8
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/naming/NameCoderWrapper.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2009, 2011 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 15. August 2009 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io.naming;
+
+/**
+ * A wrapper for another NameCoder.
+ *
+ * @author Jörg Schaible
+ * @since 1.4
+ */
+public class NameCoderWrapper implements NameCoder {
+
+ private final NameCoder wrapped;
+
+ /**
+ * Construct a new wrapper for a NameCoder.
+ *
+ * @param inner the wrapped NameCoder
+ * @since 1.4
+ */
+ public NameCoderWrapper(NameCoder inner) {
+ this.wrapped = inner;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String decodeAttribute(String attributeName) {
+ return wrapped.decodeAttribute(attributeName);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String decodeNode(String nodeName) {
+ return wrapped.decodeNode(nodeName);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String encodeAttribute(String name) {
+ return wrapped.encodeAttribute(name);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String encodeNode(String name) {
+ return wrapped.encodeNode(name);
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/naming/NoNameCoder.java b/xstream/src/java/com/thoughtworks/xstream/io/naming/NoNameCoder.java
new file mode 100644
index 0000000..d240f27
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/naming/NoNameCoder.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2009, 2011 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 15. August 2009 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io.naming;
+
+/**
+ * A NameCoder that does nothing.
+ *
+ * The usage of this implementation implies that the names used for the objects can also be used
+ * in the target format without any change. This applies also for XML if the object graph
+ * contains no object that is an instance of an inner class type or is in the default package.
+ *
+ *
+ * @author Jörg Schaiblea
+ * @since 1.4
+ */
+public class NoNameCoder implements NameCoder {
+
+ /**
+ * {@inheritDoc}
+ */
+ public String decodeAttribute(String attributeName) {
+ return attributeName;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String decodeNode(String nodeName) {
+ return nodeName;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String encodeAttribute(String name) {
+ return name;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String encodeNode(String name) {
+ return name;
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/naming/StaticNameCoder.java b/xstream/src/java/com/thoughtworks/xstream/io/naming/StaticNameCoder.java
new file mode 100644
index 0000000..8531d4a
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/naming/StaticNameCoder.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2009, 2011 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 15. August 2009 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io.naming;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+
+/**
+ * A NameCoder that encodes and decodes names based on a map.
+ *
+ * The provided map should contain a mapping between the name of the Java type or field to the
+ * proper element in the target format. If a name cannot be found in the map, it is assumed not
+ * to be mapped at all. Note that the values of the map should be unique also, otherwise the
+ * decoding will produce wrong results.
+ *
+ *
+ * @author Jörg Schaible
+ * @since 1.4
+ */
+public class StaticNameCoder implements NameCoder {
+
+ private final Map java2Node;
+ private final Map java2Attribute;
+
+ private transient Map node2Java;
+ private transient Map attribute2Java;
+
+ /**
+ * Construct a StaticNameCoder.
+ *
+ * @param java2Node mapping of Java names to nodes
+ * @param java2Attribute mapping of Java names to attributes
+ * @since 1.4
+ */
+ public StaticNameCoder(Map java2Node, Map java2Attribute) {
+ this.java2Node = new HashMap(java2Node);
+ if (java2Node == java2Attribute || java2Attribute == null) {
+ this.java2Attribute = this.java2Node;
+ } else {
+ this.java2Attribute = new HashMap(java2Attribute);
+ }
+ readResolve();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String decodeAttribute(String attributeName) {
+ String name = (String)attribute2Java.get(attributeName);
+ return name == null ? attributeName : name;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String decodeNode(String nodeName) {
+ String name = (String)node2Java.get(nodeName);
+ return name == null ? nodeName : name;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String encodeAttribute(String name) {
+ String friendlyName = (String)java2Attribute.get(name);
+ return friendlyName == null ? name : friendlyName;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String encodeNode(String name) {
+ String friendlyName = (String)java2Node.get(name);
+ return friendlyName == null ? name : friendlyName;
+ }
+
+ private Object readResolve() {
+ node2Java = invertMap(java2Node);
+ if (java2Node == java2Attribute) {
+ attribute2Java = node2Java;
+ } else {
+ attribute2Java = invertMap(java2Attribute);
+ }
+ return this;
+ }
+
+ private Map invertMap(Map map) {
+ Map inverseMap = new HashMap(map.size());
+ for (final Iterator iter = map.entrySet().iterator(); iter.hasNext();) {
+ final Map.Entry entry = (Map.Entry)iter.next();
+ inverseMap.put((String)entry.getValue(), (String)entry.getKey());
+ }
+ return inverseMap;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/path/Path.java b/xstream/src/java/com/thoughtworks/xstream/io/path/Path.java
new file mode 100644
index 0000000..b743939
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/path/Path.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2013 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 02. September 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.path;
+
+import com.thoughtworks.xstream.core.util.FastStack;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a path to a single node in the tree.
+ *
+ *
Two absolute paths can also be compared to calculate the relative path between them.
+ * A relative path can be applied to an absolute path to calculate another absolute path.
+ *
+ *
Note that the paths are normally XPath compliant, so can be read by other XPath engines.
+ * However, {@link #toString()} will select a node list while {@link #explicit()} will always select
+ * an individual node. If the return type of the XPath evaluation is a node, the result will be the same,
+ * because XPath will then use the first element of the list. The following are examples of path
+ * expressions that the Path object supports:
+ *
+ *
Note that the implementation does not take care if the paths are XPath compliant, it simply
+ * manages the values between the path separator. However, it normalizes the path if a path element
+ * ends with a selector for the first element (i.e. "[1]"). Those will be handled transparent i.e. two Paths
+ * are treated equal if one was created with path elements containing this selector and the other one
+ * without.
+ *
+ *
The following are examples of path expressions that the Path object supports:
+ *
+ *
/
+ *
/some/node
+ *
/a/b/c/b/a
+ *
/a/b[1]/c[1]/b[1]/a[1]
+ *
/some[3]/node[2]/a
+ *
../../../another[3]/node
+ *
+ *
+ *
Example
+ *
+ *
+ * Path a = new Path("/html/body/div[1]/table[2]/tr[3]/td/div");
+ * Path b = new Path("/html/body/div/table[2]/tr[6]/td/form");
+ *
+ * Path relativePath = a.relativeTo(b); // produces: "../../../tr[6]/td/form"
+ * Path c = a.apply(relativePath); // same as Path b.
+ *
+ *
+ * @see PathTracker
+ *
+ * @author Joe Walnes
+ */
+public class Path {
+
+ private final String[] chunks;
+ private transient String pathAsString;
+ private transient String pathExplicit;
+ private static final Path DOT = new Path(new String[] {"."});
+
+ public Path(String pathAsString) {
+ // String.split() too slow. StringTokenizer too crappy.
+ List result = new ArrayList();
+ int currentIndex = 0;
+ int nextSeparator;
+ this.pathAsString = pathAsString;
+ while ((nextSeparator = pathAsString.indexOf('/', currentIndex)) != -1) {
+ // normalize explicit paths
+ result.add(normalize(pathAsString, currentIndex, nextSeparator));
+ currentIndex = nextSeparator + 1;
+ }
+ result.add(normalize(pathAsString,currentIndex,pathAsString.length()));
+ String[] arr = new String[result.size()];
+ result.toArray(arr);
+ chunks = arr;
+ }
+
+ private String normalize(String s, int start, int end) {
+ if (end - start > 3
+ && s.charAt(end-3) == '['
+ && s.charAt(end-2) == '1'
+ && s.charAt(end-1) == ']') {
+ this.pathAsString = null;
+ return s.substring(start, end-3);
+ } else {
+ return s.substring(start, end);
+ }
+
+ }
+
+ public Path(String[] chunks) {
+ this.chunks = chunks;
+ }
+
+ public String toString() {
+ if (pathAsString == null) {
+ StringBuffer buffer = new StringBuffer();
+ for (int i = 0; i < chunks.length; i++) {
+ if (i > 0) buffer.append('/');
+ buffer.append(chunks[i]);
+ }
+ pathAsString = buffer.toString();
+ }
+ return pathAsString;
+ }
+
+ public String explicit() {
+ if (pathExplicit == null) {
+ StringBuffer buffer = new StringBuffer();
+ for (int i = 0; i < chunks.length; i++) {
+ if (i > 0) buffer.append('/');
+ String chunk = chunks[i];
+ buffer.append(chunk);
+ int length = chunk.length();
+ if (length > 0) {
+ char c = chunk.charAt(length-1);
+ if (c != ']' && c != '.') {
+ buffer.append("[1]");
+ }
+ }
+ }
+ pathExplicit = buffer.toString();
+ }
+ return pathExplicit;
+ }
+
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Path)) return false;
+
+ final Path other = (Path) o;
+ if (chunks.length != other.chunks.length) return false;
+ for (int i = 0; i < chunks.length; i++) {
+ if (!chunks[i].equals(other.chunks[i])) return false;
+ }
+
+ return true;
+ }
+
+ public int hashCode() {
+ int result = 543645643;
+ for (int i = 0; i < chunks.length; i++) {
+ result = 29 * result + chunks[i].hashCode();
+ }
+ return result;
+ }
+
+ public Path relativeTo(Path that) {
+ int depthOfPathDivergence = depthOfPathDivergence(chunks, that.chunks);
+ String[] result = new String[chunks.length + that.chunks.length - 2 * depthOfPathDivergence];
+ int count = 0;
+
+ for (int i = depthOfPathDivergence; i < chunks.length; i++) {
+ result[count++] = "..";
+ }
+ for (int j = depthOfPathDivergence; j < that.chunks.length; j++) {
+ result[count++] = that.chunks[j];
+ }
+
+ if (count == 0) {
+ return DOT;
+ } else {
+ return new Path(result);
+ }
+ }
+
+ private int depthOfPathDivergence(String[] path1, String[] path2) {
+ int minLength = Math.min(path1.length, path2.length);
+ for (int i = 0; i < minLength; i++) {
+ if (!path1[i].equals(path2[i])) {
+ return i;
+ }
+ }
+ return minLength;
+ }
+
+ public Path apply(Path relativePath) {
+ FastStack absoluteStack = new FastStack(16);
+
+ for (int i = 0; i < chunks.length; i++) {
+ absoluteStack.push(chunks[i]);
+ }
+
+ for (int i = 0; i < relativePath.chunks.length; i++) {
+ String relativeChunk = relativePath.chunks[i];
+ if (relativeChunk.equals("..")) {
+ absoluteStack.pop();
+ } else if (!relativeChunk.equals(".")) {
+ absoluteStack.push(relativeChunk);
+ }
+ }
+
+ String[] result = new String[absoluteStack.size()];
+ for (int i = 0; i < result.length; i++) {
+ result[i] = (String) absoluteStack.get(i);
+ }
+
+ return new Path(result);
+ }
+
+ public boolean isAncestor(Path child) {
+ if (child == null || child.chunks.length < chunks.length) {
+ return false;
+ }
+ for (int i = 0; i < chunks.length; i++) {
+ if (!chunks[i].equals(child.chunks[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/path/PathTracker.java b/xstream/src/java/com/thoughtworks/xstream/io/path/PathTracker.java
new file mode 100644
index 0000000..76b25cd
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/path/PathTracker.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2011 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 07. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.path;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Maintains the current {@link Path} as a stream is moved through.
+ *
+ *
Can be linked to a {@link com.thoughtworks.xstream.io.HierarchicalStreamWriter} or
+ * {@link com.thoughtworks.xstream.io.HierarchicalStreamReader} by wrapping them with a
+ * {@link PathTrackingWriter} or {@link PathTrackingReader}.
+ *
+ * @see Path
+ * @see PathTrackingReader
+ * @see PathTrackingWriter
+ *
+ * @author Joe Walnes
+ */
+public class PathTracker {
+
+ private int pointer;
+ private int capacity;
+ private String[] pathStack;
+ private Map[] indexMapStack;
+
+ private Path currentPath;
+
+ public PathTracker() {
+ this(16);
+ }
+
+ /**
+ * @param initialCapacity Size of the initial stack of nodes (one level per depth in the tree). Note that this is
+ * only for optimizations - the stack will resize itself if it exceeds its capacity. If in doubt,
+ * use the other constructor.
+ */
+ public PathTracker(int initialCapacity) {
+ this.capacity = Math.max(1, initialCapacity);
+ pathStack = new String[capacity];
+ indexMapStack = new Map[capacity];
+ }
+
+ /**
+ * Notify the tracker that the stream has moved into a new element.
+ *
+ * @param name Name of the element
+ */
+ public void pushElement(String name) {
+ if (pointer + 1 >= capacity) {
+ resizeStacks(capacity * 2);
+ }
+ pathStack[pointer] = name;
+ Map indexMap = indexMapStack[pointer];
+ if (indexMap == null) {
+ indexMap = new HashMap();
+ indexMapStack[pointer] = indexMap;
+ }
+ if (indexMap.containsKey(name)) {
+ indexMap.put(name, new Integer(((Integer) indexMap.get(name)).intValue() + 1));
+ } else {
+ indexMap.put(name, new Integer(1));
+ }
+ pointer++;
+ currentPath = null;
+ }
+
+ /**
+ * Notify the tracker that the stream has moved out of an element.
+ */
+ public void popElement() {
+ indexMapStack[pointer] = null;
+ pathStack[pointer] = null;
+ currentPath = null;
+ pointer--;
+ }
+
+ /**
+ * Get the last path element from the stack.
+ *
+ * @return the name of the path element
+ * @since 1.4.2
+ */
+ public String peekElement() {
+ return peekElement(0);
+ }
+
+ /**
+ * Get a path element from the stack.
+ *
+ * @param i path index
+ * @return the name of the path element
+ * @since 1.4.2
+ * @throws ArrayIndexOutOfBoundsException if the index is >= 0 or <= -depth()
+ */
+ public String peekElement(int i) {
+ if (i < -pointer || i > 0) {
+ throw new ArrayIndexOutOfBoundsException(i);
+ }
+ int idx = pointer + i - 1;
+ final String name;
+ Integer integer = ((Integer) indexMapStack[idx].get(pathStack[idx]));
+ int index = integer.intValue();
+ if (index > 1) {
+ StringBuffer chunk = new StringBuffer(pathStack[idx].length() + 6);
+ chunk.append(pathStack[idx]).append('[').append(index).append(']');
+ name = chunk.toString();
+ } else {
+ name = pathStack[idx];
+ }
+ return name;
+ }
+
+ /**
+ * Get the depth of the stack.
+ *
+ * @return the stack depth
+ * @since 1.4.2
+ */
+ public int depth() {
+ return pointer;
+ }
+
+ private void resizeStacks(int newCapacity) {
+ String[] newPathStack = new String[newCapacity];
+ Map[] newIndexMapStack = new Map[newCapacity];
+ int min = Math.min(capacity, newCapacity);
+ System.arraycopy(pathStack, 0, newPathStack, 0, min);
+ System.arraycopy(indexMapStack, 0, newIndexMapStack, 0, min);
+ pathStack = newPathStack;
+ indexMapStack = newIndexMapStack;
+ capacity = newCapacity;
+ }
+
+ /**
+ * Current Path in stream.
+ */
+ public Path getPath() {
+ if (currentPath == null) {
+ String[] chunks = new String[pointer + 1];
+ chunks[0] = "";
+ for (int i = -pointer; ++i <= 0; ) {
+ final String name = peekElement(i);
+ chunks[i + pointer] = name;
+ }
+ currentPath = new Path(chunks);
+ }
+ return currentPath;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/path/PathTrackingReader.java b/xstream/src/java/com/thoughtworks/xstream/io/path/PathTrackingReader.java
new file mode 100644
index 0000000..b990a0e
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/path/PathTrackingReader.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 03. April 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.path;
+
+import com.thoughtworks.xstream.converters.ErrorWriter;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.ReaderWrapper;
+
+/**
+ * Wrapper for HierarchicalStreamReader that tracks the path (a subset of XPath) of the current node that is being read.
+ *
+ * @see PathTracker
+ * @see Path
+ *
+ * @author Joe Walnes
+ */
+public class PathTrackingReader extends ReaderWrapper {
+
+ private final PathTracker pathTracker;
+
+ public PathTrackingReader(HierarchicalStreamReader reader, PathTracker pathTracker) {
+ super(reader);
+ this.pathTracker = pathTracker;
+ pathTracker.pushElement(getNodeName());
+ }
+
+ public void moveDown() {
+ super.moveDown();
+ pathTracker.pushElement(getNodeName());
+ }
+
+ public void moveUp() {
+ super.moveUp();
+ pathTracker.popElement();
+ }
+
+ public void appendErrors(ErrorWriter errorWriter) {
+ errorWriter.add("path", pathTracker.getPath().toString());
+ super.appendErrors(errorWriter);
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/path/PathTrackingWriter.java b/xstream/src/java/com/thoughtworks/xstream/io/path/PathTrackingWriter.java
new file mode 100644
index 0000000..2c3a716
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/path/PathTrackingWriter.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2011 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 07. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.path;
+
+import com.thoughtworks.xstream.io.AbstractWriter;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.io.WriterWrapper;
+
+/**
+ * Wrapper for HierarchicalStreamWriter that tracks the path (a subset of XPath) of the current node that is being written.
+ *
+ * @see PathTracker
+ * @see Path
+ *
+ * @author Joe Walnes
+ */
+public class PathTrackingWriter extends WriterWrapper {
+
+ private final PathTracker pathTracker;
+ private final boolean isNameEncoding;
+
+ public PathTrackingWriter(HierarchicalStreamWriter writer, PathTracker pathTracker) {
+ super(writer);
+ this.isNameEncoding = writer.underlyingWriter() instanceof AbstractWriter;
+ this.pathTracker = pathTracker;
+ }
+
+ public void startNode(String name) {
+ pathTracker.pushElement(isNameEncoding ? ((AbstractWriter)wrapped.underlyingWriter()).encodeNode(name) : name);
+ super.startNode(name);
+ }
+
+ public void startNode(String name, Class clazz) {
+ pathTracker.pushElement(isNameEncoding ? ((AbstractWriter)wrapped.underlyingWriter()).encodeNode(name) : name);
+ super.startNode(name, clazz);
+ }
+
+ public void endNode() {
+ super.endNode();
+ pathTracker.popElement();
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/path/package.html b/xstream/src/java/com/thoughtworks/xstream/io/path/package.html
new file mode 100644
index 0000000..86d4a4b
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/path/package.html
@@ -0,0 +1,43 @@
+
+
+
+
+
+
Library for tracking paths of nodes in documents using a subset of XPath. This subset of XPath is just enough
+ for XStream to be able to use XPath expressions to represent references across the object graph, while still remaining
+ very quick.
+
+
The Path class represents a path to a single node in the tree.
+ Two absolute paths can also be compared to calculate the relative path between them.
+ A relative path can be applied to an absolute path to calculate another absolute path.
Note that the paths produced are XPath compliant, so can be read by other XPath engines. The following are examples of path
+ expressions that the Path object supports:
+
+
+
/
+
/some/node
+
/a/b/c/b/a
+
/some[3]/node[2]/a
+
../../../another[3]/node
+
+
+
+
+
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/AbstractDocumentReader.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/AbstractDocumentReader.java
new file mode 100644
index 0000000..4cb562f
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/AbstractDocumentReader.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2011 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 24. April 2005 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import java.util.Iterator;
+
+import com.thoughtworks.xstream.converters.ErrorWriter;
+import com.thoughtworks.xstream.core.util.FastStack;
+import com.thoughtworks.xstream.io.AttributeNameIterator;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+public abstract class AbstractDocumentReader extends AbstractXmlReader implements DocumentReader {
+
+ private FastStack pointers = new FastStack(16);
+ private Object current;
+
+ protected AbstractDocumentReader(Object rootElement) {
+ this(rootElement, new XmlFriendlyNameCoder());
+ }
+
+ /**
+ * @since 1.4
+ */
+ protected AbstractDocumentReader(Object rootElement, NameCoder nameCoder) {
+ super(nameCoder);
+ this.current = rootElement;
+ pointers.push(new Pointer());
+ reassignCurrentElement(current);
+ }
+
+ /**
+ * @since 1.2
+ * @deprecated As of 1.4, use {@link AbstractDocumentReader#AbstractDocumentReader(Object, NameCoder)} instead.
+ */
+ protected AbstractDocumentReader(Object rootElement, XmlFriendlyReplacer replacer) {
+ this(rootElement, (NameCoder)replacer);
+ }
+
+ protected abstract void reassignCurrentElement(Object current);
+ protected abstract Object getParent();
+ protected abstract Object getChild(int index);
+ protected abstract int getChildCount();
+
+ private static class Pointer {
+ public int v;
+ }
+
+ public boolean hasMoreChildren() {
+ Pointer pointer = (Pointer) pointers.peek();
+
+ if (pointer.v < getChildCount()) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public void moveUp() {
+ current = getParent();
+ pointers.popSilently();
+ reassignCurrentElement(current);
+ }
+
+ public void moveDown() {
+ Pointer pointer = (Pointer) pointers.peek();
+ pointers.push(new Pointer());
+
+ current = getChild(pointer.v);
+
+ pointer.v++;
+ reassignCurrentElement(current);
+ }
+
+ public Iterator getAttributeNames() {
+ return new AttributeNameIterator(this);
+ }
+
+ public void appendErrors(ErrorWriter errorWriter) {
+ }
+
+ public Object getCurrent() {
+ return this.current;
+ }
+
+ public void close() {
+ // don't need to do anything
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/AbstractDocumentWriter.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/AbstractDocumentWriter.java
new file mode 100644
index 0000000..f13f56a
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/AbstractDocumentWriter.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2006, 2007, 2009, 2011 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 18. October 2007 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.core.util.FastStack;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * A generic {@link com.thoughtworks.xstream.io.HierarchicalStreamWriter} for DOM writer
+ * implementations. The implementation manages a list of top level DOM nodes. Every time the
+ * last node is closed on the node stack, the next started node is added to the list. This list
+ * can be retrieved using the {@link DocumentWriter#getTopLevelNodes()} method.
+ *
+ * @author Laurent Bihanic
+ * @author Jörg Schaible
+ * @since 1.2.1
+ */
+public abstract class AbstractDocumentWriter extends AbstractXmlWriter implements
+ DocumentWriter {
+
+ private final List result = new ArrayList();
+ private final FastStack nodeStack = new FastStack(16);
+
+ /**
+ * Constructs an AbstractDocumentWriter.
+ *
+ * @param container the top level container for the nodes to create (may be
+ * null)
+ * @param nameCoder the object that creates XML-friendly names
+ * @since 1.4
+ */
+ public AbstractDocumentWriter(final Object container, final NameCoder nameCoder) {
+ super(nameCoder);
+ if (container != null) {
+ nodeStack.push(container);
+ result.add(container);
+ }
+ }
+
+ /**
+ * Constructs an AbstractDocumentWriter.
+ *
+ * @param container the top level container for the nodes to create (may be
+ * null)
+ * @param replacer the object that creates XML-friendly names
+ * @since 1.2.1
+ * @deprecated As of 1.4 use
+ * {@link AbstractDocumentWriter#AbstractDocumentWriter(Object, NameCoder)}
+ * instead.
+ */
+ public AbstractDocumentWriter(final Object container, final XmlFriendlyReplacer replacer) {
+ this(container, (NameCoder)replacer);
+ }
+
+ public final void startNode(final String name) {
+ final Object node = createNode(name);
+ nodeStack.push(node);
+ }
+
+ /**
+ * Create a node. The provided node name is not yet XML friendly. If {@link #getCurrent()}
+ * returns null the node is a top level node.
+ *
+ * @param name the node name
+ * @return the new node
+ * @since 1.2.1
+ */
+ protected abstract Object createNode(String name);
+
+ public final void endNode() {
+ endNodeInternally();
+ final Object node = nodeStack.pop();
+ if (nodeStack.size() == 0) {
+ result.add(node);
+ }
+ }
+
+ /**
+ * Called when a node ends. Hook for derived implementations.
+ *
+ * @since 1.2.1
+ */
+ public void endNodeInternally() {
+ }
+
+ /**
+ * @since 1.2.1
+ */
+ protected final Object getCurrent() {
+ return nodeStack.peek();
+ }
+
+ public List getTopLevelNodes() {
+ return result;
+ }
+
+ public void flush() {
+ // don't need to do anything
+ }
+
+ public void close() {
+ // don't need to do anything
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/AbstractPullReader.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/AbstractPullReader.java
new file mode 100644
index 0000000..be2d247
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/AbstractPullReader.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2010, 2011 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 24. April 2005 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import java.util.Iterator;
+
+import com.thoughtworks.xstream.core.util.FastStack;
+import com.thoughtworks.xstream.io.AttributeNameIterator;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+/**
+ * Base class that contains common functionality across HierarchicalStreamReader implementations
+ * that need to read from a pull parser.
+ *
+ * @author Joe Walnes
+ * @author James Strachan
+ */
+public abstract class AbstractPullReader extends AbstractXmlReader {
+
+ protected static final int START_NODE = 1;
+ protected static final int END_NODE = 2;
+ protected static final int TEXT = 3;
+ protected static final int COMMENT = 4;
+ protected static final int OTHER = 0;
+
+ private final FastStack elementStack = new FastStack(16);
+ private final FastStack pool = new FastStack(16);
+
+ private final FastStack lookahead = new FastStack(4);
+ private final FastStack lookback = new FastStack(4);
+ private boolean marked;
+
+ private static class Event {
+ int type;
+ String value;
+ }
+
+ /**
+ * @since 1.4
+ */
+ protected AbstractPullReader(NameCoder nameCoder) {
+ super(nameCoder);
+ }
+
+ /**
+ * @since 1.2
+ * @deprecated As of 1.4 use {@link AbstractPullReader#AbstractPullReader(NameCoder)} instead
+ */
+ protected AbstractPullReader(XmlFriendlyReplacer replacer) {
+ this((NameCoder)replacer);
+ }
+
+
+ /**
+ * Pull the next event from the stream.
+ *
+ *
This MUST return {@link #START_NODE}, {@link #END_NODE}, {@link #TEXT}, {@link #COMMENT},
+ * {@link #OTHER} or throw {@link com.thoughtworks.xstream.io.StreamException}.
+ *
+ *
The underlying pull parser will most likely return its own event types. These must be
+ * mapped to the appropriate events.
+ */
+ protected abstract int pullNextEvent();
+
+ /**
+ * Pull the name of the current element from the stream.
+ */
+ protected abstract String pullElementName();
+
+ /**
+ * Pull the contents of the current text node from the stream.
+ */
+ protected abstract String pullText();
+
+ public boolean hasMoreChildren() {
+ mark();
+ while (true) {
+ switch (readEvent().type) {
+ case START_NODE:
+ reset();
+ return true;
+ case END_NODE:
+ reset();
+ return false;
+ default:
+ continue;
+ }
+ }
+ }
+
+ public void moveDown() {
+ int currentDepth = elementStack.size();
+ while (elementStack.size() <= currentDepth) {
+ move();
+ if (elementStack.size() < currentDepth) {
+ throw new RuntimeException(); // sanity check
+ }
+ }
+ }
+
+ public void moveUp() {
+ int currentDepth = elementStack.size();
+ while (elementStack.size() >= currentDepth) {
+ move();
+ }
+ }
+
+ private void move() {
+ final Event event = readEvent();
+ pool.push(event);
+ switch (event.type) {
+ case START_NODE:
+ elementStack.push(pullElementName());
+ break;
+ case END_NODE:
+ elementStack.pop();
+ break;
+ }
+ }
+
+ private Event readEvent() {
+ if (marked) {
+ if (lookback.hasStuff()) {
+ return (Event) lookahead.push(lookback.pop());
+ } else {
+ return (Event) lookahead.push(readRealEvent());
+ }
+ } else {
+ if (lookback.hasStuff()) {
+ return (Event) lookback.pop();
+ } else {
+ return readRealEvent();
+ }
+ }
+ }
+
+ private Event readRealEvent() {
+ Event event = pool.hasStuff() ? (Event)pool.pop() : new Event();
+ event.type = pullNextEvent();
+ if (event.type == TEXT) {
+ event.value = pullText();
+ } else if (event.type == START_NODE) {
+ event.value = pullElementName();
+ } else {
+ event.value = null;
+ }
+ return event;
+ }
+
+ public void mark() {
+ marked = true;
+ }
+
+ public void reset() {
+ while(lookahead.hasStuff()) {
+ lookback.push(lookahead.pop());
+ }
+ marked = false;
+ }
+
+ public String getValue() {
+ // we should collapse together any text which
+ // contains comments
+
+ // lets only use a string buffer when we get 2 strings
+ // to avoid copying strings
+ String last = null;
+ StringBuffer buffer = null;
+
+ mark();
+ Event event = readEvent();
+ while (true) {
+ if (event.type == TEXT) {
+ String text = event.value;
+ if (text != null && text.length() > 0) {
+ if (last == null) {
+ last = text;
+ } else {
+ if (buffer == null) {
+ buffer = new StringBuffer(last);
+ }
+ buffer.append(text);
+ }
+ }
+ } else if (event.type != COMMENT) {
+ break;
+ }
+ event = readEvent();
+ }
+ reset();
+ if (buffer != null) {
+ return buffer.toString();
+ } else {
+ return (last == null) ? "" : last;
+ }
+ }
+
+ public Iterator getAttributeNames() {
+ return new AttributeNameIterator(this);
+ }
+
+ public String getNodeName() {
+ return unescapeXmlName((String) elementStack.peek());
+ }
+
+ public String peekNextChild() {
+ mark();
+ while (true) {
+ Event ev = readEvent();
+ switch (ev.type) {
+ case START_NODE:
+ reset();
+ return ev.value;
+ case END_NODE:
+ reset();
+ return null;
+ default:
+ continue;
+ }
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/AbstractXmlDriver.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/AbstractXmlDriver.java
new file mode 100644
index 0000000..8aa15be
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/AbstractXmlDriver.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2011 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 28. May 2005 by Mauro Talevi
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.io.AbstractDriver;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+
+/**
+ * Base class for HierarchicalStreamDrivers to use xml-based HierarchicalStreamReader and
+ * HierarchicalStreamWriter.
+ *
+ * @author Mauro Talevi
+ * @author Jörg Schaible
+ * @since 1.2
+ * @deprecated As of 1.4
+ */
+public abstract class AbstractXmlDriver extends AbstractDriver {
+
+ /**
+ * Creates a AbstractXmlFriendlyDriver with default XmlFriendlyReplacer
+ *
+ * @deprecated As of 1.4
+ */
+ public AbstractXmlDriver() {
+ this(new XmlFriendlyNameCoder());
+ }
+
+ /**
+ * Creates a AbstractXmlFriendlyDriver with default XmlFriendlyReplacer
+ * @since 1.4
+ */
+ public AbstractXmlDriver(NameCoder nameCoder) {
+ super(nameCoder);
+ }
+
+ /**
+ * Creates a AbstractXmlFriendlyDriver with custom XmlFriendlyReplacer
+ *
+ * @param replacer the XmlFriendlyReplacer
+ * @deprecated As of 1.4
+ */
+ public AbstractXmlDriver(XmlFriendlyReplacer replacer) {
+ this((NameCoder)replacer);
+ }
+
+ /**
+ * @deprecated As of 1.4
+ */
+ protected XmlFriendlyReplacer xmlFriendlyReplacer() {
+ NameCoder nameCoder = getNameCoder();
+ return nameCoder instanceof XmlFriendlyReplacer ? (XmlFriendlyReplacer)nameCoder : null;
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/AbstractXmlReader.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/AbstractXmlReader.java
new file mode 100644
index 0000000..40a6c7c
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/AbstractXmlReader.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009, 2011 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 04. June 2006 by Mauro Talevi
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.io.AbstractReader;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+
+/**
+ * Abstract base implementation of HierarchicalStreamReader that provides common functionality
+ * to all XML-based readers.
+ *
+ * @author Mauro Talevi
+ * @author Jörg Schaible
+ * @since 1.2
+ * @deprecated As of 1.4, use {@link AbstractReader} instead.
+ */
+public abstract class AbstractXmlReader extends AbstractReader /* implements XmlFriendlyReader */ {
+
+ protected AbstractXmlReader() {
+ this(new XmlFriendlyNameCoder());
+ }
+
+ /**
+ * @deprecated As of 1.4, use {@link AbstractReader} instead.
+ */
+ protected AbstractXmlReader(XmlFriendlyReplacer replacer) {
+ this((NameCoder)replacer);
+ }
+
+ protected AbstractXmlReader(NameCoder nameCoder) {
+ super(nameCoder);
+ }
+
+ /**
+ * Unescapes XML-friendly name (node or attribute)
+ *
+ * @param name the escaped XML-friendly name
+ * @return An unescaped name with original characters
+ * @deprecated As of 1.4, use {@link #decodeNode(String)} or {@link #decodeAttribute(String)} instead.
+ */
+ public String unescapeXmlName(String name) {
+ return decodeNode(name);
+ }
+
+ /**
+ * Escapes XML-unfriendly name (node or attribute)
+ *
+ * @param name the unescaped XML-unfriendly name
+ * @return An escaped name with original characters
+ * @deprecated As of 1.4, use {@link AbstractReader} instead.
+ */
+ protected String escapeXmlName(String name) {
+ return encodeNode(name);
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/AbstractXmlWriter.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/AbstractXmlWriter.java
new file mode 100644
index 0000000..054ea2e
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/AbstractXmlWriter.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2011 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 04. June 2006 by Mauro Talevi
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.io.AbstractWriter;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+/**
+ * Abstract base implementation of HierarchicalStreamWriter that provides common functionality
+ * to all XML-based writers.
+ *
+ * @author Mauro Talevi
+ * @author Jörg Schaible
+ * @since 1.2
+ * @deprecated As of 1.4 use {@link AbstractWriter} instead
+ */
+public abstract class AbstractXmlWriter extends AbstractWriter implements XmlFriendlyWriter {
+
+ protected AbstractXmlWriter(){
+ this(new XmlFriendlyNameCoder());
+ }
+
+ /**
+ * @deprecated As of 1.4
+ */
+ protected AbstractXmlWriter(XmlFriendlyReplacer replacer) {
+ this((NameCoder)replacer);
+ }
+
+ protected AbstractXmlWriter(NameCoder nameCoder) {
+ super(nameCoder);
+ }
+
+ /**
+ * Escapes XML name (node or attribute) to be XML-friendly
+ *
+ * @param name the unescaped XML name
+ * @return An escaped name with original characters replaced
+ * @deprecated As of 1.4 use {@link #encodeNode(String)} or {@link #encodeAttribute(String)} instead
+ */
+ public String escapeXmlName(String name) {
+ return super.encodeNode(name);
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/AbstractXppDomDriver.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/AbstractXppDomDriver.java
new file mode 100644
index 0000000..ef58dba
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/AbstractXppDomDriver.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2009, 2011 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 03. May 2009 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.core.util.XmlHeaderAwareReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.io.StreamException;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+import com.thoughtworks.xstream.io.xml.xppdom.XppDom;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+
+/**
+ * An abstract base class for a driver using an XPP DOM implementation.
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ * @since 1.4
+ */
+public abstract class AbstractXppDomDriver extends AbstractXmlDriver {
+
+ /**
+ * Construct an AbstractXppDomDriver.
+ *
+ * @param nameCoder the replacer for XML friendly names
+ * @since 1.4
+ */
+ public AbstractXppDomDriver(NameCoder nameCoder) {
+ super(nameCoder);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public HierarchicalStreamReader createReader(Reader in) {
+ try {
+ XmlPullParser parser = createParser();
+ parser.setInput(in);
+ return new XppDomReader(XppDom.build(parser), getNameCoder());
+ } catch (XmlPullParserException e) {
+ throw new StreamException(e);
+ } catch (IOException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public HierarchicalStreamReader createReader(InputStream in) {
+ try {
+ return createReader(new XmlHeaderAwareReader(in));
+ } catch (UnsupportedEncodingException e) {
+ throw new StreamException(e);
+ } catch (IOException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public HierarchicalStreamWriter createWriter(Writer out) {
+ return new PrettyPrintWriter(out, getNameCoder());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public HierarchicalStreamWriter createWriter(OutputStream out) {
+ return createWriter(new OutputStreamWriter(out));
+ }
+
+ /**
+ * Create the parser of the XPP implementation.
+
+ * @throws XmlPullParserException if the parser cannot be created
+ * @since 1.4
+ */
+ protected abstract XmlPullParser createParser() throws XmlPullParserException;
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/AbstractXppDriver.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/AbstractXppDriver.java
new file mode 100644
index 0000000..efcd59a
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/AbstractXppDriver.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2009, 2011 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 29. April 2009 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.core.util.XmlHeaderAwareReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.io.StreamException;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+
+/**
+ * An abstract base class for a driver using an XPP implementation.
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ * @since 1.4
+ */
+public abstract class AbstractXppDriver extends AbstractXmlDriver {
+
+ /**
+ * Construct an AbstractXppDriver.
+ *
+ * @param nameCoder the replacer for XML friendly tag and attribute names
+ * @since 1.4
+ */
+ public AbstractXppDriver(NameCoder nameCoder) {
+ super(nameCoder);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public HierarchicalStreamReader createReader(Reader in) {
+ try {
+ return new XppReader(in, createParser(), getNameCoder());
+ } catch (XmlPullParserException e) {
+ throw new StreamException("Cannot create XmlPullParser");
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public HierarchicalStreamReader createReader(InputStream in) {
+ try {
+ return createReader(new XmlHeaderAwareReader(in));
+ } catch (UnsupportedEncodingException e) {
+ throw new StreamException(e);
+ } catch (IOException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public HierarchicalStreamWriter createWriter(Writer out) {
+ return new PrettyPrintWriter(out, getNameCoder());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public HierarchicalStreamWriter createWriter(OutputStream out) {
+ return createWriter(new OutputStreamWriter(out));
+ }
+
+ /**
+ * Create the parser of the XPP implementation.
+
+ * @throws XmlPullParserException if the parser cannot be created
+ * @since 1.4
+ */
+ protected abstract XmlPullParser createParser() throws XmlPullParserException;
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/BEAStaxDriver.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/BEAStaxDriver.java
new file mode 100644
index 0000000..3c67699
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/BEAStaxDriver.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2009, 2011 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 29. April 2009 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.bea.xml.stream.MXParserFactory;
+import com.bea.xml.stream.XMLOutputFactoryBase;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLOutputFactory;
+
+/**
+ * A driver using the BEA StAX implementation.
+ *
+ * @author Jörg Schaible
+ * @since 1.4
+ */
+public class BEAStaxDriver extends StaxDriver {
+
+ public BEAStaxDriver() {
+ super();
+ }
+
+ /**
+ * @deprecated As of 1.4.6 use {@link #BEAStaxDriver(QNameMap, NameCoder)}
+ */
+ public BEAStaxDriver(QNameMap qnameMap, XmlFriendlyNameCoder nameCoder) {
+ super(qnameMap, nameCoder);
+ }
+
+ /**
+ * @since 1.4.6
+ */
+ public BEAStaxDriver(QNameMap qnameMap, NameCoder nameCoder) {
+ super(qnameMap, nameCoder);
+ }
+
+ public BEAStaxDriver(QNameMap qnameMap) {
+ super(qnameMap);
+ }
+
+ /**
+ * @deprecated As of 1.4.6 use {@link #BEAStaxDriver(NameCoder)}
+ */
+ public BEAStaxDriver(XmlFriendlyNameCoder nameCoder) {
+ super(nameCoder);
+ }
+
+ /**
+ * @since 1.4.6
+ */
+ public BEAStaxDriver(NameCoder nameCoder) {
+ super(nameCoder);
+ }
+
+ protected XMLInputFactory createInputFactory() {
+ return new MXParserFactory();
+ }
+
+ protected XMLOutputFactory createOutputFactory() {
+ return new XMLOutputFactoryBase();
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/CompactWriter.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/CompactWriter.java
new file mode 100644
index 0000000..18a9695
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/CompactWriter.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009, 2011 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 07. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+import java.io.Writer;
+
+public class CompactWriter extends PrettyPrintWriter {
+
+ public CompactWriter(Writer writer) {
+ super(writer);
+ }
+
+ /**
+ * @since 1.3
+ */
+ public CompactWriter(Writer writer, int mode) {
+ super(writer, mode);
+ }
+
+ /**
+ * @since 1.4
+ */
+ public CompactWriter(Writer writer, NameCoder nameCoder) {
+ super(writer, nameCoder);
+ }
+
+ /**
+ * @since 1.4
+ */
+ public CompactWriter(Writer writer, int mode, NameCoder nameCoder) {
+ super(writer, mode, nameCoder);
+ }
+
+ /**
+ * @deprecated As of 1.4 use {@link CompactWriter#CompactWriter(Writer, NameCoder)} instead.
+ */
+ public CompactWriter(Writer writer, XmlFriendlyReplacer replacer) {
+ super(writer, replacer);
+ }
+
+ /**
+ * @since 1.3
+ * @deprecated As of 1.4 use {@link CompactWriter#CompactWriter(Writer, int, NameCoder)} instead.
+ */
+ public CompactWriter(Writer writer, int mode, XmlFriendlyReplacer replacer) {
+ super(writer, mode, replacer);
+ }
+
+ protected void endOfLine() {
+ // override parent: don't write anything at end of line
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/DocumentReader.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/DocumentReader.java
new file mode 100644
index 0000000..1747205
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/DocumentReader.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2006, 2007 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 18. October 2007 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+
+
+/**
+ * A generic interface for all {@link HierarchicalStreamReader} implementations reading a DOM.
+ *
+ * @author Jörg Schaible
+ * @since 1.2.1
+ */
+public interface DocumentReader extends HierarchicalStreamReader {
+
+ /**
+ * Retrieve the current processed node of the DOM.
+ *
+ * @return the current node
+ * @since 1.2.1
+ */
+ public Object getCurrent();
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/DocumentWriter.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/DocumentWriter.java
new file mode 100644
index 0000000..7435cfb
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/DocumentWriter.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2006, 2007 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 18. October 2007 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import java.util.List;
+
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+
+
+/**
+ * A generic interface for all {@link HierarchicalStreamWriter} implementations generating a
+ * DOM.
+ *
+ * @author Jörg Schaible
+ * @since 1.2.1
+ */
+public interface DocumentWriter extends HierarchicalStreamWriter {
+
+ /**
+ * Retrieve a {@link List} with the top elements. In the standard use case this list will
+ * only contain a single element. Additional elements can only occur, if
+ * {@link HierarchicalStreamWriter#startNode(String)} of the implementing
+ * {@link HierarchicalStreamWriter} was called multiple times with an empty node stack. Such
+ * a situation occurs calling
+ * {@link com.thoughtworks.xstream.XStream#marshal(Object, HierarchicalStreamWriter)}
+ * multiple times directly.
+ *
+ * @return a {@link List} with top nodes
+ * @since 1.2.1
+ */
+ List getTopLevelNodes();
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/Dom4JDriver.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/Dom4JDriver.java
new file mode 100644
index 0000000..bce7cff
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/Dom4JDriver.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2011 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 07. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import java.io.File;
+import java.io.FilterWriter;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.URL;
+
+import org.dom4j.Document;
+import org.dom4j.DocumentException;
+import org.dom4j.DocumentFactory;
+import org.dom4j.io.OutputFormat;
+import org.dom4j.io.SAXReader;
+import org.dom4j.io.XMLWriter;
+
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.io.StreamException;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+public class Dom4JDriver extends AbstractXmlDriver {
+
+ private DocumentFactory documentFactory;
+ private OutputFormat outputFormat;
+
+ public Dom4JDriver() {
+ this(new XmlFriendlyNameCoder());
+ }
+
+ /**
+ * @since 1.4
+ */
+ public Dom4JDriver(NameCoder nameCoder) {
+ this(new DocumentFactory(), OutputFormat.createPrettyPrint(), nameCoder);
+ outputFormat.setTrimText(false);
+ }
+
+ public Dom4JDriver(DocumentFactory documentFactory, OutputFormat outputFormat) {
+ this(documentFactory, outputFormat, new XmlFriendlyNameCoder());
+ }
+
+ /**
+ * @since 1.4
+ */
+ public Dom4JDriver(DocumentFactory documentFactory, OutputFormat outputFormat, NameCoder nameCoder) {
+ super(nameCoder);
+ this.documentFactory = documentFactory;
+ this.outputFormat = outputFormat;
+ }
+
+ /**
+ * @since 1.2
+ * @deprecated As of 1.4, use {@link Dom4JDriver#Dom4JDriver(DocumentFactory, OutputFormat, NameCoder)} instead.
+ */
+ public Dom4JDriver(DocumentFactory documentFactory, OutputFormat outputFormat, XmlFriendlyReplacer replacer) {
+ this(documentFactory, outputFormat, (NameCoder)replacer);
+ }
+
+
+ public DocumentFactory getDocumentFactory() {
+ return documentFactory;
+ }
+
+ public void setDocumentFactory(DocumentFactory documentFactory) {
+ this.documentFactory = documentFactory;
+ }
+
+ public OutputFormat getOutputFormat() {
+ return outputFormat;
+ }
+
+ public void setOutputFormat(OutputFormat outputFormat) {
+ this.outputFormat = outputFormat;
+ }
+
+ public HierarchicalStreamReader createReader(Reader text) {
+ try {
+ SAXReader reader = new SAXReader();
+ Document document = reader.read(text);
+ return new Dom4JReader(document, getNameCoder());
+ } catch (DocumentException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public HierarchicalStreamReader createReader(InputStream in) {
+ try {
+ SAXReader reader = new SAXReader();
+ Document document = reader.read(in);
+ return new Dom4JReader(document, getNameCoder());
+ } catch (DocumentException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ /**
+ * @since 1.4
+ */
+ public HierarchicalStreamReader createReader(URL in) {
+ try {
+ SAXReader reader = new SAXReader();
+ Document document = reader.read(in);
+ return new Dom4JReader(document, getNameCoder());
+ } catch (DocumentException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ /**
+ * @since 1.4
+ */
+ public HierarchicalStreamReader createReader(File in) {
+ try {
+ SAXReader reader = new SAXReader();
+ Document document = reader.read(in);
+ return new Dom4JReader(document, getNameCoder());
+ } catch (DocumentException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public HierarchicalStreamWriter createWriter(final Writer out) {
+ final HierarchicalStreamWriter[] writer = new HierarchicalStreamWriter[1];
+ final FilterWriter filter = new FilterWriter(out){
+ public void close() {
+ writer[0].close();
+ }
+ };
+ writer[0] = new Dom4JXmlWriter(new XMLWriter(filter, outputFormat), getNameCoder());
+ return writer[0];
+ }
+
+ public HierarchicalStreamWriter createWriter(final OutputStream out) {
+ final Writer writer = new OutputStreamWriter(out);
+ return createWriter(writer);
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/Dom4JReader.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/Dom4JReader.java
new file mode 100644
index 0000000..8ffe174
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/Dom4JReader.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2011 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 07. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.converters.ErrorWriter;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+import java.util.List;
+import org.dom4j.Document;
+import org.dom4j.Element;
+
+public class Dom4JReader extends AbstractDocumentReader {
+
+ private Element currentElement;
+
+ public Dom4JReader(Element rootElement) {
+ this(rootElement, new XmlFriendlyNameCoder());
+ }
+
+ public Dom4JReader(Document document) {
+ this(document.getRootElement());
+ }
+
+ /**
+ * @since 1.4
+ */
+ public Dom4JReader(Element rootElement, NameCoder nameCoder) {
+ super(rootElement, nameCoder);
+ }
+
+ /**
+ * @since 1.4
+ */
+ public Dom4JReader(Document document, NameCoder nameCoder) {
+ this(document.getRootElement(), nameCoder);
+ }
+
+ /**
+ * @since 1.2
+ * @deprecated As of 1.4, use {@link Dom4JReader#Dom4JReader(Element, NameCoder)} instead
+ */
+ public Dom4JReader(Element rootElement, XmlFriendlyReplacer replacer) {
+ this(rootElement, (NameCoder)replacer);
+ }
+
+ /**
+ * @since 1.2
+ * @deprecated As of 1.4, use {@link Dom4JReader#Dom4JReader(Document, NameCoder)} instead
+ */
+ public Dom4JReader(Document document, XmlFriendlyReplacer replacer) {
+ this(document.getRootElement(), (NameCoder)replacer);
+ }
+
+ public String getNodeName() {
+ return decodeNode(currentElement.getName());
+ }
+
+ public String getValue() {
+ return currentElement.getText();
+ }
+
+ public String getAttribute(String name) {
+ return currentElement.attributeValue(encodeAttribute(name));
+ }
+
+ public String getAttribute(int index) {
+ return currentElement.attribute(index).getValue();
+ }
+
+ public int getAttributeCount() {
+ return currentElement.attributeCount();
+ }
+
+ public String getAttributeName(int index) {
+ return decodeAttribute(currentElement.attribute(index).getQualifiedName());
+ }
+
+ protected Object getParent() {
+ return currentElement.getParent();
+ }
+
+ protected Object getChild(int index) {
+ return currentElement.elements().get(index);
+ }
+
+ protected int getChildCount() {
+ return currentElement.elements().size();
+ }
+
+ protected void reassignCurrentElement(Object current) {
+ currentElement = (Element) current;
+ }
+
+ public String peekNextChild() {
+ List list = currentElement.elements();
+ if (null == list || list.isEmpty()) {
+ return null;
+ }
+ return decodeNode(((Element) list.get(0)).getName());
+ }
+
+ public void appendErrors(ErrorWriter errorWriter) {
+ errorWriter.add("xpath", currentElement.getPath());
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/Dom4JWriter.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/Dom4JWriter.java
new file mode 100644
index 0000000..a981bad
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/Dom4JWriter.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2011 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 07. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+import org.dom4j.Branch;
+import org.dom4j.DocumentFactory;
+import org.dom4j.Element;
+
+
+public class Dom4JWriter extends AbstractDocumentWriter {
+
+ private final DocumentFactory documentFactory;
+
+ /**
+ * @since 1.4
+ */
+ public Dom4JWriter(
+ final Branch root, final DocumentFactory factory, final NameCoder nameCoder) {
+ super(root, nameCoder);
+ documentFactory = factory;
+ }
+
+ /**
+ * @since 1.4
+ */
+ public Dom4JWriter(final DocumentFactory factory, final NameCoder nameCoder) {
+ this(null, factory, nameCoder);
+ }
+
+ /**
+ * @since 1.4
+ */
+ public Dom4JWriter(final Branch root, final NameCoder nameCoder) {
+ this(root, new DocumentFactory(), nameCoder);
+ }
+
+ /**
+ * @since 1.2.1
+ * @deprecated As of 1.4 use {@link Dom4JWriter#Dom4JWriter(Branch, DocumentFactory, NameCoder)} instead.
+ */
+ public Dom4JWriter(
+ final Branch root, final DocumentFactory factory, final XmlFriendlyReplacer replacer) {
+ this(root, factory, (NameCoder)replacer);
+ }
+
+ /**
+ * @since 1.2.1
+ * @deprecated As of 1.4 use {@link Dom4JWriter#Dom4JWriter(DocumentFactory, NameCoder)} instead.
+ */
+ public Dom4JWriter(final DocumentFactory factory, final XmlFriendlyReplacer replacer) {
+ this(null, factory, (NameCoder)replacer);
+ }
+
+ /**
+ * @since 1.2.1
+ */
+ public Dom4JWriter(final DocumentFactory documentFactory) {
+ this(documentFactory, new XmlFriendlyNameCoder());
+ }
+
+ /**
+ * @since 1.2.1
+ * @deprecated As of 1.4 use {@link Dom4JWriter#Dom4JWriter(Branch, NameCoder)} instead
+ */
+ public Dom4JWriter(final Branch root, final XmlFriendlyReplacer replacer) {
+ this(root, new DocumentFactory(), (NameCoder)replacer);
+ }
+
+ public Dom4JWriter(final Branch root) {
+ this(root, new DocumentFactory(), new XmlFriendlyNameCoder());
+ }
+
+ /**
+ * @since 1.2.1
+ */
+ public Dom4JWriter() {
+ this(new DocumentFactory(), new XmlFriendlyNameCoder());
+ }
+
+ protected Object createNode(final String name) {
+ final Element element = documentFactory.createElement(encodeNode(name));
+ final Branch top = top();
+ if (top != null) {
+ top().add(element);
+ }
+ return element;
+ }
+
+ public void setValue(final String text) {
+ top().setText(text);
+ }
+
+ public void addAttribute(final String key, final String value) {
+ ((Element)top()).addAttribute(encodeAttribute(key), value);
+ }
+
+ private Branch top() {
+ return (Branch)getCurrent();
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/Dom4JXmlWriter.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/Dom4JXmlWriter.java
new file mode 100644
index 0000000..f0c0f75
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/Dom4JXmlWriter.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2011 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 07. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.core.util.FastStack;
+import com.thoughtworks.xstream.io.StreamException;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+import org.dom4j.Element;
+import org.dom4j.io.XMLWriter;
+import org.dom4j.tree.DefaultElement;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import java.io.IOException;
+
+
+public class Dom4JXmlWriter extends AbstractXmlWriter {
+
+ private final XMLWriter writer;
+ private final FastStack elementStack;
+ private AttributesImpl attributes;
+ private boolean started;
+ private boolean children;
+
+ public Dom4JXmlWriter(XMLWriter writer) {
+ this(writer, new XmlFriendlyNameCoder());
+ }
+
+ /**
+ * @since 1.4
+ */
+ public Dom4JXmlWriter(XMLWriter writer, NameCoder nameCoder) {
+ super(nameCoder);
+ this.writer = writer;
+ this.elementStack = new FastStack(16);
+ this.attributes = new AttributesImpl();
+ try {
+ writer.startDocument();
+ } catch (SAXException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ /**
+ * @since 1.2
+ * @deprecated As of 1.4 use {@link Dom4JXmlWriter#Dom4JXmlWriter(XMLWriter, NameCoder)} instead.
+ */
+ public Dom4JXmlWriter(XMLWriter writer, XmlFriendlyReplacer replacer) {
+ this(writer, (NameCoder)replacer);
+ }
+
+ public void startNode(String name) {
+ if (elementStack.size() > 0) {
+ try {
+ startElement();
+ } catch (SAXException e) {
+ throw new StreamException(e);
+ }
+ started = false;
+ }
+ elementStack.push(encodeNode(name));
+ children = false;
+ }
+
+ public void setValue(String text) {
+ char[] value = text.toCharArray();
+ if (value.length > 0) {
+ try {
+ startElement();
+ writer.characters(value, 0, value.length);
+ } catch (SAXException e) {
+ throw new StreamException(e);
+ }
+ children = true;
+ }
+ }
+
+ public void addAttribute(String key, String value) {
+ attributes.addAttribute("", "", encodeAttribute(key), "string", value);
+ }
+
+ public void endNode() {
+ try {
+ if (!children) {
+ Element element = new DefaultElement((String)elementStack.pop());
+ for (int i = 0; i < attributes.getLength(); ++i) {
+ element.addAttribute(attributes.getQName(i), attributes.getValue(i));
+ }
+ writer.write(element);
+ attributes.clear();
+ children = true; // node just closed is child of node on top of stack
+ started = true;
+ } else {
+ startElement();
+ writer.endElement("", "", (String)elementStack.pop());
+ }
+ } catch (SAXException e) {
+ throw new StreamException(e);
+ } catch (IOException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public void flush() {
+ try {
+ writer.flush();
+ } catch (IOException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public void close() {
+ try {
+ writer.endDocument();
+ } catch (SAXException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ private void startElement() throws SAXException {
+ if (!started) {
+ writer.startElement("", "", (String)elementStack.peek(), attributes);
+ attributes.clear();
+ started = true;
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/DomDriver.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/DomDriver.java
new file mode 100644
index 0000000..7b1709e
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/DomDriver.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009, 2011 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 07. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.net.URL;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.FactoryConfigurationError;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.io.StreamException;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+
+public class DomDriver extends AbstractXmlDriver {
+
+ private final String encoding;
+ private final DocumentBuilderFactory documentBuilderFactory;
+
+ /**
+ * Construct a DomDriver.
+ */
+ public DomDriver() {
+ this(null);
+ }
+
+ /**
+ * Construct a DomDriver with a specified encoding. The created DomReader will ignore any
+ * encoding attribute of the XML header though.
+ */
+ public DomDriver(String encoding) {
+ this(encoding, new XmlFriendlyNameCoder());
+ }
+
+ /**
+ * @since 1.4
+ */
+ public DomDriver(String encoding, NameCoder nameCoder) {
+ super(nameCoder);
+ documentBuilderFactory = DocumentBuilderFactory.newInstance();
+ this.encoding = encoding;
+ }
+
+ /**
+ * @since 1.2
+ * @deprecated As of 1.4, use {@link #DomDriver(String, NameCoder)} instead.
+ */
+ public DomDriver(String encoding, XmlFriendlyReplacer replacer) {
+ this(encoding, (NameCoder)replacer);
+ }
+
+ public HierarchicalStreamReader createReader(Reader in) {
+ return createReader(new InputSource(in));
+ }
+
+ public HierarchicalStreamReader createReader(InputStream in) {
+ return createReader(new InputSource(in));
+ }
+
+ public HierarchicalStreamReader createReader(URL in) {
+ return createReader(new InputSource(in.toExternalForm()));
+ }
+
+ public HierarchicalStreamReader createReader(File in) {
+ return createReader(new InputSource(in.toURI().toASCIIString()));
+ }
+
+ private HierarchicalStreamReader createReader(InputSource source) {
+ try {
+ DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
+ if (encoding != null) {
+ source.setEncoding(encoding);
+ }
+ Document document = documentBuilder.parse(source);
+ return new DomReader(document, getNameCoder());
+ } catch (FactoryConfigurationError e) {
+ throw new StreamException(e);
+ } catch (ParserConfigurationException e) {
+ throw new StreamException(e);
+ } catch (SAXException e) {
+ throw new StreamException(e);
+ } catch (IOException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public HierarchicalStreamWriter createWriter(Writer out) {
+ return new PrettyPrintWriter(out, getNameCoder());
+ }
+
+ public HierarchicalStreamWriter createWriter(OutputStream out) {
+ try {
+ return createWriter(encoding != null
+ ? new OutputStreamWriter(out, encoding)
+ : new OutputStreamWriter(out));
+ } catch (UnsupportedEncodingException e) {
+ throw new StreamException(e);
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/DomReader.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/DomReader.java
new file mode 100644
index 0000000..2774440
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/DomReader.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2011 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 07. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Text;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DomReader extends AbstractDocumentReader {
+
+ private Element currentElement;
+ private StringBuffer textBuffer;
+ private List childElements;
+
+ public DomReader(Element rootElement) {
+ this(rootElement, new XmlFriendlyNameCoder());
+ }
+
+ public DomReader(Document document) {
+ this(document.getDocumentElement());
+ }
+
+ /**
+ * @since 1.4
+ */
+ public DomReader(Element rootElement, NameCoder nameCoder) {
+ super(rootElement, nameCoder);
+ textBuffer = new StringBuffer();
+ }
+
+ /**
+ * @since 1.4
+ */
+ public DomReader(Document document, NameCoder nameCoder) {
+ this(document.getDocumentElement(), nameCoder);
+ }
+
+ /**
+ * @since 1.2
+ * @deprecated As of 1.4, use {@link DomReader#DomReader(Element, NameCoder)} instead.
+ */
+ public DomReader(Element rootElement, XmlFriendlyReplacer replacer) {
+ this(rootElement, (NameCoder)replacer);
+ }
+
+ /**
+ * @since 1.2
+ * @deprecated As of 1.4, use {@link DomReader#DomReader(Document, NameCoder)} instead.
+ */
+ public DomReader(Document document, XmlFriendlyReplacer replacer) {
+ this(document.getDocumentElement(), (NameCoder)replacer);
+ }
+
+ public String getNodeName() {
+ return decodeNode(currentElement.getTagName());
+ }
+
+ public String getValue() {
+ NodeList childNodes = currentElement.getChildNodes();
+ textBuffer.setLength(0);
+ int length = childNodes.getLength();
+ for (int i = 0; i < length; i++) {
+ Node childNode = childNodes.item(i);
+ if (childNode instanceof Text) {
+ Text text = (Text) childNode;
+ textBuffer.append(text.getData());
+ }
+ }
+ return textBuffer.toString();
+ }
+
+ public String getAttribute(String name) {
+ Attr attribute = currentElement.getAttributeNode(encodeAttribute(name));
+ return attribute == null ? null : attribute.getValue();
+ }
+
+ public String getAttribute(int index) {
+ return ((Attr) currentElement.getAttributes().item(index)).getValue();
+ }
+
+ public int getAttributeCount() {
+ return currentElement.getAttributes().getLength();
+ }
+
+ public String getAttributeName(int index) {
+ return decodeAttribute(((Attr) currentElement.getAttributes().item(index)).getName());
+ }
+
+ protected Object getParent() {
+ return currentElement.getParentNode();
+ }
+
+ protected Object getChild(int index) {
+ return childElements.get(index);
+ }
+
+ protected int getChildCount() {
+ return childElements.size();
+ }
+
+ protected void reassignCurrentElement(Object current) {
+ currentElement = (Element) current;
+ NodeList childNodes = currentElement.getChildNodes();
+ childElements = new ArrayList();
+ for (int i = 0; i < childNodes.getLength(); i++) {
+ Node node = childNodes.item(i);
+ if (node instanceof Element) {
+ childElements.add(node);
+ }
+ }
+ }
+
+ public String peekNextChild() {
+ NodeList childNodes = currentElement.getChildNodes();
+ for (int i = 0; i < childNodes.getLength(); i++) {
+ Node node = childNodes.item(i);
+ if (node instanceof Element) {
+ return decodeNode(((Element) node).getTagName());
+ }
+ }
+ return null;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/DomWriter.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/DomWriter.java
new file mode 100644
index 0000000..9c65e7a
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/DomWriter.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2011 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 02. September 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+
+/**
+ * @author Michael Kopp
+ */
+public class DomWriter extends AbstractDocumentWriter {
+
+ private final Document document;
+ private boolean hasRootElement;
+
+ public DomWriter(final Document document) {
+ this(document, new XmlFriendlyNameCoder());
+ }
+
+ public DomWriter(final Element rootElement) {
+ this(rootElement, new XmlFriendlyNameCoder());
+ }
+
+ /**
+ * @since 1.4
+ */
+ public DomWriter(final Document document, final NameCoder nameCoder) {
+ this(document.getDocumentElement(), document, nameCoder);
+ }
+
+ /**
+ * @since 1.4
+ */
+ public DomWriter(final Element element, final Document document, final NameCoder nameCoder) {
+ super(element, nameCoder);
+ this.document = document;
+ hasRootElement = document.getDocumentElement() != null;
+ }
+
+ /**
+ * @since 1.4
+ */
+ public DomWriter(final Element rootElement, final NameCoder nameCoder) {
+ this(rootElement, rootElement.getOwnerDocument(), nameCoder);
+ }
+
+ /**
+ * @since 1.2
+ * @deprecated As of 1.4 use {@link DomWriter#DomWriter(Document, NameCoder)} instead.
+ */
+ public DomWriter(final Document document, final XmlFriendlyReplacer replacer) {
+ this(document.getDocumentElement(), document, (NameCoder)replacer);
+ }
+
+ /**
+ * @since 1.2.1
+ * @deprecated As of 1.4 use {@link DomWriter#DomWriter(Element, Document, NameCoder)} instead.
+ */
+ public DomWriter(final Element element, final Document document, final XmlFriendlyReplacer replacer) {
+ this(element, document, (NameCoder)replacer);
+ }
+
+ /**
+ * @since 1.2
+ * @deprecated As of 1.4 use {@link DomWriter#DomWriter(Element, NameCoder)} instead.
+ */
+ public DomWriter(final Element rootElement, final XmlFriendlyReplacer replacer) {
+ this(rootElement, rootElement.getOwnerDocument(), (NameCoder)replacer);
+ }
+
+ protected Object createNode(final String name) {
+ final Element child = document.createElement(encodeNode(name));
+ final Element top = top();
+ if (top != null) {
+ top().appendChild(child);
+ } else if (!hasRootElement) {
+ document.appendChild(child);
+ hasRootElement = true;
+ }
+ return child;
+ }
+
+ public void addAttribute(final String name, final String value) {
+ top().setAttribute(encodeAttribute(name), value);
+ }
+
+ public void setValue(final String text) {
+ top().appendChild(document.createTextNode(text));
+ }
+
+ private Element top() {
+ return (Element)getCurrent();
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/JDom2Driver.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/JDom2Driver.java
new file mode 100644
index 0000000..876630e
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/JDom2Driver.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2013 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 24. June 2012 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.URL;
+
+import org.jdom2.Document;
+import org.jdom2.JDOMException;
+import org.jdom2.input.SAXBuilder;
+
+import com.thoughtworks.xstream.io.AbstractDriver;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.io.StreamException;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+/**
+ * @since 1.4.5
+ */
+public class JDom2Driver extends AbstractDriver {
+
+ public JDom2Driver() {
+ super(new XmlFriendlyNameCoder());
+ }
+
+ /**
+ * @since 1.4.5
+ */
+ public JDom2Driver(NameCoder nameCoder) {
+ super(nameCoder);
+ }
+
+ public HierarchicalStreamReader createReader(Reader reader) {
+ try {
+ SAXBuilder builder = new SAXBuilder();
+ Document document = builder.build(reader);
+ return new JDom2Reader(document, getNameCoder());
+ } catch (IOException e) {
+ throw new StreamException(e);
+ } catch (JDOMException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public HierarchicalStreamReader createReader(InputStream in) {
+ try {
+ SAXBuilder builder = new SAXBuilder();
+ Document document = builder.build(in);
+ return new JDom2Reader(document, getNameCoder());
+ } catch (IOException e) {
+ throw new StreamException(e);
+ } catch (JDOMException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public HierarchicalStreamReader createReader(URL in) {
+ try {
+ SAXBuilder builder = new SAXBuilder();
+ Document document = builder.build(in);
+ return new JDom2Reader(document, getNameCoder());
+ } catch (IOException e) {
+ throw new StreamException(e);
+ } catch (JDOMException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public HierarchicalStreamReader createReader(File in) {
+ try {
+ SAXBuilder builder = new SAXBuilder();
+ Document document = builder.build(in);
+ return new JDom2Reader(document, getNameCoder());
+ } catch (IOException e) {
+ throw new StreamException(e);
+ } catch (JDOMException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public HierarchicalStreamWriter createWriter(Writer out) {
+ return new PrettyPrintWriter(out, getNameCoder());
+ }
+
+ public HierarchicalStreamWriter createWriter(OutputStream out) {
+ return new PrettyPrintWriter(new OutputStreamWriter(out));
+ }
+}
+
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/JDom2Reader.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/JDom2Reader.java
new file mode 100644
index 0000000..65cc7fd
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/JDom2Reader.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2013 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 24. June 2012 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.io.naming.NameCoder;
+import java.util.List;
+import org.jdom2.Document;
+import org.jdom2.Element;
+
+/**
+ * @since 1.4.5
+ */
+public class JDom2Reader extends AbstractDocumentReader {
+
+ private Element currentElement;
+
+ /**
+ * @since 1.4.5
+ */
+ public JDom2Reader(Element root) {
+ super(root);
+ }
+
+ /**
+ * @since 1.4.5
+ */
+ public JDom2Reader(Document document) {
+ super(document.getRootElement());
+ }
+
+ /**
+ * @since 1.4.5
+ */
+ public JDom2Reader(Element root, NameCoder nameCoder) {
+ super(root, nameCoder);
+ }
+
+ /**
+ * @since 1.4.5
+ */
+ public JDom2Reader(Document document, NameCoder nameCoder) {
+ super(document.getRootElement(), nameCoder);
+ }
+
+ protected void reassignCurrentElement(Object current) {
+ currentElement = (Element) current;
+ }
+
+ protected Object getParent() {
+ return currentElement.getParentElement();
+ }
+
+ protected Object getChild(int index) {
+ return currentElement.getChildren().get(index);
+ }
+
+ protected int getChildCount() {
+ return currentElement.getChildren().size();
+ }
+
+ public String getNodeName() {
+ return decodeNode(currentElement.getName());
+ }
+
+ public String getValue() {
+ return currentElement.getText();
+ }
+
+ public String getAttribute(String name) {
+ return currentElement.getAttributeValue(encodeAttribute(name));
+ }
+
+ public String getAttribute(int index) {
+ return currentElement.getAttributes().get(index).getValue();
+ }
+
+ public int getAttributeCount() {
+ return currentElement.getAttributes().size();
+ }
+
+ public String getAttributeName(int index) {
+ return decodeAttribute(currentElement.getAttributes().get(index).getQualifiedName());
+ }
+
+ public String peekNextChild() {
+ List list = currentElement.getChildren();
+ if (null == list || list.isEmpty()) {
+ return null;
+ }
+ return decodeNode(((Element) list.get(0)).getName());
+ }
+}
+
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/JDom2Writer.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/JDom2Writer.java
new file mode 100644
index 0000000..7c9047f
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/JDom2Writer.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2013 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 24. June 2012 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+import org.jdom2.DefaultJDOMFactory;
+import org.jdom2.Element;
+import org.jdom2.JDOMFactory;
+
+
+/**
+ * @since 1.4.5
+ */
+public class JDom2Writer extends AbstractDocumentWriter {
+
+ private final JDOMFactory documentFactory;
+
+ /**
+ * @since 1.4.5
+ */
+ public JDom2Writer(
+ final Element container, final JDOMFactory factory,
+ final NameCoder nameCoder) {
+ super(container, nameCoder);
+ documentFactory = factory;
+ }
+
+ /**
+ * @since 1.4.5
+ */
+ public JDom2Writer(final Element container, final JDOMFactory factory) {
+ this(container, factory, new XmlFriendlyNameCoder());
+ }
+
+ /**
+ * @since 1.4.5
+ */
+ public JDom2Writer(final JDOMFactory factory, final NameCoder nameCoder) {
+ this(null, factory, nameCoder);
+ }
+
+ /**
+ * @since 1.4.5
+ */
+ public JDom2Writer(final JDOMFactory factory) {
+ this(null, factory);
+ }
+
+ /**
+ * @since 1.4.5
+ */
+ public JDom2Writer(final Element container, final NameCoder nameCoder) {
+ this(container, new DefaultJDOMFactory(), nameCoder);
+ }
+
+ /**
+ * @since 1.4.5
+ */
+ public JDom2Writer(final Element container) {
+ this(container, new DefaultJDOMFactory());
+ }
+
+ /**
+ * @since 1.4.5
+ */
+ public JDom2Writer() {
+ this(new DefaultJDOMFactory());
+ }
+
+ protected Object createNode(final String name) {
+ final Element element = documentFactory.element(encodeNode(name));
+ final Element parent = top();
+ if (parent != null) {
+ parent.addContent(element);
+ }
+ return element;
+ }
+
+ public void setValue(final String text) {
+ top().addContent(documentFactory.text(text));
+ }
+
+ public void addAttribute(final String key, final String value) {
+ top().setAttribute(documentFactory.attribute(encodeAttribute(key), value));
+ }
+
+ /**
+ * @since 1.4.5
+ */
+ private Element top() {
+ return (Element)getCurrent();
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/JDomDriver.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/JDomDriver.java
new file mode 100644
index 0000000..6bc3255
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/JDomDriver.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2011 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 03. September 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.URL;
+
+import org.jdom.Document;
+import org.jdom.JDOMException;
+import org.jdom.input.SAXBuilder;
+
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.io.StreamException;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+/**
+ * @author Laurent Bihanic
+ */
+public class JDomDriver extends AbstractXmlDriver {
+
+ public JDomDriver() {
+ super(new XmlFriendlyNameCoder());
+ }
+
+ /**
+ * @since 1.4
+ */
+ public JDomDriver(NameCoder nameCoder) {
+ super(nameCoder);
+ }
+
+ /**
+ * @since 1.2
+ * @deprecated As of 1.4, use {@link JDomDriver#JDomDriver(NameCoder)} instead.
+ */
+ public JDomDriver(XmlFriendlyReplacer replacer) {
+ this((NameCoder)replacer);
+ }
+
+ public HierarchicalStreamReader createReader(Reader reader) {
+ try {
+ SAXBuilder builder = new SAXBuilder();
+ Document document = builder.build(reader);
+ return new JDomReader(document, getNameCoder());
+ } catch (IOException e) {
+ throw new StreamException(e);
+ } catch (JDOMException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public HierarchicalStreamReader createReader(InputStream in) {
+ try {
+ SAXBuilder builder = new SAXBuilder();
+ Document document = builder.build(in);
+ return new JDomReader(document, getNameCoder());
+ } catch (IOException e) {
+ throw new StreamException(e);
+ } catch (JDOMException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public HierarchicalStreamReader createReader(URL in) {
+ try {
+ SAXBuilder builder = new SAXBuilder();
+ Document document = builder.build(in);
+ return new JDomReader(document, getNameCoder());
+ } catch (IOException e) {
+ throw new StreamException(e);
+ } catch (JDOMException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public HierarchicalStreamReader createReader(File in) {
+ try {
+ SAXBuilder builder = new SAXBuilder();
+ Document document = builder.build(in);
+ return new JDomReader(document, getNameCoder());
+ } catch (IOException e) {
+ throw new StreamException(e);
+ } catch (JDOMException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public HierarchicalStreamWriter createWriter(Writer out) {
+ return new PrettyPrintWriter(out, getNameCoder());
+ }
+
+ public HierarchicalStreamWriter createWriter(OutputStream out) {
+ return new PrettyPrintWriter(new OutputStreamWriter(out));
+ }
+
+}
+
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/JDomReader.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/JDomReader.java
new file mode 100644
index 0000000..f84478c
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/JDomReader.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2011 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 03. September 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.io.naming.NameCoder;
+import java.util.List;
+import org.jdom.Attribute;
+import org.jdom.Document;
+import org.jdom.Element;
+
+/**
+ * @author Laurent Bihanic
+ */
+public class JDomReader extends AbstractDocumentReader {
+
+ private Element currentElement;
+
+ public JDomReader(Element root) {
+ super(root);
+ }
+
+ public JDomReader(Document document) {
+ super(document.getRootElement());
+ }
+
+ /**
+ * @since 1.4
+ */
+ public JDomReader(Element root, NameCoder nameCoder) {
+ super(root, nameCoder);
+ }
+
+ /**
+ * @since 1.4
+ */
+ public JDomReader(Document document, NameCoder nameCoder) {
+ super(document.getRootElement(), nameCoder);
+ }
+
+ /**
+ * @since 1.2
+ * @deprecated As of 1.4, use {@link JDomReader#JDomReader(Element, NameCoder)} instead.
+ */
+ public JDomReader(Element root, XmlFriendlyReplacer replacer) {
+ this(root, (NameCoder)replacer);
+ }
+
+ /**
+ * @since 1.2
+ * @deprecated As of 1.4, use {@link JDomReader#JDomReader(Document, NameCoder)} instead.
+ */
+ public JDomReader(Document document, XmlFriendlyReplacer replacer) {
+ this(document.getRootElement(), (NameCoder)replacer);
+ }
+
+ protected void reassignCurrentElement(Object current) {
+ currentElement = (Element) current;
+ }
+
+ protected Object getParent() {
+ // JDOM 1.0:
+ return currentElement.getParentElement();
+
+ // JDOM b10:
+ // Parent parent = currentElement.getParent();
+ // return (parent instanceof Element) ? (Element)parent : null;
+
+ // JDOM b9 and earlier:
+ // return currentElement.getParent();
+ }
+
+ protected Object getChild(int index) {
+ return currentElement.getChildren().get(index);
+ }
+
+ protected int getChildCount() {
+ return currentElement.getChildren().size();
+ }
+
+ public String getNodeName() {
+ return decodeNode(currentElement.getName());
+ }
+
+ public String getValue() {
+ return currentElement.getText();
+ }
+
+ public String getAttribute(String name) {
+ return currentElement.getAttributeValue(encodeAttribute(name));
+ }
+
+ public String getAttribute(int index) {
+ return ((Attribute) currentElement.getAttributes().get(index)).getValue();
+ }
+
+ public int getAttributeCount() {
+ return currentElement.getAttributes().size();
+ }
+
+ public String getAttributeName(int index) {
+ return decodeAttribute(((Attribute) currentElement.getAttributes().get(index)).getQualifiedName());
+ }
+
+ public String peekNextChild() {
+ List list = currentElement.getChildren();
+ if (null == list || list.isEmpty()) {
+ return null;
+ }
+ return decodeNode(((Element) list.get(0)).getName());
+ }
+
+}
+
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/JDomWriter.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/JDomWriter.java
new file mode 100644
index 0000000..6759609
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/JDomWriter.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2011 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 03. September 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+import org.jdom.DefaultJDOMFactory;
+import org.jdom.Element;
+import org.jdom.JDOMFactory;
+
+
+/**
+ * @author Laurent Bihanic
+ */
+public class JDomWriter extends AbstractDocumentWriter {
+
+ private final JDOMFactory documentFactory;
+
+ /**
+ * @since 1.4
+ */
+ public JDomWriter(
+ final Element container, final JDOMFactory factory,
+ final NameCoder nameCoder) {
+ super(container, nameCoder);
+ documentFactory = factory;
+ }
+
+ /**
+ * @since 1.2
+ * @deprecated As of 1.4 use {@link JDomWriter#JDomWriter(Element, JDOMFactory, NameCoder)} instead.
+ */
+ public JDomWriter(
+ final Element container, final JDOMFactory factory,
+ final XmlFriendlyReplacer replacer) {
+ this(container, factory, (NameCoder)replacer);
+ }
+
+ public JDomWriter(final Element container, final JDOMFactory factory) {
+ this(container, factory, new XmlFriendlyNameCoder());
+ }
+
+ /**
+ * @since 1.4
+ */
+ public JDomWriter(final JDOMFactory factory, final NameCoder nameCoder) {
+ this(null, factory, nameCoder);
+ }
+
+ /**
+ * @since 1.2.1
+ * @deprecated As of 1.4 use {@link JDomWriter#JDomWriter(JDOMFactory, NameCoder)} instead.
+ */
+ public JDomWriter(final JDOMFactory factory, final XmlFriendlyReplacer replacer) {
+ this(null, factory, (NameCoder)replacer);
+ }
+
+ public JDomWriter(final JDOMFactory factory) {
+ this(null, factory);
+ }
+
+ /**
+ * @since 1.4
+ */
+ public JDomWriter(final Element container, final NameCoder nameCoder) {
+ this(container, new DefaultJDOMFactory(), nameCoder);
+ }
+
+ /**
+ * @since 1.2.1
+ * @deprecated As of 1.4 use {@link JDomWriter#JDomWriter(Element, NameCoder)} instead.
+ */
+ public JDomWriter(final Element container, final XmlFriendlyReplacer replacer) {
+ this(container, new DefaultJDOMFactory(), (NameCoder)replacer);
+ }
+
+ public JDomWriter(final Element container) {
+ this(container, new DefaultJDOMFactory());
+ }
+
+ public JDomWriter() {
+ this(new DefaultJDOMFactory());
+ }
+
+ protected Object createNode(final String name) {
+ final Element element = documentFactory.element(encodeNode(name));
+ final Element parent = top();
+ if (parent != null) {
+ parent.addContent(element);
+ }
+ return element;
+ }
+
+ public void setValue(final String text) {
+ top().addContent(documentFactory.text(text));
+ }
+
+ public void addAttribute(final String key, final String value) {
+ top().setAttribute(documentFactory.attribute(encodeAttribute(key), value));
+ }
+
+ private Element top() {
+ return (Element)getCurrent();
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/KXml2DomDriver.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/KXml2DomDriver.java
new file mode 100644
index 0000000..0dd889f
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/KXml2DomDriver.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2009, 2011 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 03. May 2009 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.io.HierarchicalStreamDriver;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+import org.kxml2.io.KXmlParser;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * A {@link HierarchicalStreamDriver} for XPP DOM using the kXML2 parser.
+ *
+ * @author Jörg Schaible
+ * @since 1.4
+ */
+public class KXml2DomDriver extends AbstractXppDomDriver {
+
+ /**
+ * Construct an KXml2DomDriver.
+ *
+ * @since 1.4
+ */
+ public KXml2DomDriver() {
+ super(new XmlFriendlyNameCoder());
+ }
+
+ /**
+ * Construct a KXml2DomDriver.
+ *
+ * @param nameCoder the replacer for XML friendly names
+ * @since 1.4
+ */
+ public KXml2DomDriver(NameCoder nameCoder) {
+ super(nameCoder);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected XmlPullParser createParser() {
+ return new KXmlParser();
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/KXml2Driver.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/KXml2Driver.java
new file mode 100644
index 0000000..c94f0c1
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/KXml2Driver.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2009, 2011 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 29. April 2009 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io.xml;
+
+
+import com.thoughtworks.xstream.io.HierarchicalStreamDriver;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+import org.kxml2.io.KXmlParser;
+import org.xmlpull.v1.XmlPullParser;
+
+
+/**
+ * A {@link HierarchicalStreamDriver} using the kXML2 parser.
+ *
+ * @author Jörg Schaible
+ * @since 1.4
+ */
+public class KXml2Driver extends AbstractXppDriver {
+
+ /**
+ * Construct a KXml2Driver.
+ *
+ * @since 1.4
+ */
+ public KXml2Driver() {
+ super(new XmlFriendlyNameCoder());
+ }
+
+ /**
+ * Construct a KXml2Driver.
+ *
+ * @param nameCoder the replacer for XML friendly names
+ * @since 1.4
+ */
+ public KXml2Driver(NameCoder nameCoder) {
+ super(nameCoder);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected XmlPullParser createParser() {
+ return new KXmlParser();
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/PrettyPrintWriter.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/PrettyPrintWriter.java
new file mode 100644
index 0000000..bf7ac34
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/PrettyPrintWriter.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009, 2011, 2013 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 07. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.core.util.FastStack;
+import com.thoughtworks.xstream.core.util.QuickWriter;
+import com.thoughtworks.xstream.io.StreamException;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+import java.io.Writer;
+
+
+/**
+ * A simple writer that outputs XML in a pretty-printed indented stream.
+ *
+ * By default, the chars & < > " ' \r are escaped
+ * and replaced with a suitable XML entity. To alter this behavior, override
+ * the {@link #writeText(com.thoughtworks.xstream.core.util.QuickWriter, String)} and
+ * {@link #writeAttributeValue(com.thoughtworks.xstream.core.util.QuickWriter, String)} methods.
+ *
+ *
+ * The XML specification requires XML parsers to drop CR characters completely. This implementation
+ * will therefore use only a LF for line endings, never the platform encoding. You can overwrite the
+ * {@link #getNewLine()} method for a different behavior.
+ *
+ *
+ * Note: Depending on the XML version some characters cannot be written. Especially a 0
+ * character is never valid in XML, neither directly nor as entity nor within CDATA. However, this writer
+ * works by default in a quirks mode, where it will write any character at least as character entity (even
+ * a null character). You may switch into XML_1_1 mode (which supports most characters) or XML_1_0
+ * that does only support a very limited number of control characters. See XML specification for version
+ * 1.0 or
+ * 1.1. If a character is
+ * not supported, a {@link StreamException} is thrown. Select a proper parser implementation that
+ * respects the version in the XML header (the Xpp3 parser will also read character entities of normally
+ * invalid characters).
+ *
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ */
+public class PrettyPrintWriter extends AbstractXmlWriter {
+
+ public static int XML_QUIRKS = -1;
+ public static int XML_1_0 = 0;
+ public static int XML_1_1 = 1;
+
+ private final QuickWriter writer;
+ private final FastStack elementStack = new FastStack(16);
+ private final char[] lineIndenter;
+ private final int mode;
+
+ private boolean tagInProgress;
+ protected int depth;
+ private boolean readyForNewLine;
+ private boolean tagIsEmpty;
+ private String newLine;
+
+ private static final char[] NULL = "".toCharArray();
+ private static final char[] AMP = "&".toCharArray();
+ private static final char[] LT = "<".toCharArray();
+ private static final char[] GT = ">".toCharArray();
+ private static final char[] CR = "
".toCharArray();
+ private static final char[] QUOT = """.toCharArray();
+ private static final char[] APOS = "'".toCharArray();
+ private static final char[] CLOSE = "".toCharArray();
+
+ private PrettyPrintWriter(
+ Writer writer, int mode, char[] lineIndenter, NameCoder nameCoder,
+ String newLine) {
+ super(nameCoder);
+ this.writer = new QuickWriter(writer);
+ this.lineIndenter = lineIndenter;
+ this.newLine = newLine;
+ this.mode = mode;
+ if (mode < XML_QUIRKS || mode > XML_1_1) {
+ throw new IllegalArgumentException("Not a valid XML mode");
+ }
+ }
+
+ /**
+ * @since 1.2
+ * @deprecated As of 1.3
+ */
+ public PrettyPrintWriter(
+ Writer writer, char[] lineIndenter, String newLine, XmlFriendlyReplacer replacer) {
+ this(writer, XML_QUIRKS, lineIndenter, replacer, newLine);
+ }
+
+ /**
+ * @since 1.4
+ */
+ public PrettyPrintWriter(
+ Writer writer, int mode, char[] lineIndenter, NameCoder nameCoder) {
+ this(writer, mode, lineIndenter, nameCoder, "\n");
+ }
+
+ /**
+ * @since 1.3
+ * @deprecated As of 1.4 use {@link PrettyPrintWriter#PrettyPrintWriter(Writer, int, char[], NameCoder)} instead
+ */
+ public PrettyPrintWriter(
+ Writer writer, int mode, char[] lineIndenter, XmlFriendlyReplacer replacer) {
+ this(writer, mode, lineIndenter, replacer, "\n");
+ }
+
+ /**
+ * @deprecated As of 1.3
+ */
+ public PrettyPrintWriter(Writer writer, char[] lineIndenter, String newLine) {
+ this(writer, lineIndenter, newLine, new XmlFriendlyReplacer());
+ }
+
+ /**
+ * @since 1.3
+ */
+ public PrettyPrintWriter(Writer writer, int mode, char[] lineIndenter) {
+ this(writer, mode, lineIndenter, new XmlFriendlyNameCoder());
+ }
+
+ public PrettyPrintWriter(Writer writer, char[] lineIndenter) {
+ this(writer, XML_QUIRKS, lineIndenter);
+ }
+
+ /**
+ * @deprecated As of 1.3
+ */
+ public PrettyPrintWriter(Writer writer, String lineIndenter, String newLine) {
+ this(writer, lineIndenter.toCharArray(), newLine);
+ }
+
+ /**
+ * @since 1.3
+ */
+ public PrettyPrintWriter(Writer writer, int mode, String lineIndenter) {
+ this(writer, mode, lineIndenter.toCharArray());
+ }
+
+ public PrettyPrintWriter(Writer writer, String lineIndenter) {
+ this(writer, lineIndenter.toCharArray());
+ }
+
+ /**
+ * @since 1.4
+ */
+ public PrettyPrintWriter(Writer writer, int mode, NameCoder nameCoder) {
+ this(writer, mode, new char[]{' ', ' '}, nameCoder);
+ }
+
+ /**
+ * @since 1.3
+ * @deprecated As of 1.4 use {@link PrettyPrintWriter#PrettyPrintWriter(Writer, int, NameCoder)} instead
+ */
+ public PrettyPrintWriter(Writer writer, int mode, XmlFriendlyReplacer replacer) {
+ this(writer, mode, new char[]{' ', ' '}, replacer);
+ }
+
+ /**
+ * @since 1.4
+ */
+ public PrettyPrintWriter(Writer writer, NameCoder nameCoder) {
+ this(writer, XML_QUIRKS, new char[]{' ', ' '}, nameCoder, "\n");
+ }
+
+ /**
+ * @deprecated As of 1.4 use {@link PrettyPrintWriter#PrettyPrintWriter(Writer, NameCoder)} instead.
+ */
+ public PrettyPrintWriter(Writer writer, XmlFriendlyReplacer replacer) {
+ this(writer, new char[]{' ', ' '}, "\n", replacer);
+ }
+
+ /**
+ * @since 1.3
+ */
+ public PrettyPrintWriter(Writer writer, int mode) {
+ this(writer, mode, new char[]{' ', ' '});
+ }
+
+ public PrettyPrintWriter(Writer writer) {
+ this(writer, new char[]{' ', ' '});
+ }
+
+ public void startNode(String name) {
+ String escapedName = encodeNode(name);
+ tagIsEmpty = false;
+ finishTag();
+ writer.write('<');
+ writer.write(escapedName);
+ elementStack.push(escapedName);
+ tagInProgress = true;
+ depth++ ;
+ readyForNewLine = true;
+ tagIsEmpty = true;
+ }
+
+ public void startNode(String name, Class clazz) {
+ startNode(name);
+ }
+
+ public void setValue(String text) {
+ readyForNewLine = false;
+ tagIsEmpty = false;
+ finishTag();
+
+ writeText(writer, text);
+ }
+
+ public void addAttribute(String key, String value) {
+ writer.write(' ');
+ writer.write(encodeAttribute(key));
+ writer.write('=');
+ writer.write('\"');
+ writeAttributeValue(writer, value);
+ writer.write('\"');
+ }
+
+ protected void writeAttributeValue(QuickWriter writer, String text) {
+ writeText(text, true);
+ }
+
+ protected void writeText(QuickWriter writer, String text) {
+ writeText(text, false);
+ }
+
+ private void writeText(String text, boolean isAttribute) {
+ int length = text.length();
+ for (int i = 0; i < length; i++ ) {
+ char c = text.charAt(i);
+ switch (c) {
+ case '\0':
+ if (mode == XML_QUIRKS) {
+ this.writer.write(NULL);
+ } else {
+ throw new StreamException("Invalid character 0x0 in XML stream");
+ }
+ break;
+ case '&':
+ this.writer.write(AMP);
+ break;
+ case '<':
+ this.writer.write(LT);
+ break;
+ case '>':
+ this.writer.write(GT);
+ break;
+ case '"':
+ this.writer.write(QUOT);
+ break;
+ case '\'':
+ this.writer.write(APOS);
+ break;
+ case '\r':
+ this.writer.write(CR);
+ break;
+ case '\t':
+ case '\n':
+ if (!isAttribute) {
+ this.writer.write(c);
+ break;
+ }
+ default:
+ if (Character.isDefined(c) && !Character.isISOControl(c)) {
+ if (mode != XML_QUIRKS) {
+ if (c > '\ud7ff' && c < '\ue000') {
+ throw new StreamException("Invalid character 0x"
+ + Integer.toHexString(c)
+ + " in XML stream");
+ }
+ }
+ this.writer.write(c);
+ } else {
+ if (mode == XML_1_0) {
+ if (c < 9
+ || c == '\u000b'
+ || c == '\u000c'
+ || c == '\u000e'
+ || (c >= '\u000f' && c <= '\u001f')) {
+ throw new StreamException("Invalid character 0x"
+ + Integer.toHexString(c)
+ + " in XML 1.0 stream");
+ }
+ }
+ if (mode != XML_QUIRKS) {
+ if (c == '\ufffe' || c == '\uffff') {
+ throw new StreamException("Invalid character 0x"
+ + Integer.toHexString(c)
+ + " in XML stream");
+ }
+ }
+ this.writer.write("");
+ this.writer.write(Integer.toHexString(c));
+ this.writer.write(';');
+ }
+ }
+ }
+ }
+
+ public void endNode() {
+ depth-- ;
+ if (tagIsEmpty) {
+ writer.write('/');
+ readyForNewLine = false;
+ finishTag();
+ elementStack.popSilently();
+ } else {
+ finishTag();
+ writer.write(CLOSE);
+ writer.write((String)elementStack.pop());
+ writer.write('>');
+ }
+ readyForNewLine = true;
+ if (depth == 0) {
+ writer.flush();
+ }
+ }
+
+ private void finishTag() {
+ if (tagInProgress) {
+ writer.write('>');
+ }
+ tagInProgress = false;
+ if (readyForNewLine) {
+ endOfLine();
+ }
+ readyForNewLine = false;
+ tagIsEmpty = false;
+ }
+
+ protected void endOfLine() {
+ writer.write(getNewLine());
+ for (int i = 0; i < depth; i++ ) {
+ writer.write(lineIndenter);
+ }
+ }
+
+ public void flush() {
+ writer.flush();
+ }
+
+ public void close() {
+ writer.close();
+ }
+
+ /**
+ * Retrieve the line terminator.
+ *
+ * This method returns always a line feed, since according the XML specification any parser
+ * must ignore a carriage return. Overload this method, if you need different behavior.
+ *
+ * @return the line terminator
+ * @since 1.3
+ */
+ protected String getNewLine() {
+ return newLine;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/QNameMap.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/QNameMap.java
new file mode 100644
index 0000000..e047b13
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/QNameMap.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007 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 01. October 2004 by James Strachan
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import javax.xml.namespace.QName;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Represents a mapping of {@link QName} instances to Java class names
+ * allowing class aliases and namespace aware mappings of QNames to class names.
+ *
+ * @author James Strachan
+ * @version $Revision: 1345 $
+ */
+public class QNameMap {
+
+ // lets make the mapping a no-op unless we specify some mapping
+ private Map qnameToJava;
+ private Map javaToQName;
+ private String defaultPrefix = "";
+ private String defaultNamespace = "";
+
+ /**
+ * Returns the Java class name that should be used for the given QName.
+ * If no explicit mapping has been made then the localPart of the QName is used
+ * which is the normal default in XStream.
+ */
+ public String getJavaClassName(QName qname) {
+ if (qnameToJava != null) {
+ String answer = (String) qnameToJava.get(qname);
+ if (answer != null) {
+ return answer;
+ }
+ }
+ return qname.getLocalPart();
+ }
+
+ /**
+ * Returns the Java class name that should be used for the given QName.
+ * If no explicit mapping has been made then the localPart of the QName is used
+ * which is the normal default in XStream.
+ */
+ public QName getQName(String javaClassName) {
+ if (javaToQName != null) {
+ QName answer = (QName) javaToQName.get(javaClassName);
+ if (answer != null) {
+ return answer;
+ }
+ }
+ return new QName(defaultNamespace, javaClassName, defaultPrefix);
+ }
+
+ /**
+ * Registers the mapping of the Java class name to the QName
+ */
+ public synchronized void registerMapping(QName qname, String javaClassName) {
+ if (javaToQName == null) {
+ javaToQName = Collections.synchronizedMap(new HashMap());
+ }
+ if (qnameToJava == null) {
+ qnameToJava = Collections.synchronizedMap(new HashMap());
+ }
+ javaToQName.put(javaClassName, qname);
+ qnameToJava.put(qname, javaClassName);
+ }
+
+ /**
+ * Registers the mapping of the type to the QName
+ */
+ public synchronized void registerMapping(QName qname, Class type) {
+ registerMapping(qname, type.getName());
+ }
+
+ public String getDefaultNamespace() {
+ return defaultNamespace;
+ }
+
+ public void setDefaultNamespace(String defaultNamespace) {
+ this.defaultNamespace = defaultNamespace;
+ }
+
+ public String getDefaultPrefix() {
+ return defaultPrefix;
+ }
+
+ public void setDefaultPrefix(String defaultPrefix) {
+ this.defaultPrefix = defaultPrefix;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/SaxWriter.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/SaxWriter.java
new file mode 100644
index 0000000..e4c9852
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/SaxWriter.java
@@ -0,0 +1,728 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2011, 2013 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 14. August 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.io.StreamException;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.DTDHandler;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXNotRecognizedException;
+import org.xml.sax.SAXNotSupportedException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.AttributesImpl;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A SAX {@link org.xml.sax.XMLReader parser} that acts as an XStream
+ * {@link com.thoughtworks.xstream.io.HierarchicalStreamWriter} to enable direct generation of a
+ * SAX event flow from the XStream serialization of a list of list of Java objects.
+ *
+ * As a
+ * custom SAX parser, this class ignores the arguments of the two standard parse methods ({@link #parse(java.lang.String)}
+ * and {@link #parse(org.xml.sax.InputSource)}) but relies on a proprietary SAX property
+ * {@link #SOURCE_OBJECT_LIST_PROPERTY} to define the list of objects to serialize.
+ *
+ *
+ * Configuration of this SAX parser is achieved through the standard
+ * {@link #setProperty SAX property mechanism}. While specific setter methods require direct
+ * access to the parser instance, SAX properties support configuration settings to be propagated
+ * through a chain of {@link org.xml.sax.XMLFilter filters} down to the underlying parser
+ * object.
+ *
+ *
+ * This mechanism shall be used to configure the
+ * {@link #SOURCE_OBJECT_LIST_PROPERTY objects to be serialized} as well as the
+ * {@link #CONFIGURED_XSTREAM_PROPERTY XStream facade}.
+ *
+ *
+ * @author Laurent Bihanic
+ */
+public final class SaxWriter extends AbstractXmlWriter implements XMLReader {
+ /**
+ * The {@link #setProperty SAX property} to configure the XStream
+ * facade to be used for object serialization. If the property
+ * is not set, a new XStream facade will be allocated for each
+ * parse.
+ */
+ public final static String CONFIGURED_XSTREAM_PROPERTY =
+ "http://com.thoughtworks.xstream/sax/property/configured-xstream";
+
+ /**
+ * The {@link #setProperty SAX property} to configure the list of
+ * Java objects to serialize. Setting this property prior
+ * invoking one of the parse() methods is mandatory.
+ *
+ * @see #parse(java.lang.String)
+ * @see #parse(org.xml.sax.InputSource)
+ */
+ public final static String SOURCE_OBJECT_LIST_PROPERTY =
+ "http://com.thoughtworks.xstream/sax/property/source-object-list";
+
+ //=========================================================================
+ // SAX XMLReader interface support
+ //=========================================================================
+
+ /**
+ * The SAX EntityResolver associated to this XMLReader.
+ */
+ private EntityResolver entityResolver = null;
+
+ /**
+ * The SAX DTDHandler associated to this XMLReader.
+ */
+ private DTDHandler dtdHandler = null;
+
+ /**
+ * The SAX ContentHandler associated to this XMLReader.
+ */
+ private ContentHandler contentHandler = null;
+
+ /**
+ * The SAX ErrorHandler associated to this XMLReader.
+ */
+ private ErrorHandler errorHandler = null;
+
+ /**
+ * The SAX features defined for this XMLReader.
+ *
+ * This class does not define any feature (yet) and ignores
+ * the SAX mandatory feature. Thus, this member is present
+ * only to support the mandatory feature setting and retrieval
+ * logic defined by SAX.
+ */
+ private Map features = new HashMap();
+
+ /**
+ * The SAX properties defined for this XMLReader.
+ */
+ private final Map properties = new HashMap();
+
+ private final boolean includeEnclosingDocument;
+
+ /**
+ * @since 1.4
+ */
+ public SaxWriter(NameCoder nameCoder)
+ {
+ this(true, nameCoder);
+ }
+
+ /**
+ * @since 1.4
+ */
+ public SaxWriter(boolean includeEnclosingDocument, NameCoder nameCoder)
+ {
+ super(nameCoder);
+ this.includeEnclosingDocument = includeEnclosingDocument;
+ }
+
+ /**
+ * @deprecated As of 1.4 use {@link SaxWriter#SaxWriter(NameCoder)} instead.
+ */
+ public SaxWriter(XmlFriendlyReplacer replacer)
+ {
+ this(true, replacer);
+ }
+
+ /**
+ * @deprecated As of 1.4 use {@link SaxWriter#SaxWriter(boolean, NameCoder)} instead.
+ */
+ public SaxWriter(boolean includeEnclosingDocument, XmlFriendlyReplacer replacer)
+ {
+ this(includeEnclosingDocument, (NameCoder)replacer);
+ }
+
+ public SaxWriter(boolean includeEnclosingDocument) {
+ this(includeEnclosingDocument, new XmlFriendlyNameCoder());
+ }
+
+ public SaxWriter() {
+ this(true);
+ }
+
+ //-------------------------------------------------------------------------
+ // Configuration
+ //-------------------------------------------------------------------------
+
+ /**
+ * Sets the state of a feature.
+ *
+ * The feature name is any fully-qualified URI.
+ *
+ * All XMLReaders are required to support setting
+ * http://xml.org/sax/features/namespaces to
+ * true and
+ * http://xml.org/sax/features/namespace-prefixes to
+ * false.
+ *
+ * Some feature values may be immutable or mutable only
+ * in specific contexts, such as before, during, or after
+ * a parse.
+ *
+ * Note: This implementation only supports the two
+ * mandatory SAX features.
+ *
+ * @param name the feature name, which is a fully-qualified URI.
+ * @param value the requested state of the feature (true or false).
+ * @throws SAXNotRecognizedException when the XMLReader does not
+ * recognize the feature name.
+ * @see #getFeature
+ */
+ public void setFeature(String name, boolean value)
+ throws SAXNotRecognizedException {
+ if ((name.equals("http://xml.org/sax/features/namespaces")) ||
+ (name.equals("http://xml.org/sax/features/namespace-prefixes"))) {
+ this.features.put(name, value ? Boolean.TRUE : Boolean.FALSE); // JDK 1.3 friendly
+ } else {
+ throw new SAXNotRecognizedException(name);
+ }
+ }
+
+ /**
+ * Looks up the value of a feature.
+ *
+ * The feature name is any fully-qualified URI. It is
+ * possible for an XMLReader to recognize a feature name but
+ * to be unable to return its value; this is especially true
+ * in the case of an adapter for a SAX1 Parser, which has
+ * no way of knowing whether the underlying parser is
+ * performing validation or expanding external entities.
+ *
+ * All XMLReaders are required to recognize the
+ * http://xml.org/sax/features/namespaces and the
+ * http://xml.org/sax/features/namespace-prefixes feature
+ * names.
+ *
+ * Some feature values may be available only in specific
+ * contexts, such as before, during, or after a parse.
+ *
+ * Implementors are free (and encouraged) to invent their own
+ * features, using names built on their own URIs.
+ *
+ * @param name the feature name, which is a fully-qualified URI.
+ * @return the current state of the feature (true or false).
+ * @throws SAXNotRecognizedException when the XMLReader does not
+ * recognize the feature name.
+ * @see #setFeature
+ */
+ public boolean getFeature(String name)
+ throws SAXNotRecognizedException {
+ if ((name.equals("http://xml.org/sax/features/namespaces")) ||
+ (name.equals("http://xml.org/sax/features/namespace-prefixes"))) {
+ Boolean value = (Boolean) (this.features.get(name));
+
+ if (value == null) {
+ value = Boolean.FALSE;
+ }
+ return value.booleanValue();
+ } else {
+ throw new SAXNotRecognizedException(name);
+ }
+ }
+
+ /**
+ * Sets the value of a property.
+ *
+ * The property name is any fully-qualified URI. It is
+ * possible for an XMLReader to recognize a property name but
+ * to be unable to set its value.
+ *
+ * XMLReaders are not required to recognize setting any
+ * specific property names, though a core set is provided with
+ * SAX2.
+ *
+ * Some property values may be immutable or mutable only
+ * in specific contexts, such as before, during, or after
+ * a parse.
+ *
+ * This method is also the standard mechanism for setting
+ * extended handlers.
+ *
+ * Note: This implementation only supports two
+ * (proprietary) properties: {@link #CONFIGURED_XSTREAM_PROPERTY}
+ * and {@link #SOURCE_OBJECT_LIST_PROPERTY}.
+ *
+ * @param name the property name, which is a fully-qualified URI.
+ * @param value the requested value for the property.
+ * @throws SAXNotRecognizedException when the XMLReader does not
+ * recognize the property name.
+ * @throws SAXNotSupportedException when the XMLReader recognizes
+ * the property name but cannot set
+ * the requested value.
+ * @see #getProperty
+ */
+ public void setProperty(String name, Object value)
+ throws SAXNotRecognizedException,
+ SAXNotSupportedException {
+ if (name.equals(CONFIGURED_XSTREAM_PROPERTY)) {
+ if (!(value instanceof XStream)) {
+ throw new SAXNotSupportedException("Value for property \"" +
+ CONFIGURED_XSTREAM_PROPERTY +
+ "\" must be a non-null XStream object");
+ }
+ } else if (name.equals(SOURCE_OBJECT_LIST_PROPERTY)) {
+ if (value instanceof List) {
+ List list = (List) value;
+
+ if (list.isEmpty()) {
+ throw new SAXNotSupportedException("Value for property \"" +
+ SOURCE_OBJECT_LIST_PROPERTY +
+ "\" shall not be an empty list");
+ } else {
+ // Perform a copy of the list to prevent the application to
+ // modify its content while the parse is being performed.
+ value = Collections.unmodifiableList(new ArrayList(list));
+ }
+ } else {
+ throw new SAXNotSupportedException("Value for property \"" +
+ SOURCE_OBJECT_LIST_PROPERTY +
+ "\" must be a non-null List object");
+ }
+ } else {
+ throw new SAXNotRecognizedException(name);
+ }
+ this.properties.put(name, value);
+ }
+
+ /**
+ * Looks up the value of a property.
+ *
+ * The property name is any fully-qualified URI. It is
+ * possible for an XMLReader to recognize a property name but
+ * to be unable to return its state.
+ *
+ * XMLReaders are not required to recognize any specific
+ * property names, though an initial core set is documented for
+ * SAX2.
+ *
+ * Some property values may be available only in specific
+ * contexts, such as before, during, or after a parse.
+ *
+ * Implementors are free (and encouraged) to invent their own properties,
+ * using names built on their own URIs.
+ *
+ * @param name the property name, which is a fully-qualified URI.
+ * @return the current value of the property.
+ * @throws SAXNotRecognizedException when the XMLReader does not
+ * recognize the property name.
+ * @see #getProperty
+ */
+ public Object getProperty(String name)
+ throws SAXNotRecognizedException {
+ if ((name.equals(CONFIGURED_XSTREAM_PROPERTY)) ||
+ (name.equals(SOURCE_OBJECT_LIST_PROPERTY))) {
+ return this.properties.get(name);
+ } else {
+ throw new SAXNotRecognizedException(name);
+ }
+ }
+
+ //---------------------------------------------------------------------
+ // Event handlers
+ //---------------------------------------------------------------------
+
+ /**
+ * Allows an application to register an entity resolver.
+ *
+ * If the application does not register an entity resolver,
+ * the XMLReader will perform its own default resolution.
+ *
+ * Applications may register a new or different resolver in the
+ * middle of a parse, and the SAX parser must begin using the new
+ * resolver immediately.
+ *
+ * @param resolver the entity resolver.
+ * @throws NullPointerException if the resolver argument is
+ * null.
+ * @see #getEntityResolver
+ */
+ public void setEntityResolver(EntityResolver resolver) {
+ if (resolver == null) {
+ throw new NullPointerException("resolver");
+ }
+ this.entityResolver = resolver;
+ return;
+ }
+
+ /**
+ * Returns the current entity resolver.
+ *
+ * @return the current entity resolver, or null if none
+ * has been registered.
+ * @see #setEntityResolver
+ */
+ public EntityResolver getEntityResolver() {
+ return this.entityResolver;
+ }
+
+ /**
+ * Allows an application to register a DTD event handler.
+ *
+ * If the application does not register a DTD handler, all DTD
+ * events reported by the SAX parser will be silently ignored.
+ *
+ * Applications may register a new or different handler in the
+ * middle of a parse, and the SAX parser must begin using the new
+ * handler immediately.
+ *
+ * @param handler the DTD handler.
+ * @throws NullPointerException if the handler argument is
+ * null.
+ * @see #getDTDHandler
+ */
+ public void setDTDHandler(DTDHandler handler) {
+ if (handler == null) {
+ throw new NullPointerException("handler");
+ }
+ this.dtdHandler = handler;
+ return;
+ }
+
+ /**
+ * Returns the current DTD handler.
+ *
+ * @return the current DTD handler, or null if none
+ * has been registered.
+ * @see #setDTDHandler
+ */
+ public DTDHandler getDTDHandler() {
+ return this.dtdHandler;
+ }
+
+ /**
+ * Allows an application to register a content event handler.
+ *
+ * If the application does not register a content handler, all
+ * content events reported by the SAX parser will be silently
+ * ignored.
+ *
+ * Applications may register a new or different handler in the
+ * middle of a parse, and the SAX parser must begin using the new
+ * handler immediately.
+ *
+ * @param handler the content handler.
+ * @throws NullPointerException if the handler argument is
+ * null.
+ * @see #getContentHandler
+ */
+ public void setContentHandler(ContentHandler handler) {
+ if (handler == null) {
+ throw new NullPointerException("handler");
+ }
+ this.contentHandler = handler;
+ return;
+ }
+
+ /**
+ * Returns the current content handler.
+ *
+ * @return the current content handler, or null if none
+ * has been registered.
+ * @see #setContentHandler
+ */
+ public ContentHandler getContentHandler() {
+ return this.contentHandler;
+ }
+
+ /**
+ * Allows an application to register an error event handler.
+ *
+ * If the application does not register an error handler, all
+ * error events reported by the SAX parser will be silently
+ * ignored; however, normal processing may not continue. It is
+ * highly recommended that all SAX applications implement an
+ * error handler to avoid unexpected bugs.
+ *
+ * Applications may register a new or different handler in the
+ * middle of a parse, and the SAX parser must begin using the new
+ * handler immediately.
+ *
+ * @param handler the error handler.
+ * @throws NullPointerException if the handler argument is
+ * null.
+ * @see #getErrorHandler
+ */
+ public void setErrorHandler(ErrorHandler handler) {
+ if (handler == null) {
+ throw new NullPointerException("handler");
+ }
+ this.errorHandler = handler;
+ return;
+ }
+
+ /**
+ * Returns the current error handler.
+ *
+ * @return the current error handler, or null if none
+ * has been registered.
+ * @see #setErrorHandler
+ */
+ public ErrorHandler getErrorHandler() {
+ return this.errorHandler;
+ }
+
+ //---------------------------------------------------------------------
+ // Parsing
+ //---------------------------------------------------------------------
+
+ /**
+ * Parses an XML document from a system identifier (URI).
+ *
+ * This method is a shortcut for the common case of reading a
+ * document from a system identifier. It is the exact
+ * equivalent of the following:
+ *
+ *
+ * parse(new InputSource(systemId));
+ *
+ *
+ *
+ * If the system identifier is a URL, it must be fully resolved
+ * by the application before it is passed to the parser.
+ *
+ * Note: As a custom SAX parser, this class
+ * ignores the systemId argument of this method
+ * and relies on the proprietary SAX property
+ * {@link #SOURCE_OBJECT_LIST_PROPERTY}) to define the list of
+ * objects to serialize.
+ *
+ * @param systemId the system identifier (URI).
+ * @throws SAXException Any SAX exception, possibly wrapping
+ * another exception.
+ * @see #parse(org.xml.sax.InputSource)
+ */
+ public void parse(String systemId) throws SAXException {
+ this.parse();
+ }
+
+ /**
+ * Parse an XML document.
+ *
+ * The application can use this method to instruct the XML
+ * reader to begin parsing an XML document from any valid input
+ * source (a character stream, a byte stream, or a URI).
+ *
+ * Applications may not invoke this method while a parse is in
+ * progress (they should create a new XMLReader instead for each
+ * nested XML document). Once a parse is complete, an
+ * application may reuse the same XMLReader object, possibly
+ * with a different input source.
+ *
+ * During the parse, the XMLReader will provide information
+ * about the XML document through the registered event
+ * handlers.
+ *
+ * This method is synchronous: it will not return until parsing
+ * has ended. If a client application wants to terminate
+ * parsing early, it should throw an exception.
+ *
+ * Note: As a custom SAX parser, this class
+ * ignores the source argument of this method
+ * and relies on the proprietary SAX property
+ * {@link #SOURCE_OBJECT_LIST_PROPERTY}) to define the list of
+ * objects to serialize.
+ *
+ * @param input The input source for the top-level of the
+ * XML document.
+ * @throws SAXException Any SAX exception, possibly wrapping
+ * another exception.
+ * @see org.xml.sax.InputSource
+ * @see #parse(java.lang.String)
+ * @see #setEntityResolver
+ * @see #setDTDHandler
+ * @see #setContentHandler
+ * @see #setErrorHandler
+ */
+ public void parse(InputSource input) throws SAXException {
+ this.parse();
+ }
+
+ /**
+ * Serializes the Java objects of the configured list into a flow
+ * of SAX events.
+ *
+ * @throws SAXException if the configured object list is invalid
+ * or object serialization failed.
+ */
+ private void parse() throws SAXException {
+ XStream xstream = (XStream) (this.properties.get(CONFIGURED_XSTREAM_PROPERTY));
+ if (xstream == null) {
+ xstream = new XStream();
+ }
+
+ List source = (List) (this.properties.get(SOURCE_OBJECT_LIST_PROPERTY));
+ if ((source == null) || (source.isEmpty())) {
+ throw new SAXException("Missing or empty source object list. Setting property \"" +
+ SOURCE_OBJECT_LIST_PROPERTY + "\" is mandatory");
+ }
+
+ try {
+ this.startDocument(true);
+ for (Iterator i = source.iterator(); i.hasNext();) {
+ xstream.marshal(i.next(), this);
+ }
+ this.endDocument(true);
+ } catch (StreamException e) {
+ if (e.getCause() instanceof SAXException) {
+ throw (SAXException) (e.getCause());
+ } else {
+ throw new SAXException(e);
+ }
+ }
+ }
+
+
+ //=========================================================================
+ // XStream HierarchicalStreamWriter interface support
+ //=========================================================================
+
+ private int depth = 0;
+ private List elementStack = new LinkedList();
+ private char[] buffer = new char[128];
+ private boolean startTagInProgress = false;
+ private final AttributesImpl attributeList = new AttributesImpl();
+
+ public void startNode(String name) {
+ try {
+ if (this.depth != 0) {
+ this.flushStartTag();
+ } else if (includeEnclosingDocument) {
+ this.startDocument(false);
+ }
+ this.elementStack.add(0, escapeXmlName(name));
+
+ this.startTagInProgress = true;
+ this.depth++;
+ } catch (SAXException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public void addAttribute(String name, String value) {
+ if (this.startTagInProgress) {
+ String escapedName = escapeXmlName(name);
+ this.attributeList.addAttribute("", escapedName, escapedName, "CDATA", value);
+ } else {
+ throw new StreamException(new IllegalStateException("No startElement being processed"));
+ }
+ }
+
+ public void setValue(String text) {
+ try {
+ this.flushStartTag();
+
+ int lg = text.length();
+ if (lg > buffer.length) {
+ buffer = new char[lg];
+ }
+ text.getChars(0, lg, buffer, 0);
+
+ this.contentHandler.characters(buffer, 0, lg);
+ } catch (SAXException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public void endNode() {
+ try {
+ this.flushStartTag();
+
+ String tagName = (String) (this.elementStack.remove(0));
+
+ this.contentHandler.endElement("", tagName, tagName);
+
+ this.depth--;
+ if (this.depth == 0 && includeEnclosingDocument) {
+ this.endDocument(false);
+ }
+ } catch (SAXException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ /**
+ * Fires the SAX startDocument event towards the configured
+ * ContentHandler.
+ *
+ * @param multiObjectMode whether serialization of several
+ * object will be merge into a single
+ * SAX document.
+ * @throws SAXException if thrown by the ContentHandler.
+ */
+ private void startDocument(boolean multiObjectMode) throws SAXException {
+ if (this.depth == 0) {
+ // Notify contentHandler of document start.
+ this.contentHandler.startDocument();
+
+ if (multiObjectMode) {
+ // Prevent marshalling of each object to fire its own
+ // start/endDocument events.
+ this.depth++;
+ }
+ }
+ }
+
+ /**
+ * Fires the SAX endDocument event towards the configured
+ * ContentHandler.
+ *
+ * @param multiObjectMode whether serialization of several
+ * object will be merge into a single
+ * SAX document.
+ * @throws SAXException if thrown by the ContentHandler.
+ */
+ private void endDocument(boolean multiObjectMode) throws SAXException {
+ if ((this.depth == 0) || ((this.depth == 1) && (multiObjectMode))) {
+ this.contentHandler.endDocument();
+ this.depth = 0;
+ }
+ }
+
+ /**
+ * Fires any pending SAX startElement event towards the
+ * configured ContentHandler.
+ *
+ * @throws SAXException if thrown by the ContentHandler.
+ */
+ private void flushStartTag() throws SAXException {
+ if (this.startTagInProgress) {
+ String tagName = (String) (this.elementStack.get(0));
+
+ this.contentHandler.startElement("", tagName,
+ tagName, this.attributeList);
+ this.attributeList.clear();
+ this.startTagInProgress = false;
+ }
+ }
+
+ public void flush() {
+ // don't need to do anything
+ }
+
+ public void close() {
+ // don't need to do anything
+ }
+}
+
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/SjsxpDriver.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/SjsxpDriver.java
new file mode 100644
index 0000000..26d8009
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/SjsxpDriver.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2009, 2011, 2013 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 29. April 2009 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.io.StreamException;
+
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLOutputFactory;
+
+/**
+ * A driver using the JDK 6 StAX implementation of Sun.
+ *
+ * @author Jörg Schaible
+ * @since 1.4
+ * @deprecated As of 1.4.5 use {@link StandardStaxDriver}
+ */
+public class SjsxpDriver extends StaxDriver {
+
+ /**
+ * @deprecated As of 1.4.5 use {@link StandardStaxDriver#StandardStaxDriver()}
+ */
+ public SjsxpDriver() {
+ super();
+ }
+
+ /**
+ * @deprecated As of 1.4.5 use {@link StandardStaxDriver#StandardStaxDriver(QNameMap, XmlFriendlyNameCoder)}
+ */
+ public SjsxpDriver(QNameMap qnameMap, XmlFriendlyNameCoder nameCoder) {
+ super(qnameMap, nameCoder);
+ }
+
+ /**
+ * @deprecated As of 1.4.5 use {@link StandardStaxDriver#StandardStaxDriver(QNameMap)}
+ */
+ public SjsxpDriver(QNameMap qnameMap) {
+ super(qnameMap);
+ }
+
+ /**
+ * @deprecated As of 1.4.5 use {@link StandardStaxDriver#StandardStaxDriver(XmlFriendlyNameCoder)}
+ */
+ public SjsxpDriver(XmlFriendlyNameCoder nameCoder) {
+ super(nameCoder);
+ }
+
+ /**
+ * @deprecated As of 1.4.5 use {@link StandardStaxDriver#createInputFactory()}
+ */
+ protected XMLInputFactory createInputFactory() {
+ Exception exception = null;
+ try {
+ return (XMLInputFactory)Class.forName("com.sun.xml.internal.stream.XMLInputFactoryImpl").newInstance();
+ } catch (InstantiationException e) {
+ exception = e;
+ } catch (IllegalAccessException e) {
+ exception = e;
+ } catch (ClassNotFoundException e) {
+ exception = e;
+ }
+ throw new StreamException("Cannot create SJSXP (Sun JDK 6 StAX) XMLInputFaqctory instance.", exception);
+ }
+
+ /**
+ * @deprecated As of 1.4.5 use {@link StandardStaxDriver#createOutputFactory()}
+ */
+ protected XMLOutputFactory createOutputFactory() {
+ Exception exception = null;
+ try {
+ return (XMLOutputFactory)Class.forName("com.sun.xml.internal.stream.XMLOutputFactoryImpl").newInstance();
+ } catch (InstantiationException e) {
+ exception = e;
+ } catch (IllegalAccessException e) {
+ exception = e;
+ } catch (ClassNotFoundException e) {
+ exception = e;
+ }
+ throw new StreamException("Cannot create SJSXP (Sun JDK 6 StAX) XMLOutputFaqctory instance.", exception);
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/StandardStaxDriver.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/StandardStaxDriver.java
new file mode 100644
index 0000000..1fe744d
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/StandardStaxDriver.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2013 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 27. July 2013 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.core.JVM;
+import com.thoughtworks.xstream.io.StreamException;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLOutputFactory;
+
+
+/**
+ * A driver using the standard JDK StAX implementation provided by the Java runtime (since Java
+ * 6).
+ *
+ * In contrast to XMLInputFactory.newFactory() or XMLOutputFactory.newFactory() this
+ * implementation will ignore any implementations provided with the system properties
+ * javax.xml.stream.XMLInputFactory and javax.xml.stream.XMLOutputFactory, all
+ * implementations configured in lib/stax.properties or registered with the Service
+ * API.
+ *
+ *
+ * @author Jörg Schaible
+ * @since 1.4.5
+ */
+public class StandardStaxDriver extends StaxDriver {
+
+ public StandardStaxDriver() {
+ super();
+ }
+
+ /**
+ * @deprecated As of 1.4.6 use {@link #StandardStaxDriver(QNameMap, NameCoder)}
+ */
+ public StandardStaxDriver(QNameMap qnameMap, XmlFriendlyNameCoder nameCoder) {
+ super(qnameMap, nameCoder);
+ }
+
+ /**
+ * @since 1.4.6
+ */
+ public StandardStaxDriver(QNameMap qnameMap, NameCoder nameCoder) {
+ super(qnameMap, nameCoder);
+ }
+
+ public StandardStaxDriver(QNameMap qnameMap) {
+ super(qnameMap);
+ }
+
+ /**
+ * @deprecated As of 1.4.6 use {@link #StandardStaxDriver(NameCoder)}
+ */
+ public StandardStaxDriver(XmlFriendlyNameCoder nameCoder) {
+ super(nameCoder);
+ }
+
+ /**
+ * @since 1.4.6
+ */
+ public StandardStaxDriver(NameCoder nameCoder) {
+ super(nameCoder);
+ }
+
+ protected XMLInputFactory createInputFactory() {
+ Exception exception = null;
+ try {
+ Class staxInputFactory = JVM.getStaxInputFactory();
+ if (staxInputFactory != null) {
+ return (XMLInputFactory)staxInputFactory.newInstance();
+ } else {
+ throw new StreamException("Java runtime has no standard XMLInputFactory implementation.", exception);
+ }
+ } catch (InstantiationException e) {
+ exception = e;
+ } catch (IllegalAccessException e) {
+ exception = e;
+ } catch (ClassNotFoundException e) {
+ exception = e;
+ }
+ throw new StreamException("Cannot create standard XMLInputFactory instance of Java runtime.", exception);
+ }
+
+ protected XMLOutputFactory createOutputFactory() {
+ Exception exception = null;
+ try {
+ Class staxOutputFactory = JVM.getStaxOutputFactory();
+ if (staxOutputFactory != null) {
+ return (XMLOutputFactory)staxOutputFactory.newInstance();
+ } else {
+ throw new StreamException("Java runtime has no standard XMLOutputFactory implementation.", exception);
+ }
+ } catch (InstantiationException e) {
+ exception = e;
+ } catch (IllegalAccessException e) {
+ exception = e;
+ } catch (ClassNotFoundException e) {
+ exception = e;
+ }
+ throw new StreamException("Cannot create standard XMLOutputFactory instance of Java runtime.", exception);
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/StaxDriver.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/StaxDriver.java
new file mode 100644
index 0000000..b4c0086
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/StaxDriver.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2011, 2013 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 29. September 2004 by James Strachan
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.URL;
+
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.stream.XMLStreamWriter;
+import javax.xml.transform.Source;
+import javax.xml.transform.stream.StreamSource;
+
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.io.ReaderWrapper;
+import com.thoughtworks.xstream.io.StreamException;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+/**
+ * A driver using the StAX API to create XML reader and writer.
+ *
+ * @author James Strachan
+ * @author Jörg Schaible
+ * @version $Revision: 2116 $
+ */
+public class StaxDriver extends AbstractXmlDriver {
+
+ private QNameMap qnameMap;
+ private XMLInputFactory inputFactory;
+ private XMLOutputFactory outputFactory;
+
+ public StaxDriver() {
+ this(new QNameMap());
+ }
+
+ public StaxDriver(QNameMap qnameMap) {
+ this(qnameMap, new XmlFriendlyNameCoder());
+ }
+
+ /**
+ * @since 1.4
+ */
+ public StaxDriver(QNameMap qnameMap, NameCoder nameCoder) {
+ super(nameCoder);
+ this.qnameMap = qnameMap;
+ }
+
+ /**
+ * @since 1.4
+ */
+ public StaxDriver(NameCoder nameCoder) {
+ this(new QNameMap(), nameCoder);
+ }
+
+ /**
+ * @since 1.2
+ * @deprecated As of 1.4, use {@link StaxDriver#StaxDriver(QNameMap, NameCoder)} instead.
+ */
+ public StaxDriver(QNameMap qnameMap, XmlFriendlyReplacer replacer) {
+ this(qnameMap, (NameCoder)replacer);
+ }
+
+ /**
+ * @since 1.2
+ * @deprecated As of 1.4, use {@link StaxDriver#StaxDriver(NameCoder)} instead.
+ */
+ public StaxDriver(XmlFriendlyReplacer replacer) {
+ this(new QNameMap(), (NameCoder)replacer);
+ }
+
+ public HierarchicalStreamReader createReader(Reader xml) {
+ try {
+ return createStaxReader(createParser(xml));
+ } catch (XMLStreamException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public HierarchicalStreamReader createReader(InputStream in) {
+ try {
+ return createStaxReader(createParser(in));
+ } catch (XMLStreamException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public HierarchicalStreamReader createReader(URL in) {
+ final InputStream stream;
+ try {
+ stream = in.openStream();
+ HierarchicalStreamReader reader = createStaxReader(createParser(new StreamSource(
+ stream, in.toExternalForm())));
+ return new ReaderWrapper(reader) {
+
+ public void close() {
+ super.close();
+ try {
+ stream.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ };
+ } catch (XMLStreamException e) {
+ throw new StreamException(e);
+ } catch (IOException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public HierarchicalStreamReader createReader(File in) {
+ final InputStream stream;
+ try {
+ stream = new FileInputStream(in);
+ HierarchicalStreamReader reader = createStaxReader(createParser(new StreamSource(
+ stream, in.toURI().toASCIIString())));
+ return new ReaderWrapper(reader) {
+
+ public void close() {
+ super.close();
+ try {
+ stream.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ };
+ } catch (XMLStreamException e) {
+ throw new StreamException(e);
+ } catch (FileNotFoundException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public HierarchicalStreamWriter createWriter(Writer out) {
+ try {
+ return createStaxWriter(getOutputFactory().createXMLStreamWriter(out));
+ }
+ catch (XMLStreamException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public HierarchicalStreamWriter createWriter(OutputStream out) {
+ try {
+ return createStaxWriter(getOutputFactory().createXMLStreamWriter(out));
+ }
+ catch (XMLStreamException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public AbstractPullReader createStaxReader(XMLStreamReader in) {
+ return new StaxReader(qnameMap, in, getNameCoder());
+ }
+
+ public StaxWriter createStaxWriter(XMLStreamWriter out, boolean writeStartEndDocument) throws XMLStreamException {
+ return new StaxWriter(qnameMap, out, writeStartEndDocument, isRepairingNamespace(), getNameCoder());
+ }
+
+ public StaxWriter createStaxWriter(XMLStreamWriter out) throws XMLStreamException {
+ return createStaxWriter(out, true);
+ }
+
+
+ // Properties
+ //-------------------------------------------------------------------------
+ public QNameMap getQnameMap() {
+ return qnameMap;
+ }
+
+ public void setQnameMap(QNameMap qnameMap) {
+ this.qnameMap = qnameMap;
+ }
+
+ public XMLInputFactory getInputFactory() {
+ if (inputFactory == null) {
+ inputFactory = createInputFactory();
+ }
+ return inputFactory;
+ }
+
+ public XMLOutputFactory getOutputFactory() {
+ if (outputFactory == null) {
+ outputFactory = createOutputFactory();
+ }
+ return outputFactory;
+ }
+
+ public boolean isRepairingNamespace() {
+ return Boolean.TRUE.equals(getOutputFactory().getProperty(
+ XMLOutputFactory.IS_REPAIRING_NAMESPACES));
+ }
+
+ /**
+ * @since 1.2
+ */
+ public void setRepairingNamespace(boolean repairing) {
+ getOutputFactory().setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES,
+ repairing ? Boolean.TRUE : Boolean.FALSE);
+ }
+
+
+ // Implementation methods
+ //-------------------------------------------------------------------------
+ protected XMLStreamReader createParser(Reader xml) throws XMLStreamException {
+ return getInputFactory().createXMLStreamReader(xml);
+ }
+
+ protected XMLStreamReader createParser(InputStream xml) throws XMLStreamException {
+ return getInputFactory().createXMLStreamReader(xml);
+ }
+
+ protected XMLStreamReader createParser(Source source) throws XMLStreamException {
+ return getInputFactory().createXMLStreamReader(source);
+ }
+
+ /**
+ * @since 1.4
+ */
+ protected XMLInputFactory createInputFactory() {
+ return XMLInputFactory.newInstance();
+ }
+
+ /**
+ * @since 1.4
+ */
+ protected XMLOutputFactory createOutputFactory() {
+ return XMLOutputFactory.newInstance();
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/StaxReader.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/StaxReader.java
new file mode 100644
index 0000000..08fe718
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/StaxReader.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2011 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 29. September 2004 by James Strachan
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.converters.ErrorWriter;
+import com.thoughtworks.xstream.io.StreamException;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+
+/**
+ * A reader using the StAX API.
+ *
+ * @author James Strachan
+ * @version $Revision: 1861 $
+ */
+public class StaxReader extends AbstractPullReader {
+
+ private final QNameMap qnameMap;
+ private final XMLStreamReader in;
+
+ public StaxReader(QNameMap qnameMap, XMLStreamReader in) {
+ this(qnameMap, in, new XmlFriendlyNameCoder());
+ }
+
+ /**
+ * @since 1.4
+ */
+ public StaxReader(QNameMap qnameMap, XMLStreamReader in, NameCoder replacer) {
+ super(replacer);
+ this.qnameMap = qnameMap;
+ this.in = in;
+ moveDown();
+ }
+
+ /**
+ * @since 1.2
+ * @deprecated As of 1.4 use {@link StaxReader#StaxReader(QNameMap, XMLStreamReader, NameCoder)} instead.
+ */
+ public StaxReader(QNameMap qnameMap, XMLStreamReader in, XmlFriendlyReplacer replacer) {
+ this(qnameMap, in, (NameCoder)replacer);
+ }
+
+ protected int pullNextEvent() {
+ try {
+ switch(in.next()) {
+ case XMLStreamConstants.START_DOCUMENT:
+ case XMLStreamConstants.START_ELEMENT:
+ return START_NODE;
+ case XMLStreamConstants.END_DOCUMENT:
+ case XMLStreamConstants.END_ELEMENT:
+ return END_NODE;
+ case XMLStreamConstants.CHARACTERS:
+ return TEXT;
+ case XMLStreamConstants.COMMENT:
+ return COMMENT;
+ default:
+ return OTHER;
+ }
+ } catch (XMLStreamException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ protected String pullElementName() {
+ // let the QNameMap handle any mapping of QNames to Java class names
+ QName qname = in.getName();
+ return qnameMap.getJavaClassName(qname);
+ }
+
+ protected String pullText() {
+ return in.getText();
+ }
+
+ public String getAttribute(String name) {
+ return in.getAttributeValue(null, encodeAttribute(name));
+ }
+
+ public String getAttribute(int index) {
+ return in.getAttributeValue(index);
+ }
+
+ public int getAttributeCount() {
+ return in.getAttributeCount();
+ }
+
+ public String getAttributeName(int index) {
+ return decodeAttribute(in.getAttributeLocalName(index));
+ }
+
+ public void appendErrors(ErrorWriter errorWriter) {
+ errorWriter.add("line number", String.valueOf(in.getLocation().getLineNumber()));
+ }
+
+ public void close() {
+ try {
+ in.close();
+ } catch (XMLStreamException e) {
+ throw new StreamException(e);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/StaxWriter.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/StaxWriter.java
new file mode 100644
index 0000000..79410cc
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/StaxWriter.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 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 29. September 2004 by James Strachan
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.io.StreamException;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+
+
+/**
+ * A stream writing that outputs to a StAX stream writer
+ *
+ * @author James Strachan
+ * @version $Revision: 1906 $
+ */
+public class StaxWriter extends AbstractXmlWriter {
+
+ private final QNameMap qnameMap;
+ private final XMLStreamWriter out;
+ private final boolean writeEnclosingDocument;
+ private boolean namespaceRepairingMode;
+
+ private int tagDepth;
+
+ public StaxWriter(QNameMap qnameMap, XMLStreamWriter out) throws XMLStreamException {
+ this(qnameMap, out, true, true);
+ }
+
+ /**
+ * Allows a StaxWriter to be created for partial XML output
+ *
+ * @param qnameMap is the mapper of Java class names to QNames
+ * @param out the stream to output to
+ * @param nameCoder the xml-friendly replacer to escape Java names
+ * @throws XMLStreamException if the events could not be written to the output
+ * @since 1.4
+ */
+ public StaxWriter(QNameMap qnameMap, XMLStreamWriter out, NameCoder nameCoder)
+ throws XMLStreamException {
+ this(qnameMap, out, true, true, nameCoder);
+ }
+
+ /**
+ * Allows a StaxWriter to be created for partial XML output
+ *
+ * @param qnameMap is the mapper of Java class names to QNames
+ * @param out the stream to output to
+ * @param writeEnclosingDocument a flag to indicate whether or not the start/end document
+ * events should be written
+ * @param namespaceRepairingMode a flag to enable StAX' namespace repairing mode
+ * @param nameCoder the xml-friendly replacer to escape Java names
+ * @throws XMLStreamException if the events could not be written to the output
+ * @since 1.4
+ */
+ public StaxWriter(
+ QNameMap qnameMap, XMLStreamWriter out, boolean writeEnclosingDocument,
+ boolean namespaceRepairingMode, NameCoder nameCoder) throws XMLStreamException {
+ super(nameCoder);
+ this.qnameMap = qnameMap;
+ this.out = out;
+ this.writeEnclosingDocument = writeEnclosingDocument;
+ this.namespaceRepairingMode = namespaceRepairingMode;
+ if (writeEnclosingDocument) {
+ out.writeStartDocument();
+ }
+ }
+
+ /**
+ * Allows a StaxWriter to be created for partial XML output
+ *
+ * @param qnameMap is the mapper of Java class names to QNames
+ * @param out the stream to output to
+ * @param writeEnclosingDocument a flag to indicate whether or not the start/end document
+ * events should be written
+ * @throws XMLStreamException if the events could not be written to the output
+ */
+ public StaxWriter(
+ QNameMap qnameMap, XMLStreamWriter out, boolean writeEnclosingDocument,
+ boolean namespaceRepairingMode) throws XMLStreamException {
+ this(
+ qnameMap, out, writeEnclosingDocument, namespaceRepairingMode,
+ new XmlFriendlyNameCoder());
+ }
+
+ /**
+ * Allows a StaxWriter to be created for partial XML output
+ *
+ * @param qnameMap is the mapper of Java class names to QNames
+ * @param out the stream to output to
+ * @param writeEnclosingDocument a flag to indicate whether or not the start/end document
+ * events should be written
+ * @param replacer the xml-friendly replacer to escape Java names
+ * @throws XMLStreamException if the events could not be written to the output
+ * @since 1.2
+ * @deprecated As of 1.4 use
+ * {@link StaxWriter#StaxWriter(QNameMap, XMLStreamWriter, boolean, boolean, NameCoder)}
+ * instead
+ */
+ public StaxWriter(
+ QNameMap qnameMap, XMLStreamWriter out, boolean writeEnclosingDocument,
+ boolean namespaceRepairingMode, XmlFriendlyReplacer replacer) throws XMLStreamException {
+ this(qnameMap, out, writeEnclosingDocument, namespaceRepairingMode, (NameCoder)replacer);
+ }
+
+ public void flush() {
+ try {
+ out.flush();
+ } catch (XMLStreamException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ /**
+ * Call this method when you're finished with me
+ */
+ public void close() {
+ try {
+ out.close();
+ } catch (XMLStreamException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public void addAttribute(String name, String value) {
+ try {
+ out.writeAttribute(encodeAttribute(name), value);
+ } catch (XMLStreamException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public void endNode() {
+ try {
+ tagDepth-- ;
+ out.writeEndElement();
+ if (tagDepth == 0 && writeEnclosingDocument) {
+ out.writeEndDocument();
+ }
+ } catch (XMLStreamException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public void setValue(String text) {
+ try {
+ out.writeCharacters(text);
+ } catch (XMLStreamException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public void startNode(String name) {
+ try {
+ QName qname = qnameMap.getQName(encodeNode(name));
+ String prefix = qname.getPrefix();
+ String uri = qname.getNamespaceURI();
+
+ // before you ask - yes it really is this complicated to output QNames to StAX
+ // handling both repair namespace modes :)
+
+ boolean hasPrefix = prefix != null && prefix.length() > 0;
+ boolean hasURI = uri != null && uri.length() > 0;
+ boolean writeNamespace = false;
+
+ if (hasURI) {
+ if (hasPrefix) {
+ String currentNamespace = out.getNamespaceContext().getNamespaceURI(prefix);
+ if (currentNamespace == null || !currentNamespace.equals(uri)) {
+ writeNamespace = true;
+ }
+ } else {
+ String defaultNamespace = out.getNamespaceContext().getNamespaceURI("");
+ if (defaultNamespace == null || !defaultNamespace.equals(uri)) {
+ writeNamespace = true;
+ }
+ }
+ }
+
+ out.writeStartElement(prefix, qname.getLocalPart(), uri);
+ if (hasPrefix) {
+ out.setPrefix(prefix, uri);
+ } else if (hasURI) {
+ if (writeNamespace) {
+ out.setDefaultNamespace(uri);
+ }
+ }
+ if (hasURI && writeNamespace && !isNamespaceRepairingMode()) {
+ if (hasPrefix) {
+ out.writeNamespace(prefix, uri);
+ } else {
+ out.writeDefaultNamespace(uri);
+ }
+ }
+ tagDepth++ ;
+ } catch (XMLStreamException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ /**
+ * Is StAX namespace repairing mode on or off?
+ */
+ public boolean isNamespaceRepairingMode() {
+ return namespaceRepairingMode;
+ }
+
+ protected QNameMap getQNameMap() {
+ return this.qnameMap;
+ }
+
+ protected XMLStreamWriter getXMLStreamWriter() {
+ return this.out;
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/TraxSource.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/TraxSource.java
new file mode 100644
index 0000000..2f3b1ee
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/TraxSource.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2013 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 14. August 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.XStream;
+
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLFilter;
+import org.xml.sax.XMLReader;
+
+import javax.xml.transform.sax.SAXSource;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * A {@link SAXSource JAXP TrAX Source} that enables using XStream object serialization as
+ * direct input for XSLT processors without resorting to an intermediate representation such as
+ * text XML, DOM or DOM4J.
+ *
+ * The following example shows how to apply an XSL Transformation
+ * to a set of Java objects gathered into a List (source):
+ *
+ *
+ *
+ * public static String transform(List source, String stylesheet) {
+ * try {
+ * Transformer transformer = TransformerFactory.newInstance().newTransformer(
+ * new StreamSource(stylesheet));
+ * TraxSource in = new TraxSource(source);
+ * Writer out = new StringWriter();
+ * transformer.transform(in, new StreamResult(out));
+ * return out.toString();
+ * } catch (TransformerException e) {
+ * throw new RuntimeException("XSLT Transformation failed", e);
+ * }
+ * }
+ *
+ *
+ * @author Laurent Bihanic
+ */
+public class TraxSource extends SAXSource {
+
+ /**
+ * If {@link javax.xml.transform.TransformerFactory#getFeature} returns true
+ * when passed this value as an argument, the Transformer natively supports XStream.
+ *
+ * Note: This implementation does not override the
+ * {@link SAXSource#FEATURE} value defined by its superclass to be considered as a SAXSource
+ * by Transformer implementations not natively supporting this XStream-specific source
+ *
+ */
+ public final static String XSTREAM_FEATURE = "http://com.thoughtworks.xstream/XStreamSource/feature";
+
+ /**
+ * The XMLReader object associated to this source or null if no XMLReader has
+ * yet been requested.
+ *
+ * @see #getXMLReader
+ */
+ private XMLReader xmlReader = null;
+
+ /**
+ * The configured XStream facade to use for serializing objects.
+ */
+ private XStream xstream = null;
+
+ /**
+ * The list of Java objects to be serialized.
+ */
+ private List source = null;
+
+ // -------------------------------------------------------------------------
+ // Constructors
+ // -------------------------------------------------------------------------
+
+ /**
+ * Creates a XStream TrAX source.
+ */
+ public TraxSource() {
+ super(new InputSource());
+ }
+
+ /**
+ * Creates a XStream TrAX source, specifying the object to marshal.
+ *
+ * @param source the object to marshal.
+ * @throws IllegalArgumentException if source is null.
+ * @see #setSource(java.lang.Object)
+ */
+ public TraxSource(Object source) {
+ super(new InputSource());
+
+ this.setSource(source);
+ }
+
+ /**
+ * Creates a XStream TrAX source, specifying the object to marshal and a configured (with
+ * aliases) XStream facade.
+ *
+ * @param source the object to marshal.
+ * @param xstream a configured XStream facade.
+ * @throws IllegalArgumentException if source or xstream is
+ * null.
+ * @see #setSource(java.lang.Object)
+ * @see #setXStream(com.thoughtworks.xstream.XStream)
+ */
+ public TraxSource(Object source, XStream xstream) {
+ super(new InputSource());
+
+ this.setSource(source);
+ this.setXStream(xstream);
+ }
+
+ /**
+ * Creates a XStream TrAX source, setting the objects to marshal.
+ *
+ * @param source the list of objects to marshal.
+ * @throws IllegalArgumentException if source is null or
+ * empty.
+ * @see #setSourceAsList(java.util.List)
+ */
+ public TraxSource(List source) {
+ super(new InputSource());
+
+ this.setSourceAsList(source);
+ }
+
+ /**
+ * Creates a XStream TrAX source, setting the objects to marshal and a configured (with
+ * aliases) XStream facade.
+ *
+ * @param source the list of objects to marshal.
+ * @param xstream a configured XStream facade.
+ * @throws IllegalArgumentException if source or xstream is
+ * null or source is empty.
+ * @see #setSourceAsList(java.util.List)
+ * @see #setXStream(com.thoughtworks.xstream.XStream)
+ */
+ public TraxSource(List source, XStream xstream) {
+ super(new InputSource());
+
+ this.setSourceAsList(source);
+ this.setXStream(xstream);
+ }
+
+ // -------------------------------------------------------------------------
+ // SAXSource overwritten methods
+ // -------------------------------------------------------------------------
+
+ /**
+ * Sets the SAX InputSource to be used for the Source.
+ *
+ * As this implementation only
+ * supports object lists as data source, this method always throws an
+ * {@link UnsupportedOperationException}.
+ *
+ *
+ * @param inputSource a valid InputSource reference.
+ * @throws UnsupportedOperationException always!
+ */
+ public void setInputSource(InputSource inputSource) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Set the XMLReader to be used for the Source.
+ *
+ * As this implementation only supports
+ * object lists as data source, this method throws an {@link UnsupportedOperationException}
+ * if the provided reader object does not implement the SAX {@link XMLFilter} interface.
+ * Otherwise, a {@link SaxWriter} instance will be attached as parent of the filter chain.
+ *
+ *
+ * @param reader a valid XMLReader or XMLFilter reference.
+ * @throws UnsupportedOperationException if reader is not a SAX
+ * {@link XMLFilter}.
+ * @see #getXMLReader
+ */
+ public void setXMLReader(XMLReader reader) {
+ this.createXMLReader(reader);
+ }
+
+ /**
+ * Returns the XMLReader to be used for the Source.
+ *
+ * This implementation returns a
+ * specific XMLReader ({@link SaxWriter}) generating the XML from a list of input objects.
+ *
+ *
+ * @return an XMLReader generating the XML from a list of input objects.
+ */
+ public XMLReader getXMLReader() {
+ if (this.xmlReader == null) {
+ this.createXMLReader(null);
+ }
+ return this.xmlReader;
+ }
+
+ // -------------------------------------------------------------------------
+ // Specific implementation
+ // -------------------------------------------------------------------------
+
+ /**
+ * Sets the XStream facade to use when marshalling objects.
+ *
+ * @param xstream a configured XStream facade.
+ * @throws IllegalArgumentException if xstream is null.
+ */
+ public void setXStream(XStream xstream) {
+ if (xstream == null) {
+ throw new IllegalArgumentException("xstream");
+ }
+ this.xstream = xstream;
+
+ this.configureXMLReader();
+ }
+
+ /**
+ * Sets the object to marshal.
+ *
+ * @param obj the object to marshal.
+ * @throws IllegalArgumentException if source is null.
+ */
+ public void setSource(Object obj) {
+ if (obj == null) {
+ throw new IllegalArgumentException("obj");
+ }
+ List list = new ArrayList(1);
+ list.add(obj);
+
+ this.setSourceAsList(list);
+ }
+
+ /**
+ * Sets the list of objects to marshal.
+ *
+ * When dealing with non-text input (such as SAX
+ * or DOM), XSLT processors support multiple root node children for the source tree (see section 3.1 of the "XSL
+ * Transformations (XSLT) Version 1.0" specification. Using a list of objects as source
+ * makes use of this feature and allows creating XML documents merging the XML serialization
+ * of several Java objects.
+ *
+ * @param list the list of objects to marshal.
+ * @throws IllegalArgumentException if source is null or
+ * empty.
+ */
+ public void setSourceAsList(List list) {
+ if ((list == null) || (list.isEmpty())) {
+ throw new IllegalArgumentException("list");
+ }
+ this.source = list;
+
+ this.configureXMLReader();
+ }
+
+ private void createXMLReader(XMLReader filterChain) {
+ if (filterChain == null) {
+ this.xmlReader = new SaxWriter();
+ } else {
+ if (filterChain instanceof XMLFilter) {
+ // Connect the filter chain to a document reader.
+ XMLFilter filter = (XMLFilter)filterChain;
+ while (filter.getParent() instanceof XMLFilter) {
+ filter = (XMLFilter)(filter.getParent());
+ }
+ if (!(filter.getParent() instanceof SaxWriter)) {
+ filter.setParent(new SaxWriter());
+ }
+
+ // Read XML data from filter chain.
+ this.xmlReader = filterChain;
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ this.configureXMLReader();
+ }
+
+ private void configureXMLReader() {
+ if (this.xmlReader != null) {
+ try {
+ if (this.xstream != null) {
+ this.xmlReader.setProperty(
+ SaxWriter.CONFIGURED_XSTREAM_PROPERTY, this.xstream);
+ }
+ if (this.source != null) {
+ this.xmlReader.setProperty(
+ SaxWriter.SOURCE_OBJECT_LIST_PROPERTY, this.source);
+ }
+ } catch (SAXException e) {
+ throw new IllegalArgumentException(e.getMessage());
+ }
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/WstxDriver.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/WstxDriver.java
new file mode 100644
index 0000000..fd9b316
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/WstxDriver.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2009, 2011 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 29. April 2009 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.ctc.wstx.stax.WstxInputFactory;
+import com.ctc.wstx.stax.WstxOutputFactory;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLOutputFactory;
+
+/**
+ * A driver using the Woodstox StAX implementation.
+ *
+ * @author Jörg Schaible
+ * @since 1.4
+ */
+public class WstxDriver extends StaxDriver {
+
+ public WstxDriver() {
+ super();
+ }
+
+ /**
+ * @deprecated As of 1.4.6 use {@link #WstxDriver(QNameMap, NameCoder)}
+ */
+ public WstxDriver(QNameMap qnameMap, XmlFriendlyNameCoder nameCoder) {
+ super(qnameMap, nameCoder);
+ }
+
+ /**
+ * @since 1.4.6
+ */
+ public WstxDriver(QNameMap qnameMap, NameCoder nameCoder) {
+ super(qnameMap, nameCoder);
+ }
+
+ public WstxDriver(QNameMap qnameMap) {
+ super(qnameMap);
+ }
+
+ /**
+ * @deprecated As of 1.4.6 use {@link #WstxDriver(NameCoder)}
+ */
+ public WstxDriver(XmlFriendlyNameCoder nameCoder) {
+ super(nameCoder);
+ }
+
+ /**
+ * @since 1.4.6
+ */
+ public WstxDriver(NameCoder nameCoder) {
+ super(nameCoder);
+ }
+
+ protected XMLInputFactory createInputFactory() {
+ return new WstxInputFactory();
+ }
+
+ protected XMLOutputFactory createOutputFactory() {
+ return new WstxOutputFactory();
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/XStream11NameCoder.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/XStream11NameCoder.java
new file mode 100644
index 0000000..f4ec7fe
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/XStream11NameCoder.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2009, 2011 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 15. August 2009 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io.xml;
+
+
+/**
+ * A XmlFriendlyNameCoder to support backward compatibility with XStream 1.1.
+ *
+ * @author Jörg Schaible
+ * @since 1.4
+ */
+public class XStream11NameCoder extends XmlFriendlyNameCoder {
+
+ /**
+ * {@inheritDoc} Noop implementation that does not decode. Used for XStream 1.1
+ * compatibility.
+ */
+ public String decodeAttribute(String attributeName) {
+ return attributeName;
+ }
+
+ /**
+ * {@inheritDoc} Noop implementation that does not decode. Used for XStream 1.1
+ * compatibility.
+ */
+ public String decodeNode(String elementName) {
+ return elementName;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/XStream11XmlFriendlyReplacer.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/XStream11XmlFriendlyReplacer.java
new file mode 100644
index 0000000..ae3404a
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/XStream11XmlFriendlyReplacer.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2011 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 22. April 2006 by Mauro Talevi
+ */
+package com.thoughtworks.xstream.io.xml;
+
+/**
+ * Allows replacement of Strings in xml-friendly drivers to provide compatibility with XStream
+ * 1.1 format
+ *
+ * @author Mauro Talevi
+ * @since 1.2
+ * @deprecated As of 1.4, use {@link XStream11NameCoder} instead
+ */
+public class XStream11XmlFriendlyReplacer extends XmlFriendlyReplacer {
+
+ /**
+ * Default constructor.
+ *
+ * @deprecated As of 1.4, use {@link XStream11NameCoder} instead
+ */
+ public XStream11XmlFriendlyReplacer() {
+ }
+
+ /**
+ * {@inheritDoc} Noop implementation that does not decode. Used for XStream 1.1
+ * compatibility.
+ */
+ public String decodeAttribute(String attributeName) {
+ return attributeName;
+ }
+
+ /**
+ * {@inheritDoc} Noop implementation that does not decode. Used for XStream 1.1
+ * compatibility.
+ */
+ public String decodeNode(String elementName) {
+ return elementName;
+ }
+
+ /**
+ * Noop implementation that does not unescape name. Used for XStream 1.1 compatibility.
+ *
+ * @param name the name of attribute or node
+ * @return The String with unescaped name
+ */
+ public String unescapeName(String name) {
+ return name;
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/XmlFriendlyNameCoder.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/XmlFriendlyNameCoder.java
new file mode 100644
index 0000000..bb64a2d
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/XmlFriendlyNameCoder.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009, 2011, 2013 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 15. August 2009 by Joerg Schaible, copied from XmlFriendlyReplacer.
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.converters.reflection.ObjectAccessException;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * Encode and decode tag and attribute names in XML drivers.
+ *
+ * This NameCoder is designed to ensure the correct encoding and decoding of names used for Java
+ * types and fields to XML tags and attribute names.
+ *
+ *
+ * The default replacements are:
+ *
+ *
+ *
$ (dollar) chars are replaced with _- (underscore dash) string.
+ *
_ (underscore) chars are replaced with __ (double underscore) string.
+ *
other characters that are invalid in XML names are encoded with _.XXXX (underscore
+ * dot followed by hex representation of character).
+ *
+ *
+ * @author Jörg Schaible
+ * @author Mauro Talevi
+ * @author Tatu Saloranta
+ * @author Michael Schnell
+ * @see XML 1.0 name definition
+ * @see XML 1.1 name definition
+ * @see Java
+ * identifier definition
+ * @since 1.4
+ */
+public class XmlFriendlyNameCoder implements NameCoder, Cloneable {
+ private static final IntPair[] XML_NAME_START_CHAR_BOUNDS;
+ private static final IntPair[] XML_NAME_CHAR_EXTRA_BOUNDS;
+ static {
+ class IntPairList extends ArrayList {
+ void add(int min, int max) {
+ super.add(new IntPair(min, max));
+ }
+
+ void add(char cp) {
+ super.add(new IntPair(cp, cp));
+ }
+ }
+
+ // legal characters in XML names according to
+ // http://www.w3.org/TR/REC-xml/#NT-Name and
+ // http://www.w3.org/TR/xml11/#NT-Name
+ IntPairList list = new IntPairList();
+
+ list.add(':');
+ list.add('A', 'Z');
+ list.add('a', 'z');
+ list.add('_');
+
+ list.add(0xC0, 0xD6);
+ list.add(0xD8, 0xF6);
+ list.add(0xF8, 0x2FF);
+ list.add(0x370, 0x37D);
+ list.add(0x37F, 0x1FFF);
+ list.add(0x200C, 0x200D);
+ list.add(0x2070, 0x218F);
+ list.add(0x2C00, 0x2FEF);
+ list.add(0x3001, 0xD7FF);
+ list.add(0xF900, 0xFDCF);
+ list.add(0xFDF0, 0xFFFD);
+ list.add(0x10000, 0xEFFFF);
+ XML_NAME_START_CHAR_BOUNDS = (IntPair[])list.toArray(new IntPair[list.size()]);
+
+ list.clear();
+ list.add('-');
+ list.add('.');
+ list.add('0', '9');
+ list.add('\u00b7');
+ list.add(0x0300, 0x036F);
+ list.add(0x203F, 0x2040);
+ XML_NAME_CHAR_EXTRA_BOUNDS = (IntPair[])list.toArray(new IntPair[list.size()]);
+ }
+
+ private final String dollarReplacement;
+ private final String escapeCharReplacement;
+ private transient Map escapeCache;
+ private transient Map unescapeCache;
+ private final String hexPrefix;
+
+ /**
+ * Construct a new XmlFriendlyNameCoder.
+ *
+ * @since 1.4
+ */
+ public XmlFriendlyNameCoder() {
+ this("_-", "__");
+ }
+
+ /**
+ * Construct a new XmlFriendlyNameCoder with custom replacement strings for dollar and the
+ * escape character.
+ *
+ * @param dollarReplacement
+ * @param escapeCharReplacement
+ * @since 1.4
+ */
+ public XmlFriendlyNameCoder(String dollarReplacement, String escapeCharReplacement) {
+ this(dollarReplacement, escapeCharReplacement, "_.");
+ }
+
+ /**
+ * Construct a new XmlFriendlyNameCoder with custom replacement strings for dollar, the
+ * escape character and the prefix for hexadecimal encoding of invalid characters in XML
+ * names.
+ *
+ * @param dollarReplacement
+ * @param escapeCharReplacement
+ * @since 1.4
+ */
+ public XmlFriendlyNameCoder(
+ String dollarReplacement, String escapeCharReplacement, String hexPrefix) {
+ this.dollarReplacement = dollarReplacement;
+ this.escapeCharReplacement = escapeCharReplacement;
+ this.hexPrefix = hexPrefix;
+ readResolve();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String decodeAttribute(String attributeName) {
+ return decodeName(attributeName);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String decodeNode(String elementName) {
+ return decodeName(elementName);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String encodeAttribute(String name) {
+ return encodeName(name);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String encodeNode(String name) {
+ return encodeName(name);
+ }
+
+ private String encodeName(String name) {
+ String s = (String)escapeCache.get(name);
+ if (s == null) {
+ final int length = name.length();
+
+ // First, fast (common) case: nothing to escape
+ int i = 0;
+
+ for (; i < length; i++ ) {
+ char c = name.charAt(i);
+ if (c == '$' || c == '_' || c <= 27 || c >= 127) {
+ break;
+ }
+ }
+
+ if (i == length) {
+ return name;
+ }
+
+ // Otherwise full processing
+ final StringBuffer result = new StringBuffer(length + 8);
+
+ // We know first N chars are safe
+ if (i > 0) {
+ result.append(name.substring(0, i));
+ }
+
+ for (; i < length; i++ ) {
+ char c = name.charAt(i);
+ if (c == '$') {
+ result.append(dollarReplacement);
+ } else if (c == '_') {
+ result.append(escapeCharReplacement);
+ } else if ((i == 0 && !isXmlNameStartChar(c)) || (i > 0 && !isXmlNameChar(c))) {
+ result.append(hexPrefix);
+ if (c < 16) result.append("000");
+ else if (c < 256) result.append("00");
+ else if (c < 4096) result.append("0");
+ result.append(Integer.toHexString(c));
+ } else {
+ result.append(c);
+ }
+ }
+ s = result.toString();
+ escapeCache.put(name, s);
+ }
+ return s;
+ }
+
+ private String decodeName(String name) {
+ String s = (String)unescapeCache.get(name);
+ if (s == null) {
+ final char dollarReplacementFirstChar = dollarReplacement.charAt(0);
+ final char escapeReplacementFirstChar = escapeCharReplacement.charAt(0);
+ final char hexPrefixFirstChar = hexPrefix.charAt(0);
+ final int length = name.length();
+
+ // First, fast (common) case: nothing to decode
+ int i = 0;
+
+ for (; i < length; i++ ) {
+ char c = name.charAt(i);
+ // We'll do a quick check for potential match
+ if (c == dollarReplacementFirstChar
+ || c == escapeReplacementFirstChar
+ || c == hexPrefixFirstChar) {
+ // and if it might be a match, just quit, will check later on
+ break;
+ }
+ }
+
+ if (i == length) {
+ return name;
+ }
+
+ // Otherwise full processing
+ final StringBuffer result = new StringBuffer(length + 8);
+
+ // We know first N chars are safe
+ if (i > 0) {
+ result.append(name.substring(0, i));
+ }
+
+ for (; i < length; i++ ) {
+ char c = name.charAt(i);
+ if (c == dollarReplacementFirstChar && name.startsWith(dollarReplacement, i)) {
+ i += dollarReplacement.length() - 1;
+ result.append('$');
+ } else if (c == hexPrefixFirstChar && name.startsWith(hexPrefix, i)) {
+ i += hexPrefix.length();
+ c = (char)Integer.parseInt(name.substring(i, i + 4), 16);
+ i += 3;
+ result.append(c);
+ } else if (c == escapeReplacementFirstChar
+ && name.startsWith(escapeCharReplacement, i)) {
+ i += escapeCharReplacement.length() - 1;
+ result.append('_');
+ } else {
+ result.append(c);
+ }
+ }
+
+ s = result.toString();
+ unescapeCache.put(name, s);
+ }
+ return s;
+ }
+
+ public Object clone() {
+ try {
+ XmlFriendlyNameCoder coder = (XmlFriendlyNameCoder)super.clone();
+ coder.readResolve();
+ return coder;
+
+ } catch (CloneNotSupportedException e) {
+ throw new ObjectAccessException("Cannot clone XmlFriendlyNameCoder", e);
+ }
+ }
+
+ private Object readResolve() {
+ escapeCache = createCacheMap();
+ unescapeCache = createCacheMap();
+ return this;
+ }
+
+ protected Map createCacheMap() {
+ return new HashMap();
+ }
+
+ private static class IntPair {
+ int min;
+ int max;
+
+ public IntPair(int min, int max) {
+ this.min = min;
+ this.max = max;
+ }
+ }
+
+ private static boolean isXmlNameStartChar(int cp) {
+ return isInNameCharBounds(cp, XML_NAME_START_CHAR_BOUNDS);
+ }
+
+ private static boolean isXmlNameChar(int cp) {
+ if (isXmlNameStartChar(cp)) {
+ return true;
+ }
+ return isInNameCharBounds(cp, XML_NAME_CHAR_EXTRA_BOUNDS);
+ }
+
+ private static boolean isInNameCharBounds(int cp, IntPair[] nameCharBounds) {
+ for (int i = 0; i < nameCharBounds.length; ++i) {
+ IntPair p = nameCharBounds[i];
+ if (cp >= p.min && cp <= p.max) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/XmlFriendlyReader.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/XmlFriendlyReader.java
new file mode 100644
index 0000000..9cc4d83
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/XmlFriendlyReader.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2007, 2008, 2009, 2011 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 2007 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io.xml;
+
+/**
+ * An interface for a {@link com.thoughtworks.xstream.io.HierarchicalStreamReader} supporting
+ * XML-friendly names.
+ *
+ * @author Jörg Schaible
+ * @author Mauro Talevi
+ * @since 1.3
+ * @deprecated As of 1.4
+ */
+public interface XmlFriendlyReader {
+
+ /**
+ * Unescapes XML-friendly name (node or attribute)
+ *
+ * @param name the escaped XML-friendly name
+ * @return An unescaped name with original characters
+ * @deprecated As of 1.4
+ */
+ String unescapeXmlName(String name);
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/XmlFriendlyReplacer.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/XmlFriendlyReplacer.java
new file mode 100644
index 0000000..0e2fce8
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/XmlFriendlyReplacer.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009, 2011 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 17. April 2006 by Mauro Talevi
+ */
+package com.thoughtworks.xstream.io.xml;
+
+/**
+ * Allows replacement of Strings in XML-friendly drivers. The default replacements are:
+ *
+ *
$ (dollar) chars are replaced with _- (underscore dash) string.
+ *
+ *
_ (underscore) chars are replaced with __ (double underscore) string.
+ *
+ *
+ *
+ * @author Mauro Talevi
+ * @author Jörg Schaible
+ * @author Tatu Saloranta
+ * @since 1.2
+ * @deprecated As of 1.4, use {@link XmlFriendlyNameCoder} instead
+ */
+public class XmlFriendlyReplacer extends XmlFriendlyNameCoder {
+
+ /**
+ * Default constructor.
+ *
+ * @deprecated As of 1.4, use {@link XmlFriendlyNameCoder} instead
+ */
+ public XmlFriendlyReplacer() {
+ this("_-", "__");
+ }
+
+ /**
+ * Creates an XmlFriendlyReplacer with custom replacements
+ *
+ * @param dollarReplacement the replacement for '$'
+ * @param underscoreReplacement the replacement for '_'
+ * @deprecated As of 1.4, use {@link XmlFriendlyNameCoder} instead
+ */
+ public XmlFriendlyReplacer(String dollarReplacement, String underscoreReplacement) {
+ super(dollarReplacement, underscoreReplacement);
+ }
+
+ /**
+ * Escapes name substituting '$' and '_' with replacement strings
+ *
+ * @param name the name of attribute or node
+ * @return The String with the escaped name
+ * @deprecated As of 1.4, use {@link XmlFriendlyNameCoder} instead
+ */
+ public String escapeName(String name) {
+ return super.encodeNode(name);
+ }
+
+ /**
+ * Unescapes name re-enstating '$' and '_' when replacement strings are found
+ *
+ * @param name the name of attribute or node
+ * @return The String with unescaped name
+ * @deprecated As of 1.4, use {@link XmlFriendlyNameCoder} instead
+ */
+ public String unescapeName(String name) {
+ return super.decodeNode(name);
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/XmlFriendlyWriter.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/XmlFriendlyWriter.java
new file mode 100644
index 0000000..1ea0cf5
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/XmlFriendlyWriter.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2007, 2008, 2009, 2011 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 2007 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io.xml;
+
+/**
+ * An interface for a {@link com.thoughtworks.xstream.io.HierarchicalStreamWriter} supporting
+ * XML-friendly names.
+ *
+ * @author Jörg Schaible
+ * @author Mauro Talevi
+ * @since 1.3
+ * @deprecated As of 1.4
+ */
+public interface XmlFriendlyWriter {
+
+ /**
+ * Escapes XML name (node or attribute) to be XML-friendly
+ *
+ * @param name the unescaped XML name
+ * @return An escaped name with original characters replaced
+ * @deprecated As of 1.4
+ */
+ String escapeXmlName(String name);
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/XomDriver.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/XomDriver.java
new file mode 100644
index 0000000..5700ea6
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/XomDriver.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2011 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 12. April 2006 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.URL;
+
+import nu.xom.Builder;
+import nu.xom.Document;
+import nu.xom.ParsingException;
+import nu.xom.ValidityException;
+
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.io.StreamException;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+public class XomDriver extends AbstractXmlDriver {
+
+ private final Builder builder;
+
+ public XomDriver() {
+ this(new Builder());
+ }
+
+ public XomDriver(Builder builder) {
+ this(builder, new XmlFriendlyNameCoder());
+ }
+
+ /**
+ * @since 1.4
+ */
+ public XomDriver(NameCoder nameCoder) {
+ this(new Builder(), nameCoder);
+ }
+
+ /**
+ * @since 1.4
+ */
+ public XomDriver(Builder builder, NameCoder nameCoder) {
+ super(nameCoder);
+ this.builder = builder;
+ }
+
+ /**
+ * @since 1.2
+ * @deprecated As of 1.4, use {@link #XomDriver(Builder, NameCoder)} instead
+ */
+ public XomDriver(XmlFriendlyReplacer replacer) {
+ this(new Builder(), replacer);
+ }
+
+ /**
+ * @since 1.2
+ * @deprecated As of 1.4, use {@link #XomDriver(Builder, NameCoder)} instead
+ */
+ public XomDriver(Builder builder, XmlFriendlyReplacer replacer) {
+ this((NameCoder)replacer);
+ }
+
+ protected Builder getBuilder() {
+ return this.builder;
+ }
+
+ public HierarchicalStreamReader createReader(Reader text) {
+ try {
+ Document document = builder.build(text);
+ return new XomReader(document, getNameCoder());
+ } catch (ValidityException e) {
+ throw new StreamException(e);
+ } catch (ParsingException e) {
+ throw new StreamException(e);
+ } catch (IOException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public HierarchicalStreamReader createReader(InputStream in) {
+ try {
+ Document document = builder.build(in);
+ return new XomReader(document, getNameCoder());
+ } catch (ValidityException e) {
+ throw new StreamException(e);
+ } catch (ParsingException e) {
+ throw new StreamException(e);
+ } catch (IOException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public HierarchicalStreamReader createReader(URL in) {
+ try {
+ Document document = builder.build(in.toExternalForm());
+ return new XomReader(document, getNameCoder());
+ } catch (ValidityException e) {
+ throw new StreamException(e);
+ } catch (ParsingException e) {
+ throw new StreamException(e);
+ } catch (IOException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public HierarchicalStreamReader createReader(File in) {
+ try {
+ Document document = builder.build(in);
+ return new XomReader(document, getNameCoder());
+ } catch (ValidityException e) {
+ throw new StreamException(e);
+ } catch (ParsingException e) {
+ throw new StreamException(e);
+ } catch (IOException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ public HierarchicalStreamWriter createWriter(final Writer out) {
+ return new PrettyPrintWriter(out, getNameCoder());
+ }
+
+ public HierarchicalStreamWriter createWriter(final OutputStream out) {
+ return new PrettyPrintWriter(new OutputStreamWriter(out), getNameCoder());
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/XomReader.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/XomReader.java
new file mode 100644
index 0000000..4e9ad16
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/XomReader.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2011 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 02. September 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.io.naming.NameCoder;
+import nu.xom.Document;
+import nu.xom.Element;
+import nu.xom.Elements;
+import nu.xom.Node;
+import nu.xom.Text;
+
+public class XomReader extends AbstractDocumentReader {
+
+ private Element currentElement;
+
+ public XomReader(Element rootElement) {
+ super(rootElement);
+ }
+
+ public XomReader(Document document) {
+ super(document.getRootElement());
+ }
+
+ /**
+ * @since 1.4
+ */
+ public XomReader(Element rootElement, NameCoder nameCoder) {
+ super(rootElement, nameCoder);
+ }
+
+ /**
+ * @since 1.4
+ */
+ public XomReader(Document document, NameCoder nameCoder) {
+ super(document.getRootElement(), nameCoder);
+ }
+
+ /**
+ * @since 1.2
+ * @deprecated As of 1.4 use {@link XomReader#XomReader(Element, NameCoder)} instead.
+ */
+ public XomReader(Element rootElement, XmlFriendlyReplacer replacer) {
+ this(rootElement, (NameCoder)replacer);
+ }
+
+ /**
+ * @since 1.2
+ * @deprecated As of 1.4 use {@link XomReader#XomReader(Element, NameCoder)} instead.
+ */
+ public XomReader(Document document, XmlFriendlyReplacer replacer) {
+ this(document.getRootElement(), (NameCoder)replacer);
+ }
+
+ public String getNodeName() {
+ return decodeNode(currentElement.getLocalName());
+ }
+
+ public String getValue() {
+ // currentElement.getValue() not used as this includes text of child elements, which we don't want.
+ StringBuffer result = new StringBuffer();
+ int childCount = currentElement.getChildCount();
+ for(int i = 0; i < childCount; i++) {
+ Node child = currentElement.getChild(i);
+ if (child instanceof Text) {
+ Text text = (Text) child;
+ result.append(text.getValue());
+ }
+ }
+ return result.toString();
+ }
+
+ public String getAttribute(String name) {
+ return currentElement.getAttributeValue(encodeAttribute(name));
+ }
+
+ public String getAttribute(int index) {
+ return currentElement.getAttribute(index).getValue();
+ }
+
+ public int getAttributeCount() {
+ return currentElement.getAttributeCount();
+ }
+
+ public String getAttributeName(int index) {
+ return decodeAttribute(currentElement.getAttribute(index).getQualifiedName());
+ }
+
+ protected int getChildCount() {
+ return currentElement.getChildElements().size();
+ }
+
+ protected Object getParent() {
+ return currentElement.getParent();
+ }
+
+ protected Object getChild(int index) {
+ return currentElement.getChildElements().get(index);
+ }
+
+ protected void reassignCurrentElement(Object current) {
+ currentElement = (Element) current;
+ }
+
+ public String peekNextChild() {
+ Elements children = currentElement.getChildElements();
+ if (null == children || children.size() == 0) {
+ return null;
+ }
+ return decodeNode(children.get(0).getLocalName());
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/XomWriter.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/XomWriter.java
new file mode 100644
index 0000000..d4ba9b0
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/XomWriter.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2011 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 03. September 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+import nu.xom.Attribute;
+import nu.xom.Element;
+
+
+public class XomWriter extends AbstractDocumentWriter {
+
+ /**
+ * @since 1.2.1
+ */
+ public XomWriter() {
+ this(null);
+ }
+
+ public XomWriter(final Element parentElement) {
+ this(parentElement, new XmlFriendlyNameCoder());
+ }
+
+ /**
+ * @since 1.4
+ */
+ public XomWriter(final Element parentElement, final NameCoder nameCoder) {
+ super(parentElement, nameCoder);
+ }
+
+ /**
+ * @since 1.2
+ * @deprecated As of 1.4 use {@link XomWriter#XomWriter(Element, NameCoder)} instead
+ */
+ public XomWriter(final Element parentElement, final XmlFriendlyReplacer replacer) {
+ this(parentElement, (NameCoder)replacer);
+ }
+
+ protected Object createNode(final String name) {
+ final Element newNode = new Element(encodeNode(name));
+ final Element top = top();
+ if (top != null){
+ top().appendChild(newNode);
+ }
+ return newNode;
+ }
+
+ public void addAttribute(final String name, final String value) {
+ top().addAttribute(new Attribute(encodeAttribute(name), value));
+ }
+
+ public void setValue(final String text) {
+ top().appendChild(text);
+ }
+
+ private Element top() {
+ return (Element)getCurrent();
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/Xpp3DomDriver.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/Xpp3DomDriver.java
new file mode 100644
index 0000000..c5bb301
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/Xpp3DomDriver.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2009, 2011 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 03. May 2009 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.io.HierarchicalStreamDriver;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+import org.xmlpull.mxp1.MXParser;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * A {@link HierarchicalStreamDriver} for XPP DOM using the Xpp3 parser.
+ *
+ * @author Jörg Schaible
+ * @since 1.4
+ */
+public class Xpp3DomDriver extends AbstractXppDomDriver {
+
+ /**
+ * Construct an Xpp3DomDriver.
+ *
+ * @since 1.4
+ */
+ public Xpp3DomDriver() {
+ super(new XmlFriendlyNameCoder());
+ }
+
+ /**
+ * Construct an Xpp3DomDriver.
+ *
+ * @param nameCoder the replacer for XML friendly names
+ * @since 1.4
+ */
+ public Xpp3DomDriver(NameCoder nameCoder) {
+ super(nameCoder);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected XmlPullParser createParser() {
+ return new MXParser();
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/Xpp3Driver.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/Xpp3Driver.java
new file mode 100644
index 0000000..83e5354
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/Xpp3Driver.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2009, 2011 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 29. April 2009 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io.xml;
+
+
+import com.thoughtworks.xstream.io.HierarchicalStreamDriver;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+import org.xmlpull.mxp1.MXParser;
+import org.xmlpull.v1.XmlPullParser;
+
+
+/**
+ * A {@link HierarchicalStreamDriver} using the Xpp3 parser.
+ *
+ * @author Jörg Schaible
+ * @since 1.4
+ */
+public class Xpp3Driver extends AbstractXppDriver {
+
+ /**
+ * Construct an Xpp3Driver.
+ *
+ * @since 1.4
+ */
+ public Xpp3Driver() {
+ super(new XmlFriendlyNameCoder());
+ }
+
+ /**
+ * Construct an Xpp3Driver.
+ *
+ * @param nameCoder the replacer for XML friendly names
+ * @since 1.4
+ */
+ public Xpp3Driver(NameCoder nameCoder) {
+ super(nameCoder);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected XmlPullParser createParser() {
+ return new MXParser();
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/XppDomDriver.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/XppDomDriver.java
new file mode 100644
index 0000000..cca241d
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/XppDomDriver.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009, 2011 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 07. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.io.HierarchicalStreamDriver;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+/**
+ * A {@link HierarchicalStreamDriver} for XPP DOM using the XmlPullParserFactory to locate an parser.
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ */
+public class XppDomDriver extends AbstractXppDomDriver {
+
+ private static XmlPullParserFactory factory;
+
+ public XppDomDriver() {
+ super(new XmlFriendlyNameCoder());
+ }
+
+ /**
+ * @since 1.4
+ */
+ public XppDomDriver(NameCoder nameCoder) {
+ super(nameCoder);
+ }
+
+ /**
+ * @since 1.2
+ * @deprecated As of 1.4, use {@link XppDomDriver#XppDomDriver(NameCoder)} instead.
+ */
+ public XppDomDriver(XmlFriendlyReplacer replacer) {
+ super(replacer);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected synchronized XmlPullParser createParser() throws XmlPullParserException {
+ if (factory == null) {
+ factory = XmlPullParserFactory.newInstance(null, XppDomDriver.class);
+ }
+ return factory.newPullParser();
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/XppDomReader.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/XppDomReader.java
new file mode 100644
index 0000000..f126910
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/XppDomReader.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2011 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 07. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.io.naming.NameCoder;
+import com.thoughtworks.xstream.io.xml.xppdom.XppDom;
+
+/**
+ * @author Jason van Zyl
+ */
+public class XppDomReader extends AbstractDocumentReader {
+
+ private XppDom currentElement;
+
+ public XppDomReader(XppDom xppDom) {
+ super(xppDom);
+ }
+
+ /**
+ * @since 1.4
+ */
+ public XppDomReader(XppDom xppDom, NameCoder nameCoder) {
+ super(xppDom, nameCoder);
+ }
+
+ /**
+ * @since 1.2
+ * @deprecated As of 1.4 use {@link XppDomReader#XppDomReader(XppDom, NameCoder)} instead.
+ */
+ public XppDomReader(XppDom xppDom, XmlFriendlyReplacer replacer) {
+ this(xppDom, (NameCoder)replacer);
+ }
+
+ public String getNodeName() {
+ return decodeNode(currentElement.getName());
+ }
+
+ public String getValue() {
+ String text = null;
+
+ try {
+ text = currentElement.getValue();
+ } catch (Exception e) {
+ // do nothing.
+ }
+
+ return text == null ? "" : text;
+ }
+
+ public String getAttribute(String attributeName) {
+ return currentElement.getAttribute(encodeAttribute(attributeName));
+ }
+
+ public String getAttribute(int index) {
+ return currentElement.getAttribute(currentElement.getAttributeNames()[index]);
+ }
+
+ public int getAttributeCount() {
+ return currentElement.getAttributeNames().length;
+ }
+
+ public String getAttributeName(int index) {
+ return decodeAttribute(currentElement.getAttributeNames()[index]);
+ }
+
+ protected Object getParent() {
+ return currentElement.getParent();
+ }
+
+ protected Object getChild(int index) {
+ return currentElement.getChild(index);
+ }
+
+ protected int getChildCount() {
+ return currentElement.getChildCount();
+ }
+
+ protected void reassignCurrentElement(Object current) {
+ this.currentElement = (XppDom) current;
+ }
+
+ public String peekNextChild() {
+ if (currentElement.getChildCount() == 0) {
+ return null;
+ }
+ return decodeNode(currentElement.getChild(0).getName());
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/XppDomWriter.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/XppDomWriter.java
new file mode 100644
index 0000000..f72410d
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/XppDomWriter.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2011 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 07. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import com.thoughtworks.xstream.io.naming.NameCoder;
+import com.thoughtworks.xstream.io.xml.xppdom.XppDom;
+
+
+public class XppDomWriter extends AbstractDocumentWriter {
+ public XppDomWriter() {
+ this(null, new XmlFriendlyNameCoder());
+ }
+
+ /**
+ * @since 1.2.1
+ */
+ public XppDomWriter(final XppDom parent) {
+ this(parent, new XmlFriendlyNameCoder());
+ }
+
+ /**
+ * @since 1.4
+ */
+ public XppDomWriter(final NameCoder nameCoder) {
+ this(null, nameCoder);
+ }
+
+ /**
+ * @since 1.4
+ */
+ public XppDomWriter(final XppDom parent, final NameCoder nameCoder) {
+ super(parent, nameCoder);
+ }
+
+ /**
+ * @since 1.2
+ * @deprecated As of 1.4 use {@link XppDomWriter#XppDomWriter(NameCoder)} instead
+ */
+ public XppDomWriter(final XmlFriendlyReplacer replacer) {
+ this(null, replacer);
+ }
+
+ /**
+ * @since 1.2.1
+ * @deprecated As of 1.4 use {@link XppDomWriter#XppDomWriter(XppDom, NameCoder)} instead.
+ */
+ public XppDomWriter(final XppDom parent, final XmlFriendlyReplacer replacer) {
+ this(parent, (NameCoder)replacer);
+ }
+
+ public XppDom getConfiguration() {
+ return (XppDom)getTopLevelNodes().get(0);
+ }
+
+ protected Object createNode(final String name) {
+ final XppDom newNode = new XppDom(encodeNode(name));
+ final XppDom top = top();
+ if (top != null) {
+ top().addChild(newNode);
+ }
+ return newNode;
+ }
+
+ public void setValue(final String text) {
+ top().setValue(text);
+ }
+
+ public void addAttribute(final String key, final String value) {
+ top().setAttribute(encodeAttribute(key), value);
+ }
+
+ private XppDom top() {
+ return (XppDom)getCurrent();
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/XppDriver.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/XppDriver.java
new file mode 100644
index 0000000..0aad911
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/XppDriver.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009, 2011, 2012 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 08. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.xml;
+
+
+import com.thoughtworks.xstream.io.HierarchicalStreamDriver;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+
+/**
+ * A {@link HierarchicalStreamDriver} using the XmlPullParserFactory to locate an XML Pull Parser.
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ */
+public class XppDriver extends AbstractXppDriver {
+
+ private static XmlPullParserFactory factory;
+
+ public XppDriver() {
+ super(new XmlFriendlyNameCoder());
+ }
+
+ /**
+ * @since 1.4
+ */
+ public XppDriver(NameCoder nameCoder) {
+ super(nameCoder);
+ }
+
+ /**
+ * @since 1.2
+ * @deprecated As of 1.4, use {@link XppDriver#XppDriver(NameCoder)} instead.
+ */
+ public XppDriver(XmlFriendlyReplacer replacer) {
+ this((NameCoder)replacer);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected synchronized XmlPullParser createParser() throws XmlPullParserException {
+ if (factory == null) {
+ factory = XmlPullParserFactory.newInstance();
+ }
+ return factory.newPullParser();
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/XppReader.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/XppReader.java
new file mode 100644
index 0000000..804d19d
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/XppReader.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009, 2011 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 08. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.xml;
+
+import java.io.IOException;
+import java.io.Reader;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import com.thoughtworks.xstream.converters.ErrorWriter;
+import com.thoughtworks.xstream.io.StreamException;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+
+/**
+ * XStream reader that pulls directly from the stream using the XmlPullParser API.
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ */
+public class XppReader extends AbstractPullReader {
+
+ private final XmlPullParser parser;
+ private final Reader reader;
+
+ /**
+ * Construct an XppReader.
+ *
+ * @param reader the reader with the input data
+ * @param parser the XPP parser to use
+ * @since 1.4
+ */
+ public XppReader(Reader reader, XmlPullParser parser) {
+ this(reader, parser, new XmlFriendlyNameCoder());
+ }
+
+ /**
+ * Construct an XppReader.
+ *
+ * @param reader the reader with the input data
+ * @param parser the XPP parser to use
+ * @param nameCoder the coder for XML friendly tag and attribute names
+ * @since 1.4
+ */
+ public XppReader(Reader reader, XmlPullParser parser, NameCoder nameCoder) {
+ super(nameCoder);
+ this.parser = parser;
+ this.reader = reader;
+ try {
+ parser.setInput(this.reader);
+ } catch (XmlPullParserException e) {
+ throw new StreamException(e);
+ }
+ moveDown();
+ }
+
+ /**
+ * @deprecated As of 1.4, use {@link #XppReader(Reader, XmlPullParser)} instead
+ */
+ public XppReader(Reader reader) {
+ this(reader, new XmlFriendlyReplacer());
+ }
+
+ /**
+ * @since 1.2
+ * @deprecated As of 1.4, use {@link #XppReader(Reader, XmlPullParser, NameCoder)} instead
+ */
+ public XppReader(Reader reader, XmlFriendlyReplacer replacer) {
+ super(replacer);
+ try {
+ parser = createParser();
+ this.reader = reader;
+ parser.setInput(this.reader);
+ moveDown();
+ } catch (XmlPullParserException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ /**
+ * To use another implementation of org.xmlpull.v1.XmlPullParser, override this method.
+ * @deprecated As of 1.4, use {@link #XppReader(Reader, XmlPullParser)} instead
+ */
+ protected XmlPullParser createParser() {
+ Exception exception = null;
+ try {
+ return (XmlPullParser)Class.forName("org.xmlpull.mxp1.MXParser", true, XmlPullParser.class.getClassLoader()).newInstance();
+ } catch (InstantiationException e) {
+ exception = e;
+ } catch (IllegalAccessException e) {
+ exception = e;
+ } catch (ClassNotFoundException e) {
+ exception = e;
+ }
+ throw new StreamException("Cannot create Xpp3 parser instance.", exception);
+ }
+
+ protected int pullNextEvent() {
+ try {
+ switch(parser.next()) {
+ case XmlPullParser.START_DOCUMENT:
+ case XmlPullParser.START_TAG:
+ return START_NODE;
+ case XmlPullParser.END_DOCUMENT:
+ case XmlPullParser.END_TAG:
+ return END_NODE;
+ case XmlPullParser.TEXT:
+ return TEXT;
+ case XmlPullParser.COMMENT:
+ return COMMENT;
+ default:
+ return OTHER;
+ }
+ } catch (XmlPullParserException e) {
+ throw new StreamException(e);
+ } catch (IOException e) {
+ throw new StreamException(e);
+ }
+ }
+
+ protected String pullElementName() {
+ return parser.getName();
+ }
+
+ protected String pullText() {
+ return parser.getText();
+ }
+
+ public String getAttribute(String name) {
+ return parser.getAttributeValue(null, encodeAttribute(name));
+ }
+
+ public String getAttribute(int index) {
+ return parser.getAttributeValue(index);
+ }
+
+ public int getAttributeCount() {
+ return parser.getAttributeCount();
+ }
+
+ public String getAttributeName(int index) {
+ return decodeAttribute(parser.getAttributeName(index));
+ }
+
+ public void appendErrors(ErrorWriter errorWriter) {
+ errorWriter.add("line number", String.valueOf(parser.getLineNumber()));
+ }
+
+ public void close() {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ throw new StreamException(e);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/xppdom/Xpp3Dom.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/xppdom/Xpp3Dom.java
new file mode 100644
index 0000000..4c22086
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/xppdom/Xpp3Dom.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2004 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2011 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 07. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.xml.xppdom;
+
+/**
+ * Simple Document Object Model for XmlPullParser implementations.
+ *
+ * @author Jason van Zyl
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ * @deprecated As of 1.4, use {@link XppDom} instead
+ */
+public class Xpp3Dom extends XppDom {
+
+ /**
+ * @deprecated As of 1.4, use {@link XppDom} instead
+ */
+ public Xpp3Dom(String name) {
+ super(name);
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/xppdom/Xpp3DomBuilder.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/xppdom/Xpp3DomBuilder.java
new file mode 100644
index 0000000..76f5869
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/xppdom/Xpp3DomBuilder.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2004, 2005 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2009, 2011 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 07. March 2004 by Joe Walnes
+ */
+package com.thoughtworks.xstream.io.xml.xppdom;
+
+import org.xmlpull.mxp1.MXParser;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.Reader;
+
+
+/**
+ * @author Jason van Zyl
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ * @deprecated As of 1.4, use {@link XppDom#build(XmlPullParser)} instead
+ */
+public class Xpp3DomBuilder {
+ /**
+ * @deprecated As of 1.4, use {@link XppDom#build(XmlPullParser)} instead
+ */
+ public static Xpp3Dom build(Reader reader) throws Exception {
+ XmlPullParser parser = new MXParser();
+ parser.setInput(reader);
+ try {
+ return (Xpp3Dom)XppDom.build(parser);
+ } finally {
+ reader.close();
+ }
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/xppdom/XppDom.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/xppdom/XppDom.java
new file mode 100644
index 0000000..654043d
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/xppdom/XppDom.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2009, 2011 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 02. May 2009 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.io.xml.xppdom;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * Simple Document Object Model for XmlPullParser implementations.
+ *
+ * @author Jason van Zyl
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ * @since 1.4
+ */
+public class XppDom implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private String name;
+ private String value;
+ private Map attributes;
+ private List childList;
+ transient private Map childMap;
+ private XppDom parent;
+
+ public XppDom(String name) {
+ this.name = name;
+ childList = new ArrayList();
+ childMap = new HashMap();
+ }
+
+ // ----------------------------------------------------------------------
+ // Name handling
+ // ----------------------------------------------------------------------
+
+ public String getName() {
+ return name;
+ }
+
+ // ----------------------------------------------------------------------
+ // Value handling
+ // ----------------------------------------------------------------------
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ // ----------------------------------------------------------------------
+ // Attribute handling
+ // ----------------------------------------------------------------------
+
+ public String[] getAttributeNames() {
+ if (null == attributes) {
+ return new String[0];
+ } else {
+ return (String[])attributes.keySet().toArray(new String[0]);
+ }
+ }
+
+ public String getAttribute(String name) {
+ return (null != attributes) ? (String)attributes.get(name) : null;
+ }
+
+ public void setAttribute(String name, String value) {
+ if (null == attributes) {
+ attributes = new HashMap();
+ }
+
+ attributes.put(name, value);
+ }
+
+ // ----------------------------------------------------------------------
+ // Child handling
+ // ----------------------------------------------------------------------
+
+ public XppDom getChild(int i) {
+ return (XppDom)childList.get(i);
+ }
+
+ public XppDom getChild(String name) {
+ return (XppDom)childMap.get(name);
+ }
+
+ public void addChild(XppDom xpp3Dom) {
+ xpp3Dom.setParent(this);
+ childList.add(xpp3Dom);
+ childMap.put(xpp3Dom.getName(), xpp3Dom);
+ }
+
+ public XppDom[] getChildren() {
+ if (null == childList) {
+ return new XppDom[0];
+ } else {
+ return (XppDom[])childList.toArray(new XppDom[0]);
+ }
+ }
+
+ public XppDom[] getChildren(String name) {
+ if (null == childList) {
+ return new XppDom[0];
+ } else {
+ ArrayList children = new ArrayList();
+ int size = this.childList.size();
+
+ for (int i = 0; i < size; i++ ) {
+ XppDom configuration = (XppDom)this.childList.get(i);
+ if (name.equals(configuration.getName())) {
+ children.add(configuration);
+ }
+ }
+
+ return (XppDom[])children.toArray(new XppDom[0]);
+ }
+ }
+
+ public int getChildCount() {
+ if (null == childList) {
+ return 0;
+ }
+
+ return childList.size();
+ }
+
+ // ----------------------------------------------------------------------
+ // Parent handling
+ // ----------------------------------------------------------------------
+
+ public XppDom getParent() {
+ return parent;
+ }
+
+ public void setParent(XppDom parent) {
+ this.parent = parent;
+ }
+
+ // ----------------------------------------------------------------------
+ // Serialization
+ // ----------------------------------------------------------------------
+
+ Object readResolve() {
+ childMap = new HashMap();
+ for (final Iterator iter = childList.iterator(); iter.hasNext();) {
+ final XppDom element = (XppDom)iter.next();
+ childMap.put(element.getName(), element);
+ }
+ return this;
+ }
+
+ // ----------------------------------------------------------------------
+ // DOM builder
+ // ----------------------------------------------------------------------
+
+ /**
+ * Build an XPP DOM hierarchy. The {@link java.io.InputStream} or {@link java.io.Reader}
+ * used by the parser must have already been set. The method does not close it after reading
+ * the document's end.
+ *
+ * @param parser the XPP instance
+ * @throws XmlPullParserException if the parser turns into an invalid state or reads invalid
+ * XML
+ * @throws IOException if the data cannot be read
+ */
+ public static XppDom build(XmlPullParser parser) throws XmlPullParserException, IOException {
+ List elements = new ArrayList();
+ List values = new ArrayList();
+ XppDom node = null;
+
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG) {
+ String rawName = parser.getName();
+
+ // Use XppDom when deprecated Xpp3Dom is removed
+ XppDom child = new Xpp3Dom(rawName);
+
+ int depth = elements.size();
+ if (depth > 0) {
+ XppDom parent = (XppDom)elements.get(depth - 1);
+ parent.addChild(child);
+ }
+
+ elements.add(child);
+ values.add(new StringBuffer());
+
+ int attributesSize = parser.getAttributeCount();
+ for (int i = 0; i < attributesSize; i++ ) {
+ String name = parser.getAttributeName(i);
+ String value = parser.getAttributeValue(i);
+ child.setAttribute(name, value);
+ }
+ } else if (eventType == XmlPullParser.TEXT) {
+ int depth = values.size() - 1;
+ StringBuffer valueBuffer = (StringBuffer)values.get(depth);
+ valueBuffer.append(parser.getText());
+ } else if (eventType == XmlPullParser.END_TAG) {
+ int depth = elements.size() - 1;
+ XppDom finalNode = (XppDom)elements.remove(depth);
+ String accumulatedValue = (values.remove(depth)).toString();
+
+ String finishedValue;
+ if (0 == accumulatedValue.length()) {
+ finishedValue = null;
+ } else {
+ finishedValue = accumulatedValue;
+ }
+
+ finalNode.setValue(finishedValue);
+ if (0 == depth) {
+ node = finalNode;
+ }
+ }
+
+ eventType = parser.next();
+ }
+
+ return node;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/xppdom/XppDomComparator.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/xppdom/XppDomComparator.java
new file mode 100644
index 0000000..6574b51
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/xppdom/XppDomComparator.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2011 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 11. August 2011 by Joerg Schaible.
+ */
+package com.thoughtworks.xstream.io.xml.xppdom;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * Comparator for {@link XppDom}. Comparator can trace the XPath where the comparison failed.
+ *
+ * @author Jörg Schaible
+ * @since 1.4.1
+ */
+public class XppDomComparator implements Comparator {
+ private final ThreadLocal xpath;
+
+ /**
+ * Creates a new Xpp3DomComparator object.
+ *
+ * @since 1.4.1
+ */
+ public XppDomComparator() {
+ this(null);
+ }
+
+ /**
+ * Creates a new Xpp3DomComparator object with XPath identification.
+ *
+ * @param xpath the reference for the XPath
+ * @since 1.4.1
+ */
+ public XppDomComparator(final ThreadLocal xpath) {
+ this.xpath = xpath;
+ }
+
+ public int compare(final Object dom1, final Object dom2) {
+
+ final StringBuffer xpath = new StringBuffer("/");
+ final int s = compareInternal((XppDom)dom1, (XppDom)dom2, xpath, -1);
+ if (this.xpath != null) {
+ if (s != 0) {
+ this.xpath.set(xpath.toString());
+ } else {
+ this.xpath.set(null);
+ }
+ }
+
+ return s;
+ }
+
+ private int compareInternal(final XppDom dom1, final XppDom dom2,
+ final StringBuffer xpath, final int count) {
+ final int pathlen = xpath.length();
+ final String name = dom1.getName();
+ int s = name.compareTo(dom2.getName());
+ xpath.append(name);
+ if (count >= 0) {
+ xpath.append('[').append(count).append(']');
+ }
+
+ if (s != 0) {
+ xpath.append('?');
+
+ return s;
+ }
+
+ final String[] attributes = dom1.getAttributeNames();
+ final String[] attributes2 = dom2.getAttributeNames();
+ final int len = attributes.length;
+ s = attributes2.length - len;
+ if (s != 0) {
+ xpath.append("::count(@*)");
+
+ return s < 0 ? 1 : -1;
+ }
+
+ Arrays.sort(attributes);
+ Arrays.sort(attributes2);
+ for (int i = 0; i < len; ++i) {
+ final String attribute = attributes[i];
+ s = attribute.compareTo(attributes2[i]);
+ if (s != 0) {
+ xpath.append("[@").append(attribute).append("?]");
+
+ return s;
+ }
+
+ s = dom1.getAttribute(attribute).compareTo(dom2.getAttribute(attribute));
+ if (s != 0) {
+ xpath.append("[@").append(attribute).append(']');
+
+ return s;
+ }
+ }
+
+ final int children = dom1.getChildCount();
+ s = dom2.getChildCount() - children;
+ if (s != 0) {
+ xpath.append("::count(*)");
+
+ return s < 0 ? 1 : -1;
+ }
+
+ if (children > 0) {
+ if (dom1.getValue() != null || dom2.getValue() != null) {
+ throw new IllegalArgumentException("XppDom cannot handle mixed mode at "
+ + xpath
+ + "::text()");
+ }
+
+ xpath.append('/');
+
+ final Map names = new HashMap();
+ for (int i = 0; i < children; ++i) {
+ final XppDom child1 = dom1.getChild(i);
+ final XppDom child2 = dom2.getChild(i);
+ final String child = child1.getName();
+ if (!names.containsKey(child)) {
+ names.put(child, new int[1]);
+ }
+
+ s = compareInternal(child1, child2, xpath, ((int[])names.get(child))[0]++);
+ if (s != 0) {
+ return s;
+ }
+ }
+ } else {
+ final String value2 = dom2.getValue();
+ final String value1 = dom1.getValue();
+ if (value1 == null) {
+ s = value2 == null ? 0 : -1;
+ } else {
+ s = value2 == null ? 1 : value1.compareTo(value2);
+ }
+
+ if (s != 0) {
+ xpath.append("::text()");
+
+ return s;
+ }
+ }
+
+ xpath.setLength(pathlen);
+
+ return s;
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/io/xml/xppdom/XppFactory.java b/xstream/src/java/com/thoughtworks/xstream/io/xml/xppdom/XppFactory.java
new file mode 100644
index 0000000..5b307f7
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/io/xml/xppdom/XppFactory.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2011 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 11. August 2011 by Joerg Schaible, code from XppDom.
+ */
+package com.thoughtworks.xstream.io.xml.xppdom;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.StringReader;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+/**
+ * XmlPullParser utility methods.
+ *
+ * @author Jörg Schaible
+ * @since 1.4.1
+ */
+public class XppFactory {
+
+ /**
+ * Create a new XmlPullParser using the XPP factory.
+ *
+ * @return a new parser instance
+ * @throws XmlPullParserException if the factory fails
+ * @since 1.4.1
+ */
+ public static XmlPullParser createDefaultParser() throws XmlPullParserException {
+ XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
+ return factory.newPullParser();
+ }
+
+ /**
+ * Build an XPP DOM hierarchy from a String.
+ *
+ * @param xml the XML data
+ * @throws XmlPullParserException if the default parser cannot be created or fails with invalid XML
+ * @throws IOException if the data cannot be read
+ * @see XppDom#build(XmlPullParser)
+ * @since 1.4.1
+ */
+ public static XppDom buildDom(String xml) throws XmlPullParserException, IOException {
+ return buildDom(new StringReader(xml));
+ }
+
+ /**
+ * Build an XPP DOM hierarchy from a Reader.
+ *
+ * @param r the reader
+ * @throws XmlPullParserException if the default parser cannot be created or fails with invalid XML
+ * @throws IOException if the data cannot be read
+ * @see XppDom#build(XmlPullParser)
+ * @since 1.4.1
+ */
+ public static XppDom buildDom(Reader r) throws XmlPullParserException, IOException {
+ XmlPullParser parser = createDefaultParser();
+ parser.setInput(r);
+ return XppDom.build(parser);
+ }
+
+ /**
+ * Build an XPP DOM hierarchy from an InputStream.
+ *
+ * @param in the input stream
+ * @param encoding the encoding of the input stream
+ * @throws XmlPullParserException if the default parser cannot be created or fails with invalid XML
+ * @throws IOException if the data cannot be read
+ * @see XppDom#build(XmlPullParser)
+ * @since 1.4.1
+ */
+ public static XppDom buildDom(InputStream in, String encoding) throws XmlPullParserException, IOException {
+ XmlPullParser parser = createDefaultParser();
+ parser.setInput(in, encoding);
+ return XppDom.build(parser);
+ }
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/mapper/AbstractAttributeAliasingMapper.java b/xstream/src/java/com/thoughtworks/xstream/mapper/AbstractAttributeAliasingMapper.java
new file mode 100644
index 0000000..05c8baa
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/mapper/AbstractAttributeAliasingMapper.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 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 09. October 2008 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.mapper;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Abstract base class for AttributeAliassingMapper and its system version.
+ *
+ * @author Jörg Schaible
+ * @since 1.3.1
+ */
+public abstract class AbstractAttributeAliasingMapper extends MapperWrapper {
+
+ protected final Map aliasToName = new HashMap();
+ protected transient Map nameToAlias = new HashMap();
+
+ public AbstractAttributeAliasingMapper(Mapper wrapped) {
+ super(wrapped);
+ }
+
+ public void addAliasFor(final String attributeName, final String alias) {
+ aliasToName.put(alias, attributeName);
+ nameToAlias.put(attributeName, alias);
+ }
+
+ Object readResolve() {
+ nameToAlias = new HashMap();
+ for (final Iterator iter = aliasToName.keySet().iterator(); iter.hasNext();) {
+ final Object alias = iter.next();
+ nameToAlias.put(aliasToName.get(alias), alias);
+ }
+ return this;
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/mapper/AbstractXmlFriendlyMapper.java b/xstream/src/java/com/thoughtworks/xstream/mapper/AbstractXmlFriendlyMapper.java
new file mode 100644
index 0000000..6725da3
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/mapper/AbstractXmlFriendlyMapper.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2011 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 03. May 2006 by Mauro Talevi
+ */
+package com.thoughtworks.xstream.mapper;
+
+
+/**
+ * Mapper that ensures that all names in the serialization stream are XML friendly.
+ * The replacement chars and strings are:
+ *
+ *
$ (dollar) chars appearing in class names are replaced with _ (underscore) chars.
+ *
$ (dollar) chars appearing in field names are replaced with _DOLLAR_ string.
+ *
_ (underscore) chars appearing in field names are replaced with __ (double underscore) string.
+ *
default as the prefix for class names with no package.
+ *
+ *
+ * Note, this class is no longer in regular use for current XStream versions. It exists to provide backward compatibility
+ * to existing XML data written with older XStream versions.
+ *
+ * @author Joe Walnes
+ * @author Mauro Talevi
+ * @deprecated As of 1.4 use {@link com.thoughtworks.xstream.io.xml.XmlFriendlyReader}
+ */
+public class AbstractXmlFriendlyMapper extends MapperWrapper {
+
+ private char dollarReplacementInClass = '-';
+ private String dollarReplacementInField = "_DOLLAR_";
+ private String underscoreReplacementInField = "__";
+ private String noPackagePrefix = "default";
+
+ protected AbstractXmlFriendlyMapper(Mapper wrapped) {
+ super(wrapped);
+ }
+
+ protected String escapeClassName(String className) {
+ // the $ used in inner class names is illegal as an xml element getNodeName
+ className = className.replace('$', dollarReplacementInClass);
+
+ // special case for classes named $Blah with no package; <-Blah> is illegal XML
+ if (className.charAt(0) == dollarReplacementInClass) {
+ className = noPackagePrefix + className;
+ }
+
+ return className;
+ }
+
+ protected String unescapeClassName(String className) {
+ // special case for classes named $Blah with no package; <-Blah> is illegal XML
+ if (className.startsWith(noPackagePrefix+dollarReplacementInClass)) {
+ className = className.substring(noPackagePrefix.length());
+ }
+
+ // the $ used in inner class names is illegal as an xml element getNodeName
+ className = className.replace(dollarReplacementInClass, '$');
+
+ return className;
+ }
+
+ protected String escapeFieldName(String fieldName) {
+ StringBuffer result = new StringBuffer();
+ int length = fieldName.length();
+ for(int i = 0; i < length; i++) {
+ char c = fieldName.charAt(i);
+ if (c == '$' ) {
+ result.append(dollarReplacementInField);
+ } else if (c == '_') {
+ result.append(underscoreReplacementInField);
+ } else {
+ result.append(c);
+ }
+ }
+ return result.toString();
+ }
+
+ protected String unescapeFieldName(String xmlName) {
+ StringBuffer result = new StringBuffer();
+ int length = xmlName.length();
+ for(int i = 0; i < length; i++) {
+ char c = xmlName.charAt(i);
+ if ( stringFoundAt(xmlName, i,underscoreReplacementInField)) {
+ i +=underscoreReplacementInField.length() - 1;
+ result.append('_');
+ } else if ( stringFoundAt(xmlName, i,dollarReplacementInField)) {
+ i +=dollarReplacementInField.length() - 1;
+ result.append('$');
+ } else {
+ result.append(c);
+ }
+ }
+ return result.toString();
+ }
+
+ private boolean stringFoundAt(String name, int i, String replacement) {
+ if ( name.length() >= i + replacement.length()
+ && name.substring(i, i + replacement.length()).equals(replacement) ){
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/mapper/AnnotationConfiguration.java b/xstream/src/java/com/thoughtworks/xstream/mapper/AnnotationConfiguration.java
new file mode 100644
index 0000000..78e8263
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/mapper/AnnotationConfiguration.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2007, 2008, 2013 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 07. November 2007 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.mapper;
+
+/**
+ * An interface for the configuration part of the AnnotationMapper.
+ *
+ * @author Jörg Schaible
+ * @since 1.3
+ * @deprecated As of 1.4.5, minimal JDK version will be 1.6 for next major release
+ */
+public interface AnnotationConfiguration {
+
+ void autodetectAnnotations(boolean mode);
+
+ void processAnnotations(Class[] types);
+
+}
diff --git a/xstream/src/java/com/thoughtworks/xstream/mapper/AnnotationMapper.java b/xstream/src/java/com/thoughtworks/xstream/mapper/AnnotationMapper.java
new file mode 100644
index 0000000..7d0e78e
--- /dev/null
+++ b/xstream/src/java/com/thoughtworks/xstream/mapper/AnnotationMapper.java
@@ -0,0 +1,626 @@
+/*
+ * Copyright (C) 2007, 2008, 2009, 2011, 2012, 2013 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 07. November 2007 by Joerg Schaible
+ */
+package com.thoughtworks.xstream.mapper;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.thoughtworks.xstream.InitializationException;
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamAliasType;
+import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
+import com.thoughtworks.xstream.annotations.XStreamConverter;
+import com.thoughtworks.xstream.annotations.XStreamConverters;
+import com.thoughtworks.xstream.annotations.XStreamImplicit;
+import com.thoughtworks.xstream.annotations.XStreamImplicitCollection;
+import com.thoughtworks.xstream.annotations.XStreamInclude;
+import com.thoughtworks.xstream.annotations.XStreamOmitField;
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.ConverterLookup;
+import com.thoughtworks.xstream.converters.ConverterMatcher;
+import com.thoughtworks.xstream.converters.ConverterRegistry;
+import com.thoughtworks.xstream.converters.SingleValueConverter;
+import com.thoughtworks.xstream.converters.SingleValueConverterWrapper;
+import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
+import com.thoughtworks.xstream.core.ClassLoaderReference;
+import com.thoughtworks.xstream.core.JVM;
+import com.thoughtworks.xstream.core.util.DependencyInjectionFactory;
+import com.thoughtworks.xstream.core.util.TypedNull;
+
+
+/**
+ * A mapper that uses annotations to prepare the remaining mappers in the chain.
+ *
+ * @author Jörg Schaible
+ * @since 1.3
+ */
+public class AnnotationMapper extends MapperWrapper implements AnnotationConfiguration {
+
+ private boolean locked;
+ private transient Object[] arguments;
+ private final ConverterRegistry converterRegistry;
+ private transient ClassAliasingMapper classAliasingMapper;
+ private transient DefaultImplementationsMapper defaultImplementationsMapper;
+ private transient ImplicitCollectionMapper implicitCollectionMapper;
+ private transient FieldAliasingMapper fieldAliasingMapper;
+ private transient AttributeMapper attributeMapper;
+ private transient LocalConversionMapper localConversionMapper;
+ private final Map, Map, Converter>> converterCache =
+ new HashMap, Map, Converter>>();
+ private final Set> annotatedTypes =
+ Collections.synchronizedSet(new HashSet>());
+
+ /**
+ * Construct an AnnotationMapper.
+ *
+ * @param wrapped the next {@link Mapper} in the chain
+ * @since 1.4.5
+ */
+ public AnnotationMapper(
+ final Mapper wrapped, final ConverterRegistry converterRegistry, final ConverterLookup converterLookup,
+ final ClassLoaderReference classLoaderReference, final ReflectionProvider reflectionProvider) {
+ super(wrapped);
+ this.converterRegistry = converterRegistry;
+ annotatedTypes.add(Object.class);
+ setupMappers();
+ locked = true;
+
+ final ClassLoader classLoader = classLoaderReference.getReference();
+ arguments = new Object[]{
+ this, classLoaderReference, reflectionProvider, converterLookup, new JVM(),
+ classLoader != null ? classLoader : new TypedNull(ClassLoader.class)};
+ }
+
+ /**
+ * Construct an AnnotationMapper.
+ *
+ * @param wrapped the next {@link Mapper} in the chain
+ * @since 1.3
+ * @deprecated As of 1.4.5 use {@link #AnnotationMapper(Mapper, ConverterRegistry, ConverterLookup, ClassLoaderReference, ReflectionProvider)}
+ */
+ public AnnotationMapper(
+ final Mapper wrapped, final ConverterRegistry converterRegistry, final ConverterLookup converterLookup,
+ final ClassLoader classLoader, final ReflectionProvider reflectionProvider,
+ final JVM jvm) {
+ this(wrapped, converterRegistry, converterLookup, new ClassLoaderReference(classLoader), reflectionProvider);
+ }
+
+ @Override
+ public String realMember(final Class type, final String serialized) {
+ if (!locked) {
+ processAnnotations(type);
+ }
+ return super.realMember(type, serialized);
+ }
+
+ @Override
+ public String serializedClass(final Class type) {
+ if (!locked) {
+ processAnnotations(type);
+ }
+ return super.serializedClass(type);
+ }
+
+ @Override
+ public Class defaultImplementationOf(final Class type) {
+ if (!locked) {
+ processAnnotations(type);
+ }
+ final Class defaultImplementation = super.defaultImplementationOf(type);
+ if (!locked) {
+ processAnnotations(defaultImplementation);
+ }
+ return defaultImplementation;
+ }
+
+ @Override
+ public Converter getLocalConverter(final Class definedIn, final String fieldName) {
+ if (!locked) {
+ processAnnotations(definedIn);
+ }
+ return super.getLocalConverter(definedIn, fieldName);
+ }
+
+ public void autodetectAnnotations(final boolean mode) {
+ locked = !mode;
+ }
+
+ public void processAnnotations(final Class[] initialTypes) {
+ if (initialTypes == null || initialTypes.length == 0) {
+ return;
+ }
+ locked = true;
+
+ final Set> types = new UnprocessedTypesSet();
+ for (final Class initialType : initialTypes) {
+ types.add(initialType);
+ }
+ processTypes(types);
+ }
+
+ private void processAnnotations(final Class initialType) {
+ if (initialType == null) {
+ return;
+ }
+
+ final Set> types = new UnprocessedTypesSet();
+ types.add(initialType);
+ processTypes(types);
+ }
+
+ private void processTypes(final Set> types) {
+ while (!types.isEmpty()) {
+ final Iterator> iter = types.iterator();
+ final Class> type = iter.next();
+ iter.remove();
+
+ synchronized(type) {
+ if (annotatedTypes.contains(type)) {
+ continue;
+ }
+ try {
+ if (type.isPrimitive()) {
+ continue;
+ }
+
+ addParametrizedTypes(type, types);
+
+ processConverterAnnotations(type);
+ processAliasAnnotation(type, types);
+ processAliasTypeAnnotation(type);
+
+ if (type.isInterface()) {
+ continue;
+ }
+
+ processImplicitCollectionAnnotation(type);
+
+ final Field[] fields = type.getDeclaredFields();
+ for (int i = 0; i < fields.length; i++ ) {
+ final Field field = fields[i];
+ if (field.isEnumConstant()
+ || (field.getModifiers() & (Modifier.STATIC | Modifier.TRANSIENT)) > 0) {
+ continue;
+ }
+
+ addParametrizedTypes(field.getGenericType(), types);
+
+ if (field.isSynthetic()) {
+ continue;
+ }
+
+ processFieldAliasAnnotation(field);
+ processAsAttributeAnnotation(field);
+ processImplicitAnnotation(field);
+ processOmitFieldAnnotation(field);
+ processLocalConverterAnnotation(field);
+ }
+ } finally {
+ annotatedTypes.add(type);
+ }
+ }
+ }
+ }
+
+ private void addParametrizedTypes(Type type, final Set> types) {
+ final Set processedTypes = new HashSet();
+ final Set localTypes = new LinkedHashSet() {
+
+ @Override
+ public boolean add(final Type o) {
+ if (o instanceof Class) {
+ return types.add((Class>)o);
+ }
+ return o == null || processedTypes.contains(o) ? false : super.add(o);
+ }
+
+ };
+ while (type != null) {
+ processedTypes.add(type);
+ if (type instanceof Class) {
+ final Class> clazz = (Class>)type;
+ types.add(clazz);
+ if (!clazz.isPrimitive()) {
+ final TypeVariable>[] typeParameters = clazz.getTypeParameters();
+ for (final TypeVariable> typeVariable : typeParameters) {
+ localTypes.add(typeVariable);
+ }
+ localTypes.add(clazz.getGenericSuperclass());
+ for (final Type iface : clazz.getGenericInterfaces()) {
+ localTypes.add(iface);
+ }
+ }
+ } else if (type instanceof TypeVariable) {
+ final TypeVariable> typeVariable = (TypeVariable>)type;
+ final Type[] bounds = typeVariable.getBounds();
+ for (final Type bound : bounds) {
+ localTypes.add(bound);
+ }
+ } else if (type instanceof ParameterizedType) {
+ final ParameterizedType parametrizedType = (ParameterizedType)type;
+ localTypes.add(parametrizedType.getRawType());
+ final Type[] actualArguments = parametrizedType.getActualTypeArguments();
+ for (final Type actualArgument : actualArguments) {
+ localTypes.add(actualArgument);
+ }
+ } else if (type instanceof GenericArrayType) {
+ final GenericArrayType arrayType = (GenericArrayType)type;
+ localTypes.add(arrayType.getGenericComponentType());
+ }
+
+ if (!localTypes.isEmpty()) {
+ final Iterator iter = localTypes.iterator();
+ type = iter.next();
+ iter.remove();
+ } else {
+ type = null;
+ }
+ }
+ }
+
+ private void processConverterAnnotations(final Class> type) {
+ if (converterRegistry != null) {
+ final XStreamConverters convertersAnnotation = type
+ .getAnnotation(XStreamConverters.class);
+ final XStreamConverter converterAnnotation = type
+ .getAnnotation(XStreamConverter.class);
+ final List annotations = convertersAnnotation != null
+ ? new ArrayList(Arrays.asList(convertersAnnotation.value()))
+ : new ArrayList();
+ if (converterAnnotation != null) {
+ annotations.add(converterAnnotation);
+ }
+ for (final XStreamConverter annotation : annotations) {
+ final Converter converter = cacheConverter(
+ annotation, converterAnnotation != null ? type : null);
+ if (converter != null) {
+ if (converterAnnotation != null || converter.canConvert(type)) {
+ converterRegistry.registerConverter(converter, annotation.priority());
+ } else {
+ throw new InitializationException("Converter "
+ + annotation.value().getName()
+ + " cannot handle annotated class "
+ + type.getName());
+ }
+ }
+ }
+ }
+ }
+
+ private void processAliasAnnotation(final Class> type, final Set> types) {
+ final XStreamAlias aliasAnnotation = type.getAnnotation(XStreamAlias.class);
+ if (aliasAnnotation != null) {
+ if (classAliasingMapper == null) {
+ throw new InitializationException("No "
+ + ClassAliasingMapper.class.getName()
+ + " available");
+ }
+ classAliasingMapper.addClassAlias(aliasAnnotation.value(), type);
+ if (aliasAnnotation.impl() != Void.class) {
+ // Alias for Interface/Class with an impl
+ defaultImplementationsMapper.addDefaultImplementation(
+ aliasAnnotation.impl(), type);
+ if (type.isInterface()) {
+ types.add(aliasAnnotation.impl()); // alias Interface's impl
+ }
+ }
+ }
+ }
+
+ private void processAliasTypeAnnotation(final Class> type) {
+ final XStreamAliasType aliasAnnotation = type.getAnnotation(XStreamAliasType.class);
+ if (aliasAnnotation != null) {
+ if (classAliasingMapper == null) {
+ throw new InitializationException("No "
+ + ClassAliasingMapper.class.getName()
+ + " available");
+ }
+ classAliasingMapper.addTypeAlias(aliasAnnotation.value(), type);
+ }
+ }
+
+ @Deprecated
+ private void processImplicitCollectionAnnotation(final Class> type) {
+ final XStreamImplicitCollection implicitColAnnotation = type
+ .getAnnotation(XStreamImplicitCollection.class);
+ if (implicitColAnnotation != null) {
+ if (implicitCollectionMapper == null) {
+ throw new InitializationException("No "
+ + ImplicitCollectionMapper.class.getName()
+ + " available");
+ }
+ final String fieldName = implicitColAnnotation.value();
+ final String itemFieldName = implicitColAnnotation.item();
+ final Field field;
+ try {
+ field = type.getDeclaredField(fieldName);
+ } catch (final NoSuchFieldException e) {
+ throw new InitializationException(type.getName()
+ + " does not have a field named '"
+ + fieldName
+ + "' as required by "
+ + XStreamImplicitCollection.class.getName());
+ }
+ Class itemType = null;
+ final Type genericType = field.getGenericType();
+ if (genericType instanceof ParameterizedType) {
+ final Type typeArgument = ((ParameterizedType)genericType)
+ .getActualTypeArguments()[0];
+ itemType = getClass(typeArgument);
+ }
+ if (itemType == null) {
+ implicitCollectionMapper.add(type, fieldName, null, Object.class);
+ } else {
+ if (itemFieldName.equals("")) {
+ implicitCollectionMapper.add(type, fieldName, null, itemType);
+ } else {
+ implicitCollectionMapper.add(type, fieldName, itemFieldName, itemType);
+ }
+ }
+ }
+ }
+
+ private void processFieldAliasAnnotation(final Field field) {
+ final XStreamAlias aliasAnnotation = field.getAnnotation(XStreamAlias.class);
+ if (aliasAnnotation != null) {
+ if (fieldAliasingMapper == null) {
+ throw new InitializationException("No "
+ + FieldAliasingMapper.class.getName()
+ + " available");
+ }
+ fieldAliasingMapper.addFieldAlias(
+ aliasAnnotation.value(), field.getDeclaringClass(), field.getName());
+ }
+ }
+
+ private void processAsAttributeAnnotation(final Field field) {
+ final XStreamAsAttribute asAttributeAnnotation = field
+ .getAnnotation(XStreamAsAttribute.class);
+ if (asAttributeAnnotation != null) {
+ if (attributeMapper == null) {
+ throw new InitializationException("No "
+ + AttributeMapper.class.getName()
+ + " available");
+ }
+ attributeMapper.addAttributeFor(field);
+ }
+ }
+
+ private void processImplicitAnnotation(final Field field) {
+ final XStreamImplicit implicitAnnotation = field.getAnnotation(XStreamImplicit.class);
+ if (implicitAnnotation != null) {
+ if (implicitCollectionMapper == null) {
+ throw new InitializationException("No "
+ + ImplicitCollectionMapper.class.getName()
+ + " available");
+ }
+ final String fieldName = field.getName();
+ final String itemFieldName = implicitAnnotation.itemFieldName();
+ final String keyFieldName = implicitAnnotation.keyFieldName();
+ boolean isMap = Map.class.isAssignableFrom(field.getType());
+ Class itemType = null;
+ if (!field.getType().isArray()) {
+ final Type genericType = field.getGenericType();
+ if (genericType instanceof ParameterizedType) {
+ final Type[] actualTypeArguments = ((ParameterizedType)genericType)
+ .getActualTypeArguments();
+ final Type typeArgument = actualTypeArguments[isMap ? 1 : 0];
+ itemType = getClass(typeArgument);
+ }
+ }
+ if (isMap) {
+ implicitCollectionMapper.add(
+ field.getDeclaringClass(), fieldName,
+ itemFieldName != null && !"".equals(itemFieldName) ? itemFieldName : null,
+ itemType, keyFieldName != null && !"".equals(keyFieldName)
+ ? keyFieldName
+ : null);
+ } else {
+ if (itemFieldName != null && !"".equals(itemFieldName)) {
+ implicitCollectionMapper.add(
+ field.getDeclaringClass(), fieldName, itemFieldName, itemType);
+ } else {
+ implicitCollectionMapper
+ .add(field.getDeclaringClass(), fieldName, itemType);
+ }
+ }
+ }
+ }
+
+ private void processOmitFieldAnnotation(final Field field) {
+ final XStreamOmitField omitFieldAnnotation = field
+ .getAnnotation(XStreamOmitField.class);
+ if (omitFieldAnnotation != null) {
+ if (fieldAliasingMapper == null) {
+ throw new InitializationException("No "
+ + FieldAliasingMapper.class.getName()
+ + " available");
+ }
+ fieldAliasingMapper.omitField(field.getDeclaringClass(), field.getName());
+ }
+ }
+
+ private void processLocalConverterAnnotation(final Field field) {
+ final XStreamConverter annotation = field.getAnnotation(XStreamConverter.class);
+ if (annotation != null) {
+ final Converter converter = cacheConverter(annotation, field.getType());
+ if (converter != null) {
+ if (localConversionMapper == null) {
+ throw new InitializationException("No "
+ + LocalConversionMapper.class.getName()
+ + " available");
+ }
+ localConversionMapper.registerLocalConverter(
+ field.getDeclaringClass(), field.getName(), converter);
+ }
+ }
+ }
+
+ private Converter cacheConverter(final XStreamConverter annotation,
+ final Class targetType) {
+ Converter result = null;
+ final Object[] args;
+ final List