View Javadoc

1   /*
2    $Id: Closure.java,v 1.46 2005/03/15 21:11:38 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.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                         // 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             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             // invoke the closure
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     /* (non-Javadoc)
412      * @see java.lang.Runnable#run()
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     /* (non-Javadoc)
436      * @see java.lang.Object#clone()
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         /* (non-Javadoc)
452      * @see groovy.lang.Writable#writeTo(java.io.Writer)
453      */
454         public Writer writeTo(Writer out) throws IOException {
455             Closure.this.call(out);
456 
457             return out;
458         }
459 
460         /* (non-Javadoc)
461          * @see groovy.lang.GroovyObject#invokeMethod(java.lang.String, java.lang.Object)
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         /* (non-Javadoc)
476          * @see groovy.lang.GroovyObject#getProperty(java.lang.String)
477          */
478         public Object getProperty(String property) {
479             return Closure.this.getProperty(property);
480         }
481 
482         /* (non-Javadoc)
483          * @see groovy.lang.GroovyObject#setProperty(java.lang.String, java.lang.Object)
484          */
485         public void setProperty(String property, Object newValue) {
486             Closure.this.setProperty(property, newValue);
487         }
488 
489         /* (non-Javadoc)
490          * @see groovy.lang.Closure#call()
491          */
492         public Object call() {
493             return Closure.this.call();
494         }
495 
496         /* (non-Javadoc)
497          * @see groovy.lang.Closure#call(java.lang.Object)
498          */
499         public Object call(Object arguments) {
500             return Closure.this.call(arguments);
501         }
502 
503         /* (non-Javadoc)
504          * @see groovy.lang.Closure#doCall(java.lang.Object)
505          */
506         protected Object doCall(Object p1) {
507             return Closure.this.doCall(p1);
508         }
509 
510         /* (non-Javadoc)
511          * @see groovy.lang.Closure#doCall(java.lang.Object, java.lang.Object)
512          */
513         protected Object doCall(Object p1, Object p2) {
514             return Closure.this.doCall(p1, p2);
515         }
516 
517         /* (non-Javadoc)
518          * @see groovy.lang.Closure#getDelegate()
519          */
520         public Object getDelegate() {
521             return Closure.this.getDelegate();
522         }
523 
524         /* (non-Javadoc)
525          * @see groovy.lang.Closure#setDelegate(java.lang.Object)
526          */
527         public void setDelegate(Object delegate) {
528             Closure.this.setDelegate(delegate);
529         }
530 
531         /* (non-Javadoc)
532          * @see groovy.lang.Closure#getParameterTypes()
533          */
534         public Class[] getParameterTypes() {
535             return Closure.this.getParameterTypes();
536         }
537 
538         /* (non-Javadoc)
539          * @see groovy.lang.Closure#asWritable()
540          */
541         public Closure asWritable() {
542             return this;
543         }
544 
545         /* (non-Javadoc)
546          * @see java.lang.Runnable#run()
547          */
548         public void run() {
549             Closure.this.run();
550         }
551 
552         /* (non-Javadoc)
553          * @see groovy.lang.Closure#curry(java.lang.Object[])
554          */
555         public Closure curry(Object[] arguments) {
556             return Closure.this.curry(arguments).asWritable();
557         }
558 
559         /* (non-Javadoc)
560          * @see java.lang.Object#clone()
561          */
562         public Object clone() {
563             return ((Closure) Closure.this.clone()).asWritable();
564         }
565 
566         /* (non-Javadoc)
567          * @see java.lang.Object#hashCode()
568          */
569         public int hashCode() {
570             return Closure.this.hashCode();
571         }
572 
573         /* (non-Javadoc)
574          * @see java.lang.Object#equals(java.lang.Object)
575          */
576         public boolean equals(Object arg0) {
577             return Closure.this.equals(arg0);
578         }
579 
580         /* (non-Javadoc)
581          * @see java.lang.Object#toString()
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 }