/******************************************************************************
* Copyright (c) 2016 Oracle
* 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:
* Konstantin Komissarchik - initial implementation and ongoing maintenance
******************************************************************************/
package org.eclipse.sapphire.modeling.el;
import java.util.List;
import org.eclipse.sapphire.Event;
import org.eclipse.sapphire.Listener;
import org.eclipse.sapphire.LocalizableText;
import org.eclipse.sapphire.Text;
import org.eclipse.sapphire.modeling.internal.SapphireModelingExtensionSystem;
import org.eclipse.sapphire.util.ListFactory;
/**
* @author <a href="mailto:konstantin.komissarchik@oracle.com">Konstantin Komissarchik</a>
*/
public final class DeferredFunction extends Function
{
@Text( "Function {0} with {1} parameters is undefined." )
private static LocalizableText undefinedFunctionMessageExt;
@Text( "Function {0} with one parameter is undefined." )
private static LocalizableText undefinedFunctionMessageExt1;
@Text( "Function {0}( {1} ) is undefined." )
private static LocalizableText undefinedFunctionMessage;
static
{
LocalizableText.init( DeferredFunction.class );
}
private final String name;
public DeferredFunction( final String name )
{
this.name = name;
}
public static DeferredFunction create( final String name,
final List<Function> operands )
{
final DeferredFunction function = new DeferredFunction( name );
function.init( operands );
return function;
}
@Override
public String name()
{
return this.name;
}
@Override
public FunctionResult evaluate( final FunctionContext context )
{
final int arity = operands().size();
final List<Function> functions = SapphireModelingExtensionSystem.functions( this.name, arity );
if( functions.isEmpty() )
{
if( arity == 1 )
{
throw new FunctionException( undefinedFunctionMessageExt1.format( this.name ) );
}
else
{
throw new FunctionException( undefinedFunctionMessageExt.format( this.name, arity ) );
}
}
final ListFactory<Function> typedFunctionsListFactory = ListFactory.start();
Function genericFunction = null;
for( Function f : functions )
{
f.init( operands() );
if( f.signature() == null )
{
if( genericFunction == null )
{
genericFunction = f;
}
}
else
{
typedFunctionsListFactory.add( f );
}
}
final List<Function> typedFunctions = typedFunctionsListFactory.result();
final int typedFunctionsCount = typedFunctions.size();
final Function genericFunctionFinal = genericFunction;
if( genericFunction != null && typedFunctionsCount == 0 )
{
return genericFunction.evaluate( context );
}
else
{
return new FunctionResult( this, context )
{
private Function baseFunction;
private FunctionResult baseFunctionResult;
private Listener listener;
@Override
protected Object evaluate()
{
final Function function = findFunction();
if( function == null )
{
if( this.baseFunctionResult != null )
{
this.baseFunctionResult.dispose();
}
this.baseFunction = null;
this.baseFunctionResult = null;
final StringBuilder buf = new StringBuilder();
for( FunctionResult operand : operands() )
{
if( buf.length() > 0 )
{
buf.append( ", " );
}
final Object value = operand.value();
if( value == null )
{
buf.append( "null" );
}
else
{
buf.append( value.getClass().getName() );
}
}
throw new FunctionException( undefinedFunctionMessage.format( name(), buf.toString() ) );
}
else
{
if( function != this.baseFunction )
{
if( this.baseFunctionResult != null )
{
this.baseFunctionResult.dispose();
}
this.baseFunction = function;
this.baseFunctionResult = function.evaluate( context() );
if( this.listener == null )
{
this.listener = new Listener()
{
@Override
public void handle( final Event event )
{
refresh();
}
};
}
this.baseFunctionResult.attach( this.listener );
}
return this.baseFunctionResult.value();
}
}
private Function findFunction()
{
Function function = null;
// Match typed functions first.
// Each function is scored based on how closely its declared parameter types match actual parameter types.
// For each parameter, the best score is 0. Each step up the type tree from the actual parameter type to
// declared parameter type is 1. A conversion requiring more than a Java type cast is 100. If no path exists,
// the score is -1, which immediately disqualifies the function from further consideration. The total score
// for a function is the sum of the parameter scores.
final int[] scores = new int[ typedFunctionsCount ];
for( int i = 0; i < arity; i++ )
{
final Object parameter = operand( i );
if( parameter != null )
{
for( int j = 0; j < typedFunctionsCount; j++ )
{
final int score = scores[ j ];
if( score != -1 )
{
final Class<?> declaredParameterType = typedFunctions.get( j ).signature().get( i );
final int scoreForParameter = score( declaredParameterType, parameter.getClass() );
if( scoreForParameter == -1 )
{
Object converted = null;
try
{
converted = cast( parameter, declaredParameterType );
}
catch( Exception e )
{
// Safe to ignore. We just want to know if it is possible to cast.
}
if( converted == null )
{
scores[ j ] = -1;
}
else
{
scores[ j ] = score + 100;
}
}
else
{
scores[ j ] = score + scoreForParameter;
}
}
}
}
}
// Once all functions are scored, the first one with the lowest score is picked.
int lowestScore = Integer.MAX_VALUE;
for( int i = 0; i < typedFunctionsCount; i++ )
{
final int score = scores[ i ];
if( score != -1 && score < lowestScore )
{
function = typedFunctions.get( i );
lowestScore = score;
}
}
// If no typed functions matched, the generic function is used, if available.
if( function == null )
{
function = genericFunctionFinal;
}
return function;
}
private int score( final Class<?> declaredParameterType, final Class<?> actualParameterType )
{
int distance;
if( actualParameterType == null )
{
distance = -1;
}
else if( declaredParameterType == actualParameterType )
{
distance = 0;
}
else
{
distance = score( declaredParameterType, actualParameterType.getSuperclass() );
if( distance != -1 )
{
distance++;
}
else
{
for( Class<?> intrfc : actualParameterType.getInterfaces() )
{
distance = score( declaredParameterType, intrfc );
if( distance != -1 )
{
distance++;
break;
}
}
}
}
return distance;
}
@Override
public void dispose()
{
super.dispose();
if( this.baseFunctionResult != null )
{
this.baseFunctionResult.dispose();
}
}
};
}
}
}