001 /* 002 $Id: Closure.java,v 1.53 2005/07/16 21:01:36 blackdrag Exp $ 003 004 Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved. 005 006 Redistribution and use of this software and associated documentation 007 ("Software"), with or without modification, are permitted provided 008 that the following conditions are met: 009 010 1. Redistributions of source code must retain copyright 011 statements and notices. Redistributions must also contain a 012 copy of this document. 013 014 2. Redistributions in binary form must reproduce the 015 above copyright notice, this list of conditions and the 016 following disclaimer in the documentation and/or other 017 materials provided with the distribution. 018 019 3. The name "groovy" must not be used to endorse or promote 020 products derived from this Software without prior written 021 permission of The Codehaus. For written permission, 022 please contact info@codehaus.org. 023 024 4. Products derived from this Software may not be called "groovy" 025 nor may "groovy" appear in their names without prior written 026 permission of The Codehaus. "groovy" is a registered 027 trademark of The Codehaus. 028 029 5. Due credit should be given to The Codehaus - 030 http://groovy.codehaus.org/ 031 032 THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS 033 ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT 034 NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 035 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 036 THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 037 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 038 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 039 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 040 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 041 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 042 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 043 OF THE POSSIBILITY OF SUCH DAMAGE. 044 045 */ 046 package groovy.lang; 047 048 import org.codehaus.groovy.runtime.InvokerHelper; 049 import org.codehaus.groovy.runtime.InvokerInvocationException; 050 051 import java.util.*; 052 import java.io.IOException; 053 import java.io.StringWriter; 054 import java.io.Writer; 055 import java.lang.reflect.InvocationTargetException; 056 import java.lang.reflect.Method; 057 import java.security.AccessController; 058 import java.security.PrivilegedAction; 059 060 /** 061 * Represents any closure object in Groovy. 062 * 063 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a> 064 * @author <a href="mailto:tug@wilson.co.uk">John Wilson</a> 065 * @version $Revision: 1.53 $ 066 */ 067 public abstract class Closure extends GroovyObjectSupport implements Cloneable, Runnable { 068 069 private static final Object noParameters[] = new Object[]{null}; 070 private static final Object emptyArray[] = new Object[0]; 071 private static final Object emptyArrayParameter[] = new Object[]{emptyArray}; 072 073 private Object delegate; 074 private final Object owner; 075 private final Method doCallMethod; 076 private final HashMap callsMap; 077 private final boolean supportsVarargs; 078 private final Class[] parameterTypes; 079 private final int numberOfParameters; 080 private Object curriedParams[] = emptyArray; 081 082 083 private int directive = 0; 084 public static int DONE = 1; 085 public static int SKIP = 2; 086 087 public Closure(Object delegate) { 088 this.delegate = delegate; 089 this.owner = delegate; 090 091 Class closureClass = this.getClass(); 092 callsMap = new HashMap(); 093 int paramLenTemp = -1; 094 Method doCallTemp = null; 095 096 while (true) { 097 final Class clazz = closureClass; 098 final Method[] methods = (Method[]) AccessController.doPrivileged(new PrivilegedAction() { 099 public Object run() { 100 return clazz.getDeclaredMethods(); 101 } 102 }); 103 104 int i = 0; 105 106 for (int j = 0; j < methods.length; j++) { 107 if ("doCall".equals(methods[j].getName())) { 108 callsMap.put(new Integer(methods[j].getParameterTypes().length), methods[j]); 109 if (methods[j].getParameterTypes().length > paramLenTemp) { 110 doCallTemp = methods[j]; 111 paramLenTemp = methods[j].getParameterTypes().length; 112 } 113 } 114 } 115 116 if (!callsMap.isEmpty()) { 117 break; 118 } 119 120 closureClass = closureClass.getSuperclass(); 121 } 122 123 this.doCallMethod = doCallTemp; 124 125 AccessController.doPrivileged(new PrivilegedAction() { 126 public Object run() { 127 for (Iterator iter = callsMap.values().iterator(); iter.hasNext(); ) { 128 ((Method) iter.next()).setAccessible(true); 129 } 130 return null; 131 } 132 }); 133 134 this.parameterTypes = this.doCallMethod.getParameterTypes(); 135 this.numberOfParameters = this.parameterTypes.length; 136 137 if (this.numberOfParameters > 0) { 138 this.supportsVarargs = this.parameterTypes[this.numberOfParameters - 1].equals(Object[].class); 139 } else { 140 this.supportsVarargs = false; 141 } 142 } 143 144 public Object invokeMethod(String method, Object arguments) { 145 if ("doCall".equals(method) || "call".equals(method)) { 146 if (arguments instanceof Object[]) { 147 Object[] objs = (Object[]) arguments; 148 } 149 return callSpecial(new ParameterArray(arguments)); 150 } else if ("curry".equals(method)) { 151 return curry((Object[]) arguments); 152 } else { 153 try { 154 return getMetaClass().invokeMethod(this, method, arguments); 155 } catch (MissingMethodException e) { 156 if (owner != this) { 157 try { 158 // lets try invoke method on the owner 159 return InvokerHelper.invokeMethod(this.owner, method, arguments); 160 } catch (InvokerInvocationException iie) { 161 throw iie; 162 } catch (GroovyRuntimeException e1) { 163 if (this.delegate != null && this.delegate != this && this.delegate != this.owner) { 164 // lets try invoke method on the delegate 165 try { 166 return InvokerHelper.invokeMethod(this.delegate, method, arguments); 167 } catch (MissingMethodException mme) { 168 throw new InvokerInvocationException(mme); 169 } catch (GroovyRuntimeException gre) { 170 throw new InvokerInvocationException(gre.getCause()); 171 } 172 } 173 } 174 } 175 throw e; 176 } 177 } 178 179 } 180 181 public Object getProperty(String property) { 182 if ("delegate".equals(property)) { 183 return getDelegate(); 184 } else if ("owner".equals(property)) { 185 return getOwner(); 186 } else if ("method".equals(property)) { 187 return getMethod(); 188 } else if ("parameterTypes".equals(property)) { 189 return getParameterTypes(); 190 } else if ("metaClass".equals(property)) { 191 return getMetaClass(); 192 } else if ("class".equals(property)) { 193 return getClass(); 194 } else { 195 try { 196 // lets try getting the property on the owner 197 return InvokerHelper.getProperty(this.owner, property); 198 } catch (GroovyRuntimeException e1) { 199 if (this.delegate != null && this.delegate != this && this.delegate != this.owner) { 200 try { 201 // lets try getting the property on the delegate 202 return InvokerHelper.getProperty(this.delegate, property); 203 } catch (GroovyRuntimeException e2) { 204 // ignore, we'll throw e1 205 } 206 } 207 208 throw e1; 209 } 210 } 211 } 212 213 public void setProperty(String property, Object newValue) { 214 if ("delegate".equals(property)) { 215 setDelegate(newValue); 216 } else if ("metaClass".equals(property)) { 217 setMetaClass((MetaClass) newValue); 218 } else { 219 try { 220 // lets try setting the property on the owner 221 InvokerHelper.setProperty(this.owner, property, newValue); 222 return; 223 } catch (GroovyRuntimeException e1) { 224 if (this.delegate != null && this.delegate != this && this.delegate != this.owner) { 225 try { 226 // lets try setting the property on the delegate 227 InvokerHelper.setProperty(this.delegate, property, newValue); 228 return; 229 } catch (GroovyRuntimeException e2) { 230 // ignore, we'll throw e1 231 } 232 } 233 234 throw e1; 235 } 236 } 237 } 238 239 public boolean isCase(Object candidate){ 240 return InvokerHelper.asBool(call(candidate)); 241 } 242 243 /** 244 * Invokes the closure without any parameters, returning any value if applicable. 245 * 246 * @return the value if applicable or null if there is no return statement in the closure 247 */ 248 public Object call() { 249 return call(noParameters); 250 } 251 252 private Object[] getArguments(Object arguments) { 253 Object[] args; 254 if (arguments instanceof ParameterArray) { 255 Object paramObj = ((ParameterArray) arguments).get(); 256 if (paramObj instanceof Object[]) 257 args = (Object[]) paramObj; 258 else 259 args = new Object[] { paramObj }; 260 } 261 else { 262 args = new Object[]{arguments}; 263 } 264 return args; 265 } 266 267 268 /** 269 * Invokes the closure, returning any value if applicable. 270 * 271 * @param arguments could be a single value or a List of values 272 * @return the value if applicable or null if there is no return statement in the closure 273 */ 274 public Object call(final Object arguments) { 275 final Object params[]; 276 277 if (this.curriedParams.length != 0) { 278 final Object[] args = getArguments(arguments); 279 params = new Object[this.curriedParams.length + args.length]; 280 281 System.arraycopy(this.curriedParams, 0, params, 0, this.curriedParams.length); 282 System.arraycopy(args, 0, params, this.curriedParams.length, args.length); 283 } else { 284 params = getArguments(arguments); 285 } 286 287 final int lastParam = this.numberOfParameters - 1; 288 289 if (this.supportsVarargs && !(this.numberOfParameters == params.length && (params[lastParam] == null || params[lastParam].getClass() == Object[].class))) { 290 final Object actualParameters[] = new Object[this.numberOfParameters]; 291 292 // 293 // We have a closure which supports variable arguments and we haven't got actual 294 // parameters which have exactly the right number of parameters and ends with a null or an Object[] 295 // 296 if (params.length < lastParam) { 297 // 298 // Not enough parameters throw exception 299 // 300 // Note we allow there to be one fewer actual parameter than the number of formal parameters 301 // in this case we pass an zero length Object[] as the last parameter 302 // 303 throw new IncorrectClosureArgumentsException(this, params, this.parameterTypes); 304 } else { 305 final Object rest[] = new Object[params.length - lastParam]; // array used to pass the rest of the paraters 306 307 // fill the parameter array up to but not including the last one 308 System.arraycopy(params, 0, actualParameters, 0, lastParam); 309 310 // put the rest of the parameters in the overflow araay 311 System.arraycopy(params, lastParam, rest, 0, rest.length); 312 313 // pass the overflow array as the last parameter 314 actualParameters[lastParam] = rest; 315 316 return callViaReflection(actualParameters); 317 } 318 } 319 320 if (params.length == 0) { 321 return doCall(); 322 } else { 323 return callViaReflection(params); 324 } 325 } 326 327 public Object callSpecial(final Object arguments) { 328 final Object params[]; 329 330 if (this.curriedParams.length > 0) { 331 final Object[] args = getArguments(arguments); 332 params = new Object[this.curriedParams.length + args.length]; 333 334 System.arraycopy(this.curriedParams, 0, params, 0, this.curriedParams.length); 335 System.arraycopy(args, 0, params, this.curriedParams.length, args.length); 336 } 337 else { 338 params = getArguments(arguments); 339 } 340 341 final int lastParam = this.numberOfParameters - 1; 342 343 if (this.supportsVarargs && !(this.numberOfParameters == params.length && (params.length > lastParam) && (params[lastParam] == null || params[lastParam].getClass() == Object[].class))) { 344 final Object actualParameters[] = new Object[this.numberOfParameters]; 345 346 // 347 // We have a closure which supports variable arguments and we haven't got actual 348 // parameters which have exactly the right number of parameters and ends with a null or an Object[] 349 // 350 if (params.length < lastParam) { 351 // 352 // Not enough parameters throw exception 353 // 354 // Note we allow there to be one fewer actual parameter than the number of formal parameters 355 // in this case we pass an zero length Object[] as the last parameter 356 // 357 throw new IncorrectClosureArgumentsException(this, params, this.parameterTypes); 358 } else { 359 final Object rest[] = new Object[params.length - lastParam]; // array used to pass the rest of the paraters 360 361 // fill the parameter array up to but not including the last one 362 System.arraycopy(params, 0, actualParameters, 0, lastParam); 363 364 // put the rest of the parameters in the overflow araay 365 System.arraycopy(params, lastParam, rest, 0, rest.length); 366 367 // pass the overflow array as the last parameter 368 actualParameters[lastParam] = rest; 369 370 return callViaReflection(actualParameters); 371 } 372 } 373 374 if (params.length == 0) { 375 return doCall(); 376 } else { 377 return callViaReflection(params); 378 } 379 } 380 381 protected static Object throwRuntimeException(Throwable throwable) { 382 if (throwable instanceof RuntimeException) { 383 throw (RuntimeException) throwable; 384 } else { 385 throw new GroovyRuntimeException(throwable.getMessage(), throwable); 386 } 387 } 388 389 /** 390 * An attempt to optimise calling closures with one parameter 391 * If the closure has one untyped parameter then it will overload this function 392 * If not this will be called ans will use reflection to deal with the case of a 393 * single typed parameter 394 * 395 * @param p1 396 * @return the result of calling the closure 397 */ 398 protected Object doCall(final Object p1) { 399 return callViaReflection(new Object[]{p1}); 400 } 401 402 /** 403 * An attempt to optimise calling closures with no parameter 404 * This method only calls doCall(Object) and will be called by call(Object) 405 * if the parameter given to call is an empty Object array 406 * 407 * @return the result of calling the closure 408 */ 409 protected Object doCall() { 410 return doCall((Object)null); 411 } 412 413 414 /** 415 * An attempt to optimise calling closures with two parameters 416 * If the closure has two untyped parameters then it will overload this function 417 * If not this will be called ans will use reflection to deal with the case of one 418 * or two typed parameters 419 * 420 * @param p1 421 * @return the result of calling the closure 422 */ 423 protected Object doCall(final Object p1, final Object p2) { 424 return callViaReflection(new Object[]{p1, p2}); 425 } 426 427 private Object callViaReflection(final Object params[]) { 428 try { 429 // invoke the closure 430 if (callsMap.get(new Integer(params.length)) != null) { 431 return ((Method) callsMap.get(new Integer(params.length))).invoke(this, params); 432 } 433 else 434 return this.doCallMethod.invoke(this, params); 435 } catch (final IllegalArgumentException e) { 436 throw new IncorrectClosureArgumentsException(this, params, this.parameterTypes); 437 } catch (final IllegalAccessException e) { 438 final Throwable cause = e.getCause(); 439 440 return throwRuntimeException((cause == null) ? e : cause); 441 } catch (final InvocationTargetException e) { 442 final Throwable cause = e.getCause(); 443 444 return throwRuntimeException((cause == null) ? e : cause); 445 } 446 } 447 448 /** 449 * Used when a closure wraps a method on a class 450 * 451 * @return empty string 452 */ 453 public String getMethod() { 454 return ""; 455 } 456 457 /** 458 * @return the owner Object to which method calls will go which is 459 * typically the outer class when the closure is constructed 460 */ 461 public Object getOwner() { 462 return this.owner; 463 } 464 465 /** 466 * @return the delegate Object to which method calls will go which is 467 * typically the outer class when the closure is constructed 468 */ 469 public Object getDelegate() { 470 return this.delegate; 471 } 472 473 /** 474 * Allows the delegate to be changed such as when performing markup building 475 * 476 * @param delegate 477 */ 478 public void setDelegate(Object delegate) { 479 this.delegate = delegate; 480 } 481 482 /** 483 * @return the parameter types of this closure 484 */ 485 public Class[] getParameterTypes() { 486 return this.parameterTypes; 487 } 488 489 /** 490 * @return a version of this closure which implements Writable 491 */ 492 public Closure asWritable() { 493 return new WritableClosure(); 494 } 495 496 /* (non-Javadoc) 497 * @see java.lang.Runnable#run() 498 */ 499 public void run() { 500 call(); 501 } 502 503 /** 504 * Support for closure currying 505 * 506 * @param arguments 507 */ 508 public Closure curry(final Object arguments[]) { 509 final Closure curriedClosure = (Closure) this.clone(); 510 final Object newCurriedParams[] = new Object[curriedClosure.curriedParams.length + arguments.length]; 511 512 System.arraycopy(curriedClosure.curriedParams, 0, newCurriedParams, 0, curriedClosure.curriedParams.length); 513 System.arraycopy(arguments, 0, newCurriedParams, curriedClosure.curriedParams.length, arguments.length); 514 515 curriedClosure.curriedParams = newCurriedParams; 516 517 return curriedClosure; 518 } 519 520 /* (non-Javadoc) 521 * @see java.lang.Object#clone() 522 */ 523 public Object clone() { 524 try { 525 return super.clone(); 526 } catch (final CloneNotSupportedException e) { 527 return null; 528 } 529 } 530 531 private class WritableClosure extends Closure implements Writable { 532 public WritableClosure() { 533 super(null); 534 } 535 536 /* (non-Javadoc) 537 * @see groovy.lang.Writable#writeTo(java.io.Writer) 538 */ 539 public Writer writeTo(Writer out) throws IOException { 540 Closure.this.call(new ParameterArray(out)); 541 542 return out; 543 } 544 545 /* (non-Javadoc) 546 * @see groovy.lang.GroovyObject#invokeMethod(java.lang.String, java.lang.Object) 547 */ 548 public Object invokeMethod(String method, Object arguments) { 549 if ("clone".equals(method)) { 550 return clone(); 551 } else if ("curry".equals(method)) { 552 return curry((Object[]) arguments); 553 } else if ("asWritable".equals(method)) { 554 return asWritable(); 555 } else { 556 return Closure.this.invokeMethod(method, arguments); 557 } 558 } 559 560 /* (non-Javadoc) 561 * @see groovy.lang.GroovyObject#getProperty(java.lang.String) 562 */ 563 public Object getProperty(String property) { 564 return Closure.this.getProperty(property); 565 } 566 567 /* (non-Javadoc) 568 * @see groovy.lang.GroovyObject#setProperty(java.lang.String, java.lang.Object) 569 */ 570 public void setProperty(String property, Object newValue) { 571 Closure.this.setProperty(property, newValue); 572 } 573 574 /* (non-Javadoc) 575 * @see groovy.lang.Closure#call() 576 */ 577 public Object call() { 578 return Closure.this.call(); 579 } 580 581 /* (non-Javadoc) 582 * @see groovy.lang.Closure#call(java.lang.Object) 583 */ 584 public Object call(Object arguments) { 585 return Closure.this.call(arguments); 586 } 587 588 /* (non-Javadoc) 589 * @see groovy.lang.Closure#doCall(java.lang.Object) 590 */ 591 protected Object doCall(Object p1) { 592 return Closure.this.doCall(p1); 593 } 594 595 /* (non-Javadoc) 596 * @see groovy.lang.Closure#doCall(java.lang.Object, java.lang.Object) 597 */ 598 protected Object doCall(Object p1, Object p2) { 599 return Closure.this.doCall(p1, p2); 600 } 601 602 /* (non-Javadoc) 603 * @see groovy.lang.Closure#getDelegate() 604 */ 605 public Object getDelegate() { 606 return Closure.this.getDelegate(); 607 } 608 609 /* (non-Javadoc) 610 * @see groovy.lang.Closure#setDelegate(java.lang.Object) 611 */ 612 public void setDelegate(Object delegate) { 613 Closure.this.setDelegate(delegate); 614 } 615 616 /* (non-Javadoc) 617 * @see groovy.lang.Closure#getParameterTypes() 618 */ 619 public Class[] getParameterTypes() { 620 return Closure.this.getParameterTypes(); 621 } 622 623 /* (non-Javadoc) 624 * @see groovy.lang.Closure#asWritable() 625 */ 626 public Closure asWritable() { 627 return this; 628 } 629 630 /* (non-Javadoc) 631 * @see java.lang.Runnable#run() 632 */ 633 public void run() { 634 Closure.this.run(); 635 } 636 637 /* (non-Javadoc) 638 * @see groovy.lang.Closure#curry(java.lang.Object[]) 639 */ 640 public Closure curry(Object[] arguments) { 641 return Closure.this.curry(arguments).asWritable(); 642 } 643 644 /* (non-Javadoc) 645 * @see java.lang.Object#clone() 646 */ 647 public Object clone() { 648 return ((Closure) Closure.this.clone()).asWritable(); 649 } 650 651 /* (non-Javadoc) 652 * @see java.lang.Object#hashCode() 653 */ 654 public int hashCode() { 655 return Closure.this.hashCode(); 656 } 657 658 /* (non-Javadoc) 659 * @see java.lang.Object#equals(java.lang.Object) 660 */ 661 public boolean equals(Object arg0) { 662 return Closure.this.equals(arg0); 663 } 664 665 /* (non-Javadoc) 666 * @see java.lang.Object#toString() 667 */ 668 public String toString() { 669 final StringWriter writer = new StringWriter(); 670 671 try { 672 writeTo(writer); 673 } catch (IOException e) { 674 return null; 675 } 676 677 return writer.toString(); 678 } 679 } 680 681 /** 682 * @return Returns the directive. 683 */ 684 public int getDirective() { 685 return directive; 686 } 687 688 /** 689 * @param directive The directive to set. 690 */ 691 public void setDirective(int directive) { 692 this.directive = directive; 693 } 694 }