package fr.lteconsulting.hexa.databinding;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
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.properties.Properties;
import fr.lteconsulting.hexa.databinding.propertyadapters.ObjectPropertyAdapter;
/**
* A data binding utility for the support of automatic DTO binding.
*
* @author Arnaud Tournier (c) LTE Consulting - 2015 http://www.lteconsulting.fr
*
*/
public class DTOMapper
{
private static final Logger LOGGER = Logger.getLogger( DTOMapper.class.getName() );
// tries to bind as much fields of source to destination and the other way
// around
// returns mapping resources handle that were created for this mapping
public static Object Map( Object source, Object destination )
{
List<DataBinding> res = new ArrayList<DataBinding>();
LOGGER.fine( "Binding object of class " + getSimpleName( source.getClass() ) + " to another of class " + getSimpleName( destination.getClass() ) );
Clazz<?> sourceClass = ClassInfo.Clazz( source.getClass() );
Clazz<?> destinationClass = ClassInfo.Clazz( destination.getClass() );
// registers all possible bindings...
HashSet<String> bindedNames = new HashSet<String>();
// fields wise...
for( Field field : sourceClass.getAllFields() )
bindedNames.add( field.getName() );
for( Field field : destinationClass.getAllFields() )
bindedNames.add( field.getName() );
// ... and method wise
for( Method method : sourceClass.getMethods() )
{
if( !method.getName().startsWith( "get" ) && !method.getName().startsWith( "set" ) )
continue;
String fieldName = method.getName().substring( 3, 4 ).toLowerCase() + method.getName().substring( 4 );
bindedNames.add( fieldName );
}
for( Method method : destinationClass.getMethods() )
{
if( !method.getName().startsWith( "get" ) && !method.getName().startsWith( "set" ) )
continue;
String fieldName = method.getName().substring( 3, 4 ).toLowerCase() + method.getName().substring( 4 );
bindedNames.add( fieldName );
}
for( String name : bindedNames )
{
boolean srcRead = Properties.hasSomethingToGetField( ClassInfo.Clazz( source.getClass() ), name );
boolean srcWrite = Properties.hasSomethingToSetField( ClassInfo.Clazz( source.getClass() ), name );
boolean destinationRead = Properties.hasSomethingToGetField( ClassInfo.Clazz( destination.getClass() ), name );
boolean destinationWrite = Properties.hasSomethingToSetField( ClassInfo.Clazz( destination.getClass() ), name );
// ensure both have necessary methods or field
if( !srcRead || !destinationWrite )
continue; // bypass
// adjust binding mode according to capabilities
Mode bindingMode = Mode.OneWay;
if( srcWrite && destinationRead )
bindingMode = Mode.TwoWay;
DataAdapterInfo sourceAdapterInfo = createDataAdapter( source, name, null );
if( sourceAdapterInfo == null )
continue;
DataAdapterInfo destinationAdapterInfo = createDataAdapter( destination, name, sourceAdapterInfo.dataType );
if( destinationAdapterInfo == null )
continue;
// bind source, "color" <----> destination, "color.$HasValue"
String symbol = "";
switch( bindingMode )
{
case OneWay:
symbol = "----->";
break;
case TwoWay:
symbol = "<---->";
break;
case OneWayToSource:
symbol = "<-----";
break;
}
LOGGER.fine( "[" + getSimpleName( sourceAdapterInfo.dataType ) + "] " + sourceAdapterInfo.debugString + symbol + destinationAdapterInfo.debugString );
DataBinding binding = new DataBinding( sourceAdapterInfo.adapter, destinationAdapterInfo.adapter, bindingMode, destinationAdapterInfo.converter, null );
binding.activate();
res.add( binding );
}
return res;
}
public static void freeMapping( Object mappingResourceHandle )
{
@SuppressWarnings( "unchecked" )
List<DataBinding> bindings = (List<DataBinding>) mappingResourceHandle;
for( DataBinding binding : bindings )
binding.terminate();
bindings.clear();
}
static String getSimpleName( Class<?> cls )
{
String[] path = cls.getName().split( "\\." );
return path[path.length - 1];
}
static DataAdapterInfo createDataAdapter( Object context, String property, Class<?> srcPptyType )
{
DataAdapterInfo res = new DataAdapterInfo();
res.dataType = Properties.getPropertyType( ClassInfo.Clazz( context.getClass() ), property );
res.debugString = getSimpleName( context.getClass() ) + ", ";
// test to see if the asked property is in fact a HasValue widget
Object widget = Properties.getValue( context, property );
if( PlatformSpecificProvider.get().isSpecificDataAdapter( widget ) )
{
PlatformSpecificProvider.get().fillSpecificDataAdapter( widget, context, property, srcPptyType, res );
}
else
{
res.debugString += "\"" + property + "\"";
res.adapter = new ObjectPropertyAdapter( context, property );
}
if( res.adapter == null )
return null;
return res;
}
}