View Javadoc

1   /*
2    $Id: Closure.java 4546 2006-12-21 19:07:22Z blackdrag $
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.CurriedClosure;
49  import org.codehaus.groovy.runtime.InvokerHelper;
50  import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
51  
52  import java.io.IOException;
53  import java.io.StringWriter;
54  import java.io.Writer;
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: 4546 $
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 Class[] parameterTypes;
75      protected int maximumNumberOfParameters;
76      private final Object thisObject;
77  
78  
79      private int directive = 0;
80      public final static int DONE = 1, SKIP = 2;
81  
82      public Closure(Object owner, Object thisObject) {
83          this.owner = owner;
84          this.delegate = owner;
85          this.thisObject = thisObject;
86  
87          Class closureClass = this.getClass();
88          final Class clazz = closureClass;
89          final Method[] methods = (Method[]) AccessController.doPrivileged(new  PrivilegedAction() {
90              public Object run() {
91                  return clazz.getDeclaredMethods();
92              }
93          });
94  
95          // set it to -1 for starters so parameterTypes will always get a type
96          maximumNumberOfParameters = -1;
97          for (int j = 0; j < methods.length; j++) {
98              if ("doCall".equals(methods[j].getName()) && methods[j].getParameterTypes().length > maximumNumberOfParameters) {
99                  parameterTypes = methods[j].getParameterTypes();
100                 maximumNumberOfParameters = parameterTypes.length;
101             }
102         }
103         // this line should be useless, but well, just in case
104         maximumNumberOfParameters = Math.max(maximumNumberOfParameters,0);
105     }
106     
107     public Closure(Object owner) {
108         this(owner,null);
109     }
110     
111     protected Object getThisObject(){
112         return thisObject;
113     }
114 
115     public Object getProperty(String property) {
116         if ("delegate".equals(property)) {
117             return getDelegate();
118         } else if ("owner".equals(property)) {
119             return getOwner();
120         } else if ("getMaximumNumberOfParameters".equals(property)) {
121             return new Integer(getMaximumNumberOfParameters());
122         } else if ("parameterTypes".equals(property)) {
123             return getParameterTypes();
124         } else if ("metaClass".equals(property)) {
125             return getMetaClass();
126         } else if ("class".equals(property)) {
127             return getClass();
128         } else {
129             try {
130                 // lets try getting the property on the owner
131                 return InvokerHelper.getProperty(this.owner, property);
132             } catch (MissingPropertyException e1) {
133                 if (this.delegate != null && this.delegate != this && this.delegate != this.owner) {
134                     try {
135                         // lets try getting the property on the delegate
136                         return InvokerHelper.getProperty(this.delegate, property);
137                     } catch (GroovyRuntimeException e2) {
138                         // ignore, we'll throw e1
139                     }
140                 }
141 
142                 throw e1;
143             }
144         }
145     }
146 
147     public void setProperty(String property, Object newValue) {
148         if ("delegate".equals(property)) {
149             setDelegate(newValue);
150         } else if ("metaClass".equals(property)) {
151             setMetaClass((MetaClass) newValue);
152         } else {
153             try {
154                 // lets try setting the property on the owner
155                 InvokerHelper.setProperty(this.owner, property, newValue);
156                 return;
157             } catch (GroovyRuntimeException e1) {
158                 if (this.delegate != null && this.delegate != this && this.delegate != this.owner) {
159                     try {
160                         // lets try setting the property on the delegate
161                         InvokerHelper.setProperty(this.delegate, property, newValue);
162                         return;
163                     } catch (GroovyRuntimeException e2) {
164                         // ignore, we'll throw e1
165                     }
166                 }
167 
168                 throw e1;
169             }
170         }
171     }
172 
173     public boolean isCase(Object candidate){
174         return DefaultTypeTransformation.castToBoolean(call(candidate));
175     }
176 
177     /***
178      * Invokes the closure without any parameters, returning any value if applicable.
179      *
180      * @return the value if applicable or null if there is no return statement in the closure
181      */
182     public Object call() {
183         return call(new Object[]{});
184     }
185     
186     public Object call(Object[] args) {
187         try {
188             return getMetaClass().invokeMethod(this,"doCall",args);
189         } catch (Exception e) {
190             return throwRuntimeException(e);
191         }
192     }
193     
194     /***
195      * Invokes the closure, returning any value if applicable.
196      *
197      * @param arguments could be a single value or a List of values
198      * @return the value if applicable or null if there is no return statement in the closure
199      */
200     public Object call(final Object arguments) {
201         return call(new Object[]{arguments});
202     }
203     
204     protected static Object throwRuntimeException(Throwable throwable) {
205         if (throwable instanceof RuntimeException) {
206             throw (RuntimeException) throwable;
207         } else {
208             throw new GroovyRuntimeException(throwable.getMessage(), throwable);
209         }
210     }
211 
212     /***
213      * @return the owner Object to which method calls will go which is
214      *         typically the outer class when the closure is constructed
215      */
216     public Object getOwner() {
217         return this.owner;
218     }
219 
220     /***
221      * @return the delegate Object to which method calls will go which is
222      *         typically the outer class when the closure is constructed
223      */
224     public Object getDelegate() {
225         return this.delegate;
226     }
227 
228     /***
229      * Allows the delegate to be changed such as when performing markup building
230      *
231      * @param delegate
232      */
233     public void setDelegate(Object delegate) {
234         this.delegate = delegate;
235     }
236     
237     /***
238      * @return the parameter types of the longest doCall method
239      * of this closure
240      */
241     public Class[] getParameterTypes() {
242         return this.parameterTypes;
243     }
244 
245     /***
246      * @return the maximum number of parameters a doCall methos
247      * of this closure can take
248      */
249     public int getMaximumNumberOfParameters() {
250         return this.maximumNumberOfParameters;
251     }
252 
253     /***
254      * @return a version of this closure which implements Writable
255      */
256     public Closure asWritable() {
257         return new WritableClosure();
258     }
259 
260     /* (non-Javadoc)
261      * @see java.lang.Runnable#run()
262      */
263     public void run() {
264         call();
265     }
266 
267     /***
268      * Support for closure currying
269      *
270      * @param arguments
271      */
272     public Closure curry(final Object arguments[]) {
273         return new CurriedClosure(this,arguments);
274     }
275 
276     /* (non-Javadoc)
277      * @see java.lang.Object#clone()
278      */
279     public Object clone() {
280         try {
281             return super.clone();
282         } catch (final CloneNotSupportedException e) {
283             return null;
284         }
285     }
286     
287     /***
288      * Implementation note: 
289      *   This has to be an inner class!
290      * 
291      * Reason: 
292      *   Closure.this.call will call the outer call method, bur
293      * with the inner class as executing object. This means any
294      * invokeMethod or getProperty call will be called on this 
295      * inner class instead of the outer!
296      */
297     private class WritableClosure extends Closure implements Writable {
298         public WritableClosure() {
299             super(Closure.this);
300         }
301 
302         /* (non-Javadoc)
303          * @see groovy.lang.Writable#writeTo(java.io.Writer)
304          */
305         public Writer writeTo(Writer out) throws IOException {
306             Closure.this.call(new Object[]{out});
307 
308             return out;
309         }
310 
311         /* (non-Javadoc)
312          * @see groovy.lang.GroovyObject#invokeMethod(java.lang.String, java.lang.Object)
313          */
314         public Object invokeMethod(String method, Object arguments) {
315             if ("clone".equals(method)) {
316                 return clone();
317             } else if ("curry".equals(method)) {
318                 return curry((Object[]) arguments);
319             } else if ("asWritable".equals(method)) {
320                 return asWritable();
321             } else {
322                 return Closure.this.invokeMethod(method, arguments);
323             }
324         }
325 
326         /* (non-Javadoc)
327          * @see groovy.lang.GroovyObject#getProperty(java.lang.String)
328          */
329         public Object getProperty(String property) {
330             return Closure.this.getProperty(property);
331         }
332 
333         /* (non-Javadoc)
334          * @see groovy.lang.GroovyObject#setProperty(java.lang.String, java.lang.Object)
335          */
336         public void setProperty(String property, Object newValue) {
337             Closure.this.setProperty(property, newValue);
338         }
339 
340         /* (non-Javadoc)
341          * @see groovy.lang.Closure#call()
342          */
343         public Object call() {
344             return Closure.this.call();
345         }
346 
347         /* (non-Javadoc)
348          * @see groovy.lang.Closure#call(java.lang.Object)
349          */
350         public Object call(Object arguments) {
351             return Closure.this.call(arguments);
352         }
353 
354         /* (non-Javadoc)
355          * @see groovy.lang.Closure#getDelegate()
356          */
357         public Object getDelegate() {
358             return Closure.this.getDelegate();
359         }
360 
361         /* (non-Javadoc)
362          * @see groovy.lang.Closure#setDelegate(java.lang.Object)
363          */
364         public void setDelegate(Object delegate) {
365             Closure.this.setDelegate(delegate);
366         }
367 
368         /* (non-Javadoc)
369          * @see groovy.lang.Closure#getParameterTypes()
370          */
371         public Class[] getParameterTypes() {
372             return Closure.this.getParameterTypes();
373         }
374         
375         /* (non-Javadoc)
376          * @see groovy.lang.Closure#getParameterTypes()
377          */
378         public int getMaximumNumberOfParameters() {
379             return Closure.this.getMaximumNumberOfParameters();
380         }
381 
382         /* (non-Javadoc)
383          * @see groovy.lang.Closure#asWritable()
384          */
385         public Closure asWritable() {
386             return this;
387         }
388 
389         /* (non-Javadoc)
390          * @see java.lang.Runnable#run()
391          */
392         public void run() {
393             Closure.this.run();
394         }
395 
396         /* (non-Javadoc)
397          * @see java.lang.Object#clone()
398          */
399         public Object clone() {
400             return ((Closure) Closure.this.clone()).asWritable();
401         }
402 
403         /* (non-Javadoc)
404          * @see java.lang.Object#hashCode()
405          */
406         public int hashCode() {
407             return Closure.this.hashCode();
408         }
409 
410         /* (non-Javadoc)
411          * @see java.lang.Object#equals(java.lang.Object)
412          */
413         public boolean equals(Object arg0) {
414             return Closure.this.equals(arg0);
415         }
416 
417         /* (non-Javadoc)
418          * @see java.lang.Object#toString()
419          */
420         public String toString() {
421             final StringWriter writer = new StringWriter();
422 
423             try {
424                 writeTo(writer);
425             } catch (IOException e) {
426                 return null;
427             }
428 
429             return writer.toString();
430         }
431         
432         public Closure curry(final Object arguments[]) {
433             return (new CurriedClosure(this,arguments)).asWritable();
434         }
435     }
436 
437     /***
438      * @return Returns the directive.
439      */
440     public int getDirective() {
441         return directive;
442     }
443 
444     /***
445      * @param directive The directive to set.
446      */
447     public void setDirective(int directive) {
448         this.directive = directive;
449     }
450 
451 }