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