001    /*
002     $Id: Closure.java,v 1.50 2005/06/12 17:31:09 dierk Exp $
003    
004     Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
005    
006     Redistribution and use of this software and associated documentation
007     ("Software"), with or without modification, are permitted provided
008     that the following conditions are met:
009    
010     1. Redistributions of source code must retain copyright
011        statements and notices.  Redistributions must also contain a
012        copy of this document.
013    
014     2. Redistributions in binary form must reproduce the
015        above copyright notice, this list of conditions and the
016        following disclaimer in the documentation and/or other
017        materials provided with the distribution.
018    
019     3. The name "groovy" must not be used to endorse or promote
020        products derived from this Software without prior written
021        permission of The Codehaus.  For written permission,
022        please contact info@codehaus.org.
023    
024     4. Products derived from this Software may not be called "groovy"
025        nor may "groovy" appear in their names without prior written
026        permission of The Codehaus. "groovy" is a registered
027        trademark of The Codehaus.
028    
029     5. Due credit should be given to The Codehaus -
030        http://groovy.codehaus.org/
031    
032     THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
033     ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
034     NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
035     FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
036     THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
037     INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
038     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
039     SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
040     HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
041     STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
042     ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
043     OF THE POSSIBILITY OF SUCH DAMAGE.
044    
045     */
046    package groovy.lang;
047    
048    import org.codehaus.groovy.runtime.InvokerHelper;
049    import org.codehaus.groovy.runtime.InvokerInvocationException;
050    
051    import java.util.*;
052    import java.io.IOException;
053    import java.io.StringWriter;
054    import java.io.Writer;
055    import java.lang.reflect.InvocationTargetException;
056    import java.lang.reflect.Method;
057    import java.security.AccessController;
058    import java.security.PrivilegedAction;
059    
060    /**
061     * Represents any closure object in Groovy.
062     *
063     * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
064     * @author <a href="mailto:tug@wilson.co.uk">John Wilson</a>
065     * @version $Revision: 1.50 $
066     */
067    public abstract class Closure extends GroovyObjectSupport implements Cloneable, Runnable {
068    
069        private static final Object noParameters[] = new Object[]{null};
070        private static final Object emptyArray[] = new Object[0];
071        private static final Object emptyArrayParameter[] = new Object[]{emptyArray};
072    
073        private Object delegate;
074        private final Object owner;
075        private final Method doCallMethod;
076        private final HashMap callsMap;
077        private final boolean supportsVarargs;
078        private final Class[] parameterTypes;
079        private final int numberOfParameters;
080        private Object curriedParams[] = emptyArray;
081    
082    
083        private int directive = 0;
084        public static int DONE = 1;
085        public static int SKIP = 2;
086    
087        public Closure(Object delegate) {
088            this.delegate = delegate;
089            this.owner = delegate;
090    
091            Class closureClass = this.getClass();
092            callsMap = new HashMap();
093            int paramLenTemp = -1;
094            Method doCallTemp = null;
095    
096            while (true) {
097                final Method methods[] = closureClass.getDeclaredMethods();
098    
099                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    }