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    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, when used with incoming arguments that have type information, the argument field
056       * names can be used directly in the the expression, for example {@code a + b }. The type of {@code a} and {@code b}
057       * will be inherited from the incoming argument fields.
058       * <p/>
059       * Or, if the incoming argument selector is {@link Fields#NONE}, an expression using only static method calls
060       * or constants can be used.
061       * <p/>
062       * This is useful when inserting random numbers for example, {@code (int) (Math.random() * Integer.MAX_VALUE) }.
063       *
064       * @param fieldDeclaration of type Fields
065       * @param expression       of type String
066       */
067      @ConstructorProperties({"fieldDeclaration", "expression"})
068      public ExpressionFunction( Fields fieldDeclaration, String expression )
069        {
070        super( fieldDeclaration, expression );
071    
072        verify( fieldDeclaration );
073        }
074    
075      /**
076       * Constructor ExpressionFunction creates a new ExpressionFunction instance.
077       * <p/>
078       * This constructor assumes all parameter are of the same type.
079       *
080       * @param fieldDeclaration of type Fields
081       * @param expression       of type String
082       * @param parameterType    of type Class
083       */
084      @ConstructorProperties({"fieldDeclaration", "expression", "parameterType"})
085      public ExpressionFunction( Fields fieldDeclaration, String expression, Class parameterType )
086        {
087        super( fieldDeclaration, expression, parameterType );
088    
089        verify( fieldDeclaration );
090        }
091    
092      /**
093       * Constructor ExpressionFunction creates a new ExpressionFunction instance.
094       * <p/>
095       * This constructor expects all parameter type names to be declared with their types. Positional parameters must
096       * be named the same as in the given expression with the "$" sign prepended.
097       *
098       * @param fieldDeclaration of type Fields
099       * @param expression       of type String
100       * @param parameterNames   of type String[]
101       * @param parameterTypes   of type Class[]
102       */
103      @ConstructorProperties({"fieldDeclaration", "expression", "parameterNames", "parameterTypes"})
104      public ExpressionFunction( Fields fieldDeclaration, String expression, String[] parameterNames, Class[] parameterTypes )
105        {
106        super( fieldDeclaration, expression, parameterNames, parameterTypes );
107    
108        verify( fieldDeclaration );
109        }
110    
111      private void verify( Fields fieldDeclaration )
112        {
113        if( !fieldDeclaration.isSubstitution() && fieldDeclaration.size() != 1 )
114          throw new IllegalArgumentException( "fieldDeclaration may only declare one field, was " + fieldDeclaration.print() );
115        }
116    
117      @Override
118      public void operate( FlowProcess flowProcess, FunctionCall<ExpressionOperation.Context> functionCall )
119        {
120        functionCall.getContext().result.set( 0, evaluate( functionCall.getContext(), functionCall.getArguments() ) );
121        functionCall.getOutputCollector().add( functionCall.getContext().result );
122        }
123      }