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    import cascading.tuple.Tuple;
030    import org.codehaus.janino.ExpressionEvaluator;
031    
032    /**
033     * Class ExpressionFunction dynamically resolves a given expression using argument {@link Tuple} values. This {@link Function}
034     * is based on the <a href="http://www.janino.net/">Janino</a> compiler.
035     * <p/>
036     * Specifically this function uses the {@link ExpressionEvaluator}, thus the syntax from that class is inherited here.
037     * <p/>
038     * An expression may use field names directly as parameters in the expression, or field positions with the syntax
039     * "$n", where n is an integer.
040     * <p/>
041     * Given an argument tuple with the fields "a" and "b", the following expression returns true: <br/>
042     * <code>a + b == $0 + $1</code><br/>
043     * <p/>
044     * Further, the types of the tuple elements will be coerced into the given parameterTypes. Regardless of the actual
045     * tuple element values, they will be converted to the types expected by the expression.
046     * <p/>
047     * Field names used in the expression should be valid Java variable names; for example, '+' or '-' are not allowed.
048     * Also the use of a field name that begins with an upper-case character is likely to fail and should be avoided.
049     */
050    public class ExpressionFunction extends ExpressionOperation implements Function<ScriptOperation.Context>
051      {
052      /**
053       * Constructor ExpressionFunction creates a new ExpressionFunction instance.
054       * <p/>
055       * This constructor assumes the given expression expects no input parameters. This is useful when
056       * inserting random numbers for example, {@code (int) (Math.random() * Integer.MAX_VALUE) }.
057       *
058       * @param fieldDeclaration of type Fields
059       * @param expression       of type String
060       */
061      @ConstructorProperties({"fieldDeclaration", "expression"})
062      public ExpressionFunction( Fields fieldDeclaration, String expression )
063        {
064        super( fieldDeclaration, expression );
065    
066        verify( fieldDeclaration );
067        }
068    
069      /**
070       * Constructor ExpressionFunction creates a new ExpressionFunction instance.
071       * <p/>
072       * This constructor assumes all parameter are of the same type.
073       *
074       * @param fieldDeclaration of type Fields
075       * @param expression       of type String
076       * @param parameterType    of type Class
077       */
078      @ConstructorProperties({"fieldDeclaration", "expression", "parameterType"})
079      public ExpressionFunction( Fields fieldDeclaration, String expression, Class parameterType )
080        {
081        super( fieldDeclaration, expression, parameterType );
082    
083        verify( fieldDeclaration );
084        }
085    
086      /**
087       * Constructor ExpressionFunction creates a new ExpressionFunction instance.
088       * <p/>
089       * This constructor expects all parameter type names to be declared with their types. Positional parameters must
090       * be named the same as in the given expression with the "$" sign prepended.
091       *
092       * @param fieldDeclaration of type Fields
093       * @param expression       of type String
094       * @param parameterNames   of type String[]
095       * @param parameterTypes   of type Class[]
096       */
097      @ConstructorProperties({"fieldDeclaration", "expression", "parameterNames", "parameterTypes"})
098      public ExpressionFunction( Fields fieldDeclaration, String expression, String[] parameterNames, Class[] parameterTypes )
099        {
100        super( fieldDeclaration, expression, parameterNames, parameterTypes );
101    
102        verify( fieldDeclaration );
103        }
104    
105      private void verify( Fields fieldDeclaration )
106        {
107        if( !fieldDeclaration.isSubstitution() && fieldDeclaration.size() != 1 )
108          throw new IllegalArgumentException( "fieldDeclaration may only declare one field, was " + fieldDeclaration.print() );
109        }
110    
111      @Override
112      public void operate( FlowProcess flowProcess, FunctionCall<ExpressionOperation.Context> functionCall )
113        {
114        functionCall.getContext().result.set( 0, evaluate( functionCall.getContext(), functionCall.getArguments() ) );
115        functionCall.getOutputCollector().add( functionCall.getContext().result );
116        }
117      }