1 /* 2 * Copyright (C) 2007 The Guava Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 15 package com.google.common.collect; 16 17 import static com.google.common.base.Preconditions.checkArgument; 18 19 import com.google.common.annotations.GwtCompatible; 20 import com.google.common.annotations.GwtIncompatible; 21 import com.google.common.base.Objects; 22 import com.google.common.collect.testing.features.CollectionFeature; 23 import com.google.common.collect.testing.features.CollectionSize; 24 import com.google.common.collect.testing.google.MultisetTestSuiteBuilder; 25 import com.google.common.collect.testing.google.TestStringMultisetGenerator; 26 import java.io.Serializable; 27 import java.util.Collections; 28 import java.util.Iterator; 29 import java.util.Map; 30 import java.util.concurrent.atomic.AtomicInteger; 31 import junit.framework.Test; 32 import junit.framework.TestCase; 33 import junit.framework.TestSuite; 34 import org.checkerframework.checker.nullness.qual.Nullable; 35 36 /** 37 * Unit test for {@link AbstractMultiset}. 38 * 39 * @author Kevin Bourrillion 40 * @author Louis Wasserman 41 */ 42 @SuppressWarnings("serial") // No serialization is used in this test 43 @GwtCompatible(emulated = true) 44 public class SimpleAbstractMultisetTest extends TestCase { 45 @GwtIncompatible // suite suite()46 public static Test suite() { 47 TestSuite suite = new TestSuite(); 48 suite.addTestSuite(SimpleAbstractMultisetTest.class); 49 suite.addTest( 50 MultisetTestSuiteBuilder.using( 51 new TestStringMultisetGenerator() { 52 @Override 53 protected Multiset<String> create(String[] elements) { 54 Multiset<String> ms = new NoRemoveMultiset<>(); 55 Collections.addAll(ms, elements); 56 return ms; 57 } 58 }) 59 .named("NoRemoveMultiset") 60 .withFeatures( 61 CollectionSize.ANY, 62 CollectionFeature.ALLOWS_NULL_VALUES, 63 CollectionFeature.SUPPORTS_ADD) 64 .createTestSuite()); 65 return suite; 66 } 67 testFastAddAllMultiset()68 public void testFastAddAllMultiset() { 69 final AtomicInteger addCalls = new AtomicInteger(); 70 Multiset<String> multiset = 71 new NoRemoveMultiset<String>() { 72 @Override 73 public int add(String element, int occurrences) { 74 addCalls.incrementAndGet(); 75 return super.add(element, occurrences); 76 } 77 }; 78 ImmutableMultiset<String> adds = 79 new ImmutableMultiset.Builder<String>().addCopies("x", 10).build(); 80 multiset.addAll(adds); 81 assertEquals(1, addCalls.get()); 82 } 83 testRemoveUnsupported()84 public void testRemoveUnsupported() { 85 Multiset<String> multiset = new NoRemoveMultiset<>(); 86 multiset.add("a"); 87 try { 88 multiset.remove("a"); 89 fail(); 90 } catch (UnsupportedOperationException expected) { 91 } 92 assertTrue(multiset.contains("a")); 93 } 94 95 private static class NoRemoveMultiset<E> extends AbstractMultiset<E> implements Serializable { 96 final Map<E, Integer> backingMap = Maps.newHashMap(); 97 98 @Override size()99 public int size() { 100 return Multisets.linearTimeSizeImpl(this); 101 } 102 103 @Override clear()104 public void clear() { 105 throw new UnsupportedOperationException(); 106 } 107 108 @Override count(@ullable Object element)109 public int count(@Nullable Object element) { 110 for (Entry<E> entry : entrySet()) { 111 if (Objects.equal(entry.getElement(), element)) { 112 return entry.getCount(); 113 } 114 } 115 return 0; 116 } 117 118 @Override add(@ullable E element, int occurrences)119 public int add(@Nullable E element, int occurrences) { 120 checkArgument(occurrences >= 0); 121 Integer frequency = backingMap.get(element); 122 if (frequency == null) { 123 frequency = 0; 124 } 125 if (occurrences == 0) { 126 return frequency; 127 } 128 checkArgument(occurrences <= Integer.MAX_VALUE - frequency); 129 backingMap.put(element, frequency + occurrences); 130 return frequency; 131 } 132 133 @Override elementIterator()134 Iterator<E> elementIterator() { 135 return Multisets.elementIterator(entryIterator()); 136 } 137 138 @Override entryIterator()139 Iterator<Entry<E>> entryIterator() { 140 final Iterator<Map.Entry<E, Integer>> backingEntries = backingMap.entrySet().iterator(); 141 return new UnmodifiableIterator<Multiset.Entry<E>>() { 142 @Override 143 public boolean hasNext() { 144 return backingEntries.hasNext(); 145 } 146 147 @Override 148 public Multiset.Entry<E> next() { 149 final Map.Entry<E, Integer> mapEntry = backingEntries.next(); 150 return new Multisets.AbstractEntry<E>() { 151 @Override 152 public E getElement() { 153 return mapEntry.getKey(); 154 } 155 156 @Override 157 public int getCount() { 158 Integer frequency = backingMap.get(getElement()); 159 return (frequency == null) ? 0 : frequency; 160 } 161 }; 162 } 163 }; 164 } 165 166 @Override 167 public Iterator<E> iterator() { 168 return Multisets.iteratorImpl(this); 169 } 170 171 @Override 172 int distinctElements() { 173 return backingMap.size(); 174 } 175 } 176 } 177