velocity-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From nbu...@apache.org
Subject svn commit: r629296 - in /velocity/tools/trunk: examples/showcase/WEB-INF/src/resources.properties examples/showcase/loop.vm src/main/java/org/apache/velocity/tools/generic/LoopTool.java src/test/java/org/apache/velocity/tools/LoopToolTests.java
Date Wed, 20 Feb 2008 01:02:23 GMT
Author: nbubna
Date: Tue Feb 19 17:02:18 2008
New Revision: 629296

URL: http://svn.apache.org/viewvc?rev=629296&view=rev
Log:
add sync() method to easily iterate over multiple lists in parallel

Modified:
    velocity/tools/trunk/examples/showcase/WEB-INF/src/resources.properties
    velocity/tools/trunk/examples/showcase/loop.vm
    velocity/tools/trunk/src/main/java/org/apache/velocity/tools/generic/LoopTool.java
    velocity/tools/trunk/src/test/java/org/apache/velocity/tools/LoopToolTests.java

Modified: velocity/tools/trunk/examples/showcase/WEB-INF/src/resources.properties
URL: http://svn.apache.org/viewvc/velocity/tools/trunk/examples/showcase/WEB-INF/src/resources.properties?rev=629296&r1=629295&r2=629296&view=diff
==============================================================================
--- velocity/tools/trunk/examples/showcase/WEB-INF/src/resources.properties (original)
+++ velocity/tools/trunk/examples/showcase/WEB-INF/src/resources.properties Tue Feb 19 17:02:18
2008
@@ -196,3 +196,11 @@
 field.in_String = Looks for a class with the specified name; if found, loads all public static
fields.
 field.in_String.param1 = 'java.lang.Float'
 field.custom = $field.in($field).INCLUDE_KEY
+
+# loop.vm resources
+loop.intro = This tool is a convenience tool to use with #foreach loops. It wraps a \
+list to let you prematurely stop iterating, skip iterations, sync iteration over \
+multiple lists in the same loop, easily \
+determine if you are on the first or last iteration, get the number of iterations \
+completed, automatically stop before or exclude particular elements and easily \
+do all the above even with complex, nested loops.

Modified: velocity/tools/trunk/examples/showcase/loop.vm
URL: http://svn.apache.org/viewvc/velocity/tools/trunk/examples/showcase/loop.vm?rev=629296&r1=629295&r2=629296&view=diff
==============================================================================
--- velocity/tools/trunk/examples/showcase/loop.vm (original)
+++ velocity/tools/trunk/examples/showcase/loop.vm Tue Feb 19 17:02:18 2008
@@ -16,41 +16,24 @@
 ## under the License.
 #title( 'LoopTool' )
 <p>
-#set( $demo = $text.demo )
-$demo.thisPage.insert("#doclink( 'LoopTool' true )").
-This tool is a convenience tool to use with ${esc.h}foreach loops. It wraps a
-list to let you prematurely end stop iterating, skip iterations, easily
-determine if you are on the first or last iteration, get the number of iterations
-completed, automatically exclude or break on particular elements and easily
-do all the above with complex, nested loops.
+$text.demo.thisPage.insert("#doclink( 'LoopTool' true )").
+</p>
+<p>
+$text.loop.intro
 </p>
 
