    001/*
    002 * Copyright (c) 2007-2017 Xplenty, Inc. All Rights Reserved.
    003 *
    004 * Project and contact information: http://www.cascading.org/
    005 *
    006 * This file is part of the Cascading project.
    007 *
    008 * Licensed under the Apache License, Version 2.0 (the "License");
    009 * you may not use this file except in compliance with the License.
    010 * You may obtain a copy of the License at
    011 *
    012 *     http://www.apache.org/licenses/LICENSE-2.0
    013 *
    014 * Unless required by applicable law or agreed to in writing, software
    015 * distributed under the License is distributed on an "AS IS" BASIS,
    016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    017 * See the License for the specific language governing permissions and
    018 * limitations under the License.
    019 */
    020
    021package cascading.operation.expression;
    022
    023import java.beans.ConstructorProperties;
    024
    025import cascading.flow.FlowProcess;
    026import cascading.operation.Function;
    027import cascading.operation.FunctionCall;
    028import cascading.tuple.Fields;
    029
    030import static cascading.tuple.coerce.Coercions.asClass;
    031
    032/**
    033 * Class ScriptFunction dynamically resolves a given expression using argument {@link cascading.tuple.Tuple} values.
    034 * This {@link cascading.operation.Function} is based on the <a href="http://www.janino.net/">Janino</a> compiler.
    035 * <p/>
    036 * This class is different from {@link ScriptTupleFunction} in that it allows any return type instance to be returned
    037 * by the script. ScriptTupleFunction allows only a single {@code Tuple} instance to be returned.
    038 * <p/>
    039 * Specifically this function uses the {@link org.codehaus.janino.ScriptEvaluator},
    040 * thus the syntax from that class is inherited here.
    041 * <p/>
    042 * A script may use field names directly as parameters in the expression, or field positions with the syntax
    043 * "$n", where n is an integer.
    044 * <p/>
    045 * Given an argument tuple with the fields "a" and "b", the following script returns true: <br/>
    046 * <code>boolean result = (a + b == $0 + $1);</code><br/>
    047 * <code>return boolean;</code><br/>
    048 * <p/>
    049 * Unlike an "expression" used by {@link ExpressionFunction}, a "script" requires each line to end in an semi-colon
    050 * (@{code ;}) and the final line to be a {@code return} statement.
    051 * <p/>
    052 * Further, the types of the tuple elements will be coerced into the given parameterTypes. Regardless of the actual
    053 * tuple element values, they will be converted to the types expected by the script if possible.
    054 */
    055public class ScriptFunction extends ScriptOperation implements Function<ScriptOperation.Context>
    056  {
    057  /**
    058   * Constructor ScriptFunction creates a new ScriptFunction instance.
    059   * <p/>
    060   * This constructor will use the runtime {@link cascading.operation.OperationCall#getArgumentFields()}
    061   * to source the {@code parameterNames} and {@code parameterTypes} required by the other constructors.
    062   * <p/>
    063   * The {@code returnType} will be retrieved from the given {@code fieldDeclaration.getTypeClass(0)}.
    064   *
    065   * @param fieldDeclaration of type Fields
    066   * @param script           of type String
    067   */
    068  @ConstructorProperties({"fieldDeclaration", "script"})
    069  public ScriptFunction( Fields fieldDeclaration, String script )
    070    {
    071    super( ANY, fieldDeclaration, script );
    072
    073    verify( fieldDeclaration );
    074    }
    075
    076  /**
    077   * Constructor ScriptFunction creates a new ScriptFunction instance.
    078   * <p/>
    079   * This constructor will use the runtime {@link cascading.operation.OperationCall#getArgumentFields()}
    080   * to source the {@code parameterNames} and {@code parameterTypes} required by the other constructors.
    081   *
    082   * @param fieldDeclaration of type Fields
    083   * @param script           of type String
    084   * @param returnType       of type Class
    085   */
    086  @ConstructorProperties({"fieldDeclaration", "script", "returnType"})
    087  public ScriptFunction( Fields fieldDeclaration, String script, Class returnType )
    088    {
    089    super( ANY, fieldDeclaration, script, returnType );
    090
    091    verify( fieldDeclaration );
    092    }
    093
    094  /**
    095   * Constructor ScriptFunction creates a new ScriptFunction instance.
    096   * <p/>
    097   * This constructor will use the runtime {@link cascading.operation.OperationCall#getArgumentFields()}
    098   * to source the {@code parameterNames} and {@code parameterTypes} required by the other constructors, but
    099   * use {@code expectedTypes} to coerce the incoming types to before passing as parameters to the expression.
    100   *
    101   * @param fieldDeclaration of type Fields
    102   * @param script           of type String
    103   * @param returnType       of type Class
    104   * @param expectedTypes    of type Class[]
    105   */
    106  @ConstructorProperties({"fieldDeclaration", "script", "returnType", "expectedTypes"})
    107  public ScriptFunction( Fields fieldDeclaration, String script, Class returnType, Class[] expectedTypes )
    108    {
    109    super( expectedTypes.length, fieldDeclaration, script, returnType, expectedTypes );
    110
    111    verify( fieldDeclaration );
    112    }
    113
    114  /**
    115   * Constructor ScriptFunction creates a new ScriptFunction instance.
    116   * <p/>
    117   * This constructor expects all parameter type names to be declared with their types. Positional parameters must
    118   * be named the same as in the given script with the "$" sign prepended.
    119   *
    120   * @param fieldDeclaration of type Fields
    121   * @param script           of type String
    122   * @param returnType       of type Class
    123   * @param parameterNames   of type String[]
    124   * @param parameterTypes   of type Class[]
    125   */
    126  @ConstructorProperties({"fieldDeclaration", "script", "returnType", "parameterNames", "parameterTypes"})
    127  public ScriptFunction( Fields fieldDeclaration, String script, Class returnType, String[] parameterNames, Class[] parameterTypes )
    128    {
    129    super( parameterTypes.length, fieldDeclaration, script, returnType, parameterNames, parameterTypes );
    130
    131    verify( fieldDeclaration );
    132    }
    133
    134  /**
    135   * Constructor ScriptFunction creates a new ScriptFunction instance.
    136   * <p/>
    137   * This constructor expects all parameter type names to be declared with their types. Positional parameters must
    138   * be named the same as in the given script with the "$" sign prepended.
    139   *
    140   * @param fieldDeclaration of type Fields
    141   * @param script           of type String
    142   * @param parameterNames   of type String[]
    143   * @param parameterTypes   of type Class[]
    144   */
    145  @ConstructorProperties({"fieldDeclaration", "script", "parameterNames", "parameterTypes"})
    146  public ScriptFunction( Fields fieldDeclaration, String script, String[] parameterNames, Class[] parameterTypes )
    147    {
    148    super( parameterTypes.length, fieldDeclaration, script, asClass( fieldDeclaration.getType( 0 ) ), parameterNames, parameterTypes );
    149
    150    verify( fieldDeclaration );
    151    }
    152
    153  private void verify( Fields fieldDeclaration )
    154    {
    155    if( !fieldDeclaration.isSubstitution() && fieldDeclaration.size() != 1 )
    156      throw new IllegalArgumentException( "fieldDeclaration may only declare one field, was " + fieldDeclaration.print() );
    157    }
    158
    159  public String getScript()
    160    {
    161    return getBlock();
    162    }
    163
    164  @Override
    165  public void operate( FlowProcess flowProcess, FunctionCall<Context> functionCall )
    166    {
    167    functionCall.getContext().result.set( 0, evaluate( functionCall.getContext(), functionCall.getArguments() ) );
    168    functionCall.getOutputCollector().add( functionCall.getContext().result );
    169    }
    170  }