/* Copyright (c) 2008-2010, developers of the Ascension Log Visualizer
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom
* the Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
package com.googlecode.logVisualizer.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* This class is a container for a collection of {@link Countable} instances
* that allows no duplicate elements inside the collection based on the
* comparison from the {@link Countable} interface. If an element is to be added
* which is already in the collection (again, based on the comparison from the
* {@link Countable} interface), it will not be added and instead the two
* similar elements will be merged with each other using
* {@link Countable#merge(Countable)}.
* <p>
* This reglementation is in ways similar to those of the {@link Set} interface,
* but said interface is stricter than what is needed for this purpose here and
* thus not used.
* <p>
* All methods in this class throw a {@link NullPointerException} if a null
* object reference is passed in any parameter.
*
* @see Countable
*/
public final class CountableSet<T extends Countable<T>> {
private final SortedMap<Comparable<?>, T> countablesMap;
public CountableSet() {
this.countablesMap = new TreeMap<>();
}
/**
* Adds a deep copy of this element to the collection, but if the element
* already exists inside the collection (based on the {@link Comparable}
* returned by {@link Countable#getComparator()}), only merges the two
* elements through the {@link Countable#merge(Countable)} method of the
* Countable interface.
*
* @param element
* The element to add.
*/
public void addElement(final T element) {
final T tmp = this.countablesMap.get(element.getComparator());
if (tmp != null) {
tmp.merge(element);
} else {
this.countablesMap.put(element.getComparator(),
element.newInstance());
}
}
/**
* Note that this method doesn't set the reference of the internal
* collection to the given collection, but only clears the old content and
* copies over the elements of the given collection. While doing so, it
* adheres to restrictions of this container class.
* <p>
* Thus the line {@code elements == countableList.getElements()} will always
* be false.
*
* @param elements
* The elements to set.
*/
public void setElements(final Collection<T> elements) {
if (elements == null) {
throw new NullPointerException("Collection must not be null.");
}
this.countablesMap.clear();
for (final T t : elements) {
this.addElement(t);
}
}
/**
* This method returns a collection containing all {@link Countable}
* elements of this container class instance.
* <p>
* Note that the contents of this collection is directly backed by this
* class, and thus one should be very careful when modifying its elements
* especially concerning the restrictions imposed by this class (not
* modifying them at all would be preferable).
* <p>
* Also, please note that this collection is read-only.
*
* @return The elements.
*/
public Collection<T> getElements() {
return Collections.unmodifiableCollection(this.countablesMap.values());
}
/**
* In contrast to {@link CountableSet#getElements()}, this method will
* return a deep copy of the internal collection. Thus, there will be no
* problems with altering its elements in a way that would violate the
* restrictions imposed by this class.
* <p>
* Of course, due to having to create deep copies of every element, this
* method may perform considerably worse than
* {@link CountableSet#getElements()}, since it has a run-time behaviour of
* O(n) instead of O(1) and it is also depending on the elements's
* {@link Countable} implementation.
*
* @return The elements.
*/
public List<T> getElementsDeepCopy() {
final List<T> listCopy = new ArrayList<>(this.countablesMap.size());
for (final T t : this.countablesMap.values()) {
listCopy.add(t.newInstance());
}
return listCopy;
}
/**
* This method removes all elements from the internal collection.
*/
public void clear() {
this.countablesMap.clear();
}
/**
* @return The number of elements this container class holds.
*/
public int size() {
return this.countablesMap.size();
}
/**
* Checks the collection for whether it contains the given {@link Countable}
* based on the {@link Comparable} returned by
* {@link Countable#getComparator()}.
*
* @param t
* The {@link Countable} which should be checked on whether this
* collection contains it.
* @return {@code true} if the given {@link Countable} is inside this
* collection, otherwise {@code false}.
*/
public boolean contains(final T t) {
return this.countablesMap.containsKey(t.getComparator());
}
@Override
public boolean equals(final Object o) {
if ((o != null) && (o instanceof CountableSet<?>)) {
return this.getElements().equals(
((CountableSet<?>) o).getElements());
}
return false;
}
@Override
public int hashCode() {
int result = 189;
result = (31 * result) + this.getElements().hashCode();
return result;
}
}