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