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 ? e==null : 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}