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 2010/12/29 07:11:07 UTC

svn commit: r1053541 - in /velocity/engine/trunk/velocity-engine-core/src: main/java/org/apache/velocity/runtime/directive/ main/java/org/apache/velocity/runtime/parser/node/ main/java/org/apache/velocity/util/ test/java/org/apache/velocity/test/

Author: nbubna
Date: Wed Dec 29 06:11:06 2010
New Revision: 1053541

URL: http://svn.apache.org/viewvc?rev=1053541&view=rev
Log:
Add better duck-typing support, make #if() false for 'empty' objects (VELOCITY-692), and refactor comparison nodes to remove gross amounts of duplicate code

Added:
    velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTComparisonNode.java   (with props)
    velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/DuckType.java   (with props)
    velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/GetAsTestCase.java   (with props)
    velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/IfEmptyTestCase.java   (with props)
Modified:
    velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Block.java
    velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTAddNode.java
    velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTEQNode.java
    velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTGENode.java
    velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTGTNode.java
    velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLENode.java
    velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLTNode.java
    velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMathNode.java
    velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTNENode.java
    velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTReference.java
    velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/IfNullTestCase.java

Modified: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Block.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Block.java?rev=1053541&r1=1053540&r2=1053541&view=diff
==============================================================================
--- velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Block.java (original)
+++ velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Block.java Wed Dec 29 06:11:06 2010
@@ -163,6 +163,14 @@ public abstract class Block extends Dire
             }
         }
 
+        /**
+         * Makes #if( $blockRef ) true without rendering, so long as we aren't beyond max depth.
+         */
+        public boolean getAsBoolean()
+        {
+            return depth <= parent.maxDepth;
+        }
+
         public String toString()
         {
             Writer writer = new StringWriter();

Modified: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTAddNode.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTAddNode.java?rev=1053541&r1=1053540&r2=1053541&view=diff
==============================================================================
--- velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTAddNode.java (original)
+++ velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTAddNode.java Wed Dec 29 06:11:06 2010
@@ -21,6 +21,7 @@ package org.apache.velocity.runtime.pars
 
 import org.apache.velocity.context.InternalContextAdapter;
 import org.apache.velocity.runtime.parser.Parser;
+import org.apache.velocity.util.DuckType;
 
 /**
  * Handles number addition of nodes.<br><br>
@@ -53,23 +54,23 @@ public class ASTAddNode extends ASTMathN
         super(p, id);
     }
 
-    //@Override
+    @Override
     protected Object handleSpecial(Object left, Object right, InternalContextAdapter context)
     {
-        /*
-         * shall we try for strings?
-         */
-        if (left instanceof String || right instanceof String)
+        // check for strings, but don't coerce
+        String lstr = DuckType.asString(left, false);
+        String rstr = DuckType.asString(right, false);
+        if (lstr != null || rstr != null)
         {
-            if (left == null)
+            if (lstr == null)
             {
-                left = jjtGetChild(0).literal();
+                lstr = left != null ? left.toString() : jjtGetChild(0).literal();
             }
-            else if (right == null)
+            else if (rstr == null)
             {
-                right = jjtGetChild(1).literal();
+                rstr = right != null ? right.toString() : jjtGetChild(1).literal();
             }
-            return left.toString().concat(right.toString());
+            return lstr.concat(rstr);
         }
         return null;
     }

Added: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTComparisonNode.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTComparisonNode.java?rev=1053541&view=auto
==============================================================================
--- velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTComparisonNode.java (added)
+++ velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTComparisonNode.java Wed Dec 29 06:11:06 2010
@@ -0,0 +1,154 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * 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.context.InternalContextAdapter;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.log.Log;
+import org.apache.velocity.runtime.parser.Parser;
+import org.apache.velocity.util.DuckType;
+
+/**
+ * Numeric comparison support<br><br>
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a>
+ * @author Nathan Bubna
+ */
+public abstract class ASTComparisonNode extends SimpleNode
+{
+    /**
+     * @param id
+     */
+    public ASTComparisonNode(int id)
+    {
+        super(id);
+    }
+
+    /**
+     * @param p
+     * @param id
+     */
+    public ASTComparisonNode(Parser p, int id)
+    {
+        super(p, id);
+    }
+
+
+    /**
+     * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+     */
+    public Object jjtAccept(ParserVisitor visitor, Object data)
+    {
+        return visitor.visit(this, data);
+    }
+
+    /**
+     * @see org.apache.velocity.runtime.parser.node.SimpleNode#evaluate(org.apache.velocity.context.InternalContextAdapter)
+     */
+    public boolean evaluate(InternalContextAdapter context) throws MethodInvocationException
+    {
+        Object left = jjtGetChild(0).value(context);
+        Object right = jjtGetChild(1).value(context);
+
+        if (left == null || right == null)
+        {
+            return compareNull(left, right);
+        }
+        Boolean result = compareNumbers(left, right);
+        if (result == null)
+        {
+            result = compareNonNumber(left, right);
+        }
+        return result;
+    }
+
+    /**
+     * Always false by default, != and == subclasses must override this.
+     */
+    public boolean compareNull(Object left, Object right)
+    {
+        // if either side is null, log and bail
+        String msg = (left == null ? "Left" : "Right")
+                       + " side ("
+                       + jjtGetChild( (left == null? 0 : 1) ).literal()
+                       + ") of comparison operation has null value at "
+                       + Log.formatFileString(this);
+        if (rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false))
+        {
+            throw new VelocityException(msg);
+        }
+        log.error(msg);
+        return false;
+    }
+
+    public Boolean compareNumbers(Object left, Object right)
+    {
+        try
+        {
+            left = DuckType.asNumber(left);
+        }
+        catch (NumberFormatException nfe) {}
+        try
+        {
+            right = DuckType.asNumber(right);
+        }
+        catch (NumberFormatException nfe) {}
+
+        // only compare Numbers
+        if (left instanceof Number && right instanceof Number)
+        {
+            return numberTest(MathUtils.compare((Number)left, (Number)right));
+        }
+        return null;
+    }
+
+    public abstract boolean numberTest(int compareResult);
+
+    public boolean compareNonNumber(Object left, Object right)
+    {
+        // by default, log and bail
+        String msg = (right instanceof Number ? "Left" : "Right")
+                       + " side of comparison operation is not a number at "
+                       + Log.formatFileString(this);
+        if (rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false))
+        {
+            throw new VelocityException(msg);
+        }
+        log.error(msg);
+        return false;
+    }
+
+    private String getLiteral(boolean left)
+    {
+        return jjtGetChild(left ? 0 : 1).literal();
+    }
+
+    /**
+     * @see org.apache.velocity.runtime.parser.node.SimpleNode#value(org.apache.velocity.context.InternalContextAdapter)
+     */
+    public Object value(InternalContextAdapter context) throws MethodInvocationException
+    {
+        return Boolean.valueOf(evaluate(context));
+    }
+
+}

