View Javadoc

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