Author: jboynes Date: Mon Feb 11 01:10:57 2013 New Revision: 1444644 URL: http://svn.apache.org/r1444644 Log: major refactor of ForEachSupport to reduce array allocation and use auto-boxing also addresses memory issue for begin..end iteration for large values of end simplify iteration of deferred expressions
Modified: tomcat/taglibs/standard/trunk/impl/src/main/java/org/apache/taglibs/standard/tag/common/core/ForEachSupport.java Modified: tomcat/taglibs/standard/trunk/impl/src/main/java/org/apache/taglibs/standard/tag/common/core/ForEachSupport.java URL: http://svn.apache.org/viewvc/tomcat/taglibs/standard/trunk/impl/src/main/java/org/apache/taglibs/standard/tag/common/core/ForEachSupport.java?rev=1444644&r1=1444643&r2=1444644&view=diff ============================================================================== --- tomcat/taglibs/standard/trunk/impl/src/main/java/org/apache/taglibs/standard/tag/common/core/ForEachSupport.java (original) +++ tomcat/taglibs/standard/trunk/impl/src/main/java/org/apache/taglibs/standard/tag/common/core/ForEachSupport.java Mon Feb 11 01:10:57 2013 @@ -17,22 +17,16 @@ package org.apache.taglibs.standard.tag.common.core; -import java.util.Arrays; -import java.util.Collection; -import java.util.Enumeration; -import java.util.Iterator; -import java.util.Map; -import java.util.StringTokenizer; +import org.apache.taglibs.standard.resources.Resources; -import javax.el.ELContext; import javax.el.ValueExpression; import javax.servlet.jsp.JspTagException; import javax.servlet.jsp.jstl.core.IndexedValueExpression; import javax.servlet.jsp.jstl.core.IteratedExpression; import javax.servlet.jsp.jstl.core.IteratedValueExpression; import javax.servlet.jsp.jstl.core.LoopTagSupport; - -import org.apache.taglibs.standard.resources.Resources; +import java.lang.reflect.Array; +import java.util.*; /** * <p>Support for tag handlers for <forEach>, the core iteration @@ -50,217 +44,50 @@ import org.apache.taglibs.standard.resou * @see javax.servlet.jsp.jstl.core.LoopTagSupport */ public abstract class ForEachSupport extends LoopTagSupport { + protected Iterator items; // our 'digested' items + protected Object rawItems; // our 'raw' items - //********************************************************************* - // Implementation overview - - /* - * This particular handler is essentially a large switching mechanism - * to support the various types that the <forEach> tag handles. The - * class is organized around the private ForEachIterator interface, - * which serves as the basis for relaying information to the iteration - * implementation we inherit from LoopTagSupport. - * - * We expect to receive our 'items' from one of our subclasses - * (presumably from the rtexprvalue or expression-evaluating libraries). - * If 'items' is missing, we construct an Integer[] array representing - * iteration indices, in line with the spec draft. From doStartTag(), - * we analyze and 'digest' the data we're passed. Then, we simply - * relay items as necessary to the iteration implementation that - * we inherit from LoopTagSupport. - */ - - - //********************************************************************* - // Internal, supporting classes and interfaces - - /* - * Acts as a focal point for converting the various types we support. - * It would have been ideal to use Iterator here except for one problem: - * Iterator.hasNext() and Iterator.next() can't throw the JspTagException - * we want to throw. So instead, we'll encapsulate the hasNext() and - * next() methods we want to provide inside this local class. - * (Other implementations are more than welcome to implement hasNext() - * and next() explicitly, not in terms of a back-end supporting class. - * For the forEach tag handler, however, this class acts as a convenient - * organizational mechanism, for we support so many different classes. - * This encapsulation makes it easier to localize implementations - * in support of particular types -- e.g., changing the implementation - * of primitive-array iteration to wrap primitives only on request, - * instead of in advance, would involve changing only those methods that - * handle primitive arrays. - */ - - protected static interface ForEachIterator { - public boolean hasNext() throws JspTagException; - - public Object next() throws JspTagException; - } - - /* - * Simple implementation of ForEachIterator that adapts from - * an Iterator. This is appropriate for cases where hasNext() and - * next() don't need to throw JspTagException. Such cases are common.core. - */ - - protected class SimpleForEachIterator implements ForEachIterator { - private Iterator i; - - public SimpleForEachIterator(Iterator i) { - this.i = i; - } - - public boolean hasNext() { - return i.hasNext(); - } - - public Object next() { - return i.next(); - } - } - - protected class DeferredForEachIterator implements ForEachIterator { - - private ValueExpression itemsValueExpression; - private IteratedExpression itemsValueIteratedExpression; - private int length = -1; - private int currentIndex = 0; - private boolean isIndexedValueExpression = false; - private boolean anIterator = false; - private Iterator myIterator; - private boolean anEnumeration = false; - private Enumeration myEnumeration; - - public DeferredForEachIterator(ValueExpression o) throws JspTagException { - itemsValueExpression = o; - determineLengthAndType(); - } - - public boolean hasNext() throws JspTagException { - if (isIndexedValueExpression) { - if (currentIndex < length) { - return true; - } else { - return false; - } - } else { - if (length != -1) { - //a Collection, Map, or StringTokenizer - if (currentIndex < length) { - return true; - } else { - return false; - } - } else { - if (anIterator) { - return myIterator.hasNext(); - } else if (anEnumeration) { - return myEnumeration.hasMoreElements(); - } else { - //don't know what this is - return false; - } - } - } - } - - public Object next() throws JspTagException { - ValueExpression nextValue = null; - if (isIndexedValueExpression) { - nextValue = new IndexedValueExpression(itemsValueExpression, currentIndex); - currentIndex++; + @Override + protected void prepare() throws JspTagException { + // produce the right sort of ForEachIterator + if (rawItems == null) { + // if no items were specified, iterate from begin to end + items = new ToEndIterator(end); + } else if (rawItems instanceof ValueExpression) { + deferredExpression = (ValueExpression) rawItems; + Object o = deferredExpression.getValue(pageContext.getELContext()); + Iterator iterator = toIterator(o); + if (isIndexed(o)) { + items = new IndexedDeferredIterator(iterator, deferredExpression); } else { - if (itemsValueIteratedExpression == null) { - itemsValueIteratedExpression = new IteratedExpression(itemsValueExpression, getDelims()); - } - nextValue = new IteratedValueExpression(itemsValueIteratedExpression, currentIndex); - currentIndex++; - if (length != -1) { - //a Collection, Map, or StringTokenizer - //nothing else needed - } else { - //need to increment these guys - if (anIterator) { - myIterator.next(); - } else if (anEnumeration) { - myEnumeration.nextElement(); - } - } + items = new IteratedDeferredIterator(iterator, new IteratedExpression(deferredExpression, getDelims())); } - return nextValue; + } else { + items = toIterator(rawItems); } + } - private void determineLengthAndType() throws JspTagException { - ELContext myELContext = pageContext.getELContext(); - Object o = itemsValueExpression.getValue(myELContext); - if (o instanceof Object[]) { - length = ((Object[]) o).length; - isIndexedValueExpression = true; - } else if (o instanceof boolean[]) { - length = ((boolean[]) o).length; - isIndexedValueExpression = true; - } else if (o instanceof byte[]) { - length = ((byte[]) o).length; - isIndexedValueExpression = true; - } else if (o instanceof char[]) { - length = ((char[]) o).length; - isIndexedValueExpression = true; - } else if (o instanceof short[]) { - length = ((short[]) o).length; - isIndexedValueExpression = true; - } else if (o instanceof int[]) { - length = ((int[]) o).length; - isIndexedValueExpression = true; - } else if (o instanceof long[]) { - length = ((long[]) o).length; - isIndexedValueExpression = true; - } else if (o instanceof float[]) { - length = ((float[]) o).length; - isIndexedValueExpression = true; - } else if (o instanceof double[]) { - length = ((double[]) o).length; - isIndexedValueExpression = true; - } else if (o instanceof Collection) { - length = ((Collection) o).size(); - isIndexedValueExpression = false; - } else if (o instanceof Iterator) { - //have to reproduce iterator here so we can determine the size - isIndexedValueExpression = false; - anIterator = true; - myIterator = (Iterator) o; - } else if (o instanceof Enumeration) { - isIndexedValueExpression = false; - anEnumeration = true; - myEnumeration = (Enumeration) o; - } else if (o instanceof Map) { - length = ((Map) o).size(); - isIndexedValueExpression = false; - // - //else if (o instanceof ResultSet) - // items = toForEachIterator((ResultSet) o); - // - } else if (o instanceof String) { - StringTokenizer st = new StringTokenizer((String) o, ","); - length = st.countTokens(); - isIndexedValueExpression = false; - } else { - // unrecognized type - throw new JspTagException(Resources.getMessage("FOREACH_BAD_ITEMS")); - } + private Iterator toIterator(Object rawItems) throws JspTagException { + if (rawItems instanceof Collection) { + return ((Collection) rawItems).iterator(); + } else if (rawItems.getClass().isArray()) { + return new ArrayIterator(rawItems); + } else if (rawItems instanceof Iterator) { + return (Iterator) rawItems; + } else if (rawItems instanceof Enumeration) { + return new EnumerationIterator((Enumeration) rawItems); + } else if (rawItems instanceof Map) { + return ((Map) rawItems).entrySet().iterator(); + } else if (rawItems instanceof String) { + return new EnumerationIterator(new StringTokenizer((String) rawItems, ",")); + } else { + throw new JspTagException(Resources.getMessage("FOREACH_BAD_ITEMS")); } } - //********************************************************************* - // ForEach-specifc state (protected) - - protected ForEachIterator items; // our 'digested' items - protected Object rawItems; // our 'raw' items - - - //********************************************************************* - // Iteration control methods (based on processed 'items' object) - - // (We inherit semantics and Javadoc from LoopTagSupport.) + private boolean isIndexed(Object o) { + return o.getClass().isArray(); + } @Override protected boolean hasNext() throws JspTagException { @@ -273,35 +100,6 @@ public abstract class ForEachSupport ext } @Override - protected void prepare() throws JspTagException { - // produce the right sort of ForEachIterator - if (rawItems != null) { - if (rawItems instanceof ValueExpression) { - deferredExpression = (ValueExpression) rawItems; - items = toDeferredForEachIterator(deferredExpression); - } else { - // extract an iterator over the 'items' we've got - items = supportedTypeForEachIterator(rawItems); - } - } else { - // no 'items', so use 'begin' and 'end' - items = beginEndForEachIterator(); - } - - /* ResultSet no more supported in <c:forEach> - // step must be 1 when ResultSet is passed in - if (rawItems instanceof ResultSet && step != 1) - throw new JspTagException(Resources.getMessage("FOREACH_STEP_NO_RESULTSET")); - */ - } - - - //********************************************************************* - // Tag logic and lifecycle management - - // Releases any resources we may have (or inherit) - - @Override public void release() { super.release(); items = null; @@ -309,277 +107,125 @@ public abstract class ForEachSupport ext } - //********************************************************************* - // Private generation methods for the ForEachIterators we produce - - /* Extracts a ForEachIterator given an object of a supported type. */ - //This should not be called for a deferred ValueExpression + /** + * Iterator that simply counts up to 'end.' + * Unlike the previous implementation this does not attempt to pre-allocate an array + * containing all values from 0 to 'end' as that can result in excessive memory allocation + * for large values of 'end.' + * LoopTagSupport calls next() 'begin' times in order to discard the initial values, + * In order to maintain this contract, this implementation always starts at 0. + * Future optimization to skip these redundant calls might be possible. + */ + private static class ToEndIterator extends ReadOnlyIterator { + private final int end; + private int i; - protected ForEachIterator supportedTypeForEachIterator(Object o) - throws JspTagException { + private ToEndIterator(int end) { + this.end = end; + } - /* - * This is, of necessity, just a big, simple chain, matching in - * order. Since we are passed on Object because of all the - * various types we support, we cannot rely on the language's - * mechanism for resolving overloaded methods. (Method overloading - * resolves via early binding, so the type of the 'o' reference, - * not the type of the eventual value that 'o' references, is - * all that's available.) - * - * Currently, we 'match' on the object we have through an - * if/else chain that picks the first interface (or class match) - * found for an Object. - */ - - ForEachIterator items; - - if (o instanceof Object[]) { - items = toForEachIterator((Object[]) o); - } else if (o instanceof boolean[]) { - items = toForEachIterator((boolean[]) o); - } else if (o instanceof byte[]) { - items = toForEachIterator((byte[]) o); - } else if (o instanceof char[]) { - items = toForEachIterator((char[]) o); - } else if (o instanceof short[]) { - items = toForEachIterator((short[]) o); - } else if (o instanceof int[]) { - items = toForEachIterator((int[]) o); - } else if (o instanceof long[]) { - items = toForEachIterator((long[]) o); - } else if (o instanceof float[]) { - items = toForEachIterator((float[]) o); - } else if (o instanceof double[]) { - items = toForEachIterator((double[]) o); - } else if (o instanceof Collection) { - items = toForEachIterator((Collection) o); - } else if (o instanceof Iterator) { - items = toForEachIterator((Iterator) o); - } else if (o instanceof Enumeration) { - items = toForEachIterator((Enumeration) o); - } else if (o instanceof Map) { - items = toForEachIterator((Map) o); - } - /* - else if (o instanceof ResultSet) - items = toForEachIterator((ResultSet) o); - */ - else if (o instanceof String) { - items = toForEachIterator((String) o); - } else { - items = toForEachIterator(o); + public boolean hasNext() { + return i <= end; } - return (items); + public Object next() { + if (i <= end) { + return i++; + } else { + throw new NoSuchElementException(); + } + } } - /* - * Creates a ForEachIterator of Integers from 'begin' to 'end' - * in support of cases where our tag handler isn't passed an - * explicit collection over which to iterate. + /** + * Iterator over an Enumeration. */ + private static class EnumerationIterator extends ReadOnlyIterator { + private final Enumeration e; - private ForEachIterator beginEndForEachIterator() { - /* - * To plug into existing support, we need to keep 'begin', 'end', - * and 'step' as they are. So we'll simply create an Integer[] - * from 0 to 'end', inclusive, and let the existing implementation - * handle the subsetting and stepping operations. (Other than - * localizing the cost of creating this Integer[] to the start - * of the operation instead of spreading it out over the lifetime - * of the iteration, this implementation isn't worse than one that - * created new Integers() as needed from next(). Such an adapter - * to ForEachIterator could easily be written but, like I said, - * wouldn't provide much benefit.) - */ - Integer[] ia = new Integer[end + 1]; - for (int i = 0; i <= end; i++) { - ia[i] = new Integer(i); + private EnumerationIterator(Enumeration e) { + this.e = e; } - return new SimpleForEachIterator(Arrays.asList(ia).iterator()); - } - - - //********************************************************************* - // Private conversion methods to handle the various types we support - - protected ForEachIterator toDeferredForEachIterator(ValueExpression o) throws JspTagException { - return new DeferredForEachIterator(o); - } - - // catch-all method whose invocation currently signals a 'matching error' - - protected ForEachIterator toForEachIterator(Object o) - throws JspTagException { - throw new JspTagException(Resources.getMessage("FOREACH_BAD_ITEMS")); - } - - // returns an iterator over an Object array (via List) - - protected ForEachIterator toForEachIterator(Object[] a) { - return new SimpleForEachIterator(Arrays.asList(a).iterator()); - } - // returns an iterator over a boolean[] array, wrapping items in Boolean - - protected ForEachIterator toForEachIterator(boolean[] a) { - Boolean[] wrapped = new Boolean[a.length]; - for (int i = 0; i < a.length; i++) { - wrapped[i] = a[i] ? Boolean.TRUE : Boolean.FALSE; + public boolean hasNext() { + return e.hasMoreElements(); } - return new SimpleForEachIterator(Arrays.asList(wrapped).iterator()); - } - - // returns an iterator over a byte[] array, wrapping items in Byte - protected ForEachIterator toForEachIterator(byte[] a) { - Byte[] wrapped = new Byte[a.length]; - for (int i = 0; i < a.length; i++) { - wrapped[i] = new Byte(a[i]); + public Object next() { + return e.nextElement(); } - return new SimpleForEachIterator(Arrays.asList(wrapped).iterator()); } - // returns an iterator over a char[] array, wrapping items in Character - - protected ForEachIterator toForEachIterator(char[] a) { - Character[] wrapped = new Character[a.length]; - for (int i = 0; i < a.length; i++) { - wrapped[i] = new Character(a[i]); + /** + * Iterator over an array, including arrays of primitive types. + */ + private static class ArrayIterator extends ReadOnlyIterator { + private final Object array; + private final int length; + private int i = 0; + + private ArrayIterator(Object array) { + this.array = array; + length = Array.getLength(array); } - return new SimpleForEachIterator(Arrays.asList(wrapped).iterator()); - } - // returns an iterator over a short[] array, wrapping items in Short - - protected ForEachIterator toForEachIterator(short[] a) { - Short[] wrapped = new Short[a.length]; - for (int i = 0; i < a.length; i++) { - wrapped[i] = new Short(a[i]); + public boolean hasNext() { + return i < length; } - return new SimpleForEachIterator(Arrays.asList(wrapped).iterator()); - } - - // returns an iterator over an int[] array, wrapping items in Integer - protected ForEachIterator toForEachIterator(int[] a) { - Integer[] wrapped = new Integer[a.length]; - for (int i = 0; i < a.length; i++) { - wrapped[i] = new Integer(a[i]); + public Object next() { + try { + return Array.get(array, i++); + } catch (ArrayIndexOutOfBoundsException e) { + throw new NoSuchElementException(); + } } - return new SimpleForEachIterator(Arrays.asList(wrapped).iterator()); } - // returns an iterator over a long[] array, wrapping items in Long + private static class IndexedDeferredIterator extends DeferredIterator { + private final ValueExpression itemsValueExpression; - protected ForEachIterator toForEachIterator(long[] a) { - Long[] wrapped = new Long[a.length]; - for (int i = 0; i < a.length; i++) { - wrapped[i] = new Long(a[i]); + private IndexedDeferredIterator(Iterator iterator, ValueExpression itemsValueExpression) { + super(iterator); + this.itemsValueExpression = itemsValueExpression; } - return new SimpleForEachIterator(Arrays.asList(wrapped).iterator()); - } - // returns an iterator over a float[] array, wrapping items in Float - - protected ForEachIterator toForEachIterator(float[] a) { - Float[] wrapped = new Float[a.length]; - for (int i = 0; i < a.length; i++) { - wrapped[i] = new Float(a[i]); + public Object next() { + iterator.next(); + return new IndexedValueExpression(itemsValueExpression, currentIndex++); } - return new SimpleForEachIterator(Arrays.asList(wrapped).iterator()); } - // returns an iterator over a double[] array, wrapping items in Double + private static class IteratedDeferredIterator extends DeferredIterator { + private final IteratedExpression itemsValueIteratedExpression; - protected ForEachIterator toForEachIterator(double[] a) { - Double[] wrapped = new Double[a.length]; - for (int i = 0; i < a.length; i++) { - wrapped[i] = new Double(a[i]); + private IteratedDeferredIterator(Iterator iterator, IteratedExpression itemsValueIteratedExpression) { + super(iterator); + this.itemsValueIteratedExpression = itemsValueIteratedExpression; } - return new SimpleForEachIterator(Arrays.asList(wrapped).iterator()); - } - - // retrieves an iterator from a Collection - - protected ForEachIterator toForEachIterator(Collection c) { - return new SimpleForEachIterator(c.iterator()); - } - - // simply passes an Iterator through... - - protected ForEachIterator toForEachIterator(Iterator i) { - return new SimpleForEachIterator(i); - } - - // converts an Enumeration to an Iterator via a local adapter - - protected ForEachIterator toForEachIterator(Enumeration e) { - - // local adapter - class EnumerationAdapter implements ForEachIterator { - private Enumeration e; - - public EnumerationAdapter(Enumeration e) { - this.e = e; - } - - public boolean hasNext() { - return e.hasMoreElements(); - } - public Object next() { - return e.nextElement(); - } + public Object next() { + iterator.next(); + return new IteratedValueExpression(itemsValueIteratedExpression, currentIndex++); } - - return new EnumerationAdapter(e); } - // retrieves an iterator over the Map.Entry items in a Map + private abstract static class DeferredIterator extends ReadOnlyIterator { + protected final Iterator iterator; + protected int currentIndex = 0; - protected ForEachIterator toForEachIterator(Map m) { - return new SimpleForEachIterator(m.entrySet().iterator()); - } - - /* No more supported in JSTL. See interface Result instead. - // thinly wraps a ResultSet in an appropriate Iterator - protected ForEachIterator toForEachIterator(ResultSet rs) - throws JspTagException { - - // local adapter - class ResultSetAdapter implements ForEachIterator { - private ResultSet rs; - public ResultSetAdapter(ResultSet rs) { - this.rs = rs; - } - public boolean hasNext() throws JspTagException { - try { - return !(rs.isLast()); // dependent on JDBC 2.0 - } catch (java.sql.SQLException ex) { - throw new JspTagException(ex.getMessage()); - } - } - public Object next() throws JspTagException { - try { - rs.next(); - return rs; - } catch (java.sql.SQLException ex) { - throw new JspTagException(ex.getMessage()); - } - } + protected DeferredIterator(Iterator iterator) { + this.iterator = iterator; } - return new ResultSetAdapter(rs); + public boolean hasNext() { + return iterator.hasNext(); + } } - */ - // tokenizes a String as a CSV and returns an iterator over it - - protected ForEachIterator toForEachIterator(String s) { - StringTokenizer st = new StringTokenizer(s, ","); - return toForEachIterator(st); // convert from Enumeration + private abstract static class ReadOnlyIterator implements Iterator { + public void remove() { + throw new UnsupportedOperationException(); + } } - } --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org