001    /*
002     $Id: Closure.java,v 1.53 2005/07/16 21:01:36 blackdrag 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.53 $
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 Class clazz = closureClass;
098                final Method[] methods = (Method[]) AccessController.doPrivileged(new  PrivilegedAction() {
099                    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    }