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