View Javadoc

    1   /*
    2    $Id: Closure.java 4546 2006-12-21 19:07:22Z blackdrag $
    3
    4    Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
    5
    6    Redistribution and use of this software and associated documentation
    7    ("Software"), with or without modification, are permitted provided
    8    that the following conditions are met:
    9
    10   1. Redistributions of source code must retain copyright
    11      statements and notices.  Redistributions must also contain a
    12      copy of this document.
    13
    14   2. Redistributions in binary form must reproduce the
    15      above copyright notice, this list of conditions and the
    16      following disclaimer in the documentation and/or other
    17      materials provided with the distribution.
    18
    19   3. The name "groovy" must not be used to endorse or promote
    20      products derived from this Software without prior written
    21      permission of The Codehaus.  For written permission,
    22      please contact info@codehaus.org.
    23
    24   4. Products derived from this Software may not be called "groovy"
    25      nor may "groovy" appear in their names without prior written
    26      permission of The Codehaus. "groovy" is a registered
    27      trademark of The Codehaus.
    28
    29   5. Due credit should be given to The Codehaus -
    30      http://groovy.codehaus.org/
    31
    32   THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
    33   ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
    34   NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
    35   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
    36   THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
    37   INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    38   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    39   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
    40   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
    41   STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    42   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
    43   OF THE POSSIBILITY OF SUCH DAMAGE.
    44
    45   */
    46  package groovy.lang;
    47
    48  import org.codehaus.groovy.runtime.CurriedClosure;
    49  import org.codehaus.groovy.runtime.InvokerHelper;
    50  import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
    51
    52  import java.io.IOException;
    53  import java.io.StringWriter;
    54  import java.io.Writer;
    55  import java.lang.reflect.Method;
    56  import java.security.AccessController;
    57  import java.security.PrivilegedAction;
    58
    59  /***
    60   * Represents any closure object in Groovy.
    61   *
    62   * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
    63   * @author <a href="mailto:tug@wilson.co.uk">John Wilson</a>
    64   * @version $Revision: 4546 $
    65   */
    66  public abstract class Closure extends GroovyObjectSupport implements Cloneable, Runnable {
    67
    68      private static final Object noParameters[] = new Object[]{null};
    69      private static final Object emptyArray[] = new Object[0];
    70      private static final Object emptyArrayParameter[] = new Object[]{emptyArray};
    71
    72      private Object delegate;
    73      private final Object owner;
    74      private Class[] parameterTypes;
    75      protected int maximumNumberOfParameters;
    76      private final Object thisObject;
    77
    78
    79      private int directive = 0;
    80      public final static int DONE = 1, SKIP = 2;
    81
    82      public Closure(Object owner, Object thisObject) {
    83          this.owner = owner;
    84          this.delegate = owner;
    85          this.thisObject = thisObject;
    86
    87          Class closureClass = this.getClass();
    88          final Class clazz = closureClass;
    89          final Method[] methods = (Method[]) AccessController.doPrivileged(new  PrivilegedAction() {
    90              public Object run() {
    91                  return clazz.getDeclaredMethods();
    92              }
    93          });
    94
    95          // set it to -1 for starters so parameterTypes will always get a type
    96          maximumNumberOfParameters = -1;
    97          for (int j = 0; j < methods.length; j++) {
    98              if ("doCall".equals(methods[j].getName()) && methods[j].getParameterTypes().length > maximumNumberOfParameters) {
    99                  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 }