You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@velocity.apache.org by jv...@locus.apache.org on 2000/08/26 08:28:48 UTC

cvs commit: jakarta-velocity/src/java/org/apache/velocity/visitor CacheVisitor.java ProcessVisitor.java

jvanzyl     00/08/25 23:28:48

  Modified:    src/java/org/apache/velocity/visitor ProcessVisitor.java
  Added:       src/java/org/apache/velocity/visitor CacheVisitor.java
  Log:
  - starting building a visitor that will produce a cachable template.
  
  Revision  Changes    Path
  1.2       +0 -1      jakarta-velocity/src/java/org/apache/velocity/visitor/ProcessVisitor.java
  
  Index: ProcessVisitor.java
  ===================================================================
  RCS file: /home/cvs/jakarta-velocity/src/java/org/apache/velocity/visitor/ProcessVisitor.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- ProcessVisitor.java	2000/08/24 21:42:49	1.1
  +++ ProcessVisitor.java	2000/08/26 06:28:47	1.2
  @@ -62,7 +62,6 @@
   import java.util.ArrayList;
   
   import org.apache.velocity.Context;
  -import org.apache.velocity.Utils;
   
   import org.apache.velocity.parser.*;
   
  
  
  
  1.1                  jakarta-velocity/src/java/org/apache/velocity/visitor/CacheVisitor.java
  
  Index: CacheVisitor.java
  ===================================================================
  package org.apache.velocity.visitor;
  
  /*
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2000 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   */
  
  import java.util.Collection;
  import java.util.Iterator;
  import java.util.Vector;
  import java.util.Enumeration;
  import java.util.Stack;
  import java.util.ArrayList;
  
  import org.apache.velocity.Context;
  
  import org.apache.velocity.parser.*;
  
  /**
   * This class is used to build a cachable template.
   */
  public class CacheVisitor implements ParserVisitor
  {
      protected StringBuffer document;
      protected int line;
      protected int lastLine;
      protected int column;
      protected Context context;
      protected Stack contexts;
      protected boolean processingStatement;
      protected int foreachBlockLength;
      protected int ifBlockLength;
  
      /** 
        * This is to make adjustments to current line number
        * of the resultant document. This is only altered
        * by a #foreach directive. But might later be
        * used by other looping constructs such as
        * #while().
        */
      protected int lineAdjustment;
      
      public CacheVisitor()
      {
          document = new StringBuffer();
          
          line = 1;
          lastLine = 0;
          column = 1;
          
          lineAdjustment = 0;
          foreachBlockLength = 0;
          ifBlockLength = 0;
          processingStatement = false;
      }
      
      /**
       * Set the visitor's initial context.
       */
      public void setContext(Context context)
      {
          this.context = context;
      }        
      
      /**
       * The the completed template text.
       */
      public String getDocument()
      {
          return document.toString();
      }        
  
      /** */
      private void print(String text, int x, int y)
      {
          print(text, x, y, 0);
      }
  
      /** */
      private void print(String text, int x, int y, int columnAdjustment)
      {
          for (int i = line; line < (y + lineAdjustment); i++)
          {
              document.append("\n");
              line++;
          }
          
          if (line != lastLine)
              column = 1;
              
          for (int j = column; column < x; j++)
          {
              document.append(" ");
              column++;
          }
          
          document.append(text);
          
          column += text.length() + columnAdjustment;
          lastLine = line;
      }
  
      /** */
      private String location(Token t)
      {
          return " (" + t.beginColumn + "," + t.beginLine + ")";
      }
  
      /** */
      public Object visit(SimpleNode node, Object data)
      {
          data = node.childrenAccept(this, data);
          return data;
      }
  
      /** */
      // use getPropertyValue()
      public Object visit(ASTProperty node, Object data)
      {
          Token t = node.getFirstToken();
          
          String contextKey = t.image.substring(1, t.image.indexOf("."));
          String property = "get" + t.image.substring(t.image.indexOf(".") + 1);
          
          if (context.containsKey(contextKey))
          {
              Object o = context.get(contextKey);
              String content = (String) Utils.invoke(o, property);
              print(content , t.beginColumn, t.beginLine, t.image.length() - content.length());
          }
          else
              print(t.toString(), t.beginColumn, t.beginLine);
          
          data = node.childrenAccept(this, data);
          return data;
      }
  
      public Object visit(ASTParameter node, Object data)
      {
          data = node.childrenAccept(this, data);
          return data;
      }
  
      public Object visit(ASTParameters node, Object data)
      {
          data = node.childrenAccept(this, data);
          return data;
      }
  
      /** */
      // use getMethodValue()
      public Object visit(ASTMethod node, Object data)
      {
          Token t = node.getFirstToken();
          String contextKey = t.image.substring(1, t.image.indexOf("."));
          String property = t.image.substring(t.image.indexOf(".") + 1, t.image.indexOf("("));
          
          if (context.containsKey(contextKey))
          {
              Object o = context.get(contextKey);
              String content = (String) Utils.invoke(o, property);
              
              if (content == null)
                  content = t.image;
              
              print(content , t.beginColumn, t.beginLine, t.image.length() - content.length());
          }
          else
              print(t.toString(), t.beginColumn, t.beginLine);
  
          data = node.childrenAccept(this, data);
          return data;
      }
  
      /** */
      public Object visit(ASTprocess node, Object data)
      {
          data = node.childrenAccept(this, data);
          return data;
      }
      
      /** */
      // use getVariableValue()
      public Object visit(ASTVariable node, Object data)
      {
          Token t = node.getFirstToken();
  
          // This should be replaced with getVariableValue!
  
          String variable = t.image.substring(1);
          
          if (context.containsKey(variable))
          {
              String content = (String) context.get(variable);
              print(content , t.beginColumn, t.beginLine, t.image.length() - content.length());
          }            
          else
              print(t.toString(), t.beginColumn, t.beginLine);
          
          data = node.childrenAccept(this, data);
          return data;
      }
      
      /** */
      public Object visit(ASTExpression node, Object data)
      {
          data = node.childrenAccept(this, data);
          return data;
      }
      
      /** */
      public Object visit(ASTBlock node, Object data)
      {
          Token t1 = node.getFirstToken();
          Token t2 = node.getLastToken();
          Token p = node.jjtGetParent().getFirstToken();
          
          switch (p.kind)
          {
              case ParserConstants.FOREACH_DIRECTIVE:
                  foreachBlockLength = t2.beginLine - t1.beginLine - 1;
                  data = node.childrenAccept(this, data);
                  break;
              
              case ParserConstants.ELSE_DIRECTIVE:
              case ParserConstants.IF_DIRECTIVE:
                  // Get rid of the space the {} take up.
                  lineAdjustment -= 2;
                  data = node.childrenAccept(this, data);
                  break;
              
              // This will print braces when not in the context
              // of a directive.
              default:
                  print(t1.image, t1.beginColumn, t1.beginLine);
                  data = node.childrenAccept(this, data);
                  print(t2.image, t2.beginColumn, t2.beginLine);
          }
          
          return data;
      }
      
      /** */
      
      // For variables should they just have to be present
      // in the context, or should they evaluate to a Boolean
      // object. 
      
      public Object visit(ASTIfStatement node, Object data)
      {
          // token 3
          boolean expressionState = false;
          Token expression = node.getFirstToken().next.next;
                  
          switch(expression.kind)
          {
              case ParserConstants.VARIABLE:
                  // We might want to change this to evaluate
                  // the variable to a boolean, not just test for
                  // existence in the context. Maybe I could test
                  // for both.
                  if (getVariableValue(expression.image) != null)
                      expressionState = true;
                  break;
                  
              case ParserConstants.PROPERTY:
                  if (((Boolean) getPropertyValue(expression.image)).booleanValue() == true)
                      expressionState = true;
                  break;
                  
              case ParserConstants.METHOD:
                  if (((Boolean) getMethodValue(expression.image)).booleanValue() == true)
                      expressionState = true;
                  break;
                  
              case ParserConstants.TRUE:
                  expressionState = true;
                  break;
              
              case ParserConstants.FALSE:
                              
          }
  
          // Remove the space that the actual directive
          // takes up so that it takes up no space in the
          // resultant document.
          lineAdjustment--;
  
          // Only process the child nodes if the expression
          // evaluates to true. But we also need to look for
          // the presence of an else block and process that
          // if the expression doesn't evaluate to true.
          if (expressionState)
          {
              processingStatement = true;
              data = node.childrenAccept(this, data);
              processingStatement = false;
          }
          else if (node.jjtGetNumChildren() == 2)
          {
              // We are not going to process the #if block
              // but we need to know how much space it
              // occupied so that we can remove it. We'll
              // remove the space the actual #if directive
              // took up here as well.
              lineAdjustment -= (
              node.jjtGetChild(1).getLastToken().beginLine -
              node.jjtGetChild(1).getFirstToken().beginLine - 2);
              
              // Remove the space that the actual directive
              // takes up so that it takes up no space in the
              // resultant document and remove the space
              // the failed #if block above took up.
              //lineAdjustment--;
              
              // Then there is another node which is an
              // ASTBlock and it is the optional
              // #else block to the #if directive above.
              // So we want to bypass the first block
              // which is only executed #if (true) and
              // only process the #else block. We do that
              // by grabbing the second child node and process
              // that node by passing it to childrenAccept(). 
              // The index is zero-based for jjtGetChild(index).
              processingStatement = true;
              data = ((SimpleNode) node.jjtGetChild(1)).childrenAccept(this, data);
              processingStatement = false;
          }
          
          return data;
      }
    
    /**
     * In a statement like:
     *
     *     1      2    3    4
     * #foreach $knob in $genePool
     * {
     * }
     *
     * we want the element ($knob) and
     * we want the list ($genePool) from
     * which the element is taken.
     *
     * So we want token 2 and 4.
     */
      public Object visit(ASTForeachStatement node, Object data)
      {
          // tokens 2,4
          Object listObject = null;
          Token element = node.getFirstToken().next;
          Token list = element.next.next;
          
          // Now we have to iterate over all the child nodes
          // for each in the list, we have to change the
          // context each time the element changes.
          
          // Now I have to adjust the position of the
          // tokens.
  
          switch(list.kind)
          {
              case ParserConstants.VARIABLE:
                  listObject = getVariableValue(list.image);
                  break;
                  
              case ParserConstants.PROPERTY:
                  listObject = getPropertyValue(list.image);
                  break;
                  
              case ParserConstants.METHOD:
                  listObject = getMethodValue(list.image);
                  break;
          }
  
          String elementKey = element.image.substring(1);
  
          // Remove the space that the actual directive
          // takes up so that it takes up no space in the
          // resultant document and the {.
          lineAdjustment -= 2;
  
          // if there is an object in the context with
          // the same name as the $element save it so
          // we can restore it after the #foreach.
          
          // The Collection interface provides iterator()
          // I don't think elements() is provided by an interface
          // it just looks like it belongs to Vector. Crappy
          // 1.1 vs 1.2 junk.
  
          Object tmp = null;
          if (context.containsKey(elementKey))
              tmp = context.get(elementKey);
  
          if (listObject instanceof Object[])
          {
              int length = ((Object[]) listObject).length;
              
              for (int i = 0; i < length; i++)
              {
                  context.put(elementKey,((Object[])listObject)[i]);
                  processingStatement = true;
                  data = node.childrenAccept(this, data);
                  
                  if (i != length - 1)
                      lineAdjustment += foreachBlockLength;
                  
                  processingStatement = false;
              }
              context.remove(elementKey);
          }
          else if (Utils.implementsMethod(listObject, "iterator"))
          {
              // Maybe this could be optimized with get(index) ?
              // Check the interface. size() and get(index) might
              // be faster then using an Iterator.
              Iterator i = ((Collection) listObject).iterator();
              
              while (i.hasNext())
              {
                  context.put(elementKey,i.next());
                  processingStatement = true;
                  data = node.childrenAccept(this, data);
                  lineAdjustment += foreachBlockLength;
                  processingStatement = false;
              }
              context.remove(elementKey);
          }
          else if (Utils.implementsMethod(listObject, "elements"))
          {
              Enumeration e = ((Vector) listObject).elements();
              
              while (e.hasMoreElements())
              {
                  context.put(elementKey,e.nextElement());
                  processingStatement = true;
                  data = node.childrenAccept(this, data);
                  lineAdjustment += foreachBlockLength;
                  processingStatement = false;
              }
              context.remove(elementKey);
          }
  
          if (tmp != null)
              context.put(elementKey, tmp);
  
          // For the final }.
          lineAdjustment--;
          
          return data;
      }
      
      /** */
      public Object visit(ASTSetStatement node, Object data)
      {
          // tokens 2,4
          Object value = null;;
          Token leftHand = node.getFirstToken().next;
          Token rightHand = leftHand.next.next;
          
          switch(rightHand.kind)
          {
              case ParserConstants.STRING_LITERAL:
                  value = getStringLiteralValue(rightHand.image);
                  break;
                  
              case ParserConstants.VARIABLE:
                  value = getVariableValue(rightHand.image);
                  break;
                  
              case ParserConstants.PROPERTY:
                  value = getPropertyValue(rightHand.image);
                  break;
                  
              case ParserConstants.METHOD:
                  value = getMethodValue(rightHand.image);
                  break;
              
              case ParserConstants.TRUE:
                  value = new Boolean(true);
                  break;
                  
              case ParserConstants.FALSE:
                  value = new Boolean(false);
                  break;
          }
  
          switch(leftHand.kind)
          {
              // We are just setting a variable in the
              // context.
              case ParserConstants.VARIABLE:
                  context.put(leftHand.image.substring(1), value);
                  break;
                  
              // We are setting the value of a property
              // via  a setter.
              case ParserConstants.PROPERTY:
                  setPropertyValue(leftHand.image, value);
                  break;
          }                
          
          // Remove the space that the actual directive
          // takes up so that it takes up no space in the
          // resultant document.
          lineAdjustment--;
          
          data = node.childrenAccept(this, data);
          return data;
      }
      
      /** */
      // I don't think this is going to be required given that
      // any text can pass through unscathed.
      public Object visit(ASTUseStatement node, Object data)
      {
          data = node.childrenAccept(this, data);
          return data;
      }
      
      /** */
      public Object visit(ASTParamStatement node, Object data)
      {
          // tokens 2,4
          Token name = node.getFirstToken().next;
          Token value = name.next.next;
          context.putParam(name.image.substring(1), value.image.substring(1, value.image.length() - 1));
          
          // Remove the space that the actual directive
          // takes up so that it takes up no space in the
          // resultant document.
          lineAdjustment--;
          
          data = node.childrenAccept(this, data);
          return data;
      }
      
      public Object visit(ASTText node, Object data)
      {
          Token text = node.getFirstToken();
          print(text.toString(), text.beginColumn, text.beginLine);
          data = node.childrenAccept(this, data);
          return data;
      }
  
      public Object getStringLiteralValue(String s)
      {
          return s.substring(1, s.length() - 1);
      }
      
      public Object getVariableValue(String s)
      {
          String variable = s.substring(1);
          
          if (context.containsKey(variable))
          {
              return context.get(variable);
          }            
          else
          {
              return null;
          }            
      }
      
      public Object getPropertyValue(String s)
      {
          String contextKey = s.substring(1, s.indexOf("."));
          String property = "get" + s.substring(s.indexOf(".") + 1);
  
          if (context.containsKey(contextKey))
          {
              Object o = context.get(contextKey);
              return Utils.invoke(o, property);
          }
          else
          {
              return null;
          }            
      }
  
      // Might have to do some casting here to get
      // things right.
      public void setPropertyValue(String s, Object value)
      {
          Object[] args = new Object[1];
          String contextKey = s.substring(1, s.indexOf("."));
          String property = "set" + s.substring(s.indexOf(".") + 1);
          
          args[0] = value;
  
          if (context.containsKey(contextKey))
          {
              Object o = context.get(contextKey);
              Utils.invoke(o, property, args);
          }
          
          // Add some logging if the property doesn't
          // exist.
      }
  
      public Object getMethodValue(String s)
      {
          String contextKey = s.substring(1, s.indexOf("."));
          String property = s.substring(s.indexOf(".") + 1, s.indexOf("("));
          
          if (context.containsKey(contextKey))
          {
              Object o = context.get(contextKey);
              Object content = Utils.invoke(o, property);
              
              if (content == null)
                  content = "";
              
              return content;            
          }
          else
          {
              return "";
          }            
      }
  }