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    
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      }