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 }