KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > thoughtworks > xstream > converters > reflection > SerializableConverter


1 package com.thoughtworks.xstream.converters.reflection;
2
3 import com.thoughtworks.xstream.converters.ConversionException;
4 import com.thoughtworks.xstream.converters.Converter;
5 import com.thoughtworks.xstream.converters.MarshallingContext;
6 import com.thoughtworks.xstream.converters.UnmarshallingContext;
7 import com.thoughtworks.xstream.core.util.CustomObjectInputStream;
8 import com.thoughtworks.xstream.core.util.CustomObjectOutputStream;
9 import com.thoughtworks.xstream.io.HierarchicalStreamReader;
10 import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
11 import com.thoughtworks.xstream.mapper.Mapper;
12
13 import java.io.IOException JavaDoc;
14 import java.io.InvalidObjectException JavaDoc;
15 import java.io.ObjectInputStream JavaDoc;
16 import java.io.ObjectInputValidation JavaDoc;
17 import java.io.ObjectOutputStream JavaDoc;
18 import java.io.ObjectStreamClass JavaDoc;
19 import java.io.ObjectStreamField JavaDoc;
20 import java.io.Serializable JavaDoc;
21 import java.lang.reflect.Field JavaDoc;
22 import java.util.ArrayList JavaDoc;
23 import java.util.Collections JavaDoc;
24 import java.util.HashMap JavaDoc;
25 import java.util.Iterator JavaDoc;
26 import java.util.List JavaDoc;
27 import java.util.Map JavaDoc;
28
29 /**
30  * Emulates the mechanism used by standard Java Serialization for classes that implement java.io.Serializable AND
31  * implement a custom readObject()/writeObject() method.
32  *
33  * <h3>Supported features of serialization</h3>
34  * <ul>
35  * <li>readObject(), writeObject()</li>
36  * <li>class inheritance</li>
37  * <li>readResolve(), writeReplace()</li>
38  * </ul>
39  *
40  * <h3>Currently unsupported features</h3>
41  * <ul>
42  * <li>putFields(), writeFields(), readFields()</li>
43  * <li>ObjectStreamField[] serialPersistentFields</li>
44  * <li>ObjectInputValidation</li>
45  * </ul>
46  *
47  * @author Joe Walnes
48  */

