/* * 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; } }