Propchange: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTComparisonNode.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTComparisonNode.java
------------------------------------------------------------------------------
    svn:keywords = Revision

Propchange: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTComparisonNode.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTEQNode.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTEQNode.java?rev=1053541&r1=1053540&r2=1053541&view=diff
==============================================================================
--- velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTEQNode.java (original)
+++ velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTEQNode.java Wed Dec 29 06:11:06 2010
@@ -19,130 +19,65 @@ package org.apache.velocity.runtime.pars
  * under the License.    
  */
 
-import org.apache.velocity.context.InternalContextAdapter;
-import org.apache.velocity.exception.MethodInvocationException;
 import org.apache.velocity.runtime.parser.Parser;
-import org.apache.velocity.util.TemplateNumber;
+import org.apache.velocity.util.DuckType;
 
 /**
- *  Handles <code>arg1  == arg2</code>
+ *  Handles <code>arg1 == arg2</code>
  *
  *  This operator requires that the LHS and RHS are both of the
- *  same Class OR both are subclasses of java.lang.Number
+ *  same Class, both numbers or both coerce-able to strings.
  *
  *  @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
  *  @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a>
- *  @version $Id$
+ *  @author Nathan Bubna
  */
-public class ASTEQNode extends SimpleNode
+public class ASTEQNode extends ASTComparisonNode
 {
-    /**
-     * @param id
-     */
     public ASTEQNode(int id)
     {
         super(id);
     }
 
-    /**
-     * @param p
-     * @param id
-     */
     public ASTEQNode(Parser p, int id)
     {
         super(p, id);
     }
 
-    /**
-     * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
-     */
-    public Object jjtAccept(ParserVisitor visitor, Object data)
+    @Override
+    public boolean compareNull(Object left, Object right)
     {
-        return visitor.visit(this, data);
+        // at least one is null, see if other is null or acts as a null
+        return left == right || DuckType.asNull(left == null ? right : left);
     }
 
-    /**
-     *   Calculates the value of the logical expression
-     *
-     *     arg1 == arg2
-     *
-     *   All class types are supported.   Uses equals() to
-     *   determine equivalence.  This should work as we represent
-     *   with the types we already support, and anything else that
-     *   implements equals() to mean more than identical references.
-     *
-     *
-     *  @param context  internal context used to evaluate the LHS and RHS
-     *  @return true if equivalent, false if not equivalent,
-     *          false if not compatible arguments, or false
-     *          if either LHS or RHS is null
-     * @throws MethodInvocationException
-     */
-    public boolean evaluate(InternalContextAdapter context)
-        throws MethodInvocationException
+    public boolean numberTest(int compareResult)
     {
-        Object left = jjtGetChild(0).value(context);
-        Object right = jjtGetChild(1).value(context);
-
-        /*
-         *  convert to Number if applicable
-         */
-        if (left instanceof TemplateNumber)
-        {
-           left = ( (TemplateNumber) left).getAsNumber();
-        }
-        if (right instanceof TemplateNumber)
-        {
-           right = ( (TemplateNumber) right).getAsNumber();
-        }
-
-       /*
-        * If comparing Numbers we do not care about the Class.
-        */
-       if (left instanceof Number && right instanceof Number)
-       {
-           return MathUtils.compare( (Number)left, (Number)right) == 0;
-       }
+        return compareResult == 0;
+    }
 
+    @Override
+    public boolean compareNonNumber(Object left, Object right)
+    {
         /**
          * if both are not null, then assume that if one class
          * is a subclass of the other that we should use the equals operator
          */
-        if (left != null && right != null &&
-            (left.getClass().isAssignableFrom(right.getClass()) ||
-             right.getClass().isAssignableFrom(left.getClass())))
+        if (left.getClass().isAssignableFrom(right.getClass()) ||
+            right.getClass().isAssignableFrom(left.getClass()))
         {
-            return left.equals( right );
+            return left.equals(right);
         }
 
-        /*
-         * Ok, time to compare string values
-         */
-        left = (left == null) ? null : left.toString();
-        right = (right == null) ? null: right.toString();
-
-        if (left == null && right == null)
+        // coerce to string, remember getAsString() methods may return null
+        left = DuckType.asString(left);
+        right = DuckType.asString(right);
+        if (left == right)
         {
-            if (log.isDebugEnabled())
-            {
-                log.debug("Both right (" + getLiteral(false) + " and left "
-                          + getLiteral(true) + " sides of '==' operation returned null."
-                          + "If references, they may not be in the context."
-                          + getLocation(context));
-            }
             return true;
         }
         else if (left == null || right == null)
         {
-            if (log.isDebugEnabled())
-            {
-                log.debug((left == null ? "Left" : "Right")
-                        + " side (" + getLiteral(left == null)
-                        + ") of '==' operation has null value. If it is a "
-                        + "reference, it may not be in the context or its "
-                        + "toString() returned null. " + getLocation(context));
-
-            }
             return false;
         }
         else
@@ -151,17 +86,4 @@ public class ASTEQNode extends SimpleNod
         }
     }
 