-<form method="post" action="$link.self">
-  <textarea name="demo" rows="10" cols="65">
-#if( $params.demo )##
-$params.demo##
-#else##
-${esc.h}set( ${esc.d}numbers = [1, 2, 3, 4, 5, 6] )
-${esc.h}set( ${esc.d}letters = ['A','B','C'] )
-${esc.h}foreach( ${esc.d}l in ${esc.d}loop.watch(${esc.d}letters, 'l') )
-${esc.h}foreach( ${esc.d}n in ${esc.d}loop.watch(${esc.d}numbers).exclude(3) )
-${esc.h}if( ${esc.d}loop.first )${esc.d}l[${esc.h}end${esc.h}${esc.h}
-${esc.d}n${esc.h}${esc.h}
-${esc.h}if( ${esc.d}loop.last )]  ${esc.h}else, ${esc.h}end${esc.h}${esc.h}
-${esc.h}if( ${esc.d}l == 'B' )${esc.d}loop.stop('l')${esc.h}end${esc.h}${esc.h} stop the
letter loop after B
-${esc.h}end
-${esc.h}end##
-#end#*
-*#</textarea>
-  <input type="submit" value="$demo.try">
-  #if( $params.layout )
-  <input type="hidden" name="layout" value="$params.layout">
+#set( $fullDemo =
+'#set( $numbers = [1..5] )
+#set( $letters = ["A","B","C"] )
+#set( $symbols = ["!", "@"] )
+#foreach( $l in $loop.watch($letters, "l").sync($symbols, "s") )
+  #foreach( $n in $loop.watch($numbers).exclude(3) )
+#if( $loop.first )$l$loop.s[#end##
+$n##
+#if( $loop.last )]  #else, #end##
+#if( $l == "B" )$loop.stop("l")#end## stop the letter loop after B
   #end
-</form>
+#end'
+)
 
-#if( $params.demo )
-$demo.mainResultsIntro:
-<pre>
-  $render.eval($params.demo)
-</pre>
-#end
+#parse( 'fullDemo.vm' )

Modified: velocity/tools/trunk/src/main/java/org/apache/velocity/tools/generic/LoopTool.java
URL: http://svn.apache.org/viewvc/velocity/tools/trunk/src/main/java/org/apache/velocity/tools/generic/LoopTool.java?rev=629296&r1=629295&r2=629296&view=diff
==============================================================================
--- velocity/tools/trunk/src/main/java/org/apache/velocity/tools/generic/LoopTool.java (original)
+++ velocity/tools/trunk/src/main/java/org/apache/velocity/tools/generic/LoopTool.java Tue
Feb 19 17:02:18 2008
@@ -20,8 +20,10 @@
  */
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Stack;
 import org.apache.velocity.tools.ClassUtils;
