package hijac.tools.modelgen.circus.freemarker;

import com.sun.source.tree.Tree;
import com.sun.source.tree.TreeVisitor;

import freemarker.template.AdapterTemplateModel;
import freemarker.template.TemplateMethodModelEx;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.utility.DeepUnwrap;

import hijac.tools.collections.Copyable;

import java.util.List;

/**
 * A generic class that permits the creation of FreeMarker method models
 * from AST visitors of the JDK Tree API.
 * The visitor has to return a string but the class type for the context
 * is generic and can be freely chosen.
 *
 * @author Frank Zeyda
 * @version $Revision: 212 $
 */
public class VisitorMethodModel<P extends Copyable<P>> implements
      TemplateMethodModelEx {
   protected final String method_name;
   protected final TreeVisitor<String, P> visitor;
   protected final P default_context;

   public VisitorMethodModel(String name, TreeVisitor<String, P> visitor,
         P default_context) {
      this.visitor = visitor;
      this.method_name = name;
      this.default_context = default_context;
   }

   public VisitorMethodModel(String name, TreeVisitor<String, P> visitor) {
      this(name, visitor, null);
   }

   @SuppressWarnings("unchecked")
   public Object exec(List arguments) throws TemplateModelException {
      if (!(arguments.size() == 1 || arguments.size() == 2)) {
         throw new TemplateModelException(
            "Wrong number of arguments when calling " + method_name + ".");
      }
      if (!(arguments.get(0) instanceof TreeTemplateModel)) {
         throw new TemplateModelException(
            "First argument of " + method_name +
               " is not a TreeTemplateModel.");
      }
      Tree node = (Tree)
         DeepUnwrap.unwrap((TreeTemplateModel) arguments.get(0));
      P context;
      if (arguments.size() == 2) {
         /* We cannot check in the program whether this cast is safe. */
         Object unwrapped_context =
            DeepUnwrap.unwrap((TemplateModel) arguments.get(1));
         try {
            /* We cannot use instanceof to check this cast is safe. */
            context = (P) unwrapped_context;
         }
         catch (ClassCastException e) {
            throw new TemplateModelException(
               "Second argument of " + method_name +
                  " is not of type " + default_context.getClass() + ".");
         }
      }
      else {
         if (default_context != null) {
            /* It is crucial that we copy the context below as the
             * the visitor assumes the prerogative to modify it. */
            context = default_context.copy();
         }
         else {
            throw new TemplateModelException(
               "No context argument in " + method_name +
                  " and no default context available.");
         }
      }
      return node.accept(visitor, context);
   }
}