49 public class SerializableConverter implements Converter {
50
51     private final SerializationMethodInvoker serializationMethodInvoker = new SerializationMethodInvoker();
52     private final Mapper mapper;
53     private final ReflectionProvider reflectionProvider;
54
55     private static final String JavaDoc ELEMENT_NULL = "null";
56     private static final String JavaDoc ELEMENT_DEFAULT = "default";
57     private static final String JavaDoc ATTRIBUTE_CLASS = "class";
58     private static final String JavaDoc ATTRIBUTE_SERIALIZATION = "serialization";
59     private static final String JavaDoc ATTRIBUTE_VALUE_CUSTOM = "custom";
60     private static final String JavaDoc ELEMENT_FIELDS = "fields";
61     private static final String JavaDoc ELEMENT_FIELD = "field";
62     private static final String JavaDoc ATTRIBUTE_NAME = "name";
63
64     public SerializableConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
65         this.mapper = mapper;
66         this.reflectionProvider = reflectionProvider;
67     }
68
69     public boolean canConvert(Class JavaDoc type) {
70         return Serializable JavaDoc.class.isAssignableFrom(type)
71           && ( serializationMethodInvoker.supportsReadObject(type, true)
72             || serializationMethodInvoker.supportsWriteObject(type, true) );
73     }
74
75     public void marshal(Object JavaDoc source, final HierarchicalStreamWriter writer, final MarshallingContext context) {
76         final Object JavaDoc replacedSource = serializationMethodInvoker.callWriteReplace(source);
77
78         if (replacedSource.getClass() != source.getClass()) {
79             writer.addAttribute(mapper.attributeForReadResolveField(), mapper.serializedClass(replacedSource.getClass()));
80         }
81
82         writer.addAttribute(ATTRIBUTE_SERIALIZATION, ATTRIBUTE_VALUE_CUSTOM);
83
84         // this is an array as it's a non final value that's accessed from an anonymous inner class.
85
final Class JavaDoc[] currentType = new Class JavaDoc[1];
86         final boolean[] writtenClassWrapper = {false};
87
88         CustomObjectOutputStream.StreamCallback callback = new CustomObjectOutputStream.StreamCallback() {
89
90             public void writeToStream(Object JavaDoc object) {
91                 if (object == null) {
92                     writer.startNode(ELEMENT_NULL);
93                     writer.endNode();
94                 } else {
95                     writer.startNode(mapper.serializedClass(object.getClass()));
96                     context.convertAnother(object);
97                     writer.endNode();
98                 }
99             }
100
101             public void writeFieldsToStream(Map JavaDoc fields) {
102                 ObjectStreamClass JavaDoc objectStreamClass = ObjectStreamClass.lookup(currentType[0]);
103
104                 writer.startNode(ELEMENT_DEFAULT);
105                 for (Iterator JavaDoc iterator = fields.keySet().iterator(); iterator.hasNext();) {
106                     String JavaDoc name = (String JavaDoc) iterator.next();
107                     ObjectStreamField JavaDoc field = objectStreamClass.getField(name);
108                     Object JavaDoc value = fields.get(name);
109                     if (field == null) {
110                         throw new ObjectAccessException("Class " + value.getClass().getName()
111                                 + " may not write a field named '" + name + "'");
112                     }
113                     if (value != null) {
114                         writer.startNode(mapper.serializedMember(currentType[0], name));
115                         if (field.getType() != value.getClass() && !field.getType().isPrimitive()) {
116                             writer.addAttribute(ATTRIBUTE_CLASS, mapper.serializedClass(value.getClass()));
117                         }
118                         context.convertAnother(value);
119                         writer.endNode();
120                     }
121                 }
122                 writer.endNode();
123             }
124
125             public void defaultWriteObject() {
126                 boolean writtenDefaultFields = false;
127
128                 ObjectStreamClass JavaDoc objectStreamClass = ObjectStreamClass.lookup(currentType[0]);
129
130                 if (objectStreamClass == null) {
131                     return;
132                 }
133
134                 ObjectStreamField JavaDoc[] fields = objectStreamClass.getFields();
135                 for (int i = 0; i < fields.length; i++) {
136                     ObjectStreamField JavaDoc field = fields[i];
137                     Object JavaDoc value = readField(field, currentType[0], replacedSource);
138                     if (value != null) {
139                         if (!writtenClassWrapper[0]) {
140                             writer.startNode(mapper.serializedClass(currentType[0]));
141                             writtenClassWrapper[0] = true;
142                         }
143                         if (!writtenDefaultFields) {
144                             writer.startNode(ELEMENT_DEFAULT);
145                             writtenDefaultFields = true;
146                         }
147
148                         writer.startNode(mapper.serializedMember(currentType[0], field.getName()));
149
150                         Class JavaDoc actualType = value.getClass();
151                         Class JavaDoc defaultType = mapper.defaultImplementationOf(field.getType());
152                         if (!actualType.equals(defaultType)) {
153                             writer.addAttribute(ATTRIBUTE_CLASS, mapper.serializedClass(actualType));
154                         }
155
156                         context.convertAnother(value);
157
158                         writer.endNode();
159                     }
160                 }
161                 if (writtenClassWrapper[0] && !writtenDefaultFields) {
162                     writer.startNode(ELEMENT_DEFAULT);
163                     writer.endNode();
164                 } else if (writtenDefaultFields) {
165                     writer.endNode();
166                 }
167             }
168
169             public void flush() {
170                 writer.flush();
171             }
172
173             public void close() {
174                 throw new UnsupportedOperationException JavaDoc("Objects are not allowed to call ObjectOutputStream.close() from writeObject()");
175             }
176         };
177
178         try {
179             Iterator JavaDoc classHieararchy = hierarchyFor(replacedSource.getClass());
180             while (classHieararchy.hasNext()) {
181                 currentType[0] = (Class JavaDoc) classHieararchy.next();
182                 if (serializationMethodInvoker.supportsWriteObject(currentType[0], false)) {
183                     writtenClassWrapper[0] = true;
184                     writer.startNode(mapper.serializedClass(currentType[0]));
185                     ObjectOutputStream objectOutputStream = CustomObjectOutputStream.getInstance(context, callback);
186                     serializationMethodInvoker.callWriteObject(currentType[0], replacedSource, objectOutputStream);
187                     writer.endNode();
188                 } else if (serializationMethodInvoker.supportsReadObject(currentType[0], false)) {
189                     // Special case for objects that have readObject(), but not writeObject().
190
// The class wrapper is always written, whether or not this class in the hierarchy has
191
// serializable fields. This guarantees that readObject() will be called upon deserialization.
192
writtenClassWrapper[0] = true;
193                     writer.startNode(mapper.serializedClass(currentType[0]));
194                     callback.defaultWriteObject();
195                     writer.endNode();
196                 } else {
197                     writtenClassWrapper[0] = false;
198                     callback.defaultWriteObject();
199                     if (writtenClassWrapper[0]) {
200                         writer.endNode();
201                     }
202                 }
203             }
204         } catch (IOException JavaDoc e) {
205             throw new ObjectAccessException("Could not call defaultWriteObject()", e);
206         }
207     }
208
209     private Object JavaDoc readField(ObjectStreamField JavaDoc field, Class JavaDoc type, Object JavaDoc instance) {
210         try {
211             Field JavaDoc javaField = type.getDeclaredField(field.getName());
212             javaField.setAccessible(true);
213             return javaField.get(instance);
214         } catch (IllegalArgumentException JavaDoc e) {
215             throw new ObjectAccessException("Could not get field " + field.getClass() + "." + field.getName(), e);
216         } catch (IllegalAccessException JavaDoc e) {
217             throw new ObjectAccessException("Could not get field " + field.getClass() + "." + field.getName(), e);
218         } catch (NoSuchFieldException JavaDoc e) {
219             throw new ObjectAccessException("Could not get field " + field.getClass() + "." + field.getName(), e);
220         } catch (SecurityException JavaDoc e) {
221             throw new ObjectAccessException("Could not get field " + field.getClass() + "." + field.getName(), e);
222         }
223     }
224
225     private Iterator JavaDoc hierarchyFor(Class JavaDoc type) {
226         List JavaDoc result = new ArrayList JavaDoc();
227         while(type != null) {
228             result.add(type);
229             type = type.getSuperclass();
230         }
231
232         // In Java Object Serialization, the classes are deserialized starting from parent class and moving down.
233
Collections.reverse(result);
234
235         return result.iterator();
236     }
237
238     public Object JavaDoc unmarshal(final HierarchicalStreamReader reader, final UnmarshallingContext context) {
239         String JavaDoc resolvesAttribute = reader.getAttribute(mapper.attributeForReadResolveField());
240         Class JavaDoc requiredType;
241         if (resolvesAttribute != null) {
242             requiredType = mapper.realClass(resolvesAttribute);
243         } else {
244             requiredType = context.getRequiredType();
245         }
246         final Object JavaDoc result = reflectionProvider.newInstance(requiredType);
247
248         // this is an array as it's a non final value that's accessed from an anonymous inner class.
249
final Class JavaDoc[] currentType = new Class JavaDoc[1];
250
251         if (!ATTRIBUTE_VALUE_CUSTOM.equals(reader.getAttribute(ATTRIBUTE_SERIALIZATION))) {
252             throw new ConversionException("Cannot deserialize object with new readObject()/writeObject() methods");
253         }
254
255         CustomObjectInputStream.StreamCallback callback = new CustomObjectInputStream.StreamCallback() {
256             public Object JavaDoc readFromStream() {
257                 reader.moveDown();
258                 Class JavaDoc type = mapper.realClass(reader.getNodeName());
259                 Object JavaDoc value = context.convertAnother(result, type);
260                 reader.moveUp();
261                 return value;
262             }
263
264             public Map JavaDoc readFieldsFromStream() {
265                 Map JavaDoc result = new HashMap JavaDoc();
266                 reader.moveDown();
267                 if (reader.getNodeName().equals(ELEMENT_FIELDS)) {
268                     // Maintain compatability with XStream 1.1.0
269
while (reader.hasMoreChildren()) {
270                         reader.moveDown();
271                         if (!reader.getNodeName().equals(ELEMENT_FIELD)) {
272                             throw new ConversionException("Expected <" + ELEMENT_FIELD + "/> element inside <" + ELEMENT_FIELD + "/>");
273                         }
274                         String JavaDoc name = reader.getAttribute(ATTRIBUTE_NAME);
275                         Class JavaDoc type = mapper.realClass(reader.getAttribute(ATTRIBUTE_CLASS));
276                         Object JavaDoc value = context.convertAnother(result, type);
277                         result.put(name, value);
278                         reader.moveUp();
279                     }
280                 } else if (reader.getNodeName().equals(ELEMENT_DEFAULT)) {
281                     // New format introduced in XStream 1.1.1
282
ObjectStreamClass JavaDoc objectStreamClass = ObjectStreamClass.lookup(currentType[0]);
283                     while (reader.hasMoreChildren()) {
284                         reader.moveDown();
285                         String JavaDoc name = reader.getNodeName();
286                         String JavaDoc typeName = reader.getAttribute(ATTRIBUTE_CLASS);
287                         Class JavaDoc type;
288                         if (typeName != null) {
289                             type = mapper.realClass(typeName);
290                         } else {
291                             ObjectStreamField JavaDoc field = objectStreamClass.getField(name);
292                             if (field == null) {
293                                 throw new ObjectAccessException("Class " + currentType[0]
294                                         + " does not contain a field named '" + name + "'");
295                             }
296                             type = field.getType();
297                         }
298                         Object JavaDoc value = context.convertAnother(result, type);
299                         result.put(name, value);
300                         reader.moveUp();
301                     }
302                 } else {
303                     throw new ConversionException("Expected <" + ELEMENT_FIELDS + "/> or <" +
304                             ELEMENT_DEFAULT + "/> element when calling ObjectInputStream.readFields()");
305                 }
306                 reader.moveUp();
307                 return result;
308             }
309
310             public void defaultReadObject() {
311                 if (!reader.hasMoreChildren()) {
312                     return;
313                 }
314                 reader.moveDown();
315                 if (!reader.getNodeName().equals(ELEMENT_DEFAULT)) {
316                     throw new ConversionException("Expected <" + ELEMENT_DEFAULT + "/> element in readObject() stream");
317                 }
318                 while (reader.hasMoreChildren()) {
319                     reader.moveDown();
320
321                     Class JavaDoc type;
322                     String JavaDoc fieldName = mapper.realMember(currentType[0], reader.getNodeName());
323                     String JavaDoc classAttribute = reader.getAttribute(ATTRIBUTE_CLASS);
324                     if (classAttribute != null) {
325                         type = mapper.realClass(classAttribute);
326                     } else {
327                         type = mapper.defaultImplementationOf(reflectionProvider.getFieldType(result, fieldName, currentType[0]));
328                     }
329
330                     Object JavaDoc value = context.convertAnother(result, type);
331                     reflectionProvider.writeField(result, fieldName, value, currentType[0]);
332
333                     reader.moveUp();
334                 }
335                 reader.moveUp();
336             }
337
338             public void registerValidation(final ObjectInputValidation JavaDoc validation, int priority) {
339                 context.addCompletionCallback(new Runnable JavaDoc() {
340                     public void run() {
341                         try {
342                             validation.validateObject();
343                         } catch (InvalidObjectException JavaDoc e) {
344                             throw new ObjectAccessException("Cannot validate object : " + e.getMessage(), e);
345                         }
346                     }
347                 }, priority);
348             }
349
350             public void close() {
351                 throw new UnsupportedOperationException JavaDoc("Objects are not allowed to call ObjectInputStream.close() from readObject()");
352             }
353         };
354
355         while (reader.hasMoreChildren()) {
356             reader.moveDown();
357             currentType[0] = mapper.defaultImplementationOf(mapper.realClass(reader.getNodeName()));
358             if (serializationMethodInvoker.supportsReadObject(currentType[0], false)) {
359                 ObjectInputStream objectInputStream = CustomObjectInputStream.getInstance(context, callback);
360                 serializationMethodInvoker.callReadObject(currentType[0], result, objectInputStream);
361             } else {
362                 try {
363                     callback.defaultReadObject();
364                 } catch (IOException JavaDoc e) {
365                     throw new ObjectAccessException("Could not call defaultWriteObject()", e);
366                 }
367             }
368             reader.moveUp();
369         }
370
371         return serializationMethodInvoker.callReadResolve(result);
372     }
373
374 }
375
Popular Tags