View Javadoc

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