package org.intellimate.izou.util; import org.intellimate.izou.identification.Identifiable; import org.intellimate.izou.identification.Identification; import org.intellimate.izou.identification.IdentificationManager; import java.util.*; /** * It has the same Properties as an normal HashSet, but (optionally) keeps an Identification for every object to * identify its source. * * @author Leander Kurscheidt * @version 1.0 */ public class IdentificationSet<X> extends AbstractSet<X> implements Set<X>, Cloneable, Identifiable { private HashMap<X, Identification> map; private boolean allowElementsWithoutIdentification = false; private static Identification placeholder = null; /** * Constructs a new, empty set; * <p> * the backing HashMap instance has default initial capacity (16) and load factor (0.75). * </p> */ public IdentificationSet() { map = new HashMap<>(); init(); } /** * Constructs a new, empty set. * <p>the backing HashMap instance has the specified initial capacity and the specified load factor.</p> * @param initialCapacity the initial capacity of the hash map * @param loadFactor the load factor of the hash map * @throws java.lang.IllegalArgumentException if the initial capacity is less than zero, or if the load factor is * nonpositive */ public IdentificationSet(int initialCapacity, float loadFactor) { map = new HashMap<>(initialCapacity, loadFactor); init(); } /** * Constructs a new, empty set. * <p>the backing HashMap instance has the specified initial capacity and default load factor (0.75).</p> * @param initialCapacity initialCapacity the initial capacity of the hash table * @throws java.lang.IllegalArgumentException if the initial capacity is less than zero */ public IdentificationSet(int initialCapacity) { map = new HashMap<>(initialCapacity); init(); } /** * Constructs a new, empty set; * <p> * the backing HashMap instance has default initial capacity (16) and load factor (0.75). * </p> * @param allow whether it is allowed to put Elements without Identification in this Set */ public IdentificationSet(boolean allow) { map = new HashMap<>(); allowElementsWithoutIdentification = allow; init(); } /** * Constructs a new, empty set. * <p>the backing HashMap instance has the specified initial capacity and the specified load factor.</p> * @param initialCapacity the initial capacity of the hash map * @param loadFactor the load factor of the hash map * @param allow whether it is allowed to put Elements without Identification in this Set * @throws java.lang.IllegalArgumentException if the initial capacity is less than zero, or if the load factor is * nonpositive */ public IdentificationSet(int initialCapacity, float loadFactor, boolean allow) { map = new HashMap<>(initialCapacity, loadFactor); allowElementsWithoutIdentification = allow; init(); } /** * Constructs a new, empty set. * <p>the backing HashMap instance has the specified initial capacity and default load factor (0.75).</p> * @param initialCapacity initialCapacity the initial capacity of the hash table * @param allow whether it is allowed to put Elements without Identification in this Set * @throws java.lang.IllegalArgumentException if the initial capacity is less than zero */ public IdentificationSet(int initialCapacity, boolean allow) { map = new HashMap<>(initialCapacity); allowElementsWithoutIdentification = allow; init(); } /** * initializes some common fields in the Set */ private void init() { if (placeholder == null) { IdentificationManager.getInstance().registerIdentification(this); Optional<Identification> identification = IdentificationManager.getInstance().getIdentification(this); if (!identification.isPresent()) { throw new IllegalStateException("Unable to obtain Identification"); } else { placeholder = identification.get(); } } } /** * Returns the number of elements in this set (its cardinality). If this * set contains more than <tt>Integer.MAX_VALUE</tt> elements, returns * <tt>Integer.MAX_VALUE</tt>. * * @return the number of elements in this set (its cardinality) */ @Override public int size() { return map.size(); } /** * Returns <tt>true</tt> if this set contains no elements. * * @return <tt>true</tt> if this set contains no elements */ @Override public boolean isEmpty() { return map.isEmpty(); } /** * Returns <tt>true</tt> if this set contains the specified element. * More formally, returns <tt>true</tt> if and only if this set * contains an element <tt>e</tt> such that * <tt>(o==null ? e==null : o.equals(e))</tt>. * * @param o element whose presence in this set is to be tested * @return <tt>true</tt> if this set contains the specified element * @throws ClassCastException if the type of the specified element * is incompatible with this set * (<a href="Collection.html#optional-restrictions">optional</a>) * @throws NullPointerException if the specified element is null and this * set does not permit null elements * (<a href="Collection.html#optional-restrictions">optional</a>) */ @Override public boolean contains(Object o) { return map.containsKey(o); } /** * Returns an iterator over the elements in this set. The elements are returned in no particular order. * @return an Iterator over the elements in this set */ public Iterator<X> iterator() { return map.keySet().iterator(); } /** * An ID must always be unique. * A Class like Activator or OutputPlugin can just provide their .class.getCanonicalName() * If you have to implement this interface multiple times, just concatenate unique Strings to * .class.getCanonicalName() * * @return A String containing an ID */ @Override public String getID() { return IdentificationSet.class.getCanonicalName(); } /** * Adds an Element to the Set * @param x the Element * @return true if this set did not already contain the specified element * @throws java.lang.IllegalArgumentException if it is not allowed to put Elements without Identification in this * Set */ @Override public boolean add(X x) { if (!allowElementsWithoutIdentification) throw new IllegalArgumentException("It is not allowed to put Elements without Identification in this Set"); return map.put(x, placeholder) == null; } /** * Adds an Element to the Set * @param x the Element * @param identification the identification * @return true if this set did not already contain the specified element */ public boolean add(X x, Identification identification) { return map.put(x, identification) == null; } /** * {@inheritDoc} * <p>This implementation iterates over the collection looking for the * specified element. If it finds the element, it removes the element * from the collection using the iterator's remove method. * <p> * * @param o the object to remove * @throws ClassCastException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ @Override public boolean remove(Object o) { return map.remove(o) != null; } /** * {@inheritDoc} * <p>This implementation iterates over this collection, removing each * element using the <tt>Iterator.remove</tt> operation. Most * implementations will probably choose to override this method for * efficiency. * <p> */ @Override public void clear() { map.clear(); } /** * Returns a shallow copy of this HashSet instance: the elements themselves are not cloned. * * @return a shallow copy of this set */ @Override public Object clone() { try { IdentificationSet<X> newSet = (IdentificationSet<X>) super.clone(); newSet.map = (HashMap<X, Identification>) map.clone(); return newSet; } catch (CloneNotSupportedException e) { throw new InternalError(); } } /** * returns the associated Identification (if it was added with an identification) * @param x the Element * @return the identification or an Empty Optional */ public Optional<Identification> getIdentificationFor(X x) { Identification identification = map.get(x); if (identification == null || identification.equals(placeholder)) { return Optional.empty(); } else { return Optional.of(identification); } } }