/*
* Copyright (c) 2007-2010 Concurrent, Inc. All Rights Reserved.
*
* Project and contact information: http://www.cascading.org/
*
* This file is part of the Cascading project.
*
* Cascading is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Cascading 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Cascading. If not, see <http://www.gnu.org/licenses/>.
*/
package cascading.operation.expression;
import java.beans.ConstructorProperties;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import cascading.flow.FlowProcess;
import cascading.operation.BaseOperation;
import cascading.operation.OperationCall;
import cascading.operation.OperationException;
import cascading.tuple.Fields;
import cascading.tuple.Tuple;
import cascading.tuple.TupleEntry;
import cascading.tuple.Tuples;
import org.codehaus.janino.CompileException;
import org.codehaus.janino.ExpressionEvaluator;
import org.codehaus.janino.Parser;
import org.codehaus.janino.Scanner;
/**
* Class ExpressionOperation is the base class for {@link ExpressionFunction}, {@link ExpressionFilter},
* {@link cascading.operation.assertion.AssertExpression}.
*/
public class ExpressionOperation extends BaseOperation<ExpressionOperation.Context>
{
/** Field expression */
protected String expression;
/** Field parameterTypes */
private Class[] parameterTypes;
/** Field parameterNames */
private String[] parameterNames;
public static class Context
{
private Class[] parameterTypes;
private ExpressionEvaluator expressionEvaluator;
private Fields parameterFields;
private String[] parameterNames;
}
@ConstructorProperties({"fieldDeclaration", "expression"})
protected ExpressionOperation( Fields fieldDeclaration, String expression )
{
super( fieldDeclaration );
this.parameterTypes = new Class[]{};
this.expression = expression;
}
@ConstructorProperties({"fieldDeclaration", "expression", "parameterType"})
protected ExpressionOperation( Fields fieldDeclaration, String expression, Class parameterType )
{
super( fieldDeclaration );
this.parameterTypes = new Class[]{parameterType};
this.expression = expression;
}
@ConstructorProperties({"fieldDeclaration", "expression", "parameterNames", "parameterTypes"})
protected ExpressionOperation( Fields fieldDeclaration, String expression, String[] parameterNames, Class[] parameterTypes )
{
super( parameterTypes.length, fieldDeclaration );
this.parameterTypes = Arrays.copyOf( parameterTypes, parameterTypes.length );
this.parameterNames = Arrays.copyOf( parameterNames, parameterNames.length );
this.expression = expression;
if( parameterNames.length != parameterTypes.length )
throw new IllegalArgumentException( "parameterNames must be same length as parameterTypes" );
}
@ConstructorProperties({"fieldDeclaration", "parameterType"})
protected ExpressionOperation( String expression, Class parameterType )
{
this.parameterTypes = new Class[]{parameterType};
this.expression = expression;
}
@ConstructorProperties({"expression", "parameterNames", "parameterTypes"})
protected ExpressionOperation( String expression, String[] parameterNames, Class[] parameterTypes )
{
super( parameterTypes.length );
this.parameterTypes = Arrays.copyOf( parameterTypes, parameterTypes.length );
this.parameterNames = Arrays.copyOf( parameterNames, parameterNames.length );
this.expression = expression;
if( parameterNames.length != parameterTypes.length )
throw new IllegalArgumentException( "parameterNames must be same length as parameterTypes" );
}
private String[] getParameterNames()
{
if( parameterNames != null )
return parameterNames;
try
{
parameterNames = ExpressionEvaluator.guessParameterNames( new Scanner( "expressionEval", new StringReader( expression ) ) );
}
catch( Parser.ParseException exception )
{
throw new OperationException( "could not parse expression: " + expression, exception );
}
catch( Scanner.ScanException exception )
{
throw new OperationException( "could not scan expression: " + expression, exception );
}
catch( IOException exception )
{
throw new OperationException( "could not read expression: " + expression, exception );
}
return parameterNames;
}
private Fields getParameterFields()
{
return makeFields( getParameterNames() );
}
private Class[] getParameterTypes( String[] parameterNames )
{
if( parameterNames.length == parameterTypes.length )
return parameterTypes;
if( parameterTypes.length != 1 )
throw new IllegalStateException( "wrong number of parameter types, expects: " + parameterNames.length );
Class[] types = new Class[parameterNames.length];
Arrays.fill( types, parameterTypes[ 0 ] );
parameterTypes = types;
return parameterTypes;
}
private ExpressionEvaluator getExpressionEvaluator( String[] parameterNames, Class[] parameterTypes )
{
try
{
return new ExpressionEvaluator( expression, Comparable.class, parameterNames, parameterTypes );
}
catch( CompileException exception )
{
throw new OperationException( "could not compile expression: " + expression, exception );
}
catch( Parser.ParseException exception )
{
throw new OperationException( "could not parse expression: " + expression, exception );
}
catch( Scanner.ScanException exception )
{
throw new OperationException( "could not scan expression: " + expression, exception );
}
}
private Fields makeFields( String[] parameters )
{
Comparable[] fields = new Comparable[parameters.length];
for( int i = 0; i < parameters.length; i++ )
{
String parameter = parameters[ i ];
if( parameter.startsWith( "$" ) )
fields[ i ] = Integer.parseInt( parameter.substring( 1 ) );
else
fields[ i ] = parameter;
}
return new Fields( fields );
}
@Override
public void prepare( FlowProcess flowProcess, OperationCall<Context> operationCall )
{
if( operationCall.getContext() == null )
operationCall.setContext( new Context() );
Context context = operationCall.getContext();
context.parameterNames = getParameterNames();
context.parameterFields = getParameterFields();
context.parameterTypes = getParameterTypes( context.parameterNames );
context.expressionEvaluator = getExpressionEvaluator( context.parameterNames, context.parameterTypes );
}
/**
* Performs the actual expression evaluation.
*
* @param context
* @param input of type TupleEntry @return Comparable
*/
protected Comparable evaluate( Context context, TupleEntry input )
{
try
{
if( context.parameterTypes.length == 0 )
return (Comparable) context.expressionEvaluator.evaluate( null );
Tuple parameterTuple = input.selectTuple( context.parameterFields );
return (Comparable) context.expressionEvaluator.evaluate( Tuples.asArray( parameterTuple, context.parameterTypes ) );
}
catch( InvocationTargetException exception )
{
throw new OperationException( "could not evaluate expression: " + expression, exception );
}
}
@Override
public boolean equals( Object object )
{
if( this == object )
return true;
if( !( object instanceof ExpressionOperation ) )
return false;
if( !super.equals( object ) )
return false;
ExpressionOperation that = (ExpressionOperation) object;
if( expression != null ? !expression.equals( that.expression ) : that.expression != null )
return false;
if( !Arrays.equals( parameterNames, that.parameterNames ) )
return false;
if( !Arrays.equals( parameterTypes, that.parameterTypes ) )
return false;
return true;
}
@Override
public int hashCode()
{
int result = super.hashCode();
result = 31 * result + ( expression != null ? expression.hashCode() : 0 );
result = 31 * result + ( parameterTypes != null ? Arrays.hashCode( parameterTypes ) : 0 );
result = 31 * result + ( parameterNames != null ? Arrays.hashCode( parameterNames ) : 0 );
return result;
}
}