zcommon/src/main/java/org/zkoss/util/CollectionsX.java

Summary

Maintainability
C
1 day
Test Coverage
/* CollectionsX.java

    Purpose:
    Description:
    History:
    2001/09/15 13:46:20, Create, Tom M. Yeh.

Copyright (C) 2001 Potix Corporation. All Rights Reserved.

{{IS_RIGHT
    This program is distributed under LGPL Version 2.1 in the hope that
    it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.zkoss.util;

import java.lang.reflect.Array;
import java.util.AbstractCollection;
import java.util.Collection;
import java.util.Set;
import java.util.Map;
import java.util.AbstractList;
import java.util.LinkedList;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Enumeration;
import java.util.NoSuchElementException;

import org.zkoss.lang.Strings;

/**
 * The collection related utilities.
 *
 * @author tomyeh
 * @see java.util.Collections
 */
public class CollectionsX {
    /** An enumeration on top of a collection or iterator.
     */
    public static final class CollectionEnumeration<E> implements Enumeration<E> {
        private final Iterator<? extends E> _iter;
        public CollectionEnumeration(Collection<? extends E> c) {
            this(c != null ? c.iterator(): null);
        }
        public CollectionEnumeration(Iterator<? extends E> iter) {
            _iter = iter;
        }
        public final boolean hasMoreElements() {
            return _iter != null && _iter.hasNext();
        }
        public final E nextElement() {
            if (_iter != null)
                return _iter.next();
            throw new NoSuchElementException();
        }
    }
    /** An enumeration on top of an array.
     */
    public static final class ArrayEnumeration<E> implements Enumeration<E> {
        private final Object[] _ary;
        private int _cursor = 0;
        public ArrayEnumeration(Object[] ary) {
            _ary = ary;
        }
        public final boolean hasMoreElements() {
            return _ary != null && _cursor < _ary.length;
        }
        @SuppressWarnings("unchecked")
        public final E nextElement() {
            if (hasMoreElements())
                return (E)_ary[_cursor++];
            throw new NoSuchElementException();
        }
    }
    /** An enumeration that enumerates one element.
     */
    public static final class OneEnumeration<E> implements Enumeration<E> {
        private boolean _nomore;
        private final E _one;
        public OneEnumeration(E one) {
            _one = one;
        }
        public final boolean hasMoreElements() {
            return !_nomore;
        }
        public final E nextElement() {
            if (_nomore)
                throw new NoSuchElementException();
            _nomore = true;
            return _one;
        }
    }
    /** An readonly collection on top of an array.
     */
    public static final class ArrayCollection<E> extends AbstractCollection<E> {
        private final Object[] _ary;
        public ArrayCollection(Object[] ary) {
            _ary = ary;
        }
        public final int size() {
            return _ary != null ? _ary.length: 0;
        }
        public Iterator<E> iterator() {
            return new ArrayIterator<E>(_ary);
        }
    }
    /** An readonly list on top of an array.
     */
    public static final class ArrayList<E> extends AbstractList<E> {
        private final Object[] _ary;
        public ArrayList(Object[] ary) {
            _ary = ary;
        }
        public final int size() {
            return _ary != null ? _ary.length: 0;
        }
        @SuppressWarnings("unchecked")
        public final E get(int index) {
            return (E)_ary[index];
        }
    }
    /** An iterator on top of an array.
     */
    public static class ArrayIterator<E> implements Iterator<E> {
        /*package*/ final Object[] _ary;
        /*package*/ int _cursor = 0, _last = -1;
        /** @param ary an array or null. */
        public ArrayIterator(Object[] ary) {
            _ary = ary;
        }
        public final boolean hasNext() {
            return _ary != null && _cursor < _ary.length;
        }
        @SuppressWarnings("unchecked")
        public final E next() {
            if (hasNext())
                return (E)_ary[_last = _cursor++];
            throw new NoSuchElementException("cursor="+_cursor);
        }
        public final void remove() {
            throw new UnsupportedOperationException();
        }
    }
    public static class ArrayListIterator<E> extends ArrayIterator<E>
    implements ListIterator<E> {
        /** @param ary an array or null. */
        public ArrayListIterator(Object[] ary) {
            super(ary);
        }
        /** @param ary an array or null. */
        public ArrayListIterator(E[] ary, int index) {
            super(ary);
            _cursor = index;
            final int len = _ary != null ? _ary.length: 0;
            if (_cursor < 0 || _cursor > len)
                throw new IndexOutOfBoundsException("index="+index+" but len="+len);
        }
        public final boolean hasPrevious() {
            return _ary != null && _cursor > 0;
        }
        @SuppressWarnings("unchecked")
        public final E previous() {
            if (hasPrevious())
                return (E)_ary[_last = --_cursor];
            throw new NoSuchElementException("cursor="+_cursor);
        }
        public final int nextIndex() {
            return _cursor;
        }
        public final int previousIndex() {
            return _cursor - 1;
        }
        public final void set(E o) {
            if (_last < 0)
                throw new IllegalStateException("neither next nor previous have been called");
            _ary[_last] = o;
        }
        public final void add(E o) {
            throw new UnsupportedOperationException();
        }
    }
    /** A collection that contains only one element.
     */
    public static final class OneCollection<E> extends AbstractCollection<E> {
        private final E _one;
        public OneCollection(E one) {
            _one = one;
        }
        public final int size() {
            return 1;
        }
        public Iterator<E> iterator() {
            return new OneIterator<E>(_one);
        }
    }
    /** An iterator that iterates one element.
     */
    public static final class OneIterator<E> implements Iterator<E> {
        private boolean _nomore;
        private final E _one;
        public OneIterator(E one) {
            _one = one;
        }
        public final boolean hasNext() {
            return !_nomore;
        }
        public final E next() {
            if (_nomore)
                throw new NoSuchElementException();
            _nomore = true;
            return _one;
        }
        public final void remove() {
            throw new UnsupportedOperationException();
        }
    }
    /** An iterator that iterates thru an Enumeration.
     */
    public static final class EnumerationIterator<E> implements Iterator<E> {
        private final Enumeration<? extends E> _enm;
        /**
         * @param enm the enumeration. If null, it means empty.
         */
        public EnumerationIterator(Enumeration<? extends E> enm) {
            _enm = enm;
        }
        public final boolean hasNext() {
            return _enm != null && _enm.hasMoreElements();
        }
        public final E next() {
            if (_enm == null)
                throw new NoSuchElementException();
            return _enm.nextElement();
        }
        public final void remove() {
            throw new UnsupportedOperationException();
        }
    };

