blob: c01b5bedbb0a950c8e4207a3efb4d0fbf85de71c [file] [log] [blame]
/*
*/
package org.apache.tomcat.test.watchdog;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Refactoring of IntrospectionUtils and modeler dynamic bean.
*
* Unlike IntrospectionUtils, the method informations can be cached.
* Also I hope this class will be simpler to use.
* There is no static cache.
*
* @author Costin Manolache
*/
public class DynamicObject {
// Based on MbeansDescriptorsIntrospectionSource
private static Logger log = Logger.getLogger(DynamicObject.class.getName());
private static Class<?> NO_PARAMS[] = new Class[0];
private static String strArray[] = new String[0];
private static Class<?>[] supportedTypes = new Class[] { Boolean.class,
Boolean.TYPE, Byte.class, Byte.TYPE, Character.class,
Character.TYPE, Short.class, Short.TYPE, Integer.class,
Integer.TYPE, Long.class, Long.TYPE, Float.class, Float.TYPE,
Double.class, Double.TYPE, String.class, strArray.getClass(),
BigDecimal.class, BigInteger.class, AtomicInteger.class,
AtomicLong.class, java.io.File.class, };
private Class realClass;
// Method or Field
private Map<String, AccessibleObject> getAttMap;
public DynamicObject(Class beanClass) {
this.realClass = beanClass;
initCache();
}
private void initCache() {
Method methods[] = null;
getAttMap = new HashMap<String, AccessibleObject>();
methods = realClass.getMethods();
for (int j = 0; j < methods.length; ++j) {
if (ignorable(methods[j])) {
continue;
}
String name = methods[j].getName();
Class<?> params[] = methods[j].getParameterTypes();
if (name.startsWith("get") && params.length == 0) {
Class<?> ret = methods[j].getReturnType();
if (!supportedType(ret)) {
if (log.isLoggable(Level.FINE))
log.fine("Unsupported type " + methods[j]);
continue;
}
name = unCapitalize(name.substring(3));
getAttMap.put(name, methods[j]);
} else if (name.startsWith("is") && params.length == 0) {
Class<?> ret = methods[j].getReturnType();
if (Boolean.TYPE != ret) {
if (log.isLoggable(Level.FINE))
log.fine("Unsupported type " + methods[j] + " " + ret);
continue;
}
name = unCapitalize(name.substring(2));
getAttMap.put(name, methods[j]);
}
}
// non-private AtomicInteger and AtomicLong - stats
Field fields[] = realClass.getFields();
for (int j = 0; j < fields.length; ++j) {
if (fields[j].getType() == AtomicInteger.class) {
getAttMap.put(fields[j].getName(), fields[j]);
}
}
}
public List<String> attributeNames() {
return new ArrayList<String>(getAttMap.keySet());
}
public Object invoke(Object proxy, String method) throws Exception {
Method executeM = null;
Class<?> c = proxy.getClass();
executeM = c.getMethod(method, NO_PARAMS);
if (executeM == null) {
throw new RuntimeException("No execute in " + proxy.getClass());
}
return executeM.invoke(proxy, (Object[]) null);
}
// TODO
// public Object invoke(String method, Object[] params) {
// return null;
// }
public Object getAttribute(Object o, String att) {
AccessibleObject m = getAttMap.get(att);
if (m instanceof Method) {
try {
return ((Method) m).invoke(o);
} catch (Throwable e) {
log.log(Level.INFO, "Error getting attribute " + realClass + " "
+ att, e);
return null;
}
} if (m instanceof Field) {
if (((Field) m).getType() == AtomicInteger.class) {
try {
Object value = ((Field) m).get(o);
return ((AtomicInteger) value).get();
} catch (Throwable e) {
return null;
}
} else {
return null;
}
} else {
return null;
}
}
/**
* Set an object-type attribute.
*
* Use setProperty to use a string value and convert it to the
* specific (primitive) type.
*/
public boolean setAttribute(Object proxy, String name, Object value) {
// TODO: use the cache...
String methodName = "set" + capitalize(name);
Method[] methods = proxy.getClass().getMethods();
for (Method m : methods) {
Class<?>[] paramT = m.getParameterTypes();
if (methodName.equals(m.getName())
&& paramT.length == 1
&& (value == null || paramT[0].isAssignableFrom(value
.getClass()))) {
try {
m.invoke(proxy, value);
return true;
} catch (IllegalArgumentException e) {
log.severe("Error setting: " + name + " "
+ proxy.getClass().getName() + " " + e);
} catch (IllegalAccessException e) {
log.severe("Error setting: " + name + " "
+ proxy.getClass().getName() + " " + e);
} catch (InvocationTargetException e) {
log.severe("Error setting: " + name + " "
+ proxy.getClass().getName() + " " + e);
}
}
}
return false;
}
public boolean setProperty(Object proxy, String name, String value) {
// TODO: use the cache...
String setter = "set" + capitalize(name);
try {
Method methods[] = proxy.getClass().getMethods();
Method setPropertyMethod = null;
// First, the ideal case - a setFoo( String ) method
for (int i = 0; i < methods.length; i++) {
if (ignorable(methods[i])) {
continue;
}
Class<?> paramT[] = methods[i].getParameterTypes();
if (setter.equals(methods[i].getName()) && paramT.length == 1) {
if ("java.lang.String".equals(paramT[0].getName())) {
methods[i].invoke(proxy, new Object[] { value });
return true;
} else {
// match - find the type and invoke it
Class<?> paramType = methods[i].getParameterTypes()[0];
Object params[] = new Object[1];
params[0] = convert(value, paramType);
if (params[0] != null) {
methods[i].invoke(proxy, params);
return true;
}
}
}
// save "setProperty" for later
if ("setProperty".equals(methods[i].getName()) &&
paramT.length == 2 &&
paramT[0] == String.class &&
paramT[1] == String.class) {
setPropertyMethod = methods[i];
}
}
try {
Field field = proxy.getClass().getField(name);
if (field != null) {
Object conv = convert(value, field.getType());
if (conv != null) {
field.set(proxy, conv);
return true;
}
}
} catch (NoSuchFieldException e) {
// ignore
}
// Ok, no setXXX found, try a setProperty("name", "value")
if (setPropertyMethod != null) {
Object params[] = new Object[2];
params[0] = name;
params[1] = value;
setPropertyMethod.invoke(proxy, params);
return true;
}
} catch (Throwable ex2) {
log.log(Level.WARNING, "IAE " + proxy + " " + name + " " + value,
ex2);
}
return false;
}
// ----------- Helpers ------------------
static Object convert(String object, Class<?> paramType) {
Object result = null;
if ("java.lang.String".equals(paramType.getName())) {
result = object;
} else if ("java.lang.Long".equals(paramType.getName())
|| "long".equals(paramType.getName())) {
try {
result = Long.parseLong(object);
} catch (NumberFormatException ex) {
}
// Try a setFoo ( boolean )
} else if ("java.lang.Integer".equals(paramType.getName())
|| "int".equals(paramType.getName())) {
try {
result = new Integer(object);
} catch (NumberFormatException ex) {
}
// Try a setFoo ( boolean )
} else if ("java.lang.Boolean".equals(paramType.getName())
|| "boolean".equals(paramType.getName())) {
result = new Boolean(object);
} else {
log.info("Unknown type " + paramType.getName());
}
if (result == null) {
throw new IllegalArgumentException("Can't convert argument: "
+ object + " to " + paramType );
}
return result;
}
/**
* Converts the first character of the given String into lower-case.
*
* @param name
* The string to convert
* @return String
*/
static String unCapitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
char chars[] = name.toCharArray();
chars[0] = Character.toLowerCase(chars[0]);
return new String(chars);
}
/**
* Check if this class is one of the supported types. If the class is
* supported, returns true. Otherwise, returns false.
*
* @param ret
* The class to check
* @return boolean True if class is supported
*/
static boolean supportedType(Class<?> ret) {
for (int i = 0; i < supportedTypes.length; i++) {
if (ret == supportedTypes[i]) {
return true;
}
}
if (isBeanCompatible(ret)) {
return true;
}
return false;
}
/**
* Check if this class conforms to JavaBeans specifications. If the class is
* conformant, returns true.
*
* @param javaType
* The class to check
* @return boolean True if the class is compatible.
*/
static boolean isBeanCompatible(Class<?> javaType) {
// Must be a non-primitive and non array
if (javaType.isArray() || javaType.isPrimitive()) {
return false;
}
// Anything in the java or javax package that
// does not have a defined mapping is excluded.
if (javaType.getName().startsWith("java.")
|| javaType.getName().startsWith("javax.")) {
return false;
}
try {
javaType.getConstructor(new Class[] {});
} catch (java.lang.NoSuchMethodException e) {
return false;
}
// Make sure superclass is compatible
Class<?> superClass = javaType.getSuperclass();
if (superClass != null && superClass != java.lang.Object.class
&& superClass != java.lang.Exception.class
&& superClass != java.lang.Throwable.class) {
if (!isBeanCompatible(superClass)) {
return false;
}
}
return true;
}
/**
* Reverse of Introspector.decapitalize
*/
static String capitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
char chars[] = name.toCharArray();
chars[0] = Character.toUpperCase(chars[0]);
return new String(chars);
}
private boolean ignorable(Method method) {
if (Modifier.isStatic(method.getModifiers()))
return true;
if (!Modifier.isPublic(method.getModifiers())) {
return true;
}
if (method.getDeclaringClass() == Object.class)
return true;
return false;
}
}