-    private String getLiteral(boolean left)
-    {
-        return jjtGetChild(left ? 0 : 1).literal();
-    }
-
-    /**
-     * @see org.apache.velocity.runtime.parser.node.SimpleNode#value(org.apache.velocity.context.InternalContextAdapter)
-     */
-    public Object value(InternalContextAdapter context)
-        throws MethodInvocationException
-    {
-        return evaluate(context) ? Boolean.TRUE : Boolean.FALSE;
-    }
 }

Modified: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTGENode.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTGENode.java?rev=1053541&r1=1053540&r2=1053541&view=diff
==============================================================================
--- velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTGENode.java (original)
+++ velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTGENode.java Wed Dec 29 06:11:06 2010
@@ -19,133 +19,26 @@ package org.apache.velocity.runtime.pars
  * under the License.    
  */
 
-import org.apache.velocity.context.InternalContextAdapter;
-import org.apache.velocity.exception.MethodInvocationException;
-import org.apache.velocity.exception.VelocityException;
-import org.apache.velocity.runtime.RuntimeConstants;
-import org.apache.velocity.runtime.log.Log;
 import org.apache.velocity.runtime.parser.Parser;
-import org.apache.velocity.util.TemplateNumber;
-
 
 /**
  * Handles arg1 &gt;= arg2<br><br>
- *
- * Only subclasses of Number can be compared.<br><br>
- *
- * Please look at the Parser.jjt file which is
- * what controls the generation of this class.
- *
- * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
- * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a>
  */
-public class ASTGENode extends SimpleNode
+public class ASTGENode extends ASTComparisonNode
 {
-    /**
-     * @param id
-     */
     public ASTGENode(int id)
     {
         super(id);
     }
 
-    /**
-     * @param p
-     * @param id
-     */
     public ASTGENode(Parser p, int id)
     {
         super(p, id);
     }
 
-
-    /**
-     * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
-     */
-    public Object jjtAccept(ParserVisitor visitor, Object data)
-    {
-        return visitor.visit(this, data);
-    }
-
-    /**
-     * @see org.apache.velocity.runtime.parser.node.SimpleNode#evaluate(org.apache.velocity.context.InternalContextAdapter)
-     */
-    public boolean evaluate( InternalContextAdapter context)
-        throws MethodInvocationException
+    public boolean numberTest(int compareResult)
     {
-        /*
-         *  get the two args
-         */
-
-        Object left = jjtGetChild(0).value( context );
-        Object right = jjtGetChild(1).value( context );
-
-        /*
-         *  if either is null, lets log and bail
-         */
-
-        if (left == null || right == null)
-        {
-            String msg = (left == null ? "Left" : "Right")
-                           + " side ("
-                           + jjtGetChild( (left == null? 0 : 1) ).literal()
-                           + ") of '>=' operation has null value at "
-                           + Log.formatFileString(this);
-
-            if (rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false))
-            {
-              throw new VelocityException(msg);
-            }
-            
-            log.error(msg);
-            return false;
-        }
-
-
-        /*
-         *  convert to Number if applicable
-         */
-        if (left instanceof TemplateNumber)
-        {
-           left = ( (TemplateNumber) left).getAsNumber();
-        }
-        if (right instanceof TemplateNumber)
-        {
-           right = ( (TemplateNumber) right).getAsNumber();
-        }
-
-        /*
-         *  Only compare Numbers
-         */
-
-        if ( !( left instanceof Number )  || !( right instanceof Number ))
-        {
-            String msg = (!(left instanceof Number) ? "Left" : "Right")
-                           + " side of '>=' operation is not a Number at "
-                           + Log.formatFileString(this);
-
-            if (rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false))
-            {
-              throw new VelocityException(msg);
-            }
-
-            log.error(msg);
-            return false;
-        }
-
-       return MathUtils.compare ( (Number)left,(Number)right) >= 0;
-
-    }
-
-    /**
-     * @see org.apache.velocity.runtime.parser.node.SimpleNode#value(org.apache.velocity.context.InternalContextAdapter)
-     */
-    public Object value(InternalContextAdapter context)
-        throws MethodInvocationException
-    {
-        boolean val = evaluate(context);
-
-        return val ? Boolean.TRUE : Boolean.FALSE;
+        return compareResult >= 0;
     }
 
 }

Modified: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTGTNode.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTGTNode.java?rev=1053541&r1=1053540&r2=1053541&view=diff
==============================================================================
--- velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTGTNode.java (original)
+++ velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTGTNode.java Wed Dec 29 06:11:06 2010
@@ -19,132 +19,26 @@ package org.apache.velocity.runtime.pars
  * under the License.    
  */
 
-import org.apache.velocity.context.InternalContextAdapter;
-import org.apache.velocity.exception.MethodInvocationException;
-import org.apache.velocity.exception.VelocityException;
-import org.apache.velocity.runtime.RuntimeConstants;
-import org.apache.velocity.runtime.log.Log;
 import org.apache.velocity.runtime.parser.Parser;
-import org.apache.velocity.util.TemplateNumber;
 
 /**
  * Handles arg1 &gt; arg2<br><br>
- *
- * Only subclasses of Number can be compared.<br><br>
- *
- * Please look at the Parser.jjt file which is
- * what controls the generation of this class.
- *
- * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
- * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a>
  */