    /** Empty iterator.
     */
    public static final Iterator EMPTY_ITERATOR =
        new Iterator() {
            public final boolean hasNext() {
                return false;
            }
            public final Object next() {
                throw new NoSuchElementException();
            }
            public final void remove() {
                throw new IllegalStateException();
            }
        };
    /** Empty enumeration.
     */
    public static final Enumeration EMPTY_ENUMERATION =
        new Enumeration() {
            public final boolean hasMoreElements() {
                return false;
            }
            public final Object nextElement() {
                throw new NoSuchElementException();
            }
        };

    /** Returns a generic empty iterator.
     * @since 6.0.0
     */
    @SuppressWarnings("unchecked")
    public static final <T> Iterator<T> emptyIterator() {
        return EMPTY_ITERATOR;
    }
    /** Returns a generic empty enumeration.
     * @since 6.0.0
     */
    @SuppressWarnings("unchecked")
    public static final <T> Enumeration<T> emptyEnumeration() {
        return EMPTY_ENUMERATION;
    }

    /** Returns an empty iterable object.
     * @since 6.0.0
     */
    @SuppressWarnings("unchecked")
    public static final <T> Iterable<T> emptyIterable() {
        return EMPTY_ITERABLE;
    }
    private static final Iterable EMPTY_ITERABLE = new Iterable() {
        public Iterator iterator() {
            return EMPTY_ITERATOR;
        };
    };

