/* * (c) Copyright 2010-2011 AgileBirds * * This file is part of OpenFlexo. * * OpenFlexo is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OpenFlexo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>. * */ package org.openflexo.antar.binding; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; import java.util.Hashtable; import java.util.Map; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; public class KeyValueLibrary { private static final Logger logger = Logger.getLogger(KeyValueLibrary.class.getPackage().getName()); private static final Map<Type, Hashtable<String, KeyValueProperty>> properties = new Hashtable<Type, Hashtable<String, KeyValueProperty>>(); private static final Map<Type, Vector<KeyValueProperty>> declaredKeyValueProperties = new Hashtable<Type, Vector<KeyValueProperty>>(); private static final Map<Type, Vector<MethodDefinition>> declaredMethods = new Hashtable<Type, Vector<MethodDefinition>>(); private static final Map<Type, Vector<KeyValueProperty>> accessibleKeyValueProperties = new Hashtable<Type, Vector<KeyValueProperty>>(); private static final Map<Type, Vector<MethodDefinition>> accessibleMethods = new Hashtable<Type, Vector<MethodDefinition>>(); public static void clearCache() { properties.clear(); declaredKeyValueProperties.clear(); declaredMethods.clear(); accessibleKeyValueProperties.clear(); accessibleMethods.clear(); } public static KeyValueProperty getKeyValueProperty(Type declaringType, String propertyName) { Hashtable<String, KeyValueProperty> cacheForType = properties.get(declaringType); if (cacheForType == null) { cacheForType = new Hashtable<String, KeyValueProperty>(); properties.put(declaringType, cacheForType); } KeyValueProperty returned = cacheForType.get(propertyName); if (returned == null) { try { returned = new KeyValueProperty(declaringType, propertyName, false); cacheForType.put(propertyName, returned); } catch (InvalidKeyValuePropertyException e) { // logger.warning("While computing getKeyValueProperty("+propertyName+") for "+declaringType+" message:"+e.getMessage()); // e.printStackTrace(); return null; } } return returned; } public static Vector<KeyValueProperty> getDeclaredProperties(Type declaringType) { Vector<KeyValueProperty> returned = declaredKeyValueProperties.get(declaringType); if (returned == null) { logger.fine("build declaredProperties() for " + declaringType); Vector<String> excludedSignatures = new Vector<String>(); returned = searchForProperties(declaringType, true, excludedSignatures); declaredKeyValueProperties.put(declaringType, returned); Vector<MethodDefinition> methods = searchForMethods(declaringType, excludedSignatures); declaredMethods.put(declaringType, methods); } return returned; } public static Vector<MethodDefinition> getDeclaredMethods(Type declaringType) { Vector<MethodDefinition> returned = declaredMethods.get(declaringType); if (returned == null) { logger.fine("build declaredMethods() for " + declaringType); Vector<String> excludedSignatures = new Vector<String>(); Vector<KeyValueProperty> properties = searchForProperties(declaringType, true, excludedSignatures); declaredKeyValueProperties.put(declaringType, properties); returned = searchForMethods(declaringType, excludedSignatures); declaredMethods.put(declaringType, returned); } return returned; } public static Vector<KeyValueProperty> getAccessibleProperties(Type declaringType) { Vector<KeyValueProperty> returned = accessibleKeyValueProperties.get(declaringType); if (returned == null) { returned = new Vector<KeyValueProperty>(); appendAccessibleProperties(declaringType, returned); Collections.sort(returned, new Comparator<KeyValueProperty>() { @Override public int compare(KeyValueProperty o1, KeyValueProperty o2) { return o1.getName().compareTo(o2.getName()); } }); accessibleKeyValueProperties.put(declaringType, returned); } return returned; } public static void appendAccessibleProperties(Type declaringType, Vector<KeyValueProperty> returned) { Type current = declaringType; while (current != null) { Vector<KeyValueProperty> declaredProperties = getDeclaredProperties(current); for (KeyValueProperty p : declaredProperties) { boolean isAlreadyContained = false; for (KeyValueProperty p2 : returned) { if (p.getName().equals(p2.getName())) { isAlreadyContained = true; break; } } if (!isAlreadyContained) { returned.add(p); } } for (Type t : TypeUtils.getSuperInterfaceTypes(current)) { appendAccessibleProperties(t, returned); } // returned.addAll(getDeclaredProperties(current)); current = TypeUtils.getSuperType(current); } } public static Vector<MethodDefinition> getAccessibleMethods(Type declaringType) { Vector<MethodDefinition> returned = accessibleMethods.get(declaringType); if (returned == null) { returned = new Vector<MethodDefinition>(); Type current = declaringType; while (current != null) { returned.addAll(getDeclaredMethods(current)); current = TypeUtils.getSuperType(current); // current = current.getSuperclass(); } Collections.sort(returned, new Comparator<MethodDefinition>() { @Override public int compare(MethodDefinition o1, MethodDefinition o2) { return o1.getSignature().compareTo(o2.getSignature()); } }); accessibleMethods.put(declaringType, returned); } return returned; } private static Vector<MethodDefinition> searchForMethods(Type declaringType, Vector<String> excludedSignatures) { Vector<MethodDefinition> returned = new Vector<MethodDefinition>(); if (logger.isLoggable(Level.FINE)) { logger.fine("searchForMethods()"); } for (String excludedSignature : excludedSignatures) { if (logger.isLoggable(Level.FINE)) { logger.fine("Excluded: " + excludedSignature); } } Class theClass = TypeUtils.getBaseClass(declaringType); if (theClass == null) { logger.warning("Cannot search properties for type: " + declaringType); return null; } try { Method[] declaredMethods = theClass.getDeclaredMethods(); for (int i = 0; i < declaredMethods.length; i++) { Method method = declaredMethods[i]; MethodDefinition methodDefinition = MethodDefinition.getMethodDefinition(declaringType, method); if (!excludedSignatures.contains(methodDefinition.getSignature())) { returned.add(methodDefinition); } } } catch (NoClassDefFoundError e) { if (logger.isLoggable(Level.WARNING)) { logger.warning("Could not find class: " + e.getMessage()); } } catch (Throwable e) { if (logger.isLoggable(Level.WARNING)) { logger.warning("Unexpected exception raised " + e); } e.printStackTrace(); } Collections.sort(returned, new Comparator<MethodDefinition>() { @Override public int compare(MethodDefinition o1, MethodDefinition o2) { return o1.getSignature().compareTo(o2.getSignature()); } }); return returned; } private static Vector<KeyValueProperty> searchForProperties(Type declaringTypeType, boolean includesGetOnlyProperties, Vector<String> excludedSignatures) { Vector<KeyValueProperty> returned = new Vector<KeyValueProperty>(); Class<?> theClass = TypeUtils.getBaseClass(declaringTypeType); if (theClass == null) { logger.warning("Cannot search properties for type: " + declaringTypeType); return null; } try { Method[] declaredMethods = theClass.getDeclaredMethods(); for (int i = 0; i < declaredMethods.length; i++) { Method method = declaredMethods[i]; KeyValueProperty newProperty = makeProperty(declaringTypeType, method, includesGetOnlyProperties, excludedSignatures); if (newProperty != null && !containsAPropertyNamed(returned, newProperty.getName())) { if (logger.isLoggable(Level.FINE)) { logger.fine("Make property from method: " + method); } returned.add(newProperty); } } Field[] declaredFields = theClass.getDeclaredFields(); for (int i = 0; i < declaredFields.length; i++) { Field field = declaredFields[i]; KeyValueProperty newProperty = makeProperty(declaringTypeType, field); if (newProperty != null && !containsAPropertyNamed(returned, newProperty.getName())) { if (logger.isLoggable(Level.FINE)) { logger.fine("Make property from field: " + field); } returned.add(newProperty); } } } catch (NoClassDefFoundError e) { if (logger.isLoggable(Level.WARNING)) { logger.warning("Could not find class: " + e.getMessage()); } } catch (Throwable e) { if (logger.isLoggable(Level.WARNING)) { logger.warning("Unexpected exception raised " + e); } e.printStackTrace(); } Collections.sort(returned, new Comparator<KeyValueProperty>() { @Override public int compare(KeyValueProperty o1, KeyValueProperty o2) { return o1.getName().compareTo(o2.getName()); } }); return returned; } private static boolean containsAPropertyNamed(Vector<KeyValueProperty> properties, String aName) { for (KeyValueProperty p : properties) { if (p.getName().equals(aName)) { return true; } } return false; } private static KeyValueProperty makeProperty(Type declaringType, Method method, boolean includesGetOnlyProperties, Vector<String> excludedSignatures) { Type returnType = method.getGenericReturnType(); Type[] parameters = method.getGenericParameterTypes(); if (returnType != Void.TYPE && Modifier.isPublic(method.getModifiers()) && !Modifier.isStatic(method.getModifiers()) && parameters.length == 0) { // This signature matches a GET property, lets continue ! // Look for name String propertyName = method.getName(); // Exclude it from methods if (excludedSignatures != null) { excludedSignatures.add(MethodDefinition.getMethodDefinition(declaringType, method).getSignature()); } // Beautify property name if (propertyName.length() > 3 && propertyName.substring(0, 3).equalsIgnoreCase("get")) { propertyName = propertyName.substring(3); } if (propertyName.length() > 1 && propertyName.substring(0, 1).equals("_")) { propertyName = propertyName.substring(1); } // First char always to lower case propertyName = propertyName.substring(0, 1).toLowerCase() + propertyName.substring(1, propertyName.length()); // Is there a SET method ? Method setMethod = searchMatchingSetMethod(declaringType, propertyName, returnType); boolean isSettable = setMethod != null; if (setMethod != null && excludedSignatures != null) { excludedSignatures.add(MethodDefinition.getMethodDefinition(declaringType, setMethod).getSignature()); } // Creates and register the property if (includesGetOnlyProperties || isSettable) { return getKeyValueProperty(declaringType, propertyName); } } return null; } /** * Build a new DMProperty */ private static KeyValueProperty makeProperty(Type declaringType, Field field) { Type fieldType = field.getGenericType(); if (fieldType != Void.TYPE && Modifier.isPublic(field.getModifiers()) && !Modifier.isStatic(field.getModifiers())) { // This signature matches a GET property, lets continue ! // Look for name String propertyName = field.getName(); return getKeyValueProperty(declaringType, propertyName); } return null; } /** * Try to find a matching "set" method, such as (in order): * <ul> * <li>setPropertyName(Type)</li> * <li>_setPropertyName(Type)</li> * </ul> * Returns corresponding method, null if no such method exist */ private static Method searchMatchingSetMethod(Type declaringType, String propertyName, Type aType) { String propertyNameWithFirstCharToUpperCase = propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()); Vector<String> tries = new Vector<String>(); Type params[] = new Type[1]; params[0] = aType; /*if (aType instanceof Class) params[0] = (Class)aType; else if (aType instanceof ParameterizedType) params[0] = (Class)((ParameterizedType)aType).getRawType(); else if (aType instanceof TypeVariable){ logger.warning ("Pas tres bien gere pour le moment "+aType.getClass()); params[0] = Object.class; }*/ tries.add("set" + propertyNameWithFirstCharToUpperCase); tries.add("_set" + propertyNameWithFirstCharToUpperCase); for (Enumeration<String> e = tries.elements(); e.hasMoreElements();) { try { String methodName = e.nextElement(); // Method returned = type.getMethod(methodName, params); Method returned = getMethod(declaringType, methodName, params); return returned; } catch (SecurityException err) { // we continue } catch (NoSuchMethodException err) { // we continue } } return null; } private static Method getMethod(Type type, String methodName, Type... params) throws NoSuchMethodException { Class theClass = TypeUtils.getBaseClass(type); if (theClass == null) { logger.warning("Cannot search properties for type: " + type); return null; } if (params == null) { params = new Type[0]; } StringBuffer sb = null; if (logger.isLoggable(Level.FINE)) { sb = new StringBuffer(); for (Type t : params) { sb.append(" " + t.toString()); } if (logger.isLoggable(Level.FINE)) { logger.fine("Looking for " + methodName + " with" + sb.toString()); } } for (Method m : theClass.getMethods()) { if (logger.isLoggable(Level.FINE)) { logger.fine("Examining " + m); } if (m.getName().equals(methodName) && m.getGenericParameterTypes().length == params.length) { boolean paramMatches = true; for (int i = 0; i < params.length; i++) { if (!params[i].equals(m.getGenericParameterTypes()[i])) { paramMatches = false; } } if (paramMatches) { if (logger.isLoggable(Level.FINE)) { logger.fine("Looking for " + methodName + " with" + sb.toString() + ": found"); } return m; } } } if (logger.isLoggable(Level.FINE)) { logger.fine("Looking for " + methodName + " with" + sb.toString() + ": NOT found"); } throw new NoSuchMethodException(); } }