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
021package cascading.operation.expression;
022
023import java.beans.ConstructorProperties;
024
025import cascading.flow.FlowProcess;
026import cascading.operation.Function;
027import cascading.operation.FunctionCall;
028import cascading.tuple.Fields;
029import cascading.tuple.Tuple;
030import 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 */
050public 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  }