blob: c9bd0d56242436712278916280ede03ffc40e9c0 [file] [log] [blame]
/*
* Copyright 2003-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package groovy.lang;
import org.codehaus.groovy.runtime.CurriedClosure;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
/**
* Represents any closure object in Groovy.
* <p/>
* Groovy allows instances of Closures to be called in a
* short form. For example:
* <pre>
* def a = 1
* def c = {a}
* assert c() == 1
* </pre>
* To be able to use a Closure in this way with your own
* subclass, you need to provide a doCall method with any
* signature you want to. This ensures that
* {@link #getMaximumNumberOfParameters()} and
* {@link #getParameterTypes()} will work too without any
* additional code. If no doCall method is provided a
* closure must be used in its long form like
* <pre>
* def a = 1
* def c = {a}
* assert c.call() == 1
* </pre>
*
* @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
* @author <a href="mailto:tug@wilson.co.uk">John Wilson</a>
* @author <a href="mailto:blackdrag@gmx.org">Jochen Theodorou</a>
* @author Graeme Rocher
*
* @version $Revision$
*/
public abstract class Closure extends GroovyObjectSupport implements Cloneable, Runnable {
/**
* With this resolveStrategy set the closure will attempt to resolve property references to the
* owner first
*/
public static final int OWNER_FIRST = 0;
/**
* With this resolveStrategy set the closure will attempt to resolve property references to the
* delegate first
*/
public static final int DELEGATE_FIRST = 1;
/**
* With this resolveStrategy set the closure will resolve property references to the owner only
* and not call the delegate at all
*/
public static final int OWNER_ONLY = 2;
/**
* With this resolveStrategy set the closure will resolve property references to the delegate
* only and entirely bypass the owner
*/
public static final int DELEGATE_ONLY = 3;
/**
* With this resolveStrategy set the closure will resolve property references to itself and go
* through the usual MetaClass look-up process. This allows the developer to override getProperty
* using ExpandoMetaClass of the closure itself
*/
public static final int TO_SELF = 4;
private Object delegate;
private final Object owner;
private Class[] parameterTypes;
protected int maximumNumberOfParameters;
private final Object thisObject;
private int resolveStrategy = OWNER_FIRST;
private int directive;
public static final int DONE = 1, SKIP = 2;
public Closure(Object owner, Object thisObject) {
this.owner = owner;
this.delegate = owner;
this.thisObject = thisObject;
final Class clazz = this.getClass();
final Method[] methods = (Method[]) AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return clazz.getDeclaredMethods();
}
});
// set it to -1 for starters so parameterTypes will always get a type
maximumNumberOfParameters = -1;
for (int j = 0; j < methods.length; j++) {
if ("doCall".equals(methods[j].getName()) && methods[j].getParameterTypes().length > maximumNumberOfParameters) {
parameterTypes = methods[j].getParameterTypes();
maximumNumberOfParameters = parameterTypes.length;
}
}
// this line should be useless, but well, just in case
maximumNumberOfParameters = Math.max(maximumNumberOfParameters,0);
}
public Closure(Object owner) {
this(owner,null);
}
/**
* Sets the strategy which the closure uses to resolve property references. The default is Closure.OWNER_FIRST
*
* @param resolveStrategy The resolve strategy to set
*
* @see groovy.lang.Closure#DELEGATE_FIRST
* @see groovy.lang.Closure#DELEGATE_ONLY
* @see groovy.lang.Closure#OWNER_FIRST
* @see groovy.lang.Closure#OWNER_ONLY
* @see groovy.lang.Closure#TO_SELF
*/
public void setResolveStrategy(int resolveStrategy) {
this.resolveStrategy = resolveStrategy;
}
/**
* Gets the strategy which the closure users to resolve methods and properties
*
* @return The resolve strategy
*
* @see groovy.lang.Closure#DELEGATE_FIRST
* @see groovy.lang.Closure#DELEGATE_ONLY
* @see groovy.lang.Closure#OWNER_FIRST
* @see groovy.lang.Closure#OWNER_ONLY
* @see groovy.lang.Closure#TO_SELF
*/
public int getResolveStrategy() {
return resolveStrategy;
}
protected Object getThisObject(){
return thisObject;
}
public Object getProperty(final String property) {
if ("delegate".equals(property)) {
return getDelegate();
} else if ("owner".equals(property)) {
return getOwner();
} else if ("maximumNumberOfParameters".equals(property)) {
return new Integer(getMaximumNumberOfParameters());
} else if ("parameterTypes".equals(property)) {
return getParameterTypes();
} else if ("metaClass".equals(property)) {
return getMetaClass();
} else if ("class".equals(property)) {
return getClass();
} else if ("directive".equals(property)) {
return new Integer(getDirective());
} else {
switch(resolveStrategy) {
case DELEGATE_FIRST:
return getPropertyDelegateFirst(property);
case DELEGATE_ONLY:
return InvokerHelper.getProperty(this.delegate, property);
case OWNER_ONLY:
return InvokerHelper.getProperty(this.owner, property);
case TO_SELF:
return super.getProperty(property);
default:
return getPropertyOwnerFirst(property);
}
}
}
private Object getPropertyDelegateFirst(String property) {
if(delegate == null) return getPropertyOwnerFirst(property);
return getPropertyTryThese(property, this.delegate, this.owner);
}
private Object getPropertyTryThese(String property, Object firstTry, Object secondTry) {
try {
// lets try getting the property on the owner
return InvokerHelper.getProperty(firstTry, property);
} catch (MissingPropertyException e1) {
if (secondTry != null && firstTry != this && firstTry != secondTry) {
try {
// lets try getting the property on the delegate
return InvokerHelper.getProperty(secondTry, property);
} catch (GroovyRuntimeException e2) {
// ignore, we'll throw e1
}
}
throw e1;
}
}
private Object getPropertyOwnerFirst(String property) {
return getPropertyTryThese(property, this.owner, this.delegate);
}
public void setProperty(String property, Object newValue) {
if ("delegate".equals(property)) {
setDelegate(newValue);
} else if ("metaClass".equals(property)) {
setMetaClass((MetaClass) newValue);
} else if ("resolveStrategy".equals(property)) {
setResolveStrategy(((Number)newValue).intValue());
}
else {
switch(resolveStrategy) {
case DELEGATE_FIRST:
setPropertyDelegateFirst(property, newValue);
break;
case DELEGATE_ONLY:
InvokerHelper.setProperty(this.delegate, property, newValue);
break;
case OWNER_ONLY:
InvokerHelper.setProperty(this.owner, property, newValue);
break;
case TO_SELF:
super.setProperty(property, newValue);
break;
default:
setPropertyOwnerFirst(property, newValue);
}
}
}
private void setPropertyDelegateFirst(String property, Object newValue) {
if(delegate == null) setPropertyOwnerFirst(property, newValue);
else
setPropertyTryThese(property, newValue, this.delegate, this.owner);
}
private void setPropertyTryThese(String property, Object newValue, Object firstTry, Object secondTry) {
try {
// lets try setting the property on the owner
InvokerHelper.setProperty(firstTry, property, newValue);
} catch (GroovyRuntimeException e1) {
if (firstTry != null && firstTry != this && firstTry != secondTry) {
try {
// lets try setting the property on the delegate
InvokerHelper.setProperty(secondTry, property, newValue);
return;
} catch (GroovyRuntimeException e2) {
// ignore, we'll throw e1
}
}
throw e1;
}
}
private void setPropertyOwnerFirst(String property, Object newValue) {
setPropertyTryThese(property, newValue, this.owner, this.delegate);
}
public boolean isCase(Object candidate){
return DefaultTypeTransformation.castToBoolean(call(candidate));
}
/**
* Invokes the closure without any parameters, returning any value if applicable.
*
* @return the value if applicable or null if there is no return statement in the closure
*/
public Object call() {
return call(new Object[]{});
}
public Object call(Object[] args) {
try {
return getMetaClass().invokeMethod(this,"doCall",args);
} catch (Exception e) {
return throwRuntimeException(e);
}
}
/**
* Invokes the closure, returning any value if applicable.
*
* @param arguments could be a single value or a List of values
* @return the value if applicable or null if there is no return statement in the closure
*/
public Object call(final Object arguments) {
return call(new Object[]{arguments});
}
protected static Object throwRuntimeException(Throwable throwable) {
if (throwable instanceof RuntimeException) {
throw (RuntimeException) throwable;
} else {
throw new GroovyRuntimeException(throwable.getMessage(), throwable);
}
}
/**
* @return the owner Object to which method calls will go which is
* typically the outer class when the closure is constructed
*/
public Object getOwner() {
return this.owner;
}
/**
* @return the delegate Object to which method calls will go which is
* typically the outer class when the closure is constructed
*/
public Object getDelegate() {
return this.delegate;
}
/**
* Allows the delegate to be changed such as when performing markup building
*
* @param delegate the new delegate
*/
public void setDelegate(Object delegate) {
this.delegate = delegate;
}
/**
* @return the parameter types of the longest doCall method
* of this closure
*/
public Class[] getParameterTypes() {
return this.parameterTypes;
}
/**
* @return the maximum number of parameters a doCall methos
* of this closure can take
*/
public int getMaximumNumberOfParameters() {
return this.maximumNumberOfParameters;
}
/**
* @return a version of this closure which implements Writable
*/
public Closure asWritable() {
return new WritableClosure();
}
/* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
public void run() {
call();
}
/**
* Support for closure currying
*
* @param arguments the arguments to bind
* @return the new closure with its arguments bound
*/
public Closure curry(final Object arguments[]) {
return new CurriedClosure(this,arguments);
}
/* (non-Javadoc)
* @see java.lang.Object#clone()
*/
public Object clone() {
try {
return super.clone();
} catch (final CloneNotSupportedException e) {
return null;
}
}
/**
* Implementation note:
* This has to be an inner class!
*
* Reason:
* Closure.this.call will call the outer call method, bur
* with the inner class as executing object. This means any
* invokeMethod or getProperty call will be called on this
* inner class instead of the outer!
*/
private class WritableClosure extends Closure implements Writable {
public WritableClosure() {
super(Closure.this);
}
/* (non-Javadoc)
* @see groovy.lang.Writable#writeTo(java.io.Writer)
*/
public Writer writeTo(Writer out) throws IOException {
Closure.this.call(new Object[]{out});
return out;
}
/* (non-Javadoc)
* @see groovy.lang.GroovyObject#invokeMethod(java.lang.String, java.lang.Object)
*/
public Object invokeMethod(String method, Object arguments) {
if ("clone".equals(method)) {
return clone();
} else if ("curry".equals(method)) {
return curry((Object[]) arguments);
} else if ("asWritable".equals(method)) {
return asWritable();
} else {
return Closure.this.invokeMethod(method, arguments);
}
}
/* (non-Javadoc)
* @see groovy.lang.GroovyObject#getProperty(java.lang.String)
*/
public Object getProperty(String property) {
return Closure.this.getProperty(property);
}
/* (non-Javadoc)
* @see groovy.lang.GroovyObject#setProperty(java.lang.String, java.lang.Object)
*/
public void setProperty(String property, Object newValue) {
Closure.this.setProperty(property, newValue);
}
/* (non-Javadoc)
* @see groovy.lang.Closure#call()
*/
public Object call() {
return Closure.this.call();
}
/* (non-Javadoc)
* @see groovy.lang.Closure#call(java.lang.Object)
*/
public Object call(Object arguments) {
return Closure.this.call(arguments);
}
/* (non-Javadoc)
* @see groovy.lang.Closure#getDelegate()
*/
public Object getDelegate() {
return Closure.this.getDelegate();
}
/* (non-Javadoc)
* @see groovy.lang.Closure#setDelegate(java.lang.Object)
*/
public void setDelegate(Object delegate) {
Closure.this.setDelegate(delegate);
}
/* (non-Javadoc)
* @see groovy.lang.Closure#getParameterTypes()
*/
public Class[] getParameterTypes() {
return Closure.this.getParameterTypes();
}
/* (non-Javadoc)
* @see groovy.lang.Closure#getParameterTypes()
*/
public int getMaximumNumberOfParameters() {
return Closure.this.getMaximumNumberOfParameters();
}
/* (non-Javadoc)
* @see groovy.lang.Closure#asWritable()
*/
public Closure asWritable() {
return this;
}
/* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
public void run() {
Closure.this.run();
}
/* (non-Javadoc)
* @see java.lang.Object#clone()
*/
public Object clone() {
return ((Closure) Closure.this.clone()).asWritable();
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
return Closure.this.hashCode();
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object arg0) {
return Closure.this.equals(arg0);
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
public String toString() {
final StringWriter writer = new StringWriter();
try {
writeTo(writer);
} catch (IOException e) {
return null;
}
return writer.toString();
}
public Closure curry(final Object arguments[]) {
return (new CurriedClosure(this,arguments)).asWritable();
}
public void setResolveStrategy(int resolveStrategy) {
Closure.this.setResolveStrategy(resolveStrategy);
}
public int getResolveStrategy() {
return Closure.this.getResolveStrategy();
}
}
/**
* @return Returns the directive.
*/
public int getDirective() {
return directive;
}
/**
* @param directive The directive to set.
*/
public void setDirective(int directive) {
this.directive = directive;
}
}