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    import cascading.tuple.Tuple;
030    
031    /**
032     * Class ScriptTupleFunction dynamically resolves a given expression using argument {@link cascading.tuple.Tuple} values.
033     * This {@link cascading.operation.Function} is based on the <a href="http://www.janino.net/">Janino</a> compiler.
034     * <p/>
035     * This class is different from {@link ScriptFunction} in that it requires a new {@link Tuple} instance to be returned
036     * by the script. ScriptFunction allows only a single value to be returned, which is passed into a result Tuple instance
037     * internally.
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);}<br/>
047     * {@code return cascading.tuple.Tuples.tuple( boolean );}<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 that returns a new {@link Tuple} instance.
051     * <p/>
052     * Since Janino does not support "varargs", see the {@link cascading.tuple.Tuples} class for helper methods.
053     * <p/>
054     * Further, the types of the tuple elements will be coerced into the given parameterTypes. Regardless of the actual
055     * tuple element values, they will be converted to the types expected by the script if possible.
056     */
057    public class ScriptTupleFunction extends ScriptOperation implements Function<ScriptOperation.Context>
058      {
059      /**
060       * Constructor ScriptFunction creates a new ScriptFunction instance.
061       * <p/>
062       * This constructor will use the runtime {@link cascading.operation.OperationCall#getArgumentFields()}
063       * to source the {@code parameterNames} and {@code parameterTypes} required by the other constructors.
064       * <p/>
065       * The {@code returnType} will be retrieved from the given {@code fieldDeclaration.getTypeClass(0)}.
066       *
067       * @param fieldDeclaration of type Fields
068       * @param script           of type String
069       */
070      @ConstructorProperties({"fieldDeclaration", "script"})
071      public ScriptTupleFunction( Fields fieldDeclaration, String script )
072        {
073        super( ANY, fieldDeclaration, script, Tuple.class );
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, but
081       * use {@code expectedTypes} to coerce the incoming types to before passing as parameters to the expression.
082       *
083       * @param fieldDeclaration of type Fields
084       * @param script           of type String
085       * @param expectedTypes    of type Class[]
086       */
087      @ConstructorProperties({"fieldDeclaration", "script", "expectedTypes"})
088      public ScriptTupleFunction( Fields fieldDeclaration, String script, Class[] expectedTypes )
089        {
090        super( expectedTypes.length, fieldDeclaration, script, Tuple.class, expectedTypes );
091        }
092    
093      /**
094       * Constructor ScriptFunction creates a new ScriptFunction instance.
095       * <p/>
096       * This constructor expects all parameter type names to be declared with their types. Positional parameters must
097       * be named the same as in the given script with the "$" sign prepended.
098       *
099       * @param fieldDeclaration of type Fields
100       * @param script           of type String
101       * @param parameterNames   of type String[]
102       * @param parameterTypes   of type Class[]
103       */
104      @ConstructorProperties({"fieldDeclaration", "script", "parameterNames", "parameterTypes"})
105      public ScriptTupleFunction( Fields fieldDeclaration, String script, String[] parameterNames, Class[] parameterTypes )
106        {
107        super( parameterTypes.length, fieldDeclaration, script, Tuple.class, parameterNames, parameterTypes );
108        }
109    
110      public String getScript()
111        {
112        return getBlock();
113        }
114    
115      @Override
116      public void operate( FlowProcess flowProcess, FunctionCall<Context> functionCall )
117        {
118        functionCall.getOutputCollector().add( (Tuple) evaluate( functionCall.getContext(), functionCall.getArguments() ) );
119        }
120      }