All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jopendocument.util.CollectionUtils Maven / Gradle / Ivy

Go to download

jOpenDocument is a free library for developers looking to use Open Document files without OpenOffice.org.

The newest version!
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 2008-2013 jOpenDocument, by ILM Informatique. All rights reserved.
 * 
 * The contents of this file are subject to the terms of the GNU
 * General Public License Version 3 only ("GPL").  
 * You may not use this file except in compliance with the License. 
 * You can obtain a copy of the License at http://www.gnu.org/licenses/gpl-3.0.html
 * See the License for the specific language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each file.
 * 
 */

package org.jopendocument.util;

import org.jopendocument.util.cc.IClosure;
import org.jopendocument.util.cc.IPredicate;
import org.jopendocument.util.cc.ITransformer;
import org.jopendocument.util.cc.IdentityHashSet;
import org.jopendocument.util.cc.IdentitySet;

import java.io.Serializable;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.RandomAccess;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Pattern;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.TransformerUtils;

/**
 * Une classe regroupant des méthodes utilitaires pour les collections.
 * 
 * @author ILM Informatique 30 sept. 2004
 */
public class CollectionUtils {

    /**
     * Concatene une collection. Cette méthode va appliquer un transformation sur chaque élément
     * avant d'appeler toString(). join([-1, 3, 0], " ,", doubleTransformer) == "-2, 6, 0"
     * 
     * @param  type of items
     * @param c la collection a concaténer.
     * @param sep le séparateur entre chaque élément.
     * @param tf la transformation à appliquer à chaque élément.
     * @return la chaine composée de chacun des éléments séparés par sep.
     */
    static public final  String join(final Collection c, final String sep, final ITransformer tf) {
        final int size = c.size();
        if (size == 0)
            return "";

        final StringBuffer res = new StringBuffer(size * 4);
        if (c instanceof RandomAccess && c instanceof List) {
            final List list = (List) c;
            for (int i = 0; i < size; i++) {
                res.append(tf.transformChecked(list.get(i)));
                if (i < size - 1)
                    res.append(sep);
            }
        } else {
            final Iterator iter = c.iterator();
            while (iter.hasNext()) {
                final E elem = iter.next();
                res.append(tf.transformChecked(elem));
                if (iter.hasNext())
                    res.append(sep);
            }
        }
        return res.toString();
    }

    /**
     * Concatene une collection en appelant simplement toString() sur chaque élément.
     * 
     * @param  type of collection
     * @param c la collection a concaténer.
     * @param sep le séparateur entre chaque élément.
     * @return la chaine composée de chacun des éléments séparés par sep.
     * @see #join(Collection, String, ITransformer)
     */
    static public  String join(Collection c, String sep) {
        return join(c, sep, org.jopendocument.util.cc.Transformer. nopTransformer());
    }

    static public > C transform(final Collection c, final ITransformer transf, final C res) {
        return transformAndFilter(c, transf, IPredicate.truePredicate(), res);
    }

    static public > C transformAndFilter(final Collection c, final ITransformer transf, final IPredicate filterAfter, final C res) {
        return filterTransformAndFilter(c, IPredicate.truePredicate(), transf, filterAfter, res);
    }

    static public > C filterAndTransform(final Collection c, final IPredicate filterBefore, final ITransformer transf, final C res) {
        return filterTransformAndFilter(c, filterBefore, transf, IPredicate.truePredicate(), res);
    }

    static public > C filterTransformAndFilter(final Collection c, final IPredicate filterBefore, final ITransformer transf,
            final IPredicate filterAfter, final C res) {
        iterate(c, filterBefore, new IClosure() {
            @Override
            public void executeChecked(T input) {
                final U item = transf.transformChecked(input);
                if (filterAfter.evaluateChecked(item))
                    res.add(item);
            }
        });
        return res;
    }

    static public  void iterate(final Collection c, final IClosure cl) {
        iterate(c, IPredicate.truePredicate(), cl);
    }

    static public  void iterate(final Collection c, final IPredicate filterBefore, final IClosure cl) {
        if (c instanceof RandomAccess && c instanceof List) {
            final List list = (List) c;
            final int size = c.size();
            for (int i = 0; i < size; i++) {
                final T item = list.get(i);
                if (filterBefore.evaluateChecked(item))
                    cl.executeChecked(item);
            }
        } else {
            final Iterator iter = c.iterator();
            while (iter.hasNext()) {
                final T item = iter.next();
                if (filterBefore.evaluateChecked(item))
                    cl.executeChecked(item);
            }
        }
    }

