/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program 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 Lesser General Public License for more details.
*
* Copyright (c) 2001 - 2013 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved.
*/
package org.pentaho.reporting.engine.classic.core.util.beans;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.beans.BeanInfo;
import java.beans.IndexedPropertyDescriptor;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
/**
* The BeanUtility class enables access to bean properties using the reflection API.
*
* @author Thomas Morgner
*/
public final class BeanUtility {
private static final Log logger = LogFactory.getLog( BeanUtility.class );
/**
* A property specification parses a compound property name into segments and allows access to the next property.
*/
private static class PropertySpecification {
/**
* The raw value of the property name.
*/
private String raw;
/**
* The next direct property that should be accessed.
*/
private String name;
/**
* The index, if the named property points to an indexed property.
*/
private String index;
/**
* Creates a new PropertySpecification object for the given property string.
*
* @param raw
* the property string, posssibly with index specifications.
*/
private PropertySpecification( final String raw ) {
this.raw = raw;
this.name = getNormalizedName( raw );
this.index = getIndex( raw );
}
/**
* Returns the name of the property without any index information.
*
* @param property
* the raw name
* @return the normalized name.
*/
private String getNormalizedName( final String property ) {
final int idx = property.indexOf( '[' );
if ( idx < 0 ) {
return property;
}
return property.substring( 0, idx );
}
/**
* Extracts the first index from the given raw property.
*
* @param property
* the raw name
* @return the index as String.
*/
private String getIndex( final String property ) {
final int idx = property.indexOf( '[' );
if ( idx < 0 ) {
return null;
}
final int end = property.indexOf( ']', idx + 1 );
if ( end < 0 ) {
return null;
}
return property.substring( idx + 1, end );
}
public String getRaw() {
return raw;
}
public String getName() {
return name;
}
public String getIndex() {
return index;
}
public String toString() {
final StringBuilder b = new StringBuilder( "PropertySpecification={" );
b.append( "raw=" );
b.append( raw );
b.append( '}' );
return b.toString();
}
}
private BeanInfo beanInfo;
private Object bean;
private HashMap<String, PropertyDescriptor> properties;
public BeanUtility( final Object o ) throws IntrospectionException {
beanInfo = Introspector.getBeanInfo( o.getClass() );
bean = o;
properties = new HashMap<String, PropertyDescriptor>();
final PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for ( int i = 0; i < propertyDescriptors.length; i++ ) {
properties.put( propertyDescriptors[i].getName(), propertyDescriptors[i] );
}
}
public void reconfigure( final Object o ) throws IntrospectionException {
if ( bean.getClass().equals( o.getClass() ) ) {
bean = o;
} else {
beanInfo = Introspector.getBeanInfo( o.getClass() );
bean = o;
properties.clear();
final PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for ( int i = 0; i < propertyDescriptors.length; i++ ) {
properties.put( propertyDescriptors[i].getName(), propertyDescriptors[i] );
}
}
}
public PropertyDescriptor[] getPropertyInfos() {
return beanInfo.getPropertyDescriptors();
}
public Object getProperty( final String name ) throws BeanException {
return getPropertyForSpecification( new PropertySpecification( name ) );
}
private Object getPropertyForSpecification( final PropertySpecification name ) throws BeanException {
final PropertyDescriptor pd = properties.get( name.getName() );
if ( pd == null ) {
throw new BeanException( "No such property:" + name );
}
if ( pd instanceof IndexedPropertyDescriptor && name.getIndex() != null ) {
final IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor) pd;
final Method readMethod = ipd.getIndexedReadMethod();
if ( readMethod == null ) {
throw new BeanException( "Property is not readable: " + name );
}
try {
return readMethod.invoke( bean, new Integer( name.getIndex() ) );
} catch ( Exception e ) {
throw BeanException.getInstance( "InvokationError", e );
}
} else {
final Method readMethod = pd.getReadMethod();
if ( readMethod == null ) {
throw BeanException.getInstance( "Property is not readable: " + name, null );
}
if ( name.getIndex() != null ) {
// handle access to array-only properties ..
try {
// System.out.println(readMethod);
final Object value = readMethod.invoke( bean );
// we have (possibly) an array.
if ( value == null ) {
// noinspection ThrowCaughtLocally
throw new IndexOutOfBoundsException( "No such index, property is null" );
}
if ( value.getClass().isArray() == false ) {
// noinspection ThrowCaughtLocally
throw new BeanException( "The property contains no array." );
}
final int index = Integer.parseInt( name.getIndex() );
return Array.get( value, index );
} catch ( BeanException be ) {
throw be;
} catch ( IndexOutOfBoundsException iob ) {
throw iob;
} catch ( Exception e ) {
throw new BeanException( "Failed to read indexed property." );
}
}
try {
return readMethod.invoke( bean );
} catch ( Exception e ) {
throw BeanException.getInstance( "InvokationError", e );
}
}
}
public String getPropertyAsString( final String name ) throws BeanException {
final PropertySpecification ps = new PropertySpecification( name );
final PropertyDescriptor pd = properties.get( ps.getName() );
if ( pd == null ) {
throw new BeanException( "No such property:" + name );
}
final Object o = getPropertyForSpecification( ps );
if ( o == null ) {
return null;
}
final ValueConverter vc = ConverterRegistry.getInstance().getValueConverter( o.getClass() );
if ( vc == null ) {
throw new BeanException( "Unable to handle property of type " + o.getClass().getName() );
}
return vc.toAttributeValue( o );
}
public void setProperty( final String name, final Object o ) throws BeanException {
if ( name == null ) {
throw new NullPointerException( "Name must not be null" );
}
setProperty( new PropertySpecification( name ), o );
}
private void setProperty( final PropertySpecification name, final Object o ) throws BeanException {
final PropertyDescriptor pd = properties.get( name.getName() );
if ( pd == null ) {
throw new BeanException( "No such property:" + name );
}
if ( pd instanceof IndexedPropertyDescriptor && name.getIndex() != null ) {
final IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor) pd;
final Method writeMethod = ipd.getIndexedWriteMethod();
if ( writeMethod != null ) {
try {
writeMethod.invoke( bean, new Integer( name.getIndex() ), o );
} catch ( Exception e ) {
throw BeanException.getInstance( "InvokationError", e );
}
// we've done the job ...
return;
}
}
final Method writeMethod = pd.getWriteMethod();
if ( writeMethod == null ) {
throw BeanException.getInstance( "Property is not writeable: " + name, null );
}
if ( name.getIndex() != null ) {
// this is a indexed access, but no indexWrite method was found ...
updateArrayProperty( pd, name, o );
} else {
try {
writeMethod.invoke( bean, o );
} catch ( Exception e ) {
throw BeanException.getInstance( "InvokationError on property '" + name + "' on bean type " + bean.getClass(),
e );
}
}
}
private void updateArrayProperty( final PropertyDescriptor pd, final PropertySpecification name, final Object o )
throws BeanException {
final Method readMethod = pd.getReadMethod();
if ( readMethod == null ) {
throw new BeanException( "Property is not readable, cannot perform array update: " + name );
}
try {
// System.out.println(readMethod);
final Object value = readMethod.invoke( bean );
// we have (possibly) an array.
final int index = Integer.parseInt( name.getIndex() );
final Object array = validateArray( BeanUtility.getPropertyType( pd ), value, index );
Array.set( array, index, o );
final Method writeMethod = pd.getWriteMethod();
writeMethod.invoke( bean, array );
} catch ( BeanException e ) {
throw e;
} catch ( Exception e ) {
BeanUtility.logger.warn( "Failed to read property, cannot perform array update: " + name, e );
throw new BeanException( "Failed to read property, cannot perform array update: " + name );
}
}
/**
* @noinspection SuspiciousSystemArraycopy
*/
private Object validateArray( final Class propertyType, final Object o, final int minArrayIndexValue )
throws BeanException {
if ( propertyType.isArray() == false ) {
throw new BeanException( "The property's value is no array." );
}
if ( o == null ) {
return Array.newInstance( propertyType.getComponentType(), minArrayIndexValue + 1 );
}
if ( o.getClass().isArray() == false ) {
throw new BeanException( "The property's value is no array." );
}
final int length = Array.getLength( o );
if ( length > minArrayIndexValue ) {
return o;
}
// we have to copy the array ..
final Object retval = Array.newInstance( o.getClass().getComponentType(), minArrayIndexValue + 1 );
System.arraycopy( o, 0, retval, 0, length );
return o;
}
public void setPropertyAsString( final String name, final String txt ) throws BeanException {
if ( name == null ) {
throw new NullPointerException( "Name must not be null" );
}
if ( txt == null ) {
throw new NullPointerException( "Text must not be null" );
}
final PropertySpecification ps = new PropertySpecification( name );
final PropertyDescriptor pd = properties.get( ps.getName() );
if ( pd == null ) {
throw new BeanException( bean.getClass() + ": No such property:" + name );
}
setPropertyAsString( name, BeanUtility.getPropertyType( pd ), txt );
}
public Class getPropertyType( final String name ) throws BeanException {
if ( name == null ) {
throw new NullPointerException( "Name must not be null" );
}
final PropertySpecification ps = new PropertySpecification( name );
final PropertyDescriptor pd = properties.get( ps.getName() );
if ( pd == null ) {
throw new BeanException( "No such property:" + name );
}
return BeanUtility.getPropertyType( pd );
}
public static Class getPropertyType( final PropertyDescriptor pd ) throws BeanException {
final Class typeFromDescriptor = pd.getPropertyType();
if ( typeFromDescriptor != null ) {
return typeFromDescriptor;
}
if ( pd instanceof IndexedPropertyDescriptor ) {
final IndexedPropertyDescriptor idx = (IndexedPropertyDescriptor) pd;
return idx.getIndexedPropertyType();
}
throw new BeanException( "Unable to determine the property type." );
}
public void setPropertyAsString( final String name, final Class type, final String txt ) throws BeanException {
if ( name == null ) {
throw new NullPointerException( "Name must not be null" );
}
if ( type == null ) {
throw new NullPointerException( "Type must not be null" );
}
if ( txt == null ) {
throw new NullPointerException( "Text must not be null" );
}
final PropertySpecification ps = new PropertySpecification( name );
final ValueConverter vc;
if ( ps.getIndex() != null && type.isArray() ) {
vc = ConverterRegistry.getInstance().getValueConverter( type.getComponentType() );
} else {
vc = ConverterRegistry.getInstance().getValueConverter( type );
}
if ( vc == null ) {
throw new BeanException( "Unable to handle '" + type + "' for property '" + name + '\'' );
}
final Object o = vc.toPropertyValue( txt );
setProperty( ps, o );
}
public String[] getProperties() throws BeanException {
final ArrayList<String> propertyNames = new ArrayList<String>();
final PropertyDescriptor[] pd = getPropertyInfos();
for ( int i = 0; i < pd.length; i++ ) {
final PropertyDescriptor property = pd[i];
if ( property.isHidden() ) {
continue;
}
if ( property.getReadMethod() == null || property.getWriteMethod() == null ) {
// it will make no sense to write a property now, that
// we can't read in later...
continue;
}
if ( BeanUtility.getPropertyType( property ).isArray() ) {
final int max = findMaximumIndex( property );
for ( int idx = 0; idx < max; idx++ ) {
propertyNames.add( property.getName() + '[' + idx + ']' );
}
} else {
propertyNames.add( property.getName() );
}
}
return propertyNames.toArray( new String[propertyNames.size()] );
}
private int findMaximumIndex( final PropertyDescriptor id ) {
try {
final Object o = getPropertyForSpecification( new PropertySpecification( id.getName() ) );
return Array.getLength( o );
} catch ( Exception e ) {
// ignore, we run 'til we encounter an index out of bounds Ex.
}
return 0;
}
public static boolean isSameType( final Class declared, final Class object ) {
if ( declared.equals( object ) ) {
return true;
}
if ( Float.TYPE.equals( declared ) && Float.class.equals( object ) ) {
return true;
}
if ( Double.TYPE.equals( declared ) && Double.class.equals( object ) ) {
return true;
}
if ( Byte.TYPE.equals( declared ) && Byte.class.equals( object ) ) {
return true;
}
if ( Character.TYPE.equals( declared ) && Character.class.equals( object ) ) {
return true;
}
if ( Short.TYPE.equals( declared ) && Short.class.equals( object ) ) {
return true;
}
if ( Integer.TYPE.equals( declared ) && Integer.class.equals( object ) ) {
return true;
}
if ( Long.TYPE.equals( declared ) && Long.class.equals( object ) ) {
return true;
}
return false;
}
}