View Javadoc

1   /*
2    $Id: Closure.java,v 1.41 2004/12/14 06:25:17 spullara Exp $
3   
4    Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
5   
6    Redistribution and use of this software and associated documentation
7    ("Software"), with or without modification, are permitted provided
8    that the following conditions are met:
9   
10   1. Redistributions of source code must retain copyright
11      statements and notices.  Redistributions must also contain a
12      copy of this document.
13  
14   2. Redistributions in binary form must reproduce the
15      above copyright notice, this list of conditions and the
16      following disclaimer in the documentation and/or other
17      materials provided with the distribution.
18  
19   3. The name "groovy" must not be used to endorse or promote
20      products derived from this Software without prior written
21      permission of The Codehaus.  For written permission,
22      please contact info@codehaus.org.
23  
24   4. Products derived from this Software may not be called "groovy"
25      nor may "groovy" appear in their names without prior written
26      permission of The Codehaus. "groovy" is a registered
27      trademark of The Codehaus.
28  
29   5. Due credit should be given to The Codehaus -
30      http://groovy.codehaus.org/
31  
32   THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
33   ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
34   NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
35   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
36   THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
37   INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
38   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
39   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
40   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
41   STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
42   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
43   OF THE POSSIBILITY OF SUCH DAMAGE.
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                         // lets try invoke method on the owner
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                             // lets try invoke method on the delegate
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 // lets try getting the property on the owner
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 // lets try getting the property on the delegate
170                         return InvokerHelper.getProperty(this.delegate, property);
171                     } catch (GroovyRuntimeException e2) {
172 // ignore, we'll throw e1
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 // lets try setting the property on the owner
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 // lets try setting the property on the delegate
195                         InvokerHelper.setProperty(this.delegate, property, newValue);
196                         return;
197                     } catch (GroovyRuntimeException e2) {
198 // ignore, we'll throw e1
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             // We have a closure which supports variable arguments and we haven't got actual
253             // parameters which have exactly the right number of parameters and ends with a null or an Object[]
254             //
255             if (params.length < lastParam) {
256                 //
257                 // Not enough parameters throw exception
258                 //
259                 // Note we allow there to be one fewer actual parameter than the number of formal parameters
260                 // in this case we pass an zero length Object[] as the last parameter
261                 //
262                 throw new IncorrectClosureArgumentsException(this, params, this.parameterTypes);
263             } else {
264                 final Object rest[] = new Object[params.length - lastParam];	 // array used to pass the rest of the paraters
265 
266                 // fill the parameter array up to but not including the last one
267                 System.arraycopy(params, 0, actualParameters, 0, lastParam);
268 
269                 // put the rest of the parameters in the overflow araay
270                 System.arraycopy(params, lastParam, rest, 0, rest.length);
271 
272                 // pass the overflow array as the last parameter
273                 actualParameters[lastParam] = rest;
274 
275                 return callViaReflection(actualParameters);
276             }
277         }
278 
279         if (params.length == 0) {
280             // pass a single null parameter if no parameters specified
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             // invoke the closure
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     /* (non-Javadoc)
392      * @see java.lang.Runnable#run()
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     /* (non-Javadoc)
416      * @see java.lang.Object#clone()
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         /* (non-Javadoc)
432      * @see groovy.lang.Writable#writeTo(java.io.Writer)
433      */
434         public Writer writeTo(Writer out) throws IOException {
435             Closure.this.call(out);
436 
437             return out;
438         }
439 
440         /* (non-Javadoc)
441          * @see groovy.lang.GroovyObject#invokeMethod(java.lang.String, java.lang.Object)
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         /* (non-Javadoc)
456          * @see groovy.lang.GroovyObject#getProperty(java.lang.String)
457          */
458         public Object getProperty(String property) {
459             return Closure.this.getProperty(property);
460         }
461 
462         /* (non-Javadoc)
463          * @see groovy.lang.GroovyObject#setProperty(java.lang.String, java.lang.Object)
464          */
465         public void setProperty(String property, Object newValue) {
466             Closure.this.setProperty(property, newValue);
467         }
468 
469         /* (non-Javadoc)
470          * @see groovy.lang.Closure#call()
471          */
472         public Object call() {
473             return Closure.this.call();
474         }
475 
476         /* (non-Javadoc)
477          * @see groovy.lang.Closure#call(java.lang.Object)
478          */
479         public Object call(Object arguments) {
480             return Closure.this.call(arguments);
481         }
482 
483         /* (non-Javadoc)
484          * @see groovy.lang.Closure#doCall(java.lang.Object)
485          */
486         protected Object doCall(Object p1) {
487             return Closure.this.doCall(p1);
488         }
489 
490         /* (non-Javadoc)
491          * @see groovy.lang.Closure#doCall(java.lang.Object, java.lang.Object)
492          */
493         protected Object doCall(Object p1, Object p2) {
494             return Closure.this.doCall(p1, p2);
495         }
496 
497         /* (non-Javadoc)
498          * @see groovy.lang.Closure#getDelegate()
499          */
500         public Object getDelegate() {
501             return Closure.this.getDelegate();
502         }
503 
504         /* (non-Javadoc)
505          * @see groovy.lang.Closure#setDelegate(java.lang.Object)
506          */
507         public void setDelegate(Object delegate) {
508             Closure.this.setDelegate(delegate);
509         }
510 
511         /* (non-Javadoc)
512          * @see groovy.lang.Closure#getParameterTypes()
513          */
514         public Class[] getParameterTypes() {
515             return Closure.this.getParameterTypes();
516         }
517 
518         /* (non-Javadoc)
519          * @see groovy.lang.Closure#asWritable()
520          */
521         public Closure asWritable() {
522             return this;
523         }
524 
525         /* (non-Javadoc)
526          * @see java.lang.Runnable#run()
527          */
528         public void run() {
529             Closure.this.run();
530         }
531 
532         /* (non-Javadoc)
533          * @see groovy.lang.Closure#curry(java.lang.Object[])
534          */
535         public Closure curry(Object[] arguments) {
536             return Closure.this.curry(arguments).asWritable();
537         }
538 
539         /* (non-Javadoc)
540          * @see java.lang.Object#clone()
541          */
542         public Object clone() {
543             return ((Closure) Closure.this.clone()).asWritable();
544         }
545 
546         /* (non-Javadoc)
547          * @see java.lang.Object#hashCode()
548          */
549         public int hashCode() {
550             return Closure.this.hashCode();
551         }
552 
553         /* (non-Javadoc)
554          * @see java.lang.Object#equals(java.lang.Object)
555          */
556         public boolean equals(Object arg0) {
557             return Closure.this.equals(arg0);
558         }
559 
560         /* (non-Javadoc)
561          * @see java.lang.Object#toString()
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 }