001 /* 002 $Id: Closure.java,v 1.50 2005/06/12 17:31:09 dierk 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.50 $ 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 Method methods[] = closureClass.getDeclaredMethods(); 098 099 int i = 0; 100 101 for (int j = 0; j < methods.length; j++) { 102 if ("doCall".equals(methods[j].getName())) { 103 callsMap.put(new Integer(methods[j].getParameterTypes().length), methods[j]); 104 if (methods[j].getParameterTypes().length > paramLenTemp) { 105 doCallTemp = methods[j]; 106 paramLenTemp = methods[j].getParameterTypes().length; 107 } 108 } 109 } 110 111 if (!callsMap.isEmpty()) { 112 break; 113 } 114 115 closureClass = closureClass.getSuperclass(); 116 } 117 118 this.doCallMethod = doCallTemp; 119 120 AccessController.doPrivileged(new PrivilegedAction() { 121 public Object run() { 122 for (Iterator iter = callsMap.values().iterator(); iter.hasNext(); ) { 123 ((Method) iter.next()).setAccessible(true); 124 } 125 return null; 126 } 127 }); 128 129 this.parameterTypes = this.doCallMethod.getParameterTypes(); 130 this.numberOfParameters = this.parameterTypes.length; 131 132 if (this.numberOfParameters > 0) { 133 this.supportsVarargs = this.parameterTypes[this.numberOfParameters - 1].equals(Object[].class); 134 } else { 135 this.supportsVarargs = false; 136 } 137 } 138 139 public Object invokeMethod(String method, Object arguments) { 140 if ("doCall".equals(method) || "call".equals(method)) { 141 if (arguments instanceof Object[]) { 142 Object[] objs = (Object[]) arguments; 143 if ((objs != null) && (objs.length > 1) && (objs[0] instanceof Object[])) { 144 boolean allNull = true; 145 for (int j = 1; j < objs.length; j++) { 146 if (objs[j] != null) { 147 allNull = false; 148 break; 149 } 150 } 151 if (allNull) 152 return callViaReflection((Object[]) (objs[0])); 153 } 154 } 155 return callSpecial(arguments); 156 } else if ("curry".equals(method)) { 157 return curry((Object[]) arguments); 158 } else { 159 try { 160 return getMetaClass().invokeMethod(this, method, arguments); 161 } catch (MissingMethodException e) { 162 if (owner != this) { 163 try { 164 // lets try invoke method on the owner 165 return InvokerHelper.invokeMethod(this.owner, method, arguments); 166 } catch (InvokerInvocationException iie) { 167 throw iie; 168 } catch (GroovyRuntimeException e1) { 169 if (this.delegate != null && this.delegate != this && this.delegate != this.owner) { 170 // lets try invoke method on the delegate 171 try { 172 return InvokerHelper.invokeMethod(this.delegate, method, arguments); 173 } catch (MissingMethodException mme) { 174 throw new InvokerInvocationException(mme); 175 } catch (GroovyRuntimeException gre) { 176 throw new InvokerInvocationException(gre.getCause()); 177 } 178 } 179 } 180 } 181 throw e; 182 } 183 } 184 185 } 186 187 public Object getProperty(String property) { 188 if ("delegate".equals(property)) { 189 return getDelegate(); 190 } else if ("owner".equals(property)) { 191 return getOwner(); 192 } else if ("method".equals(property)) { 193 return getMethod(); 194 } else if ("parameterTypes".equals(property)) { 195 return getParameterTypes(); 196 } else if ("metaClass".equals(property)) { 197 return getMetaClass(); 198 } else if ("class".equals(property)) { 199 return getClass(); 200 } else { 201 try { 202 // lets try getting the property on the owner 203 return InvokerHelper.getProperty(this.owner, property); 204 } catch (GroovyRuntimeException e1) { 205 if (this.delegate != null && this.delegate != this && this.delegate != this.owner) { 206 try { 207 // lets try getting the property on the delegate 208 return InvokerHelper.getProperty(this.delegate, property); 209 } catch (GroovyRuntimeException e2) { 210 // ignore, we'll throw e1 211 } 212 } 213 214 throw e1; 215 } 216 } 217 } 218 219 public void setProperty(String property, Object newValue) { 220 if ("delegate".equals(property)) { 221 setDelegate(newValue); 222 } else if ("metaClass".equals(property)) { 223 setMetaClass((MetaClass) newValue); 224 } else { 225 try { 226 // lets try setting the property on the owner 227 InvokerHelper.setProperty(this.owner, property, newValue); 228 return; 229 } catch (GroovyRuntimeException e1) { 230 if (this.delegate != null && this.delegate != this && this.delegate != this.owner) { 231 try { 232 // lets try setting the property on the delegate 233 InvokerHelper.setProperty(this.delegate, property, newValue); 234 return; 235 } catch (GroovyRuntimeException e2) { 236 // ignore, we'll throw e1 237 } 238 } 239 240 throw e1; 241 } 242 } 243 } 244 245 public boolean isCase(Object candidate){ 246 return InvokerHelper.asBool(call(candidate)); 247 } 248 249 /** 250 * Invokes the closure without any parameters, returning any value if applicable. 251 * 252 * @return the value if applicable or null if there is no return statement in the closure 253 */ 254 public Object call() { 255 return call(emptyArray); 256 } 257 258 /** 259 * Invokes the closure, returning any value if applicable. 260 * 261 * @param arguments could be a single value or a List of values 262 * @return the value if applicable or null if there is no return statement in the closure 263 */ 264 public Object call(final Object arguments) { 265 final Object params[]; 266 267 if (this.curriedParams.length != 0) { 268 final Object args[]; 269 270 if (arguments instanceof Object[]) { 271 args = (Object[]) arguments; 272 } else { 273 args = new Object[]{arguments}; 274 } 275 276 params = new Object[this.curriedParams.length + args.length]; 277 278 System.arraycopy(this.curriedParams, 0, params, 0, this.curriedParams.length); 279 System.arraycopy(args, 0, params, this.curriedParams.length, args.length); 280 } else { 281 if (arguments instanceof Object[]) { 282 params = (Object[]) arguments; 283 } else { 284 return doCall(arguments); 285 } 286 } 287 288 final int lastParam = this.numberOfParameters - 1; 289 290 if (this.supportsVarargs && !(this.numberOfParameters == params.length && (params[lastParam] == null || params[lastParam].getClass() == Object[].class))) { 291 final Object actualParameters[] = new Object[this.numberOfParameters]; 292 293 // 294 // We have a closure which supports variable arguments and we haven't got actual 295 // parameters which have exactly the right number of parameters and ends with a null or an Object[] 296 // 297 if (params.length < lastParam) { 298 // 299 // Not enough parameters throw exception 300 // 301 // Note we allow there to be one fewer actual parameter than the number of formal parameters 302 // in this case we pass an zero length Object[] as the last parameter 303 // 304 throw new IncorrectClosureArgumentsException(this, params, this.parameterTypes); 305 } else { 306 final Object rest[] = new Object[params.length - lastParam]; // array used to pass the rest of the paraters 307 308 // fill the parameter array up to but not including the last one 309 System.arraycopy(params, 0, actualParameters, 0, lastParam); 310 311 // put the rest of the parameters in the overflow araay 312 System.arraycopy(params, lastParam, rest, 0, rest.length); 313 314 // pass the overflow array as the last parameter 315 actualParameters[lastParam] = rest; 316 317 return callViaReflection(actualParameters); 318 } 319 } 320 321 if (params.length == 0) { 322 return doCall(); 323 } else if (params.length == 1) { 324 return doCall(params[0]); 325 } else if (params.length == 2) { 326 return doCall(params[0], params[1]); 327 } else { 328 return callViaReflection(params); 329 } 330 } 331 332 public Object callSpecial(final Object arguments) { 333 final Object params[]; 334 335 if (this.curriedParams.length > 0) { 336 final Object args[]; 337 338 if (arguments instanceof Object[]) { 339 args = (Object[]) arguments; 340 } else { 341 args = new Object[]{arguments}; 342 } 343 344 params = new Object[this.curriedParams.length + args.length]; 345 346 System.arraycopy(this.curriedParams, 0, params, 0, this.curriedParams.length); 347 System.arraycopy(args, 0, params, this.curriedParams.length, args.length); 348 } else { 349 Object[] tmpParams = null; 350 if (arguments instanceof Object[]) { 351 tmpParams = (Object[]) arguments; 352 353 if ((tmpParams != null) && (tmpParams.length > 1)) { 354 boolean allNull = true; 355 for (int j = 1; j < tmpParams.length; j++) { 356 if (tmpParams[j] != null) { 357 allNull = false; 358 break; 359 } 360 } 361 if (allNull) { 362 if (tmpParams[0] instanceof Object[]) 363 tmpParams = (Object[]) (tmpParams[0]); 364 else 365 throw new IncorrectClosureArgumentsException(this, new Object[] { tmpParams[0] }, this.parameterTypes); 366 } 367 } 368 params = tmpParams; 369 370 } else { 371 return doCall(arguments); 372 } 373 } 374 375 final int lastParam = this.numberOfParameters - 1; 376 377 if (this.supportsVarargs && !(this.numberOfParameters == params.length && (params.length > lastParam) && (params[lastParam] == null || params[lastParam].getClass() == Object[].class))) { 378 final Object actualParameters[] = new Object[this.numberOfParameters]; 379 380 // 381 // We have a closure which supports variable arguments and we haven't got actual 382 // parameters which have exactly the right number of parameters and ends with a null or an Object[] 383 // 384 if (params.length < lastParam) { 385 // 386 // Not enough parameters throw exception 387 // 388 // Note we allow there to be one fewer actual parameter than the number of formal parameters 389 // in this case we pass an zero length Object[] as the last parameter 390 // 391 throw new IncorrectClosureArgumentsException(this, params, this.parameterTypes); 392 } else { 393 final Object rest[] = new Object[params.length - lastParam]; // array used to pass the rest of the paraters 394 395 // fill the parameter array up to but not including the last one 396 System.arraycopy(params, 0, actualParameters, 0, lastParam); 397 398 // put the rest of the parameters in the overflow araay 399 System.arraycopy(params, lastParam, rest, 0, rest.length); 400 401 // pass the overflow array as the last parameter 402 actualParameters[lastParam] = rest; 403 404 return callViaReflection(actualParameters); 405 } 406 } 407 408 if (params.length == 0) { 409 return doCall(); 410 } else if (params.length == 1) { 411 return doCall(params[0]); 412 } else if (params.length == 2) { 413 return doCall(params[0], params[1]); 414 } else { 415 return callViaReflection(params); 416 } 417 } 418 419 protected static Object throwRuntimeException(Throwable throwable) { 420 if (throwable instanceof RuntimeException) { 421 throw (RuntimeException) throwable; 422 } else { 423 throw new GroovyRuntimeException(throwable.getMessage(), throwable); 424 } 425 } 426 427 /** 428 * An attempt to optimise calling closures with one parameter 429 * If the closure has one untyped parameter then it will overload this function 430 * If not this will be called ans will use reflection to deal with the case of a 431 * single typed parameter 432 * 433 * @param p1 434 * @return the result of calling the closure 435 */ 436 protected Object doCall(final Object p1) { 437 return callViaReflection(new Object[]{p1}); 438 } 439 440 /** 441 * An attempt to optimise calling closures with no parameter 442 * This method only calls doCall(Object) and will be called by call(Object) 443 * if the parameter given to call is an empty Object array 444 * 445 * @return the result of calling the closure 446 */ 447 protected Object doCall() { 448 return doCall((Object)null); 449 } 450 451 452 /** 453 * An attempt to optimise calling closures with two parameters 454 * If the closure has two untyped parameters then it will overload this function 455 * If not this will be called ans will use reflection to deal with the case of one 456 * or two typed parameters 457 * 458 * @param p1 459 * @return the result of calling the closure 460 */ 461 protected Object doCall(final Object p1, final Object p2) { 462 return callViaReflection(new Object[]{p1, p2}); 463 } 464 465 private Object callViaReflection(final Object params[]) { 466 try { 467 // invoke the closure 468 return ((Method) callsMap.get(new Integer(params.length))).invoke(this, params); 469 } catch (final IllegalArgumentException e) { 470 throw new IncorrectClosureArgumentsException(this, params, this.parameterTypes); 471 } catch (final IllegalAccessException e) { 472 final Throwable cause = e.getCause(); 473 474 return throwRuntimeException((cause == null) ? e : cause); 475 } catch (final InvocationTargetException e) { 476 final Throwable cause = e.getCause(); 477 478 return throwRuntimeException((cause == null) ? e : cause); 479 } 480 } 481 482 /** 483 * Used when a closure wraps a method on a class 484 * 485 * @return empty string 486 */ 487 public String getMethod() { 488 return ""; 489 } 490 491 /** 492 * @return the owner Object to which method calls will go which is 493 * typically the outer class when the closure is constructed 494 */ 495 public Object getOwner() { 496 return this.owner; 497 } 498 499 /** 500 * @return the delegate Object to which method calls will go which is 501 * typically the outer class when the closure is constructed 502 */ 503 public Object getDelegate() { 504 return this.delegate; 505 } 506 507 /** 508 * Allows the delegate to be changed such as when performing markup building 509 * 510 * @param delegate 511 */ 512 public void setDelegate(Object delegate) { 513 this.delegate = delegate; 514 } 515 516 /** 517 * @return the parameter types of this closure 518 */ 519 public Class[] getParameterTypes() { 520 return this.parameterTypes; 521 } 522 523 /** 524 * @return a version of this closure which implements Writable 525 */ 526 public Closure asWritable() { 527 return new WritableClosure(); 528 } 529 530 /* (non-Javadoc) 531 * @see java.lang.Runnable#run() 532 */ 533 public void run() { 534 call(); 535 } 536 537 /** 538 * Support for closure currying 539 * 540 * @param arguments 541 */ 542 public Closure curry(final Object arguments[]) { 543 final Closure curriedClosure = (Closure) this.clone(); 544 final Object newCurriedParams[] = new Object[curriedClosure.curriedParams.length + arguments.length]; 545 546 System.arraycopy(curriedClosure.curriedParams, 0, newCurriedParams, 0, curriedClosure.curriedParams.length); 547 System.arraycopy(arguments, 0, newCurriedParams, curriedClosure.curriedParams.length, arguments.length); 548 549 curriedClosure.curriedParams = newCurriedParams; 550 551 return curriedClosure; 552 } 553 554 /* (non-Javadoc) 555 * @see java.lang.Object#clone() 556 */ 557 public Object clone() { 558 try { 559 return super.clone(); 560 } catch (final CloneNotSupportedException e) { 561 return null; 562 } 563 } 564 565 private class WritableClosure extends Closure implements Writable { 566 public WritableClosure() { 567 super(null); 568 } 569 570 /* (non-Javadoc) 571 * @see groovy.lang.Writable#writeTo(java.io.Writer) 572 */ 573 public Writer writeTo(Writer out) throws IOException { 574 Closure.this.call(out); 575 576 return out; 577 } 578 579 /* (non-Javadoc) 580 * @see groovy.lang.GroovyObject#invokeMethod(java.lang.String, java.lang.Object) 581 */ 582 public Object invokeMethod(String method, Object arguments) { 583 if ("clone".equals(method)) { 584 return clone(); 585 } else if ("curry".equals(method)) { 586 return curry((Object[]) arguments); 587 } else if ("asWritable".equals(method)) { 588 return asWritable(); 589 } else { 590 return Closure.this.invokeMethod(method, arguments); 591 } 592 } 593 594 /* (non-Javadoc) 595 * @see groovy.lang.GroovyObject#getProperty(java.lang.String) 596 */ 597 public Object getProperty(String property) { 598 return Closure.this.getProperty(property); 599 } 600 601 /* (non-Javadoc) 602 * @see groovy.lang.GroovyObject#setProperty(java.lang.String, java.lang.Object) 603 */ 604 public void setProperty(String property, Object newValue) { 605 Closure.this.setProperty(property, newValue); 606 } 607 608 /* (non-Javadoc) 609 * @see groovy.lang.Closure#call() 610 */ 611 public Object call() { 612 return Closure.this.call(); 613 } 614 615 /* (non-Javadoc) 616 * @see groovy.lang.Closure#call(java.lang.Object) 617 */ 618 public Object call(Object arguments) { 619 return Closure.this.call(arguments); 620 } 621 622 /* (non-Javadoc) 623 * @see groovy.lang.Closure#doCall(java.lang.Object) 624 */ 625 protected Object doCall(Object p1) { 626 return Closure.this.doCall(p1); 627 } 628 629 /* (non-Javadoc) 630 * @see groovy.lang.Closure#doCall(java.lang.Object, java.lang.Object) 631 */ 632 protected Object doCall(Object p1, Object p2) { 633 return Closure.this.doCall(p1, p2); 634 } 635 636 /* (non-Javadoc) 637 * @see groovy.lang.Closure#getDelegate() 638 */ 639 public Object getDelegate() { 640 return Closure.this.getDelegate(); 641 } 642 643 /* (non-Javadoc) 644 * @see groovy.lang.Closure#setDelegate(java.lang.Object) 645 */ 646 public void setDelegate(Object delegate) { 647 Closure.this.setDelegate(delegate); 648 } 649 650 /* (non-Javadoc) 651 * @see groovy.lang.Closure#getParameterTypes() 652 */ 653 public Class[] getParameterTypes() { 654 return Closure.this.getParameterTypes(); 655 } 656 657 /* (non-Javadoc) 658 * @see groovy.lang.Closure#asWritable() 659 */ 660 public Closure asWritable() { 661 return this; 662 } 663 664 /* (non-Javadoc) 665 * @see java.lang.Runnable#run() 666 */ 667 public void run() { 668 Closure.this.run(); 669 } 670 671 /* (non-Javadoc) 672 * @see groovy.lang.Closure#curry(java.lang.Object[]) 673 */ 674 public Closure curry(Object[] arguments) { 675 return Closure.this.curry(arguments).asWritable(); 676 } 677 678 /* (non-Javadoc) 679 * @see java.lang.Object#clone() 680 */ 681 public Object clone() { 682 return ((Closure) Closure.this.clone()).asWritable(); 683 } 684 685 /* (non-Javadoc) 686 * @see java.lang.Object#hashCode() 687 */ 688 public int hashCode() { 689 return Closure.this.hashCode(); 690 } 691 692 /* (non-Javadoc) 693 * @see java.lang.Object#equals(java.lang.Object) 694 */ 695 public boolean equals(Object arg0) { 696 return Closure.this.equals(arg0); 697 } 698 699 /* (non-Javadoc) 700 * @see java.lang.Object#toString() 701 */ 702 public String toString() { 703 final StringWriter writer = new StringWriter(); 704 705 try { 706 writeTo(writer); 707 } catch (IOException e) { 708 return null; 709 } 710 711 return writer.toString(); 712 } 713 } 714 715 /** 716 * @return Returns the directive. 717 */ 718 public int getDirective() { 719 return directive; 720 } 721 722 /** 723 * @param directive The directive to set. 724 */ 725 public void setDirective(int directive) { 726 this.directive = directive; 727 } 728 }