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.45 $
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
290 return doCall(null);
291 } else if (params.length == 1) {
292 return doCall(params[0]);
293 } else if (params.length == 2) {
294 return doCall(params[0], params[1]);
295 } else {
296 return callViaReflection(params);
297 }
298 }
299
300 protected static Object throwRuntimeException(Throwable throwable) {
301 if (throwable instanceof RuntimeException) {
302 throw (RuntimeException) throwable;
303 } else {
304 throw new GroovyRuntimeException(throwable.getMessage(), throwable);
305 }
306 }
307
308 /***
309 * An attempt to optimise calling closures with one parameter
310 * If the closure has one untyped parameter then it will overload this function
311 * If not this will be called ans will use reflection to deal with the case of a
312 * single typed parameter
313 *
314 * @param p1
315 * @return the result of calling the closure
316 */
317 protected Object doCall(final Object p1) {
318 return callViaReflection(new Object[]{p1});
319 }
320
321 /***
322 * An attempt to optimise calling closures with two parameters
323 * If the closure has two untyped parameters then it will overload this function
324 * If not this will be called ans will use reflection to deal with the case of one
325 * or two typed parameters
326 *
327 * @param p1
328 * @return the result of calling the closure
329 */
330
331 protected Object doCall(final Object p1, final Object p2) {
332 return callViaReflection(new Object[]{p1, p2});
333 }
334
335 private Object callViaReflection(final Object params[]) {
336 try {
337
338 return this.doCallMethod.invoke(this, params);
339 } catch (final IllegalArgumentException e) {
340 throw new IncorrectClosureArgumentsException(this, params, this.parameterTypes);
341 } catch (final IllegalAccessException e) {
342 final Throwable cause = e.getCause();
343
344 return throwRuntimeException((cause == null) ? e : cause);
345 } catch (final InvocationTargetException e) {
346 final Throwable cause = e.getCause();
347
348 return throwRuntimeException((cause == null) ? e : cause);
349 }
350 }
351
352 /***
353 * Used when a closure wraps a method on a class
354 *
355 * @return empty string
356 */
357 public String getMethod() {
358 return "";
359 }
360
361 /***
362 * @return the owner Object to which method calls will go which is
363 * typically the outer class when the closure is constructed
364 */
365 public Object getOwner() {
366 return this.owner;
367 }
368
369 /***
370 * @return the delegate Object to which method calls will go which is
371 * typically the outer class when the closure is constructed
372 */
373 public Object getDelegate() {
374 return this.delegate;
375 }
376
377 /***
378 * Allows the delegate to be changed such as when performing markup building
379 *
380 * @param delegate
381 */
382 public void setDelegate(Object delegate) {
383 this.delegate = delegate;
384 }
385
386 /***
387 * @return the parameter types of this closure
388 */
389 public Class[] getParameterTypes() {
390 return this.parameterTypes;
391 }
392
393 /***
394 * @return a version of this closure which implements Writable
395 */
396 public Closure asWritable() {
397 return new WritableClosure();
398 }
399
400
401
402
403 public void run() {
404 call();
405 }
406
407 /***
408 * Support for closure currying
409 *
410 * @param arguments
411 */
412 public Closure curry(final Object arguments[]) {
413 final Closure curriedClosure = (Closure) this.clone();
414 final Object newCurriedParams[] = new Object[curriedClosure.curriedParams.length + arguments.length];
415
416 System.arraycopy(curriedClosure.curriedParams, 0, newCurriedParams, 0, curriedClosure.curriedParams.length);
417 System.arraycopy(arguments, 0, newCurriedParams, curriedClosure.curriedParams.length, arguments.length);
418
419 curriedClosure.curriedParams = newCurriedParams;
420
421 return curriedClosure;
422 }
423
424
425
426
427 public Object clone() {
428 try {
429 return super.clone();
430 } catch (final CloneNotSupportedException e) {
431 return null;
432 }
433 }
434
435 private class WritableClosure extends Closure implements Writable {
436 public WritableClosure() {
437 super(null);
438 }
439
440
441
442
443 public Writer writeTo(Writer out) throws IOException {
444 Closure.this.call(out);
445
446 return out;
447 }
448
449
450
451
452 public Object invokeMethod(String method, Object arguments) {
453 if ("clone".equals(method)) {
454 return clone();
455 } else if ("curry".equals(method)) {
456 return curry((Object[]) arguments);
457 } else if ("asWritable".equals(method)) {
458 return asWritable();
459 } else {
460 return Closure.this.invokeMethod(method, arguments);
461 }
462 }
463
464
465
466
467 public Object getProperty(String property) {
468 return Closure.this.getProperty(property);
469 }
470
471
472
473
474 public void setProperty(String property, Object newValue) {
475 Closure.this.setProperty(property, newValue);
476 }
477
478
479
480
481 public Object call() {
482 return Closure.this.call();
483 }
484
485
486
487
488 public Object call(Object arguments) {
489 return Closure.this.call(arguments);
490 }
491
492
493
494
495 protected Object doCall(Object p1) {
496 return Closure.this.doCall(p1);
497 }
498
499
500
501
502 protected Object doCall(Object p1, Object p2) {
503 return Closure.this.doCall(p1, p2);
504 }
505
506
507
508
509 public Object getDelegate() {
510 return Closure.this.getDelegate();
511 }
512
513
514
515
516 public void setDelegate(Object delegate) {
517 Closure.this.setDelegate(delegate);
518 }
519
520
521
522
523 public Class[] getParameterTypes() {
524 return Closure.this.getParameterTypes();
525 }
526
527
528
529
530 public Closure asWritable() {
531 return this;
532 }
533
534
535
536
537 public void run() {
538 Closure.this.run();
539 }
540
541
542
543
544 public Closure curry(Object[] arguments) {
545 return Closure.this.curry(arguments).asWritable();
546 }
547
548
549
550
551 public Object clone() {
552 return ((Closure) Closure.this.clone()).asWritable();
553 }
554
555
556
557
558 public int hashCode() {
559 return Closure.this.hashCode();
560 }
561
562
563
564
565 public boolean equals(Object arg0) {
566 return Closure.this.equals(arg0);
567 }
568
569
570
571
572 public String toString() {
573 final StringWriter writer = new StringWriter();
574
575 try {
576 writeTo(writer);
577 } catch (IOException e) {
578 return null;
579 }
580
581 return writer.toString();
582 }
583 }
584
585 /***
586 * @return Returns the directive.
587 */
588 public int getDirective() {
589 return directive;
590 }
591
592 /***
593 * @param directive The directive to set.
594 */
595 public void setDirective(int directive) {
596 this.directive = directive;
597 }
598 }