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
50 import java.io.IOException;
51 import java.io.StringWriter;
52 import java.io.Writer;
53 import java.lang.reflect.InvocationTargetException;
54 import java.lang.reflect.Method;
55 import java.security.AccessController;
56 import java.security.PrivilegedAction;
57
58 /***
59 * Represents any closure object in Groovy.
60 *
61 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
62 * @author <a href="mailto:tug@wilson.co.uk">John Wilson</a>
63 * @version $Revision: 1.41 $
64 */
65 public abstract class Closure extends GroovyObjectSupport implements Cloneable, Runnable {
66
67 private static final Object noParameters[] = new Object[]{null};
68 private static final Object emptyArray[] = new Object[0];
69 private static final Object emptyArrayParameter[] = new Object[]{emptyArray};
70
71 private Object delegate;
72 private final Object owner;
73 private final Method doCallMethod;
74 private final boolean supportsVarargs;
75 private final Class parameterTypes[];
76 private final int numberOfParameters;
77 private Object curriedParams[] = emptyArray;
78
79
80 private int directive = 0;
81 public static int DONE = 1;
82 public static int SKIP = 2;
83
84 public Closure(Object delegate) {
85 this.delegate = delegate;
86 this.owner = delegate;
87
88 Class closureClass = this.getClass();
89
90 while (true) {
91 final Method methods[] = closureClass.getDeclaredMethods();
92
93 int i = 0;
94
95 while (!methods[i].getName().equals("doCall") && ++i != methods.length) ;
96
97 if (i < methods.length) {
98 this.doCallMethod = methods[i];
99 break;
100 }
101
102 closureClass = closureClass.getSuperclass();
103 }
104
105 AccessController.doPrivileged(new PrivilegedAction() {
106 public Object run() {
107 Closure.this.doCallMethod.setAccessible(true);
108 return null;
109 }
110 });
111
112 this.parameterTypes = this.doCallMethod.getParameterTypes();
113
114 this.numberOfParameters = this.parameterTypes.length;
115
116 if (this.numberOfParameters != 0) {
117 this.supportsVarargs = this.parameterTypes[this.numberOfParameters - 1].equals(Object[].class);
118 } else {
119 this.supportsVarargs = false;
120 }
121 }
122
123 public Object invokeMethod(String method, Object arguments) {
124 if ("doCall".equals(method) || "call".equals(method)) {
125 return call(arguments);
126 } else if ("curry".equals(method)) {
127 return curry((Object[]) arguments);
128 } else {
129 try {
130 return getMetaClass().invokeMethod(this, method, arguments);
131 } catch (MissingMethodException e) {
132 if (owner != this) {
133 try {
134
135 return InvokerHelper.invokeMethod(this.owner, method, arguments);
136 } catch (GroovyRuntimeException e1) {
137 if (this.delegate != null && this.delegate != this && this.delegate != this.owner) {
138
139 return InvokerHelper.invokeMethod(this.delegate, method, arguments);
140 }
141 }
142 }
143 throw e;
144 }
145 }
146
147 }
148
149 public Object getProperty(String property) {
150 if ("delegate".equals(property)) {
151 return getDelegate();
152 } else if ("owner".equals(property)) {
153 return getOwner();
154 } else if ("method".equals(property)) {
155 return getMethod();
156 } else if ("parameterTypes".equals(property)) {
157 return getParameterTypes();
158 } else if ("metaClass".equals(property)) {
159 return getMetaClass();
160 } else if ("class".equals(property)) {
161 return getClass();
162 } else {
163 try {
164
165 return InvokerHelper.getProperty(this.owner, property);
166 } catch (GroovyRuntimeException e1) {
167 if (this.delegate != null && this.delegate != this && this.delegate != this.owner) {
168 try {
169
170 return InvokerHelper.getProperty(this.delegate, property);
171 } catch (GroovyRuntimeException e2) {
172
173 }
174 }
175
176 throw e1;
177 }
178 }
179 }
180
181 public void setProperty(String property, Object newValue) {
182 if ("delegate".equals(property)) {
183 setDelegate(newValue);
184 } else if ("metaClass".equals(property)) {
185 setMetaClass((MetaClass) newValue);
186 } else {
187 try {
188
189 InvokerHelper.setProperty(this.owner, property, newValue);
190 return;
191 } catch (GroovyRuntimeException e1) {
192 if (this.delegate != null && this.delegate != this && this.delegate != this.owner) {
193 try {
194
195 InvokerHelper.setProperty(this.delegate, property, newValue);
196 return;
197 } catch (GroovyRuntimeException e2) {
198
199 }
200 }
201
202 throw e1;
203 }
204 }
205 }
206
207 /***
208 * Invokes the closure without any parameters, returning any value if applicable.
209 *
210 * @return the value if applicable or null if there is no return statement in the closure
211 */
212 public Object call() {
213 return call(emptyArray);
214 }
215
216 /***
217 * Invokes the closure, returning any value if applicable.
218 *
219 * @param arguments could be a single value or a List of values
220 * @return the value if applicable or null if there is no return statement in the closure
221 */
222 public Object call(final Object arguments) {
223 final Object params[];
224
225 if (this.curriedParams.length != 0) {
226 final Object args[];
227
228 if (arguments instanceof Object[]) {
229 args = (Object[]) arguments;
230 } else {
231 args = new Object[]{arguments};
232 }
233
234 params = new Object[this.curriedParams.length + args.length];
235
236 System.arraycopy(this.curriedParams, 0, params, 0, this.curriedParams.length);
237 System.arraycopy(args, 0, params, this.curriedParams.length, args.length);
238 } else {
239 if (arguments instanceof Object[]) {
240 params = (Object[]) arguments;
241 } else {
242 return doCall(arguments);
243 }
244 }
245
246 final int lastParam = this.numberOfParameters - 1;
247
248 if (this.supportsVarargs && !(this.numberOfParameters == params.length && (params[lastParam] == null || params[lastParam].getClass() == Object[].class))) {
249 final Object actualParameters[] = new Object[this.numberOfParameters];
250
251
252
253
254
255 if (params.length < lastParam) {
256
257
258
259
260
261
262 throw new IncorrectClosureArgumentsException(this, params, this.parameterTypes);
263 } else {
264 final Object rest[] = new Object[params.length - lastParam];
265
266
267 System.arraycopy(params, 0, actualParameters, 0, lastParam);
268
269
270 System.arraycopy(params, lastParam, rest, 0, rest.length);
271
272
273 actualParameters[lastParam] = rest;
274
275 return callViaReflection(actualParameters);
276 }
277 }
278
279 if (params.length == 0) {
280
281 return doCall(null);
282 } else if (params.length == 1) {
283 return doCall(params[0]);
284 } else if (params.length == 2) {
285 return doCall(params[0], params[1]);
286 } else {
287 return callViaReflection(params);
288 }
289 }
290
291 protected static Object throwRuntimeException(Throwable throwable) {
292 if (throwable instanceof RuntimeException) {
293 throw (RuntimeException) throwable;
294 } else {
295 throw new GroovyRuntimeException(throwable.getMessage(), throwable);
296 }
297 }
298
299 /***
300 * An attempt to optimise calling closures with one parameter
301 * If the closure has one untyped parameter then it will overload this function
302 * If not this will be called ans will use reflection to deal with the case of a
303 * single typed parameter
304 *
305 * @param p1
306 * @return the result of calling the closure
307 */
308 protected Object doCall(final Object p1) {
309 return callViaReflection(new Object[]{p1});
310 }
311
312 /***
313 * An attempt to optimise calling closures with two parameters
314 * If the closure has two untyped parameters then it will overload this function
315 * If not this will be called ans will use reflection to deal with the case of one
316 * or two typed parameters
317 *
318 * @param p1
319 * @return the result of calling the closure
320 */
321
322 protected Object doCall(final Object p1, final Object p2) {
323 return callViaReflection(new Object[]{p1, p2});
324 }
325
326 private Object callViaReflection(final Object params[]) {
327 try {
328
329 return this.doCallMethod.invoke(this, params);
330 } catch (final IllegalArgumentException e) {
331 throw new IncorrectClosureArgumentsException(this, params, this.parameterTypes);
332 } catch (final IllegalAccessException e) {
333 final Throwable cause = e.getCause();
334
335 return throwRuntimeException((cause == null) ? e : cause);
336 } catch (final InvocationTargetException e) {
337 final Throwable cause = e.getCause();
338
339 return throwRuntimeException((cause == null) ? e : cause);
340 }
341 }
342
343 /***
344 * Used when a closure wraps a method on a class
345 *
346 * @return empty string
347 */
348 public String getMethod() {
349 return "";
350 }
351
352 /***
353 * @return the owner Object to which method calls will go which is
354 * typically the outer class when the closure is constructed
355 */
356 public Object getOwner() {
357 return this.owner;
358 }
359
360 /***
361 * @return the delegate Object to which method calls will go which is
362 * typically the outer class when the closure is constructed
363 */
364 public Object getDelegate() {
365 return this.delegate;
366 }
367
368 /***
369 * Allows the delegate to be changed such as when performing markup building
370 *
371 * @param delegate
372 */
373 public void setDelegate(Object delegate) {
374 this.delegate = delegate;
375 }
376
377 /***
378 * @return the parameter types of this closure
379 */
380 public Class[] getParameterTypes() {
381 return this.parameterTypes;
382 }
383
384 /***
385 * @return a version of this closure which implements Writable
386 */
387 public Closure asWritable() {
388 return new WritableClosure();
389 }
390
391
392
393
394 public void run() {
395 call();
396 }
397
398 /***
399 * Support for closure currying
400 *
401 * @param arguments
402 */
403 public Closure curry(final Object arguments[]) {
404 final Closure curriedClosure = (Closure) this.clone();
405 final Object newCurriedParams[] = new Object[curriedClosure.curriedParams.length + arguments.length];
406
407 System.arraycopy(curriedClosure.curriedParams, 0, newCurriedParams, 0, curriedClosure.curriedParams.length);
408 System.arraycopy(arguments, 0, newCurriedParams, curriedClosure.curriedParams.length, arguments.length);
409
410 curriedClosure.curriedParams = newCurriedParams;
411
412 return curriedClosure;
413 }
414
415
416
417
418 public Object clone() {
419 try {
420 return super.clone();
421 } catch (final CloneNotSupportedException e) {
422 return null;
423 }
424 }
425
426 private class WritableClosure extends Closure implements Writable {
427 public WritableClosure() {
428 super(null);
429 }
430
431
432
433
434 public Writer writeTo(Writer out) throws IOException {
435 Closure.this.call(out);
436
437 return out;
438 }
439
440
441
442
443 public Object invokeMethod(String method, Object arguments) {
444 if ("clone".equals(method)) {
445 return clone();
446 } else if ("curry".equals(method)) {
447 return curry((Object[]) arguments);
448 } else if ("asWritable".equals(method)) {
449 return asWritable();
450 } else {
451 return Closure.this.invokeMethod(method, arguments);
452 }
453 }
454
455
456
457
458 public Object getProperty(String property) {
459 return Closure.this.getProperty(property);
460 }
461
462
463
464
465 public void setProperty(String property, Object newValue) {
466 Closure.this.setProperty(property, newValue);
467 }
468
469
470
471
472 public Object call() {
473 return Closure.this.call();
474 }
475
476
477
478
479 public Object call(Object arguments) {
480 return Closure.this.call(arguments);
481 }
482
483
484
485
486 protected Object doCall(Object p1) {
487 return Closure.this.doCall(p1);
488 }
489
490
491
492
493 protected Object doCall(Object p1, Object p2) {
494 return Closure.this.doCall(p1, p2);
495 }
496
497
498
499
500 public Object getDelegate() {
501 return Closure.this.getDelegate();
502 }
503
504
505
506
507 public void setDelegate(Object delegate) {
508 Closure.this.setDelegate(delegate);
509 }
510
511
512
513
514 public Class[] getParameterTypes() {
515 return Closure.this.getParameterTypes();
516 }
517
518
519
520
521 public Closure asWritable() {
522 return this;
523 }
524
525
526
527
528 public void run() {
529 Closure.this.run();
530 }
531
532
533
534
535 public Closure curry(Object[] arguments) {
536 return Closure.this.curry(arguments).asWritable();
537 }
538
539
540
541
542 public Object clone() {
543 return ((Closure) Closure.this.clone()).asWritable();
544 }
545
546
547
548
549 public int hashCode() {
550 return Closure.this.hashCode();
551 }
552
553
554
555
556 public boolean equals(Object arg0) {
557 return Closure.this.equals(arg0);
558 }
559
560
561
562
563 public String toString() {
564 final StringWriter writer = new StringWriter();
565
566 try {
567 writeTo(writer);
568 } catch (IOException e) {
569 return null;
570 }
571
572 return writer.toString();
573 }
574 }
575
576 /***
577 * @return Returns the directive.
578 */
579 public int getDirective() {
580 return directive;
581 }
582
583 /***
584 * @param directive The directive to set.
585 */
586 public void setDirective(int directive) {
587 this.directive = directive;
588 }
589 }