@@ -32,26 +34,27 @@
 /**
  * <p>
  * A convenience tool to use with #foreach loops. It wraps a list
- * with a custom iterator to let the designer specify a condition
- * to stop looping over the items early.
+ * with a custom iterator to provide additional controls and feedback
+ * for managing loops.
  * </p>
  * <p>
- * This tool is heavily inspired the now-deprecated IteratorTool,
- * which provided similar functionality but was somewhat more difficult
+ * This tool was originally inspired the now-deprecated IteratorTool,
+ * which provided similar base functionality but was somewhat more difficult
  * to understand and use.  Rather than try to migrate that implementation
  * via deprecation and new methods, it was simplest to just create an
- * entirely new tool with a simplified API, support for nested loops
- * (which can optionally be given names), skipping ahead in loops,
- * getting the iteration count of loops, identifying if a loop is
+ * entirely new tool that simplified the original API and was easy
+ * to augment with useful new features like support for nested 
+ * (and nameable) loops, skipping ahead in loops, synchronizing multiple
+ * iterators, getting the iteration count of loops, identifying if a loop is
  * on its first or last iteration, and so on.
  * </p>
  * <p>
- * Most users, of course, will probably never need anything beyond the 
- * simple {@link #watch(Object)}, {@link ManagedIterator#stop(Object)}, 
- * {@link ManagedIterator#exclude(Object)}, {@link #isFirst},
- * {@link #isLast},and maybe {@link #getCount}
+ * Most usage, of course, will probably never go much beyond the 
+ * simple {@link #watch(Object)}, {@link ManagedIterator#sync(Object)}, 
+ * {@link ManagedIterator#stop(Object)}, {@link #isFirst},
+ * {@link #isLast},and maybe {@link #getCount} or {@link #getIndex}
  * methods, if even that much.  However, it is with complicated nested
- * #foreach loops and varying "break" conditions that this tool can
+ * #foreach loops and varying "break" conditions,  that this tool can
  * probably do the most to simplify your templates.
  * </p>
  * <p>
@@ -59,15 +62,20 @@
  * <pre>
  *  Template
  *  ---
- *  #set ($list = [1..10])
- *  #foreach( $item in $loop.watch($list).exclude(3) )
- *  $item ##
+ *  #set( $list = [1..7] )
+ *  #set( $others = [3..10] )
+ *  #foreach( $item in $loop.watch($list).sync($others, 'other') )
+ *  $item -> $loop.other
  *  #if( $item >= 5 )$loop.stop()#end
  *  #end
  *
  *  Output
  *  ------
- *  1 2 4 5
+ *  1 -> 3
+ *  2 -> 4
+ *  3 -> 5
+ *  4 -> 6
+ *  5 -> 7
  *
  * Example tools.xml config (if you want to use this with VelocityView):
  * &lt;tools&gt;
@@ -359,6 +367,76 @@
     }
 
     /**
+     * Searches all the loops being managed for one with a sync'ed
+     * Iterator under the specified name and returns the current value
+     * for that sync'ed iterator, if any.
+     */
+    public Object get(String synced)
+    {
+        // search all iterators in reverse
+        // (so nested ones take priority)
+        // for one that is responsible for synced
+        for (int i=iterators.size() - 1; i >= 0; i--)
+        {
+            ManagedIterator iterator = iterators.get(i);
+            if (iterator.isSyncedWith(synced))
+            {
+                return iterator.get(synced);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Asks the loop with the specified name for the current value
+     * of the specified sync'ed iterator, if any.
+     */
+    public Object get(String name, String synced)
+    {
+        // just ask the matching iterator for the sync'ed value
+        ManagedIterator iterator = findIterator(name);
+        if (iterator != null)
+        {
+            return iterator.get(synced);
+        }
+        return null;
+    }
+
+    /**
+     * Returns the 0-based index of the item the current loop is handling.
+     * So, if this is the first iteration, then the index will be 0. If
+     * you {@link #skip} ahead in this loop, those skipped iterations will
+     * still be reflected in the index.  If iteration has not begun, this
+     * will return {@code null}.
+     */
+    public Integer getIndex()
+    {
+        Integer count = getCount();
+        if (count == null || count == 0)
+        {
+            return null;
+        }
+        return count - 1;
+    }
+
+    /**
+     * Returns the 0-based index of the item the specified loop is handling.
+     * So, if this is the first iteration, then the index will be 0. If
+     * you {@link #skip} ahead in this loop, those skipped iterations will
+     * still be reflected in the index.  If iteration has not begun, this
+     * will return {@code null}.
+     */
+    public Integer getIndex(String name)
+    {
+        Integer count = getCount(name);
+        if (count == null || count == 0)
+        {
+            return null;
+        }
+        return count - 1;
+    }
+
+    /**
      * Returns the number of items the current loop has handled. So, if this
      * is the first iteration, then the count will be 1.  If you {@link #skip}
      * ahead in this loop, those skipped iterations will still be included in
@@ -469,6 +547,7 @@
         private int count = 0;
         private Object next;
         private List<ActionCondition> conditions;
+        private Map<String,SyncedIterator> synced;
 
         public ManagedIterator(String name, Iterator iterator, LoopTool owner)
         {
@@ -586,9 +665,53 @@
             }
 
             // ok, looks like we have a next that met all the conditions
+            shiftSynced();
             return true;
         }
 
+        private void shiftSynced()
+        {
+            if (synced != null)
+            {
+                for (SyncedIterator parallel : synced.values())
+                {
+                    parallel.shift();
+                }
+            }
+        }
+
+        /**
+         * Returns {@code true} if this ManagedIterator has a sync'ed
+         * iterator with the specified name.
+         */
+        public boolean isSyncedWith(String name)
+        {
+            if (synced == null)
+            {
+                return false;
+            }
+            return synced.containsKey(name);
+        }
+
+        /**
+         * Returns the parallel value from the specified sync'ed iterator.
+         * If no sync'ed iterator exists with that name or that iterator
+         * is finished, this will return {@code null}.
+         */
+        public Object get(String name)
+        {
+            if (synced == null)
+            {
+                return null;
+            }
+            SyncedIterator parallel = synced.get(name);
+            if (parallel == null)
+            {
+                return null;
+            }
+            return parallel.get();
+        }
+
         /**
          * Returns the number of elements returned by {@link #next()} so far.
          */
@@ -694,6 +817,53 @@
             return this;
         }
 
+        /**
+         * <p>Adds another iterator to be kept in sync with the one
+         * being managed by this instance.  The values of the parallel
+         * iterator can be retrieved from the LoopTool under the
+         * name s"synced" (e.g. $loop.synched or $loop.get('synced'))
+         * and are automatically updated for each iteration by this instance.
+         * </p><p><b>NOTE</b>: if you are sync'ing multiple iterators
+         * with the same managed iterator, you must use 
+         * {@link #sync(Object,String)} or else your the later iterators
+         * will simply replace the earlier ones under the default
+         * 'synced' key.</p>
+         *
+         * @return This same {@link ManagedIterator} instance
+         * @see SyncedIterator
+         * @see #get(String)
+         */
+        public ManagedIterator sync(Object iterable)
+        {
+            return sync(iterable, "synced");
+        }
+
+        /**
+         * Adds another iterator to be kept in sync with the one
+         * being managed by this instance.  The values of the parallel
+         * iterator can be retrieved from the LoopTool under the
+         * name specified here (e.g. $loop.name or $loop.get('name'))
+         * and are automatically updated for each iteration by this instance.
+         *
+         * @return This same {@link ManagedIterator} instance
+         * @see SyncedIterator
+         * @see #get(String)
+         */
+        public ManagedIterator sync(Object iterable, String name)
+        {
+            Iterator parallel = LoopTool.getIterator(iterable);
+            if (parallel == null)
+            {
+                return null;
+            }
+            if (synced == null)
+            {
+                synced = new HashMap<String,SyncedIterator>();
+            }
+            synced.put(name, new SyncedIterator(parallel));
+            return this;
+        }
+
         @Override
         public String toString()
         {
@@ -795,6 +965,53 @@
             }
             // compare them as strings as a last resort
             return String.valueOf(value).equals(String.valueOf(compare));
+        }
+    }
+
+
+    /**
+     * Simple wrapper to make it easy to keep an arbitray Iterator
+     * in sync with a {@link ManagedIterator}.
+     */
+    public static class SyncedIterator
+    {
+        private Iterator iterator;
+        private Object current;
+
+        public SyncedIterator(Iterator iterator)
+        {
+            if (iterator == null)
+            {
+                // do we really care?  perhaps we should just keep quiet...
+                throw new NullPointerException("Cannot synchronize a null Iterator");
+            }
+            this.iterator = iterator;
+        }
+
+        /**
+         * If the sync'ed iterator has any more values,
+         * this sets the next() value as the current one.
+         * If there are no more values, this sets the current
+         * one to {@code null}.
+         */
+        public void shift()
+        {
+            if (iterator.hasNext())
+            {
+                current = iterator.next();
+            }
+            else
+            {
+                current = null;
+            }
+        }
+
+        /**
+         * Returns the currently parallel value, if any.
+         */
+        public Object get()
+        {
+            return current;
         }
     }
 

