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 ; 14 import java.io.InvalidObjectException ; 15 import java.io.ObjectInputStream ; 16 import java.io.ObjectInputValidation ; 17 import java.io.ObjectOutputStream ; 18 import java.io.ObjectStreamClass ; 19 import java.io.ObjectStreamField ; 20 import java.io.Serializable ; 21 import java.lang.reflect.Field ; 22 import java.util.ArrayList ; 23 import java.util.Collections ; 24 import java.util.HashMap ; 25 import java.util.Iterator ; 26 import java.util.List ; 27 import java.util.Map ; 28 29 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 ELEMENT_NULL = "null"; 56 private static final String ELEMENT_DEFAULT = "default"; 57 private static final String ATTRIBUTE_CLASS = "class"; 58 private static final String ATTRIBUTE_SERIALIZATION = "serialization"; 59 private static final String ATTRIBUTE_VALUE_CUSTOM = "custom"; 60 private static final String ELEMENT_FIELDS = "fields"; 61 private static final String ELEMENT_FIELD = "field"; 62 private static final String 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 type) { 70 return Serializable .class.isAssignableFrom(type) 71 && ( serializationMethodInvoker.supportsReadObject(type, true) 72 || serializationMethodInvoker.supportsWriteObject(type, true) ); 73 } 74 75 public void marshal(Object source, final HierarchicalStreamWriter writer, final MarshallingContext context) { 76 final Object 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 final Class [] currentType = new Class [1]; 86 final boolean[] writtenClassWrapper = {false}; 87 88 CustomObjectOutputStream.StreamCallback callback = new CustomObjectOutputStream.StreamCallback() { 89 90 public void writeToStream(Object 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 fields) { 102 ObjectStreamClass objectStreamClass = ObjectStreamClass.lookup(currentType[0]); 103 104 writer.startNode(ELEMENT_DEFAULT); 105 for (Iterator iterator = fields.keySet().iterator(); iterator.hasNext();) { 106 String name = (String ) iterator.next(); 107 ObjectStreamField field = objectStreamClass.getField(name); 108 Object 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 objectStreamClass = ObjectStreamClass.lookup(currentType[0]); 129 130 if (objectStreamClass == null) { 131 return; 132 } 133 134 ObjectStreamField [] fields = objectStreamClass.getFields(); 135 for (int i = 0; i < fields.length; i++) { 136 ObjectStreamField field = fields[i]; 137 Object 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 actualType = value.getClass(); 151 Class 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 ("Objects are not allowed to call ObjectOutputStream.close() from writeObject()"); 175 } 176 }; 177 178 try { 179 Iterator classHieararchy = hierarchyFor(replacedSource.getClass()); 180 while (classHieararchy.hasNext()) { 181 currentType[0] = (Class ) 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 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 e) { 205 throw new ObjectAccessException("Could not call defaultWriteObject()", e); 206 } 207 } 208 209 private Object readField(ObjectStreamField field, Class type, Object instance) { 210 try { 211 Field javaField = type.getDeclaredField(field.getName()); 212 javaField.setAccessible(true); 213 return javaField.get(instance); 214 } catch (IllegalArgumentException e) { 215 throw new ObjectAccessException("Could not get field " + field.getClass() + "." + field.getName(), e); 216 } catch (IllegalAccessException e) { 217 throw new ObjectAccessException("Could not get field " + field.getClass() + "." + field.getName(), e); 218 } catch (NoSuchFieldException e) { 219 throw new ObjectAccessException("Could not get field " + field.getClass() + "." + field.getName(), e); 220 } catch (SecurityException e) { 221 throw new ObjectAccessException("Could not get field " + field.getClass() + "." + field.getName(), e); 222 } 223 } 224 225 private Iterator hierarchyFor(Class type) { 226 List result = new ArrayList (); 227 while(type != null) { 228 result.add(type); 229 type = type.getSuperclass(); 230 } 231 232 Collections.reverse(result); 234 235 return result.iterator(); 236 } 237 238 public Object unmarshal(final HierarchicalStreamReader reader, final UnmarshallingContext context) { 239 String resolvesAttribute = reader.getAttribute(mapper.attributeForReadResolveField()); 240 Class requiredType; 241 if (resolvesAttribute != null) { 242 requiredType = mapper.realClass(resolvesAttribute); 243 } else { 244 requiredType = context.getRequiredType(); 245 } 246 final Object result = reflectionProvider.newInstance(requiredType); 247 248 final Class [] currentType = new Class [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 readFromStream() { 257 reader.moveDown(); 258 Class type = mapper.realClass(reader.getNodeName()); 259 Object value = context.convertAnother(result, type); 260 reader.moveUp(); 261 return value; 262 } 263 264 public Map readFieldsFromStream() { 265 Map result = new HashMap (); 266 reader.moveDown(); 267 if (reader.getNodeName().equals(ELEMENT_FIELDS)) { 268 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 name = reader.getAttribute(ATTRIBUTE_NAME); 275 Class type = mapper.realClass(reader.getAttribute(ATTRIBUTE_CLASS)); 276 Object value = context.convertAnother(result, type); 277 result.put(name, value); 278 reader.moveUp(); 279 } 280 } else if (reader.getNodeName().equals(ELEMENT_DEFAULT)) { 281 ObjectStreamClass objectStreamClass = ObjectStreamClass.lookup(currentType[0]); 283 while (reader.hasMoreChildren()) { 284 reader.moveDown(); 285 String name = reader.getNodeName(); 286 String typeName = reader.getAttribute(ATTRIBUTE_CLASS); 287 Class type; 288 if (typeName != null) { 289 type = mapper.realClass(typeName); 290 } else { 291 ObjectStreamField 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 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 type; 322 String fieldName = mapper.realMember(currentType[0], reader.getNodeName()); 323 String 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 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 validation, int priority) { 339 context.addCompletionCallback(new Runnable () { 340 public void run() { 341 try { 342 validation.validateObject(); 343 } catch (InvalidObjectException 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 ("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 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 |