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 }