Modified: velocity/tools/trunk/src/test/java/org/apache/velocity/tools/LoopToolTests.java
URL: http://svn.apache.org/viewvc/velocity/tools/trunk/src/test/java/org/apache/velocity/tools/LoopToolTests.java?rev=629296&r1=629295&r2=629296&view=diff
==============================================================================
--- velocity/tools/trunk/src/test/java/org/apache/velocity/tools/LoopToolTests.java (original)
+++ velocity/tools/trunk/src/test/java/org/apache/velocity/tools/LoopToolTests.java Tue Feb
19 17:02:18 2008
@@ -256,35 +256,97 @@
         assertTrue(loop.isLast("j"));
     }
 
-    public @Test void methodGetCount() throws Exception
+    public @Test void methodGet() throws Exception
+    {
+        LoopTool loop = new LoopTool();
+        // sync an array with itself
+        Iterator i = loop.watch(ARRAY).sync(ARRAY, "twin");
+        while (i.hasNext())
+        {
+            // make sure they match
+            assertEquals(i.next(), loop.get("twin"));
+        }
+
+        // sync a shorter array with a longer one to be
+        // sure the values turn to null once they're gone
+        int[] little = { 10, 20, 30 };
+        Integer[] big = { 1, 2, 3, 4, 5 };
+        i = loop.watch(big).sync(little, "little");
+        while (i.hasNext())
+        {
+            Integer val = (Integer)i.next();
+            if (val < 4)
+            {
+                assertEquals(val * 10, loop.get("little"));
+            }
+            else
+            {
+                assertNull(loop.get("little"));
+            }
+        }
+    }
+
+    public @Test void methodGet_StringString() throws Exception
+    {
+        LoopTool loop = new LoopTool();
+        int[] other = { 1, 2, 3 };
+
+        // sync arrays with nested loops using default names
+        // the way we iterate over both i and j together below
+        // is, of course, impossible in a template, but it
+        // makes writing a reasonable test for this method a lot
+        // easier.
+        //NOTE: this reliese on the default name for synced iterators
+        //      being "synced", for i being "loop0", and for j being "loop1"
+        Iterator i = loop.watch(ARRAY).sync(other);
+        Iterator j = loop.watch(other).sync(ARRAY);
+        while (i.hasNext() && j.hasNext())
+        {
+            // i and loop.synced (aka loop.get("loop1","synced")) should match
+            assertEquals(i.next(), loop.get("synced"));
+            // j and loop.get("loop0","synced") should match
+            assertEquals(j.next(), loop.get("loop0", "synced"));
+        }
+    }
+
+    public @Test void methodGetCountOrGetIndex() throws Exception
     {
         LoopTool loop = new LoopTool();
         Iterator i = loop.watch(ARRAY);
         assertEquals(0, loop.getCount());
+        assertNull(loop.getIndex());
         i.next();
         assertEquals(1, loop.getCount());
+        assertEquals(0, loop.getIndex());
         i.next();
         assertEquals(2, loop.getCount());
+        assertEquals(1, loop.getIndex());
         i.next();
         assertEquals(3, loop.getCount());
+        assertEquals(2, loop.getIndex());
         loop.pop();
         // test that skipped iterations are still included
         i = loop.watch(ARRAY);
         loop.skip(2);
         assertEquals(2, loop.getCount());
+        assertEquals(1, loop.getIndex());
     }
 
-    public @Test void methodGetCount_String() throws Exception
+    public @Test void methodGetCountOrGetIndex_String() throws Exception
     {
         LoopTool loop = new LoopTool();
         Iterator i = loop.watch(ARRAY, "i");
         assertEquals(0, loop.getCount("i"));
+        assertNull(loop.getIndex("i"));
         i.next();
         assertEquals(1, loop.getCount("i"));
+        assertEquals(0, loop.getIndex("i"));
         Iterator j = loop.watch(ARRAY, "j");
         loop.skip(2);
         assertEquals(2, loop.getCount("j"));
+        assertEquals(1, loop.getIndex("j"));
         assertEquals(1, loop.getCount("i"));
+        assertEquals(0, loop.getIndex("i"));
     }
 
     public @Test void aliasMethods() throws Exception



Mime
View raw message