/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ package org.apache.shindig.social.core.util.xstream; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.thoughtworks.xstream.converters.javabean.BeanProperty; import com.thoughtworks.xstream.converters.reflection.ObjectAccessException; import java.beans.Introspector; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * Builds the serializable properties maps for each bean and caches them. */ public class PropertyDictionary { private final Map<String, Map<String, BeanProperty>> keyedByPropertyNameCache = new ConcurrentHashMap<String, Map<String, BeanProperty>>(); public Iterator<BeanProperty> serializablePropertiesFor(Class<?> cls) { return buildMap(cls).values().iterator(); } /** * Locates a serializable property * * @param cls * @param name * @param definedIn * @return */ public BeanProperty property(Class<?> cls, String name) { Map<String, BeanProperty> properties = buildMap(cls); BeanProperty property = (BeanProperty) properties.get(name); if (property == null) { throw new ObjectAccessException("No such property " + cls.getName() + "." + name); } else { return property; } } /** * Builds the map of all serializable properties for the the provided bean * * @param cls * @param tupleKeyed * @return */ private Map<String, BeanProperty> buildMap(Class<?> cls) { final String clsName = cls.getName(); if (!keyedByPropertyNameCache.containsKey(clsName)) { synchronized (keyedByPropertyNameCache) { if (!keyedByPropertyNameCache.containsKey(clsName)) { // double check // Gather all the properties, using only the keyed map. It // is possible that a class have two writable only // properties that have the same name // but different types final Map<PropertyKey, BeanProperty> propertyMap = Maps.newHashMap(); Method[] methods = cls.getMethods(); for (int i = 0; i < methods.length; i++) { if (!Modifier.isPublic(methods[i].getModifiers()) || Modifier.isStatic(methods[i].getModifiers())) { continue; } String methodName = methods[i].getName(); Class<?>[] parameters = methods[i].getParameterTypes(); Class<?> returnType = methods[i].getReturnType(); String propertyName = null; if ((methodName.startsWith("get") || methodName.startsWith("is")) && parameters.length == 0 && returnType != null) { if (methodName.startsWith("get")) propertyName = Introspector.decapitalize(methodName .substring(3)); else propertyName = Introspector.decapitalize(methodName .substring(2)); BeanProperty property = getBeanProperty(propertyMap, cls, propertyName, returnType); property.setGetterMethod(methods[i]); } else if (methodName.startsWith("set") && parameters.length == 1 && returnType.equals(void.class)) { propertyName = Introspector.decapitalize(methodName.substring(3)); BeanProperty property = getBeanProperty(propertyMap, cls, propertyName, parameters[0]); property.setSetterMethod(methods[i]); } } // retain only those that can be both read and written and // sort them by name List<BeanProperty> serializableProperties = Lists.newArrayList(); for (BeanProperty property : propertyMap.values()) { if (property.isReadable() || property.isWritable()) { serializableProperties.add(property); } } Collections .sort(serializableProperties, new BeanPropertyComparator()); // build the maps and return final Map<String, BeanProperty> keyedByFieldName = new OrderRetainingMap(); for (BeanProperty property : serializableProperties) { keyedByFieldName.put(property.getName(), property); } keyedByPropertyNameCache.put(clsName, keyedByFieldName); } } } return (Map<String, BeanProperty>) keyedByPropertyNameCache.get(clsName); } private BeanProperty getBeanProperty( Map<PropertyKey, BeanProperty> propertyMap, Class<?> cls, String propertyName, Class<?> type) { PropertyKey key = new PropertyKey(propertyName, type); BeanProperty property = (BeanProperty) propertyMap.get(key); if (property == null) { property = new BeanProperty(cls, propertyName, type); propertyMap.put(key, property); } return property; } /** * Needed to avoid problems with multiple setters with the same name, but * referred to different types 0 */ private static class PropertyKey { private String propertyName; private Class<?> propertyType; public PropertyKey(String propertyName, Class<?> propertyType) { this.propertyName = propertyName; this.propertyType = propertyType; } public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof PropertyKey)) return false; final PropertyKey propertyKey = (PropertyKey) o; if (propertyName != null ? !propertyName.equals(propertyKey.propertyName) : propertyKey.propertyName != null) return false; if (propertyType != null ? !propertyType.equals(propertyKey.propertyType) : propertyKey.propertyType != null) return false; return true; } public int hashCode() { int result; result = (propertyName != null ? propertyName.hashCode() : 0); result = 29 * result + (propertyType != null ? propertyType.hashCode() : 0); return result; } public String toString() { return "PropertyKey{propertyName='" + propertyName + "'" + ", propertyType=" + propertyType + "}"; } } /** * Compares properties by name */ private static class BeanPropertyComparator implements Comparator<BeanProperty> { public int compare(BeanProperty o1, BeanProperty o2) { return o1.getName().compareTo(o2.getName()); } } private static class OrderRetainingMap<K, V> extends HashMap<K, V> { private static final long serialVersionUID = 1565370254073638221L; private List<V> valueOrder = Lists.newArrayList(); public V put(K key, V value) { valueOrder.add(value); return super.put(key, value); } public Collection<V> values() { return Collections.unmodifiableList(valueOrder); } } }