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; 030 031import static cascading.tuple.coerce.Coercions.asClass; 032 033/** 034 * Class ScriptFunction dynamically resolves a given expression using argument {@link cascading.tuple.Tuple} values. 035 * This {@link cascading.operation.Function} is based on the <a href="http://www.janino.net/">Janino</a> compiler. 036 * <p> 037 * This class is different from {@link ScriptTupleFunction} in that it allows any return type instance to be returned 038 * by the script. ScriptTupleFunction allows only a single {@code Tuple} instance to be returned. 039 * <p> 040 * Specifically this function uses the {@link org.codehaus.janino.ScriptEvaluator}, 041 * thus the syntax from that class is inherited here. 042 * <p> 043 * A script may use field names directly as parameters in the expression, or field positions with the syntax 044 * "$n", where n is an integer. 045 * <p> 046 * Given an argument tuple with the fields "a" and "b", the following script returns true: <br> 047 * {@code boolean result = (a + b == $0 + $1);}<br> 048 * {@code return boolean;}<br> 049 * <p> 050 * Unlike an "expression" used by {@link ExpressionFunction}, a "script" requires each line to end in an semi-colon 051 * (@{code ;}) and the final line to be a {@code return} statement. 052 * <p> 053 * Further, the types of the tuple elements will be coerced into the given parameterTypes. Regardless of the actual 054 * tuple element values, they will be converted to the types expected by the script if possible. 055 */ 056public class ScriptFunction extends ScriptOperation implements Function<ScriptOperation.Context> 057 { 058 /** 059 * Constructor ScriptFunction creates a new ScriptFunction instance. 060 * <p> 061 * This constructor will use the runtime {@link cascading.operation.OperationCall#getArgumentFields()} 062 * to source the {@code parameterNames} and {@code parameterTypes} required by the other constructors. 063 * <p> 064 * The {@code returnType} will be retrieved from the given {@code fieldDeclaration.getTypeClass(0)}. 065 * 066 * @param fieldDeclaration of type Fields 067 * @param script of type String 068 */ 069 @ConstructorProperties({"fieldDeclaration", "script"}) 070 public ScriptFunction( Fields fieldDeclaration, String script ) 071 { 072 super( ANY, fieldDeclaration, script ); 073 074 verify( fieldDeclaration ); 075 } 076 077 /** 078 * Constructor ScriptFunction creates a new ScriptFunction instance. 079 * <p> 080 * This constructor will use the runtime {@link cascading.operation.OperationCall#getArgumentFields()} 081 * to source the {@code parameterNames} and {@code parameterTypes} required by the other constructors. 082 * 083 * @param fieldDeclaration of type Fields 084 * @param script of type String 085 * @param returnType of type Class 086 */ 087 @ConstructorProperties({"fieldDeclaration", "script", "returnType"}) 088 public ScriptFunction( Fields fieldDeclaration, String script, Class returnType ) 089 { 090 super( ANY, fieldDeclaration, script, returnType ); 091 092 verify( fieldDeclaration ); 093 } 094 095 /** 096 * Constructor ScriptFunction creates a new ScriptFunction instance. 097 * <p> 098 * This constructor will use the runtime {@link cascading.operation.OperationCall#getArgumentFields()} 099 * to source the {@code parameterNames} and {@code parameterTypes} required by the other constructors, but 100 * use {@code expectedTypes} to coerce the incoming types to before passing as parameters to the expression. 101 * 102 * @param fieldDeclaration of type Fields 103 * @param script of type String 104 * @param returnType of type Class 105 * @param expectedTypes of type Class[] 106 */ 107 @ConstructorProperties({"fieldDeclaration", "script", "returnType", "expectedTypes"}) 108 public ScriptFunction( Fields fieldDeclaration, String script, Class returnType, Class[] expectedTypes ) 109 { 110 super( expectedTypes.length, fieldDeclaration, script, returnType, expectedTypes ); 111 112 verify( fieldDeclaration ); 113 } 114 115 /** 116 * Constructor ScriptFunction creates a new ScriptFunction instance. 117 * <p> 118 * This constructor expects all parameter type names to be declared with their types. Positional parameters must 119 * be named the same as in the given script with the "$" sign prepended. 120 * 121 * @param fieldDeclaration of type Fields 122 * @param script of type String 123 * @param returnType of type Class 124 * @param parameterNames of type String[] 125 * @param parameterTypes of type Class[] 126 */ 127 @ConstructorProperties({"fieldDeclaration", "script", "returnType", "parameterNames", "parameterTypes"}) 128 public ScriptFunction( Fields fieldDeclaration, String script, Class returnType, String[] parameterNames, Class[] parameterTypes ) 129 { 130 super( parameterTypes.length, fieldDeclaration, script, returnType, parameterNames, parameterTypes ); 131 132 verify( fieldDeclaration ); 133 } 134 135 /** 136 * Constructor ScriptFunction creates a new ScriptFunction instance. 137 * <p> 138 * This constructor expects all parameter type names to be declared with their types. Positional parameters must 139 * be named the same as in the given script with the "$" sign prepended. 140 * 141 * @param fieldDeclaration of type Fields 142 * @param script of type String 143 * @param parameterNames of type String[] 144 * @param parameterTypes of type Class[] 145 */ 146 @ConstructorProperties({"fieldDeclaration", "script", "parameterNames", "parameterTypes"}) 147 public ScriptFunction( Fields fieldDeclaration, String script, String[] parameterNames, Class[] parameterTypes ) 148 { 149 super( parameterTypes.length, fieldDeclaration, script, asClass( fieldDeclaration.getType( 0 ) ), parameterNames, parameterTypes ); 150 151 verify( fieldDeclaration ); 152 } 153 154 private void verify( Fields fieldDeclaration ) 155 { 156 if( !fieldDeclaration.isSubstitution() && fieldDeclaration.size() != 1 ) 157 throw new IllegalArgumentException( "fieldDeclaration may only declare one field, was " + fieldDeclaration.print() ); 158 } 159 160 public String getScript() 161 { 162 return getBlock(); 163 } 164 165 @Override 166 public void operate( FlowProcess flowProcess, FunctionCall<Context> functionCall ) 167 { 168 functionCall.getContext().result.set( 0, evaluate( functionCall.getContext(), functionCall.getArguments() ) ); 169 functionCall.getOutputCollector().add( functionCall.getContext().result ); 170 } 171 }