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. */ AbstractMapBasedMultiset(Map<E, Count> backingMap)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. */ setBackingMap(Map<E, Count> backingMap)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 entrySet()81 public Set<Multiset.Entry<E>> entrySet() { 82 return super.entrySet(); 83 } 84 85 @Override entryIterator()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