You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@velocity.apache.org by nb...@apache.org on 2008/08/19 17:55:53 UTC

svn commit: r687081 - in /velocity/engine/trunk/src: java/org/apache/velocity/runtime/ java/org/apache/velocity/runtime/defaults/ java/org/apache/velocity/runtime/directive/ test/org/apache/velocity/test/

Author: nbubna
Date: Tue Aug 19 08:55:52 2008
New Revision: 687081

URL: http://svn.apache.org/viewvc?rev=687081&view=rev
Log:
VELOCITY-174 add #define directive (thanks to Andrew Tetlaw)

Added:
    velocity/engine/trunk/src/java/org/apache/velocity/runtime/directive/Define.java   (with props)
    velocity/engine/trunk/src/test/org/apache/velocity/test/DefineTestCase.java   (with props)
Modified:
    velocity/engine/trunk/src/java/org/apache/velocity/runtime/RuntimeConstants.java
    velocity/engine/trunk/src/java/org/apache/velocity/runtime/defaults/directive.properties

Modified: velocity/engine/trunk/src/java/org/apache/velocity/runtime/RuntimeConstants.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/src/java/org/apache/velocity/runtime/RuntimeConstants.java?rev=687081&r1=687080&r2=687081&view=diff
==============================================================================
--- velocity/engine/trunk/src/java/org/apache/velocity/runtime/RuntimeConstants.java (original)
+++ velocity/engine/trunk/src/java/org/apache/velocity/runtime/RuntimeConstants.java Tue Aug 19 08:55:52 2008
@@ -144,6 +144,9 @@
     /** Maximum recursion depth allowed for the #parse directive. */
     String PARSE_DIRECTIVE_MAXDEPTH = "directive.parse.max.depth";
 
