/*
* (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.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeSet;
// addTo > setForKey
// removeFrom > removeWithKey
/**
* <p>
* A HashtableKeyValueProperty property represents a hashtable-like property, accessible directly by related field or related accessors.
* </p>
*
* @author <a href="mailto:Sylvain.Guerin@enst-bretagne.fr">Sylvain Guerin</a>
* @see KeyValueCoder
* @see KeyValueDecoder
*
*/
public class HashtableKeyValueProperty extends KeyValueProperty {
/**
* Stores all 'setForKey...' methods, in order of the specialization of their arguments, as {@link AccessorMethod} objects. (the first
* methods are the first we will try to invoke, that's why vector needs to be sorted).<br>
* NB: 'setForKey...' methods are methods with general form: <code>setXXXForKey(Class anObject, Class aKey)</code> or
* <code>_setXXXForKey(Class anObject, Class aKey)</code>, where XXX is the property name (try with or without a terminal 's'
* character), and Class could be anything...
*/
protected TreeSet<AccessorMethod> setForKeyMethods;
/**
* Stores all 'removeWithKey...' methods, in order of the specialization of their arguments, as {@link AccessorMethod} objects. (the
* first methods are the first we will try to invoke, that's why vector needs to be sorted).<br>
* NB: 'removeWithKey...' methods are methods with general form: <code>removeXXXWithKey(Class aKey)</code> or
* <code>_removeXXXWithKey(Class aKey)</code>, where XXX is the property name (try with or without a terminal 's' character), and Class
* could be anything...
*/
protected TreeSet<AccessorMethod> removeWithKeyMethods;
/**
* Creates a new <code>HashtableKeyValueProperty</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 HashtableKeyValueProperty(Class<?> anObjectClass, String propertyName, boolean setMethodIsMandatory)
throws InvalidKeyValuePropertyException {
super(anObjectClass, propertyName);
init(propertyName, setMethodIsMandatory);
}
/**
* Returns boolean indicating if related type inherits from Hastable
*/
public boolean typeInheritsFromMap() {
return Map.class.isAssignableFrom(getType());
}
/**
* 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
* accessors methods. If the field is accessible, and only some of accessors methods are accessible, a warning will be thrown.
*/
@Override
protected void init(String propertyName, boolean setMethodIsMandatory) throws InvalidKeyValuePropertyException {
super.init(propertyName, setMethodIsMandatory);
if (!typeInheritsFromMap()) {
throw new InvalidKeyValuePropertyException("Property " + propertyName + " found, but doesn't seem inherits from java.util.Map");
}
// If related type is a sub-class of hashtable, check that there is a
// a trivial constructor
if (!getType().equals(Hashtable.class) && !getType().equals(HashMap.class)) {
if (!type.isAssignableFrom(Hashtable.class) || !type.isAssignableFrom(HashMap.class)) {
try {
// Test instantiation
type.newInstance();
} catch (InstantiationException e) {
throw new InvalidKeyValuePropertyException("Class " + type.getName()
+ " cannot be instanciated directly (check that this class has a constructor with no arguments [public "
+ type.getName() + "()] and that this class is not abstract.");
} catch (Exception e) {
throw new InvalidModelException("Unexpected error occurs during model initialization. Please send a bug report.");
}
}
}
if (field == null) {
// Debugging.debug ("Trying to find hashtable-specific accessors
// methods");
setForKeyMethods = searchMatchingSetForKeyMethods(propertyName);
if (setForKeyMethods.size() == 0 && setMethodIsMandatory) {
throw new InvalidKeyValuePropertyException("No public field " + propertyName
+ " found, and no 'setForKey' methods accessors found");
}
removeWithKeyMethods = searchMatchingRemoveWithKeyMethods(propertyName);
if (removeWithKeyMethods.size() == 0 && setMethodIsMandatory) {
throw new InvalidKeyValuePropertyException("No public field " + propertyName
+ " found, and no 'removeWithKey' methods accessors found");
}
}
}
/**
* Search and return matching "setForKey" methods<br>
* NB: 'setForKey...' methods are methods with general form: <code>setXXXForKey(Class anObject, Class aKey)</code> or
* <code>_setXXXForKey(Class anObject, Class aKey)</code>, where XXX is the property name (try with or without a terminal 's'
* character), and Class could be anything... Returns an ordered TreeSet of {@link AccessorMethod} objects
*/
protected TreeSet<AccessorMethod> searchMatchingSetForKeyMethods(String propertyName) {
String singularPropertyName;
String pluralPropertyName;
if (propertyName.endsWith("ies")) {
singularPropertyName = propertyName.substring(0, propertyName.length() - 3) + "y";
pluralPropertyName = propertyName;
} else if (propertyName.endsWith("s") || propertyName.endsWith("S")) {
singularPropertyName = propertyName.substring(0, propertyName.length() - 1);
pluralPropertyName = propertyName;
} else {
singularPropertyName = propertyName;
pluralPropertyName = propertyName + "s";
} // end of else
String[] methodNameCondidates = new String[4];
methodNameCondidates[0] = "set" + singularPropertyName + "ForKey";
methodNameCondidates[1] = "_set" + singularPropertyName + "ForKey";
methodNameCondidates[2] = "set" + pluralPropertyName + "ForKey";
methodNameCondidates[3] = "_set" + pluralPropertyName + "ForKey";
return searchMethodsWithNameAndParamsNumber(methodNameCondidates, 2);
}
/**
* Search and return matching "removeWithKey" methods<br>
* NB: 'removeWithKey...' methods are methods with general form: <code>removeXXXWithKey(Class aKey)</code> or
* <code>_removeXXXWithKey(Class aKey)</code>, where XXX is the property name (try with or without a terminal 's' character), and Class
* could be anything... Returns an ordered TreeSet of {@link AccessorMethod} objects
*/
protected TreeSet<AccessorMethod> searchMatchingRemoveWithKeyMethods(String propertyName) {
String singularPropertyName;
String pluralPropertyName;
if (propertyName.endsWith("ies")) {
singularPropertyName = propertyName.substring(0, propertyName.length() - 3) + "y";
pluralPropertyName = propertyName;
} else if (propertyName.endsWith("s") || propertyName.endsWith("S")) {
singularPropertyName = propertyName.substring(0, propertyName.length() - 1);
pluralPropertyName = propertyName;
} else {
singularPropertyName = propertyName;
pluralPropertyName = propertyName + "s";
} // end of else
String[] methodNameCondidates = new String[4];
methodNameCondidates[0] = "remove" + singularPropertyName + "WithKey";
methodNameCondidates[1] = "_remove" + singularPropertyName + "WithKey";
methodNameCondidates[2] = "remove" + pluralPropertyName + "WithKey";
methodNameCondidates[3] = "_remove" + pluralPropertyName + "WithKey";
return searchMethodsWithNameAndParamsNumber(methodNameCondidates, 1);
}
/**
* Add Object value for considered object <code>anObject</code>, asserting that this property represents a Hashtable-like property (if
* not, throw an InvalidKeyValuePropertyException exception)
*
* @param aValue
* an <code>Object</code> value
* @exception InvalidKeyValuePropertyException
* if an error occurs
*/
public synchronized void setObjectValueForKey(Object aValue, Object aKey, Object object) {
if (object == null) {
throw new InvalidKeyValuePropertyException("No object is specified");
} else {
if (field != null) {
try {
Map<Object, Object> hashtable = (Map<Object, Object>) field.get(object);
hashtable.put(aKey, aValue);
} catch (Exception e) {
throw new InvalidKeyValuePropertyException("InvalidKeyValuePropertyException: class " + getObjectClass().getName()
+ ": field " + field.getName() + " Exception raised: " + e.toString());
}
} else {
Object params[] = new Object[2];
params[0] = aValue;
params[1] = aKey;
for (Iterator<AccessorMethod> i = setForKeyMethods.iterator(); i.hasNext();) {
Method method = null;
try {
method = i.next().getMethod();
method.invoke(object, params);
return;
} catch (InvocationTargetException e) {
throw new AccessorInvocationException("Exception thrown while invoking: " + method, e);
} catch (IllegalArgumentException e) {
// try next one
/*System.out.println("method: "+method);
System.out.println("aValue: "+aValue);
System.out.println("aKey: "+aKey);*/
} catch (Exception e) {
// try next one
e.printStackTrace();
}
}
throw new InvalidKeyValuePropertyException("addObjectValue, class " + getObjectClass().getName()
+ ": could not find a valid 'setForKey' accessor method: object class was " + aValue.getClass().getName());
}
}
}
/**
* Remove Object value for considered object <code>anObject</code>, asserting that this property represents a Hashtable-like property
* (if not, throw an InvalidKeyValuePropertyException exception)
*
* @param aValue
* an <code>Object</code> value
* @exception InvalidKeyValuePropertyException
* if an error occurs
*/
public synchronized void removeWithKeyValue(Object aKey, Object object) {
if (object == null) {
throw new InvalidKeyValuePropertyException("No object is specified");
} else {
if (field != null) {
try {
Map<?, ?> hashtable = (Map<?, ?>) field.get(object);
hashtable.remove(aKey);
} catch (Exception e) {
throw new InvalidKeyValuePropertyException("InvalidKeyValuePropertyException: class " + getObjectClass().getName()
+ ": field " + field.getName() + " Exception raised: " + e.toString());
}
} else {
Object params[] = new Object[1];
params[0] = aKey;
for (Iterator<AccessorMethod> i = removeWithKeyMethods.iterator(); i.hasNext();) {
Method method = null;
try {
method = i.next().getMethod();
method.invoke(object, params);
return;
} catch (InvocationTargetException e) {
throw new AccessorInvocationException("Exception thrown while invoking: " + method, e);
} catch (Exception e) {
// try next one
}
}
throw new InvalidKeyValuePropertyException("removeObjectValue, class " + getObjectClass().getName()
+ ": could not find a valid 'removeWithKey' accessor method: key class was " + aKey.getClass().getName());
}
}
}
/**
* Creates a new instance of this represented class, which MUST be a {@link Map} or a subclass of {@link Map}.
*/
public Map<?, ?> newInstance() throws InvalidObjectSpecificationException {
if (type.isInterface() || Modifier.isAbstract(type.getModifiers())) {
if (type.isAssignableFrom(Hashtable.class)) {
return new Hashtable();
} else if (type.isAssignableFrom(HashMap.class)) {
return new HashMap();
}
}
try {
return (Map<?, ?>) type.newInstance();
} catch (Exception e) {
throw new InvalidObjectSpecificationException("Could not instanciate a new " + type.getName() + ": reason " + e);
}
}
}