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