/* * (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.Type; import java.util.Observable; import java.util.TreeSet; import java.util.logging.Logger; import org.openflexo.antar.binding.AbstractBinding.BindingEvaluationContext; import org.openflexo.toolbox.ToolBox; import org.openflexo.xmlcode.KeyValueCoder; import org.openflexo.xmlcode.KeyValueDecoder; public class KeyValueProperty extends Observable implements SimplePathElement<Object> { static final Logger logger = Logger.getLogger(BindingValue.class.getPackage().getName()); /** Stores property's name */ protected String name; /** Stores related object'class */ protected Class<?> declaringClass; /** Stores related object'type */ protected Type declaringType; /** * Stores related field (if this one is public) or null if field is protected or non-existant */ protected Field field; /** Stores related type (the class of related property) */ protected Type type; /** * Stores related "get" method (if this one is public) or null if method is protected or non-existant */ protected Method getMethod; /** * Stores related "set" method (if this one is public) or null if method is protected or non-existant */ protected Method setMethod; private boolean settable = false; KeyValueProperty(Type aDeclaringType, String propertyName, boolean setMethodIsMandatory) throws InvalidKeyValuePropertyException { super(); declaringClass = TypeUtils.getBaseClass(aDeclaringType); declaringType = aDeclaringType; init(propertyName, setMethodIsMandatory); } @Override public boolean equals(Object obj) { if (obj instanceof KeyValueProperty) { KeyValueProperty kvp = (KeyValueProperty) obj; return (declaringClass.equals(kvp.declaringClass) || TypeUtils.isClassAncestorOf(declaringClass, kvp.declaringClass) || TypeUtils .isClassAncestorOf(kvp.declaringClass, declaringClass)) && name.equals(kvp.name); } return super.equals(obj); } @Override public int hashCode() { return name.hashCode() + (field != null ? field.getDeclaringClass().hashCode() : getMethod.getDeclaringClass().hashCode()); } /** * Initialize this property, given a propertyName.<br> * This method is called during constructor invokation. NB: to be valid, a property should be identified by at least the field or the * get/set methods pair. If the field is accessible, and only the get or the set method is accessible, a warning will be thrown. */ protected void init(String propertyName, boolean setMethodIsMandatory) throws InvalidKeyValuePropertyException { name = propertyName; // System.out.println("Declaring type = "+declaringType+" search property "+propertyName); String propertyNameWithFirstCharToUpperCase = name.substring(0, 1).toUpperCase() + name.substring(1, name.length()); field = null; try { field = declaringClass.getField(name); } catch (NoSuchFieldException e) { // Debugging.debug ("NoSuchFieldException, trying to find get/set // methods pair"); } catch (SecurityException e) { // Debugging.debug ("SecurityException, trying to find get/set // methods pair"); } getMethod = searchMatchingGetMethod(declaringClass, name); if (field == null) { if (getMethod == null) { throw new InvalidKeyValuePropertyException("No public field " + name + " found, nor method matching " + name + "() nor " + "_" + name + "() nor " + "get" + propertyNameWithFirstCharToUpperCase + "() nor " + "_get" + propertyNameWithFirstCharToUpperCase + "() found in class:" + declaringClass.getName()); } else { type = getMethod.getGenericReturnType(); } } else { // field != null type = field.getGenericType(); if (getMethod != null) { if (getMethod.getGenericReturnType() != type) { logger.warning("Public field " + name + " found, with type " + type + " found " + " and method " + getMethod.getName() + " found " + " declaring return type " + getMethod.getReturnType() + " Ignoring method..."); getMethod = null; } } } setMethod = searchMatchingSetMethod(declaringClass, name, type); if (setMethodIsMandatory) { if (setMethod == null) { if (field == null) { throw new InvalidKeyValuePropertyException("No public field " + name + " found, nor method matching " + "set" + propertyNameWithFirstCharToUpperCase + "(" + type + ") or " + "_set" + propertyNameWithFirstCharToUpperCase + "(" + type + ") found " + "in class " + declaringClass); } else { if (getMethod != null) { // Debugging.debug ("Public field "+propertyName+" // found, with type " // + type.getName()+ " found " // + " and method "+getMethod.getName()+" found " // + " but no method matching " // +"set"+propertyNameWithFirstCharToUpperCase+"("+type.getName()+") // or " // +"_set"+propertyNameWithFirstCharToUpperCase+"("+type.getName()+") // found." // +" Will use directly the field to set values."); } } } } settable = field != null || setMethod != null; if (getMethod != null && setMethod != null) { // If related field exist (is public) and accessors methods exists // also, // the operations are processed using accessors (field won't be used // directly, // and should be set to null). field = null; } // System.out.println("Made KeyValueProperty "+name+" for class "+declaringClass.getSimpleName()+" type="+type); if (TypeUtils.isGeneric(type)) { type = TypeUtils.makeInstantiatedType(type, declaringType); } /*if (type instanceof TypeVariable) { TypeVariable<GenericDeclaration> tv = (TypeVariable<GenericDeclaration>)type; //System.out.println("Found type variable "+tv+" name="+tv.getName()+" GD="+tv.getGenericDeclaration()); if (declaringType instanceof ParameterizedType) { GenericDeclaration gd = tv.getGenericDeclaration(); for (int i=0; i<gd.getTypeParameters().length; i++) { if (gd.getTypeParameters()[i] == tv) { type = ((ParameterizedType)declaringType).getActualTypeArguments()[i]; // Found matching parameterized type } } } }*/ } /** * Try to find a matching "get" method, such as (in order): * <ul> * <li>propertyName()</li> * <li>_propertyName()</li> * <li>getPropertyName()</li> * <li>_getPropertyName()</li> * </ul> * Returns corresponding method, null if no such method exist */ protected Method searchMatchingGetMethod(Class lastClass, String propertyName) { Method returnedMethod; String propertyNameWithFirstCharToUpperCase = propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()); String[] tries = new String[5]; tries[0] = "get" + propertyNameWithFirstCharToUpperCase; tries[1] = propertyName; tries[2] = "_" + propertyName; tries[3] = "_get" + propertyNameWithFirstCharToUpperCase; tries[4] = "is" + propertyNameWithFirstCharToUpperCase; for (String trie : tries) { try { return lastClass.getMethod(trie, null); } catch (SecurityException err) { // we continue } catch (NoSuchMethodException err) { // we continue } catch (NoClassDefFoundError err) { // we continue } } // Debugging.debug ("No method matching " // +propertyName+"() or " // +"_"+propertyName+"() or " // +"get"+propertyNameWithFirstCharToUpperCase+"() or " // +"_get"+propertyNameWithFirstCharToUpperCase+"() found."); 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 */ protected Method searchMatchingSetMethod(Class declaringClass, String propertyName, Type aType) { String propertyNameWithFirstCharToUpperCase = propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()); String[] tries; if (TypeUtils.isBoolean(aType) && propertyName.startsWith("is")) { tries = new String[4]; String propertyNameWithFirstCharToUpperCase2 = propertyName.substring(2); tries[2] = "set" + propertyNameWithFirstCharToUpperCase2; tries[3] = "_set" + propertyNameWithFirstCharToUpperCase2; } else { tries = new String[2]; } tries[0] = "set" + propertyNameWithFirstCharToUpperCase; tries[1] = "_set" + propertyNameWithFirstCharToUpperCase; for (Method m : declaringClass.getMethods()) { for (int i = 0; i < tries.length; i++) { if (m.getName().equals(tries[i]) && m.getGenericParameterTypes().length == 1 && m.getGenericParameterTypes()[0].equals(aType)) { return m; } } } Type superType = TypeUtils.getSuperType(aType); if (superType != null) { // Try with a super class return searchMatchingSetMethod(declaringClass, propertyName, superType); } /* Class typeClass = TypeUtils.getBaseClass(aType); if (typeClass != null && typeClass.getSuperclass() != null) { // Try with a super class return searchMatchingSetMethod(declaringClass, propertyName, typeClass.getSuperclass()); }*/ return null; } /** * Stores related "get" method (if this one is public) or null if method is protected/private or non-existant */ public Method getGetMethod() { return getMethod; } /** * Stores related "set" method (if this one is public) or null if method is protected/private or non-existant */ public Method getSetMethod() { return setMethod; } /** * Returns name of this property */ public String getName() { return name; } /** * Returns related object class (never null) */ @Override public Class getDeclaringClass() { return declaringClass; } /** * Returns related field (if this one is public) or null if field is protected or non-existant */ public Field getField() { return field; } /** * Returns related type */ @Override public Type getType() { return type; } /** * Search and returns all methods (as {@link AccessorMethod} objects) of related class whose names is in the specified string list, with * exactly the specified number of parameters, ascendant ordered regarding parameters specialization. * * @see AccessorMethod */ protected TreeSet<AccessorMethod> searchMethodsWithNameAndParamsNumber(String[] searchedNames, int paramNumber) { TreeSet<AccessorMethod> returnedTreeSet = new TreeSet<AccessorMethod>(); Method[] allMethods = declaringClass.getMethods(); for (int i = 0; i < allMethods.length; i++) { Method tempMethod = allMethods[i]; for (int j = 0; j < searchedNames.length; j++) { if (tempMethod.getName().equalsIgnoreCase(searchedNames[j]) && tempMethod.getParameterTypes().length == paramNumber) { // This is a good candidate returnedTreeSet.add(new AccessorMethod(this, tempMethod)); } } } // Debugging.debug ("Class "+objectClass.getName()+": found " // +returnedTreeSet.size()+" accessors:"); // for (Iterator i = returnedTreeSet.iterator(); i.hasNext();) { // Debugging.debug ("> "+((AccessorMethod)i.next()).getMethod()); // } return returnedTreeSet; } @Override public String getSerializationRepresentation() { return name; } @Override public boolean isBindingValid() { return true; } @Override public boolean isSettable() { return settable; } @Override public String toString() { return "KeyValueProperty: " + (declaringClass != null ? declaringClass.getSimpleName() : declaringType) + "." + name; } @Override public String getLabel() { return getName(); } @Override public String getTooltipText(Type resultingType) { String returned = "<html>"; String resultingTypeAsString; if (resultingType != null) { resultingTypeAsString = TypeUtils.simpleRepresentation(resultingType); resultingTypeAsString = ToolBox.replaceStringByStringInString("<", "<", resultingTypeAsString); resultingTypeAsString = ToolBox.replaceStringByStringInString(">", ">", resultingTypeAsString); } else { resultingTypeAsString = "???"; } returned += "<p><b>" + resultingTypeAsString + " " + getName() + "</b></p>"; // returned += // "<p><i>"+(property.getDescription()!=null?property.getDescription():FlexoLocalization.localizedForKey("no_description"))+"</i></p>"; returned += "</html>"; return returned; } @Override public Object getBindingValue(Object target, BindingEvaluationContext context) { return KeyValueDecoder.objectForKey(target, getName()); } @Override public void setBindingValue(Object value, Object target, BindingEvaluationContext context) { KeyValueCoder.setObjectForKey(target, value, getName()); } }