org.apache.myfaces.trinidad.util.CollectionUtils Maven / Gradle / Ivy
Show all versions of trinidad-api Show documentation
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.myfaces.trinidad.util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.AbstractQueue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.RandomAccess;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.myfaces.trinidad.component.CompositeIterator;
import org.apache.myfaces.trinidad.logging.TrinidadLogger;
/**
* This class contains Collection utilities.
*/
public final class CollectionUtils
{
private CollectionUtils()
{
// no-op
}
/**
* Returns an ArrayList containing all of the elements of the
* Iterator
* @param iterator Iterator to copy the contexts of
* @return an ArrayList containing a copy of the iterator contents
*/
public static ArrayList arrayList(Iterator iterator)
{
ArrayList arrayList = new ArrayList();
while (iterator.hasNext())
arrayList.add(iterator.next());
return arrayList;
}
/**
* Returns an array containing all of the elements of the
* Iterator
* @param iterator Iterator to copy the contexts of
* @return an array containing a copy of the iterator contents
*/
public static T[] toArray(Iterator iterator, Class type)
{
if (iterator.hasNext())
{
Collection arrayList = arrayList(iterator);
T[] outArray = (T[])Array.newInstance(type, arrayList.size());
return (T[])arrayList.toArray(outArray);
}
else
{
// optimize empty iterator case
return (T[])Array.newInstance(type, 0);
}
}
/**
* Returns an empty, unmodifiable, Serializable Queue.
* @return an empty, unmodifiable, Serializable Queue.
*/
public static Queue emptyQueue()
{
return (Queue)_EMPTY_QUEUE;
}
/**
* Returns an empty, unmodifiable, Iterator.
* @return an empty, unmodifiable, Iterator.
*/
public static Iterator emptyIterator()
{
return (Iterator)_EMPTY_ITERATOR;
}
/**
* Returns an empty, unmodifiable, ListIterator.
* @return an empty, unmodifiable, ListIterator.
*/
public static ListIterator emptyListIterator()
{
return (ListIterator)_EMPTY_LIST_ITERATOR;
}
/**
* Return an iterator of the values in map
with entries in allowedLeys
* @param Map key type
* @param Map value type
* @param map Map of keys and values
* @param allowedKeys Collection of keys to return values for if present in map
* @return Iterator of values for which the map contains a key from allowedKeys
*/
public static Iterator subsetValueIterator(Map map,
Collection allowedKeys)
{
if (map.isEmpty() || allowedKeys.isEmpty())
{
return emptyIterator();
}
else
{
return new SubsetValueIterator(map, allowedKeys.iterator());
}
}
/**
* Return an iterator of the values in map
with entries in allowedLeys
* The values returned in the iterator will be in the same order as their corresponding
* keys in allowedKeys.
* @param Map key type
* @param Map value type
* @param map Map of keys and values
* @param allowedKeys Iterator of keys to return values for if present in map
* @return Iterator of values for which the map contains a key from allowedKeys
*/
public static Iterator subsetValueIterator(Map map,
Iterator allowedKeys)
{
if (map.isEmpty() || !allowedKeys.hasNext())
{
return emptyIterator();
}
else
{
return new SubsetValueIterator(map, allowedKeys);
}
}
/**
* Return an iterator of the values in map
with entries in allowedLeys
*/
private static class SubsetValueIterator implements Iterator
{
public SubsetValueIterator(Map sourceMap,
Iterator allowedkeys)
{
if ((sourceMap == null) || (allowedkeys == null))
throw new NullPointerException();
_sourceMap = sourceMap;
_allowedkeys = allowedkeys;
}
@Override
public boolean hasNext()
{
return (_getKey() != null);
}
@Override
public V next()
{
K key = _getKey();
if (key == null)
throw new NoSuchElementException();
// next call to _getKey() will nove to the next key
_calcNext = true;
return _sourceMap.get(key);
}
@Override
public void remove()
{
// make sure that we have called next() before remove()
if (_nextValidKey == null)
throw new IllegalStateException();
try
{
_sourceMap.remove(_nextValidKey);
}
finally
{
_nextValidKey = null;
_calcNext = true;
}
}
private K _getKey()
{
if ((_nextValidKey == null) || _calcNext)
{
_nextValidKey = _getNextContainedKey();
_calcNext = false;
}
return _nextValidKey;
}
private K _getNextContainedKey()
{
while (_allowedkeys.hasNext())
{
K nextKey = _allowedkeys.next();
if (_sourceMap.containsKey(nextKey))
{
return nextKey;
}
}
return null;
}
private final Map _sourceMap;
private final Iterator _allowedkeys;
private K _nextValidKey;
private boolean _calcNext = true;
}
/**
* Returns a minimal Set containing the elements passed in. There is no guarantee that the
* returned set is mutable or immutable.
* @param
* @param a The elements to add to the Set
* @return A set containing the elements passed in.
* @see #asUnmodifiableSet
*/
public static Set asSet(T... a)
{
return _asSet(a, false);
}
/**
* Returns an unmodifiable Set containing the elements passed in.
* @param
* @param a The elements to add to the Set
* @return A set containing the elements passed in.
* @see #asSet
*/
public static Set asUnmodifiableSet(T... a)
{
return _asSet(a, true);
}
/**
* Returns an unmodifiable versions of the Set of Enums. If the contents of the set are known
* to be unmodifiable by the caller in any way, the set itself will be retured, otherwise an
* unmodifiable copy of the Set will be returned.
* @param s Set to get the tamper-proof version of
* @return An unmodifiable tamper-proof version of the set
*/
public static > Set unmodifiableCopyOfEnumSet(Set s)
{
Class copyClass = s.getClass();
if ((_EMPTY_SET == copyClass) || (_SINGLETON_SET == copyClass))
{
// these classes are already unmodifiable, so just return
return s;
}
else
{
return Collections.unmodifiableSet(EnumSet.copyOf(s));
}
}
private static Set _asSet(T[] a, boolean makeImmutable)
{
int count = (a != null) ? a.length : 0;
Set outSet;
if (count == 0)
{
outSet = Collections.emptySet();
}
else
{
if (count == 1)
{
outSet = Collections.singleton(a[0]);
}
else
{
// make the initial size big enough that we don't have to rehash to fit the array, for
// the .75 load factor we pass in
int initialSize = (int)Math.ceil(count / .75d);
outSet = new HashSet(initialSize, .75f);
for (int i = 0; i < count ; i++)
{
outSet.add(a[i]);
}
if (makeImmutable)
{
outSet = Collections.unmodifiableSet(outSet);
}
}
}
return outSet;
}
/**
* Given two disjoint sets, returns a live composition of the two Sets that maintains
* the disjoint invariant. If both Sets are Serializable, the returned
* implementation will be Serializable as well. The returned Set implementation is
* not thread safe.
* @param primarySet The Set that modifications will be applied to
* @param secondarySet The other Set. Modifications will be applied in response to changes
* to the primary set to ensure that the disjoint invariant is maintained
* @return The composition of the two disjoint Sets
* @throws NullPointerException of primarySet or secondarySet are null
* @see #overlappingCompositeSet
*/
public static Set compositeSet(Set primarySet, Set secondarySet)
{
if ((primarySet instanceof Serializable) && (secondarySet instanceof Serializable))
return new SerializableFixedCompositeSet(primarySet, secondarySet);
else
return new FixedCompositeSet(primarySet, secondarySet);
}
/**
* Given two possibly overlapping sets, returns a live composition of the two Sets.
* If both Sets are Serializable, the returned
* implementation will be Serializable as well. The lack of the disjoint invariant makes
* operations such as calculating the size of the Set expensive. If the disjoint invariant
* can be guaranteed, compositeSet
should be used instead.
* The returned Set implementation is
* not thread safe.
* @param primarySet The Set that modifications will be applied to
* @param secondarySet The other Set. If a removal is performed on the primarySet, it will
* also be applied to the secondarySet to ensure that the element is logically removed from the
* Set.
* @return The composition of the two possibly overallping Sets
* @throws NullPointerException of primarySet or secondarySet are null
* @see #compositeSet
*/
public static Set overlappingCompositeSet(Set primarySet, Set secondarySet)
{
if ((primarySet instanceof Serializable) && (secondarySet instanceof Serializable))
return new SerializableLenientFixedCompositeSet(primarySet, secondarySet);
else
return new LenientFixedCompositeSet(primarySet, secondarySet);
}
/**
* Returns a Collection based on the passed in Collection c
,
* guaranteed to be Serializable. If c
is Serializable,
* c
will be returned, otherwise, c
will be
* wrapped in a Collection that implements Serializable and upon
* Serialization the contents of c
will be copied into
* the result.
*
* The results is very similar to creating a new ArrayList with the
* contents of c
, but no wrapper is created unless necessary
* and the actual creation of the Serializable copy is deferred until
* Serialization occurs.
* @param c The Collection to get a Serializable version of
* @return A Serializable version of Collection c
* @see #getSerializableList
*/
public static Collection getSerializableCollection(Collection c)
{
if (c instanceof Serializable)
return c;
else
return new SerializableCollection(c);
}
/**
* Returns a List based on the passed in List l
,
* guaranteed to be Serializable. List l
will be
* wrapped in a List that implements Serializable and upon
* Serialization the contents of l
will be copied into
* the result.
*
* If l
implements RandomAccess, any returned List will also
* implement RandomAccess.
*
* The results is very similar to creating a new ArrayList with the
* contents of l
, but no wrapper is created unless necessary
* and the actual creation of the Serializable copy is deferred until
* Serialization occurs.
*
* Code that calls List.subList() and needs the result to be Serializable should always
* use newSerializableList
rather than getSerializableList
because
* the java.util.Collections
implementations of checkedList
,
* unmodifiableList
, and synchronizedList
all lie and always implement
* Serializable, regardless of the serializability of their backing List.
* @param l The List to get a Serializable version of
* @return A Serializable version of List l
* @see #getSerializableList
* @see #getSerializableCollection
*/
public static List newSerializableList(List l)
{
if (l instanceof RandomAccess)
{
return new SerializableRandomAccessList(l);
}
else
{
return new SerializableList(l);
}
}
/**
* Returns a List based on the passed in List l
,
* guaranteed to be Serializable. If l
is Serializable,
* l
will be returned, otherwise, l
will be
* wrapped in a List that implements Serializable and upon
* Serialization the contents of l
will be copied into
* the result.
*
* If l
implements RandomAccess, any returned List will also
* implement RandomAccess.
*
* The results is very similar to creating a new ArrayList with the
* contents of l
, but no wrapper is created unless necessary
* and the actual creation of the Serializable copy is deferred until
* Serialization occurs.
*
* Code that calls List.subList() and needs the result to be Serializable should always
* use newSerializableList
rather than getSerializableList
because
* the java.util.Collections
implementations of checkedList
,
* unmodifiableList
, and synchronizedList
all lie and always implement
* Serializable, regardless of the serializability of their backing List.
* @param l The List to get a Serializable version of
* @return A Serializable version of List l
* @see #newSerializableList
* @see #getSerializableCollection
*/
public static List getSerializableList(List l)
{
// because we can't trust the implementations of the checked, unmodifiable, and synchronized
// versions, always create a Serializable wrapper if we see one of these classes
if ((l instanceof Serializable) &&
!_CHECKED_LIST.isInstance(l) &&
!_UNMODIFIABLE_LIST.isInstance(l) &&
!_SYNCHRONIZED_LIST.isInstance(l))
return l;
else
{
return newSerializableList(l);
}
}
/**
* Interface for trapping mutations to a Map.
* @param the type of the keys of the Map that MapMutationHooks are associated with
* @param the type of the values of the Map that MapMutationHooks are associated with
* @see #newMutationHookedMap
*/
public interface MapMutationHooks
{
/**
* Called when the associated Map of the MapMutationHooks is written to
* @param map Map the write occurred on
* @param key key of entry that has changed
* @param value value of entry that has changed
*/
public void writeNotify(Map map, K key, V value);
/**
* Called when an entry is removed from the associated Map of the MapMutationHooks
* @param map Map the removal occurred on
* @param key key of entry that has been removed
*/
public void removeNotify(Map map, Object key);
/**
* Called when all entries are removed from the Map associated with the MapMutationHooks
* @param map Map the clear occurred on
*/
public void clearNotify(Map map);
}
/**
* Creates a new Map that informs the MapMutationHooks of any direct mutations. Mutations to
* the underlying Map will not be caught.
* If the base map is Serializable, the returned Map will be Serializable
* @param type of the keys of the Map
* @param type of the values of the Map
* @param map Underlying map to trap mutations of
* @param hooks MapMutationHooks to inform of mutations to the returned Map
* @return a new Map that traps the mutations to the underlying Map
* @throws NullPointerException if map or hooks are null
*/
public static Map newMutationHookedMap(Map map, MapMutationHooks hooks)
{
if (map == null)
throw new NullPointerException();
if (hooks == null)
throw new NullPointerException();
if (map instanceof Serializable)
return new SerializableExternalAccessHookMap(map, hooks);
else
return new ExternalAccessHookMap(map, hooks);
}
/**
* Creates a Map that dynamically verifies that all keys and values added to it will
* succeed Serialization. The validations checks not only that the keys and values added
* to the Map implement Serializable, but that these instances will actually succeed
* Serialization.
*
* This checking can be defeated by either modifying the backing map directly or by modifying
* an object added to the checked Map after adding it.
*
* @param map Map to wrap for Serialization validation
* @return Map where all modifications are checked to ensure that they will succeeed if
* serialized
*/
public static Map getCheckedSerializationMap(Map map)
{
return getCheckedSerializationMap(map, true);
}
/**
* Creates a Map that dynamically verifies that all keys and values added to it will
* succeed Serialization. The validations checks not only that the keys and values added
* to the Map implement Serializable, but that these instances will actually succeed
* Serialization.
*
* This checking can be defeated by either modifying the backing map directly or by modifying
* an object added to the checked Map after adding it.
*
* @param map Map to wrap for Serialization validation
* @param requireSerializable if true
, require that all values in the map implement
* Serializable.
* @return Map where modifications are checked to ensure that they will succeeed if
* serialized
*/
public static Map getCheckedSerializationMap(
Map map,
boolean requireSerializable)
{
if (map instanceof CheckedSerializationMap)
return map;
else
return new CheckedSerializationMap(map, requireSerializable);
}
/**
* Given two Collections, return the size of their union
* @param firstCollection The first collection. null
is allowed.
* @param secondCollection The second collection. null
is allowed.
* @return
*/
public static int getUnionSize(
Collection firstCollection,
Collection secondCollection)
{
int firstSize = (firstCollection != null)
? firstCollection.size()
: 0;
int secondSize = (secondCollection != null)
? secondCollection.size()
: 0;
if (firstSize == 0)
return secondSize;
if (secondSize == 0)
return firstSize;
// determine the size of the union by iterating over the smaller collection
int size;
Collection iteratingCollection;
Collection baseCollection;
if (firstSize >= secondSize)
{
baseCollection = firstCollection;
iteratingCollection = secondCollection;
size = firstSize;
}
else
{
baseCollection = secondCollection;
iteratingCollection = firstCollection;
size = secondSize;
}
for (E currValue : iteratingCollection)
{
if (!baseCollection.contains(currValue))
{
size++;
}
}
return size;
}
protected static T[] copyOf(T[] original, int newLength)
{
return (T[]) copyOf(original, newLength, original.getClass());
}
protected static T[] copyOf(U[] original, int newLength, Class newType)
{
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
protected abstract static class DelegatingCollection implements Collection
{
protected abstract Collection getDelegate();
public int size()
{
return getDelegate().size();
}
public boolean isEmpty()
{
return getDelegate().isEmpty();
}
public boolean contains(Object o)
{
return getDelegate().contains(o);
}
public Iterator iterator()
{
return getDelegate().iterator();
}
public Object[] toArray()
{
return getDelegate().toArray();
}
public T[] toArray(T[] a)
{
return getDelegate().toArray(a);
}
public boolean add(E e)
{
return getDelegate().add(e);
}
public boolean remove(Object o)
{
return getDelegate().remove(0);
}
public boolean containsAll(Collection c)
{
return getDelegate().containsAll(c);
}
public boolean addAll(Collection c)
{
return getDelegate().addAll(c);
}
public boolean removeAll(Collection c)
{
return getDelegate().removeAll(c);
}
public boolean retainAll(Collection c)
{
return getDelegate().retainAll(c);
}
public void clear()
{
getDelegate().clear();
}
/**
* All Collections
* @param o
* @return
*/
public boolean equals(Object o)
{
return (o == this) || getDelegate().equals(o);
}
public int hashCode()
{
return getDelegate().hashCode();
}
public String toString()
{
return getDelegate().toString();
}
}
/**
* Note: Requires contents to be disjoint!
* @param
*/
protected abstract static class CompositeCollection implements Collection
{
protected abstract Collection getPrimaryDelegate();
protected abstract Collection getSecondaryDelegate();
public int size()
{
return getPrimaryDelegate().size() + getSecondaryDelegate().size();
}
public boolean isEmpty()
{
return getPrimaryDelegate().isEmpty() && getSecondaryDelegate().isEmpty();
}
public boolean contains(Object o)
{
return getPrimaryDelegate().contains(o) || getSecondaryDelegate().contains(o);
}
public Iterator iterator()
{
return new CompositeIterator(getPrimaryDelegate().iterator(),
getSecondaryDelegate().iterator());
}
public Object[] toArray()
{
int size = size();
Object[] out = new Object[size];
int i = 0;
for (Object currObject : this)
{
out[i] = currObject;
i++;
if (i == size)
break;
}
return out;
}
public T[] toArray(T[] outArray)
{
int collectionSize = size();
int arraySize = outArray.length;
// size isn't big enough, so need a new array
if (collectionSize > arraySize)
{
outArray = (T[])Array.newInstance(outArray.getClass().getComponentType(),
collectionSize);
}
Iterator iterator = this.iterator();
for (int i = 0; i < collectionSize; i++)
{
if (!iterator.hasNext())
break;
outArray[i] = (T)iterator.next();
}
return outArray;
}
public boolean add(E e)
{
boolean modified = getPrimaryDelegate().add(e);
if (modified)
{
// maintain disjointness. If the secondary delegate already contained this element
// then we didn't really change
modified = !getSecondaryDelegate().remove(e);
}
return modified;
}
public boolean remove(Object o)
{
boolean removed = getPrimaryDelegate().remove(0);
if (!removed)
removed = getSecondaryDelegate().remove(0);
return removed;
}
public boolean containsAll(Collection c)
{
// find all of the items in both the collection and the primary delegate
Set