001: /*
002: * Copyright (C) 2004, 2005, 2006 Joe Walnes.
003: * Copyright (C) 2006, 2007, 2008 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 02. March 2006 by Joerg Schaible
011: */
012: package com.thoughtworks.xstream.converters.reflection;
013:
014: import com.thoughtworks.xstream.converters.ConversionException;
015: import com.thoughtworks.xstream.converters.Converter;
016: import com.thoughtworks.xstream.converters.MarshallingContext;
017: import com.thoughtworks.xstream.converters.SingleValueConverter;
018: import com.thoughtworks.xstream.converters.UnmarshallingContext;
019: import com.thoughtworks.xstream.core.util.Primitives;
020: import com.thoughtworks.xstream.io.HierarchicalStreamReader;
021: import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
022: import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper;
023: import com.thoughtworks.xstream.mapper.Mapper;
024:
025: import java.lang.reflect.Field;
026: import java.lang.reflect.Modifier;
027: import java.util.Collection;
028: import java.util.HashMap;
029: import java.util.HashSet;
030: import java.util.Iterator;
031: import java.util.Map;
032: import java.util.Set;
033:
034: public abstract class AbstractReflectionConverter implements Converter {
035:
036: protected final ReflectionProvider reflectionProvider;
037: protected final Mapper mapper;
038: protected transient SerializationMethodInvoker serializationMethodInvoker;
039: private transient ReflectionProvider pureJavaReflectionProvider;
040:
041: public AbstractReflectionConverter(Mapper mapper,
042: ReflectionProvider reflectionProvider) {
043: this .mapper = mapper;
044: this .reflectionProvider = reflectionProvider;
045: serializationMethodInvoker = new SerializationMethodInvoker();
046: }
047:
048: public void marshal(Object original,
049: final HierarchicalStreamWriter writer,
050: final MarshallingContext context) {
051: final Object source = serializationMethodInvoker
052: .callWriteReplace(original);
053:
054: if (source.getClass() != original.getClass()) {
055: writer.addAttribute(
056: mapper.aliasForAttribute("resolves-to"), mapper
057: .serializedClass(source.getClass()));
058: }
059:
060: doMarshal(source, writer, context);
061: }
062:
063: protected void doMarshal(final Object source,
064: final HierarchicalStreamWriter writer,
065: final MarshallingContext context) {
066: final Set seenFields = new HashSet();
067: final Map defaultFieldDefinition = new HashMap();
068:
069: // Attributes might be preferred to child elements ...
070: reflectionProvider.visitSerializableFields(source,
071: new ReflectionProvider.Visitor() {
072: public void visit(String fieldName, Class type,
073: Class definedIn, Object value) {
074: if (!mapper.shouldSerializeMember(definedIn,
075: fieldName)) {
076: return;
077: }
078: if (!defaultFieldDefinition
079: .containsKey(fieldName)) {
080: Class lookupType = source.getClass();
081: // See XSTR-457 and OmitFieldsTest
082: // if (definedIn != source.getClass() && !mapper.shouldSerializeMember(lookupType, fieldName)) {
083: // lookupType = definedIn;
084: // }
085: defaultFieldDefinition.put(fieldName,
086: reflectionProvider.getField(
087: lookupType, fieldName));
088: }
089:
090: SingleValueConverter converter = mapper
091: .getConverterFromItemType(fieldName,
092: type, definedIn);
093: if (converter != null) {
094: if (value != null) {
095: if (seenFields.contains(fieldName)) {
096: throw new ConversionException(
097: "Cannot write field with name '"
098: + fieldName
099: + "' twice as attribute for object of type "
100: + source.getClass()
101: .getName());
102: }
103: final String str = converter
104: .toString(value);
105: if (str != null) {
106: writer
107: .addAttribute(
108: mapper
109: .aliasForAttribute(mapper
110: .serializedMember(
111: definedIn,
112: fieldName)),
113: str);
114: }
115: }
116: // TODO: name is not enough, need also "definedIn"!
117: seenFields.add(fieldName);
118: }
119: }
120: });
121:
122: // Child elements not covered already processed as attributes ...
123: reflectionProvider.visitSerializableFields(source,
124: new ReflectionProvider.Visitor() {
125: public void visit(String fieldName,
126: Class fieldType, Class definedIn,
127: Object newObj) {
128: if (!mapper.shouldSerializeMember(definedIn,
129: fieldName)) {
130: return;
131: }
132: if (!seenFields.contains(fieldName)
133: && newObj != null) {
134: Mapper.ImplicitCollectionMapping mapping = mapper
135: .getImplicitCollectionDefForFieldName(
136: source.getClass(),
137: fieldName);
138: if (mapping != null) {
139: if (mapping.getItemFieldName() != null) {
140: Collection list = (Collection) newObj;
141: for (Iterator iter = list
142: .iterator(); iter.hasNext();) {
143: Object obj = iter.next();
144: writeField(fieldName, mapping
145: .getItemFieldName(),
146: mapping.getItemType(),
147: definedIn, obj);
148: }
149: } else {
150: context.convertAnother(newObj);
151: }
152: } else {
153: writeField(fieldName, fieldName,
154: fieldType, definedIn, newObj);
155: }
156: }
157: }
158:
159: private void writeField(String fieldName,
160: String aliasName, Class fieldType,
161: Class definedIn, Object newObj) {
162: ExtendedHierarchicalStreamWriterHelper
163: .startNode(
164: writer,
165: mapper.serializedMember(source
166: .getClass(), aliasName),
167: fieldType);
168:
169: Class actualType = newObj.getClass();
170:
171: Class defaultType = mapper
172: .defaultImplementationOf(fieldType);
173: if (!actualType.equals(defaultType)) {
174: String serializedClassName = mapper
175: .serializedClass(actualType);
176: if (!serializedClassName.equals(mapper
177: .serializedClass(defaultType))) {
178: writer.addAttribute(mapper
179: .aliasForAttribute("class"),
180: serializedClassName);
181: }
182: }
183:
184: final Field defaultField = (Field) defaultFieldDefinition
185: .get(fieldName);
186: if (defaultField.getDeclaringClass() != definedIn) {
187: writer.addAttribute(mapper
188: .aliasForAttribute("defined-in"),
189: mapper.serializedClass(definedIn));
190: }
191:
192: Field field = reflectionProvider.getField(
193: definedIn, fieldName);
194: marshallField(context, newObj, field);
195: writer.endNode();
196: }
197:
198: });
199: }
200:
201: protected void marshallField(final MarshallingContext context,
202: Object newObj, Field field) {
203: context.convertAnother(newObj, mapper.getLocalConverter(field
204: .getDeclaringClass(), field.getName()));
205: }
206:
207: public Object unmarshal(final HierarchicalStreamReader reader,
208: final UnmarshallingContext context) {
209: Object result = instantiateNewInstance(reader, context);
210: result = doUnmarshal(result, reader, context);
211: return serializationMethodInvoker.callReadResolve(result);
212: }
213:
214: public Object doUnmarshal(final Object result,
215: final HierarchicalStreamReader reader,
216: final UnmarshallingContext context) {
217: final SeenFields seenFields = new SeenFields();
218: Iterator it = reader.getAttributeNames();
219:
220: // Process attributes before recursing into child elements.
221: while (it.hasNext()) {
222: String attrAlias = (String) it.next();
223: String attrName = mapper.realMember(result.getClass(),
224: mapper.attributeForAlias(attrAlias));
225: Class classDefiningField = determineWhichClassDefinesField(reader);
226: boolean fieldExistsInClass = reflectionProvider
227: .fieldDefinedInClass(attrName, result.getClass());
228: if (fieldExistsInClass) {
229: Field field = reflectionProvider.getField(result
230: .getClass(), attrName);
231: if (Modifier.isTransient(field.getModifiers())
232: && !shouldUnmarshalTransientFields()) {
233: continue;
234: }
235: SingleValueConverter converter = mapper
236: .getConverterFromAttribute(field
237: .getDeclaringClass(), attrName);
238: Class type = field.getType();
239: if (converter != null) {
240: Object value = converter.fromString(reader
241: .getAttribute(attrAlias));
242: if (type.isPrimitive()) {
243: type = Primitives.box(type);
244: }
245: if (value != null
246: && !type.isAssignableFrom(value.getClass())) {
247: throw new ConversionException(
248: "Cannot convert type "
249: + value.getClass().getName()
250: + " to type " + type.getName());
251: }
252: reflectionProvider.writeField(result, attrName,
253: value, classDefiningField);
254: seenFields.add(classDefiningField, attrName);
255: }
256: }
257: }
258:
259: Map implicitCollectionsForCurrentObject = null;
260: while (reader.hasMoreChildren()) {
261: reader.moveDown();
262:
263: String originalNodeName = reader.getNodeName();
264: String fieldName = mapper.realMember(result.getClass(),
265: originalNodeName);
266: Mapper.ImplicitCollectionMapping implicitCollectionMapping = mapper
267: .getImplicitCollectionDefForFieldName(result
268: .getClass(), fieldName);
269:
270: Class classDefiningField = determineWhichClassDefinesField(reader);
271: boolean fieldExistsInClass = implicitCollectionMapping == null
272: && reflectionProvider.fieldDefinedInClass(
273: fieldName, result.getClass());
274:
275: Class type = implicitCollectionMapping == null ? determineType(
276: reader, fieldExistsInClass, result, fieldName,
277: classDefiningField)
278: : implicitCollectionMapping.getItemType();
279: final Object value;
280: if (fieldExistsInClass) {
281: Field field = reflectionProvider.getField(
282: classDefiningField != null ? classDefiningField
283: : result.getClass(), fieldName);
284: if (Modifier.isTransient(field.getModifiers())
285: && !shouldUnmarshalTransientFields()) {
286: reader.moveUp();
287: continue;
288: }
289: value = unmarshallField(context, result, type, field);
290: // TODO the reflection provider should have returned the proper field in first place ....
291: Class definedType = reflectionProvider.getFieldType(
292: result, fieldName, classDefiningField);
293: if (!definedType.isPrimitive()) {
294: type = definedType;
295: }
296: } else {
297: value = type != null ? context.convertAnother(result,
298: type) : null;
299: }
300:
301: if (value != null
302: && !type.isAssignableFrom(value.getClass())) {
303: throw new ConversionException("Cannot convert type "
304: + value.getClass().getName() + " to type "
305: + type.getName());
306: }
307:
308: if (fieldExistsInClass) {
309: reflectionProvider.writeField(result, fieldName, value,
310: classDefiningField);
311: seenFields.add(classDefiningField, fieldName);
312: } else if (type != null) {
313: implicitCollectionsForCurrentObject = writeValueToImplicitCollection(
314: context, value,
315: implicitCollectionsForCurrentObject, result,
316: originalNodeName);
317: }
318:
319: reader.moveUp();
320: }
321:
322: return result;
323: }
324:
325: protected Object unmarshallField(
326: final UnmarshallingContext context, final Object result,
327: Class type, Field field) {
328: return context.convertAnother(result, type, mapper
329: .getLocalConverter(field.getDeclaringClass(), field
330: .getName()));
331: }
332:
333: protected boolean shouldUnmarshalTransientFields() {
334: return false;
335: }
336:
337: private Map writeValueToImplicitCollection(
338: UnmarshallingContext context, Object value,
339: Map implicitCollections, Object result, String itemFieldName) {
340: String fieldName = mapper.getFieldNameForItemTypeAndName(
341: context.getRequiredType(), value.getClass(),
342: itemFieldName);
343: if (fieldName != null) {
344: if (implicitCollections == null) {
345: implicitCollections = new HashMap(); // lazy instantiation
346: }
347: Collection collection = (Collection) implicitCollections
348: .get(fieldName);
349: if (collection == null) {
350: Class fieldType = mapper
351: .defaultImplementationOf(reflectionProvider
352: .getFieldType(result, fieldName, null));
353: if (!Collection.class.isAssignableFrom(fieldType)) {
354: throw new ObjectAccessException(
355: "Field "
356: + fieldName
357: + " of "
358: + result.getClass().getName()
359: + " is configured for an implicit Collection, but field is of type "
360: + fieldType.getName());
361: }
362: if (pureJavaReflectionProvider == null) {
363: pureJavaReflectionProvider = new PureJavaReflectionProvider();
364: }
365: collection = (Collection) pureJavaReflectionProvider
366: .newInstance(fieldType);
367: reflectionProvider.writeField(result, fieldName,
368: collection, null);
369: implicitCollections.put(fieldName, collection);
370: }
371: collection.add(value);
372: }
373: return implicitCollections;
374: }
375:
376: private Class determineWhichClassDefinesField(
377: HierarchicalStreamReader reader) {
378: String definedIn = reader.getAttribute(mapper
379: .aliasForAttribute("defined-in"));
380: return definedIn == null ? null : mapper.realClass(definedIn);
381: }
382:
383: protected Object instantiateNewInstance(
384: HierarchicalStreamReader reader,
385: UnmarshallingContext context) {
386: String readResolveValue = reader.getAttribute(mapper
387: .aliasForAttribute("resolves-to"));
388: Object currentObject = context.currentObject();
389: if (currentObject != null) {
390: return currentObject;
391: } else if (readResolveValue != null) {
392: return reflectionProvider.newInstance(mapper
393: .realClass(readResolveValue));
394: } else {
395: return reflectionProvider.newInstance(context
396: .getRequiredType());
397: }
398: }
399:
400: private static class SeenFields {
401:
402: private Set seen = new HashSet();
403:
404: public void add(Class definedInCls, String fieldName) {
405: String uniqueKey = fieldName;
406: if (definedInCls != null) {
407: uniqueKey += " [" + definedInCls.getName() + "]";
408: }
409: if (seen.contains(uniqueKey)) {
410: throw new DuplicateFieldException(uniqueKey);
411: } else {
412: seen.add(uniqueKey);
413: }
414: }
415:
416: }
417:
418: private Class determineType(HierarchicalStreamReader reader,
419: boolean validField, Object result, String fieldName,
420: Class definedInCls) {
421: String classAttribute = reader.getAttribute(mapper
422: .aliasForAttribute("class"));
423: if (classAttribute != null) {
424: return mapper.realClass(classAttribute);
425: } else if (!validField) {
426: Class itemType = mapper.getItemTypeForItemFieldName(result
427: .getClass(), fieldName);
428: if (itemType != null) {
429: return itemType;
430: } else {
431: String originalNodeName = reader.getNodeName();
432: if (definedInCls == null) {
433: for (definedInCls = result.getClass(); definedInCls != null; definedInCls = definedInCls
434: .getSuperclass()) {
435: if (!mapper.shouldSerializeMember(definedInCls,
436: originalNodeName)) {
437: return null;
438: }
439: }
440: }
441: return mapper.realClass(originalNodeName);
442: }
443: } else {
444: return mapper.defaultImplementationOf(reflectionProvider
445: .getFieldType(result, fieldName, definedInCls));
446: }
447: }
448:
449: private Object readResolve() {
450: serializationMethodInvoker = new SerializationMethodInvoker();
451: return this ;
452: }
453:
454: public static class DuplicateFieldException extends
455: ConversionException {
456: public DuplicateFieldException(String msg) {
457: super (msg);
458: add("duplicate-field", msg);
459: }
460: }
461: }
|