Details
-
Type: Improvement
-
Status: Closed
-
Priority: Minor
-
Resolution: Fixed
-
Affects Version/s: 1.3.1
-
Fix Version/s: 1.4
-
Component/s: Converters
-
Labels:None
-
JDK version and platform:Sun 1.5.0_17 for Windows
Description
Hi
Using XStream 1.3.1, I run into a runtime error when I deserialize a field with an alias and the attribute "defined-in". In the following, I present a simple demonstration example explaining my motivation and present a source code change that solves it.
Here is the example:
import java.io.Serializable;
public class C1 implements Serializable {
static private final long serialVersionUID = 1L;
public String f1;
C1(String f1)
{ this.f1 = f1; }}
// ------------------------------------------------------------------
public class C2 extends C1 {
static private final long serialVersionUID = 1L;
public String f2;
C2 (String f1, String f2)
{ super(f1); this.f2 = f2; }}
// ------------------------------------------------------------------
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
public class Test {
static public void main(String[] args) throws IOException
static public void serialize(File file) throws IOException {
XStream xstream = new XStream(new DomDriver());
C2 c2 = new C2("s1", "s2");
OutputStream os = new FileOutputStream(file);
try
{ xstream.toXML(c2, os); }finally
{ os.close(); }}
static public void deserialize(File file) throws IOException {
XStream xstream = new XStream(new DomDriver());
InputStream is = new FileInputStream(file);
try
{ xstream.fromXML(is); }finally
{ is.close(); } }
}
In a normal Java Runtime Environment this produces the file "test.xml" with following contents:
<C2>
<f1>s1</f1>
<f2>s2</f2>
</C2>
I have to use obfuscation. When I run it in an obfuscated environment, it still works, but the file contents is obfuscated:
<pck.aab>
<a defined-in="pck.aaa">s1</a>
<a>s2</a>
</pck.aab>
Now, I want to deserialize an obfuscated xml output in a non-obfuscated environment. Thus, I use the following aliases:
xstream.alias("pck.aaa", C1.class);
xstream.aliasField("a", C1.class, "f1");
xstream.alias("pck.aab", C2.class);
xstream.aliasField("a", C2.class, "f2");
When I run it, the following log of a runtime exception is printed:
Exception in thread "main" com.thoughtworks.xstream.converters.ConversionException: No such field C2.f2 : No such field C2.f2
---- Debugging information ----
message : No such field C2.f2
cause-exception : com.thoughtworks.xstream.converters.reflection.ObjectAccessException
cause-message : No such field C2.f2
class : C2
required-type : C2
path : /pck.aab/a
-------------------------------
at com.thoughtworks.xstream.core.TreeUnmarshaller.convert(TreeUnmarshaller.java:89)
at com.thoughtworks.xstream.core.AbstractReferenceUnmarshaller.convert(AbstractReferenceUnmarshaller.java:63)
at com.thoughtworks.xstream.core.TreeUnmarshaller.convertAnother(TreeUnmarshaller.java:76)
at com.thoughtworks.xstream.core.TreeUnmarshaller.convertAnother(TreeUnmarshaller.java:60)
at com.thoughtworks.xstream.core.TreeUnmarshaller.start(TreeUnmarshaller.java:137)
at com.thoughtworks.xstream.core.AbstractTreeMarshallingStrategy.unmarshal(AbstractTreeMarshallingStrategy.java:33)
at com.thoughtworks.xstream.XStream.unmarshal(XStream.java:923)
at com.thoughtworks.xstream.XStream.unmarshal(XStream.java:909)
at com.thoughtworks.xstream.XStream.fromXML(XStream.java:861)
at TestDeserializeObfuscated.deserialize(TestDeserializeObfuscated.java:25)
at TestDeserializeObfuscated.main(TestDeserializeObfuscated.java:12)
Caused by: com.thoughtworks.xstream.converters.reflection.ObjectAccessException: No such field C2.f2
at com.thoughtworks.xstream.converters.reflection.FieldDictionary.field(FieldDictionary.java:94)
at com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider.getFieldType(PureJavaReflectionProvider.java:151)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.determineType(AbstractReflectionConverter.java:350)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.doUnmarshal(AbstractReflectionConverter.java:208)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.unmarshal(AbstractReflectionConverter.java:162)
at com.thoughtworks.xstream.core.TreeUnmarshaller.convert(TreeUnmarshaller.java:82)
... 10 more
I had a look into the source code and got the impression, that the "defined-in" attribute is not considered in antialiasing. The deserialization works if I consider the class defining field in field name calculation according to the following code change in com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter#doUnmarshal(Object, HierarchicalStreamReader reader, UnmarshallingContext), line 202f:
Class classDefiningField = determineWhichClassDefinesField(reader);
Class fieldClass = classDefiningField == null ? result.getClass() : classDefiningField;
String fieldName = mapper.realMember(fieldClass, originalNodeName);
Mapper.ImplicitCollectionMapping implicitCollectionMapping = mapper.getImplicitCollectionDefForFieldName(result.getClass(), fieldName);
boolean fieldExistsInClass = implicitCollectionMapping == null && reflectionProvider.fieldDefinedInClass(fieldName, result.getClass());
instead of
String fieldName = mapper.realMember(result.getClass(), originalNodeName);
Mapper.ImplicitCollectionMapping implicitCollectionMapping = mapper.getImplicitCollectionDefForFieldName(result.getClass(), fieldName);
Class classDefiningField = determineWhichClassDefinesField(reader);
boolean fieldExistsInClass = implicitCollectionMapping == null && reflectionProvider.fieldDefinedInClass(fieldName, result.getClass());
Consistently, the "defined-in" attribute would have to be considered both in aliasing and in antialiasing - if I try my example without obfuscation and with aliasing for both side, I get a file without "defined-in" and a DuplicateFieldException on deserialization. Do you see a possibility to realize such a extension it in the next XStream version?
Thanks,
Till
Hi Till, XStream supports this scenario now at deserialization time - more or less with the changes you've proposed. However, I see no possibility to implement support at serialization time. Elements of the parent are always written first and to write a defined-in attribute in case an alias creates a name clash, we would have to collect the alias names for all other fields in advance. Available in HEAD now.