    /**
     * Returns the specified range of the specified collection into a new array.
     * The initial index of the range (<tt>from</tt>) must lie between zero
     * and <tt>col.size</tt>, inclusive. 
     * The final index of the range (<tt>to</tt>), which must be greater than or
     * equal to <tt>from</tt>.
     * <p>The returned array will be "safe" in that no references to it are
     * maintained by this list.  (In other words, this method must allocate
     * a new array).  The caller is thus free to modify the returned array.
     *
     * <p>This method acts as bridge between array-based and collection-based
     * APIs.
     * @param col the collection to be copied.
     * @param from the initial index of the range to be copied, inclusive.
     * @param to the final index of the range to be copied, exclusive.
     * @since 3.0.6
     */
    @SuppressWarnings("unchecked")
    public static final Object[] toArray(Collection col, int from, int to) {
        return toArray(col, new Object[0], from, to);
    }
    /**
     * Returns the specified range of the given collection;
     * the runtime type of the returned array is that of the specified array.
     * @param col the collection to be copied.
     * @param dst the array into which the elements of this list are to be stored,
     * if it is big enough (less than <code>to - from</code>; otherwise, a new array of the same runtime type is allocated for this purpose. 
     * Note: dst[0] will be col[from].
     * @param from the initial index of the range to be copied, inclusive.
     * @param to the final index of the range to be copied, exclusive.
     * @since 6.0.0
     */
    @SuppressWarnings("unchecked")
    public static final <T> T[] toArray(Collection<? extends T> col, T[] dst, int from, int to) {
        int sz = col.size();
        if (to > sz) to = sz;
        if (from < 0) from = 0;

        int newLength = to - from;
        if (newLength < 0)
            throw new IllegalArgumentException(from + " > " + to);

        if (dst.length < newLength)
            dst = (T[])Array.newInstance(dst.getClass().getComponentType(), newLength);

        int i = 0, j = 0;
        for (Iterator<? extends T> it = col.iterator(); it.hasNext() && i < dst.length;) {
            if (j++ < from) {
                it.next();
                continue;
            }
            dst[i++] = it.next();
        }
        return dst;
    }

    /** Adds all elements returned by the iterator to a collection.
     * @param iter the iterator; null is OK
     * @return the number element being added
     */
    public static final <T> int addAll(Collection<T> col, Iterator<? extends T> iter) {
        int cnt = 0;
        if (iter != null)
            for (; iter.hasNext(); ++cnt)
                col.add(iter.next());
        return cnt;
    }
    /** Adds all elements returned by the enumerator to a collection.
     * @param enm the enumeration; null is OK
     * @return the number element being added
     */
    public static final <T> int addAll(Collection<T> col, Enumeration<? extends T> enm) {
        int cnt = 0;
        if (enm != null)
            for (; enm.hasMoreElements(); ++cnt)
                col.add(enm.nextElement());
        return cnt;
    }
    /** Adds all elements of an array to a collection.
     * @param ary the array; null is OK
     * @return the number element being added
     */
    @SuppressWarnings("unchecked")
    public static final <T> int addAll(Collection<T> col, Object[] ary) {
        int cnt = 0;
        if (ary != null)
            for (; cnt < ary.length; ++cnt)
                col.add((T)ary[cnt]);
        return cnt;
    }