-
-public class ASTGTNode extends SimpleNode
+public class ASTGTNode extends ASTComparisonNode
 {
-    /**
-     * @param id
-     */
     public ASTGTNode(int id)
     {
         super(id);
     }
 
-    /**
-     * @param p
-     * @param id
-     */
     public ASTGTNode(Parser p, int id)
     {
         super(p, id);
     }
 
-
-    /**
-     * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
-     */
-    public Object jjtAccept(ParserVisitor visitor, Object data)
-    {
-        return visitor.visit(this, data);
-    }
-
-    /**
-     * @see org.apache.velocity.runtime.parser.node.SimpleNode#evaluate(org.apache.velocity.context.InternalContextAdapter)
-     */
-    public boolean evaluate(InternalContextAdapter context)
-        throws MethodInvocationException
-    {
-        /*
-         *  get the two args
-         */
-
-        Object left = jjtGetChild(0).value( context );
-        Object right = jjtGetChild(1).value( context );
-
-        /*
-         *  if either is null, lets log and bail
-         */
-
-        if (left == null || right == null)
-        {
-            String msg = (left == null ? "Left" : "Right")
-                           + " side ("
-                           + jjtGetChild( (left == null? 0 : 1) ).literal()
-                           + ") of '>' operation has null value at "
-                           + Log.formatFileString(this);
-
-            if (rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false))
-            {
-              throw new VelocityException(msg);
-            }
-
-            log.error(msg);
-            return false;
-        }
-
-        /*
-         *  convert to Number if applicable
-         */
-        if (left instanceof TemplateNumber)
-        {
-           left = ( (TemplateNumber) left).getAsNumber();
-        }
-        if (right instanceof TemplateNumber)
-        {
-           right = ( (TemplateNumber) right).getAsNumber();
-        }
-
-        /*
-         *  Only compare Numbers
-         */
-
-        if ( !( left instanceof Number )  || !( right instanceof Number ))
-        {
-            String msg = (!(left instanceof Number) ? "Left" : "Right")
-                           + " side of '>=' operation is not a Number at "
-                           + Log.formatFileString(this);
-
-            if (rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false))
-            {
-              throw new VelocityException(msg);
-            }
-            
-            log.error(msg);
-            return false;
-        }
-
-        return MathUtils.compare ( (Number)left,(Number)right) == 1;
-
-    }
-
-    /**
-     * @see org.apache.velocity.runtime.parser.node.SimpleNode#value(org.apache.velocity.context.InternalContextAdapter)
-     */
-    public Object value(InternalContextAdapter context)
-        throws MethodInvocationException
+    public boolean numberTest(int compareResult)
     {
-        boolean val = evaluate(context);
-
-        return val ? Boolean.TRUE : Boolean.FALSE;
+        return compareResult == 1;
     }
 
 }

Modified: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLENode.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLENode.java?rev=1053541&r1=1053540&r2=1053541&view=diff
==============================================================================
--- velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLENode.java (original)
+++ velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLENode.java Wed Dec 29 06:11:06 2010
@@ -19,131 +19,26 @@ package org.apache.velocity.runtime.pars
  * under the License.    
  */
 
-import org.apache.velocity.context.InternalContextAdapter;
-import org.apache.velocity.exception.MethodInvocationException;
-import org.apache.velocity.exception.VelocityException;
-import org.apache.velocity.runtime.RuntimeConstants;
-import org.apache.velocity.runtime.log.Log;
 import org.apache.velocity.runtime.parser.Parser;
-import org.apache.velocity.util.TemplateNumber;
 
 /**
  * Handles arg1 &lt;= arg2<br><br>
- *
- * Only subclasses of Number can be compared.<br><br>
- *
- * Please look at the Parser.jjt file which is
- * what controls the generation of this class.
- *
- * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
- * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a>
  */
-
-public class ASTLENode extends SimpleNode
+public class ASTLENode extends ASTComparisonNode
 {
-    /**
-     * @param id
-     */
     public ASTLENode(int id)
     {
         super(id);
     }
 
-    /**
-     * @param p
-     * @param id
-     */
     public ASTLENode(Parser p, int id)
     {
         super(p, id);
     }
 
-
-    /**
-     * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
-     */
-    public Object jjtAccept(ParserVisitor visitor, Object data)
-    {
-        return visitor.visit(this, data);
-    }
-
-    /**
-     * @see org.apache.velocity.runtime.parser.node.SimpleNode#evaluate(org.apache.velocity.context.InternalContextAdapter)
-     */
-    public boolean evaluate( InternalContextAdapter context)
-      throws MethodInvocationException
-    {
-        /*
-         *  get the two args
-         */
-
-        Object left = jjtGetChild(0).value( context );
-        Object right = jjtGetChild(1).value( context );
-
-        /*
-         *  if either is null, lets log and bail
-         */
-
-        if (left == null || right == null)
-        {
-            String msg = (left == null ? "Left" : "Right")
-                           + " side ("
-                           + jjtGetChild( (left == null? 0 : 1) ).literal()
-                           + ") of '<=' operation has null value at "
-                           + Log.formatFileString(this);
-
-            if (rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false))
-            {
-              throw new VelocityException(msg);
-            }
-            
-            log.error(msg);
-            return false;
-        }
-
-        /*
-         *  convert to Number if applicable
-         */
-        if (left instanceof TemplateNumber)
-        {
-           left = ( (TemplateNumber) left).getAsNumber();
-        }
-        if (right instanceof TemplateNumber)
-        {
-           right = ( (TemplateNumber) right).getAsNumber();
-        }
-
-        /*
-         *  Only compare Numbers
-         */
-        if ( !( left instanceof Number )  || !( right instanceof Number ))
-        {
-            String msg = (!(left instanceof Number) ? "Left" : "Right")
-                           + " side of '>=' operation is not a Number at "
-                           + Log.formatFileString(this);
-            
-            if (rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false))
-            {
-              throw new VelocityException(msg);
-            }
-
-            log.error(msg);
-            return false;
-        }
-
-        return MathUtils.compare ( (Number)left,(Number)right) <= 0;
-
-    }
-
-    /**
-     * @see org.apache.velocity.runtime.parser.node.SimpleNode#value(org.apache.velocity.context.InternalContextAdapter)
-     */
-    public Object value(InternalContextAdapter context)
-        throws MethodInvocationException
+    public boolean numberTest(int compareResult)
     {
-        boolean val = evaluate(context);
-
-        return val ? Boolean.TRUE : Boolean.FALSE;
+        return compareResult <= 0;
     }
 
 }

