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 }