Package net.sourceforge.javautil.groovy.builder.interceptor.objectfactory

Source Code of net.sourceforge.javautil.groovy.builder.interceptor.objectfactory.ObjectFactoryInterceptor

package net.sourceforge.javautil.groovy.builder.interceptor.objectfactory;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;

import groovy.lang.Closure;
import groovy.lang.MetaClass;
import groovy.lang.MetaMethod;
import groovy.lang.MetaProperty;
import groovy.lang.MissingMethodException;
import groovy.lang.MissingPropertyException;

import net.sourceforge.javautil.groovy.builder.GroovyBuilder;
import net.sourceforge.javautil.groovy.builder.GroovyBuilderInterceptor;
import net.sourceforge.javautil.groovy.builder.GroovyBuilderStack;
import net.sourceforge.javautil.groovy.util.ParameterUtil;
import net.sourceforge.javautil.groovy.util.ParameterUtil.InvalidParameterException;

import org.codehaus.groovy.runtime.InvokerHelper;

/**
* This class allows the interceptor functionality to be pluggable using the {@link ObjectFactoryInstantiator}
* interface and {@link NodeAttributeApplicator} interface. Class implementing independently or together with the
* {@link NodeConstructor} interface can specify exactly how objects are instantiated in relation to node calls
* and how attributes are applied and parent<->child nodes are associated.
*
* @author elponderador
*
* @see #handleInvokedMethod(GroovyBuilder, GroovyBuilderStack, MetaMethod, String, Object[])
*/
public class ObjectFactoryInterceptor<T extends GroovyBuilder, S extends GroovyBuilderStack> implements GroovyBuilderInterceptor<T, S> {
 
  protected boolean passParentToConstructor = true;
  protected boolean callProxyFirst = false;
  protected boolean attemptBinding = false;
 
  protected ObjectFactoryInstantiator instantiator;
  protected NodeAttributeApplicator applicator;
  protected NodeMapper mapper;
 
  private Map<String, MetaClass> cache = new HashMap<String, MetaClass>();
 
  /**
   * Since {@link NodeConstructor}'s implement both {@link ObjectFactoryInstantiator} and
   * {@link NodeAttributeApplicator} this simply calls the main {@link #ObjectFactoryInterceptor(ObjectFactoryInstantiator, NodeAttributeApplicator, boolean, boolean)}
   * constructor and passes the nc to the first two arguments.
   *
   * @param nc The node constructor implementation
   * @param passParentToConstructor See {@link #isPassParentToConstructor()}
   * @param callProxyFirst See {@link #isCallProxyFirst()}
   */
  public ObjectFactoryInterceptor(NodeConstructor nc, boolean passParentToConstructor, boolean callProxyFirst) {
    this(nc, nc, nc, passParentToConstructor, callProxyFirst);
  }
 
  public ObjectFactoryInterceptor(ObjectFactoryInstantiator instantiator, NodeMapper mapper, NodeAttributeApplicator applicator, boolean passParentToConstructor, boolean callProxyFirst) {
    this(instantiator, mapper, applicator, passParentToConstructor, callProxyFirst, false);
  }

  /**
   * This allows complete setup of the interceptor.
   *
   * @param instantiator
   * @param applicator
   * @param passParentToConstructor
   * @param callProxyFirst
   */
  public ObjectFactoryInterceptor(ObjectFactoryInstantiator instantiator, NodeMapper mapper, NodeAttributeApplicator applicator,
      boolean passParentToConstructor, boolean callProxyFirst, boolean attemptBindingCalls) {
    this.passParentToConstructor = passParentToConstructor;
    this.callProxyFirst = callProxyFirst;
    this.instantiator = instantiator;
    this.applicator = applicator;
    this.mapper = mapper;
    this.attemptBinding = attemptBindingCalls;
  }
 
  /**
   * This allows caching for MetaClass associations to node names (objects). In particular
   * {@link NodeAttributeApplicator}'s should make use of this instead of looking up again info
   * that is stored here by {@link ObjectFactoryInstantiator}'s.
   *
   * @param nodeName
   * @return
   */
  public MetaClass getMetaClassForNode (String nodeName) { return this.cache.get(nodeName); }
 
  /**
   * This allows {@link ObjectFactoryInstantiator}'s to cache MetaClass information
   * when they resolve new node's. Then the same {@link ObjectFactoryInstantiator}'s
   * should use the {@link #getMetaClassForNode(String)} method for retrieving cached
   * meta class association.
   *
   * @param nodeName The node name to associate with this MetaClass
   * @param mc The MetaClass to associate with the node name
   */
  public void setMetaClassForNode (String nodeName, MetaClass mc) { this.cache.put(nodeName, mc); }

  /**
   * @return If returns true, it means the interceptor will attempt to create a node object first before calling any possible found
   * meta methods on the builder. If returns false, it means that the interceptor will called any meta methods on the builder first
   * before attempting to attempting to create a node object.
   */
  public boolean isCallProxyFirst() { return callProxyFirst; }

