001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.collections4.multiset; 018 019import java.io.IOException; 020import java.io.ObjectInputStream; 021import java.io.ObjectOutputStream; 022import java.util.AbstractCollection; 023import java.util.AbstractSet; 024import java.util.Collection; 025import java.util.Iterator; 026import java.util.Set; 027 028import org.apache.commons.collections4.IteratorUtils; 029import org.apache.commons.collections4.MultiSet; 030import org.apache.commons.collections4.Transformer; 031 032/** 033 * Abstract implementation of the {@link MultiSet} interface to simplify the 034 * creation of subclass implementations. 035 * 036 * @param <E> the type held in the multiset 037 * @since 4.1 038 */ 039public abstract class AbstractMultiSet<E> extends AbstractCollection<E> implements MultiSet<E> { 040 041 /** View of the elements */ 042 private transient Set<E> uniqueSet; 043 /** View of the entries */ 044 private transient Set<Entry<E>> entrySet; 045 046 /** 047 * Constructor needed for subclass serialisation. 048 */ 049 protected AbstractMultiSet() { 050 super(); 051 } 052 053 //----------------------------------------------------------------------- 054 /** 055 * Returns the number of elements in this multiset. 056 * 057 * @return current size of the multiset 058 */ 059 @Override 060 public int size() { 061 int totalSize = 0; 062 for (final Entry<E> entry : entrySet()) { 063 totalSize += entry.getCount(); 064 } 065 return totalSize; 066 } 067 068 /** 069 * Returns the number of occurrence of the given element in this multiset by 070 * iterating over its entrySet. 071 * 072 * @param object the object to search for 073 * @return the number of occurrences of the object, zero if not found 074 */ 075 @Override 076 public int getCount(final Object object) { 077 for (final Entry<E> entry : entrySet()) { 078 final E element = entry.getElement(); 079 if (element == object || 080 element != null && element.equals(object)) { 081 return entry.getCount(); 082 } 083 } 084 return 0; 085 } 086 087 @Override 088 public int setCount(final E object, final int count) { 089 if (count < 0) { 090 throw new IllegalArgumentException("Count must not be negative."); 091 } 092 093 final int oldCount = getCount(object); 094 if (oldCount < count) { 095 add(object, count - oldCount); 096 } else { 097 remove(object, oldCount - count); 098 } 099 return oldCount; 100 } 101 102 //----------------------------------------------------------------------- 103 /** 104 * Determines if the multiset contains the given element. 105 * 106 * @param object the object to search for 107 * @return true if the multiset contains the given element 108 */ 109 @Override 110 public boolean contains(final Object object) { 111 return getCount(object) > 0; 112 } 113 114 //----------------------------------------------------------------------- 115 /** 116 * Gets an iterator over the multiset elements. Elements present in the 117 * MultiSet more than once will be returned repeatedly. 118 * 119 * @return the iterator 120 */ 121 @Override 122 public Iterator<E> iterator() { 123 return new MultiSetIterator<>(this); 124 } 125 126 /** 127 * Inner class iterator for the MultiSet. 128 */ 129 private static class MultiSetIterator<E> implements Iterator<E> { 130 private final AbstractMultiSet<E> parent; 131 private final Iterator<Entry<E>> entryIterator; 132 private Entry<E> current; 133 private int itemCount; 134 private boolean canRemove; 135 136 /** 137 * Constructor. 138 * 139 * @param parent the parent multiset 140 */ 141 public MultiSetIterator(final AbstractMultiSet<E> parent) { 142 this.parent = parent; 143 this.entryIterator = parent.entrySet().iterator(); 144 this.current = null; 145 this.canRemove = false; 146 } 147 148 /** {@inheritDoc} */ 149 @Override 150 public boolean hasNext() { 151 return itemCount > 0 || entryIterator.hasNext(); 152 } 153 154 /** {@inheritDoc} */ 155 @Override 156 public E next() { 157 if (itemCount == 0) { 158 current = entryIterator.next(); 159 itemCount = current.getCount(); 160 } 161 canRemove = true; 162 itemCount--; 163 return current.getElement(); 164 } 165 166 /** {@inheritDoc} */ 167 @Override 168 public void remove() { 169 if (canRemove == false) { 170 throw new IllegalStateException(); 171 } 172 final int count = current.getCount(); 173 if (count > 1) { 174 parent.remove(current.getElement()); 175 } else { 176 entryIterator.remove(); 177 } 178 canRemove = false; 179 } 180 } 181 182 //----------------------------------------------------------------------- 183 @Override 184 public boolean add(final E object) { 185 add(object, 1); 186 return true; 187 } 188 189 @Override 190 public int add(final E object, final int occurrences) { 191 throw new UnsupportedOperationException(); 192 } 193 194 //----------------------------------------------------------------------- 195 /** 196 * Clears the multiset removing all elements from the entrySet. 197 */ 198 @Override 199 public void clear() { 200 final Iterator<Entry<E>> it = entrySet().iterator(); 201 while (it.hasNext()) { 202 it.next(); 203 it.remove(); 204 } 205 } 206 207 @Override 208 public boolean remove(final Object object) { 209 return remove(object, 1) != 0; 210 } 211 212 @Override 213 public int remove(final Object object, final int occurrences) { 214 throw new UnsupportedOperationException(); 215 } 216 217 @Override 218 public boolean removeAll(final Collection<?> coll) { 219 boolean result = false; 220 final Iterator<?> i = coll.iterator(); 221 while (i.hasNext()) { 222 final Object obj = i.next(); 223 final boolean changed = remove(obj, getCount(obj)) != 0; 224 result = result || changed; 225 } 226 return result; 227 } 228 229 //----------------------------------------------------------------------- 230 /** 231 * Returns a view of the unique elements of this multiset. 232 * 233 * @return the set of unique elements in this multiset 234 */ 235 @Override 236 public Set<E> uniqueSet() { 237 if (uniqueSet == null) { 238 uniqueSet = createUniqueSet(); 239 } 240 return uniqueSet; 241 } 242 243 /** 244 * Create a new view for the set of unique elements in this multiset. 245 * 246 * @return a view of the set of unique elements 247 */ 248 protected Set<E> createUniqueSet() { 249 return new UniqueSet<>(this); 250 } 251 252 /** 253 * Creates a unique set iterator. 254 * Subclasses can override this to return iterators with different properties. 255 * 256 * @return the uniqueSet iterator 257 */ 258 protected Iterator<E> createUniqueSetIterator() { 259 final Transformer<Entry<E>, E> transformer = new Transformer<Entry<E>, E>() { 260 @Override 261 public E transform(final Entry<E> entry) { 262 return entry.getElement(); 263 } 264 }; 265 return IteratorUtils.transformedIterator(entrySet().iterator(), transformer); 266 } 267 268 /** 269 * Returns an unmodifiable view of the entries of this multiset. 270 * 271 * @return the set of entries in this multiset 272 */ 273 @Override 274 public Set<Entry<E>> entrySet() { 275 if (entrySet == null) { 276 entrySet = createEntrySet(); 277 } 278 return entrySet; 279 } 280 281 /** 282 * Create a new view for the set of entries in this multiset. 283 * 284 * @return a view of the set of entries 285 */ 286 protected Set<Entry<E>> createEntrySet() { 287 return new EntrySet<>(this); 288 } 289 290 /** 291 * Returns the number of unique elements in this multiset. 292 * 293 * @return the number of unique elements 294 */ 295 protected abstract int uniqueElements(); 296 297 /** 298 * Creates an entry set iterator. 299 * Subclasses can override this to return iterators with different properties. 300 * 301 * @return the entrySet iterator 302 */ 303 protected abstract Iterator<Entry<E>> createEntrySetIterator(); 304 305 //----------------------------------------------------------------------- 306 /** 307 * Inner class UniqueSet. 308 */ 309 protected static class UniqueSet<E> extends AbstractSet<E> { 310 311 /** The parent multiset */ 312 protected final AbstractMultiSet<E> parent; 313 314 /** 315 * Constructs a new unique element view of the MultiSet. 316 * 317 * @param parent the parent MultiSet 318 */ 319 protected UniqueSet(final AbstractMultiSet<E> parent) { 320 this.parent = parent; 321 } 322 323 @Override 324 public Iterator<E> iterator() { 325 return parent.createUniqueSetIterator(); 326 } 327 328 @Override 329 public boolean contains(final Object key) { 330 return parent.contains(key); 331 } 332 333 @Override 334 public boolean containsAll(final Collection<?> coll) { 335 return parent.containsAll(coll); 336 } 337 338 @Override 339 public boolean remove(final Object key) { 340 return parent.remove(key, parent.getCount(key)) != 0; 341 } 342 343 @Override 344 public int size() { 345 return parent.uniqueElements(); 346 } 347 348 @Override 349 public void clear() { 350 parent.clear(); 351 } 352 } 353 354 //----------------------------------------------------------------------- 355 /** 356 * Inner class EntrySet. 357 */ 358 protected static class EntrySet<E> extends AbstractSet<Entry<E>> { 359 360 private final AbstractMultiSet<E> parent; 361 362 /** 363 * Constructs a new view of the MultiSet. 364 * 365 * @param parent the parent MultiSet 366 */ 367 protected EntrySet(final AbstractMultiSet<E> parent) { 368 this.parent = parent; 369 } 370 371 @Override 372 public int size() { 373 return parent.uniqueElements(); 374 } 375 376 @Override 377 public Iterator<Entry<E>> iterator() { 378 return parent.createEntrySetIterator(); 379 } 380 381 @Override 382 public boolean contains(final Object obj) { 383 if (obj instanceof Entry<?> == false) { 384 return false; 385 } 386 final Entry<?> entry = (Entry<?>) obj; 387 final Object element = entry.getElement(); 388 return parent.getCount(element) == entry.getCount(); 389 } 390 391 @Override 392 public boolean remove(final Object obj) { 393 if (obj instanceof Entry<?> == false) { 394 return false; 395 } 396 final Entry<?> entry = (Entry<?>) obj; 397 final Object element = entry.getElement(); 398 if (parent.contains(element)) { 399 final int count = parent.getCount(element); 400 if (entry.getCount() == count) { 401 parent.remove(element, count); 402 return true; 403 } 404 } 405 return false; 406 } 407 } 408 409 /** 410 * Inner class AbstractEntry. 411 */ 412 protected static abstract class AbstractEntry<E> implements Entry<E> { 413 414 @Override 415 public boolean equals(final Object object) { 416 if (object instanceof Entry) { 417 final Entry<?> other = (Entry<?>) object; 418 final E element = this.getElement(); 419 final Object otherElement = other.getElement(); 420 421 return this.getCount() == other.getCount() && 422 (element == otherElement || 423 element != null && element.equals(otherElement)); 424 } 425 return false; 426 } 427 428 @Override 429 public int hashCode() { 430 final E element = getElement(); 431 return ((element == null) ? 0 : element.hashCode()) ^ getCount(); 432 } 433 434 @Override 435 public String toString() { 436 return String.format("%s:%d", getElement(), getCount()); 437 } 438 439 } 440 441 //----------------------------------------------------------------------- 442 /** 443 * Write the multiset out using a custom routine. 444 * @param out the output stream 445 * @throws IOException any of the usual I/O related exceptions 446 */ 447 protected void doWriteObject(final ObjectOutputStream out) throws IOException { 448 out.writeInt(entrySet().size()); 449 for (final Entry<E> entry : entrySet()) { 450 out.writeObject(entry.getElement()); 451 out.writeInt(entry.getCount()); 452 } 453 } 454 455 /** 456 * Read the multiset in using a custom routine. 457 * @param in the input stream 458 * @throws IOException any of the usual I/O related exceptions 459 * @throws ClassNotFoundException if the stream contains an object which class can not be loaded 460 * @throws ClassCastException if the stream does not contain the correct objects 461 */ 462 protected void doReadObject(final ObjectInputStream in) 463 throws IOException, ClassNotFoundException { 464 final int entrySize = in.readInt(); 465 for (int i = 0; i < entrySize; i++) { 466 @SuppressWarnings("unchecked") // This will fail at runtime if the stream is incorrect 467 final E obj = (E) in.readObject(); 468 final int count = in.readInt(); 469 setCount(obj, count); 470 } 471 } 472 473 //----------------------------------------------------------------------- 474 @Override 475 public boolean equals(final Object object) { 476 if (object == this) { 477 return true; 478 } 479 if (object instanceof MultiSet == false) { 480 return false; 481 } 482 final MultiSet<?> other = (MultiSet<?>) object; 483 if (other.size() != size()) { 484 return false; 485 } 486 for (final Entry<E> entry : entrySet()) { 487 if (other.getCount(entry.getElement()) != getCount(entry.getElement())) { 488 return false; 489 } 490 } 491 return true; 492 } 493 494 @Override 495 public int hashCode() { 496 return entrySet().hashCode(); 497 } 498 499 /** 500 * Implement a toString() method suitable for debugging. 501 * 502 * @return a debugging toString 503 */ 504 @Override 505 public String toString() { 506 return entrySet().toString(); 507 } 508 509}