/*
* This file is part of the HyperGraphDB source distribution. This is copyrighted
* software. For permitted uses, licensing options and redistribution, please see
* the LicensingInformation file at the root level of the distribution.
*
* Copyright (c) 2005-2010 Kobrix Software, Inc. All rights reserved.
*/
package org.hypergraphdb.type;
import com.googlecode.openbeans.Introspector;
import com.googlecode.openbeans.BeanInfo;
import com.googlecode.openbeans.PropertyDescriptor;
import com.googlecode.openbeans.IndexedPropertyDescriptor;
import com.googlecode.openbeans.IntrospectionException;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.hypergraphdb.HGException;
/**
* This is a utility class to handle bean introspection. All functions may throw
* a <code>HGException</code> which will wrap the underlying bean introspection
* and reflection (for set and get) exceptions.
*
*/
public class BonesOfBeans
{
// Cache bean property descriptors as soon as they are fetched. The cache is
// only
// being added, never freed. The assumption is number of bean classes used
// is finite and not very big. It is not very hard to keep only a constant
// max number of cached property descriptors, but then we will have to
// synchronize the cache and THAT will slow things down...
// static private HashMap<Class, HashMap<String, PropertyDescriptor>> cache
// =
// new HashMap<Class, HashMap<String, PropertyDescriptor>>();
private static Map<String, PropertyDescriptor> getpropmap(Class<?> clazz)
{
return getpropmap(clazz, false);
}
private static Map<String, PropertyDescriptor> getpropmap(Class<?> clazz,
boolean incl_cls)
{
HashMap<String, PropertyDescriptor> propmap = null; // cache.get(clazz);
if (propmap == null)
{
try
{
BeanInfo bean_info = Introspector.getBeanInfo(clazz);
propmap = new HashMap<String, PropertyDescriptor>();
PropertyDescriptor beanprops[] = bean_info
.getPropertyDescriptors();
for (int i = 0; i < beanprops.length; i++)
{
// filter the Class property which is not used
if (!incl_cls && "class".equals(beanprops[i].getName()))
continue;
propmap.put(beanprops[i].getName(), beanprops[i]);
}
// cache.put(clazz, propmap);
} catch (IntrospectionException ex)
{
throw new HGException("The bean " + clazz.getName()
+ " doesn't want us to introspect it: " + ex.toString());
}
}
return propmap;
}
private static PropertyDescriptor getorthrow_propdesc(Object bean,
String propname, int index)
{
PropertyDescriptor desc = getPropertyDescriptor(bean, propname);
if (desc == null)
throw new HGException("Property " + propname
+ " could not be found in bean "
+ bean.getClass().getName());
else if (index >= 0 && !(desc instanceof IndexedPropertyDescriptor))
throw new HGException("Property " + propname
+ " is not an indexed property in bean "
+ bean.getClass().getName());
else
return desc;
}
/**
* @param beanClass
* The bean class whose property descriptors are desired. Cannot
* be <code>null</code>.
*
* @return a <code>Map</code> of all property descriptors in the bean. The
* map elements are keyed by property name.
*/
public static Map<String, PropertyDescriptor> getAllPropertyDescriptors(
Class<?> beanClass)
{
return getpropmap(beanClass);
}
public static Map<String, PropertyDescriptor> getAllPropertyDescriptorsEx(
Class<?> beanClass)
{
return getpropmap(beanClass, true);
}
/**
* @param bean
* The bean whose property descriptors are desired. Cannot be
* <code>null</code>
*
* @return a <code>Map</code> of all property descriptors in the bean. The
* map elements are keyed by property name.
*/
public static Map<String, PropertyDescriptor> getAllPropertyDescriptors(
Object bean)
{
return getpropmap(bean.getClass());
}
/**
* @param bean
* Any object conforming to the Java Beans Specification
* @param propname
* The name a bean property.
* @return The <code>PropertyDescriptor</code> for that property as returned
* from the <code>BeanInfo</code> associated with that bean. If
* there is no property with that name in the bean,
* <code>null</code> is returned.
*/
public static PropertyDescriptor getPropertyDescriptor(Object bean,
String propname)
{
return getPropertyDescriptor(bean.getClass(), propname);
}
public static PropertyDescriptor getPropertyDescriptor(Class<?> clazz, String propname)
{
return (PropertyDescriptor) getpropmap(clazz).get(propname);
}
/**
* Same as <code>getProperty(Object, String, int)</code>, but use a supplied
* <code>PropertyDescriptor</code> instead of the property name.
*/
public static Object getProperty(Object bean, PropertyDescriptor prop,
int index)
{
try
{
Method method = null;
if (prop instanceof IndexedPropertyDescriptor)
if (index < 0)
method = prop.getReadMethod();
else
method = ((IndexedPropertyDescriptor) prop)
.getIndexedReadMethod();
else if (index >= 0)
throw new java.lang.UnsupportedOperationException("Property "
+ prop.getName() + " of bean "
+ bean.getClass().getName()
+ " is not an indexed property.");
else
method = prop.getReadMethod();
if (method == null)
throw new java.lang.UnsupportedOperationException("Property "
+ prop.getName() + " of bean "
+ bean.getClass().getName() + " is not readable.");
if (index < 0)
return method.invoke(bean, (Object[]) null);
else
return method.invoke(bean, new Object[] { new Integer(index) });
} catch (IllegalAccessException ex)
{
throw new HGException("Illegal access to property "
+ prop.getName() + " of bean " + bean.getClass().getName()
+ " it is probably a private property: " + ex.toString());
} catch (InvocationTargetException ex)
{
throw new HGException("InvocationTargetException while accessing "
+ "property " + prop.getName() + " of bean "
+ bean.getClass().getName() + ex.toString()
+ ", target exception is "
+ ex.getTargetException().toString(), ex);
}
}
/**
* <p>
* Return a Java Map of all read-write properties of the specified Java bean.
* </p>
*/
public static Map<String, Object> getPropertiesAsMap(Object bean)
{
HashMap<String, Object> m = new HashMap<String, Object>();
Map<String, PropertyDescriptor> dm = getAllPropertyDescriptors(bean);
for (Map.Entry<String, PropertyDescriptor> e : dm.entrySet())
if (e.getValue().getWriteMethod() != null && e.getValue().getReadMethod() != null)
m.put(e.getKey(), getProperty(bean, e.getValue()));
return m;
}
public static void setPropertiesFromMap(Object bean, Map<String, Object> properties)
{
HashMap<String, Object> m = new HashMap<String, Object>();
Map<String, PropertyDescriptor> dm = getAllPropertyDescriptors(bean);
for (Map.Entry<String, PropertyDescriptor> e : dm.entrySet())
if (e.getValue().getWriteMethod() != null && e.getValue().getReadMethod() != null)
setProperty(bean, e.getKey(), properties.get(e.getKey()));
}
/**
* Same as <code>getProperty(Object, String)</code>, but use a supplied
* <code>PropertyDescriptor</code> instead of the property name.
*/
public static Object getProperty(Object bean, PropertyDescriptor prop)
{
return getProperty(bean, prop, -1);
}
/**
* Get the value of a particular (indexed) bean property.
*
* @param bean
* An object conforming to the Java Beans Specification.
* @param propname
* The name of the desired bean property.
* @param index
* The <code>integer</code> index within the property. If the
* value is negative, the property itself is returned. That is,
* for example, if the property is <code>String [] values</code>
* and if <code>index</code> is < 0, <code>values</code> will be
* returned, otherwise <code>values[index]</code> will be
* returned.
* @return The value of the property at the specified index.
*
* @throws HGException
* if the property cannot be found in the bean or if
* <code>index >= 0 </code> and the property is not an indexed
* property.
*/
public static Object getProperty(Object bean, String propname, int index)
{
return getProperty(bean, getorthrow_propdesc(bean, propname, index),
index);
}
/**
* Get the value of a particular bean property.
*
* @param bean
* An object conforming to the Java Beans Specification.
* @param propname
* The name of the desired bean property.
* @return The value of the specified property.
*
* @throws HGException
* if the property cannot be found in the bean or if
* <code>index >= 0 </code> and the property is not an indexed
* property.
*/
public static Object getProperty(Object bean, String propname)
{
return getProperty(bean, propname, -1);
}
/**
* Same as <code>setProperty(Object, String, int, Object)</code>, but use a
* supplied <code>PropertyDescriptor</code> instead of the property name.
*/
public static void setProperty(Object bean, PropertyDescriptor prop,
int index, Object newvalue)
{
try
{
Method method = null;
if (prop instanceof IndexedPropertyDescriptor)
if (index < 0)
method = prop.getWriteMethod();
else
method = ((IndexedPropertyDescriptor) prop)
.getIndexedWriteMethod();
else if (index >= 0)
throw new java.lang.UnsupportedOperationException("Property "
+ prop.getName() + " of bean "
+ bean.getClass().getName()
+ " is not an indexed property.");
else
method = prop.getWriteMethod();
if (method == null)
throw new java.lang.UnsupportedOperationException("Property "
+ prop.getName() + " of bean "
+ bean.getClass().getName()
+ " is not mutable (writeable).");
if (index < 0)
method.invoke(bean, new Object[] { newvalue });
else
method.invoke(bean,
new Object[] { new Integer(index), newvalue });
} catch (IllegalAccessException ex)
{
throw new HGException("Illegal access to property "
+ prop.getName() + " of bean " + bean.getClass().getName()
+ " it is probably a private property: " + ex.toString());
} catch (InvocationTargetException ex)
{
throw new HGException("InvocationTargetException while accessing "
+ "property " + prop.getName() + " of bean "
+ bean.getClass().getName() + ex.toString()
+ ", taget exception is "
+ ex.getTargetException().toString(), ex);
}
}
/**
* Same as <code>setProperty(Object, String, Object)</code>, but use a
* supplied <code>PropertyDescriptor</code> instead of the property name.
*/
public static void setProperty(Object bean, PropertyDescriptor prop,
Object newvalue)
{
setProperty(bean, prop, -1, newvalue);
}
/**
* Assign a new value to a specified bean property.
*
* @param bean
* An object conforming to the Java Beans Specification.
* @param propname
* The name of the desired bean property.
* @param index
* The <code>integer</code> index within the property. If the
* value is negative, the property itself will be modified. That
* is, for example, if the property is
* <code>String [] values</code> and if <code>index</code> is <
* 0, <code>values</code> will be modified, otherwise
* <code>values[index]</code> will be modified.
* @param newvalue
* The value to assign to the specified property.
*
* @throws HGException
* if the property cannot be found in the bean or if
* <code>index >= 0 </code> and the property is not an indexed
* property.
*/
public static void setProperty(Object bean, String propname, int index,
Object newvalue)
{
setProperty(bean, getorthrow_propdesc(bean, propname, index), index,
newvalue);
}
/**
* Assign a new value to a specified bean property.
*
* @param bean
* An object conforming to the Java Beans Specification.
* @param propname
* The name of the desired bean property.
* @param newvalue
* The value to assign to the specified property.
*
* @throws HGException
* if the property cannot be found in the bean or if
* <code>index >= 0 </code> and the property is not an indexed
* property.
*/
public static void setProperty(Object bean, String propname, Object newvalue)
{
setProperty(bean, propname, -1, newvalue);
}
/**
* Construct a new instance of a bean.
*
* @param classname
* The fully qualified class name of the bean to construct.
* @return The newly constructed bean.
* @throws HGException
* is there's any error during the construction process, such as
* ClassNotFoundException etc.
*/
public static Object makeBean(String classname)
{
try
{
Class<?> clazz = Class.forName(classname);
return clazz.newInstance();
} catch (Exception ex)
{
throw new HGException("Could not construct a bean DataObject "
+ "from " + classname, ex);
}
}
/**
* @param aClass
* a Class
* @return the class's primitive equivalent, if aClass is a primitive
* wrapper. If aClass is primitive, returns aClass. Otherwise,
* returns null.
*/
public static Class<?> primitiveEquivalentOf(Class<?> aClass)
{
return aClass.isPrimitive() ? aClass : (Class<?>) objectToPrimitiveMap
.get(aClass);
}
public static Class<?> wrapperEquivalentOf(Class<?> aClass)
{
Iterator<Map.Entry<Class<?>, Class<?>>> it = objectToPrimitiveMap
.entrySet().iterator();
while (it.hasNext())
{
Map.Entry<Class<?>, Class<?>> entry = it.next();
if (aClass.equals(entry.getValue()))
return (Class<?>) entry.getKey();
}
return aClass;
}
/**
* Mapping from primitive wrapper Classes to their corresponding primitive
* Classes.
*/
private static final Map<Class<?>, Class<?>> objectToPrimitiveMap = new HashMap<Class<?>, Class<?>>(
13);
static
{
objectToPrimitiveMap.put(Boolean.class, Boolean.TYPE);
objectToPrimitiveMap.put(Byte.class, Byte.TYPE);
objectToPrimitiveMap.put(Character.class, Character.TYPE);
objectToPrimitiveMap.put(Double.class, Double.TYPE);
objectToPrimitiveMap.put(Float.class, Float.TYPE);
objectToPrimitiveMap.put(Integer.class, Integer.TYPE);
objectToPrimitiveMap.put(Long.class, Long.TYPE);
objectToPrimitiveMap.put(Short.class, Short.TYPE);
}
}