Modified: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLTNode.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLTNode.java?rev=1053541&r1=1053540&r2=1053541&view=diff
==============================================================================
--- velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLTNode.java (original)
+++ velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLTNode.java Wed Dec 29 06:11:06 2010
@@ -19,132 +19,26 @@ package org.apache.velocity.runtime.pars
  * under the License.    
  */
 
-import org.apache.velocity.context.InternalContextAdapter;
-import org.apache.velocity.exception.MethodInvocationException;
-import org.apache.velocity.exception.VelocityException;
-import org.apache.velocity.runtime.RuntimeConstants;
-import org.apache.velocity.runtime.log.Log;
 import org.apache.velocity.runtime.parser.Parser;
-import org.apache.velocity.util.TemplateNumber;
 
 /**
  * Handles arg1 &lt; arg2<br><br>
- *
- * Only subclasses of Number can be compared.<br><br>
- *
- * Please look at the Parser.jjt file which is
- * what controls the generation of this class.
- *
- * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
- * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a>
  */
-
-public class ASTLTNode extends SimpleNode
+public class ASTLTNode extends ASTComparisonNode
 {
-    /**
-     * @param id
-     */
     public ASTLTNode(int id)
     {
         super(id);
     }
 
-    /**
-     * @param p
-     * @param id
-     */
     public ASTLTNode(Parser p, int id)
     {
         super(p, id);
     }
 
-
-    /**
-     * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
-     */
-    public Object jjtAccept(ParserVisitor visitor, Object data)
-    {
-        return visitor.visit(this, data);
-    }
-
-    /**
-     * @see org.apache.velocity.runtime.parser.node.SimpleNode#evaluate(org.apache.velocity.context.InternalContextAdapter)
-     */
-    public boolean evaluate(InternalContextAdapter context)
-        throws MethodInvocationException
+    public boolean numberTest(int compareResult)
     {
-        /*
-         *  get the two args
-         */
-
-        Object left = jjtGetChild(0).value( context );
-        Object right = jjtGetChild(1).value( context );
-
-        /*
-         *  if either is null, lets log and bail
-         */
-
-        if (left == null || right == null)
-        {
-            String msg = (left == null ? "Left" : "Right")
-                           + " side ("
-                           + jjtGetChild( (left == null? 0 : 1) ).literal()
-                           + ") of '<' operation has null value at "
-                           + Log.formatFileString(this);
-
-            if (rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false))
-            {
-              throw new VelocityException(msg);
-            }
-                        
-            log.error(msg);
-            return false;
-        }
-
-        /*
-         *  convert to Number if applicable
-         */
-        if (left instanceof TemplateNumber)
-        {
-           left = ( (TemplateNumber) left).getAsNumber();
-        }
-        if (right instanceof TemplateNumber)
-        {
-           right = ( (TemplateNumber) right).getAsNumber();
-        }
-
-        /*
-         *  Only compare Numbers
-         */
-
-        if ( !( left instanceof Number )  || !( right instanceof Number ))
-        {
-            String msg = (!(left instanceof Number) ? "Left" : "Right")
-                           + " side of '>=' operation is not a valid Number at "
-                           + Log.formatFileString(this);
-
-            if (rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false))
-            {
-              throw new VelocityException(msg);
-            }
-            
-            log.error(msg);
-            return false;
-        }
-
-        return MathUtils.compare ( (Number)left,(Number)right) == -1;
-
-    }
-
-    /**
-     * @see org.apache.velocity.runtime.parser.node.SimpleNode#value(org.apache.velocity.context.InternalContextAdapter)
-     */
-    public Object value(InternalContextAdapter context)
-        throws MethodInvocationException
-    {
-        boolean val = evaluate(context);
-
-        return val ? Boolean.TRUE : Boolean.FALSE;
+        return compareResult == -1;
     }
 
 }

Modified: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMathNode.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMathNode.java?rev=1053541&r1=1053540&r2=1053541&view=diff
==============================================================================
--- velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMathNode.java (original)
+++ velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMathNode.java Wed Dec 29 06:11:06 2010
@@ -25,7 +25,7 @@ import org.apache.velocity.exception.Met
 import org.apache.velocity.exception.TemplateInitException;
 import org.apache.velocity.runtime.RuntimeConstants;
 import org.apache.velocity.runtime.parser.Parser;
-import org.apache.velocity.util.TemplateNumber;
+import org.apache.velocity.util.DuckType;
 
 /**
  * Helps handle math<br><br>
@@ -93,17 +93,17 @@ public abstract class ASTMathNode extend
             return special;
         }
 
-        /*
-         * convert to Number if applicable
-         */
-        if (left instanceof TemplateNumber)
+        // coerce to Number type, if possible
+        try
         {
-           left = ((TemplateNumber)left).getAsNumber();
+            left = DuckType.asNumber(left);
         }
-        if (right instanceof TemplateNumber)
+        catch (NumberFormatException nfe) {}
+        try
         {
-           right = ((TemplateNumber)right).getAsNumber();
+            right = DuckType.asNumber(right);
         }
+        catch (NumberFormatException nfe) {}
 
         /*
          * if not a Number, not much we can do

Modified: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTNENode.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTNENode.java?rev=1053541&r1=1053540&r2=1053541&view=diff
==============================================================================
--- velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTNENode.java (original)
+++ velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTNENode.java Wed Dec 29 06:11:06 2010
@@ -22,134 +22,29 @@ package org.apache.velocity.runtime.pars
 import org.apache.velocity.context.InternalContextAdapter;
 import org.apache.velocity.exception.MethodInvocationException;
 import org.apache.velocity.runtime.parser.Parser;
-import org.apache.velocity.util.TemplateNumber;
 
 /**
- *  Handles <code>arg1  != arg2</code>
- *
- *  This operator requires that the LHS and RHS are both of the
- *  same Class OR both are subclasses of java.lang.Number
- *
- *  @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
- *  @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a>
+ *  Handles <code>arg1 != arg2</code> by negating evaluation of ASTEQNode
  */
