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