    /** Tests whether two sets has any intersection.
     */
    public static final boolean isIntersected(Set<?> a, Set<?> b) {
        final int sza = a != null ? a.size(): 0;
        final int szb = b != null ? b.size(): 0;
        if (sza == 0 || szb == 0)
            return false;

        final Set large, small;
        if (sza > szb) {
            large = a;
            small = b;
        } else {
            large = b;
            small = a;
        }
        for (final Iterator it = small.iterator(); it.hasNext();)
            if (large.contains(it.next()))
                return true;
        return false;
    }
    /**
     * Parses a string into a list.
     * It is the same as parse(c, src, separator, true, false).
     * Refer to {@link #parse(Collection, String, char, boolean, boolean)}
     * for details.
     *
     * @exception IllegalSyntaxException if syntax errors
     * @see Maps#parse
     * @see #parse(Collection, String, char, boolean, boolean)
     */
    public static final Collection<String>
    parse(Collection<String> c, final String src, char separator) {
        return parse(c, src, separator, true);
    }
    /**
     * Parses a string into a list.
     * It is the same as parse(c, src, separator, escBackslash, false).
     * Refer to {@link #parse(Collection, String, char, boolean, boolean)}
     * for details.
     *
     * @exception IllegalSyntaxException if syntax errors
     * @see Maps#parse
     * @see #parse(Collection, String, char, boolean, boolean)
     */
    public static final
    Collection<String> parse(Collection<String> c, final String src,
    char separator, boolean escBackslash) {
        return parse(c, src, separator, escBackslash, false);
    }
    /**
     * Parses a string into a list.
     * To quote a string, both '\'' and '"' are OK.
     * Whitespaces are trimmed between quotation and separators.
     *
     * <p>Unlike Java, quotation could spread over multiple lines.
     *
     * <p>Example,<br>
     * a b , ' c d',"'f'", '1' "2", 3<br>
     * generate a list of "a b", "c d", "'f'", "1", "2" and "3".
     * Note: the separator between "1" and "2" is optional.
     *
     * <p>Note: Like Java, if the string is ending with a separator,
     * it will be ignored.
     *
     * <p>Example,<br>
     * a, , b, <br>
     * generate a list of "a", "", "b".
     *
     * @param c the collection to hold the parsed results; a linked list
     * is created if c is null.
     * @param src the string to parse
     * @param separator the separator, e.g., ',', '\n' or ' '.
     * Note: if separator is ' ', it denotes any white space.
     * @param escBackslash whether to treat '\\' specially (as escape char)
     * @return the <code>c</code> collection if not null; or a linked list
     * if c is null (so you can cast it to List)
     * @param parenthesis whether to parse parenthesis in the value, {}, () and [].
     * If true, the separator is ignored inside the parenthesis.
     * Specify true if the value might contain EL expressions.
     *
     * @exception IllegalSyntaxException if syntax errors
     * @see Maps#parse
     * @since 3.0.6
     */
    public static final Collection<String> parse(Collection<String> c, final String src,
    char separator, boolean escBackslash, boolean parenthesis) {
        if (c == null)
            c = new LinkedList<String>();

        final char[] seps = new char[] {separator};
        int j = 0;
        for (Strings.Result res;
        (res = Strings.nextToken(src, j, seps, escBackslash, true, parenthesis)) != null;
        j = res.next) {
            assert res.token != null;
            c.add(res.token);
        }
        return c;
    }

    /**
     * Based on the given collection type of Object, return an iterator. The
     * Collection type of object can be Collection, Map (return the entry), or
     * Array.
     */
    @SuppressWarnings("unchecked")
    public static final Iterator iterator(Object obj) {
        if (obj instanceof Object[]) {
            return new ArrayIterator((Object[])obj);
        } else if (obj instanceof Collection) {
            return ((Collection)obj).iterator();
        } else if (obj instanceof Map) {
            return ((Map)obj).entrySet().iterator();
        }
        throw new IllegalArgumentException("obj must be a Collection, a Map, or an Object array. obj: "+obj);
    }

    /** Returns an iterator that allows the caller to modify the collection
     * directly (in addition to Iterator.remove()).
     * In other words, the iterator will handle
     * java.util.ConcurrentModificationException and return the items one-by-one
     * even if collection's add, remove or other method was called
     * (which is not allowed by a regular iterator).
     * <p>Limitation:
     * <ul><li>It is still not safe to modify the collection
     * between hasNext() and next().Modify it after next(), if necessary.</li>
     * <li>If the collection have multiple items referencing to the same object,
     * and if we remove the item that has been iterated, the other item might
     * be ignored. For example, if a collection has A, B, C, B, then the
     * following code only iterates A, B, C</li>
     * </ul>
     * <pre><code>
    for (Iterator it = org.zkoss.util.CollectionsX.comodifiableIterator(list); it.hasNext();) {
        it.next();
        l.remove(0);
    }</code></pre>
     * @since 5.0.6
     */
    public static <E> Iterator<E> comodifiableIterator(Collection<E> col) {
        return new ComodifiableIterator<E, E>(col, null);
    }
    /** Returns an iterator that allows the caller to modify the collection
     * directly (in addition to Iterator.remove()).
     * @param converter the filter used to convert the value of each element
     * found in the given collection to the return iterator.
     * Ignored if null (and then F must be the same as T or extends from T).
     * @since 6.0.0
     */
    public static <F, T> Iterator<T> comodifiableIterator(Collection<F> col, 
            Converter<F, T> converter) {
        return new ComodifiableIterator<F, T>(col, converter);
    }
}