001    /*
002     $Id: Closure.java,v 1.58 2005/10/19 09:00:12 tug 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.CurriedClosure;
049    import org.codehaus.groovy.runtime.InvokerHelper;
050    
051    import java.io.IOException;
052    import java.io.StringWriter;
053    import java.io.Writer;
054    import java.lang.reflect.Method;
055    import java.security.AccessController;
056    import java.security.PrivilegedAction;
057    
058    /**
059     * Represents any closure object in Groovy.
060     *
061     * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
062     * @author <a href="mailto:tug@wilson.co.uk">John Wilson</a>
063     * @version $Revision: 1.58 $
064     */
065    public abstract class Closure extends GroovyObjectSupport implements Cloneable, Runnable {
066    
067        private static final Object noParameters[] = new Object[]{null};
068        private static final Object emptyArray[] = new Object[0];
069        private static final Object emptyArrayParameter[] = new Object[]{emptyArray};
070    
071        private Object delegate;
072        private final Object owner;
073        private Class[] parameterTypes;
074        protected int maximumNumberOfParameters;
075    
076    
077        private int directive = 0;
078        public static int DONE = 1;
079        public static int SKIP = 2;
080    
081        public Closure(Object owner) {
082            this.owner = owner;
083            this.delegate = owner;
084    
085            Class closureClass = this.getClass();
086            maximumNumberOfParameters = 0;
087    
088            final Class clazz = closureClass;
089            final Method[] methods = (Method[]) AccessController.doPrivileged(new  PrivilegedAction() {
090                public Object run() {
091                    return clazz.getDeclaredMethods();
092                }
093            });
094    
095            for (int j = 0; j < methods.length; j++) {
096                if ("doCall".equals(methods[j].getName()) && methods[j].getParameterTypes().length > maximumNumberOfParameters) {
097                    parameterTypes = methods[j].getParameterTypes();
098                    maximumNumberOfParameters = parameterTypes.length;
099                }
100            }
101        }
102    
103        public Object getProperty(String property) {
104            if ("delegate".equals(property)) {
105                return getDelegate();
106            } else if ("owner".equals(property)) {
107                return getOwner();
108            } else if ("getMaximumNumberOfParameters".equals(property)) {
109                return new Integer(getMaximumNumberOfParameters());
110            } else if ("parameterTypes".equals(property)) {
111                return getParameterTypes();
112            } else if ("metaClass".equals(property)) {
113                return getMetaClass();
114            } else if ("class".equals(property)) {
115                return getClass();
116            } else {
117                try {
118                    // lets try getting the property on the owner
119                    return InvokerHelper.getProperty(this.owner, property);
120                } catch (GroovyRuntimeException e1) {
121                    if (this.delegate != null && this.delegate != this && this.delegate != this.owner) {
122                        try {
123                            // lets try getting the property on the delegate
124                            return InvokerHelper.getProperty(this.delegate, property);
125                        } catch (GroovyRuntimeException e2) {
126                            // ignore, we'll throw e1
127                        }
128                    }
129    
130                    throw e1;
131                }
132            }
133        }
134    
135        public void setProperty(String property, Object newValue) {
136            if ("delegate".equals(property)) {
137                setDelegate(newValue);
138            } else if ("metaClass".equals(property)) {
139                setMetaClass((MetaClass) newValue);
140            } else {
141                try {
142                    // lets try setting the property on the owner
143                    InvokerHelper.setProperty(this.owner, property, newValue);
144                    return;
145                } catch (GroovyRuntimeException e1) {
146                    if (this.delegate != null && this.delegate != this && this.delegate != this.owner) {
147                        try {
148                            // lets try setting the property on the delegate
149                            InvokerHelper.setProperty(this.delegate, property, newValue);
150                            return;
151                        } catch (GroovyRuntimeException e2) {
152                            // ignore, we'll throw e1
153                        }
154                    }
155    
156                    throw e1;
157                }
158            }
159        }
160    
161        public boolean isCase(Object candidate){
162            return InvokerHelper.asBool(call(candidate));
163        }
164    
165        /**
166         * Invokes the closure without any parameters, returning any value if applicable.
167         *
168         * @return the value if applicable or null if there is no return statement in the closure
169         */
170        public Object call() {
171            return call(new Object[]{});
172        }
173        
174        public Object call(Object[] args) {
175            try {
176                return getMetaClass().invokeMethod(this,"doCall",args);
177            } catch (Exception e) {
178                return throwRuntimeException(e);
179            }
180        }
181        
182        /**
183         * Invokes the closure, returning any value if applicable.
184         *
185         * @param arguments could be a single value or a List of values
186         * @return the value if applicable or null if there is no return statement in the closure
187         */
188        public Object call(final Object arguments) {
189            return call(new Object[]{arguments});
190        }
191        
192        protected static Object throwRuntimeException(Throwable throwable) {
193            if (throwable instanceof RuntimeException) {
194                throw (RuntimeException) throwable;
195            } else {
196                throw new GroovyRuntimeException(throwable.getMessage(), throwable);
197            }
198        }
199    
200        /**
201         * @return the owner Object to which method calls will go which is
202         *         typically the outer class when the closure is constructed
203         */
204        public Object getOwner() {
205            return this.owner;
206        }
207    
208        /**
209         * @return the delegate Object to which method calls will go which is
210         *         typically the outer class when the closure is constructed
211         */
212        public Object getDelegate() {
213            return this.delegate;
214        }
215    
216        /**
217         * Allows the delegate to be changed such as when performing markup building
218         *
219         * @param delegate
220         */
221        public void setDelegate(Object delegate) {
222            this.delegate = delegate;
223        }
224        
225        /**
226         * @return the parameter types of the longest doCall method
227         * of this closure
228         */
229        public Class[] getParameterTypes() {
230            return this.parameterTypes;
231        }
232    
233        /**
234         * @return the maximum number of parameters a doCall methos
235         * of this closure can take
236         */
237        public int getMaximumNumberOfParameters() {
238            return this.maximumNumberOfParameters;
239        }
240    
241        /**
242         * @return a version of this closure which implements Writable
243         */
244        public Closure asWritable() {
245            return new WritableClosure();
246        }
247    
248        /* (non-Javadoc)
249         * @see java.lang.Runnable#run()
250         */
251        public void run() {
252            call();
253        }
254    
255        /**
256         * Support for closure currying
257         *
258         * @param arguments
259         */
260        public Closure curry(final Object arguments[]) {
261            return new CurriedClosure(this,arguments);
262        }
263    
264        /* (non-Javadoc)
265         * @see java.lang.Object#clone()
266         */
267        public Object clone() {
268            try {
269                return super.clone();
270            } catch (final CloneNotSupportedException e) {
271                return null;
272            }
273        }
274        
275        /**
276         * Implementation note: 
277         *   This has to be an inner class!
278         * 
279         * Reason: 
280         *   Closure.this.call will call the outer call method, bur
281         * with the inner class as executing object. This means any
282         * invokeMethod or getProperty call will be called on this 
283         * inner class instead of the outer!
284         */
285        private class WritableClosure extends Closure implements Writable {
286            public WritableClosure() {
287                super(Closure.this);
288            }
289    
290            /* (non-Javadoc)
291             * @see groovy.lang.Writable#writeTo(java.io.Writer)
292             */
293            public Writer writeTo(Writer out) throws IOException {
294                Closure.this.call(new Object[]{out});
295    
296                return out;
297            }
298    
299            /* (non-Javadoc)
300             * @see groovy.lang.GroovyObject#invokeMethod(java.lang.String, java.lang.Object)
301             */
302            public Object invokeMethod(String method, Object arguments) {
303                if ("clone".equals(method)) {
304                    return clone();
305                } else if ("curry".equals(method)) {
306                    return curry((Object[]) arguments);
307                } else if ("asWritable".equals(method)) {
308                    return asWritable();
309                } else {
310                    return Closure.this.invokeMethod(method, arguments);
311                }
312            }
313    
314            /* (non-Javadoc)
315             * @see groovy.lang.GroovyObject#getProperty(java.lang.String)
316             */
317            public Object getProperty(String property) {
318                return Closure.this.getProperty(property);
319            }
320    
321            /* (non-Javadoc)
322             * @see groovy.lang.GroovyObject#setProperty(java.lang.String, java.lang.Object)
323             */
324            public void setProperty(String property, Object newValue) {
325                Closure.this.setProperty(property, newValue);
326            }
327    
328            /* (non-Javadoc)
329             * @see groovy.lang.Closure#call()
330             */
331            public Object call() {
332                return Closure.this.call();
333            }
334    
335            /* (non-Javadoc)
336             * @see groovy.lang.Closure#call(java.lang.Object)
337             */
338            public Object call(Object arguments) {
339                return Closure.this.call(arguments);
340            }
341    
342            /* (non-Javadoc)
343             * @see groovy.lang.Closure#getDelegate()
344             */
345            public Object getDelegate() {
346                return Closure.this.getDelegate();
347            }
348    
349            /* (non-Javadoc)
350             * @see groovy.lang.Closure#setDelegate(java.lang.Object)
351             */
352            public void setDelegate(Object delegate) {
353                Closure.this.setDelegate(delegate);
354            }
355    
356            /* (non-Javadoc)
357             * @see groovy.lang.Closure#getParameterTypes()
358             */
359            public Class[] getParameterTypes() {
360                return Closure.this.getParameterTypes();
361            }
362            
363            /* (non-Javadoc)
364             * @see groovy.lang.Closure#getParameterTypes()
365             */
366            public int getMaximumNumberOfParameters() {
367                return Closure.this.getMaximumNumberOfParameters();
368            }
369    
370            /* (non-Javadoc)
371             * @see groovy.lang.Closure#asWritable()
372             */
373            public Closure asWritable() {
374                return this;
375            }
376    
377            /* (non-Javadoc)
378             * @see java.lang.Runnable#run()
379             */
380            public void run() {
381                Closure.this.run();
382            }
383    
384            /* (non-Javadoc)
385             * @see java.lang.Object#clone()
386             */
387            public Object clone() {
388                return ((Closure) Closure.this.clone()).asWritable();
389            }
390    
391            /* (non-Javadoc)
392             * @see java.lang.Object#hashCode()
393             */
394            public int hashCode() {
395                return Closure.this.hashCode();
396            }
397    
398            /* (non-Javadoc)
399             * @see java.lang.Object#equals(java.lang.Object)
400             */
401            public boolean equals(Object arg0) {
402                return Closure.this.equals(arg0);
403            }
404    
405            /* (non-Javadoc)
406             * @see java.lang.Object#toString()
407             */
408            public String toString() {
409                final StringWriter writer = new StringWriter();
410    
411                try {
412                    writeTo(writer);
413                } catch (IOException e) {
414                    return null;
415                }
416    
417                return writer.toString();
418            }
419            
420            public Closure curry(final Object arguments[]) {
421                return (new CurriedClosure(this,arguments)).asWritable();
422            }
423        }
424    
425        /**
426         * @return Returns the directive.
427         */
428        public int getDirective() {
429            return directive;
430        }
431    
432        /**
433         * @param directive The directive to set.
434         */
435        public void setDirective(int directive) {
436            this.directive = directive;
437        }
438    
439    }