View Javadoc

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