View Javadoc

1   /*
2    $Id: Closure.java,v 1.39 2004/07/10 03:31:36 bran 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 java.io.IOException;
49  import java.io.Writer;
50  import java.lang.reflect.InvocationTargetException;
51  import java.lang.reflect.Method;
52  import java.security.AccessController;
53  import java.security.PrivilegedAction;
54  
55  import org.codehaus.groovy.runtime.InvokerHelper;
56  
57  /***
58   * Represents any closure object in Groovy.
59   * 
60   * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
61   * @author <a href="mailto:tug@wilson.co.uk">John Wilson</a>
62   * @version $Revision: 1.39 $
63   */
64  public abstract class Closure extends GroovyObjectSupport implements Cloneable, Runnable {
65  
66      private static final Object noParameters[] = new Object[] { null };
67      private static final Object emptyArray[] = new Object[0];
68      private static final Object emptyArrayParameter[] = new Object[] { emptyArray };
69      
70      private Object delegate;
71      private final Object owner;
72      private final Method doCallMethod;
73      private final boolean supportsVarargs;
74      private final Class parameterTypes[];
75      private final int numberOfParameters;
76      private Object curriedParams[] = emptyArray;
77  
78  
79      private int directive = 0;
80      public static int DONE = 1;
81      public static int SKIP = 2;
82  
83      public Closure(Object delegate) {
84          this.delegate = delegate;
85          this.owner = delegate;
86  
87          Class closureClass = this.getClass();
88  
89          while (true) {
90              final Method methods[] = closureClass.getDeclaredMethods();
91  
92              int i = 0;
93  
94              while (!methods[i].getName().equals("doCall")  && ++i != methods.length);
95  
96              if (i < methods.length) {
97                  this.doCallMethod = methods[i];
98                  break;
99              }
100 
101             closureClass = closureClass.getSuperclass();
102         }
103 
104         AccessController.doPrivileged(new PrivilegedAction() {
105             public Object run() {
106                 Closure.this.doCallMethod.setAccessible(true);
107                 return null;
108             }
109         });
110 
111         this.parameterTypes = this.doCallMethod.getParameterTypes();
112 
113         this.numberOfParameters = this.parameterTypes.length;
114 
115         if (this.numberOfParameters != 0) {
116             this.supportsVarargs = this.parameterTypes[this.numberOfParameters - 1].equals(Object[].class);
117         } else {
118             this.supportsVarargs = false;
119         }
120     }
121 
122     public Object invokeMethod(String method, Object arguments) {
123         if ("doCall".equals(method) || "call".equals(method)) {
124             return call(arguments);
125         } else if ("curry".equals(method)) {
126             return curry((Object [])arguments);
127         } else {
128             try {
129                 return getMetaClass().invokeMethod(this, method, arguments);
130             } catch (MissingMethodException e) {
131                 if (owner != this) {
132                     try {
133                         // lets try invoke method on the owner
134                         return InvokerHelper.invokeMethod(this.owner, method, arguments);
135                     } catch (GroovyRuntimeException e1) {
136                         if (this.delegate != null && this.delegate != this && this.delegate != this.owner) {
137                             try {
138                                 // lets try invoke method on the delegate
139                                 return InvokerHelper.invokeMethod(this.delegate, method, arguments);
140                             } catch (GroovyRuntimeException e2) {
141                                 // ignore, we'll throw e
142                             }
143                         }
144                     }
145                  }
146                 throw e;
147             }
148         }
149 
150     }
151 
152     public Object getProperty(String property) {
153         	if ("delegate".equals(property)) {
154         		return getDelegate();
155         	} else if ("owner".equals(property)) {
156         		return getOwner();
157         	} else if ("method".equals(property)) {
158         		return getMethod();
159         	} else if ("parameterTypes".equals(property)) {
160         		return getParameterTypes();
161         	} else if ("metaClass".equals(property)) {
162         		return getMetaClass();
163         	} else if ("class".equals(property)) {
164         		return getClass();
165         	} else {
166             try {
167                 // lets try getting the property on the owner
168                 return InvokerHelper.getProperty(this.owner, property);
169             } catch (GroovyRuntimeException e1) {
170                 if (this.delegate != null && this.delegate != this && this.delegate != this.owner) {
171                     try {
172                         // lets try getting the property on the delegate
173                         return InvokerHelper.getProperty(this.delegate, property);
174                     } catch (GroovyRuntimeException e2) {
175                         // ignore, we'll throw e1
176                     }
177                 }
178                 
179                 throw e1;
180             }
181          }
182     }
183 
184     public void setProperty(String property, Object newValue) {
185 	    	if ("delegate".equals(property)) {
186 	    		setDelegate(newValue);
187 	    	} else if ("metaClass".equals(property)) {
188         		setMetaClass((MetaClass)newValue);
189         	} else {
190             try {
191                 // lets try setting the property on the owner
192                 InvokerHelper.setProperty(this.owner, property, newValue);
193                 return;
194             } catch (GroovyRuntimeException e1) {
195                 if (this.delegate != null && this.delegate != this && this.delegate != this.owner) {
196                     try {
197                         // lets try setting the property on the delegate
198                         InvokerHelper.setProperty(this.delegate, property, newValue);
199                         return;
200                     } catch (GroovyRuntimeException e2) {
201                         // ignore, we'll throw e1
202                     }
203                  }
204                 
205                 throw e1;
206            }
207         }
208     }
209 
210     /***
211      * Invokes the closure without any parameters, returning any value if applicable.
212      * 
213      * @return the value if applicable or null if there is no return statement in the closure
214      */
215     public Object call() {
216         return call(emptyArray);
217     }
218 
219     /***
220      * Invokes the closure, returning any value if applicable.
221      * 
222      * @param arguments could be a single value or a List of values
223      * @return the value if applicable or null if there is no return statement in the closure
224      */
225     public Object call(final Object arguments) {
226         final Object params[];
227 
228         if (this.curriedParams.length != 0) {
229             final Object args[];
230 
231             if (arguments instanceof Object[]) {
232                 args = (Object[])arguments;
233             } else {
234                 args = new Object[] {arguments};
235             }
236 
237             params = new Object[this.curriedParams.length + args.length];
238 
239             System.arraycopy(this.curriedParams, 0, params, 0, this.curriedParams.length);
240             System.arraycopy(args, 0, params, this.curriedParams.length, args.length);
241         } else {
242             if (arguments instanceof Object[]) {
243                 params = (Object[])arguments;
244             } else {
245                 return doCall(arguments);
246             }
247         }
248 
249         final int lastParam = this.numberOfParameters - 1;
250 
251         if (this.supportsVarargs && !(this.numberOfParameters == params.length && (params[lastParam] == null || params[lastParam].getClass() == Object [].class))) {
252             final Object actualParameters[] = new Object[this.numberOfParameters];
253 
254             //
255             // We have a closure which supports variable arguments and we haven't got actual
256             // parameters which have exactly the right number of parameters and ends with a null or an Object[]
257             //
258             if (params.length < lastParam) {
259                 //
260                 // Not enough parameters throw exception
261                 //
262                 // Note we allow there to be one fewer actual parameter than the number of formal parameters
263                 // in this case we pass an zero length Object[] as the last parameter
264                 //
265                 throw new IncorrectClosureArgumentsException(this, params, this.parameterTypes);
266             } else {
267                 final Object rest[] = new Object[params.length - lastParam];	 // array used to pass the rest of the paraters
268 
269                 // fill the parameter array up to but not including the last one
270                 System.arraycopy(params, 0, actualParameters, 0, lastParam);
271 
272                 // put the rest of the parameters in the overflow araay
273                 System.arraycopy(params, lastParam, rest, 0, rest.length);
274 
275                 // pass the overflow array as the last parameter
276                 actualParameters[lastParam] = rest;
277 
278                 return callViaReflection(actualParameters);
279             }
280         }
281 
282         if (params.length == 0) {
283             // pass a single null parameter if no parameters specified
284             return doCall(null);
285         } else if (params.length == 1) {
286             return doCall(params[0]);
287         } else if (params.length == 2) {
288             return doCall(params[0], params[1]);
289         } else {
290             return callViaReflection(params);
291         }
292     }
293 
294     protected static Object throwRuntimeException(Throwable throwable) {
295         if (throwable instanceof RuntimeException) {
296             throw (RuntimeException) throwable;
297         } else {
298             throw new GroovyRuntimeException(throwable.getMessage(), throwable);
299         }
300     }
301     
302     /***
303      * An attempt to optimise calling closures with one parameter
304      * If the closure has one untyped parameter then it will overload this function
305      * If not this will be called ans will use reflection to deal with the case of a
306      * single typed parameter
307      * @param p1
308      * @return the result of calling the closure
309      */
310     protected Object doCall(final Object p1) {
311 		return callViaReflection(new Object[] {p1});
312     }
313     
314     /***
315      * An attempt to optimise calling closures with two parameters
316      * If the closure has two untyped parameters then it will overload this function
317      * If not this will be called ans will use reflection to deal with the case of one
318      * or two typed parameters
319      * @param p1
320      * @return the result of calling the closure
321      */
322     
323     protected Object doCall(final Object p1, final Object p2) {
324     		return callViaReflection(new Object[] {p1, p2});
325     }
326     
327     private Object callViaReflection(final Object params[]) {   	
328 		try {
329 			// invoke the closure
330 			return this.doCallMethod.invoke(this, params);
331 		} catch (final IllegalArgumentException e) {
332 			throw new IncorrectClosureArgumentsException(this, params, this.parameterTypes);
333 		} catch (final IllegalAccessException e) {
334         final Throwable cause = e.getCause();
335         
336             return throwRuntimeException((cause == null) ? e : cause);
337 		} catch (final InvocationTargetException e) {
338         final Throwable cause = e.getCause();
339         
340             return throwRuntimeException((cause == null) ? e : cause);
341 		}
342     }
343     
344     /***
345      * Used when a closure wraps a method on a class
346      * 
347      * @return empty string
348      */
349     public String getMethod() {
350     		return "";
351     }
352 
353     /***
354      * @return the owner Object to which method calls will go which is
355      * typically the outer class when the closure is constructed
356      */
357     public Object getOwner() {
358         return this.owner;
359     }
360 
361     /***
362      * @return the delegate Object to which method calls will go which is
363      * typically the outer class when the closure is constructed
364      */
365     public Object getDelegate() {
366         return this.delegate;
367     }
368 
369     /***
370      * Allows the delegate to be changed such as when performing markup building
371      * @param delegate
372      */
373     public void setDelegate(Object delegate) {
374         this.delegate = delegate;
375     }
376 
377     /***
378      * @return the parameter types of this closure
379      */
380     public Class[] getParameterTypes() {
381         return this.parameterTypes;
382     }
383 
384     /***
385      * @return a version of this closure which implements Writable
386      */
387     public Closure asWritable() {
388     		return new WritableClosure();
389     }
390 
391     /* (non-Javadoc)
392      * @see java.lang.Runnable#run()
393      */
394     public void run() {
395         call();
396     }
397 
398     /***
399      * Support for closure currying
400      * @param arguments
401      */
402     public Closure curry(final Object arguments[]) {
403         final Closure curriedClosure= (Closure)this.clone();
404         final Object newCurriedParams[] = new Object[curriedClosure.curriedParams.length + arguments.length];
405 
406         System.arraycopy(curriedClosure.curriedParams, 0, newCurriedParams, 0, curriedClosure.curriedParams.length);
407         System.arraycopy(arguments, 0, newCurriedParams, curriedClosure.curriedParams.length, arguments.length);
408 
409         curriedClosure.curriedParams = newCurriedParams;
410 
411         return curriedClosure;
412     }
413     
414     /* (non-Javadoc)
415      * @see java.lang.Object#clone()
416      */
417     public Object clone() {
418     		try {
419     			return super.clone();
420     		} catch (final CloneNotSupportedException e) {
421     			return null;
422     		}
423     }
424     
425     private class WritableClosure extends Closure implements Writable {
426     		public WritableClosure() {
427     			super(null);
428     		}
429     		
430     		/* (non-Javadoc)
431 		 * @see groovy.lang.Writable#writeTo(java.io.Writer)
432 		 */
433 		public Writer writeTo(Writer out) throws IOException {
434 			Closure.this.call(out);
435 			
436 			return out;
437 		}
438 		
439 		/* (non-Javadoc)
440 		 * @see groovy.lang.GroovyObject#invokeMethod(java.lang.String, java.lang.Object)
441 		 */
442 		public Object invokeMethod(String method, Object arguments) {
443 			if ("clone".equals(method)) {
444 				return clone();
445 			} else if ("curry".equals(method)) {
446 				return curry((Object[])arguments);
447 			} else if ("asWritable".equals(method)) {
448 				return asWritable();
449 			} else {
450 				return Closure.this.invokeMethod(method, arguments);
451 			}
452 		}
453 		
454 		/* (non-Javadoc)
455 		 * @see groovy.lang.GroovyObject#getProperty(java.lang.String)
456 		 */
457 		public Object getProperty(String property) {
458 			return Closure.this.getProperty(property);
459 		}
460 		
461 		/* (non-Javadoc)
462 		 * @see groovy.lang.GroovyObject#setProperty(java.lang.String, java.lang.Object)
463 		 */
464 		public void setProperty(String property, Object newValue) {
465 			Closure.this.setProperty(property, newValue);
466 		}
467 		
468 		/* (non-Javadoc)
469 		 * @see groovy.lang.Closure#call()
470 		 */
471 		public Object call() {
472 			return Closure.this.call();
473 		}
474 		
475 		/* (non-Javadoc)
476 		 * @see groovy.lang.Closure#call(java.lang.Object)
477 		 */
478 		public Object call(Object arguments) {
479 			return Closure.this.call(arguments);
480 		}
481 		
482 		/* (non-Javadoc)
483 		 * @see groovy.lang.Closure#doCall(java.lang.Object)
484 		 */
485 		protected Object doCall(Object p1) {
486 			return Closure.this.doCall(p1);
487 		}
488 		
489 		/* (non-Javadoc)
490 		 * @see groovy.lang.Closure#doCall(java.lang.Object, java.lang.Object)
491 		 */
492 		protected Object doCall(Object p1, Object p2) {
493 			return Closure.this.doCall(p1, p2);
494 		}
495 		
496 		/* (non-Javadoc)
497 		 * @see groovy.lang.Closure#getDelegate()
498 		 */
499 		public Object getDelegate() {
500 			return Closure.this.getDelegate();
501 		}
502 		
503 		/* (non-Javadoc)
504 		 * @see groovy.lang.Closure#setDelegate(java.lang.Object)
505 		 */
506 		public void setDelegate(Object delegate) {
507 			Closure.this.setDelegate(delegate);
508 		}
509 
510 		/* (non-Javadoc)
511 		 * @see groovy.lang.Closure#getParameterTypes()
512 		 */
513 		public Class[] getParameterTypes() {
514 			return Closure.this.getParameterTypes();
515 		}
516 		
517 		/* (non-Javadoc)
518 		 * @see groovy.lang.Closure#asWritable()
519 		 */
520 		public Closure asWritable() {
521 			return this;
522 		}
523 		
524 		/* (non-Javadoc)
525 		 * @see java.lang.Runnable#run()
526 		 */
527 		public void run() {
528 			Closure.this.run();
529 		}
530 		
531 		/* (non-Javadoc)
532 		 * @see groovy.lang.Closure#curry(java.lang.Object[])
533 		 */
534 		public Closure curry(Object[] arguments) {
535 			return Closure.this.curry(arguments).asWritable();
536 		}
537 		
538 		/* (non-Javadoc)
539 		 * @see java.lang.Object#clone()
540 		 */
541 		public Object clone() {
542 			return ((Closure)Closure.this.clone()).asWritable();
543 		}
544 		
545 		/* (non-Javadoc)
546 		 * @see java.lang.Object#hashCode()
547 		 */
548 		public int hashCode() {
549 			return Closure.this.hashCode();
550 		}
551 		
552 		/* (non-Javadoc)
553 		 * @see java.lang.Object#equals(java.lang.Object)
554 		 */
555 		public boolean equals(Object arg0) {
556 			return Closure.this.equals(arg0);
557 		}
558 		
559 		/* (non-Javadoc)
560 		 * @see java.lang.Object#toString()
561 		 */
562 		public String toString() {
563 			return Closure.this.toString();
564 		}
565     }
566 	/***
567 	 * @return Returns the directive.
568 	 */
569 	public int getDirective() {
570 		return directive;
571 	}
572 	/***
573 	 * @param directive The directive to set.
574 	 */
575 	public void setDirective(int directive) {
576 		this.directive = directive;
577 	}
578 }