/*******************************************************************************
* Copyright (c) 2002-2006 Innoopract Informationssysteme GmbH.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Innoopract Informationssysteme GmbH - initial API and implementation
******************************************************************************/
package com.w4t.ajax;
import java.beans.*;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import org.eclipse.rwt.Adaptable;
import org.eclipse.rwt.internal.events.IEventAdapter;
import org.eclipse.rwt.internal.util.ParamCheck;
import com.w4t.*;
import com.w4t.dhtml.Node;
import com.w4t.dhtml.menustyle.MenuProperties;
import com.w4t.types.WebPropertyBase;
/**
* <p><code>AbstractHashCodeBuilder</code> implementation which uses all public
* read- and writable (bean-style) properties to compute the hash code.</p>
*/
final class DefaultHashCodeBuilder implements HashCodeBuilder {
private static final Object[] NO_ARGS = new Object[ 0 ];
private BeanInfo beanInfo;
private Method[] readMethods;
private Method[] writeMethods;
DefaultHashCodeBuilder( final Class clazz ) {
this.beanInfo = getBeanInfo( clazz );
}
////////////////////////
// AbstractHashCodeBuilder override
public int compute( final HashCodeBuilderSupport support,
final Object object )
{
checkComputeArgument( object );
HashCodeCalculator calculator = new HashCodeCalculator();
// Special handling for non-java-beans style get-property on WebContainer
// and the non-java-beans style area on its WebLayout
if ( object instanceof WebContainer ) {
WebContainer container = ( WebContainer )object;
calculator.append( container );
WebLayout webLayout = container.getWebLayout();
int count = container.getWebComponentCount();
for( int i = 0; i < count; i++ ) {
Object constraint = container.getConstraint( i );
if( constraint != null ) {
Area area = webLayout.getArea( constraint );
if( area == null && constraint instanceof String ) {
constraint = RenderUtil.resolve( ( String )constraint );
}
area = webLayout.getArea( constraint );
if( area != null ) {
HashCodeBuilder builder
= HashCodeBuilderFactory.getBuilder( area.getClass() );
calculator.append( builder.compute( support, area ) );
}
}
}
}
// in case that a listener was added/removed to the WebComponent/
// WebLayout etc. rendering may be necessary
if( object instanceof Adaptable ) {
Adaptable adaptable = ( Adaptable )object;
Class clz = IEventAdapter.class;
IEventAdapter adapter = ( IEventAdapter )adaptable.getAdapter( clz );
if( adapter != null ) {
calculator.append( adapter.getListener() );
}
}
// standard java-beans style properties
initPerfomantMethodCaches();
for( int i = 0; i < readMethods.length; i++ ) {
if( readMethods[ i ] != null && writeMethods[ i ] != null ) {
Object propertyValue = getPropertyValue( object, readMethods[ i ] );
internalCompute( support, calculator, propertyValue );
}
}
return calculator.toHashCode();
}
/////////////////////////////
// Private helper methods
private void initPerfomantMethodCaches() {
PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
if( readMethods == null ) {
readMethods = new Method[ descriptors.length ];
writeMethods = new Method[ descriptors.length ];
for( int i = 0; i < descriptors.length; i++ ) {
readMethods[ i ] = descriptors[ i ].getReadMethod();
writeMethods[ i ] = descriptors[ i ].getWriteMethod();
}
}
}
private static void internalCompute( final HashCodeBuilderSupport support,
final HashCodeCalculator calculator,
final Object value )
{
if( value != null && HashCodeBuilderFactory.isW4TClass( value.getClass() ) )
{
if( !support.isInRecursionList( value ) ) {
if( canCauseRecursion( value ) )
{
support.addToRecursionList( value );
}
HashCodeBuilder nestedBuilder;
nestedBuilder = HashCodeBuilderFactory.getBuilder( value.getClass() );
calculator.append( nestedBuilder.compute( support, value ) );
}
} else if( value instanceof WebPropertyBase
|| value instanceof Style
|| value instanceof MenuProperties )
{
// TODO: [fappel] check how we could use the common super class here
calculator.append( value.toString() );
} else {
calculator.append( value );
}
}
// /////////////
// Private helper methods
/**
* <p>Returns whether the given <code>component</code> can potentially cause
* endless recursion.</p>
*/
private static boolean canCauseRecursion( final Object component ) {
return component instanceof WebContainer
|| component instanceof Decorator
|| component instanceof Node;
}
private Object getPropertyValue( final Object object,
final Method readMethod )
{
try {
// In case we have a package private or protected WebComponent, we
// have to change the access modifier.
if( !readMethod.isAccessible() ) {
readMethod.setAccessible( true );
}
return readMethod.invoke( object, NO_ARGS );
} catch( Exception e ) {
String text = "Failed to obtain value of accessor ''{0}#{1}''.";
Object[] args = new Object[]{
beanInfo.getBeanDescriptor().getBeanClass().getName(),
readMethod.getName()
};
throw new RuntimeException( MessageFormat.format( text, args ), e );
}
}
private void checkComputeArgument( final Object object ) {
Class beanClass = beanInfo.getBeanDescriptor().getBeanClass();
if( !beanClass.isAssignableFrom( object.getClass() ) ) {
String text = "The argument ''object'' must be of type ''{0}''.";
Object[] args = new Object[] { beanClass.getName() };
throw new IllegalArgumentException( MessageFormat.format( text, args ) );
}
}
private static BeanInfo getBeanInfo( final Class clazz ) {
ParamCheck.notNull( clazz, "clazz" );
try {
return Introspector.getBeanInfo( clazz );
} catch( IntrospectionException e ) {
String text
= "Failed to obtain BeanInfo for class \'{0}\' (Reason: {1}).";
Object[] args = new Object[] { clazz.getName(), e.getMessage() };
throw new IllegalArgumentException( MessageFormat.format( text, args ) );
}
}
}