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.53 $
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 Class clazz = closureClass;
98 final Method[] methods = (Method[]) AccessController.doPrivileged(new PrivilegedAction() {
99 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
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
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
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
202 return InvokerHelper.getProperty(this.delegate, property);
203 } catch (GroovyRuntimeException e2) {
204
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
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
227 InvokerHelper.setProperty(this.delegate, property, newValue);
228 return;
229 } catch (GroovyRuntimeException e2) {
230
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
294
295
296 if (params.length < lastParam) {
297
298
299
300
301
302
303 throw new IncorrectClosureArgumentsException(this, params, this.parameterTypes);
304 } else {
305 final Object rest[] = new Object[params.length - lastParam];
306
307
308 System.arraycopy(params, 0, actualParameters, 0, lastParam);
309
310
311 System.arraycopy(params, lastParam, rest, 0, rest.length);
312
313
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
348
349
350 if (params.length < lastParam) {
351
352
353
354
355
356
357 throw new IncorrectClosureArgumentsException(this, params, this.parameterTypes);
358 } else {
359 final Object rest[] = new Object[params.length - lastParam];
360
361
362 System.arraycopy(params, 0, actualParameters, 0, lastParam);
363
364
365 System.arraycopy(params, lastParam, rest, 0, rest.length);
366
367
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
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
497
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
521
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
537
538
539 public Writer writeTo(Writer out) throws IOException {
540 Closure.this.call(new ParameterArray(out));
541
542 return out;
543 }
544
545
546
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
561
562
563 public Object getProperty(String property) {
564 return Closure.this.getProperty(property);
565 }
566
567
568
569
570 public void setProperty(String property, Object newValue) {
571 Closure.this.setProperty(property, newValue);
572 }
573
574
575
576
577 public Object call() {
578 return Closure.this.call();
579 }
580
581
582
583
584 public Object call(Object arguments) {
585 return Closure.this.call(arguments);
586 }
587
588
589
590
591 protected Object doCall(Object p1) {
592 return Closure.this.doCall(p1);
593 }
594
595
596
597
598 protected Object doCall(Object p1, Object p2) {
599 return Closure.this.doCall(p1, p2);
600 }
601
602
603
604
605 public Object getDelegate() {
606 return Closure.this.getDelegate();
607 }
608
609
610
611
612 public void setDelegate(Object delegate) {
613 Closure.this.setDelegate(delegate);
614 }
615
616
617
618
619 public Class[] getParameterTypes() {
620 return Closure.this.getParameterTypes();
621 }
622
623
624
625
626 public Closure asWritable() {
627 return this;
628 }
629
630
631
632
633 public void run() {
634 Closure.this.run();
635 }
636
637
638
639
640 public Closure curry(Object[] arguments) {
641 return Closure.this.curry(arguments).asWritable();
642 }
643
644
645
646
647 public Object clone() {
648 return ((Closure) Closure.this.clone()).asWritable();
649 }
650
651
652
653
654 public int hashCode() {
655 return Closure.this.hashCode();
656 }
657
658
659
660
661 public boolean equals(Object arg0) {
662 return Closure.this.equals(arg0);
663 }
664
665
666
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 }