package net.sourceforge.javautil.groovy.builder;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import net.sourceforge.javautil.common.proxy.CollectionTargetProxy;
import net.sourceforge.javautil.groovy.io.GroovyPrintableOutputHandler;
import net.sourceforge.javautil.groovy.builder.GroovyBuilderInterceptor.InterceptorContext;
import org.codehaus.groovy.runtime.InvokerHelper;
import groovy.lang.Binding;
import groovy.lang.Closure;
import groovy.lang.GroovyObjectSupport;
import groovy.lang.MetaMethod;
import groovy.lang.MissingPropertyException;
/**
* This is the basic foundation for all net.sourceforge.javautil Groovy Builder's. It provides basic
* delegation functionality to allow method calls to be 'intercepted' by the builder and then delegated
* to what the implementation considers part of the build process. It will also allow pluggable binding's
* similar to script bindings to be setup for the builder.<br/><br/>
*
* @author elponderador
*
* @param <T> This is passed onto the GroovyBuilderStack, which means that a builder stack dealing with this type should
* be provided to the builder.
*
* @see net.sourceforge.javautil.groovy.framework.builders.GroovyBuilderStack
*/
public abstract class GroovyBuilder<T> extends GroovyObjectSupport implements GroovyPrintableOutputHandler {
protected Binding binding;
protected GroovyBuilderInterceptor interceptor;
protected List<GroovyBuilderListener> listeners = new CopyOnWriteArrayList<GroovyBuilderListener>();
protected GroovyBuilderListener propagator = CollectionTargetProxy.create(GroovyBuilderListener.class, listeners);
/**
* This constructor can be used by subclasses in order to allow them to
* setup the binding and interceptor when it is not practical to pass them
* to the super constructor.
*/
public GroovyBuilder () {}
/**
* This is the primary constructor to be used in order to setup immediately which
* binding and interceptor will be used by this builder implementation.
*
* @param binding The binding to be used for variable resolution.
* @param interceptor The interceptor that will translate method calls into the building process.
*/
public GroovyBuilder(Binding binding, GroovyBuilderInterceptor interceptor) {
this.binding = binding;
this.interceptor = interceptor;
}
/**
* The instance returned can be used to 'propagate' events.
*
* @return The event propagator, that implements the {@link GroovyBuilderListener} interface.
* @see {@link GroovyBuilderInterceptor}
*/
public GroovyBuilderListener<T> getEventPropagator () { return this.propagator; }
/**
* Add a {@link GroovyBuilderListener} to the list of listeners, that will be called in the order they are
* added, that will be notified of node and builder related events.
*
* @param listener The {@link GroovyBuilderListener} to add.
*/
public void addBuilderListener (GroovyBuilderListener<T> listener) { this.listeners.add(listener); }
/**
* Remove a {@link GroovyBuilderListener} from the list of listeners.
*
* @param listener The {@link GroovyBuilderListener} to be removed.
* @see #addBuilderListener(GroovyBuilderListener)
*/
public void removeBuilderListener (GroovyBuilderListener<T> listener) { this.listeners.remove(listener); }
/**
* @return This is a context object for contextual processing, resolution and the like. {@link GroovyBuilderStack}'s
* may decide to store the result of their processing inside the context for indirect storage.
*/
public abstract GroovyBuildingContext getBuildingContext ();
/**
* The binding can be retrieved publicly.
*
* @return The binding currently being used by this builder.
*/
public Binding getBinding() { return binding; }
/**
* To setup the binding. It is discouraged for this to be made public and is
* primarily intended for situations where the default constructor is used and
* later in the implementation constructor the binding can be set.
*
* @param binding A binding instance for variable resolution.
*/
protected void setBinding(Binding binding) { this.binding = binding; }
/**
* The interceptor currently being used by this builder for method delegation.
*
* @return The currently set interceptor;
*/
public GroovyBuilderInterceptor getInterceptor() { return interceptor; }
/**
* Like the {@link #setBinding(Binding)} method this is principally to be used
* when the default constructor is used and the interceptor is setup after initial
* super class instantiation.
*
* @param interceptor The interceptor implementation that is to be used by this builder.
*/
protected void setInterceptor(GroovyBuilderInterceptor interceptor) { this.interceptor = interceptor; }
/**
* The link to the stack in this thread which is the destination for all of the nodes
* built in a single building process.
*
* @return The implementation of the GroovyBuilderStack that is currently being used by this builder.
*/
public abstract GroovyBuilderStack<T> getStack ();
/**
* This is called on every invocation of {@link #invokeMethod(String, Object)} in order
* to determine if the stack has been initialized. If it returns false, then
* {@link #initializeStack()} will be called.
* @return
*/
public abstract boolean isStackInitialized ();
/**
* This allows the implementation the opportunity to
* initialize the stack before any nodes are created or
* added. This is called before {@link #startBuilding()}.
* Implementations MUST call {@link GroovyBuilderListener#stackStarting(GroovyBuilder)} AFTER
* {@link GroovyBuilderStack#initialize()} has been called.
*
* This method will be called by {@link #invokeMethod(String, Object)}
* whenever {@link #isStackInitialized()} returns false.
*/
public abstract void initializeStack ();
/**
* This allows the implementation the ability to
* cleanup the stack after the last node was completed.
* This is called after {@link #stopBuilding()}. Implementations MUST call
* {@link GroovyBuilderListener#stackStopping(GroovyBuilder)} BEFORE
* {@link GroovyBuilderStack#cleanup()} is called.
*/
public abstract void cleanupStack ();
/**
* This will be called before the first node is created, allowing the builder to do initialization.
* This will be called usually by the interceptor when it is sure, or is almost sure that a node is
* about to be created. It MUST be called before the root node is created for each building process.
*/
public void startBuilding () {
this.getBuildingContext().initialize();
}
/**
* This will be called after the last node is completed, allowing the builder to do cleanup.
*/
public void stopBuilding () {
this.getBuildingContext().cleanup();
}
/**
* A facility that can be used to setup a closure to be used by this builder. This
* will normally be called principally by the interceptor implementation. This can
* be used by other implementations to wrap the closure.
*
* @param closure The closure to be setup.
*/
public Closure setupClosure (Closure closure) {
closure.setDelegate(this);
closure.setResolveStrategy(Closure.DELEGATE_FIRST); closure.getMetaClass();
if (!(closure.getMetaClass() instanceof BuilderClosureMetaClass)) closure.setMetaClass( new BuilderClosureMetaClass(closure, this) );
return closure;
}
/**
* A facility that can be used to invoke a closure. It will by default call
* the @link {@link #setupClosure(Closure)} method and then using the returned
* value call the closure.
*
* @param closure
* @param arguments
*/
public void invoke (Closure closure, Object... arguments) {
closure = this.setupClosure(closure);
closure.call(arguments);
}
/**
* This is overridden to allow missing properties to be
* delegated to the binding.
*/
public Object getProperty(String property) {
try {
return super.getProperty(property);
} catch (MissingPropertyException e) {
return binding.getProperty(property);
}
}
/**
* This simply looks up the method that should be called via the meta class. If there
* is none it will pass null along with the other arguments to the
* {@link net.sourceforge.javautil.groovy.framework.builders.GroovyBuilderInterceptor#handleInvokedMethod(GroovyBuilder, MetaMethod, String, Object[])}.
*
* This will also call {@link #initializeStack()} whenever the {@link #getStack()} method returns null.
*
* It does NOT invoke any methods, even if a meta method was found for the call.
*/
public Object invokeMethod(String name, Object args) {
if (!this.isStackInitialized()) this.initializeStack();
Object[] arguments = InvokerHelper.asArray(args);
return interceptor.handleInvokedMethod(new InterceptorContext( this, this.getStack(), name, arguments ));
}
/**
* This is overridden to allow missing property exceptions to fall back
* to the binding that has been set.
*/
public void setProperty(String property, Object newValue) {
try {
super.setProperty(property, newValue);
} catch (MissingPropertyException e) {
binding.setProperty(property, newValue);
}
}
}