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 }