View Javadoc

1   /*
2    $Id: Closure.java,v 1.45 2005/02/15 21:57:12 blackdrag 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  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                         // lets try invoke method on the owner
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                             // lets try invoke method on the delegate
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 // lets try getting the property on the owner
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 // lets try getting the property on the delegate
179                         return InvokerHelper.getProperty(this.delegate, property);
180                     } catch (GroovyRuntimeException e2) {
181 // ignore, we'll throw e1
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 // lets try setting the property on the owner
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 // lets try setting the property on the delegate
204                         InvokerHelper.setProperty(this.delegate, property, newValue);
205                         return;
206                     } catch (GroovyRuntimeException e2) {
207 // ignore, we'll throw e1
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             // We have a closure which supports variable arguments and we haven't got actual
262             // parameters which have exactly the right number of parameters and ends with a null or an Object[]
263             //
264             if (params.length < lastParam) {
265                 //
266                 // Not enough parameters throw exception
267                 //
268                 // Note we allow there to be one fewer actual parameter than the number of formal parameters
269                 // in this case we pass an zero length Object[] as the last parameter
270                 //
271                 throw new IncorrectClosureArgumentsException(this, params, this.parameterTypes);
272             } else {
273                 final Object rest[] = new Object[params.length - lastParam];	 // array used to pass the rest of the paraters
274 
275                 // fill the parameter array up to but not including the last one
276                 System.arraycopy(params, 0, actualParameters, 0, lastParam);
277 
278                 // put the rest of the parameters in the overflow araay
279                 System.arraycopy(params, lastParam, rest, 0, rest.length);
280 
281                 // pass the overflow array as the last parameter
282                 actualParameters[lastParam] = rest;
283 
284                 return callViaReflection(actualParameters);
285             }
286         }
287 
288         if (params.length == 0) {
289             // pass a single null parameter if no parameters specified
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             // invoke the closure
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     /* (non-Javadoc)
401      * @see java.lang.Runnable#run()
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     /* (non-Javadoc)
425      * @see java.lang.Object#clone()
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         /* (non-Javadoc)
441      * @see groovy.lang.Writable#writeTo(java.io.Writer)
442      */
443         public Writer writeTo(Writer out) throws IOException {
444             Closure.this.call(out);
445 
446             return out;
447         }
448 
449         /* (non-Javadoc)
450          * @see groovy.lang.GroovyObject#invokeMethod(java.lang.String, java.lang.Object)
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         /* (non-Javadoc)
465          * @see groovy.lang.GroovyObject#getProperty(java.lang.String)
466          */
467         public Object getProperty(String property) {
468             return Closure.this.getProperty(property);
469         }
470 
471         /* (non-Javadoc)
472          * @see groovy.lang.GroovyObject#setProperty(java.lang.String, java.lang.Object)
473          */
474         public void setProperty(String property, Object newValue) {
475             Closure.this.setProperty(property, newValue);
476         }
477 
478         /* (non-Javadoc)
479          * @see groovy.lang.Closure#call()
480          */
481         public Object call() {
482             return Closure.this.call();
483         }
484 
485         /* (non-Javadoc)
486          * @see groovy.lang.Closure#call(java.lang.Object)
487          */
488         public Object call(Object arguments) {
489             return Closure.this.call(arguments);
490         }
491 
492         /* (non-Javadoc)
493          * @see groovy.lang.Closure#doCall(java.lang.Object)
494          */
495         protected Object doCall(Object p1) {
496             return Closure.this.doCall(p1);
497         }
498 
499         /* (non-Javadoc)
500          * @see groovy.lang.Closure#doCall(java.lang.Object, java.lang.Object)
501          */
502         protected Object doCall(Object p1, Object p2) {
503             return Closure.this.doCall(p1, p2);
504         }
505 
506         /* (non-Javadoc)
507          * @see groovy.lang.Closure#getDelegate()
508          */
509         public Object getDelegate() {
510             return Closure.this.getDelegate();
511         }
512 
513         /* (non-Javadoc)
514          * @see groovy.lang.Closure#setDelegate(java.lang.Object)
515          */
516         public void setDelegate(Object delegate) {
517             Closure.this.setDelegate(delegate);
518         }
519 
520         /* (non-Javadoc)
521          * @see groovy.lang.Closure#getParameterTypes()
522          */
523         public Class[] getParameterTypes() {
524             return Closure.this.getParameterTypes();
525         }
526 
527         /* (non-Javadoc)
528          * @see groovy.lang.Closure#asWritable()
529          */
530         public Closure asWritable() {
531             return this;
532         }
533 
534         /* (non-Javadoc)
535          * @see java.lang.Runnable#run()
536          */
537         public void run() {
538             Closure.this.run();
539         }
540 
541         /* (non-Javadoc)
542          * @see groovy.lang.Closure#curry(java.lang.Object[])
543          */
544         public Closure curry(Object[] arguments) {
545             return Closure.this.curry(arguments).asWritable();
546         }
547 
548         /* (non-Javadoc)
549          * @see java.lang.Object#clone()
550          */
551         public Object clone() {
552             return ((Closure) Closure.this.clone()).asWritable();
553         }
554 
555         /* (non-Javadoc)
556          * @see java.lang.Object#hashCode()
557          */
558         public int hashCode() {
559             return Closure.this.hashCode();
560         }
561 
562         /* (non-Javadoc)
563          * @see java.lang.Object#equals(java.lang.Object)
564          */
565         public boolean equals(Object arg0) {
566             return Closure.this.equals(arg0);
567         }
568 
569         /* (non-Javadoc)
570          * @see java.lang.Object#toString()
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 }