    private static final Pattern COMMA = Pattern.compile("\\p{Space}*,\\p{Space}*");

    static public List split(String s) {
        return split(s, COMMA);
    }

    static public List split(String s, String sep) {
        return split(s, Pattern.compile(sep));
    }

    /**
     * Split a string into a list based on a pattern.
     * 
     * @param s the string to split.
     * @param pattern the pattern where to cut the string.
     * @return the splitted string, empty list if s is "".
     */
    static public List split(String s, Pattern pattern) {
        return s.length() == 0 ? Collections. emptyList() : Arrays.asList(pattern.split(s));
    }

    /**
     * Return an index between 0 and l.size() inclusive. If i
     * is negative, it is added to l.size() (but bounded to 0), ie for a list of 3
     * items, -1 is the index of the last item ; -3 and -4 are both the first. If i is
     * greater than l.size() then l.size() is returned.
     * 
     * 
     *    a  b  c  a  b  c
     *   -3 -2 -1  0  1  2  3
     * 
* * @param l the list, eg a list of 3 items. * @param i the virtual index, eg -1. * @return the real index, eg 2. */ static public int getValidIndex(final List l, final int i) { return getValidIndex(l, i, false); } static public int getValidIndex(final List l, final int i, final boolean strict) { final int size = l.size(); if (i > size) { if (strict) throw new IndexOutOfBoundsException("Too high : " + i + " > " + size); return size; } else if (i < -size) { if (strict) throw new IndexOutOfBoundsException("Too low : " + i + " < " + -size); return 0; } else if (i >= 0) { return i; } else { return size + i; } } /** * Deletes a slice of a list. Pass indexes to {@link #getValidIndex(List, int)} to allow * delete(l, 0, -1) to clear l or delete(l, -2, -2) to remove the penultimate item. * * @param l the list to delete from. * @param from the first index to be removed (inclusive). * @param to the last index to be removed (inclusive). */ static public void delete(List l, int from, int to) { if (!l.isEmpty()) l.subList(getValidIndex(l, from), getValidIndex(l, to) + 1).clear(); } /** * Deletes the tail of a list. The resulting list will have a size of from. * * @param l the list to delete from. * @param from the first index to be removed (inclusive). */ static public void delete(List l, int from) { delete(l, from, -1); } public static void filter(Collection collection, IPredicate predicate) { org.apache.commons.collections.CollectionUtils.filter(collection, predicate); } public static boolean exists(Collection collection, IPredicate predicate) { return org.apache.commons.collections.CollectionUtils.exists(collection, predicate); } /** * Permet d'organiser une collection en une hiérarchie à l'aide de Map. Avec * Col = [ * Obs1(bat=BAT A, local=A1, num=1), * Obs2(bat=BAT B, local=B1, num=2), * Obs3(bat=BAT B, local=B2, num=3), * Obs4(bat=BAT B, local=B2, num=4) * ] * * ainsi que deux extracteurs pour trouver le batiment et le local, et enfin itemOrdering suivant le numero, on a * { BAT A => {A1 => {Obs1}}, {BAT B => {B1 => {Obs2}, B2 => {Obs3, Obs4}}}}. * * @param col la collection à organiser. * @param propExtractors les extracteurs de propriétes. * @param propComp les Comparator pour les propriétés renvoyées par les extracteurs, peut être * null si les propriétés sont des Comparable. * @param itemOrdering comment ordonner les éléments dans la dernière tranche, peut être * null si les éléments sont des Comparable. * @return une hiérarchie de SortedMap et en dernier un SortedSet. */ static public final SortedMap organize(Collection col, List propExtractors, List propComp, Comparator itemOrdering) { if (propExtractors.size() == 0) throw new IllegalArgumentException("Empty property extractors"); if (propComp == null) propComp = Collections.nCopies(propExtractors.size(), null); else if (propExtractors.size() != propComp.size()) throw new IllegalArgumentException("Size mismatch between " + propExtractors + " and " + propComp); final SortedMap res = new TreeMap(propComp.get(0)); Iterator iter = col.iterator(); while (iter.hasNext()) { final Object item = iter.next(); Map m = res; for (int i = 0; i < propExtractors.size() - 1; i++) { final Transformer extractor = propExtractors.get(i); final Object property = extractor.transform(item); Map newM = (Map) m.get(property); if (newM == null) { newM = new TreeMap(propComp.get(i + 1)); m.put(property, newM); } m = newM; } final Object property = propExtractors.get(propExtractors.size() - 1).transform(item); SortedSet s = (SortedSet) m.get(property); if (s == null) { s = new TreeSet(itemOrdering); m.put(property, s); } s.add(item); } return res; } /** * Permet d'aplatir une hiérarchie. Exemple : * *
     *   A-
     *      A1
     *      A2
     *   B-
     *      B1
     *         B11
     *         B12
     * 
* * devient [A, A1, A2, B, B1, B11, B12]. * * @param hierarchy la hiérarchie à aplatir. * @param itemTransf la transformation à faire sur les feuilles. * @return la liste correspondante. */ static public final List flatten(Map hierarchy, Transformer itemTransf) { final List res = new ArrayList(); final Iterator iter = hierarchy.keySet().iterator(); while (iter.hasNext()) { final Object obj = iter.next(); res.add(obj); final Object value = hierarchy.get(obj); if (value instanceof Map) res.addAll(flatten((Map) value, itemTransf)); else if (value instanceof Collection) { final Collection items = (Collection) value; final Iterator itemIter = items.iterator(); while (itemIter.hasNext()) { final Object item = itemIter.next(); res.add(itemTransf.transform(item)); } } else throw new IllegalArgumentException("Illegal value: " + value); } return res; } /** * Permet d'aplatir une hiérarchie. * * @param hierarchy la hiérarchie à aplatir. * @return la liste correspondante. */ static public final List flatten(Map hierarchy) { return flatten(hierarchy, TransformerUtils.nopTransformer()); } /** * Convertit une map en 2 listes, une pour les clefs, une pour les valeurs. * * @param map la Map à convertir. * @return un tuple de 2 List, en 0 les clefs, en 1 les valeurs. * @param type of key * @param type of value */ static public Tuple2, List> mapToLists(Map map) { final List keys = new ArrayList(map.size()); final List vals = new ArrayList(map.size()); for (final Map.Entry e : map.entrySet()) { keys.add(e.getKey()); vals.add(e.getValue()); } return Tuple2.create(keys, vals); } /** * Add entries from toAdd into map only if the key is not already * present. * * @param type of keys. * @param type of values. * @param map the map to fill. * @param toAdd the entries to add. * @return map. */ static public Map addIfNotPresent(Map map, Map toAdd) { for (final Map.Entry e : toAdd.entrySet()) { if (!map.containsKey(e.getKey())) map.put(e.getKey(), e.getValue()); } return map; } /** * Compute the index that have changed (added or removed) between 2 lists. One of the lists MUST * be a sublist of the other, ie the to go from one to the other we just add or remove items but * we don't do both. * * @param oldList the first list. * @param newList the second list. * @return a list of Integer. * @param type of item * @throws IllegalStateException if one list is not a sublist of the other. */ static public List getIndexesChanged(List oldList, List newList) { final List longer; final List shorter; if (newList.size() > oldList.size()) { longer = new ArrayList(newList); shorter = new ArrayList(oldList); } else { longer = new ArrayList(oldList); shorter = new ArrayList(newList); } final List res = new ArrayList(); int offset = 0; while (shorter.size() > 0) { if (longer.size() < shorter.size()) throw new IllegalStateException(shorter + " is not a sublist of " + longer); // compare nulls if (CompareUtils.equals(shorter.get(0), longer.get(0))) { shorter.remove(0); longer.remove(0); } else { longer.remove(0); res.add(offset); } offset++; } for (int i = 0; i < longer.size(); i++) { res.add(i + offset); } return res; } /** * Aggregate a list of ints into a list of intervals. Eg aggregate([-1,0,1,2,5]) returns * [[-1,2], [5,5]]. * * @param ints a list of Integer strictly increasing. * @return a list of int[2]. */ static public List aggregate(Collection ints) { final List res = new ArrayList(); int[] currentInterval = null; for (final Number n : ints) { final int index = n.intValue(); if (currentInterval == null || index != currentInterval[1] + 1) { currentInterval = new int[2]; currentInterval[0] = index; currentInterval[1] = currentInterval[0]; res.add(currentInterval); } else { currentInterval[1] = index; } } return res; } /** * Test whether col2 is contained in col1. * * @param type of collection * @param col1 the first collection * @param col2 the second collection * @return null if col1 contains all of col2, else return the extra items that col2 * have. */ static public Set contains(final Set col1, final Set col2) { if (col1.containsAll(col2)) return null; else { final Set names = new HashSet(col2); names.removeAll(col1); return names; } } static public > boolean containsAny(final C coll1, final C coll2) { return org.apache.commons.collections.CollectionUtils.containsAny(coll1, coll2); } /** * Convert an array to a list of a different type. * * @param type of array * @param type of list * @param array the array to convert, eg new Object[]{"a", "b"}. * @param clazz the class of the list items, eg String.class. * @return all items of array into a list, eg ["a", "b"]. * @throws ClassCastException if some item of array is not a T. */ static public List castToList(U[] array, Class clazz) throws ClassCastException { final List res = new ArrayList(array.length); for (final U item : array) { res.add(clazz.cast(item)); } return res; } /** * The number of equals item between a and b, starting from the end. * * @param type of items. * @param a the first list, eg [a, b, c]. * @param b the second list, eg [a, null, z, c]. * @return the number of common items, eg 1. */ public static int equalsFromEnd(final List a, final List b) { return equals(a, b, true, null); } public static int equalsFromStart(final List a, final List b) { return equals(a, b, false, null); } /** * The number of equals item between a and b, starting from the choosen end. * * @param type of the first list. * @param type of the second list. * @param a the first list, eg [a, b, c]. * @param b the second list, eg [a, null, z, c]. * @param fromEnd whether search from the start or the end, true. * @param transf how items of a should be transformed before being compared, can be * null. * @return the number of common items, eg 1. */ public final static int equals(final List a, final List b, boolean fromEnd, ITransformer transf) { final int sizeA = a.size(); final int sizeB = b.size(); final int lastI = Math.min(sizeA, sizeB); for (int i = 0; i < lastI; i++) { final A itemA = a.get(fromEnd ? sizeA - 1 - i : i); final B itemB = b.get(fromEnd ? sizeB - 1 - i : i); if (!CompareUtils.equals(transf == null ? itemA : transf.transformChecked(itemA), itemB)) return i; } return lastI; } public static Collection select(final Collection a, final IPredicate pred) { return select(a, pred, new ArrayList()); } public static > C select(final Collection a, final IPredicate pred, final C b) { for (final T item : a) if (pred.evaluateChecked(item)) b.add(item); return b; } // avoid name collision causing eclipse bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=319603 @SuppressWarnings("unchecked") public static Collection intersection(final Collection a, final Collection b) { return org.apache.commons.collections.CollectionUtils.intersection(a, b); } /** * Compute the intersection of a and b. nulls are ignored : x ∩ null = x. * * @param type of collection. * @param a the first set, can be null. * @param b the second set, can be null. * @return the intersection. */ @SuppressWarnings("unchecked") public static Set inter(final Set a, final Set b) { return (Set) interSubtype(a, b); } public static Set interSubtype(final Set a, final Set b) { if (a == b) return a; else if (a == null) return b; else if (b == null) return a; else if (a.size() > b.size()) { return interSubtype(b, a); } final Set res = new HashSet(); for (final T item : a) { if (b.contains(item)) res.add(item); } return res; } public static Set inter(final Set... sets) { return inter(Arrays.asList(sets)); } public static Set inter(final List> sets) { final List> mutable = new ArrayList>(sets.size()); for (final Set s : sets) { // ignore nulls if (s != null) mutable.add(s); } if (mutable.isEmpty()) return null; else if (mutable.size() == 1) return mutable.get(0); final int indexMin = indexOfMinSize(mutable); if (indexMin != 0) { mutable.add(0, mutable.remove(indexMin)); return inter(mutable); } if (mutable.get(0).isEmpty()) return Collections.emptySet(); // replace the first 2 by their intersection // (inter will swap as appropriate if java doesn't evalute args in source order) mutable.add(0, inter(mutable.remove(0), mutable.remove(0))); return inter(mutable); } private static final int indexOfMinSize(final List> sets) { if (sets.isEmpty()) throw new IllegalArgumentException("empty sets"); int res = 0; for (int i = 1; i < sets.size(); i++) { if (sets.get(i).size() < sets.get(res).size()) res = i; } return res; } /** * Returns a {@link Set} containing the union of the given {@link Set}s. * * @param type of items. * @param a the first set, must not be null * @param b the second set, must not be null * @return the union of the two. */ public static Set union(final Set a, final Set b) { final Set res = new HashSet(a); if (a != b) res.addAll(b); return res; } @SuppressWarnings("unchecked") public static Collection subtract(final Collection a, final Collection b) { return org.apache.commons.collections.CollectionUtils.subtract(a, b); } @SuppressWarnings("unchecked") public static Collection substract(final Collection a, final Collection b) { return org.apache.commons.collections.CollectionUtils.subtract(a, b); } /** * Return the first item of l if it's the only one, otherwise null. * * @param type of list. * @param l the list. * @return the first item of l or null. */ public static T getSole(List l) { return l.size() == 1 ? l.get(0) : null; } public static T getSole(Collection l) { return l.size() == 1 ? l.iterator().next() : null; } public static T getFirst(Collection l) { return l.size() > 0 ? l.iterator().next() : null; } /** * Return the first item of l if it isn't empty, otherwise null. * * @param type of list. * @param l the list. * @return the first item of l or null. */ public static T getFirst(List l) { return getNoExn(l, 0); } /** * Return the last item of l if it isn't empty, otherwise null. * * @param type of list. * @param l the list. * @return the last item of l or null. */ public static T getLast(List l) { return getNoExn(l, l.size() - 1); } /** * Return the item no index of l if it exists, otherwise * null. * * @param type of list. * @param l the list. * @param index the wanted index. * @return the corresponding item of l or null. */ public static T getNoExn(List l, int index) { return index >= 0 && index < l.size() ? l.get(index) : null; } @SuppressWarnings("rawtypes") private static final Iterator EMPTY_ITERATOR = new Iterator() { @Override public boolean hasNext() { return false; } @Override public Object next() { throw new NoSuchElementException(); } @Override public void remove() { throw new UnsupportedOperationException(); } }; @SuppressWarnings("unchecked") public static Iterator emptyIterator() { return (Iterator) EMPTY_ITERATOR; } public static LinkedList toLinkedList(final Iterator iter) { return addTo(iter, new LinkedList()); } public static ArrayList toArrayList(final Iterator iter, final int estimatedSize) { return addTo(iter, new ArrayList(estimatedSize)); } public static > C addTo(final Iterator iter, final C c) { while (iter.hasNext()) c.add(iter.next()); return c; } public static ListIterator getListIterator(final List l, final boolean reversed) { if (!reversed) return l.listIterator(); return reverseListIterator(l.listIterator(l.size())); } public static ListIterator reverseListIterator(final ListIterator listIter) { if (listIter instanceof ReverseListIter) return ((ReverseListIter) listIter).listIter; else return new ReverseListIter(listIter); } private static final class ReverseListIter implements ListIterator { private final ListIterator listIter; private ReverseListIter(ListIterator listIter) { this.listIter = listIter; } @Override public boolean hasNext() { return this.listIter.hasPrevious(); } @Override public T next() { return this.listIter.previous(); } @Override public boolean hasPrevious() { return this.listIter.hasNext(); } @Override public T previous() { return this.listIter.next(); } @Override public int nextIndex() { return this.listIter.previousIndex(); } @Override public int previousIndex() { return this.listIter.nextIndex(); } @Override public void remove() { this.listIter.remove(); } @Override public void set(T e) { this.listIter.set(e); } @Override public void add(T e) { throw new UnsupportedOperationException(); } } public static Set createSet(T... items) { return new HashSet(Arrays.asList(items)); } public static IdentitySet createIdentitySet(T... items) { return new IdentityHashSet(Arrays.asList(items)); } /** * Return an {@link IdentitySet} consisting of items. * * @param items the collection whose elements are to be in the result. * @return a set, possibly items if it's already an identity set. */ public static Set toIdentitySet(Collection items) { if (items instanceof IdentitySet) return (Set) items; else return new IdentityHashSet(items); } @SuppressWarnings("rawtypes") private static final IdentitySet EMPTY_SET = new EmptyIdentitySet(); @SuppressWarnings("unchecked") public static IdentitySet emptyIdentitySet() { return (IdentitySet) EMPTY_SET; } private static final class EmptyIdentitySet extends AbstractSet implements IdentitySet, Serializable { @Override public Iterator iterator() { return emptyIterator(); } @Override public int size() { return 0; } @Override public boolean contains(Object obj) { return false; } // Preserves singleton property private Object readResolve() { return EMPTY_SET; } } public static Map createMap(K key, V val, K key2, V val2) { final HashMap res = new HashMap(); res.put(key, val); res.put(key2, val2); return res; } public static Map createMap(K key, V val, K key2, V val2, K key3, V val3) { final Map res = createMap(key, val, key2, val2); res.put(key3, val3); return res; } /** * Creates a map with null values. * * @param type of key. * @param type of value. * @param keys the keys of the map. * @return a new map, if keys is a {@link List} it will be ordered. */ public static Map createMap(Collection keys) { return fillMap(keys instanceof List ? new LinkedHashMap(keys.size()) : new HashMap(keys.size()), keys); } /** * Fills a map with null values. * * @param type of key. * @param type of value. * @param type of map. * @param m the map to fill. * @param keys the keys to add. * @return the passed map. */ public static > M fillMap(final M m, Collection keys) { for (final K key : keys) m.put(key, null); return m; } }