/* * (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.xmlcode; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Enumeration; import java.util.StringTokenizer; import java.util.Vector; /** * <p> * A KeyValue property represents a property accessible using parameters * </p> * * @author <a href="mailto:Sylvain.Guerin@enst-bretagne.fr">Sylvain Guerin</a> * @see KeyValueCoder * @see KeyValueDecoder * */ public class ParameteredKeyValueProperty extends SingleKeyValueProperty { private Vector<KeyValueProperty> arguments; /** * Stores related "get" methods (if there is more that one method matching signature) */ protected Vector<Method> getMethods; /** * Stores related "set" method (if there is more that one method matching signature) */ protected Vector<Method> setMethods; public static boolean isParameteredKeyValuePropertyPattern(String propertyName) { return isParameteredPathElement(parsePath(propertyName).lastElement()); } /** * Creates a new <code>KeyValueProperty</code> instance, given an object class.<br> * To be usable, this property should be set with a correct object (according to object class) * * @param anObject * an <code>Object</code> value * @param propertyName * a <code>String</code> value * @exception InvalidKeyValuePropertyException * if an error occurs */ public ParameteredKeyValueProperty(Class anObjectClass, String propertyName, boolean setMethodIsMandatory) throws InvalidKeyValuePropertyException { super(anObjectClass, propertyName, setMethodIsMandatory); } /** * Initialize this property, given a propertyName.<br> * This method is called during constructor invokation. */ @Override protected void init(String propertyName, boolean setMethodIsMandatory) throws InvalidKeyValuePropertyException { arguments = parsePathElementArguments(parsePath(propertyName).lastElement()); super.init(propertyName, setMethodIsMandatory); } /** * Returns Object value, asserting that this property represents an Object property (if not, throw an InvalidKeyValuePropertyException * exception) * * @return an <code>Object</code> value * @exception InvalidKeyValuePropertyException * if an error occurs */ @Override protected synchronized Object getObjectValue(Object object, Object initialObject) { if (object == null) { throw new InvalidKeyValuePropertyException("No object is specified"); } else { Object currentObject = object; if (isCompound) { for (KeyValueProperty p : compoundKeyValueProperties) { if (currentObject != null) { currentObject = p.getObjectValue(currentObject, initialObject); } } if (currentObject == null) { return null; } } if (getMethods.size() == 0) { throw new InvalidKeyValuePropertyException("InvalidKeyValuePropertyException: no get method found !!!"); } for (Method m : getMethods) { try { Object[] parameters = new Object[arguments.size()]; // String debug = ""; for (int i = 0; i < arguments.size(); i++) { parameters[i] = _translateValueForAssignability(m.getParameterTypes()[i], arguments.get(i).getObjectValue(initialObject)); // debug = debug + (i>0?",":"") + // parameters[i]+"/"+(parameters[i]!=null?parameters[i].getClass().getSimpleName():"null"); } // System.out.println("Trying to invoke on object "+currentObject+" method "+m+" with "+debug); return m.invoke(currentObject, parameters); } catch (IllegalArgumentException e) { System.out.println("IllegalArgumentException: " + e); } catch (IllegalAccessException e) { System.out.println("IllegalAccessException: " + e); } catch (InvocationTargetException e) { e.getTargetException().printStackTrace(); throw new AccessorInvocationException("AccessorInvocationException: class " + getObjectClass().getName() + ": method " + m + " Exception raised: " + e.getTargetException().toString(), e); } catch (NonAssignableParameterException e) { // Does NOT seem to be acceptable method } } throw new InvalidKeyValuePropertyException("InvalidKeyValuePropertyException: class " + getObjectClass().getName() + ": could not access ParameteredKeyValueProperty " + name); } } /** * Sets Object value, asserting that this property represents an Object property (if not, throw an InvalidKeyValuePropertyException * exception) * * @param aValue * an <code>Object</code> value * @exception InvalidKeyValuePropertyException * if an error occurs */ /** * Sets Object value, asserting that this property represents an Object property (if not, throw an InvalidKeyValuePropertyException * exception) * * @param aValue * an <code>Object</code> value * @exception InvalidKeyValuePropertyException * if an error occurs */ @Override public synchronized void setObjectValue(Object aValue, Object object, Object initialObject) { if (object == null) { throw new InvalidKeyValuePropertyException("No object is specified"); } else { Object currentObject = object; if (isCompound) { for (KeyValueProperty p : compoundKeyValueProperties) { if (currentObject != null) { currentObject = p.getObjectValue(currentObject, initialObject); } } if (currentObject == null) { return; } } if (setMethods.size() == 0) { throw new InvalidKeyValuePropertyException("InvalidKeyValuePropertyException: no set method found !!!"); } for (Method m : setMethods) { try { Object[] parameters = new Object[arguments.size() + 1]; parameters[0] = aValue; // String debug = ""+aValue; for (int i = 0; i < arguments.size(); i++) { parameters[i + 1] = _translateValueForAssignability(m.getParameterTypes()[i + 1], arguments.get(i).getObjectValue(initialObject)); // debug = debug + "," + // parameters[i+1]+"/"+(parameters[i+1]!=null?parameters[i+1].getClass().getSimpleName():"null"); } // System.out.println("Trying to invoke on object "+currentObject+" method "+m+" with "+debug); m.invoke(currentObject, parameters); return; } catch (IllegalArgumentException e) { System.out.println("IllegalArgumentException: " + e); } catch (IllegalAccessException e) { System.out.println("IllegalAccessException: " + e); } catch (InvocationTargetException e) { e.getTargetException().printStackTrace(); throw new AccessorInvocationException("AccessorInvocationException: class " + getObjectClass().getName() + ": method " + m + " Exception raised: " + e.getTargetException().toString(), e); } catch (NonAssignableParameterException e) { // Does NOT seem to be acceptable method } } throw new InvalidKeyValuePropertyException("InvalidKeyValuePropertyException: class " + getObjectClass().getName() + ": could not access ParameteredKeyValueProperty " + name); } } /** * Try to find a matching "get" method, such as (in order): * <ul> * <li>propertyName(...parameters...)</li> * <li>_propertyName(...parameters...)</li> * <li>getPropertyName(...parameters...)</li> * <li>_getPropertyName(...parameters...)</li> * </ul> * Returns corresponding method, null if no such method exist */ @Override protected Method searchMatchingGetMethod(Class lastClass, String parameteredPropertyName) { String propertyName = parameteredPathElementName(parameteredPropertyName); // System.out.println("searchMatchingGetMethod in "+lastClass+" for "+parameteredPropertyName); Object[] parameters = new Object[arguments.size()]; for (int i = 0; i < arguments.size(); i++) { if (arguments.get(i) instanceof ConstantKeyValueProperty) { parameters[i] = ((ConstantKeyValueProperty) arguments.get(i)).getConstant(); } else { parameters[i] = null; } } String propertyNameWithFirstCharToUpperCase = propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()); String[] tries = new String[4]; tries[0] = "get" + propertyNameWithFirstCharToUpperCase; tries[1] = propertyName; tries[2] = "_" + propertyName; tries[3] = "_get" + propertyNameWithFirstCharToUpperCase; getMethods = _lookupMethods(lastClass, tries, parameters); if (getMethods.size() > 1) { Class type = getMethods.firstElement().getReturnType(); for (int i = 1; i < getMethods.size(); i++) { if (!type.equals(getMethods.get(i).getReturnType())) { throw new InvalidKeyValuePropertyException( "InvalidKeyValuePropertyException: Ambigous methods found with different return types for property " + name); } } } // System.out.println("When trying to find getter for "+parameteredPropertyName+" finding "+getMethods.size()+" methods: "+getMethods); if (getMethods.size() > 0) { return getMethods.firstElement(); } else { return null; } } /** * Try to find a matching "set" method, such as (in order): * <ul> * <li>setPropertyName(Type,...parameters...)</li> * <li>_setPropertyName(Type,...parameters...)</li> * </ul> * Returns corresponding method, null if no such method exist */ @Override protected Method searchMatchingSetMethod(Class lastClass, String parameteredPropertyName, Class aType) { String propertyName = parameteredPathElementName(parameteredPropertyName); // System.out.println("searchMatchingSetMethod in "+lastClass+" for "+parameteredPropertyName); Object[] parameters = new Object[arguments.size() + 1]; parameters[0] = null; for (int i = 0; i < arguments.size(); i++) { if (arguments.get(i) instanceof ConstantKeyValueProperty) { parameters[i + 1] = ((ConstantKeyValueProperty) arguments.get(i)).getConstant(); } else { parameters[i + 1] = null; } } String propertyNameWithFirstCharToUpperCase = propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()); String[] tries = new String[2]; tries[0] = "set" + propertyNameWithFirstCharToUpperCase; tries[1] = "_set" + propertyNameWithFirstCharToUpperCase; setMethods = new Vector<Method>(); Vector<Method> potentialSetMethods = _lookupMethods(lastClass, tries, parameters); for (Method m : potentialSetMethods) { // Check that first param is of right type !!! if (m.getParameterTypes()[0].equals(getType())) { setMethods.add(m); } } // System.out.println("When trying to find setter for "+parameteredPropertyName+" finding "+setMethods.size()+" methods: "+setMethods); if (setMethods.size() > 0) { return setMethods.firstElement(); } else { return null; } } /** * Return a string representation of this object (debug purposes) */ @Override public String toString() { return "Field: " + field + "\nGetMethod: " + getMethod + "\nSetMethod: " + setMethod + "\nType: " + type; } /*private synchronized Object getObjectValue(Object inspectable, String aPropertyName, Object[] parameters) throws InvalidKeyValuePropertyException,AccessorInvocationException { Method m = _lookupGetter(inspectable.getClass(), aPropertyName, parameters); if (m == null) { throw new InvalidKeyValuePropertyException("getObjectValue() failed for property " + name + " for object " + inspectable.getClass().getName() + " : cannot find GETTER method for " + name+" with "+parameters); } else { Object returned; try { returned = m.invoke(inspectable,parameters); } catch (IllegalArgumentException e) { throw new InvalidKeyValuePropertyException("getObjectValue() failed for property " + name + " for object " + inspectable.getClass().getName() + " : exception " + e.getMessage()); } catch (IllegalAccessException e) { throw new InvalidKeyValuePropertyException("getObjectValue() failed for property " + name + " for object " + inspectable.getClass().getName() + " : exception " + e.getMessage()); } catch (InvocationTargetException e) { throw new AccessorInvocationException("getObjectValue() failed for property " + name + " for object " + inspectable.getClass().getName(),e); } return returned; } } private synchronized void setObjectValue(Object inspectable, String aPropertyName, Object[] initialParameters, Object aValue) throws InvalidKeyValuePropertyException,AccessorInvocationException { Object[] parameters = new Object[initialParameters.length+1]; parameters[0] = aValue; for (int i=0; i<initialParameters.length; i++) parameters[i+1] = initialParameters[i]; Method m = _lookupSetter(inspectable.getClass(), aPropertyName, parameters, aValue); if (m == null) { throw new InvalidKeyValuePropertyException("setObjectValue() failed for property " + name + " for object " + inspectable.getClass().getName()+" and value "+aValue + " : cannot find SETTER method for " + name+" with "+parameters); } else { try { m.invoke(inspectable,parameters); } catch (IllegalArgumentException e) { throw new InvalidKeyValuePropertyException("setObjectValue() failed for property " + name + " for object " + inspectable.getClass().getName()+" and value "+aValue + " : exception " + e.getMessage()); } catch (IllegalAccessException e) { throw new InvalidKeyValuePropertyException("setObjectValue() failed for property " + name + " for object " + inspectable.getClass().getName()+" and value "+aValue + " : exception " + e.getMessage()); } catch (InvocationTargetException e) { throw new AccessorInvocationException("setObjectValue() failed for property " + name + " for object " + inspectable.getClass().getName()+" and value "+aValue,e); } } }*/ /* private Method _lookupGetter(Class aClass, String aPropertyName, Object[] parameters) { if (true) return aClass.getMethods()[0]; Class[] parameterTypes = new Class[parameters.length]; for (int i=0; i<parameters.length; i++) { if (parameters[i] != null) parameterTypes[i] = parameters[i].getClass(); else parameterTypes[i] = null; } String propertyNameWithFirstCharToUpperCase = aPropertyName.substring(0, 1).toUpperCase() + aPropertyName.substring(1, aPropertyName.length()); String[] tries = new String[4]; tries[0]="get" + propertyNameWithFirstCharToUpperCase; tries[1]=aPropertyName; tries[2]="_" + name; tries[3]="_get" + propertyNameWithFirstCharToUpperCase; Method returned = _lookupMethod(aClass, tries, parameterTypes, parameters); if (returned != null) { // Now convert parameters for (int i=0; i<returned.getParameterTypes().length; i++) { parameters[i] = _translateValueForAssignability(returned.getParameterTypes()[i], parameters[i]); } } return returned; } private Method _lookupSetter(Class aClass, String aPropertyName, Object[] parameters, Object aValue) { if (true) return aClass.getMethods()[0]; Class[] parameterTypes = new Class[parameters.length]; for (int i=0; i<parameters.length; i++) { if (parameters[i] != null) parameterTypes[i] = parameters[i].getClass(); else parameterTypes[i] = null; } String propertyNameWithFirstCharToUpperCase = aPropertyName.substring(0, 1).toUpperCase() + aPropertyName.substring(1, aPropertyName.length()); String[] tries = new String[2]; tries[0]="set" + propertyNameWithFirstCharToUpperCase; tries[1]="_set" + propertyNameWithFirstCharToUpperCase; Method returned = _lookupMethod(aClass, tries, parameterTypes, parameters); if (returned != null) { // Now convert parameters for (int i=0; i<returned.getParameterTypes().length; i++) { parameters[i] = _translateValueForAssignability(returned.getParameterTypes()[i], parameters[i]); } } return returned; }*/ private Vector<Method> _lookupMethods(Class aClass, String[] methodNames, Object[] parameters) { Vector<Method> returned = new Vector<Method>(); for (Method m : aClass.getMethods()) { // System.out.println("Method: "+m+" parameters.length="+parameters.length); for (String methodName : methodNames) { if (m.getName().equals(methodName) && m.getParameterTypes().length == parameters.length) { boolean ok = true; for (int i = 0; i < m.getParameterTypes().length; i++) { if (!_checkAssignability(m.getParameterTypes()[i], parameters[i])) { ok = false; } } if (ok) { returned.add(m); } } } } return returned; } private boolean _checkAssignability(Class<?> methodParamType, Object myValue) { if (methodParamType == null) { System.err.println("Unexpected null type"); return false; } if (myValue == null) { return true; } Class<?> myType = myValue.getClass(); if (methodParamType.equals(Double.TYPE)) { methodParamType = Double.class; } if (methodParamType.equals(Float.TYPE)) { methodParamType = Float.class; } if (methodParamType.equals(Long.TYPE)) { methodParamType = Long.class; } if (methodParamType.equals(Integer.TYPE)) { methodParamType = Integer.class; } if (methodParamType.equals(Short.TYPE)) { methodParamType = Short.class; } if (methodParamType.equals(Byte.TYPE)) { methodParamType = Byte.class; } if (methodParamType.equals(Character.TYPE)) { methodParamType = Character.class; } if (methodParamType.equals(Boolean.TYPE)) { methodParamType = Boolean.class; } if (myType.equals(Double.TYPE)) { myType = Double.class; } if (myType.equals(Float.TYPE)) { myType = Float.class; } if (myType.equals(Long.TYPE)) { myType = Long.class; } if (myType.equals(Integer.TYPE)) { myType = Integer.class; } if (myType.equals(Short.TYPE)) { myType = Short.class; } if (myType.equals(Byte.TYPE)) { myType = Byte.class; } if (myType.equals(Character.TYPE)) { myType = Character.class; } if (myType.equals(Boolean.TYPE)) { myType = Boolean.class; } if (methodParamType.isAssignableFrom(myType)) { return true; } if (Number.class.isAssignableFrom(methodParamType) && Number.class.isAssignableFrom(myType)) { // Last chance with using values Number nb = (Number) myValue; if (methodParamType.equals(Double.class)) { return true; } if (methodParamType.equals(Float.class)) { return true; // Will require truncation } if (methodParamType.equals(Long.class)) { return nb.doubleValue() == nb.longValue(); } if (methodParamType.equals(Integer.class)) { return nb.doubleValue() == nb.intValue(); } if (methodParamType.equals(Short.class)) { return nb.doubleValue() == nb.shortValue(); } if (methodParamType.equals(Byte.class)) { return nb.doubleValue() == nb.byteValue(); } } return false; } private class NonAssignableParameterException extends Exception { } private Object _translateValueForAssignability(Class<?> methodParamType, Object myValue) throws NonAssignableParameterException { if (methodParamType == null) { System.err.println("Unexpected null type"); return false; } if (myValue == null) { return null; } Class<?> myType = myValue.getClass(); if (methodParamType.equals(Double.TYPE)) { methodParamType = Double.class; } if (methodParamType.equals(Float.TYPE)) { methodParamType = Float.class; } if (methodParamType.equals(Long.TYPE)) { methodParamType = Long.class; } if (methodParamType.equals(Integer.TYPE)) { methodParamType = Integer.class; } if (methodParamType.equals(Short.TYPE)) { methodParamType = Short.class; } if (methodParamType.equals(Byte.TYPE)) { methodParamType = Byte.class; } if (methodParamType.equals(Character.TYPE)) { methodParamType = Character.class; } if (methodParamType.equals(Boolean.TYPE)) { methodParamType = Boolean.class; } if (myType.equals(Double.TYPE)) { myType = Double.class; } if (myType.equals(Float.TYPE)) { myType = Float.class; } if (myType.equals(Long.TYPE)) { myType = Long.class; } if (myType.equals(Integer.TYPE)) { myType = Integer.class; } if (myType.equals(Short.TYPE)) { myType = Short.class; } if (myType.equals(Byte.TYPE)) { myType = Byte.class; } if (myType.equals(Character.TYPE)) { myType = Character.class; } if (myType.equals(Boolean.TYPE)) { myType = Boolean.class; } if (methodParamType.isAssignableFrom(myType)) { return myValue; } if (Number.class.isAssignableFrom(methodParamType) && Number.class.isAssignableFrom(myType)) { Number nb = (Number) myValue; if (methodParamType.equals(Double.class)) { return nb.doubleValue(); } if (methodParamType.equals(Float.class)) { return nb.floatValue(); } if (methodParamType.equals(Long.class)) { return nb.longValue(); } if (methodParamType.equals(Integer.class)) { return nb.intValue(); } if (methodParamType.equals(Short.class)) { return nb.shortValue(); } if (methodParamType.equals(Byte.class)) { return nb.byteValue(); } } throw new NonAssignableParameterException(); } private static Vector<String> parsePath(String aPath) { Vector<String> returned = new Vector<String>(); // System.out.println("BEGIN parse "+aPath); PathTokenizer t = new PathTokenizer(aPath); while (t.hasMoreTokens()) { returned.add(t.nextToken()); // System.out.println("token: "+returned.lastElement()); } return returned; } private static boolean isParameteredPathElement(String pathElement) { return pathElement.indexOf("(") > 0; } private static String parameteredPathElementName(String pathElement) { return pathElement.substring(0, pathElement.indexOf("(")); } private Vector<KeyValueProperty> parsePathElementArguments(String pathElement) { String argsAsString = pathElement.substring(pathElement.indexOf("(") + 1, pathElement.lastIndexOf(")")); Vector<KeyValueProperty> returned = new Vector<KeyValueProperty>(); // System.out.println("BEGIN parse args "+argsAsString); ArgsTokenizer t = new ArgsTokenizer(argsAsString); while (t.hasMoreTokens()) { String next = t.nextToken(); KeyValueProperty newProperty; boolean isParsableAsALong = false; long parsedLong = 0; try { parsedLong = Long.parseLong(next); isParsableAsALong = true; } catch (NumberFormatException e) { } ; boolean isParsableAsADouble = false; double parsedDouble = 0; if (!isParsableAsALong) { try { parsedDouble = Double.parseDouble(next); isParsableAsADouble = true; } catch (NumberFormatException e) { } ; } if (isParsableAsADouble) { newProperty = new ConstantKeyValueProperty.DoubleConstantKeyValueProperty(getObjectClass(), parsedDouble); } else if (isParsableAsALong) { newProperty = new ConstantKeyValueProperty.LongConstantKeyValueProperty(getObjectClass(), parsedLong); } else if (next.equalsIgnoreCase("true") || next.equalsIgnoreCase("yes")) { newProperty = new ConstantKeyValueProperty.BooleanConstantKeyValueProperty(getObjectClass(), true); } else if (next.equalsIgnoreCase("false") || next.equalsIgnoreCase("no")) { newProperty = new ConstantKeyValueProperty.BooleanConstantKeyValueProperty(getObjectClass(), false); } else if (next.startsWith("\"") && next.endsWith("\"")) { newProperty = new ConstantKeyValueProperty.StringConstantKeyValueProperty(getObjectClass(), next.substring(1, next.length() - 1)); } else if (next.startsWith("'") && next.endsWith("'")) { newProperty = new ConstantKeyValueProperty.StringConstantKeyValueProperty(getObjectClass(), next.substring(1, next.length() - 1)); } else if (isParameteredKeyValuePropertyPattern(next)) { newProperty = new ParameteredKeyValueProperty(getObjectClass(), next, false); } else { newProperty = new SingleKeyValueProperty(getObjectClass(), next, false); } returned.add(newProperty); } return returned; } protected static class ArgsTokenizer { private Vector<String> _tokens; private Enumeration<String> enumeration; protected ArgsTokenizer(String value) { super(); _tokens = new Vector<String>(); StringTokenizer st = new StringTokenizer(value, ",()", true); String current = ""; int level = 0; while (st.hasMoreElements()) { String next = st.nextToken(); if (next.equals(",") && current.trim().length() > 0 && level == 0) { _tokens.add(current); current = ""; } else if (next.equals("(")) { current += next; level++; } else if (next.equals(")")) { current += next; level--; } else { current += next; } } if (current.trim().length() > 0 && level == 0) { _tokens.add(current); current = ""; } enumeration = _tokens.elements(); } public boolean hasMoreTokens() { return enumeration.hasMoreElements(); } public String nextToken() { String returned = enumeration.nextElement(); return returned; } } }