001/*
002 * Copyright (c) 2016-2017 Chris K Wensel <[email protected]>. All Rights Reserved.
003 * Copyright (c) 2007-2017 Xplenty, Inc. All Rights Reserved.
004 *
005 * Project and contact information: http://www.cascading.org/
006 *
007 * This file is part of the Cascading project.
008 *
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 *     http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 */
021
022package cascading.operation.expression;
023
024import java.beans.ConstructorProperties;
025
026import cascading.flow.FlowProcess;
027import cascading.operation.Function;
028import cascading.operation.FunctionCall;
029import cascading.tuple.Fields;
030import cascading.tuple.Tuple;
031import org.codehaus.janino.ExpressionEvaluator;
032
033/**
034 * Class ExpressionFunction dynamically resolves a given expression using argument {@link Tuple} values. This {@link Function}
035 * is based on the <a href="http://www.janino.net/">Janino</a> compiler.
036 * <p>
037 * Specifically this function uses the {@link ExpressionEvaluator}, thus the syntax from that class is inherited here.
038 * <p>
039 * An expression may use field names directly as parameters in the expression, or field positions with the syntax
040 * "$n", where n is an integer.
041 * <p>
042 * Given an argument tuple with the fields "a" and "b", the following expression returns true: <br>
043 * {@code a + b == $0 + $1}<br>
044 * <p>
045 * Further, the types of the tuple elements will be coerced into the given parameterTypes. Regardless of the actual
046 * tuple element values, they will be converted to the types expected by the expression.
047 * <p>
048 * Field names used in the expression should be valid Java variable names; for example, '+' or '-' are not allowed.
049 * Also the use of a field name that begins with an upper-case character is likely to fail and should be avoided.
050 */
051public class ExpressionFunction extends ExpressionOperation implements Function<ScriptOperation.Context>
052  {
053  /**
054   * Constructor ExpressionFunction creates a new ExpressionFunction instance.
055   * <p>
056   * This constructor, when used with incoming arguments that have type information, the argument field
057   * names can be used directly in the the expression, for example {@code a + b }. The type of {@code a} and {@code b}
058   * will be inherited from the incoming argument fields.
059   * <p>
060   * Or, if the incoming argument selector is {@link Fields#NONE}, an expression using only static method calls
061   * or constants can be used.
062   * <p>
063   * This is useful when inserting random numbers for example, {@code (int) (Math.random() * Integer.MAX_VALUE) }.
064   *
065   * @param fieldDeclaration of type Fields
066   * @param expression       of type String
067   */
068  @ConstructorProperties({"fieldDeclaration", "expression"})
069  public ExpressionFunction( Fields fieldDeclaration, String expression )
070    {
071    super( fieldDeclaration, expression );
072
073    verify( fieldDeclaration );
074    }
075
076  /**
077   * Constructor ExpressionFunction creates a new ExpressionFunction instance.
078   * <p>
079   * This constructor assumes all parameter are of the same type.
080   *
081   * @param fieldDeclaration of type Fields
082   * @param expression       of type String
083   * @param parameterType    of type Class
084   */
085  @ConstructorProperties({"fieldDeclaration", "expression", "parameterType"})
086  public ExpressionFunction( Fields fieldDeclaration, String expression, Class parameterType )
087    {
088    super( fieldDeclaration, expression, parameterType );
089
090    verify( fieldDeclaration );
091    }
092
093  /**
094   * Constructor ExpressionFunction creates a new ExpressionFunction 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 expression with the "$" sign prepended.
098   *
099   * @param fieldDeclaration of type Fields
100   * @param expression       of type String
101   * @param parameterNames   of type String[]
102   * @param parameterTypes   of type Class[]
103   */
104  @ConstructorProperties({"fieldDeclaration", "expression", "parameterNames", "parameterTypes"})
105  public ExpressionFunction( Fields fieldDeclaration, String expression, String[] parameterNames, Class[] parameterTypes )
106    {
107    super( fieldDeclaration, expression, parameterNames, parameterTypes );
108
109    verify( fieldDeclaration );
110    }
111
112  private void verify( Fields fieldDeclaration )
113    {
114    if( !fieldDeclaration.isSubstitution() && fieldDeclaration.size() != 1 )
115      throw new IllegalArgumentException( "fieldDeclaration may only declare one field, was " + fieldDeclaration.print() );
116    }
117
118  @Override
119  public void operate( FlowProcess flowProcess, FunctionCall<ExpressionOperation.Context> functionCall )
120    {
121    functionCall.getContext().result.set( 0, evaluate( functionCall.getContext(), functionCall.getArguments() ) );
122    functionCall.getOutputCollector().add( functionCall.getContext().result );
123    }
124  }