View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.collections4.multiset;
18  
19  import java.io.IOException;
20  import java.io.ObjectInputStream;
21  import java.io.ObjectOutputStream;
22  import java.util.AbstractCollection;
23  import java.util.AbstractSet;
24  import java.util.Collection;
25  import java.util.Iterator;
26  import java.util.Objects;
27  import java.util.Set;
28  
29  import org.apache.commons.collections4.IteratorUtils;
30  import org.apache.commons.collections4.MultiSet;
31  import org.apache.commons.collections4.Transformer;
32  
33  /**
34   * Abstract implementation of the {@link MultiSet} interface to simplify the
35   * creation of subclass implementations.
36   *
37   * @param <E> the type held in the multiset
38   * @since 4.1
39   */
40  public abstract class AbstractMultiSet<E> extends AbstractCollection<E> implements MultiSet<E> {
41  
42      /**
43       * Inner class AbstractEntry.
44       *
45       * @param <E> the element type.
46       */
47      protected abstract static class AbstractEntry<E> implements Entry<E> {
48  
49          @Override
50          public boolean equals(final Object object) {
51              if (object instanceof Entry) {
52                  final Entry<?> other = (Entry<?>) object;
53                  final E element = getElement();
54                  final Object otherElement = other.getElement();
55  
56                  return this.getCount() == other.getCount() &&
57                         Objects.equals(element, otherElement);
58              }
59              return false;
60          }
61  
62          @Override
63          public int hashCode() {
64              final E element = getElement();
65              return (element == null ? 0 : element.hashCode()) ^ getCount();
66          }
67  
68          @Override
69          public String toString() {
70              return String.format("%s:%d", getElement(), getCount());
71          }
72      }
73  
74      /**
75       * Inner class EntrySet.
76       *
77       * @param <E> the element type.
78       */
79      protected static class EntrySet<E> extends AbstractSet<Entry<E>> {
80  
81          private final AbstractMultiSet<E> parent;
82  
83          /**
84           * Constructs a new view of the MultiSet.
85           *
86           * @param parent  the parent MultiSet
87           */
88          protected EntrySet(final AbstractMultiSet<E> parent) {
89              this.parent = parent;
90          }
91  
92          @Override
93          public boolean contains(final Object obj) {
94              if (!(obj instanceof Entry<?>)) {
95                  return false;
96              }
97              final Entry<?> entry = (Entry<?>) obj;
98              final Object element = entry.getElement();
99              return parent.getCount(element) == entry.getCount();
100         }
101 
102         @Override
103         public Iterator<Entry<E>> iterator() {
104             return parent.createEntrySetIterator();
105         }
106 
107         @Override
108         public boolean remove(final Object obj) {
109             if (!(obj instanceof Entry<?>)) {
110                 return false;
111             }
112             final Entry<?> entry = (Entry<?>) obj;
113             final Object element = entry.getElement();
114             if (parent.contains(element)) {
115                 final int count = parent.getCount(element);
116                 if (entry.getCount() == count) {
117                     parent.remove(element, count);
118                     return true;
119                 }
120             }
121             return false;
122         }
123 
124         @Override
125         public int size() {
126             return parent.uniqueElements();
127         }
128     }
129 
130     /**
131      * Inner class iterator for the MultiSet.
132      */
133     private static final class MultiSetIterator<E> implements Iterator<E> {
134         private final AbstractMultiSet<E> parent;
135         private final Iterator<Entry<E>> entryIterator;
136         private Entry<E> current;
137         private int itemCount;
138         private boolean canRemove;
139 
140         /**
141          * Constructs a new instance.
142          *
143          * @param parent the parent multiset
144          */
145         MultiSetIterator(final AbstractMultiSet<E> parent) {
146             this.parent = parent;
147             this.entryIterator = parent.entrySet().iterator();
148             this.current = null;
149             this.canRemove = false;
150         }
151 
152         /** {@inheritDoc} */
153         @Override
154         public boolean hasNext() {
155             return itemCount > 0 || entryIterator.hasNext();
156         }
157 
158         /** {@inheritDoc} */
159         @Override
160         public E next() {
161             if (itemCount == 0) {
162                 current = entryIterator.next();
163                 itemCount = current.getCount();
164             }
165             canRemove = true;
166             itemCount--;
167             return current.getElement();
168         }
169 
170         /** {@inheritDoc} */
171         @Override
172         public void remove() {
173             if (!canRemove) {
174                 throw new IllegalStateException();
175             }
176             final int count = current.getCount();
177             if (count > 1) {
178                 parent.remove(current.getElement());
179             } else {
180                 entryIterator.remove();
181             }
182             canRemove = false;
183         }
184     }
185 
186     /**
187      * Inner class UniqueSet.
188      *
189      * @param <E> the element type.
190      */
191     protected static class UniqueSet<E> extends AbstractSet<E> {
192 
193         /** The parent multiset */
194         protected final AbstractMultiSet<E> parent;
195 
196         /**
197          * Constructs a new unique element view of the MultiSet.
198          *
199          * @param parent  the parent MultiSet
200          */
201         protected UniqueSet(final AbstractMultiSet<E> parent) {
202             this.parent = parent;
203         }
204 
205         @Override
206         public void clear() {
207             parent.clear();
208         }
209 
210         @Override
211         public boolean contains(final Object key) {
212             return parent.contains(key);
213         }
214 
215         @Override
216         public boolean containsAll(final Collection<?> coll) {
217             return parent.containsAll(coll);
218         }
219 
220         @Override
221         public Iterator<E> iterator() {
222             return parent.createUniqueSetIterator();
223         }
224 
225         @Override
226         public boolean remove(final Object key) {
227             return parent.remove(key, parent.getCount(key)) != 0;
228         }
229 
230         @Override
231         public int size() {
232             return parent.uniqueElements();
233         }
234     }
235 
236     /** View of the elements */
237     private transient Set<E> uniqueSet;
238 
239     /** View of the entries */
240     private transient Set<Entry<E>> entrySet;
241 
242     /**
243      * Constructor needed for subclass serialisation.
244      */
245     protected AbstractMultiSet() {
246     }
247 
248     @Override
249     public boolean add(final E object) {
250         add(object, 1);
251         return true;
252     }
253 
254     @Override
255     public int add(final E object, final int occurrences) {
256         throw new UnsupportedOperationException();
257     }
258 
259     /**
260      * Clears the multiset removing all elements from the entrySet.
261      */
262     @Override
263     public void clear() {
264         final Iterator<Entry<E>> it = entrySet().iterator();
265         while (it.hasNext()) {
266             it.next();
267             it.remove();
268         }
269     }
270 
271     /**
272      * Determines if the multiset contains the given element.
273      *
274      * @param object the object to search for
275      * @return true if the multiset contains the given element
276      */
277     @Override
278     public boolean contains(final Object object) {
279         return getCount(object) > 0;
280     }
281 
282     /**
283      * Create a new view for the set of entries in this multiset.
284      *
285      * @return a view of the set of entries
286      */
287     protected Set<Entry<E>> createEntrySet() {
288         return new EntrySet<>(this);
289     }
290 
291     /**
292      * Creates an entry set iterator.
293      * Subclasses can override this to return iterators with different properties.
294      *
295      * @return the entrySet iterator
296      */
297     protected abstract Iterator<Entry<E>> createEntrySetIterator();
298 
299     /**
300      * Create a new view for the set of unique elements in this multiset.
301      *
302      * @return a view of the set of unique elements
303      */
304     protected Set<E> createUniqueSet() {
305         return new UniqueSet<>(this);
306     }
307 
308     /**
309      * Creates a unique set iterator.
310      * Subclasses can override this to return iterators with different properties.
311      *
312      * @return the uniqueSet iterator
313      */
314     protected Iterator<E> createUniqueSetIterator() {
315         final Transformer<Entry<E>, E> transformer = Entry::getElement;
316         return IteratorUtils.transformedIterator(entrySet().iterator(), transformer);
317     }
318 
319     /**
320      * Read the multiset in using a custom routine.
321      * @param in the input stream
322      * @throws IOException any of the usual I/O related exceptions
323      * @throws ClassNotFoundException if the stream contains an object which class cannot be loaded
324      * @throws ClassCastException if the stream does not contain the correct objects
325      */
326     protected void doReadObject(final ObjectInputStream in)
327             throws IOException, ClassNotFoundException {
328         final int entrySize = in.readInt();
329         for (int i = 0; i < entrySize; i++) {
330             @SuppressWarnings("unchecked") // This will fail at runtime if the stream is incorrect
331             final E obj = (E) in.readObject();
332             final int count = in.readInt();
333             setCount(obj, count);
334         }
335     }
336 
337     /**
338      * Write the multiset out using a custom routine.
339      * @param out the output stream
340      * @throws IOException any of the usual I/O related exceptions
341      */
342     protected void doWriteObject(final ObjectOutputStream out) throws IOException {
343         out.writeInt(entrySet().size());
344         for (final Entry<E> entry : entrySet()) {
345             out.writeObject(entry.getElement());
346             out.writeInt(entry.getCount());
347         }
348     }
349 
350     /**
351      * Returns an unmodifiable view of the entries of this multiset.
352      *
353      * @return the set of entries in this multiset
354      */
355     @Override
356     public Set<Entry<E>> entrySet() {
357         if (entrySet == null) {
358             entrySet = createEntrySet();
359         }
360         return entrySet;
361     }
362 
363     @Override
364     public boolean equals(final Object object) {
365         if (object == this) {
366             return true;
367         }
368         if (!(object instanceof MultiSet)) {
369             return false;
370         }
371         final MultiSet<?> other = (MultiSet<?>) object;
372         if (other.size() != size()) {
373             return false;
374         }
375         for (final Entry<E> entry : entrySet()) {
376             if (other.getCount(entry.getElement()) != getCount(entry.getElement())) {
377                 return false;
378             }
379         }
380         return true;
381     }
382 
383     /**
384      * Returns the number of occurrence of the given element in this multiset by
385      * iterating over its entrySet.
386      *
387      * @param object the object to search for
388      * @return the number of occurrences of the object, zero if not found
389      */
390     @Override
391     public int getCount(final Object object) {
392         for (final Entry<E> entry : entrySet()) {
393             final E element = entry.getElement();
394             if (Objects.equals(element, object)) {
395                 return entry.getCount();
396             }
397         }
398         return 0;
399     }
400 
401     @Override
402     public int hashCode() {
403         return entrySet().hashCode();
404     }
405 
406     /**
407      * Gets an iterator over the multiset elements. Elements present in the
408      * MultiSet more than once will be returned repeatedly.
409      *
410      * @return the iterator
411      */
412     @Override
413     public Iterator<E> iterator() {
414         return new MultiSetIterator<>(this);
415     }
416 
417     @Override
418     public boolean remove(final Object object) {
419         return remove(object, 1) != 0;
420     }
421 
422     @Override
423     public int remove(final Object object, final int occurrences) {
424         throw new UnsupportedOperationException();
425     }
426 
427     @Override
428     public boolean removeAll(final Collection<?> coll) {
429         boolean result = false;
430         for (final Object obj : coll) {
431             final boolean changed = remove(obj, getCount(obj)) != 0;
432             result = result || changed;
433         }
434         return result;
435     }
436 
437     @Override
438     public int setCount(final E object, final int count) {
439         if (count < 0) {
440             throw new IllegalArgumentException("Count must not be negative.");
441         }
442 
443         final int oldCount = getCount(object);
444         if (oldCount < count) {
445             add(object, count - oldCount);
446         } else {
447             remove(object, oldCount - count);
448         }
449         return oldCount;
450     }
451 
452     /**
453      * Returns the number of elements in this multiset.
454      *
455      * @return current size of the multiset
456      */
457     @Override
458     public int size() {
459         int totalSize = 0;
460         for (final Entry<E> entry : entrySet()) {
461             totalSize += entry.getCount();
462         }
463         return totalSize;
464     }
465 
466     /**
467      * Implement a toString() method suitable for debugging.
468      *
469      * @return a debugging toString
470      */
471     @Override
472     public String toString() {
473         return entrySet().toString();
474     }
475 
476     /**
477      * Returns the number of unique elements in this multiset.
478      *
479      * @return the number of unique elements
480      */
481     protected abstract int uniqueElements();
482 
483     /**
484      * Returns a view of the unique elements of this multiset.
485      *
486      * @return the set of unique elements in this multiset
487      */
488     @Override
489     public Set<E> uniqueSet() {
490         if (uniqueSet == null) {
491             uniqueSet = createUniqueSet();
492         }
493         return uniqueSet;
494     }
495 
496 }