001/*
002 * Copyright (c) 2007-2015 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
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  }