-public class ASTNENode extends SimpleNode
+public class ASTNENode extends ASTEQNode
 {
-    /**
-     * @param id
-     */
     public ASTNENode(int id)
     {
         super(id);
     }
 
-    /**
-     * @param p
-     * @param id
-     */
     public ASTNENode(Parser p, int id)
     {
         super(p, id);
     }
 
     /**
-     * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
-     */
-    public Object jjtAccept(ParserVisitor visitor, Object data)
-    {
-        return visitor.visit(this, data);
-    }
-
-    /**
-     * @see org.apache.velocity.runtime.parser.node.SimpleNode#evaluate(org.apache.velocity.context.InternalContextAdapter)
-     */
-    public boolean evaluate(  InternalContextAdapter context)
-        throws MethodInvocationException
-    {
-        Object left = jjtGetChild(0).value( context );
-        Object right = jjtGetChild(1).value( context );
-
-        /*
-         *  convert to Number if applicable
-         */
-        if (left instanceof TemplateNumber)
-        {
-           left = ( (TemplateNumber) left).getAsNumber();
-        }
-        if (right instanceof TemplateNumber)
-        {
-           right = ( (TemplateNumber) right).getAsNumber();
-        }
-
-       /*
-        * If comparing Numbers we do not care about the Class.
-        */
-       if (left instanceof Number && right instanceof Number)
-       {
-            return MathUtils.compare ( (Number)left,(Number)right) != 0;
-       }
-
-        /**
-         * if both are not null, then assume that if one class
-         * is a subclass of the other that we should use the equals operator
-         */
-        if (left != null && right != null &&
-            (left.getClass().isAssignableFrom(right.getClass()) ||
-             right.getClass().isAssignableFrom(left.getClass())))
-        {
-            return !left.equals( right );
-        }
-
-        /*
-         * Ok, time to compare string values
-         */
-        left = (left == null) ? null : left.toString();
-        right = (right == null) ? null: right.toString();
-
-        if (left == null && right == null)
-        {
-            if (log.isDebugEnabled())
-            {
-                log.debug("Both right (" + getLiteral(false) + " and left "
-                          + getLiteral(true) + " sides of '!=' operation returned null."
-                          + "If references, they may not be in the context."
-                          + getLocation(context));
-            }
-            return false;
-        }
-        else if (left == null || right == null)
-        {
-            if (log.isDebugEnabled())
-            {
-                log.debug((left == null ? "Left" : "Right")
-                        + " side (" + getLiteral(left == null)
-                        + ") of '!=' operation has null value. If it is a "
-                        + "reference, it may not be in the context or its "
-                        + "toString() returned null. " + getLocation(context));
-
-            }
-            return true;
-        }
-        else
-        {
-            return !left.equals(right);
-        }
-    }
-
-    private String getLiteral(boolean left)
-    {
-        return jjtGetChild(left ? 0 : 1).literal();
-    }
-
-    /**
-     * @see org.apache.velocity.runtime.parser.node.SimpleNode#value(org.apache.velocity.context.InternalContextAdapter)
+     * {@inheritDoc}
      */
-    public Object value(InternalContextAdapter context)
-        throws MethodInvocationException
+    @Override
+    public boolean evaluate(InternalContextAdapter context) throws MethodInvocationException
     {
-        boolean val = evaluate(context);
-
-        return val ? Boolean.TRUE : Boolean.FALSE;
+        return !super.evaluate(context);
     }
 
 }

Modified: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTReference.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTReference.java?rev=1053541&r1=1053540&r2=1053541&view=diff
==============================================================================
--- velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTReference.java (original)
+++ velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTReference.java Wed Dec 29 06:11:06 2010
@@ -37,6 +37,7 @@ import org.apache.velocity.runtime.log.L
 import org.apache.velocity.runtime.parser.Parser;
 import org.apache.velocity.runtime.parser.Token;
 import org.apache.velocity.util.ClassUtils;
+import org.apache.velocity.util.DuckType;
 import org.apache.velocity.util.introspection.Info;
 import org.apache.velocity.util.introspection.VelMethod;
 import org.apache.velocity.util.introspection.VelPropertySet;
@@ -51,7 +52,7 @@ import org.apache.velocity.util.introspe
  * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
  * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
  * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph Reck</a>
- * @author <a href="mailto:kjohnson@transparent.com>Kent Johnson</a>
+ * @author <a href="mailto:kjohnson@transparent.com">Kent Johnson</a>
  * @version $Id$
 */
 public class ASTReference extends SimpleNode
@@ -418,7 +419,7 @@ public class ASTReference extends Simple
                 }
             }
 
-            toString = value.toString();
+            toString = DuckType.asString(value);
         }
 
         if (value == null || toString == null)
@@ -526,29 +527,18 @@ public class ASTReference extends Simple
         throws MethodInvocationException
     {
         Object value = execute(null, context);
-
         if (value == null)
         {
             return false;
         }
-        else if (value instanceof Boolean)
+        try
         {
-            if (((Boolean) value).booleanValue())
-                return true;
-            else
-                return false;
-        }        
-        else
+            return DuckType.asBoolean(value);
+        }
+        catch(Exception e)
         {
-            try
-            {
-                return value.toString() != null;
-            }
-            catch(Exception e)
-            {
-                throw new VelocityException("Reference evaluation threw an exception at " 
-                    + Log.formatFileString(this), e);
-            }
+            throw new VelocityException("Reference evaluation threw an exception at " 
+                + Log.formatFileString(this), e);
         }
     }
 

