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 }