001: /*
002: * Copyright (C) 2005 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 12. April 2005 by Joe Walnes
011: */
012: package com.thoughtworks.xstream.converters.javabean;
013:
014: import com.thoughtworks.xstream.converters.reflection.ObjectAccessException;
015:
016: import java.beans.Introspector;
017: import java.lang.reflect.Method;
018: import java.lang.reflect.Modifier;
019: import java.util.ArrayList;
020: import java.util.Collection;
021: import java.util.Collections;
022: import java.util.Comparator;
023: import java.util.HashMap;
024: import java.util.Iterator;
025: import java.util.List;
026: import java.util.Map;
027:
028: /**
029: * Builds the serializable properties maps for each bean and caches them.
030: */
031: public class PropertyDictionary {
032:
033: private final Map keyedByPropertyNameCache = Collections
034: .synchronizedMap(new HashMap());
035:
036: public Iterator serializablePropertiesFor(Class cls) {
037: return buildMap(cls).values().iterator();
038: }
039:
040: /**
041: * Locates a serializable property
042: *
043: * @param cls
044: * @param name
045: */
046: public BeanProperty property(Class cls, String name) {
047: Map properties = buildMap(cls);
048: BeanProperty property = (BeanProperty) properties.get(name);
049: if (property == null) {
050: throw new ObjectAccessException("No such property "
051: + cls.getName() + "." + name);
052: } else {
053: return property;
054: }
055: }
056:
057: /**
058: * Builds the map of all serializable properties for the the provided bean
059: *
060: * @param cls
061: */
062: private Map buildMap(Class cls) {
063: final String clsName = cls.getName();
064: if (!keyedByPropertyNameCache.containsKey(clsName)) {
065: synchronized (keyedByPropertyNameCache) {
066: if (!keyedByPropertyNameCache.containsKey(clsName)) { // double check
067: // Gather all the properties, using only the keyed map. It
068: // is possible that a class have two writable only
069: // properties that have the same name
070: // but different types
071: final Map propertyMap = new HashMap();
072: Method[] methods = cls.getMethods();
073:
074: for (int i = 0; i < methods.length; i++) {
075: if (!Modifier.isPublic(methods[i]
076: .getModifiers())
077: || Modifier.isStatic(methods[i]
078: .getModifiers()))
079: continue;
080:
081: String methodName = methods[i].getName();
082: Class[] parameters = methods[i]
083: .getParameterTypes();
084: Class returnType = methods[i].getReturnType();
085: String propertyName;
086: if ((methodName.startsWith("get") || methodName
087: .startsWith("is"))
088: && parameters.length == 0
089: && returnType != void.class) {
090: if (methodName.startsWith("get")) {
091: propertyName = Introspector
092: .decapitalize(methodName
093: .substring(3));
094: } else {
095: propertyName = Introspector
096: .decapitalize(methodName
097: .substring(2));
098: }
099: BeanProperty property = getBeanProperty(
100: propertyMap, cls, propertyName,
101: returnType);
102: property.setGetterMethod(methods[i]);
103: } else if (methodName.startsWith("set")
104: && parameters.length == 1
105: && returnType == void.class) {
106: propertyName = Introspector
107: .decapitalize(methodName
108: .substring(3));
109: BeanProperty property = getBeanProperty(
110: propertyMap, cls, propertyName,
111: parameters[0]);
112: property.setSetterMethod(methods[i]);
113: }
114: }
115:
116: // retain only those that can be both read and written and
117: // sort them by name
118: List serializableProperties = new ArrayList();
119: for (Iterator it = propertyMap.values().iterator(); it
120: .hasNext();) {
121: BeanProperty property = (BeanProperty) it
122: .next();
123: if (property.isReadable()
124: && property.isWritable()) {
125: serializableProperties.add(property);
126: }
127: }
128: Collections.sort(serializableProperties,
129: new BeanPropertyComparator());
130:
131: // build the maps and return
132: final Map keyedByFieldName = new OrderRetainingMap();
133: for (Iterator it = serializableProperties
134: .iterator(); it.hasNext();) {
135: BeanProperty property = (BeanProperty) it
136: .next();
137: keyedByFieldName.put(property.getName(),
138: property);
139: }
140:
141: keyedByPropertyNameCache.put(clsName,
142: keyedByFieldName);
143: }
144: }
145: }
146: return (Map) keyedByPropertyNameCache.get(clsName);
147: }
148:
149: private BeanProperty getBeanProperty(Map propertyMap, Class cls,
150: String propertyName, Class type) {
151: PropertyKey key = new PropertyKey(propertyName, type);
152: BeanProperty property = (BeanProperty) propertyMap.get(key);
153: if (property == null) {
154: property = new BeanProperty(cls, propertyName, type);
155: propertyMap.put(key, property);
156: }
157: return property;
158: }
159:
160: /**
161: * Needed to avoid problems with multiple setters with the same name, but
162: * referred to different types
163: */
164: private static class PropertyKey {
165: private String propertyName;
166:
167: private Class propertyType;
168:
169: public PropertyKey(String propertyName, Class propertyType) {
170: this .propertyName = propertyName;
171: this .propertyType = propertyType;
172: }
173:
174: public boolean equals(Object o) {
175: if (this == o)
176: return true;
177: if (!(o instanceof PropertyKey))
178: return false;
179:
180: final PropertyKey propertyKey = (PropertyKey) o;
181:
182: if (propertyName != null ? !propertyName
183: .equals(propertyKey.propertyName)
184: : propertyKey.propertyName != null)
185: return false;
186: if (propertyType != null ? !propertyType
187: .equals(propertyKey.propertyType)
188: : propertyKey.propertyType != null)
189: return false;
190:
191: return true;
192: }
193:
194: public int hashCode() {
195: int result;
196: result = (propertyName != null ? propertyName.hashCode()
197: : 0);
198: result = 29
199: * result
200: + (propertyType != null ? propertyType.hashCode()
201: : 0);
202: return result;
203: }
204:
205: public String toString() {
206: return "PropertyKey{propertyName='" + propertyName + "'"
207: + ", propertyType=" + propertyType + "}";
208: }
209:
210: }
211:
212: /**
213: * Compares properties by name
214: */
215: private static class BeanPropertyComparator implements Comparator {
216:
217: public int compare(Object o1, Object o2) {
218: return ((BeanProperty) o1).getName().compareTo(
219: ((BeanProperty) o2).getName());
220: }
221:
222: }
223:
224: private static class OrderRetainingMap extends HashMap {
225:
226: private List valueOrder = new ArrayList();
227:
228: public Object put(Object key, Object value) {
229: valueOrder.add(value);
230: return super .put(key, value);
231: }
232:
233: public Collection values() {
234: return Collections.unmodifiableList(valueOrder);
235: }
236: }
237:
238: }
|