View Javadoc

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