package fr.lteconsulting.hexa.databinding.properties;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import fr.lteconsulting.hexa.classinfo.ClassInfo;
import fr.lteconsulting.hexa.classinfo.Clazz;
import fr.lteconsulting.hexa.classinfo.Field;
import fr.lteconsulting.hexa.classinfo.Method;
import fr.lteconsulting.hexa.databinding.PlatformSpecific;
import fr.lteconsulting.hexa.databinding.PlatformSpecificProvider;
import fr.lteconsulting.hexa.databinding.propertyadapters.CompositePropertyAdapter;
import fr.lteconsulting.hexa.databinding.tools.Property;
class PropertyValues
{
private final static Logger LOGGER = Logger.getLogger( PropertyValues.class.getName() );
private final static PlatformSpecific propertyBagAccess = PlatformSpecificProvider.get();
/**
* Returns the class of the property
*/
Class<?> getPropertyType( Clazz<?> clazz, String name )
{
ClassInfoCache cache = retrieveCache( clazz );
Class<?> res = cache.getPropertyType( name );
if( res != null )
return res;
Class<?> getterType = getGetterPropertyType( clazz, name );
if( getterType == null )
return null;
Class<?> setterType = getSetterPropertyType( clazz, name );
if( setterType == null )
return null;
if( getterType != setterType )
return null;
cache.setPropertyType( name, getterType );
return getterType;
}
/**
* Whether a getter or a field is available with that name
*/
boolean hasSomethingToGetField( Clazz<?> clazz, String name )
{
return getGetterPropertyType( clazz, name ) != null;
}
/**
* Return the property getter type
*/
Class<?> getGetterPropertyType( Clazz<?> clazz, String name )
{
String getterName = "get" + capitalizeFirstLetter( name );
Method getter = clazz.getMethod( getterName );
if( getter != null )
return getter.getReturnType();
// try direct field access
Field field = clazz.getAllField( name );
if( field != null )
return field.getType();
return null;
}
/**
* Gets the property's value from an object
*
* @param object
* The object
* @param name
* Property name
*/
<T> T getValue( Object object, String name )
{
T result = getPropertyImpl( object, name );
if( result instanceof Property )
{
@SuppressWarnings( "unchecked" )
Property<T> property = ((Property<T>) result);
return property.getValue();
}
return result;
}
/**
* Whether there is a setter or a field to write this property
*/
boolean hasSomethingToSetField( Clazz<?> clazz, String name )
{
return getSetterPropertyType( clazz, name ) != null;
}
private final static Map<Integer, ClassInfoCache> classInfoCache = new HashMap<>();
private ClassInfoCache retrieveCache( Clazz<?> clazz )
{
Integer key = System.identityHashCode( clazz );
ClassInfoCache res = classInfoCache.get( key );
if( res == null )
{
res = new ClassInfoCache();
classInfoCache.put( key, res );
}
return res;
}
private static class ClassInfoCache
{
Map<String, Class<?>> propertyTypes = new HashMap<>();
Map<String, Class<?>> setterTypes = new HashMap<>();
public Class<?> getSetterType( String name )
{
return setterTypes.get( name );
}
public void setSetterType( String name, Class<?> res )
{
setterTypes.put( name, res );
}
public Class<?> getPropertyType( String name )
{
return propertyTypes.get( name );
}
public void setPropertyType( String name, Class<?> classs )
{
propertyTypes.put( name, classs );
}
}
/**
* Returns the class of the setter property. It can be this of the setter or
* of the field
*/
Class<?> getSetterPropertyType( Clazz<?> clazz, String name )
{
ClassInfoCache cache = retrieveCache( clazz );
Class<?> res = cache.getSetterType( name );
if( res != null )
return res;
String setterName = "set" + capitalizeFirstLetter( name );
Method setter = clazz.getMethod( setterName );
if( setter != null && setter.getParameterTypes().size() == 1 )
{
res = setter.getParameterTypes().get( 0 );
}
else
{
Field field = clazz.getAllField( name );
if( field != null )
res = field.getType();
}
if( res != null )
cache.setSetterType( name, res );
return res;
}
/**
* Sets a value on an object's property
*
* @param object
* the object on which the property is set
* @param propertyName
* the name of the property value to be set
* @param value
* the new value of the property
*/
boolean setValue( Object object, String propertyName, Object value )
{
Clazz<?> s = ClassInfo.Clazz( object.getClass() );
if( Property.class == getPropertyType( s, propertyName ) )
{
Property<Object> property = getPropertyImpl( object, propertyName );
if( property != null )
{
property.setValue( value );
return true;
}
return false;
}
return setPropertyImpl( s, object, propertyName, value );
}
/**
* Gets a dynamic property value on an object
*
* @param object
* the object from which one wants to get the property value
* @param propertyName
* the property name
*/
<T> T getObjectDynamicProperty( Object object, String propertyName )
{
DynamicPropertyBag bag = propertyBagAccess.getObjectDynamicPropertyBag( object );
if( bag == null )
return null;
@SuppressWarnings( "unchecked" )
T result = (T) bag.get( propertyName );
return result;
}
/**
* Whether a dynamic property value has already been set on this object
*/
boolean hasObjectDynamicProperty( Object object, String propertyName )
{
DynamicPropertyBag bag = propertyBagAccess.getObjectDynamicPropertyBag( object );
return bag != null && bag.contains( propertyName );
}
/**
* Sets a dynamic property value on an object.
*/
void setObjectDynamicProperty( Object object, String propertyName, Object value )
{
DynamicPropertyBag bag = propertyBagAccess.getObjectDynamicPropertyBag( object );
if( bag == null )
{
bag = new DynamicPropertyBag();
propertyBagAccess.setObjectDynamicPropertyBag( object, bag );
}
bag.set( propertyName, value );
Properties.notify( object, propertyName );
}
private <T> T getPropertyImpl( Object object, String name )
{
if( PlatformSpecificProvider.get().isBindingToken( name ) )
{
return PlatformSpecificProvider.get().getBindingValue( object, name );
}
if( name.equals( CompositePropertyAdapter.DTOMAP_TOKEN ) )
throw new RuntimeException( "Property of type $DTOMap cannot be readden !" );
// if has dynamic-property, return it !
if( hasObjectDynamicProperty( object, name ) )
{
LOGGER.fine( "'" + name + "' read dynamic property on object " + object );
return getObjectDynamicProperty( object, name );
}
Clazz<?> s = ClassInfo.Clazz( object.getClass() );
String getterName = "get" + capitalizeFirstLetter( name );
Method getter = s.getMethod( getterName );
if( getter != null )
{
try
{
@SuppressWarnings( "unchecked" )
T result = (T) getter.invoke( object );
return result;
}
catch( Exception e )
{
throw new RuntimeException( "ObjectAdapter [object]." + object.getClass().getName() + "." + getterName + "() : getter call throwed an exception. See cause.", e );
}
}
// try direct field access
Field field = s.getAllField( name );
if( field != null )
return field.getValue( object );
// Maybe a dynamic property will be set later on
LOGGER.warning( "DataBinding: Warning: assuming that the object would " + "in the future have a dynamic property set / Maybe have an opt-in " + "option on the Binding to clarify things" );
return null;
}
private boolean setPropertyImpl( Clazz<?> s, Object object, String name, Object value )
{
if( PlatformSpecificProvider.get().isBindingToken( name ) )
return PlatformSpecificProvider.get().setBindingValue( object, name, value );
String setterName = "set" + capitalizeFirstLetter( name );
Method setter = s.getMethod( setterName );
if( setter != null )
{
setter.invoke( object, value );
return true;
}
Field field = s.getAllField( name );
if( field != null )
{
field.setValue( object, value );
Properties.notify( object, name );
return true;
}
if( !hasObjectDynamicProperty( object, name ) )
LOGGER.warning( "'" + name + "' write dynamic property on object " + object.getClass().getName() + " with value " + value + " WARNING : THAT MEANS THERE IS NO GETTER/SETTER/FIELD FOR THAT CLASS ! PLEASE CHECK THAT IT IS REALLY INTENTIONAL !" );
setObjectDynamicProperty( object, name, value );
return false;
}
private final static HashMap<String, String> caps = new HashMap<>();
private String capitalizeFirstLetter( String s )
{
String res = caps.get( s );
if( res == null )
{
res = Character.toUpperCase( s.charAt( 0 ) ) + s.substring( 1 );
caps.put( s, res );
}
return res;
}
}