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 java.io.IOException;
49 import java.io.StringWriter;
50 import java.io.Writer;
51 import java.lang.reflect.InvocationTargetException;
52 import java.lang.reflect.Method;
53 import java.security.AccessController;
54 import java.security.PrivilegedAction;
55
56 import org.codehaus.groovy.runtime.InvokerHelper;
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.40 $
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 try {
139
140 return InvokerHelper.invokeMethod(this.delegate, method, arguments);
141 } catch (GroovyRuntimeException e2) {
142
143 }
144 }
145 }
146 }
147 throw e;
148 }
149 }
150
151 }
152
153 public Object getProperty(String property) {
154 if ("delegate".equals(property)) {
155 return getDelegate();
156 } else if ("owner".equals(property)) {
157 return getOwner();
158 } else if ("method".equals(property)) {
159 return getMethod();
160 } else if ("parameterTypes".equals(property)) {
161 return getParameterTypes();
162 } else if ("metaClass".equals(property)) {
163 return getMetaClass();
164 } else if ("class".equals(property)) {
165 return getClass();
166 } else {
167 try {
168
169 return InvokerHelper.getProperty(this.owner, property);
170 } catch (GroovyRuntimeException e1) {
171 if (this.delegate != null && this.delegate != this && this.delegate != this.owner) {
172 try {
173
174 return InvokerHelper.getProperty(this.delegate, property);
175 } catch (GroovyRuntimeException e2) {
176
177 }
178 }
179
180 throw e1;
181 }
182 }
183 }
184
185 public void setProperty(String property, Object newValue) {
186 if ("delegate".equals(property)) {
187 setDelegate(newValue);
188 } else if ("metaClass".equals(property)) {
189 setMetaClass((MetaClass)newValue);
190 } else {
191 try {
192
193 InvokerHelper.setProperty(this.owner, property, newValue);
194 return;
195 } catch (GroovyRuntimeException e1) {
196 if (this.delegate != null && this.delegate != this && this.delegate != this.owner) {
197 try {
198
199 InvokerHelper.setProperty(this.delegate, property, newValue);
200 return;
201 } catch (GroovyRuntimeException e2) {
202
203 }
204 }
205
206 throw e1;
207 }
208 }
209 }
210
211 /***
212 * Invokes the closure without any parameters, returning any value if applicable.
213 *
214 * @return the value if applicable or null if there is no return statement in the closure
215 */
216 public Object call() {
217 return call(emptyArray);
218 }
219
220 /***
221 * Invokes the closure, returning any value if applicable.
222 *
223 * @param arguments could be a single value or a List of values
224 * @return the value if applicable or null if there is no return statement in the closure
225 */
226 public Object call(final Object arguments) {
227 final Object params[];
228
229 if (this.curriedParams.length != 0) {
230 final Object args[];
231
232 if (arguments instanceof Object[]) {
233 args = (Object[])arguments;
234 } else {
235 args = new Object[] {arguments};
236 }
237
238 params = new Object[this.curriedParams.length + args.length];
239
240 System.arraycopy(this.curriedParams, 0, params, 0, this.curriedParams.length);
241 System.arraycopy(args, 0, params, this.curriedParams.length, args.length);
242 } else {
243 if (arguments instanceof Object[]) {
244 params = (Object[])arguments;
245 } else {
246 return doCall(arguments);
247 }
248 }
249
250 final int lastParam = this.numberOfParameters - 1;
251
252 if (this.supportsVarargs && !(this.numberOfParameters == params.length && (params[lastParam] == null || params[lastParam].getClass() == Object [].class))) {
253 final Object actualParameters[] = new Object[this.numberOfParameters];
254
255
256
257
258
259 if (params.length < lastParam) {
260
261
262
263
264
265
266 throw new IncorrectClosureArgumentsException(this, params, this.parameterTypes);
267 } else {
268 final Object rest[] = new Object[params.length - lastParam];
269
270
271 System.arraycopy(params, 0, actualParameters, 0, lastParam);
272
273
274 System.arraycopy(params, lastParam, rest, 0, rest.length);
275
276
277 actualParameters[lastParam] = rest;
278
279 return callViaReflection(actualParameters);
280 }
281 }
282
283 if (params.length == 0) {
284
285 return doCall(null);
286 } else if (params.length == 1) {
287 return doCall(params[0]);
288 } else if (params.length == 2) {
289 return doCall(params[0], params[1]);
290 } else {
291 return callViaReflection(params);
292 }
293 }
294
295 protected static Object throwRuntimeException(Throwable throwable) {
296 if (throwable instanceof RuntimeException) {
297 throw (RuntimeException) throwable;
298 } else {
299 throw new GroovyRuntimeException(throwable.getMessage(), throwable);
300 }
301 }
302
303 /***
304 * An attempt to optimise calling closures with one parameter
305 * If the closure has one untyped parameter then it will overload this function
306 * If not this will be called ans will use reflection to deal with the case of a
307 * single typed parameter
308 * @param p1
309 * @return the result of calling the closure
310 */
311 protected Object doCall(final Object p1) {
312 return callViaReflection(new Object[] {p1});
313 }
314
315 /***
316 * An attempt to optimise calling closures with two parameters
317 * If the closure has two untyped parameters then it will overload this function
318 * If not this will be called ans will use reflection to deal with the case of one
319 * or two typed parameters
320 * @param p1
321 * @return the result of calling the closure
322 */
323
324 protected Object doCall(final Object p1, final Object p2) {
325 return callViaReflection(new Object[] {p1, p2});
326 }
327
328 private Object callViaReflection(final Object params[]) {
329 try {
330
331 return this.doCallMethod.invoke(this, params);
332 } catch (final IllegalArgumentException e) {
333 throw new IncorrectClosureArgumentsException(this, params, this.parameterTypes);
334 } catch (final IllegalAccessException e) {
335 final Throwable cause = e.getCause();
336
337 return throwRuntimeException((cause == null) ? e : cause);
338 } catch (final InvocationTargetException e) {
339 final Throwable cause = e.getCause();
340
341 return throwRuntimeException((cause == null) ? e : cause);
342 }
343 }
344
345 /***
346 * Used when a closure wraps a method on a class
347 *
348 * @return empty string
349 */
350 public String getMethod() {
351 return "";
352 }
353
354 /***
355 * @return the owner Object to which method calls will go which is
356 * typically the outer class when the closure is constructed
357 */
358 public Object getOwner() {
359 return this.owner;
360 }
361
362 /***
363 * @return the delegate Object to which method calls will go which is
364 * typically the outer class when the closure is constructed
365 */
366 public Object getDelegate() {
367 return this.delegate;
368 }
369
370 /***
371 * Allows the delegate to be changed such as when performing markup building
372 * @param delegate
373 */
374 public void setDelegate(Object delegate) {
375 this.delegate = delegate;
376 }
377
378 /***
379 * @return the parameter types of this closure
380 */
381 public Class[] getParameterTypes() {
382 return this.parameterTypes;
383 }
384
385 /***
386 * @return a version of this closure which implements Writable
387 */
388 public Closure asWritable() {
389 return new WritableClosure();
390 }
391
392
393
394
395 public void run() {
396 call();
397 }
398
399 /***
400 * Support for closure currying
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 * @return Returns the directive.
577 */
578 public int getDirective() {
579 return directive;
580 }
581 /***
582 * @param directive The directive to set.
583 */
584 public void setDirective(int directive) {
585 this.directive = directive;
586 }
587 }