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