001 /* 002 * Copyright (c) 2007-2014 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.io.IOException; 024 import java.lang.reflect.InvocationTargetException; 025 import java.util.Arrays; 026 027 import cascading.flow.FlowProcess; 028 import cascading.management.annotation.Property; 029 import cascading.management.annotation.PropertyDescription; 030 import cascading.management.annotation.Visibility; 031 import cascading.operation.BaseOperation; 032 import cascading.operation.OperationCall; 033 import cascading.operation.OperationException; 034 import cascading.tuple.Fields; 035 import cascading.tuple.Tuple; 036 import cascading.tuple.TupleEntry; 037 import cascading.tuple.Tuples; 038 import cascading.tuple.coerce.Coercions; 039 import cascading.tuple.type.CoercibleType; 040 import cascading.tuple.util.TupleViews; 041 import cascading.util.Util; 042 import org.codehaus.commons.compiler.CompileException; 043 import org.codehaus.janino.ScriptEvaluator; 044 045 /** 046 * 047 */ 048 public abstract class ScriptOperation extends BaseOperation<ScriptOperation.Context> 049 { 050 /** Field expression */ 051 protected final String block; 052 /** Field parameterTypes */ 053 protected Class[] parameterTypes; 054 /** Field parameterNames */ 055 protected String[] parameterNames; 056 /** returnType */ 057 protected Class returnType = Object.class; 058 059 public ScriptOperation( int numArgs, Fields fieldDeclaration, String block ) 060 { 061 super( numArgs, fieldDeclaration ); 062 this.block = block; 063 this.returnType = fieldDeclaration.getTypeClass( 0 ) == null ? this.returnType : fieldDeclaration.getTypeClass( 0 ); 064 } 065 066 public ScriptOperation( int numArgs, Fields fieldDeclaration, String block, Class returnType ) 067 { 068 super( numArgs, fieldDeclaration ); 069 this.block = block; 070 this.returnType = returnType == null ? this.returnType : returnType; 071 } 072 073 public ScriptOperation( int numArgs, Fields fieldDeclaration, String block, Class returnType, Class[] expectedTypes ) 074 { 075 super( numArgs, fieldDeclaration ); 076 this.block = block; 077 this.returnType = returnType == null ? this.returnType : returnType; 078 079 if( expectedTypes == null ) 080 throw new IllegalArgumentException( "expectedTypes may not be null" ); 081 082 this.parameterTypes = Arrays.copyOf( expectedTypes, expectedTypes.length ); 083 } 084 085 public ScriptOperation( int numArgs, Fields fieldDeclaration, String block, Class returnType, String[] parameterNames, Class[] parameterTypes ) 086 { 087 super( numArgs, fieldDeclaration ); 088 this.parameterNames = parameterNames == null ? null : Arrays.copyOf( parameterNames, parameterNames.length ); 089 this.block = block; 090 this.returnType = returnType == null ? this.returnType : returnType; 091 this.parameterTypes = Arrays.copyOf( parameterTypes, parameterTypes.length ); 092 093 if( getParameterNamesInternal().length != getParameterTypesInternal().length ) 094 throw new IllegalArgumentException( "parameterNames must be same length as parameterTypes" ); 095 } 096 097 public ScriptOperation( int numArgs, String block, Class returnType ) 098 { 099 super( numArgs ); 100 this.block = block; 101 this.returnType = returnType == null ? this.returnType : returnType; 102 } 103 104 public ScriptOperation( int numArgs, String block, Class returnType, Class[] expectedTypes ) 105 { 106 super( numArgs ); 107 this.block = block; 108 this.returnType = returnType == null ? this.returnType : returnType; 109 110 if( expectedTypes == null || expectedTypes.length == 0 ) 111 throw new IllegalArgumentException( "expectedTypes may not be null or empty" ); 112 113 this.parameterTypes = Arrays.copyOf( expectedTypes, expectedTypes.length ); 114 } 115 116 public ScriptOperation( int numArgs, String block, Class returnType, String[] parameterNames, Class[] parameterTypes ) 117 { 118 super( numArgs ); 119 this.parameterNames = parameterNames == null ? null : Arrays.copyOf( parameterNames, parameterNames.length ); 120 this.block = block; 121 this.returnType = returnType == null ? this.returnType : returnType; 122 this.parameterTypes = Arrays.copyOf( parameterTypes, parameterTypes.length ); 123 124 if( getParameterNamesInternal().length != getParameterTypesInternal().length ) 125 throw new IllegalArgumentException( "parameterNames must be same length as parameterTypes" ); 126 } 127 128 @Property(name = "source", visibility = Visibility.PRIVATE) 129 @PropertyDescription("The Java source to execute.") 130 public String getBlock() 131 { 132 return block; 133 } 134 135 private boolean hasParameterNames() 136 { 137 return parameterNames != null; 138 } 139 140 @Property(name = "parameterNames", visibility = Visibility.PUBLIC) 141 @PropertyDescription("The declared parameter names.") 142 public String[] getParameterNames() 143 { 144 return Util.copy( parameterNames ); 145 } 146 147 private String[] getParameterNamesInternal() 148 { 149 if( parameterNames != null ) 150 return parameterNames; 151 152 try 153 { 154 parameterNames = guessParameterNames(); 155 } 156 catch( IOException exception ) 157 { 158 throw new OperationException( "could not read expression: " + block, exception ); 159 } 160 catch( CompileException exception ) 161 { 162 throw new OperationException( "could not compile expression: " + block, exception ); 163 } 164 165 return parameterNames; 166 } 167 168 protected String[] guessParameterNames() throws CompileException, IOException 169 { 170 throw new OperationException( "parameter names are required" ); 171 } 172 173 private Fields getParameterFields() 174 { 175 return makeFields( getParameterNamesInternal() ); 176 } 177 178 private boolean hasParameterTypes() 179 { 180 return parameterTypes != null; 181 } 182 183 @Property(name = "parameterTypes", visibility = Visibility.PUBLIC) 184 @PropertyDescription("The declared parameter types.") 185 public Class[] getParameterTypes() 186 { 187 return Util.copy( parameterTypes ); 188 } 189 190 private Class[] getParameterTypesInternal() 191 { 192 if( !hasParameterNames() ) 193 return parameterTypes; 194 195 if( hasParameterNames() && parameterNames.length == parameterTypes.length ) 196 return parameterTypes; 197 198 if( parameterNames.length > 0 && parameterTypes.length != 1 ) 199 throw new IllegalStateException( "wrong number of parameter types, expects: " + parameterNames.length ); 200 201 Class[] types = new Class[ parameterNames.length ]; 202 203 Arrays.fill( types, parameterTypes[ 0 ] ); 204 205 parameterTypes = types; 206 207 return parameterTypes; 208 } 209 210 protected ScriptEvaluator getEvaluator( Class returnType, String[] parameterNames, Class[] parameterTypes ) 211 { 212 try 213 { 214 return new ScriptEvaluator( block, returnType, parameterNames, parameterTypes ); 215 } 216 catch( CompileException exception ) 217 { 218 throw new OperationException( "could not compile script: " + block, exception ); 219 } 220 } 221 222 private Fields makeFields( String[] parameters ) 223 { 224 Comparable[] fields = new Comparable[ parameters.length ]; 225 226 for( int i = 0; i < parameters.length; i++ ) 227 { 228 String parameter = parameters[ i ]; 229 230 if( parameter.startsWith( "$" ) ) 231 fields[ i ] = parse( parameter ); // returns parameter if not a number after $ 232 else 233 fields[ i ] = parameter; 234 } 235 236 return new Fields( fields ); 237 } 238 239 private Comparable parse( String parameter ) 240 { 241 try 242 { 243 return Integer.parseInt( parameter.substring( 1 ) ); 244 } 245 catch( NumberFormatException exception ) 246 { 247 return parameter; 248 } 249 } 250 251 @Override 252 public void prepare( FlowProcess flowProcess, OperationCall<Context> operationCall ) 253 { 254 if( operationCall.getContext() == null ) 255 operationCall.setContext( new Context() ); 256 257 Context context = operationCall.getContext(); 258 259 Fields argumentFields = operationCall.getArgumentFields(); 260 261 if( hasParameterNames() && hasParameterTypes() ) 262 { 263 context.parameterNames = getParameterNamesInternal(); 264 context.parameterFields = argumentFields.select( getParameterFields() ); // inherit argument types 265 context.parameterTypes = getParameterTypesInternal(); 266 } 267 else if( hasParameterTypes() ) 268 { 269 context.parameterNames = toNames( argumentFields ); 270 context.parameterFields = argumentFields.applyTypes( getParameterTypesInternal() ); 271 context.parameterTypes = getParameterTypesInternal(); 272 } 273 else 274 { 275 context.parameterNames = toNames( argumentFields ); 276 context.parameterFields = argumentFields; 277 context.parameterTypes = argumentFields.getTypesClasses(); 278 279 if( context.parameterTypes == null ) 280 throw new IllegalArgumentException( "field types may not be empty" ); 281 } 282 283 context.parameterCoercions = Coercions.coercibleArray( context.parameterFields ); 284 context.parameterArray = new Object[ context.parameterTypes.length ]; // re-use object array 285 context.scriptEvaluator = getEvaluator( getReturnType(), context.parameterNames, context.parameterTypes ); 286 context.intermediate = TupleViews.createNarrow( argumentFields.getPos( context.parameterFields ) ); 287 context.result = Tuple.size( 1 ); // re-use the output tuple 288 } 289 290 private String[] toNames( Fields argumentFields ) 291 { 292 String[] names = new String[ argumentFields.size() ]; 293 294 for( int i = 0; i < names.length; i++ ) 295 { 296 Comparable comparable = argumentFields.get( i ); 297 if( comparable instanceof String ) 298 names[ i ] = (String) comparable; 299 else 300 names[ i ] = "$" + comparable; 301 } 302 303 return names; 304 } 305 306 public Class getReturnType() 307 { 308 return returnType; 309 } 310 311 /** 312 * Performs the actual expression evaluation. 313 * 314 * @param context 315 * @param input of type TupleEntry 316 * @return Comparable 317 */ 318 protected Object evaluate( Context context, TupleEntry input ) 319 { 320 try 321 { 322 if( context.parameterTypes.length == 0 ) 323 return context.scriptEvaluator.evaluate( null ); 324 325 Tuple parameterTuple = TupleViews.reset( context.intermediate, input.getTuple() ); 326 Object[] arguments = Tuples.asArray( parameterTuple, context.parameterCoercions, context.parameterTypes, context.parameterArray ); 327 328 return context.scriptEvaluator.evaluate( arguments ); 329 } 330 catch( InvocationTargetException exception ) 331 { 332 throw new OperationException( "could not evaluate expression: " + block, exception.getTargetException() ); 333 } 334 } 335 336 @Override 337 public boolean equals( Object object ) 338 { 339 if( this == object ) 340 return true; 341 if( !( object instanceof ExpressionOperation ) ) 342 return false; 343 if( !super.equals( object ) ) 344 return false; 345 346 ExpressionOperation that = (ExpressionOperation) object; 347 348 if( block != null ? !block.equals( that.block ) : that.block != null ) 349 return false; 350 if( !Arrays.equals( parameterNames, that.parameterNames ) ) 351 return false; 352 if( !Arrays.equals( parameterTypes, that.parameterTypes ) ) 353 return false; 354 355 return true; 356 } 357 358 @Override 359 public int hashCode() 360 { 361 int result = super.hashCode(); 362 result = 31 * result + ( block != null ? block.hashCode() : 0 ); 363 result = 31 * result + ( parameterTypes != null ? Arrays.hashCode( parameterTypes ) : 0 ); 364 result = 31 * result + ( parameterNames != null ? Arrays.hashCode( parameterNames ) : 0 ); 365 return result; 366 } 367 368 public static class Context 369 { 370 private Class[] parameterTypes; 371 private ScriptEvaluator scriptEvaluator; 372 private Fields parameterFields; 373 private CoercibleType[] parameterCoercions; 374 private String[] parameterNames; 375 private Object[] parameterArray; 376 private Tuple intermediate; 377 protected Tuple result; 378 } 379 }