001: /*
002: * Copyright (C) 2004, 2005, 2006 Joe Walnes.
003: * Copyright (C) 2006, 2007 XStream Committers.
004: * All rights reserved.
005: *
006: * The software in this package is published under the terms of the BSD
007: * style license a copy of which has been included with this distribution in
008: * the LICENSE.txt file.
009: *
010: * Created on 21. December 2004 by Joe Walnes
011: */
012: package com.thoughtworks.xstream.converters.reflection;
013:
014: import java.io.IOException;
015: import java.io.InvalidObjectException;
016: import java.io.ObjectInputValidation;
017: import java.io.ObjectStreamClass;
018: import java.io.ObjectStreamField;
019: import java.io.Serializable;
020: import java.lang.reflect.Field;
021: import java.util.ArrayList;
022: import java.util.Collections;
023: import java.util.HashMap;
024: import java.util.Iterator;
025: import java.util.List;
026: import java.util.Map;
027:
028: import com.thoughtworks.xstream.converters.ConversionException;
029: import com.thoughtworks.xstream.converters.MarshallingContext;
030: import com.thoughtworks.xstream.converters.UnmarshallingContext;
031: import com.thoughtworks.xstream.core.util.CustomObjectInputStream;
032: import com.thoughtworks.xstream.core.util.CustomObjectOutputStream;
033: import com.thoughtworks.xstream.io.HierarchicalStreamReader;
034: import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
035: import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper;
036: import com.thoughtworks.xstream.mapper.Mapper;
037:
038: /**
039: * Emulates the mechanism used by standard Java Serialization for classes that implement java.io.Serializable AND
040: * implement or inherit a custom readObject()/writeObject() method.
041: *
042: * <h3>Supported features of serialization</h3>
043: * <ul>
044: * <li>readObject(), writeObject()</li>
045: * <li>class inheritance</li>
046: * <li>readResolve(), writeReplace()</li>
047: * </ul>
048: *
049: * <h3>Currently unsupported features</h3>
050: * <ul>
051: * <li>putFields(), writeFields(), readFields()</li>
052: * <li>ObjectStreamField[] serialPersistentFields</li>
053: * <li>ObjectInputValidation</li>
054: * </ul>
055: *
056: * @author Joe Walnes
057: * @author Jörg Schaible
058: */
059: public class SerializableConverter extends AbstractReflectionConverter {
060:
061: private static final String ELEMENT_NULL = "null";
062: private static final String ELEMENT_DEFAULT = "default";
063: private static final String ELEMENT_UNSERIALIZABLE_PARENTS = "unserializable-parents";
064: private static final String ATTRIBUTE_CLASS = "class";
065: private static final String ATTRIBUTE_SERIALIZATION = "serialization";
066: private static final String ATTRIBUTE_VALUE_CUSTOM = "custom";
067: private static final String ELEMENT_FIELDS = "fields";
068: private static final String ELEMENT_FIELD = "field";
069: private static final String ATTRIBUTE_NAME = "name";
070:
071: public SerializableConverter(Mapper mapper,
072: ReflectionProvider reflectionProvider) {
073: super (mapper, new UnserializableParentsReflectionProvider(
074: reflectionProvider));
075: }
076:
077: public boolean canConvert(Class type) {
078: return isSerializable(type);
079: }
080:
081: private boolean isSerializable(Class type) {
082: return Serializable.class.isAssignableFrom(type)
083: && (serializationMethodInvoker.supportsReadObject(type,
084: true) || serializationMethodInvoker
085: .supportsWriteObject(type, true));
086: }
087:
088: public void doMarshal(final Object source,
089: final HierarchicalStreamWriter writer,
090: final MarshallingContext context) {
091: writer.addAttribute(mapper
092: .aliasForAttribute(ATTRIBUTE_SERIALIZATION),
093: ATTRIBUTE_VALUE_CUSTOM);
094:
095: // this is an array as it's a non final value that's accessed from an anonymous inner class.
096: final Class[] currentType = new Class[1];
097: final boolean[] writtenClassWrapper = { false };
098:
099: CustomObjectOutputStream.StreamCallback callback = new CustomObjectOutputStream.StreamCallback() {
100:
101: public void writeToStream(Object object) {
102: if (object == null) {
103: writer.startNode(ELEMENT_NULL);
104: writer.endNode();
105: } else {
106: ExtendedHierarchicalStreamWriterHelper.startNode(
107: writer, mapper.serializedClass(object
108: .getClass()), object.getClass());
109: context.convertAnother(object);
110: writer.endNode();
111: }
112: }
113:
114: public void writeFieldsToStream(Map fields) {
115: ObjectStreamClass objectStreamClass = ObjectStreamClass
116: .lookup(currentType[0]);
117:
118: writer.startNode(ELEMENT_DEFAULT);
119: for (Iterator iterator = fields.keySet().iterator(); iterator
120: .hasNext();) {
121: String name = (String) iterator.next();
122: if (!mapper.shouldSerializeMember(currentType[0],
123: name)) {
124: continue;
125: }
126: ObjectStreamField field = objectStreamClass
127: .getField(name);
128: Object value = fields.get(name);
129: if (field == null) {
130: throw new ObjectAccessException("Class "
131: + value.getClass().getName()
132: + " may not write a field named '"
133: + name + "'");
134: }
135: if (value != null) {
136: ExtendedHierarchicalStreamWriterHelper
137: .startNode(writer, mapper
138: .serializedMember(source
139: .getClass(), name),
140: field.getType());
141: if (field.getType() != value.getClass()
142: && !field.getType().isPrimitive()) {
143: writer
144: .addAttribute(
145: mapper
146: .aliasForAttribute(ATTRIBUTE_CLASS),
147: mapper
148: .serializedClass(value
149: .getClass()));
150: }
151: context.convertAnother(value);
152: writer.endNode();
153: }
154: }
155: writer.endNode();
156: }
157:
158: public void defaultWriteObject() {
159: boolean writtenDefaultFields = false;
160:
161: ObjectStreamClass objectStreamClass = ObjectStreamClass
162: .lookup(currentType[0]);
163:
164: if (objectStreamClass == null) {
165: return;
166: }
167:
168: ObjectStreamField[] fields = objectStreamClass
169: .getFields();
170: for (int i = 0; i < fields.length; i++) {
171: ObjectStreamField field = fields[i];
172: Object value = readField(field, currentType[0],
173: source);
174: if (value != null) {
175: if (!writtenClassWrapper[0]) {
176: writer.startNode(mapper
177: .serializedClass(currentType[0]));
178: writtenClassWrapper[0] = true;
179: }
180: if (!writtenDefaultFields) {
181: writer.startNode(ELEMENT_DEFAULT);
182: writtenDefaultFields = true;
183: }
184: if (!mapper.shouldSerializeMember(
185: currentType[0], field.getName())) {
186: continue;
187: }
188:
189: ExtendedHierarchicalStreamWriterHelper
190: .startNode(writer, mapper
191: .serializedMember(source
192: .getClass(), field
193: .getName()), field
194: .getType());
195:
196: Class actualType = value.getClass();
197: Class defaultType = mapper
198: .defaultImplementationOf(field
199: .getType());
200: if (!actualType.equals(defaultType)) {
201: writer
202: .addAttribute(
203: mapper
204: .aliasForAttribute(ATTRIBUTE_CLASS),
205: mapper
206: .serializedClass(actualType));
207: }
208:
209: context.convertAnother(value);
210:
211: writer.endNode();
212: }
213: }
214: if (writtenClassWrapper[0] && !writtenDefaultFields) {
215: writer.startNode(ELEMENT_DEFAULT);
216: writer.endNode();
217: } else if (writtenDefaultFields) {
218: writer.endNode();
219: }
220: }
221:
222: public void flush() {
223: writer.flush();
224: }
225:
226: public void close() {
227: throw new UnsupportedOperationException(
228: "Objects are not allowed to call ObjectOutputStream.close() from writeObject()");
229: }
230: };
231:
232: try {
233: boolean mustHandleUnserializableParent = false;
234: Iterator classHieararchy = hierarchyFor(source.getClass())
235: .iterator();
236: while (classHieararchy.hasNext()) {
237: currentType[0] = (Class) classHieararchy.next();
238: if (!Serializable.class
239: .isAssignableFrom(currentType[0])) {
240: mustHandleUnserializableParent = true;
241: continue;
242: } else {
243: if (mustHandleUnserializableParent) {
244: marshalUnserializableParent(writer, context,
245: source);
246: mustHandleUnserializableParent = false;
247: }
248: if (serializationMethodInvoker.supportsWriteObject(
249: currentType[0], false)) {
250: writtenClassWrapper[0] = true;
251: writer.startNode(mapper
252: .serializedClass(currentType[0]));
253: CustomObjectOutputStream objectOutputStream = CustomObjectOutputStream
254: .getInstance(context, callback);
255: serializationMethodInvoker.callWriteObject(
256: currentType[0], source,
257: objectOutputStream);
258: objectOutputStream.popCallback();
259: writer.endNode();
260: } else if (serializationMethodInvoker
261: .supportsReadObject(currentType[0], false)) {
262: // Special case for objects that have readObject(), but not writeObject().
263: // The class wrapper is always written, whether or not this class in the hierarchy has
264: // serializable fields. This guarantees that readObject() will be called upon deserialization.
265: writtenClassWrapper[0] = true;
266: writer.startNode(mapper
267: .serializedClass(currentType[0]));
268: callback.defaultWriteObject();
269: writer.endNode();
270: } else {
271: writtenClassWrapper[0] = false;
272: callback.defaultWriteObject();
273: if (writtenClassWrapper[0]) {
274: writer.endNode();
275: }
276: }
277: }
278: }
279: } catch (IOException e) {
280: throw new ObjectAccessException(
281: "Could not call defaultWriteObject()", e);
282: }
283: }
284:
285: private void marshalUnserializableParent(
286: final HierarchicalStreamWriter writer,
287: final MarshallingContext context,
288: final Object replacedSource) {
289: writer.startNode(ELEMENT_UNSERIALIZABLE_PARENTS);
290: super .doMarshal(replacedSource, writer, context);
291: writer.endNode();
292: }
293:
294: private Object readField(ObjectStreamField field, Class type,
295: Object instance) {
296: try {
297: Field javaField = type.getDeclaredField(field.getName());
298: javaField.setAccessible(true);
299: return javaField.get(instance);
300: } catch (IllegalArgumentException e) {
301: throw new ObjectAccessException("Could not get field "
302: + field.getClass() + "." + field.getName(), e);
303: } catch (IllegalAccessException e) {
304: throw new ObjectAccessException("Could not get field "
305: + field.getClass() + "." + field.getName(), e);
306: } catch (NoSuchFieldException e) {
307: throw new ObjectAccessException("Could not get field "
308: + field.getClass() + "." + field.getName(), e);
309: } catch (SecurityException e) {
310: throw new ObjectAccessException("Could not get field "
311: + field.getClass() + "." + field.getName(), e);
312: }
313: }
314:
315: protected List hierarchyFor(Class type) {
316: List result = new ArrayList();
317: while (type != Object.class) {
318: result.add(type);
319: type = type.getSuperclass();
320: }
321:
322: // In Java Object Serialization, the classes are deserialized starting from parent class and moving down.
323: Collections.reverse(result);
324:
325: return result;
326: }
327:
328: public Object doUnmarshal(final Object result,
329: final HierarchicalStreamReader reader,
330: final UnmarshallingContext context) {
331: // this is an array as it's a non final value that's accessed from an anonymous inner class.
332: final Class[] currentType = new Class[1];
333:
334: if (!ATTRIBUTE_VALUE_CUSTOM.equals(reader.getAttribute(mapper
335: .aliasForAttribute(ATTRIBUTE_SERIALIZATION)))) {
336: throw new ConversionException(
337: "Cannot deserialize object with new readObject()/writeObject() methods");
338: }
339:
340: CustomObjectInputStream.StreamCallback callback = new CustomObjectInputStream.StreamCallback() {
341: public Object readFromStream() {
342: reader.moveDown();
343: Class type = mapper.realClass(reader.getNodeName());
344: Object value = context.convertAnother(result, type);
345: reader.moveUp();
346: return value;
347: }
348:
349: public Map readFieldsFromStream() {
350: final Map fields = new HashMap();
351: reader.moveDown();
352: if (reader.getNodeName().equals(ELEMENT_FIELDS)) {
353: // Maintain compatibility with XStream 1.1.0
354: while (reader.hasMoreChildren()) {
355: reader.moveDown();
356: if (!reader.getNodeName().equals(ELEMENT_FIELD)) {
357: throw new ConversionException("Expected <"
358: + ELEMENT_FIELD
359: + "/> element inside <"
360: + ELEMENT_FIELD + "/>");
361: }
362: String name = reader
363: .getAttribute(ATTRIBUTE_NAME);
364: Class type = mapper.realClass(reader
365: .getAttribute(ATTRIBUTE_CLASS));
366: Object value = context.convertAnother(result,
367: type);
368: fields.put(name, value);
369: reader.moveUp();
370: }
371: } else if (reader.getNodeName().equals(ELEMENT_DEFAULT)) {
372: // New format introduced in XStream 1.1.1
373: ObjectStreamClass objectStreamClass = ObjectStreamClass
374: .lookup(currentType[0]);
375: while (reader.hasMoreChildren()) {
376: reader.moveDown();
377: String name = mapper.realMember(currentType[0],
378: reader.getNodeName());
379: if (mapper.shouldSerializeMember(
380: currentType[0], name)) {
381: String typeName = reader
382: .getAttribute(mapper
383: .aliasForAttribute(ATTRIBUTE_CLASS));
384: Class type;
385: if (typeName != null) {
386: type = mapper.realClass(typeName);
387: } else {
388: ObjectStreamField field = objectStreamClass
389: .getField(name);
390: if (field == null) {
391: throw new ObjectAccessException(
392: "Class "
393: + currentType[0]
394: + " does not contain a field named '"
395: + name + "'");
396: }
397: type = field.getType();
398: }
399: Object value = context.convertAnother(
400: result, type);
401: fields.put(name, value);
402: }
403: reader.moveUp();
404: }
405: } else {
406: throw new ConversionException(
407: "Expected <"
408: + ELEMENT_FIELDS
409: + "/> or <"
410: + ELEMENT_DEFAULT
411: + "/> element when calling ObjectInputStream.readFields()");
412: }
413: reader.moveUp();
414: return fields;
415: }
416:
417: public void defaultReadObject() {
418: if (!reader.hasMoreChildren()) {
419: return;
420: }
421: reader.moveDown();
422: if (!reader.getNodeName().equals(ELEMENT_DEFAULT)) {
423: throw new ConversionException("Expected <"
424: + ELEMENT_DEFAULT
425: + "/> element in readObject() stream");
426: }
427: while (reader.hasMoreChildren()) {
428: reader.moveDown();
429:
430: String fieldName = mapper.realMember(
431: currentType[0], reader.getNodeName());
432: if (mapper.shouldSerializeMember(currentType[0],
433: fieldName)) {
434: String classAttribute = reader
435: .getAttribute(mapper
436: .aliasForAttribute(ATTRIBUTE_CLASS));
437: final Class type;
438: if (classAttribute != null) {
439: type = mapper.realClass(classAttribute);
440: } else {
441: type = mapper
442: .defaultImplementationOf(reflectionProvider
443: .getFieldType(result,
444: fieldName,
445: currentType[0]));
446: }
447:
448: Object value = context.convertAnother(result,
449: type);
450: reflectionProvider.writeField(result,
451: fieldName, value, currentType[0]);
452: }
453:
454: reader.moveUp();
455: }
456: reader.moveUp();
457: }
458:
459: public void registerValidation(
460: final ObjectInputValidation validation, int priority) {
461: context.addCompletionCallback(new Runnable() {
462: public void run() {
463: try {
464: validation.validateObject();
465: } catch (InvalidObjectException e) {
466: throw new ObjectAccessException(
467: "Cannot validate object : "
468: + e.getMessage(), e);
469: }
470: }
471: }, priority);
472: }
473:
474: public void close() {
475: throw new UnsupportedOperationException(
476: "Objects are not allowed to call ObjectInputStream.close() from readObject()");
477: }
478: };
479:
480: while (reader.hasMoreChildren()) {
481: reader.moveDown();
482: String nodeName = reader.getNodeName();
483: if (nodeName.equals(ELEMENT_UNSERIALIZABLE_PARENTS)) {
484: super .doUnmarshal(result, reader, context);
485: } else {
486: currentType[0] = mapper.defaultImplementationOf(mapper
487: .realClass(nodeName));
488: if (serializationMethodInvoker.supportsReadObject(
489: currentType[0], false)) {
490: CustomObjectInputStream objectInputStream = CustomObjectInputStream
491: .getInstance(context, callback);
492: serializationMethodInvoker.callReadObject(
493: currentType[0], result, objectInputStream);
494: objectInputStream.popCallback();
495: } else {
496: try {
497: callback.defaultReadObject();
498: } catch (IOException e) {
499: throw new ObjectAccessException(
500: "Could not call defaultWriteObject()",
501: e);
502: }
503: }
504: }
505: reader.moveUp();
506: }
507:
508: return result;
509: }
510:
511: protected void doMarshalConditionally(final Object source,
512: final HierarchicalStreamWriter writer,
513: final MarshallingContext context) {
514: if (isSerializable(source.getClass())) {
515: doMarshal(source, writer, context);
516: } else {
517: super .doMarshal(source, writer, context);
518: }
519: }
520:
521: protected Object doUnmarshalConditionally(final Object result,
522: final HierarchicalStreamReader reader,
523: final UnmarshallingContext context) {
524: return isSerializable(result.getClass()) ? doUnmarshal(result,
525: reader, context) : super .doUnmarshal(result, reader,
526: context);
527: }
528:
529: private static class UnserializableParentsReflectionProvider extends
530: ReflectionProviderWrapper {
531:
532: public UnserializableParentsReflectionProvider(
533: final ReflectionProvider reflectionProvider) {
534: super (reflectionProvider);
535: }
536:
537: public void visitSerializableFields(final Object object,
538: final Visitor visitor) {
539: wrapped.visitSerializableFields(object, new Visitor() {
540: public void visit(String name, Class type,
541: Class definedIn, Object value) {
542: if (!Serializable.class.isAssignableFrom(definedIn)) {
543: visitor.visit(name, type, definedIn, value);
544: }
545: }
546: });
547: }
548: }
549: }
|