001package org.intellimate.izou.util;
002
003import org.intellimate.izou.identification.Identifiable;
004import org.intellimate.izou.identification.Identification;
005import org.intellimate.izou.identification.IdentificationManager;
006
007import java.util.*;
008
009/**
010 * It has the same Properties as an normal HashSet, but (optionally) keeps an Identification for every object to
011 * identify its source.
012 *
013 * @author Leander Kurscheidt
014 * @version 1.0
015 */
016public class IdentificationSet<X> extends AbstractSet<X> implements Set<X>, Cloneable, Identifiable {
017    private HashMap<X, Identification> map;
018    private boolean allowElementsWithoutIdentification = false;
019    private static Identification placeholder = null;
020
021    /**
022     * Constructs a new, empty set;
023     * <p>
024     * the backing HashMap instance has default initial capacity (16) and load factor (0.75).
025     * </p>
026     */
027    public IdentificationSet() {
028        map = new HashMap<>();
029        init();
030    }
031
032    /**
033     * Constructs a new, empty set.
034     * <p>the backing HashMap instance has the specified initial capacity and the specified load factor.</p>
035     * @param initialCapacity the initial capacity of the hash map
036     * @param loadFactor the load factor of the hash map
037     * @throws java.lang.IllegalArgumentException if the initial capacity is less than zero, or if the load factor is
038     *                                          nonpositive
039     */
040    public IdentificationSet(int initialCapacity, float loadFactor) {
041        map = new HashMap<>(initialCapacity, loadFactor);
042        init();
043    }
044
045    /**
046     * Constructs a new, empty set.
047     * <p>the backing HashMap instance has the specified initial capacity and default load factor (0.75).</p>
048     * @param initialCapacity initialCapacity the initial capacity of the hash table
049     * @throws java.lang.IllegalArgumentException if the initial capacity is less than zero
050     */
051    public IdentificationSet(int initialCapacity) {
052        map = new HashMap<>(initialCapacity);
053        init();
054    }
055
056    /**
057     * Constructs a new, empty set;
058     * <p>
059     * the backing HashMap instance has default initial capacity (16) and load factor (0.75).
060     * </p>
061     * @param allow whether it is allowed to put Elements without Identification in this Set
062     */
063    public IdentificationSet(boolean allow) {
064        map = new HashMap<>();
065        allowElementsWithoutIdentification = allow;
066        init();
067    }
068
069    /**
070     * Constructs a new, empty set.
071     * <p>the backing HashMap instance has the specified initial capacity and the specified load factor.</p>
072     * @param initialCapacity the initial capacity of the hash map
073     * @param loadFactor the load factor of the hash map
074     * @param allow whether it is allowed to put Elements without Identification in this Set
075     * @throws java.lang.IllegalArgumentException if the initial capacity is less than zero, or if the load factor is
076     *                                          nonpositive
077     */
078    public IdentificationSet(int initialCapacity, float loadFactor, boolean allow) {
079        map = new HashMap<>(initialCapacity, loadFactor);
080        allowElementsWithoutIdentification = allow;
081        init();
082    }
083
084    /**
085     * Constructs a new, empty set.
086     * <p>the backing HashMap instance has the specified initial capacity and default load factor (0.75).</p>
087     * @param initialCapacity initialCapacity the initial capacity of the hash table
088     * @param allow whether it is allowed to put Elements without Identification in this Set
089     * @throws java.lang.IllegalArgumentException if the initial capacity is less than zero
090     */
091    public IdentificationSet(int initialCapacity, boolean allow) {
092        map = new HashMap<>(initialCapacity);
093        allowElementsWithoutIdentification = allow;
094        init();
095    }
096
097    /**
098     * initializes some common fields in the Set
099     */
100    private void init() {
101        if (placeholder == null) {
102            IdentificationManager.getInstance().registerIdentification(this);
103            Optional<Identification> identification = IdentificationManager.getInstance().getIdentification(this);
104            if (!identification.isPresent()) {
105                throw new IllegalStateException("Unable to obtain Identification");
106            } else {
107                placeholder = identification.get();
108            }
109        }
110    }
111
112    /**
113     * Returns the number of elements in this set (its cardinality).  If this
114     * set contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
115     * <tt>Integer.MAX_VALUE</tt>.
116     *
117     * @return the number of elements in this set (its cardinality)
118     */
119    @Override
120    public int size() {
121        return map.size();
122    }
123
124    /**
125     * Returns <tt>true</tt> if this set contains no elements.
126     *
127     * @return <tt>true</tt> if this set contains no elements
128     */
129    @Override
130    public boolean isEmpty() {
131        return map.isEmpty();
132    }
133
134    /**
135     * Returns <tt>true</tt> if this set contains the specified element.
136     * More formally, returns <tt>true</tt> if and only if this set
137     * contains an element <tt>e</tt> such that
138     * <tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>.
139     *
140     * @param o element whose presence in this set is to be tested
141     * @return <tt>true</tt> if this set contains the specified element
142     * @throws ClassCastException   if the type of the specified element
143     *                              is incompatible with this set
144     *                              (<a href="Collection.html#optional-restrictions">optional</a>)
145     * @throws NullPointerException if the specified element is null and this
146     *                              set does not permit null elements
147     *                              (<a href="Collection.html#optional-restrictions">optional</a>)
148     */
149    @Override
150    public boolean contains(Object o) {
151        return map.containsKey(o);
152    }
153
154    /**
155     * Returns an iterator over the elements in this set. The elements are returned in no particular order.
156     * @return an Iterator over the elements in this set
157     */
158    public Iterator<X> iterator() {
159        return map.keySet().iterator();
160    }
161
162    /**
163     * An ID must always be unique.
164     * A Class like Activator or OutputPlugin can just provide their .class.getCanonicalName()
165     * If you have to implement this interface multiple times, just concatenate unique Strings to
166     * .class.getCanonicalName()
167     *
168     * @return A String containing an ID
169     */
170    @Override
171    public String getID() {
172        return IdentificationSet.class.getCanonicalName();
173    }
174
175    /**
176     * Adds an Element to the Set
177     * @param x the Element
178     * @return true if this set did not already contain the specified element
179     * @throws java.lang.IllegalArgumentException if it is not allowed to put Elements without Identification in this
180     *                                            Set
181     */
182    @Override
183    public boolean add(X x) {
184        if (!allowElementsWithoutIdentification)
185            throw new IllegalArgumentException("It is not allowed to put Elements without Identification in this Set");
186
187        return map.put(x, placeholder) == null;
188    }
189
190    /**
191     * Adds an Element to the Set
192     * @param x the Element
193     * @param identification the identification
194     * @return true if this set did not already contain the specified element
195     */
196    public boolean add(X x, Identification identification) {
197        return map.put(x, identification) == null;
198    }
199
200    /**
201     * {@inheritDoc}
202     * <p>This implementation iterates over the collection looking for the
203     * specified element.  If it finds the element, it removes the element
204     * from the collection using the iterator's remove method.
205     * <p>
206     *
207     * @param o the object to remove
208     * @throws ClassCastException            {@inheritDoc}
209     * @throws NullPointerException          {@inheritDoc}
210     */
211    @Override
212    public boolean remove(Object o) {
213        return map.remove(o) != null;
214    }
215
216    /**
217     * {@inheritDoc}
218     * <p>This implementation iterates over this collection, removing each
219     * element using the <tt>Iterator.remove</tt> operation.  Most
220     * implementations will probably choose to override this method for
221     * efficiency.
222     * <p>
223     */
224    @Override
225    public void clear() {
226        map.clear();
227    }
228
229    /**
230     * Returns a shallow copy of this HashSet instance: the elements themselves are not cloned.
231     *
232     * @return a shallow copy of this set
233     */
234    @Override
235    public Object clone() {
236        try {
237            IdentificationSet<X> newSet = (IdentificationSet<X>) super.clone();
238            newSet.map = (HashMap<X, Identification>) map.clone();
239            return newSet;
240        } catch (CloneNotSupportedException e) {
241            throw new InternalError();
242        }
243    }
244
245    /**
246     * returns the associated Identification (if it was added with an identification)
247     * @param x the Element
248     * @return the identification or an Empty Optional
249     */
250    public Optional<Identification> getIdentificationFor(X x) {
251        Identification identification = map.get(x);
252        if (identification == null || identification.equals(placeholder)) {
253            return Optional.empty();
254        } else {
255            return Optional.of(identification);
256        }
257    }
258}