+    /** Maximum recursion depth allowed for the #define directive. */
+    String DEFINE_DIRECTIVE_MAXDEPTH = "directive.define.max.depth";
+
     /**
      * class to use for local context with #evaluate()
      * @since 1.6

Modified: velocity/engine/trunk/src/java/org/apache/velocity/runtime/defaults/directive.properties
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/src/java/org/apache/velocity/runtime/defaults/directive.properties?rev=687081&r1=687080&r2=687081&view=diff
==============================================================================
--- velocity/engine/trunk/src/java/org/apache/velocity/runtime/defaults/directive.properties (original)
+++ velocity/engine/trunk/src/java/org/apache/velocity/runtime/defaults/directive.properties Tue Aug 19 08:55:52 2008
@@ -20,4 +20,5 @@
 directive.4=org.apache.velocity.runtime.directive.Macro
 directive.5=org.apache.velocity.runtime.directive.Literal
 directive.6=org.apache.velocity.runtime.directive.Evaluate
-directive.7=org.apache.velocity.runtime.directive.Break
\ No newline at end of file
+directive.7=org.apache.velocity.runtime.directive.Break
+directive.8=org.apache.velocity.runtime.directive.Define

Added: velocity/engine/trunk/src/java/org/apache/velocity/runtime/directive/Define.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/src/java/org/apache/velocity/runtime/directive/Define.java?rev=687081&view=auto
==============================================================================
--- velocity/engine/trunk/src/java/org/apache/velocity/runtime/directive/Define.java (added)
+++ velocity/engine/trunk/src/java/org/apache/velocity/runtime/directive/Define.java Tue Aug 19 08:55:52 2008
@@ -0,0 +1,201 @@
+package org.apache.velocity.runtime.directive;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.    
+ */
+
+import java.io.Writer;
+import java.io.IOException;
+import org.apache.commons.lang.text.StrBuilder;
+import org.apache.velocity.runtime.log.Log;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.RuntimeServices;
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.runtime.parser.node.Node;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.exception.VelocityException;
+
+/**
+ * Directive that puts an unrendered AST block in the context
+ * under the specified key, postponing rendering until the
+ * reference is used and rendered.
+ *
+ * @author Andrew Tetlaw
+ * @author Nathan Bubna
+ * @version $Id: Define.java 686842 2008-08-18 18:29:31Z nbubna $
+ */
+public class Define extends Directive
+{
+    private String key;
+    private Node block;
+    private Log log;
+    private int maxDepth;
+    private String definingTemplate;
+    
+    /**
+     * Return name of this directive.
+     */
+    public String getName()
+    {
+        return "define";
+    }
+
+    /**
+     * Return type of this directive.
+     */
+    public int getType()
+    {
+        return BLOCK;
+    }
+
+    /**
+     *  simple init - get the key
+     */
+    public void init(RuntimeServices rs, InternalContextAdapter context, Node node)
+        throws TemplateInitException
+    {
+        super.init(rs, context, node);
+
+        log = rs.getLog();
+
+        /*
+         * default max depth of two is used because intentional recursion is
+         * unlikely and discouraged, so make unintentional ones end fast
+         */
+        maxDepth = rs.getInt(RuntimeConstants.DEFINE_DIRECTIVE_MAXDEPTH, 2);
+
+        /*
+         * first token is the name of the block. We don't even check the format,
+         * just assume it looks like this: $block_name. Should we check if it has
+         * a '$' or not?
+         */
+        key = node.jjtGetChild(0).getFirstToken().image.substring(1);
+
+        /**
+         * No checking is done. We just grab the second child node and assume
+         * that it's the block!
+         */
+        block = node.jjtGetChild(1);
+
+        /**
+         * keep tabs on the template this came from
+         */
+        definingTemplate = context.getCurrentTemplateName();
+    }
+
+    /**
+     * directive.render() simply makes an instance of the Block inner class
+     * and places it into the context as indicated.
+     */
+    public boolean render(InternalContextAdapter context, Writer writer, Node node)
+    {
+        /* put a Block instance into the context,
+         * using the user-defined key, for later inline rendering.
+         */
+        context.put(key, new Block(context, writer, this));
+        return true;
+    }
+
+    /**
+     * Creates a string identifying the source and location of the block
+     * definition, and the current template being rendered if that is
+     * different.
+     */
+    protected String id(InternalContextAdapter context)
+    {
+        StrBuilder str = new StrBuilder(100)
+            .append("block $").append(key)
+            .append(" (defined in ").append(definingTemplate)
+            .append(" [line ").append(getLine())
+            .append(", column ").append(getColumn()).append("])");
+
+        if (!context.getCurrentTemplateName().equals(definingTemplate))
+        {
+            str.append(" used in ").append(context.getCurrentTemplateName());
+        }
+
+        return str.toString();
+    }
+    
+    /**
+     * actual class placed in the context, holds the context and writer
+     * being used for the render, as well as the parent (which already holds
+     * everything else we need).
+     */
+    public static class Block
+    {
+        private InternalContextAdapter context;
+        private Writer writer;
+        private Define parent;
+        private int depth;
+        
+        public Block(InternalContextAdapter context, Writer writer, Define parent)
+        {
+            this.context = context;
+            this.writer = writer;
+            this.parent = parent;
+        }
+        
+        /**
+         * The hack: toString() will be called when Velocity encounters
+         * the reference in a template. The node is rendered at this point
+         * using the stored context and writer. So far, this appears safe;
+         * We have no idea what the writer is doing at this point we just assume
+         * it's ok to make it write what we want right now. :)  the alternate
+         * would be to render into a StringWriter and return the result
+         */
+        public String toString()
+        {
+            try
+            {
+                depth++;
+                if (depth > parent.maxDepth)
+                {
+                    /* this is only a debug message, as recursion can
+                     * happen in quasi-innocent situations and is relatively
+                     * harmless due to how we handle it here.
+                     * this is more to help anyone nuts enough to intentionally
+                     * use recursive block definitions and having problems
+                     * pulling it off properly.
+                     */
+                    parent.log.debug("Max recursion depth reached for "+parent.id(context));
+                    depth--;
+                    return null;
+                }
+                else
+                {
+                    parent.block.render(context, writer);
+                    depth--;
+                    return "";
+                }
+            }
+            catch (IOException e)
+            {
+                String msg = "Failed to render "+parent.id(context)+" to writer";
+                parent.log.error(msg, e);
+                throw new RuntimeException(msg, e);
+            }
+            catch (VelocityException ve)
+            {
+                String msg = "Failed to render "+parent.id(context)+" due to "+ve;
+                parent.log.error(msg, ve);
+                throw ve;
+            }
+        }
+    }
+}

