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

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

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 2008 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.ITransformer;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
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 extends org.apache.commons.collections.CollectionUtils {

    // TODO rm the non generic one
    static public final  String join(final Collection c, final String sep, final ITransformer tf) {
        return join(c, sep, (Transformer) tf);
    }

    /**
     * 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 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.
     * @deprecated use {@link  #join(Collection, String, ITransformer)}
     */
    static public final String join(final Collection c, final String sep, final Transformer tf) {
        if (c.size() == 0)
            return "";

        final StringBuffer res = new StringBuffer(c.size() * 4);
        if (c instanceof RandomAccess && c instanceof List) {
            final List list = (List) c;
            final int stop = c.size() - 1;
            for (int i = 0; i < stop; i++) {
                res.append(tf.transform(list.get(i)));
                res.append(sep);

            }
            res.append(tf.transform(list.get(stop)));
        } else {
            final Iterator iter = c.iterator();
            while (iter.hasNext()) {
                final Object elem = iter.next();
                res.append(tf.transform(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, ITransformer. nopTransformer());
    }

    // *** split

    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
     * 
* * @param l the list, eg a list of 4 items. * @param i the virtual index, eg -1. * @return the real index, eg 3. */ static public int getValidIndex(final List l, final int i) { if (i > l.size()) { return l.size(); } else if (i >= 0) { return i; } else if (l.size() + i <= 0) return 0; else return l.size() + i; } /** * Deletes a slice of a list. Pass indexes to {@link #getValidIndex(List, int)}. * * @param l the list to delete from. * @param from the first index to be removed (inclusive). * @param to the last index to be removed (exclusive). */ static public void delete(List l, int from, int to) { l.subList(getValidIndex(l, from), getValidIndex(l, to)).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, l.size()); } /** * 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 tableau de 2 List, en 0 les clefs, en 1 les valeurs. * @param type of key * @param type of value */ static public 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 new List[] { keys, vals }; } /** * 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; } } /** * 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; } @SuppressWarnings("unchecked") public static Collection inter(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. */ public static Set inter(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 inter(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; } @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; } }