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