View Javadoc

    1   /*
    2    $Id: Closure.java,v 1.41 2004/12/14 06:25:17 spullara Exp $
    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.InvokerHelper;
    49
    50  import java.io.IOException;
    51  import java.io.StringWriter;
    52  import java.io.Writer;
    53  import java.lang.reflect.InvocationTargetException;
    54  import java.lang.reflect.Method;
    55  import java.security.AccessController;
    56  import java.security.PrivilegedAction;
    57
    58  /***
    59   * Represents any closure object in Groovy.
    60   *
    61   * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
    62   * @author <a href="mailto:tug@wilson.co.uk">John Wilson</a>
    63   * @version $Revision: 1.41 $
    64   */
    65  public abstract class Closure extends GroovyObjectSupport implements Cloneable, Runnable {
    66
    67      private static final Object noParameters[] = new Object[]{null};
    68      private static final Object emptyArray[] = new Object[0];
    69      private static final Object emptyArrayParameter[] = new Object[]{emptyArray};
    70
    71      private Object delegate;
    72      private final Object owner;
    73      private final Method doCallMethod;
    74      private final boolean supportsVarargs;
    75      private final Class parameterTypes[];
    76      private final int numberOfParameters;
    77      private Object curriedParams[] = emptyArray;
    78
    79
    80      private int directive = 0;
    81      public static int DONE = 1;
    82      public static int SKIP = 2;
    83
    84      public Closure(Object delegate) {
    85          this.delegate = delegate;
    86          this.owner = delegate;
    87
    88          Class closureClass = this.getClass();
    89
    90          while (true) {
    91              final Method methods[] = closureClass.getDeclaredMethods();
    92
    93              int i = 0;
    94
    95              while (!methods[i].getName().equals("doCall") && ++i != methods.length) ;
    96
    97              if (i < methods.length) {
    98                  this.doCallMethod = methods[i];
    99                  break;
    100             }
    101
    102             closureClass = closureClass.getSuperclass();
    103         }
    104
    105         AccessController.doPrivileged(new PrivilegedAction() {
    106             public Object run() {
    107                 Closure.this.doCallMethod.setAccessible(true);
    108                 return null;
    109             }
    110         });
    111
    112         this.parameterTypes = this.doCallMethod.getParameterTypes();
    113
    114         this.numberOfParameters = this.parameterTypes.length;
    115
    116         if (this.numberOfParameters != 0) {
    117             this.supportsVarargs = this.parameterTypes[this.numberOfParameters - 1].equals(Object[].class);
    118         } else {
    119             this.supportsVarargs = false;
    120         }
    121     }
    122
    123     public Object invokeMethod(String method, Object arguments) {
    124         if ("doCall".equals(method) || "call".equals(method)) {
    125             return call(arguments);
    126         } else if ("curry".equals(method)) {
    127             return curry((Object[]) arguments);
    128         } else {
    129             try {
    130                 return getMetaClass().invokeMethod(this, method, arguments);
    131             } catch (MissingMethodException e) {
    132                 if (owner != this) {
    133                     try {
    134                         // lets try invoke method on the owner
    135                         return InvokerHelper.invokeMethod(this.owner, method, arguments);
    136                     } catch (GroovyRuntimeException e1) {
    137                         if (this.delegate != null && this.delegate != this && this.delegate != this.owner) {
    138                             // lets try invoke method on the delegate
    139                             return InvokerHelper.invokeMethod(this.delegate, method, arguments);
    140                         }
    141                     }
    142                 }
    143                 throw e;
    144             }
    145         }
    146
    147     }
    148
    149     public Object getProperty(String property) {
    150         if ("delegate".equals(property)) {
    151             return getDelegate();
    152         } else if ("owner".equals(property)) {
    153             return getOwner();
    154         } else if ("method".equals(property)) {
    155             return getMethod();
    156         } else if ("parameterTypes".equals(property)) {
    157             return getParameterTypes();
    158         } else if ("metaClass".equals(property)) {
    159             return getMetaClass();
    160         } else if ("class".equals(property)) {
    161             return getClass();
    162         } else {
    163             try {
    164 // lets try getting the property on the owner
    165                 return InvokerHelper.getProperty(this.owner, property);
    166             } catch (GroovyRuntimeException e1) {
    167                 if (this.delegate != null && this.delegate != this && this.delegate != this.owner) {
    168                     try {
    169 // lets try getting the property on the delegate
    170                         return InvokerHelper.getProperty(this.delegate, property);
    171                     } catch (GroovyRuntimeException e2) {
    172 // ignore, we'll throw e1
    173                     }
    174                 }
    175
    176                 throw e1;
    177             }
    178         }
    179     }
    180
    181     public void setProperty(String property, Object newValue) {
    182         if ("delegate".equals(property)) {
    183             setDelegate(newValue);
    184         } else if ("metaClass".equals(property)) {
    185             setMetaClass((MetaClass) newValue);
    186         } else {
    187             try {
    188 // lets try setting the property on the owner
    189                 InvokerHelper.setProperty(this.owner, property, newValue);
    190                 return;
    191             } catch (GroovyRuntimeException e1) {
    192                 if (this.delegate != null && this.delegate != this && this.delegate != this.owner) {
    193                     try {
    194 // lets try setting the property on the delegate
    195                         InvokerHelper.setProperty(this.delegate, property, newValue);
    196                         return;
    197                     } catch (GroovyRuntimeException e2) {
    198 // ignore, we'll throw e1
    199                     }
    200                 }
    201
    202                 throw e1;
    203             }
    204         }
    205     }
    206
    207     /***
    208      * Invokes the closure without any parameters, returning any value if applicable.
    209      *
    210      * @return the value if applicable or null if there is no return statement in the closure
    211      */
    212     public Object call() {
    213         return call(emptyArray);
    214     }
    215
    216     /***
    217      * Invokes the closure, returning any value if applicable.
    218      *
    219      * @param arguments could be a single value or a List of values
    220      * @return the value if applicable or null if there is no return statement in the closure
    221      */
    222     public Object call(final Object arguments) {
    223         final Object params[];
    224
    225         if (this.curriedParams.length != 0) {
    226             final Object args[];
    227
    228             if (arguments instanceof Object[]) {
    229                 args = (Object[]) arguments;
    230             } else {
    231                 args = new Object[]{arguments};
    232             }
    233
    234             params = new Object[this.curriedParams.length + args.length];
    235
    236             System.arraycopy(this.curriedParams, 0, params, 0, this.curriedParams.length);
    237             System.arraycopy(args, 0, params, this.curriedParams.length, args.length);
    238         } else {
    239             if (arguments instanceof Object[]) {
    240                 params = (Object[]) arguments;
    241             } else {
    242                 return doCall(arguments);
    243             }
    244         }
    245
    246         final int lastParam = this.numberOfParameters - 1;
    247
    248         if (this.supportsVarargs && !(this.numberOfParameters == params.length && (params[lastParam] == null || params[lastParam].getClass() == Object[].class))) {
    249             final Object actualParameters[] = new Object[this.numberOfParameters];
    250
    251             //
    252             // We have a closure which supports variable arguments and we haven't got actual
    253             // parameters which have exactly the right number of parameters and ends with a null or an Object[]
    254             //
    255             if (params.length < lastParam) {
    256                 //
    257                 // Not enough parameters throw exception
    258                 //
    259                 // Note we allow there to be one fewer actual parameter than the number of formal parameters
    260                 // in this case we pass an zero length Object[] as the last parameter
    261                 //
    262                 throw new IncorrectClosureArgumentsException(this, params, this.parameterTypes);
    263             } else {
    264                 final Object rest[] = new Object[params.length - lastParam];	 // array used to pass the rest of the paraters
    265
    266                 // fill the parameter array up to but not including the last one
    267                 System.arraycopy(params, 0, actualParameters, 0, lastParam);
    268
    269                 // put the rest of the parameters in the overflow araay
    270                 System.arraycopy(params, lastParam, rest, 0, rest.length);
    271
    272                 // pass the overflow array as the last parameter
    273                 actualParameters[lastParam] = rest;
    274
    275                 return callViaReflection(actualParameters);
    276             }
    277         }
    278
    279         if (params.length == 0) {
    280             // pass a single null parameter if no parameters specified
    281             return doCall(null);
    282         } else if (params.length == 1) {
    283             return doCall(params[0]);
    284         } else if (params.length == 2) {
    285             return doCall(params[0], params[1]);
    286         } else {
    287             return callViaReflection(params);
    288         }
    289     }
    290
    291     protected static Object throwRuntimeException(Throwable throwable) {
    292         if (throwable instanceof RuntimeException) {
    293             throw (RuntimeException) throwable;
    294         } else {
    295             throw new GroovyRuntimeException(throwable.getMessage(), throwable);
    296         }
    297     }
    298
    299     /***
    300      * An attempt to optimise calling closures with one parameter
    301      * If the closure has one untyped parameter then it will overload this function
    302      * If not this will be called ans will use reflection to deal with the case of a
    303      * single typed parameter
    304      *
    305      * @param p1
    306      * @return the result of calling the closure
    307      */
    308     protected Object doCall(final Object p1) {
    309         return callViaReflection(new Object[]{p1});
    310     }
    311
    312     /***
    313      * An attempt to optimise calling closures with two parameters
    314      * If the closure has two untyped parameters then it will overload this function
    315      * If not this will be called ans will use reflection to deal with the case of one
    316      * or two typed parameters
    317      *
    318      * @param p1
    319      * @return the result of calling the closure
    320      */
    321
    322     protected Object doCall(final Object p1, final Object p2) {
    323         return callViaReflection(new Object[]{p1, p2});
    324     }
    325
    326     private Object callViaReflection(final Object params[]) {
    327         try {
    328             // invoke the closure
    329             return this.doCallMethod.invoke(this, params);
    330         } catch (final IllegalArgumentException e) {
    331             throw new IncorrectClosureArgumentsException(this, params, this.parameterTypes);
    332         } catch (final IllegalAccessException e) {
    333             final Throwable cause = e.getCause();
    334
    335             return throwRuntimeException((cause == null) ? e : cause);
    336         } catch (final InvocationTargetException e) {
    337             final Throwable cause = e.getCause();
    338
    339             return throwRuntimeException((cause == null) ? e : cause);
    340         }
    341     }
    342
    343     /***
    344      * Used when a closure wraps a method on a class
    345      *
    346      * @return empty string
    347      */
    348     public String getMethod() {
    349         return "";
    350     }
    351
    352     /***
    353      * @return the owner Object to which method calls will go which is
    354      *         typically the outer class when the closure is constructed
    355      */
    356     public Object getOwner() {
    357         return this.owner;
    358     }
    359
    360     /***
    361      * @return the delegate Object to which method calls will go which is
    362      *         typically the outer class when the closure is constructed
    363      */
    364     public Object getDelegate() {
    365         return this.delegate;
    366     }
    367
    368     /***
    369      * Allows the delegate to be changed such as when performing markup building
    370      *
    371      * @param delegate
    372      */
    373     public void setDelegate(Object delegate) {
    374         this.delegate = delegate;
    375     }
    376
    377     /***
    378      * @return the parameter types of this closure
    379      */
    380     public Class[] getParameterTypes() {
    381         return this.parameterTypes;
    382     }
    383
    384     /***
    385      * @return a version of this closure which implements Writable
    386      */
    387     public Closure asWritable() {
    388         return new WritableClosure();
    389     }
    390
    391     /* (non-Javadoc)
    392      * @see java.lang.Runnable#run()
    393      */
    394     public void run() {
    395         call();
    396     }
    397
    398     /***
    399      * Support for closure currying
    400      *
    401      * @param arguments
    402      */
    403     public Closure curry(final Object arguments[]) {
    404         final Closure curriedClosure = (Closure) this.clone();
    405         final Object newCurriedParams[] = new Object[curriedClosure.curriedParams.length + arguments.length];
    406
    407         System.arraycopy(curriedClosure.curriedParams, 0, newCurriedParams, 0, curriedClosure.curriedParams.length);
    408         System.arraycopy(arguments, 0, newCurriedParams, curriedClosure.curriedParams.length, arguments.length);
    409
    410         curriedClosure.curriedParams = newCurriedParams;
    411
    412         return curriedClosure;
    413     }
    414
    415     /* (non-Javadoc)
    416      * @see java.lang.Object#clone()
    417      */
    418     public Object clone() {
    419         try {
    420             return super.clone();
    421         } catch (final CloneNotSupportedException e) {
    422             return null;
    423         }
    424     }
    425
    426     private class WritableClosure extends Closure implements Writable {
    427         public WritableClosure() {
    428             super(null);
    429         }
    430
    431         /* (non-Javadoc)
    432      * @see groovy.lang.Writable#writeTo(java.io.Writer)
    433      */
    434         public Writer writeTo(Writer out) throws IOException {
    435             Closure.this.call(out);
    436
    437             return out;
    438         }
    439
    440         /* (non-Javadoc)
    441          * @see groovy.lang.GroovyObject#invokeMethod(java.lang.String, java.lang.Object)
    442          */
    443         public Object invokeMethod(String method, Object arguments) {
    444             if ("clone".equals(method)) {
    445                 return clone();
    446             } else if ("curry".equals(method)) {
    447                 return curry((Object[]) arguments);
    448             } else if ("asWritable".equals(method)) {
    449                 return asWritable();
    450             } else {
    451                 return Closure.this.invokeMethod(method, arguments);
    452             }
    453         }
    454
    455         /* (non-Javadoc)
    456          * @see groovy.lang.GroovyObject#getProperty(java.lang.String)
    457          */
    458         public Object getProperty(String property) {
    459             return Closure.this.getProperty(property);
    460         }
    461
    462         /* (non-Javadoc)
    463          * @see groovy.lang.GroovyObject#setProperty(java.lang.String, java.lang.Object)
    464          */
    465         public void setProperty(String property, Object newValue) {
    466             Closure.this.setProperty(property, newValue);
    467         }
    468
    469         /* (non-Javadoc)
    470          * @see groovy.lang.Closure#call()
    471          */
    472         public Object call() {
    473             return Closure.this.call();
    474         }
    475
    476         /* (non-Javadoc)
    477          * @see groovy.lang.Closure#call(java.lang.Object)
    478          */
    479         public Object call(Object arguments) {
    480             return Closure.this.call(arguments);
    481         }
    482
    483         /* (non-Javadoc)
    484          * @see groovy.lang.Closure#doCall(java.lang.Object)
    485          */
    486         protected Object doCall(Object p1) {
    487             return Closure.this.doCall(p1);
    488         }
    489
    490         /* (non-Javadoc)
    491          * @see groovy.lang.Closure#doCall(java.lang.Object, java.lang.Object)
    492          */
    493         protected Object doCall(Object p1, Object p2) {
    494             return Closure.this.doCall(p1, p2);
    495         }
    496
    497         /* (non-Javadoc)
    498          * @see groovy.lang.Closure#getDelegate()
    499          */
    500         public Object getDelegate() {
    501             return Closure.this.getDelegate();
    502         }
    503
    504         /* (non-Javadoc)
    505          * @see groovy.lang.Closure#setDelegate(java.lang.Object)
    506          */
    507         public void setDelegate(Object delegate) {
    508             Closure.this.setDelegate(delegate);
    509         }
    510
    511         /* (non-Javadoc)
    512          * @see groovy.lang.Closure#getParameterTypes()
    513          */
    514         public Class[] getParameterTypes() {
    515             return Closure.this.getParameterTypes();
    516         }
    517
    518         /* (non-Javadoc)
    519          * @see groovy.lang.Closure#asWritable()
    520          */
    521         public Closure asWritable() {
    522             return this;
    523         }
    524
    525         /* (non-Javadoc)
    526          * @see java.lang.Runnable#run()
    527          */
    528         public void run() {
    529             Closure.this.run();
    530         }
    531
    532         /* (non-Javadoc)
    533          * @see groovy.lang.Closure#curry(java.lang.Object[])
    534          */
    535         public Closure curry(Object[] arguments) {
    536             return Closure.this.curry(arguments).asWritable();
    537         }
    538
    539         /* (non-Javadoc)
    540          * @see java.lang.Object#clone()
    541          */
    542         public Object clone() {
    543             return ((Closure) Closure.this.clone()).asWritable();
    544         }
    545
    546         /* (non-Javadoc)
    547          * @see java.lang.Object#hashCode()
    548          */
    549         public int hashCode() {
    550             return Closure.this.hashCode();
    551         }
    552
    553         /* (non-Javadoc)
    554          * @see java.lang.Object#equals(java.lang.Object)
    555          */
    556         public boolean equals(Object arg0) {
    557             return Closure.this.equals(arg0);
    558         }
    559
    560         /* (non-Javadoc)
    561          * @see java.lang.Object#toString()
    562          */
    563         public String toString() {
    564             final StringWriter writer = new StringWriter();
    565
    566             try {
    567                 writeTo(writer);
    568             } catch (IOException e) {
    569                 return null;
    570             }
    571
    572             return writer.toString();
    573         }
    574     }
    575
    576     /***
    577      * @return Returns the directive.
    578      */
    579     public int getDirective() {
    580         return directive;
    581     }
    582
    583     /***
    584      * @param directive The directive to set.
    585      */
    586     public void setDirective(int directive) {
    587         this.directive = directive;
    588     }
    589 }