001: /*
002: * Copyright (C) 2005 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 12. April 2005 by Joe Walnes
011: */
012: package com.thoughtworks.xstream.converters.javabean;
013:
014: import com.thoughtworks.xstream.alias.ClassMapper;
015: import com.thoughtworks.xstream.converters.ConversionException;
016: import com.thoughtworks.xstream.converters.Converter;
017: import com.thoughtworks.xstream.converters.MarshallingContext;
018: import com.thoughtworks.xstream.converters.UnmarshallingContext;
019: import com.thoughtworks.xstream.io.HierarchicalStreamReader;
020: import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
021: import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper;
022: import com.thoughtworks.xstream.mapper.Mapper;
023:
024: /**
025: * Can convert any bean with a public default constructor. BeanInfo are not
026: * taken into consideration, this class looks for bean patterns for simple
027: * properties
028: */
029: public class JavaBeanConverter implements Converter {
030:
031: /*
032: * TODO:
033: * - support indexed properties
034: */
035: private Mapper mapper;
036: private BeanProvider beanProvider;
037: /**
038: * @deprecated since 1.2, no necessity for field anymore.
039: */
040: private String classAttributeIdentifier;
041:
042: public JavaBeanConverter(Mapper mapper) {
043: this (mapper, new BeanProvider());
044: }
045:
046: public JavaBeanConverter(Mapper mapper, BeanProvider beanProvider) {
047: this .mapper = mapper;
048: this .beanProvider = beanProvider;
049: }
050:
051: /**
052: * @deprecated As of 1.3, use {@link #JavaBeanConverter(Mapper)} and {@link com.thoughtworks.xstream.XStream#aliasAttribute(String, String)}
053: */
054: public JavaBeanConverter(Mapper mapper,
055: String classAttributeIdentifier) {
056: this (mapper, new BeanProvider());
057: this .classAttributeIdentifier = classAttributeIdentifier;
058: }
059:
060: /**
061: * @deprecated As of 1.2, use {@link #JavaBeanConverter(Mapper)} and {@link com.thoughtworks.xstream.XStream#aliasAttribute(String, String)}
062: */
063: public JavaBeanConverter(ClassMapper classMapper,
064: String classAttributeIdentifier) {
065: this ((Mapper) classMapper, classAttributeIdentifier);
066: }
067:
068: /**
069: * Only checks for the availability of a public default constructor.
070: * If you need stricter checks, subclass JavaBeanConverter
071: */
072: public boolean canConvert(Class type) {
073: return beanProvider.canInstantiate(type);
074: }
075:
076: public void marshal(final Object source,
077: final HierarchicalStreamWriter writer,
078: final MarshallingContext context) {
079: final String classAttributeName = classAttributeIdentifier != null ? classAttributeIdentifier
080: : mapper.attributeForAlias("class");
081: beanProvider.visitSerializableProperties(source,
082: new BeanProvider.Visitor() {
083: public void visit(String propertyName,
084: Class fieldType, Class definedIn,
085: Object newObj) {
086: if (newObj != null
087: && mapper.shouldSerializeMember(
088: definedIn, propertyName)) {
089: writeField(propertyName, fieldType, newObj,
090: definedIn);
091: }
092: }
093:
094: private void writeField(String propertyName,
095: Class fieldType, Object newObj,
096: Class definedIn) {
097: String serializedMember = mapper
098: .serializedMember(source.getClass(),
099: propertyName);
100: ExtendedHierarchicalStreamWriterHelper
101: .startNode(writer, serializedMember,
102: fieldType);
103: Class actualType = newObj.getClass();
104:
105: Class defaultType = mapper
106: .defaultImplementationOf(fieldType);
107: if (!actualType.equals(defaultType)) {
108: writer.addAttribute(classAttributeName,
109: mapper.serializedClass(actualType));
110: }
111: context.convertAnother(newObj);
112:
113: writer.endNode();
114: }
115:
116: });
117: }
118:
119: public Object unmarshal(final HierarchicalStreamReader reader,
120: final UnmarshallingContext context) {
121: final Object result = instantiateNewInstance(context);
122:
123: while (reader.hasMoreChildren()) {
124: reader.moveDown();
125:
126: String propertyName = mapper.realMember(result.getClass(),
127: reader.getNodeName());
128:
129: boolean propertyExistsInClass = beanProvider
130: .propertyDefinedInClass(propertyName, result
131: .getClass());
132:
133: if (propertyExistsInClass) {
134: Class type = determineType(reader, result, propertyName);
135: Object value = context.convertAnother(result, type);
136: beanProvider.writeProperty(result, propertyName, value);
137: } else if (mapper.shouldSerializeMember(result.getClass(),
138: propertyName)) {
139: throw new ConversionException("Property '"
140: + propertyName + "' not defined in class "
141: + result.getClass().getName());
142: }
143:
144: reader.moveUp();
145: }
146:
147: return result;
148: }
149:
150: private Object instantiateNewInstance(UnmarshallingContext context) {
151: Object result = context.currentObject();
152: if (result == null) {
153: result = beanProvider
154: .newInstance(context.getRequiredType());
155: }
156: return result;
157: }
158:
159: private Class determineType(HierarchicalStreamReader reader,
160: Object result, String fieldName) {
161: final String classAttributeName = classAttributeIdentifier != null ? classAttributeIdentifier
162: : mapper.attributeForAlias("class");
163: String classAttribute = reader.getAttribute(classAttributeName);
164: if (classAttribute != null) {
165: return mapper.realClass(classAttribute);
166: } else {
167: return mapper.defaultImplementationOf(beanProvider
168: .getPropertyType(result, fieldName));
169: }
170: }
171:
172: /**
173: * @deprecated since 1.3
174: */
175: public static class DuplicateFieldException extends
176: ConversionException {
177: public DuplicateFieldException(String msg) {
178: super(msg);
179: }
180: }
181: }
|