Added: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/DuckType.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/DuckType.java?rev=1053541&view=auto
==============================================================================
--- velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/DuckType.java (added)
+++ velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/DuckType.java Wed Dec 29 06:11:06 2010
@@ -0,0 +1,289 @@
+package org.apache.velocity.util;
+
+/*
+ * 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.lang.reflect.Array;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.math.BigDecimal;
+import java.util.Map;
+import java.util.HashMap;
+
+/**
+ * Support for getAs<Type>() convention for rendering (String), evaluating (Boolean)
+ * or doing math with (Number) references.
+ *
+ * @author Nathan Bubna
+ * @since 2.0
+ */
+public class DuckType
+{
+    protected enum Types
+    {
+        STRING("getAsString"),
+        NUMBER("getAsNumber"),
+        BOOLEAN("getAsBoolean"),
+        EMPTY("isEmpty");
+
+        final String name;
+        final Map<Class,Object> cache = new HashMap();
+
+        Types(String name)
+        {
+            this.name = name;
+        }
+
+        void set(Class c, Object o)
+        {
+            cache.put(c, o);
+        }
+
+        Object get(Class c)
+        {
+            return cache.get(c);
+        }
+    }
+
+    protected static final Object NO_METHOD = new Object();
+
+    public static String asString(Object value)
+    {
+        return asString(value, true);
+    }
+
+    public static String asString(Object value, boolean coerceType)
+    {
+        if (value == null)
+        {
+            return null;
+        }
+        if (value instanceof String)
+        {
+            return (String)value;
+        }
+        Object got = get(value, Types.STRING);
+        if (got == NO_METHOD)
+        {
+            return coerceType ? value.toString() : null;
+        }
+        return (String)got;
+    }
+
+    public static boolean asNull(Object value)
+    {
+        if (value == null ||
+            get(value, Types.STRING) == null ||
+            get(value, Types.NUMBER) == null)
+        {
+            return true;
+        }
+        return false;
+    }
+
+    public static boolean asBoolean(Object value)
+    {
+        return asBoolean(value, true);
+    }
+
+    public static boolean asBoolean(Object value, boolean coerceType)
+    {
+        if (value == null)
+        {
+            return false;
+        }
+        if (value instanceof Boolean)
+        {
+            return ((Boolean)value).booleanValue();
+        }
+        Object got = get(value, Types.BOOLEAN);
+        if (got != NO_METHOD)
+        {
+            return ((Boolean)got).booleanValue();
+        }
+        if (coerceType)
+        {
+            return !asEmpty(value);
+        }
+        return true;
+    }
+
+    // see VELOCITY-692 for discussion about empty values
+    public static boolean asEmpty(Object value)
+    {
+        // empty variable
+        if (value == null)
+        {
+            return true;
+        }
+
+        // empty string
+        if (value instanceof CharSequence)
+        {
+            return ((CharSequence)value).length() == 0;
+        }
+
+        // isEmpty() object
+        Object isEmpty = get(value, Types.EMPTY);
+        if (isEmpty != NO_METHOD)
+        {
+            return (Boolean)isEmpty;
+        }
+
+        // empty array
+        if (value.getClass().isArray())
+        {
+            return Array.getLength(value) == 0;// [] is false
+        }
+
+        // null getAsString()
+        Object asString = get(value, Types.STRING);
+        if (asString == null)
+        {
+            return true;// duck null
+        }
+        // empty getAsString()
+        else if (asString != NO_METHOD)
+        {
+            return ((String)asString).length() == 0;
+        }
+
+        // null getAsNumber()
+        Object asNumber = get(value, Types.NUMBER);
+        if (asNumber == null)
+        {
+            return true;
+        }
+
+        // empty toString()
+        String string = value.toString();
+        return string == null || string.length() == 0;
+    }
+
+    public static Number asNumber(Object value)
+    {
+        return asNumber(value, true);
+    }
+
+    public static Number asNumber(Object value, boolean coerceType)
+    {
+        if (value == null)
+        {
+            return null;
+        }
+        if (value instanceof Number)
+        {
+            return (Number)value;
+        }
+        Object got = get(value, Types.NUMBER);
+        if (got != NO_METHOD)
+        {
+            return (Number)got;
+        }
+        if (coerceType)
+        {
+            String string = asString(value);// coerce to string
+            if (string != null)
+            {
+                return new BigDecimal(string);
+            }
+        }
+        return null;
+    }
+
+    protected static Object get(Object value, Types type)
+    {
+        try
+        {
+            // check cache
+            Class c = value.getClass();
+            Object cached = type.get(c);
+            if (cached == NO_METHOD)
+            {
+                return cached;
+            }
+            if (cached != null)
+            {
+                return ((Method)cached).invoke(value);
+            }
+            // ok, search the class
+            Method method = findMethod(c, type);
+            if (method == null)
+            {
+                type.set(c, NO_METHOD);
+                return NO_METHOD;
+            }
+            type.set(c, method);
+            return method.invoke(value);
+        }
+        catch (RuntimeException re)
+        {
+            throw re;
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException(e);// no checked exceptions, please
+        }
+    }
+
+    protected static Method findMethod(Class c, Types type)
+    {
+        if (c == null || c == Object.class)
+        {
+            return null;
+        }
+        Method m = getMethod(c, type.name);
+        if (m != null)
+        {
+            return m;
+        }
+        for (Class i : c.getInterfaces())
+        {
+            m = findMethod(i, type);
+            if (m != null)
+            {
+                return m;
+            }
+        }
+        m = findMethod(c.getSuperclass(), type);
+        if (m != null)
+        {
+            return m;
+        }
+        return null;
+    }
+
+    private static Method getMethod(Class c, String name)
+    {
+        if (Modifier.isPublic(c.getModifiers()))
+        {
+            try
+            {
+                Method m = c.getDeclaredMethod(name);
+                if (Modifier.isPublic(m.getModifiers()))
+                {
+                    return m;
+                }
+            }
+            catch (NoSuchMethodException nsme) {}
+        }
+        return null;
+    }
+
+}

