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