  /**
   * This specifies whether or not this interceptor expected to pass parent nodes to child
   * node constructors when instantiating them in {@link #instantiateNode(String, Object)}.
   * The default is determined by what was passed in the constructor, and sub classes can
   * set this to change behaviour. Sub classes should check this flag in {@link #instantiateNode(String, Object)}
   * in order to follow this contract. If this is false, sub classes should provide an alternative
   * mechanism for associating parent nodes to child nodes.
   *
   * @return Returns true if parent nodes will/can be passed to sub node object constructors, otherwise false.
   */
  public boolean isPassParentToConstructor() { return passParentToConstructor; }
 
  /**
   * If the {@link #callProxyFirst} flag is true then it will attempt to call the method on the current node (if any). Otherwise it will then
   * attempt to find an object via {@link ObjectFactoryInstantiator#instantiateNode(ObjectFactoryInterceptor, String, Object)} before falling
   * back to the original method (if there is one) otherwise if the method parameter is not null it will invoke this method instead.<br/><br/>
   *
   * When attempting to find a class in the proxy package(s) it will first check to see 3 or less arguments have been passed to the method
   * call. These arguments should match standard optional parameters (Closure, Object, Map).
   * If there are it will then try to call the method on the current node (if any). Otherwise it will attempt to find a class in
   * via {@link ObjectFactoryInstantiator#instantiateNode(ObjectFactoryInterceptor, String, Object)}.
   */
  public Object handleInvokedMethod(InterceptorContext<T, S> ctx) {
    MetaMethod method = ctx.getMetaMethod();
    T builder = ctx.getBuilder();
    S stack = ctx.getStack();
    Object[] args = ctx.getArgs();
    String name = ctx.getMethodName();
   
    // If we should attempt method calls on the builder first
    // we will do so, only catching MME's, other exceptions mean
    // the method was probably called and should be propagated.
    if (method != null && !callProxyFirst) try {
      return method.invoke(builder, args);
    } catch (MissingMethodException mme) {}

   
    Closure closure = null;
    Object nodeInstance = stack.getCurrent();
    Map attributes = null;
   
    // If this is not the first node we want to try
    // to invoke a method corresponding to the node object.
    if (nodeInstance != null) {
      try {
        return InvokerHelper.invokeMethod(nodeInstance, name, args);
      } catch (MissingMethodException e) {
        if (attemptBinding)
          try { return builder.getBinding().invokeMethod(name, args); }
          catch (MissingMethodException ee) {}
          catch (MissingPropertyException ee) {}
      }
      nodeInstance = null;
    }
   
    // If this is a three argument or less having unique arguments being one of
    // Object, Map or Closure then this can be considered a valid node invocation
    // from the builder source.
    if (args.length <= 3) {
      try {
        Map<Class, Object> ps = ParameterUtil.scan(args, Closure.class, Map.class, Object.class);
       
        closure = (Closure) ps.get(Closure.class);
        attributes = (Map) ps.get(Map.class);
        if (ps.containsKey(Object.class)) {
          if (attributes == null) attributes = new HashMap();
          attributes.put("value", ps.get(Object.class));
        }
       
        nodeInstance = this.instantiator.instantiateNode(builder, this, name, stack.getCurrent());
      } catch (InvalidParameterException e) {}
    }
   
    // If a node object was created then we want to push it onto the stack, call
    // the closure if one was passed and then pop it off the stack.
    if (nodeInstance != null) {
      return this.handleNode(builder, stack, name, nodeInstance, attributes, closure);
    }

    // If no node instance could be created then we will
    // do a last attempt (if not already attempted above)
    // to call the method on the builder
    if (method != null && callProxyFirst) return method.invoke(builder, args);
   
    // If we get here we have attempted all methods for valid invocation
    // thus we must throw a MME
    throw new MissingMethodException(name, builder.getClass(), args);
   
  }

  public Object handleNode(T builder, S stack, String name, Object node, Map<Object, Object> attributes, Closure tree) {
    try {
      // If the stack says this is the first node, we should fire the builder's
      // method for startup. It is the responsibility of the stack to be sure this
      // is not called more than once for a single root node.
      if (stack.isFirstNodeStarting()) builder.startBuilding();
     
      stack.handleOutputBuffer();
     
      if (node != null) {
        this.cache.put(name, InvokerHelper.getMetaClass(node));
        this.applicator.applyAttributes(builder, this, name, node, attributes);
        this.mapper.associate(builder, this, node, stack.getCurrent());
      }
     
      Object current = stack.getCurrent();
      builder.getEventPropagator().nodeStarted(builder, current, node);
      stack.push(node);
      if (stack.getCurrent() != null) builder.getEventPropagator().nodeChild(builder, node, stack.getCurrent());
     
      // Builders are responsible for setting up closures
      // invoked on them.
      if (tree != null) builder.invoke(tree);

      builder.getEventPropagator().nodeFinished(builder, current, node);
      return stack.pop();
    } finally {
      // If we are at the closing of the last root node
      // we want to call the builders corresponding method
      // and then cleanup the stack.
      if (stack.isFirstNodeClosing()) {
        builder.stopBuilding();
        builder.cleanupStack();
        builder.getEventPropagator().buildComplete(builder, node);
      }
    }
  }
 
}
TOP

Related Classes of net.sourceforge.javautil.groovy.builder.interceptor.objectfactory.ObjectFactoryInterceptor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.