    001    /*
    002     * Copyright (c) 2007-2014 Concurrent, 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
    021    package cascading.operation.expression;
    022
    023    import java.beans.ConstructorProperties;
    024
    025    import cascading.flow.FlowProcess;
    026    import cascading.operation.Function;
    027    import cascading.operation.FunctionCall;
    028    import cascading.tuple.Fields;
    029
    030    import 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     */
    055    public 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      }