Propchange: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/DuckType.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/DuckType.java
------------------------------------------------------------------------------
    svn:keywords = Revision

Propchange: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/DuckType.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/GetAsTestCase.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/GetAsTestCase.java?rev=1053541&view=auto
==============================================================================
--- velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/GetAsTestCase.java (added)
+++ velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/GetAsTestCase.java Wed Dec 29 06:11:06 2010
@@ -0,0 +1,147 @@
+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.util.TemplateString;
+import org.apache.velocity.util.TemplateBoolean;
+import org.apache.velocity.util.TemplateNumber;
+
+/**
+ * Test objects with getAs<Type>() methods.
+ */
+public class GetAsTestCase extends BaseTestCase
+{
+    public GetAsTestCase(final String name)
+    {
+        super(name);
+    }
+
+    public void testCustomString()
+    {
+        // render
+        context.put("foo", new CustomString("getAsString"));
+        assertEvalEquals("getAsString", "$foo");
+        // aborted value
+        context.put("bar", new CustomString(null));
+        assertEvalEquals("", "$!bar");
+        // concatenation
+        context.put("half", new CustomString("half"));
+        assertEvalEquals("1half", "#set( $out = 1 + $half )$out");
+    }
+
+    public void testCustomBoolean()
+    {
+        context.put("foo", new CustomBoolean(false));
+        assertEvalEquals("right", "#if( !$foo )right#end");
+    }
+
+    public void testCustomNumber()
+    {
+        context.put("foo", new CustomNumber(7));
+        assertEvalEquals("14", "#set( $bar = $foo * 2 )$bar");
+    }
+
+
+    public void testTemplateString()
+    {
+        context.put("foo", new CustomTemplateString("getAsString"));
+        assertEvalEquals("getAsString", "$foo");
+    }
+
+    public void testTemplateBoolean()
+    {
+        context.put("foo", new CustomTemplateBoolean(false));
+        assertEvalEquals("right", "#if( !$foo )right#end");
+    }
+
+    public void testTemplateNumber()
+    {
+        context.put("foo", new CustomTemplateNumber(5));
+        assertEvalEquals("25", "#set( $foo = $foo * $foo )$foo");
+    }
+
+
+
+    public static class CustomString
+    {
+        String string;
+        public CustomString(String string)
+        {
+            this.string = string;
+        }
+        public String getAsString()
+        {
+            return string;
+        }
+    }
+
+    public static class CustomBoolean
+    {
+        boolean bool;
+        public CustomBoolean(boolean bool)
+        {
+            this.bool = bool;
+        }
+        public boolean getAsBoolean()
+        {
+            return bool;
+        }
+    }
+
+    public static class CustomNumber
+    {
+        Number num;
+        public CustomNumber(Number num)
+        {
+            this.num = num;
+        }
+        public Number getAsNumber()
+        {
+            return num;
+        }
+    }
+
+    public static class CustomTemplateString extends CustomString implements TemplateString
+    {
+        public CustomTemplateString(String string)
+        {
+            super(string);
+        }
+    }
+
+    public static class CustomTemplateBoolean extends CustomBoolean implements TemplateBoolean
+    {
+        public CustomTemplateBoolean(Boolean bool)
+        {
+            super(bool);
+        }
+    }
+
+    public static class CustomTemplateNumber extends CustomNumber implements TemplateNumber
+    {
+        public CustomTemplateNumber(Number num)
+        {
+            super(num);
+        }
+    }
+
+}
+
+

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

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

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

Added: velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/IfEmptyTestCase.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/IfEmptyTestCase.java?rev=1053541&view=auto
==============================================================================
--- velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/IfEmptyTestCase.java (added)
+++ velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/IfEmptyTestCase.java Wed Dec 29 06:11:06 2010
@@ -0,0 +1,95 @@
+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 java.util.Collections;
+
+/**
+ * Used to check that empty values are properly handled in #if statements
+ */
+public class IfEmptyTestCase extends BaseTestCase
+{
+    public IfEmptyTestCase(final String name)
+    {
+        super(name);
+    }
+
+    protected void assertEmpty(Object obj)
+    {
+        context.put("obj", obj);
+        assertEvalEquals("", "#if( $obj )fail#end");
+    }
+
+    public void testNull()
+    {
+        assertEmpty(null);
+        assertEmpty(new NullAsString());
+        assertEmpty(new NullAsNumber());
+    }
+
+    public void testDataStructures()
+    {
+        assertEmpty(Collections.emptyMap());
+        assertEmpty(Collections.emptyList());
+        assertEmpty(new Object[]{});
+    }
+
+    public void testString()
+    {
+        assertEmpty("");
+        assertEmpty(new EmptyAsString());
+        assertEmpty(new EmptyToString());
+    }
+
+    public static class NullAsString
+    {
+        public String getAsString()
+        {
+            return null;
+        }
+    }
+
+    public static class EmptyAsString
+    {
+        public String getAsString()
+        {
+            return "";
+        }
+    }
+
+    public static class NullAsNumber
+    {
+        public String getAsNumber()
+        {
+            return null;
+        }
+    }
+
+    public static class EmptyToString
+    {
+        public String toString()
+        {
+            return "";
+        }
+    }
+
+}
+
+

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

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

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

Modified: velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/IfNullTestCase.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/IfNullTestCase.java?rev=1053541&r1=1053540&r2=1053541&view=diff
==============================================================================
--- velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/IfNullTestCase.java (original)
+++ velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/IfNullTestCase.java Wed Dec 29 06:11:06 2010
@@ -91,7 +91,7 @@ public class IfNullTestCase extends Base
 
     public static class NullToString
     {
-        public String toString()
+        public String getAsString()
         {
             return null;
         }