Home | History | Annotate | Download | only in collect
      1 /*
      2  * Copyright (C) 2007 Google Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.google.common.collect;
     18 
     19 import com.google.common.annotations.GwtCompatible;
     20 import com.google.common.base.Objects;
     21 
     22 import java.util.AbstractCollection;
     23 import java.util.AbstractSet;
     24 import java.util.Collection;
     25 import java.util.Iterator;
     26 import java.util.NoSuchElementException;
     27 import java.util.Set;
     28 
     29 import javax.annotation.Nullable;
     30 
     31 import static com.google.common.base.Preconditions.checkNotNull;
     32 import static com.google.common.base.Preconditions.checkState;
     33 import static com.google.common.collect.Multisets.setCountImpl;
     34 
     35 
     36 /**
     37  * This class provides a skeletal implementation of the {@link Multiset}
     38  * interface. A new multiset implementation can be created easily by extending
     39  * this class and implementing the {@link Multiset#entrySet()} method, plus
     40  * optionally overriding {@link #add(Object, int)} and
     41  * {@link #remove(Object, int)} to enable modifications to the multiset.
     42  *
     43  * <p>The {@link #contains}, {@link #containsAll}, {@link #count}, and
     44  * {@link #size} implementations all iterate across the set returned by
     45  * {@link Multiset#entrySet()}, as do many methods acting on the set returned by
     46  * {@link #elementSet()}. Override those methods for better performance.
     47  *
     48  * @author Kevin Bourrillion
     49  */
     50 @GwtCompatible
     51 abstract class AbstractMultiset<E> extends AbstractCollection<E>
     52     implements Multiset<E> {
     53   public abstract Set<Entry<E>> entrySet();
     54 
     55   // Query Operations
     56 
     57   @Override public int size() {
     58     long sum = 0L;
     59     for (Entry<E> entry : entrySet()) {
     60       sum += entry.getCount();
     61     }
     62     return (int) Math.min(sum, Integer.MAX_VALUE);
     63   }
     64 
     65   @Override public boolean isEmpty() {
     66     return entrySet().isEmpty();
     67   }
     68 
     69   @Override public boolean contains(@Nullable Object element) {
     70     return elementSet().contains(element);
     71   }
     72 
     73   @Override public Iterator<E> iterator() {
     74     return new MultisetIterator();
     75   }
     76 
     77   private class MultisetIterator implements Iterator<E> {
     78     private final Iterator<Entry<E>> entryIterator;
     79     private Entry<E> currentEntry;
     80     /** Count of subsequent elements equal to current element */
     81     private int laterCount;
     82     /** Count of all elements equal to current element */
     83     private int totalCount;
     84     private boolean canRemove;
     85 
     86     MultisetIterator() {
     87       this.entryIterator = entrySet().iterator();
     88     }
     89 
     90     public boolean hasNext() {
     91       return laterCount > 0 || entryIterator.hasNext();
     92     }
     93 
     94     public E next() {
     95       if (!hasNext()) {
     96         throw new NoSuchElementException();
     97       }
     98       if (laterCount == 0) {
     99         currentEntry = entryIterator.next();
    100         totalCount = laterCount = currentEntry.getCount();
    101       }
    102       laterCount--;
    103       canRemove = true;
    104       return currentEntry.getElement();
    105     }
    106 
    107     public void remove() {
    108       checkState(canRemove,
    109           "no calls to next() since the last call to remove()");
    110       if (totalCount == 1) {
    111         entryIterator.remove();
    112       } else {
    113         AbstractMultiset.this.remove(currentEntry.getElement());
    114       }
    115       totalCount--;
    116       canRemove = false;
    117     }
    118   }
    119 
    120   public int count(Object element) {
    121     for (Entry<E> entry : entrySet()) {
    122       if (Objects.equal(entry.getElement(), element)) {
    123         return entry.getCount();
    124       }
    125     }
    126     return 0;
    127   }
    128 
    129   // Modification Operations
    130 
    131   @Override public boolean add(@Nullable E element) {
    132     add(element, 1);
    133     return true;
    134   }
    135 
    136   public int add(E element, int occurrences) {
    137     throw new UnsupportedOperationException();
    138   }
    139 
    140   @Override public boolean remove(Object element) {
    141     return remove(element, 1) > 0;
    142   }
    143 
    144   public int remove(Object element, int occurrences) {
    145     throw new UnsupportedOperationException();
    146   }
    147 
    148   public int setCount(E element, int count) {
    149     return setCountImpl(this, element, count);
    150   }
    151 
    152   public boolean setCount(E element, int oldCount, int newCount) {
    153     return setCountImpl(this, element, oldCount, newCount);
    154   }
    155 
    156   // Bulk Operations
    157 
    158   @Override public boolean containsAll(Collection<?> elements) {
    159     return elementSet().containsAll(elements);
    160   }
    161 
    162   @Override public boolean addAll(Collection<? extends E> elementsToAdd) {
    163     if (elementsToAdd.isEmpty()) {
    164       return false;
    165     }
    166     if (elementsToAdd instanceof Multiset) {
    167       @SuppressWarnings("unchecked")
    168       Multiset<? extends E> that = (Multiset<? extends E>) elementsToAdd;
    169       for (Entry<? extends E> entry : that.entrySet()) {
    170         add(entry.getElement(), entry.getCount());
    171       }
    172     } else {
    173       super.addAll(elementsToAdd);
    174     }
    175     return true;
    176   }
    177 
    178   @Override public boolean removeAll(Collection<?> elementsToRemove) {
    179     Collection<?> collection = (elementsToRemove instanceof Multiset)
    180         ? ((Multiset<?>) elementsToRemove).elementSet() : elementsToRemove;
    181 
    182     return elementSet().removeAll(collection);
    183     // TODO: implement retainAll similarly?
    184   }
    185 
    186   @Override public boolean retainAll(Collection<?> elementsToRetain) {
    187     checkNotNull(elementsToRetain);
    188     Iterator<Entry<E>> entries = entrySet().iterator();
    189     boolean modified = false;
    190     while (entries.hasNext()) {
    191       Entry<E> entry = entries.next();
    192       if (!elementsToRetain.contains(entry.getElement())) {
    193         entries.remove();
    194         modified = true;
    195       }
    196     }
    197     return modified;
    198   }
    199 
    200   @Override public void clear() {
    201     entrySet().clear();
    202   }
    203 
    204   // Views
    205 
    206   private transient Set<E> elementSet;
    207 
    208   public Set<E> elementSet() {
    209     Set<E> result = elementSet;
    210     if (result == null) {
    211       elementSet = result = createElementSet();
    212     }
    213     return result;
    214   }
    215 
    216   /**
    217    * Creates a new instance of this multiset's element set, which will be
    218    * returned by {@link #elementSet()}.
    219    */
    220   Set<E> createElementSet() {
    221     return new ElementSet();
    222   }
    223 
    224   private class ElementSet extends AbstractSet<E> {
    225     @Override public Iterator<E> iterator() {
    226       final Iterator<Entry<E>> entryIterator = entrySet().iterator();
    227       return new Iterator<E>() {
    228         public boolean hasNext() {
    229           return entryIterator.hasNext();
    230         }
    231         public E next() {
    232           return entryIterator.next().getElement();
    233         }
    234         public void remove() {
    235           entryIterator.remove();
    236         }
    237       };
    238     }
    239     @Override public int size() {
    240       return entrySet().size();
    241     }
    242   }
    243 
    244   // Object methods
    245 
    246   /**
    247    * {@inheritDoc}
    248    *
    249    * <p>This implementation returns {@code true} if {@code other} is a multiset
    250    * of the same size and if, for each element, the two multisets have the same
    251    * count.
    252    */
    253   @Override public boolean equals(@Nullable Object object) {
    254     if (object == this) {
    255       return true;
    256     }
    257     if (object instanceof Multiset) {
    258       Multiset<?> that = (Multiset<?>) object;
    259       /*
    260        * We can't simply check whether the entry sets are equal, since that
    261        * approach fails when a TreeMultiset has a comparator that returns 0
    262        * when passed unequal elements.
    263        */
    264 
    265       if (this.size() != that.size()) {
    266         return false;
    267       }
    268       for (Entry<?> entry : that.entrySet()) {
    269         if (count(entry.getElement()) != entry.getCount()) {
    270           return false;
    271         }
    272       }
    273       return true;
    274     }
    275     return false;
    276   }
    277 
    278   /**
    279    * {@inheritDoc}
    280    *
    281    * <p>This implementation returns the hash code of {@link
    282    * Multiset#entrySet()}.
    283    */
    284   @Override public int hashCode() {
    285     return entrySet().hashCode();
    286   }
    287 
    288   /**
    289    * {@inheritDoc}
    290    *
    291    * <p>This implementation returns the result of invoking {@code toString} on
    292    * {@link Multiset#entrySet()}.
    293    */
    294   @Override public String toString() {
    295     return entrySet().toString();
    296   }
    297 }
    298