Propchange: velocity/engine/trunk/src/java/org/apache/velocity/runtime/directive/Define.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: velocity/engine/trunk/src/java/org/apache/velocity/runtime/directive/Define.java
------------------------------------------------------------------------------
    svn:executable = *

Propchange: velocity/engine/trunk/src/java/org/apache/velocity/runtime/directive/Define.java
------------------------------------------------------------------------------
    svn:keywords = Revision

Propchange: velocity/engine/trunk/src/java/org/apache/velocity/runtime/directive/Define.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: velocity/engine/trunk/src/test/org/apache/velocity/test/DefineTestCase.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/src/test/org/apache/velocity/test/DefineTestCase.java?rev=687081&view=auto
==============================================================================
--- velocity/engine/trunk/src/test/org/apache/velocity/test/DefineTestCase.java (added)
+++ velocity/engine/trunk/src/test/org/apache/velocity/test/DefineTestCase.java Tue Aug 19 08:55:52 2008
@@ -0,0 +1,105 @@
+package org.apache.velocity.test;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.    
+ */
+
+import org.apache.velocity.VelocityContext;
+
+/**
+ * This class tests the #define directive
+ */
+public class DefineTestCase extends BaseEvalTestCase
+{
+    public DefineTestCase(String name)
+    {
+       super(name);
+    }
+
+    protected String defAndEval(String block)
+    {
+        return defAndEval("def", block);
+    }
+
+    protected String defAndEval(String key, String block)
+    {
+        return evaluate("#define( $"+key+" )"+block+"#end$"+key);
+    }
+
+    public void testSimple()
+    {
+        assertEquals("abc", defAndEval("abc"));
+        assertEvalEquals("abc abc abc", "#define( $a )abc#end$a $a $a");
+    }
+
+    public void testNotSimple()
+    {
+        assertEquals("true", defAndEval("#if( $def )true#end"));
+        assertEquals("123", defAndEval("#foreach( $i in [1..3] )$i#end"));
+        assertEquals("hello world", defAndEval("#macro( test )hello world#end#test()"));
+    }
+
+    public void testOverridingDefinitionInternally()
+    {
+        assertEvalEquals("truefalse", "#define( $or )true#set( $or = false )#end$or$or");
+    }
+
+    public void testLateBinding()
+    {
+        context.put("baz", "foo");
+        assertEvalEquals("foobar", "#define( $lb )$baz#end${lb}#set( $baz = 'bar' )${lb}");
+    }
+
+    public void testRerendering()
+    {
+        context.put("inc", new Inc());
+        assertEvalEquals("1 2 3", "#define( $i )$inc#end$i $i $i");
+    }
+
+    public void testRecursionLimit()
+    {
+        try
+        {
+            assertEvalEquals("$r", "#define( $r )$r#end$r");
+        }
+        catch (Exception t)
+        {
+            fail("Recursion should not have thrown an exception");
+        }
+        catch (Error e)
+        {
+            fail("Infinite recursion should not be possible.");
+        }
+    }
+
+    public void testThingsOfQuestionableMorality()
+    {
+        // redefining $foo within $foo
+        assertEquals("foobar", defAndEval("foo", "foo#define( $foo )bar#end$foo"));
+    }
+
+
+    public static class Inc
+    {
+        int foo = 1;
+        public String toString()
+        {
+            return String.valueOf(foo++);
+        }
+    }
+}

Propchange: velocity/engine/trunk/src/test/org/apache/velocity/test/DefineTestCase.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: velocity/engine/trunk/src/test/org/apache/velocity/test/DefineTestCase.java
------------------------------------------------------------------------------
    svn:executable = *

Propchange: velocity/engine/trunk/src/test/org/apache/velocity/test/DefineTestCase.java
------------------------------------------------------------------------------
    svn:keywords = Revision

Propchange: velocity/engine/trunk/src/test/org/apache/velocity/test/DefineTestCase.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain