View Javadoc

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