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

org.apache.myfaces.trinidad.util.CollectionUtils Maven / Gradle / Ivy

There is a newer version: 2.2.1
Show newest version
/*
 * 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 intersection = new HashSet(getPrimaryDelegate()); intersection.retainAll(c); if (intersection.size() == c.size()) { // the primary delegate contained all of the items, so we're done return true; } else { // compute the set of items we still haven't match in order to check against the // secondary delegate Set remainder = new HashSet(c); remainder.removeAll(intersection); return getSecondaryDelegate().containsAll(remainder); } } public boolean addAll(Collection c) { // determine the result ahead of time boolean changed = !containsAll(c); // make sure that the collections maintain disjointness getSecondaryDelegate().removeAll(c); getPrimaryDelegate().addAll(c); return changed; } public boolean removeAll(Collection c) { return getPrimaryDelegate().removeAll(c) || getSecondaryDelegate().removeAll(c); } public boolean retainAll(Collection c) { return getPrimaryDelegate().retainAll(c) || getSecondaryDelegate().retainAll(c); } public void clear() { getPrimaryDelegate().clear(); getSecondaryDelegate().clear(); } @Override public String toString() { return super.toString() + "[primary:" + getPrimaryDelegate() + ", secondary:" + getSecondaryDelegate() + "]"; } } /** * Iterator that guarantees that removals are also performed on the non-disjoint Collection * @param */ private static class RemovingIterator implements Iterator { public RemovingIterator(Iterator baseIterator, Collection disjointCollection) { _baseIterator = baseIterator; _disjointCollection = disjointCollection; } public boolean hasNext() { return _baseIterator.hasNext(); } public E next() { _last = _baseIterator.next(); return _last; } public void remove() { _baseIterator.remove(); // ensure that the removed element is also removed from the disjoint collection // so that removing the element from the primary collection doesn't accidentally // expose it in the secondary collection _disjointCollection.remove(_last); _last = null; } private final Iterator _baseIterator; private final Collection _disjointCollection; private E _last; } /** * Iterator returning only the elements in the disjoint Collection that aren't in the * checked Collection */ private static class DisjointIterator implements Iterator { public DisjointIterator(Collection checkedCollection, Collection disjointCollection) { _checkedCollection = checkedCollection; _disjointIterator = disjointCollection.iterator(); } public boolean hasNext() { if (_nextHolder == null) { do { if (_disjointIterator.hasNext()) { E next = _disjointIterator.next(); if (!_checkedCollection.contains(next)) { // found it _nextHolder = new AtomicReference(next); break; } } else { return false; } } while (true); } return true; } public E next() { // check if we have another value and if we do, populate _nextHolder if (hasNext()) { E value = _nextHolder.get(); // clear so we know that we need to recalculate next time _nextHolder = null; return value; } else { throw new NoSuchElementException(); } } public void remove() { // make sure that have have called next() before removing. In the case where // next() has never been called, the _disjointIterator should blow up on its own. // one problem we have is that this code won't work correctly if the call order // is next(), hasNext(), remove(), since hasNext() calls next() as a side-effect. // In this case we will throw an IllegalStateException(), which is probably // preferable to removing the wrong element, which is what would happen if we // didn't have the (_nextHolder == null) check. if (_nextHolder == null) _disjointIterator.remove(); else throw new IllegalStateException(); } private final Collection _checkedCollection; private final Iterator _disjointIterator; private AtomicReference _nextHolder; } /** * Note: Requires contents to be disjoint! * @param */ protected abstract static class CompositeSet extends CompositeCollection implements Set { @Override protected abstract Set getPrimaryDelegate(); @Override protected abstract Set getSecondaryDelegate(); /** * Implement Set-defined equals behavior */ @Override public boolean equals(Object o) { if (o == this) return true; else if (!(o instanceof Set)) return false; else { Collection other = (Collection) o; if (other.size() != size()) { return false; } else { // since the sizes are the same, if we contain all of the other collection's // elements, we are identical try { return containsAll(other); } catch(NullPointerException npe) { // optional NullPointerException that containsAll is allowed to throw return false; } catch(ClassCastException npe) { // optional ClassCastException that containsAll is allowed to throw return false; } } } } /** * Implement Set-defined equals behavior */ @Override public int hashCode() { // Set defines hashCode() as additive based on the contents return getPrimaryDelegate().hashCode() + getSecondaryDelegate().hashCode(); } } /** * Concrete Composite Set that takes the two sets to compose */ private static class FixedCompositeSet extends CompositeSet { FixedCompositeSet(Set primarySet, Set secondarySet) { if (primarySet == null) throw new NullPointerException(); if (secondarySet == null) throw new NullPointerException(); assert Collections.disjoint(primarySet, secondarySet) : "Composed Sets not disjoint"; _primarySet = primarySet; _secondarySet = secondarySet; } @Override protected Set getPrimaryDelegate() { return _primarySet; } @Override protected Set getSecondaryDelegate() { return _secondarySet; } private final Set _primarySet; private final Set _secondarySet; } /** * Serializable version of FixedCompositeSet * @param */ private static final class SerializableFixedCompositeSet extends FixedCompositeSet implements Serializable { SerializableFixedCompositeSet(Set primarySet, Set secondarySet) { super(primarySet, secondarySet); } private static final long serialVersionUID = 0L; } /** * Live composite set where both sets are allowed to be disjoint. * @param */ protected abstract static class LenientCompositeSet extends CompositeSet { @Override public int size() { return CollectionUtils.getUnionSize(getPrimaryDelegate(), getSecondaryDelegate()); } @Override public Iterator iterator() { // create a CompositeIterator of the primary and secondary Sets such that all of the // elements of the bigger Set are returned directly and the smaller Collection returns // only the elements not present in the larger Collection Set primaryDelegate = getPrimaryDelegate(); Set secondaryDelegate = getSecondaryDelegate(); if (primaryDelegate.size() >= secondaryDelegate.size()) { return new CompositeIterator( new RemovingIterator(primaryDelegate.iterator(), secondaryDelegate), new DisjointIterator(primaryDelegate, secondaryDelegate)); } else { return new CompositeIterator( new RemovingIterator(secondaryDelegate.iterator(), primaryDelegate), new DisjointIterator(secondaryDelegate, primaryDelegate)); } } @Override public boolean add(E e) { boolean modified = getPrimaryDelegate().add(e); if (modified) { // If the secondary delegate already contained this element // then we didn't really change. Since we don't need to maintain the disjoint // property, we don't have to remove the item from the secondaryDelegate. modified = !getSecondaryDelegate().contains(e); } return modified; } @Override public boolean remove(Object o) { // override to remove from both Sets to ensure that removing from the first // doesn't cause the same value to no longer be eclipsed in the second boolean removed = getPrimaryDelegate().remove(0); removed |= getSecondaryDelegate().remove(0); return removed; } @Override public boolean addAll(Collection c) { // determine the result ahead of time boolean changed = !containsAll(c); // We don't need to remove the items from the secondaryDelegate because we don't // need to maintain disjointness getPrimaryDelegate().addAll(c); return changed; } /** * Implement Set-defined equals behavior */ @Override public int hashCode() { // Set defines hashCode() as additive based on the contents // create a CompositeIterator of the primary and secondary Sets such that all of the // elements of the bigger Set are returned directly and the smaller Collection returns // only the elements not present in the larger Collection Set primaryDelegate = getPrimaryDelegate(); Set secondaryDelegate = getSecondaryDelegate(); int hashCode; Iterator disjointElements; if (primaryDelegate.size() >= secondaryDelegate.size()) { hashCode = primaryDelegate.hashCode(); disjointElements = new DisjointIterator(primaryDelegate, secondaryDelegate); } else { hashCode = secondaryDelegate.hashCode(); disjointElements = new DisjointIterator(secondaryDelegate, primaryDelegate); } while (disjointElements.hasNext()) { E currElement = disjointElements.next(); if (currElement != null) hashCode += currElement.hashCode(); } return hashCode; } } /** * Concrete Composite Set that takes the two sets to compose */ private static class LenientFixedCompositeSet extends LenientCompositeSet { LenientFixedCompositeSet(Set primarySet, Set secondarySet) { if (primarySet == null) throw new NullPointerException(); if (secondarySet == null) throw new NullPointerException(); _primarySet = primarySet; _secondarySet = secondarySet; } @Override protected Set getPrimaryDelegate() { return _primarySet; } @Override protected Set getSecondaryDelegate() { return _secondarySet; } private final Set _primarySet; private final Set _secondarySet; } /** * Serializable version of LenientFixedCompositeSet * @param */ private static final class SerializableLenientFixedCompositeSet extends LenientFixedCompositeSet implements Serializable { SerializableLenientFixedCompositeSet(Set primarySet, Set secondarySet) { super(primarySet, secondarySet); } private static final long serialVersionUID = 0L; } private static class SerializableCollection extends DelegatingCollection implements Serializable { SerializableCollection(Collection delegate) { // we don't check that the delegate is Serializable because of the Collections // classes that lie about Serializability if (delegate == null) throw new NullPointerException(); _delegate = delegate; } protected Collection getDelegate() { return _delegate; } protected Object writeReplace() throws ObjectStreamException { // copy delegate into a Serializable ArrayList on Serialization return new ArrayList(_delegate); } private static final long serialVersionUID = 0L; private final transient Collection _delegate; } private static class SerializableList extends SerializableCollection implements List { SerializableList(List delegate) { super(delegate); _delegate = delegate; } public void add(int index, E element) { _delegate.add(index, element); } public E remove(int index) { return _delegate.remove(index); } public boolean addAll(int index, Collection c) { return _delegate.addAll(index, c); } public E get(int index) { return _delegate.get(index); } public E set(int index, E element) { return _delegate.set(index, element); } public int indexOf(Object o) { return _delegate.indexOf(o); } public int lastIndexOf(Object o) { return _delegate.lastIndexOf(o); } public ListIterator listIterator() { return _delegate.listIterator(); } public ListIterator listIterator(int index) { return _delegate.listIterator(index); } public List subList(int fromIndex, int toIndex) { return CollectionUtils.getSerializableList(_delegate.subList(fromIndex, toIndex)); } private static final long serialVersionUID = 0L; private final transient List _delegate; } private static class SerializableRandomAccessList extends SerializableList implements RandomAccess { SerializableRandomAccessList(List delegate) { super(delegate); } private static final long serialVersionUID = 0L; } protected static abstract class DelegatingMap implements Map { protected abstract Map getDelegate(); public int size() { return getDelegate().size(); } public boolean isEmpty() { return getDelegate().isEmpty(); } public boolean containsKey(Object key) { return getDelegate().containsKey(key); } public boolean containsValue(Object value) { return getDelegate().containsValue(value); } public V get(Object key) { return getDelegate().get(key); } // Modification Operations public V put(K key, V value) { return getDelegate().put(key, value); } public V remove(Object key) { return getDelegate().remove(key); } // Bulk Operations public void putAll(Map m) { getDelegate().putAll(m); } public void clear() { getDelegate().clear(); } // Views public Set keySet() { return getDelegate().keySet(); } public Collection values() { return getDelegate().values(); } public Set> entrySet() { return getDelegate().entrySet(); } // Comparison and hashing public boolean equals(Object o) { return getDelegate().equals(o); } public int hashCode() { return getDelegate().hashCode(); } } protected static abstract class DelegatingEntry implements Map.Entry { protected abstract Map.Entry getDelegate(); public K getKey() { return getDelegate().getKey(); } public V getValue() { return getDelegate().getValue(); } public V setValue(V value) { return getDelegate().setValue(value); } public boolean equals(Object o) { return getDelegate().equals(o); } public int hashCode() { return getDelegate().hashCode(); } } protected static abstract class AccessHookMap extends DelegatingMap { protected abstract void writeNotify(K key, V value); protected abstract void removeNotify(Object key); protected abstract void clearNotify(); @Override public V put(K key, V value) { writeNotify(key, value); return super.put(key, value); } @Override public V remove(Object key) { removeNotify(key); return super.remove(key); } @Override public void putAll(Map m) { for (Map.Entry entry : m.entrySet()) { K key = entry.getKey(); V value = entry.getValue(); writeNotify(key, value); super.put(key, value); } } @Override public void clear() { clearNotify(); super.clear(); } public Set> entrySet() { return new MutationHookedEntrySet(this); } // Entry Set returns CheckedSerializationEntry Objects private static class MutationHookedEntrySet extends DelegatingCollection> implements Set> { private MutationHookedEntrySet(AccessHookMap accessHookMap) { if (accessHookMap == null) throw new NullPointerException(); _accessHookMap = accessHookMap; _delegate = accessHookMap.getDelegate().entrySet(); } protected Set> getDelegate() { return _delegate; } public Iterator> iterator() { return new MutationHookedEntrySetIterator(super.iterator(), _accessHookMap); } public Object[] toArray() { Object[] delegateEntries = super.toArray(); int entryCount = delegateEntries.length; // make sure that the array allows generic Entry objects. If so, use the source array // as the destination array, otherwise create a new destination array for our entries Object[] entries = (delegateEntries.getClass().getComponentType().isAssignableFrom(Entry.class)) ? delegateEntries : new Entry[entryCount]; for (int i = 0; i < entryCount; i++) entries[i] = new MutationHookedEntry((Entry)delegateEntries[i], _accessHookMap); return entries; } public T[] toArray(T[] a) { int inputSize = a.length; // compute the output type array so that the delegate creates an array of the correct // type. We always truncate the input array's size to 0 so that the delegate doesn't // attempt to copy any of its contents into the output array T[] outTypeArray = (inputSize == 0) ? a : CollectionUtils.copyOf(a, 0); Object[] delegateEntries = super.toArray(outTypeArray); // now proxy the contents int entryCount = delegateEntries.length; for (int i = 0; i < entryCount; i++) delegateEntries[i] = new MutationHookedEntry((Entry)delegateEntries[i], _accessHookMap); // now figure out whether we have to copy the entries into the passed in array or not if (entryCount > inputSize) return (T[])delegateEntries; // they fit so we need to copy the values into the input array System.arraycopy(delegateEntries, 0, a, 0, entryCount); // see if we have room for the wacky null terminator if (inputSize > entryCount) a[entryCount] = null; return a; } // Iterator for MutationHookedEntrySet that returns MutationHookedEntry private static final class MutationHookedEntrySetIterator implements Iterator> { private MutationHookedEntrySetIterator(Iterator> delegate, AccessHookMap accessHookMap) { _delegate = delegate; _accessHookMap = accessHookMap; } public boolean hasNext() { return _delegate.hasNext(); } public Map.Entry next() { Map.Entry nextEntry = _delegate.next(); // update the current key _currKey = nextEntry.getKey(); // return wrapped entry return new MutationHookedEntry(nextEntry, _accessHookMap); } public void remove() { if (_currKey == _NO_KEY) throw new IllegalStateException(); // notify listener of removal _accessHookMap.removeNotify(_currKey); // let the delegate remove the entry _delegate.remove(); // no more entry to remove until next call to next() _currKey = _NO_KEY; } private static final Object _NO_KEY = new Object(); // either _NO_KEY or the current key. We use volatile to ensure safe publication for // thread use private volatile Object _currKey = _NO_KEY; private final Iterator> _delegate; private final AccessHookMap _accessHookMap; } // Entry implementation that hooks calls to setValue private static class MutationHookedEntry extends DelegatingEntry { private MutationHookedEntry(Entry delegate, AccessHookMap accessHookMap) { if (delegate == null) throw new NullPointerException(); _delegate = delegate; _accessHookMap = accessHookMap; } protected Entry getDelegate() { return _delegate; } public V setValue(V value) { _accessHookMap.writeNotify(getKey(), value); return super.setValue(value); } private final Entry _delegate; private final AccessHookMap _accessHookMap; } private final AccessHookMap _accessHookMap; private final Set> _delegate; } } protected static class ExternalAccessHookMap extends AccessHookMap { protected ExternalAccessHookMap(Map delegate, MapMutationHooks mutationHooks) { if (delegate == null) throw new NullPointerException("delegate is null"); if (mutationHooks == null) throw new NullPointerException("accessHooks is null"); _delegate = delegate; _mutationHooks = mutationHooks; } protected final Map getDelegate() { return _delegate; } protected final void writeNotify(K key, V value) { _mutationHooks.writeNotify(this, key, value); } protected final void removeNotify(Object key) { _mutationHooks.removeNotify(this, key); } protected final void clearNotify() { _mutationHooks.clearNotify(this); } private static final long serialVersionUID = 1L; private final Map _delegate; private final MapMutationHooks _mutationHooks; } private static final class SerializableExternalAccessHookMap extends ExternalAccessHookMap implements Serializable { private SerializableExternalAccessHookMap( Map delegate, MapMutationHooks mutationHooks) { super(delegate, mutationHooks); if (!(delegate instanceof Serializable)) throw new IllegalArgumentException("Delegate must be Serializable"); if (!(mutationHooks instanceof Serializable)) throw new IllegalArgumentException("mutation hooka must be Serializable"); } private static final long serialVersionUID = 1L; } // Map that validates that the keys and values added to the map are Serializable private final static class CheckedSerializationMap extends AccessHookMap implements Serializable { /** * @param requireSerializable if true, require that all values in the map implement * Serializable. * @param delegate we do not check whether the delegate itself is Serializable. We just check its contents. */ public CheckedSerializationMap( Map delegate, boolean requireSerializable) { if (delegate == null) throw new NullPointerException(); _delegate = delegate; _requireSerializable = requireSerializable; } protected Map getDelegate() { return _delegate; } protected void writeNotify(K key, V value) { // don't bother checking common case of String if (!(key instanceof String)) { if (key instanceof Serializable) { // verify that the contents of the key are in fact Serializable try { new ObjectOutputStream(new ByteArrayOutputStream()).writeObject(key); } catch (IOException e) { throw new IllegalArgumentException(_LOG.getMessage("FAILED_SERIALIZATION_PROPERTY_KEY", new Object[]{key, this}), e); } } else { if (_requireSerializable) { throw new ClassCastException(_LOG.getMessage("UNSERIALIZABLE_PROPERTY_KEY", new Object[]{key, this})); } } } if (value instanceof Serializable) { // verify that the contents of the value are in fact Serializable try { new ObjectOutputStream(new ByteArrayOutputStream()).writeObject(value); } catch (IOException e) { throw new IllegalArgumentException(_LOG.getMessage("FAILED_SERIALIZATION_PROPERTY_VALUE", new Object[]{value, key, this}), e); } } else if (value != null) { if (_requireSerializable) { throw new ClassCastException(_LOG.getMessage("UNSERIALIZABLE_PROPERTY_VALUE", new Object[]{value, key, this})); } } } protected void removeNotify(Object key) { // do nothing } protected void clearNotify() { // do nothing } @Override public void putAll(Map m) { Object[] keys = m.keySet().toArray(); Object[] values = m.values().toArray(); int keyCount = keys.length; // in case an entry was added or removed between to tow toArray calls above if (keyCount != values.length) throw new ConcurrentModificationException(); // atomically check for serializability before adding for (int k = 0; k < keyCount; k++) { writeNotify((K)keys[k], (V)values[k]); } // add the contents we checked rather that calling super.putAll(m), in case // the map changed after we checked its contents above Map delegate = getDelegate(); for (int k = 0; k < keyCount; k++) { delegate.put((K)keys[k], (V)values[k]); } } private static final long serialVersionUID = 1L; private final Map _delegate; private final boolean _requireSerializable; } private static class EmptyIterator implements Iterator { public boolean hasNext() { return false; } public Object next() { throw new NoSuchElementException(); } public void remove() { throw new UnsupportedOperationException(); } } private static final class EmptyListIterator extends EmptyIterator implements ListIterator { public boolean hasPrevious() { return false; } public Object previous() { throw new NoSuchElementException(); } public int nextIndex() { return 0; } public int previousIndex() { return -1; } public void set(Object e) { throw new UnsupportedOperationException(); } public void add(Object e) { throw new UnsupportedOperationException(); } } private static final class EmptyQueue extends AbstractQueue implements Serializable { public Iterator iterator() { return _EMPTY_ITERATOR; } public int size() { return 0; } @Override public boolean isEmpty() { return true; } @Override public boolean contains(Object o) { return false; } public boolean offer(Object e) { throw new UnsupportedOperationException(); } public Object poll() { return null; } public Object peek() { return null; } private Object readResolve() { return _EMPTY_QUEUE; } private static final long serialVersionUID = 0L; } // // Build up references to implementation classes used by Collections to implement the following // features. This way we can detect when these classes are used and work around problems. // private static final Class _CHECKED_LIST; private static final Class _UNMODIFIABLE_LIST; private static final Class _SYNCHRONIZED_LIST; private static final Class _EMPTY_SET = Collections.emptySet().getClass(); private static final Class _SINGLETON_SET = Collections.singleton(null).getClass(); private static final Queue _EMPTY_QUEUE = new EmptyQueue(); private static final Iterator _EMPTY_ITERATOR = new EmptyIterator(); private static final Iterator _EMPTY_LIST_ITERATOR = new EmptyListIterator(); static { // use a LinkedList as it doesn't implement RandomAccess, so that we don't accidentally get // the RandomAccess subclasses LinkedList dummyList = new LinkedList(); _CHECKED_LIST = Collections.checkedList(dummyList, Object.class).getClass(); _UNMODIFIABLE_LIST = Collections.unmodifiableList(dummyList).getClass(); _SYNCHRONIZED_LIST = Collections.synchronizedList(dummyList).getClass(); } private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(CollectionUtils.class); }