Home | History | Annotate | Download | only in collect
      1 /*
      2  * Copyright (C) 2007 The Guava Authors
      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 static com.google.common.base.Preconditions.checkArgument;
     20 import static com.google.common.base.Preconditions.checkNotNull;
     21 import static com.google.common.collect.CollectPreconditions.checkNonnegative;
     22 import static com.google.common.collect.CollectPreconditions.checkRemove;
     23 
     24 import com.google.common.annotations.GwtCompatible;
     25 import com.google.common.annotations.GwtIncompatible;
     26 import com.google.common.primitives.Ints;
     27 
     28 import java.io.InvalidObjectException;
     29 import java.io.ObjectStreamException;
     30 import java.io.Serializable;
     31 import java.util.ConcurrentModificationException;
     32 import java.util.Iterator;
     33 import java.util.Map;
     34 import java.util.Set;
     35 
     36 import javax.annotation.Nullable;
     37 
     38 /**
     39  * Basic implementation of {@code Multiset<E>} backed by an instance of {@code
     40  * Map<E, Count>}.
     41  *
     42  * <p>For serialization to work, the subclass must specify explicit {@code
     43  * readObject} and {@code writeObject} methods.
     44  *
     45  * @author Kevin Bourrillion
     46  */
     47 @GwtCompatible(emulated = true)
     48 abstract class AbstractMapBasedMultiset<E> extends AbstractMultiset<E>
     49     implements Serializable {
     50 
     51   private transient Map<E, Count> backingMap;
     52 
     53   /*
     54    * Cache the size for efficiency. Using a long lets us avoid the need for
     55    * overflow checking and ensures that size() will function correctly even if
     56    * the multiset had once been larger than Integer.MAX_VALUE.
     57    */
     58   private transient long size;
     59 
     60   /** Standard constructor. */
     61   protected AbstractMapBasedMultiset(Map<E, Count> backingMap) {
     62     this.backingMap = checkNotNull(backingMap);
     63     this.size = super.size();
     64   }
     65 
     66   /** Used during deserialization only. The backing map must be empty. */
     67   void setBackingMap(Map<E, Count> backingMap) {
     68     this.backingMap = backingMap;
     69   }
     70 
     71   // Required Implementations
     72 
     73   /**
     74    * {@inheritDoc}
     75    *
     76    * <p>Invoking {@link Multiset.Entry#getCount} on an entry in the returned
     77    * set always returns the current count of that element in the multiset, as
     78    * opposed to the count at the time the entry was retrieved.
     79    */
     80   @Override
     81   public Set<Multiset.Entry<E>> entrySet() {
     82     return super.entrySet();
     83   }
     84 
     85   @Override
     86   Iterator<Entry<E>> entryIterator() {
     87     final Iterator<Map.Entry<E, Count>> backingEntries =
     88         backingMap.entrySet().iterator();
     89     return new Iterator<Multiset.Entry<E>>() {
     90       Map.Entry<E, Count> toRemove;
     91 
     92       @Override
     93       public boolean hasNext() {
     94         return backingEntries.hasNext();
     95       }
     96 
     97       @Override
     98       public Multiset.Entry<E> next() {
     99         final Map.Entry<E, Count> mapEntry = backingEntries.next();
    100         toRemove = mapEntry;
    101         return new Multisets.AbstractEntry<E>() {
    102           @Override
    103           public E getElement() {
    104             return mapEntry.getKey();
    105           }
    106           @Override
    107           public int getCount() {
    108             Count count = mapEntry.getValue();
    109             if (count == null || count.get() == 0) {
    110               Count frequency = backingMap.get(getElement());
    111               if (frequency != null) {
    112                 return frequency.get();
    113               }
    114             }
    115             return (count == null) ? 0 : count.get();
    116           }
    117         };
    118       }
    119 
    120       @Override
    121       public void remove() {
    122         checkRemove(toRemove != null);
    123         size -= toRemove.getValue().getAndSet(0);
    124         backingEntries.remove();
    125         toRemove = null;
    126       }
    127     };
    128   }
    129 
    130   @Override
    131   public void clear() {
    132     for (Count frequency : backingMap.values()) {
    133       frequency.set(0);
    134     }
    135     backingMap.clear();
    136     size = 0L;
    137   }
    138 
    139   @Override
    140   int distinctElements() {
    141     return backingMap.size();
    142   }
    143 
    144   // Optimizations - Query Operations
    145 
    146   @Override public int size() {
    147     return Ints.saturatedCast(size);
    148   }
    149 
    150   @Override public Iterator<E> iterator() {
    151     return new MapBasedMultisetIterator();
    152   }
    153 
    154   /*
    155    * Not subclassing AbstractMultiset$MultisetIterator because next() needs to
    156    * retrieve the Map.Entry<E, Count> entry, which can then be used for
    157    * a more efficient remove() call.
    158    */
    159   private class MapBasedMultisetIterator implements Iterator<E> {
    160     final Iterator<Map.Entry<E, Count>> entryIterator;
    161     Map.Entry<E, Count> currentEntry;
    162     int occurrencesLeft;
    163     boolean canRemove;
    164 
    165     MapBasedMultisetIterator() {
    166       this.entryIterator = backingMap.entrySet().iterator();
    167     }
    168 
    169     @Override
    170     public boolean hasNext() {
    171       return occurrencesLeft > 0 || entryIterator.hasNext();
    172     }
    173 
    174     @Override
    175     public E next() {
    176       if (occurrencesLeft == 0) {
    177         currentEntry = entryIterator.next();
    178         occurrencesLeft = currentEntry.getValue().get();
    179       }
    180       occurrencesLeft--;
    181       canRemove = true;
    182       return currentEntry.getKey();
    183     }
    184 
    185     @Override
    186     public void remove() {
    187       checkRemove(canRemove);
    188       int frequency = currentEntry.getValue().get();
    189       if (frequency <= 0) {
    190         throw new ConcurrentModificationException();
    191       }
    192       if (currentEntry.getValue().addAndGet(-1) == 0) {
    193         entryIterator.remove();
    194       }
    195       size--;
    196       canRemove = false;
    197     }
    198   }
    199 
    200   @Override public int count(@Nullable Object element) {
    201     Count frequency = Maps.safeGet(backingMap, element);
    202     return (frequency == null) ? 0 : frequency.get();
    203   }
    204 
    205   // Optional Operations - Modification Operations
    206 
    207   /**
    208    * {@inheritDoc}
    209    *
    210    * @throws IllegalArgumentException if the call would result in more than
    211    *     {@link Integer#MAX_VALUE} occurrences of {@code element} in this
    212    *     multiset.
    213    */
    214   @Override public int add(@Nullable E element, int occurrences) {
    215     if (occurrences == 0) {
    216       return count(element);
    217     }
    218     checkArgument(
    219         occurrences > 0, "occurrences cannot be negative: %s", occurrences);
    220     Count frequency = backingMap.get(element);
    221     int oldCount;
    222     if (frequency == null) {
    223       oldCount = 0;
    224       backingMap.put(element, new Count(occurrences));
    225     } else {
    226       oldCount = frequency.get();
    227       long newCount = (long) oldCount + (long) occurrences;
    228       checkArgument(newCount <= Integer.MAX_VALUE,
    229           "too many occurrences: %s", newCount);
    230       frequency.getAndAdd(occurrences);
    231     }
    232     size += occurrences;
    233     return oldCount;
    234   }
    235 
    236   @Override public int remove(@Nullable Object element, int occurrences) {
    237     if (occurrences == 0) {
    238       return count(element);
    239     }
    240     checkArgument(
    241         occurrences > 0, "occurrences cannot be negative: %s", occurrences);
    242     Count frequency = backingMap.get(element);
    243     if (frequency == null) {
    244       return 0;
    245     }
    246 
    247     int oldCount = frequency.get();
    248 
    249     int numberRemoved;
    250     if (oldCount > occurrences) {
    251       numberRemoved = occurrences;
    252     } else {
    253       numberRemoved = oldCount;
    254       backingMap.remove(element);
    255     }
    256 
    257     frequency.addAndGet(-numberRemoved);
    258     size -= numberRemoved;
    259     return oldCount;
    260   }
    261 
    262   // Roughly a 33% performance improvement over AbstractMultiset.setCount().
    263   @Override public int setCount(@Nullable E element, int count) {
    264     checkNonnegative(count, "count");
    265 
    266     Count existingCounter;
    267     int oldCount;
    268     if (count == 0) {
    269       existingCounter = backingMap.remove(element);
    270       oldCount = getAndSet(existingCounter, count);
    271     } else {
    272       existingCounter = backingMap.get(element);
    273       oldCount = getAndSet(existingCounter, count);
    274 
    275       if (existingCounter == null) {
    276         backingMap.put(element, new Count(count));
    277       }
    278     }
    279 
    280     size += (count - oldCount);
    281     return oldCount;
    282   }
    283 
    284   private static int getAndSet(Count i, int count) {
    285     if (i == null) {
    286       return 0;
    287     }
    288 
    289     return i.getAndSet(count);
    290   }
    291 
    292   // Don't allow default serialization.
    293   @GwtIncompatible("java.io.ObjectStreamException")
    294   @SuppressWarnings("unused") // actually used during deserialization
    295   private void readObjectNoData() throws ObjectStreamException {
    296     throw new InvalidObjectException("Stream data required");
    297   }
    298 
    299   @GwtIncompatible("not needed in emulated source.")
    300   private static final long serialVersionUID = -2250766705698539974L;
    301 }
    302