1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46 package groovy.lang;
47
48 import org.codehaus.groovy.runtime.InvokerHelper;
49 import org.codehaus.groovy.runtime.InvokerInvocationException;
50
51 import java.util.*;
52 import java.io.IOException;
53 import java.io.StringWriter;
54 import java.io.Writer;
55 import java.lang.reflect.InvocationTargetException;
56 import java.lang.reflect.Method;
57 import java.security.AccessController;
58 import java.security.PrivilegedAction;
59
60 /***
61 * Represents any closure object in Groovy.
62 *
63 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
64 * @author <a href="mailto:tug@wilson.co.uk">John Wilson</a>
65 * @version $Revision: 1.50 $
66 */
67 public abstract class Closure extends GroovyObjectSupport implements Cloneable, Runnable {
68
69 private static final Object noParameters[] = new Object[]{null};
70 private static final Object emptyArray[] = new Object[0];
71 private static final Object emptyArrayParameter[] = new Object[]{emptyArray};
72
73 private Object delegate;
74 private final Object owner;
75 private final Method doCallMethod;
76 private final HashMap callsMap;
77 private final boolean supportsVarargs;
78 private final Class[] parameterTypes;
79 private final int numberOfParameters;
80 private Object curriedParams[] = emptyArray;
81
82
83 private int directive = 0;
84 public static int DONE = 1;
85 public static int SKIP = 2;
86
87 public Closure(Object delegate) {
88 this.delegate = delegate;
89 this.owner = delegate;
90
91 Class closureClass = this.getClass();
92 callsMap = new HashMap();
93 int paramLenTemp = -1;
94 Method doCallTemp = null;
95
96 while (true) {
97 final Method methods[] = closureClass.getDeclaredMethods();
98
99 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
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
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
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
208 return InvokerHelper.getProperty(this.delegate, property);
209 } catch (GroovyRuntimeException e2) {
210
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
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
233 InvokerHelper.setProperty(this.delegate, property, newValue);
234 return;
235 } catch (GroovyRuntimeException e2) {
236
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
295
296
297 if (params.length < lastParam) {
298
299
300
301
302
303
304 throw new IncorrectClosureArgumentsException(this, params, this.parameterTypes);
305 } else {
306 final Object rest[] = new Object[params.length - lastParam];
307
308
309 System.arraycopy(params, 0, actualParameters, 0, lastParam);
310
311
312 System.arraycopy(params, lastParam, rest, 0, rest.length);
313
314
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
382
383
384 if (params.length < lastParam) {
385
386
387
388
389
390
391 throw new IncorrectClosureArgumentsException(this, params, this.parameterTypes);
392 } else {
393 final Object rest[] = new Object[params.length - lastParam];
394
395
396 System.arraycopy(params, 0, actualParameters, 0, lastParam);
397
398
399 System.arraycopy(params, lastParam, rest, 0, rest.length);
400
401
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
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
531
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
555
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
571
572
573 public Writer writeTo(Writer out) throws IOException {
574 Closure.this.call(out);
575
576 return out;
577 }
578
579
580
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
595
596
597 public Object getProperty(String property) {
598 return Closure.this.getProperty(property);
599 }
600
601
602
603
604 public void setProperty(String property, Object newValue) {
605 Closure.this.setProperty(property, newValue);
606 }
607
608
609
610
611 public Object call() {
612 return Closure.this.call();
613 }
614
615
616
617
618 public Object call(Object arguments) {
619 return Closure.this.call(arguments);
620 }
621
622
623
624
625 protected Object doCall(Object p1) {
626 return Closure.this.doCall(p1);
627 }
628
629
630
631
632 protected Object doCall(Object p1, Object p2) {
633 return Closure.this.doCall(p1, p2);
634 }
635
636
637
638
639 public Object getDelegate() {
640 return Closure.this.getDelegate();
641 }
642
643
644
645
646 public void setDelegate(Object delegate) {
647 Closure.this.setDelegate(delegate);
648 }
649
650
651
652
653 public Class[] getParameterTypes() {
654 return Closure.this.getParameterTypes();
655 }
656
657
658
659
660 public Closure asWritable() {
661 return this;
662 }
663
664
665
666
667 public void run() {
668 Closure.this.run();
669 }
670
671
672
673
674 public Closure curry(Object[] arguments) {
675 return Closure.this.curry(arguments).asWritable();
676 }
677
678
679
680
681 public Object clone() {
682 return ((Closure) Closure.this.clone()).asWritable();
683 }
684
685
686
687
688 public int hashCode() {
689 return Closure.this.hashCode();
690 }
691
692
693
694
695 public boolean equals(Object arg0) {
696 return Closure.this.equals(arg0);
697 }
698
699
700
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 }