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 }