1   /*
2    *  SimpleMapImpl.java
3    *
4    *  Copyright (c) 2001, The University of Sheffield.
5    *
6    *  This file is part of GATE (see http://gate.ac.uk/), and is free
7    *  software, licenced under the GNU Library General Public License,
8    *  Version 2, June 1991 (in the distribution as file licence.html,
9    *  and also available at http://gate.ac.uk/gate/licence.html).
10   *
11   *  D.Ognyanoff, 5/Nov/2001
12   *
13   *  $Id: SimpleMapImpl.java,v 1.8 2002/01/14 13:42:01 nasso Exp $
14   */
15  
16  package gate.util;
17  
18  import java.util.*;
19  import java.io.*;
20  
21  /**
22   * Implements Map interface in using less memory. Very simple but usefull
23   * for small number of items on it.
24   */
25  
26  class SimpleMapImpl implements Map, java.lang.Cloneable, java.io.Serializable {
27  
28    /**
29     * The capacity of the map
30     */
31    int capacity = 3;
32  
33    /**
34     * The current number of elements of the map
35     */
36    int count = 0;
37  
38    /**
39     * Array keeping the keys of the entries in the map. It is "synchrnized"
40     * with the values array - the Nth position in both arrays correspond
41     * to one and the same entry
42     */
43    Object theKeys[];
44  
45    /**
46     * Array keeping the values of the entries in the map. It is "synchrnized"
47     * with the keys array - the Nth position in both arrays correspond
48     * to one and the same entry
49     */
50    Object theValues[];
51  
52    /** Freeze the serialization UID. */
53    static final long serialVersionUID = -6747241616127229116L;
54  
55    /** the Object instance that will represent the NULL keys in the map */
56    transient static Object nullKey = new Object();
57  
58    /** the static 'all keys' collection */
59    transient public static HashMap theKeysHere = new HashMap();
60  
61    /** additional static members initialization */
62    static {
63      theKeysHere.put(nullKey, nullKey);
64    } // static code
65  
66    /**
67     * Constructor
68     */
69    public SimpleMapImpl() {
70      theKeys = new Object[capacity];
71      theValues = new Object[capacity];
72    } // SimpleMapImpl()
73  
74    /**
75     * return the number of elements in the map
76     */
77    public int size() {
78      return count;
79    } // size()
80  
81    /**
82     * return true if there are no elements in the map
83     */
84    public boolean isEmpty() {
85      return (count == 0);
86    } // isEmpty()
87  
88    /**
89     * Not supported. This method is here only to conform the Map interface
90     */
91    public Collection values() {
92      throw new UnsupportedOperationException(
93        "SimpleMapImpl.values() not implemented!");
94    } // values()
95  
96    /**
97     * return the set of the keys in the map. The changes in the set DO NOT
98     * affect the map.
99     */
100   public Set keySet()
101   {
102     HashSet s = new HashSet(size());
103     Object k;
104     for (int i = 0; i < count; i++) {
105       k = theKeys[i];
106       if (k == nullKey)
107            s.add(null);
108         else
109            s.add(k);
110     } //for
111     return s;
112   } // keySet()
113 
114   /**
115    * clear the map
116    */
117   public void clear()
118   {
119     for (int i = 0; i < count; i++) {
120       theKeys[i] = null;
121       theValues[i] = null;
122     } // for
123     count = 0;
124   } // clear
125 
126   /**
127    * return true if the key is in the map
128    */
129   public boolean containsKey(Object key) {
130     return (getPostionByKey(key) != -1);
131   }// containsKey
132 
133   /**
134    * return true if the map contains that value
135    */
136   public boolean containsValue(Object value) {
137     return (getPostionByValue(value) != -1);
138   }// containsValue
139 
140   /**
141    * return the value associated with the key. If the key is
142    * not in the map returns null.
143    */
144   public Object get(Object key) {
145     int pos = getPostionByKey(key);
146     return (pos == -1) ? null : theValues[pos];
147   } // get
148 
149   /**
150    * put a value in the map using the given key. If the key exist in the map
151    * the value is replaced and the old one is returned.
152    */
153   public Object put(Object key, Object value) {
154     Object gKey;
155     if (key == null) {
156       key = nullKey;
157       gKey = nullKey;
158     } else
159       gKey = theKeysHere.get(key);
160     // if the key is already in the 'all keys' map - try to find it in that instance
161     // comparing by reference
162     if (gKey != null) {
163       for (int i = 0; i < count; i++) {
164         if (gKey == theKeys[i]) {
165           // we found the reference - return the value
166           Object oldVal = theValues[i];
167           theValues[i] = value;
168           return oldVal;
169         }
170       } // for
171     } else {// if(gKey != null)
172       // no, the key is not in the 'all keys' map - put it there
173       theKeysHere.put(key, key);
174       gKey = key;
175     }
176     // enlarge the containers if necessary
177     if (count == capacity)
178       increaseCapacity();
179 
180     // put the key and value to the map
181     theKeys[count] = gKey;
182     theValues[count] = value;
183     count++;
184     return null;
185   } // put
186 
187   /**
188    * remove value from the map using it's key.
189    */
190   public Object remove(Object key) {
191     int pos = getPostionByKey(key);
192     if (pos == -1)
193         return null;
194 
195     // save the value to return it at the end
196     Object oldVal = theValues[pos];
197     count--;
198     // move the last element key and value removing the element
199     if (count != 0) {
200         theKeys[pos] = theKeys[count];
201         theValues[pos] = theValues[count];
202     }
203     // clear the last position
204     theKeys[count] = null;
205     theValues[count] = null;
206 
207     // return the value
208     return oldVal;
209   } // remove
210 
211   /**
212    * put all the elements from a map
213    */
214   public void putAll(Map t)
215   {
216     if (t == null) {
217       throw new UnsupportedOperationException(
218       "SimpleMapImpl.putAll argument is null");
219     } // if (t == null)
220 
221     if (t instanceof SimpleMapImpl) {
222       SimpleMapImpl sfm = (SimpleMapImpl)t;
223       Object key;
224       for (int i = 0; i < sfm.count; i++) {
225         key = sfm.theKeys[i];
226         put(key, sfm.theValues[i]);
227       } //for
228     } else { // if (t instanceof SimpleMapImpl)
229       Iterator entries = t.entrySet().iterator();
230       Map.Entry e;
231       while (entries.hasNext()) {
232         e = (Map.Entry)entries.next();
233         put(e.getKey(), e.getValue());
234       } // while
235     } // if(t instanceof SimpleMapImpl)
236   } // putAll
237 
238   /**
239    * return positive value as index of the key in the map.
240    * Negative value means that the key is not present in the map
241    */
242   private int getPostionByKey(Object key) {
243     if (key == null)
244       key = nullKey;
245     // check the 'all keys' map for the very first key occurence
246     key = theKeysHere.get(key);
247     if (key == null)
248       return -1;
249 
250     for (int i = 0; i < count; i++) {
251       if (key == theKeys[i])
252         return i;
253     } // for
254     return -1;
255   } // getPostionByKey
256 
257   /**
258    * return the index of the key in the map comparing them by reference only.
259    * This method is used in subsume check to speed it up.
260    */
261   protected int getSubsumeKey(Object key) {
262     for (int i = 0; i < count; i++) {
263       if (key == theKeys[i])
264         return i;
265     } // for
266     return -1;
267   } // getPostionByKey
268 
269   /**
270    * return positive value as index of the value in the map.
271    */
272   private int getPostionByValue(Object value) {
273     Object av;
274     for (int i = 0; i < count; i++) {
275       av = theValues[i];
276       if (value == null) {
277         if (av == null)
278           return i;
279       } else {//if (value == null)
280         if (value.equals(av))
281           return i;
282       } //if (value == null)
283     } // for
284 
285     return -1;
286   } // getPostionByValue
287 
288   // Modification Operations
289   private void increaseCapacity() {
290     int oldCapacity = capacity;
291     capacity *= 2;
292     Object oldKeys[] = theKeys;
293     theKeys = new Object[capacity];
294 
295     Object oldValues[] = theValues;
296     theValues = new Object[capacity];
297 
298     System.arraycopy(oldKeys, 0, theKeys, 0, oldCapacity);
299     System.arraycopy(oldValues, 0, theValues, 0, oldCapacity);
300   } // increaseCapacity
301 
302   /**
303    * Auxiliary classes needed for the support of entrySet() method
304    */
305   private static class Entry implements Map.Entry {
306     int hash;
307     Object key;
308     Object value;
309 
310     Entry(int hash, Object key, Object value) {
311       this.hash = hash;
312       this.key = key;
313       this.value = value;
314     }
315 
316     protected Object clone() {
317       return new Entry(hash, key, value);
318     }
319 
320     public Object getKey() {
321       return key;
322     }
323 
324     public Object getValue() {
325       return value;
326     }
327 
328     public Object setValue(Object value) {
329       Object oldValue = this.value;
330       this.value = value;
331       return oldValue;
332     }
333 
334     public boolean equals(Object o) {
335       if (!(o instanceof Map.Entry))
336         return false;
337       Map.Entry e = (Map.Entry)o;
338 
339       return (key==null ? e.getKey()==null : key.equals(e.getKey())) &&
340         (value==null ? e.getValue()==null : value.equals(e.getValue()));
341     }
342 
343     public int hashCode() {
344       return hash ^ (key==null ? 0 : key.hashCode());
345     }
346 
347     public String toString() {
348       return key+"="+value;
349     }
350   } // Entry
351 
352 
353   public Set entrySet() {
354     HashSet s = new HashSet(size());
355     Object v, k;
356     for (int i = 0; i < count; i++) {
357       k = theKeys[i];
358       s.add(new Entry(k.hashCode(), ((k==nullKey)?null:k), theValues[i]));
359     } //for
360     return s;
361   } // entrySet
362 
363   // Comparison and hashing
364   public boolean equals(Object o) {
365     if (!(o instanceof Map)) {
366       return false;
367     }
368 
369     Map m = (Map)o;
370     if (m.size() != count) {
371       return false;
372     }
373 
374     Object v, k;
375     for (int i = 0; i < count; i++) {
376       k = theKeys[i];
377       v = m.get(k);
378       if (v==null) {
379         if (theValues[i]!=null)
380           return false;
381       }
382       else if (!v.equals(theValues[i])){
383         return false;
384       }
385     } // for
386 
387     return true;
388   } // equals
389 
390   /**
391    * return the hashCode for the map
392    */
393   public int hashCode() {
394     int h = 0;
395     Iterator i = entrySet().iterator();
396     while (i.hasNext())
397       h += i.next().hashCode();
398     return h;
399   } // hashCode
400 
401   /**
402    * Create a copy of the map including the data.
403    */
404   public Object clone() {
405     SimpleMapImpl newMap;
406     try {
407       newMap = (SimpleMapImpl)super.clone();
408     } catch (CloneNotSupportedException e) {
409       throw(new InternalError(e.toString()));
410     }
411 
412     newMap.count = count;
413     newMap.theKeys = new Object[capacity];
414     System.arraycopy(theKeys, 0, newMap.theKeys, 0, capacity);
415 
416     newMap.theValues = new Object[capacity];
417     System.arraycopy(theValues, 0, newMap.theValues, 0, capacity);
418 
419     return newMap;
420   } // clone
421 
422   public String toString() {
423     int max = size() - 1;
424     StringBuffer buf = new StringBuffer();
425     Iterator i = entrySet().iterator();
426 
427     buf.append("{");
428     for (int j = 0; j <= max; j++) {
429       Entry e = (Entry) (i.next());
430       buf.append(e.getKey() + "=" + e.getValue());
431       if (j < max)
432         buf.append(", ");
433     }
434     buf.append("}");
435     return buf.toString();
436   } // toString
437 
438   /**
439    * readObject - calls the default readObject() and then initialises the
440    * transient data
441    *
442    * @serialData Read serializable fields. No optional data read.
443    */
444   private void readObject(ObjectInputStream s)
445       throws IOException, ClassNotFoundException {
446     s.defaultReadObject();
447     if (theKeysHere == null) {
448       theKeysHere = new HashMap();
449       theKeysHere.put(nullKey, nullKey);
450     }
451     for (int i = 0; i < theKeys.length; i++) {
452       // if the key is in the 'all keys' map
453       Object o = theKeysHere.get(theKeys[i]);
454       if (o != null) // yes - so reuse the reference
455         theKeys[i] = o;
456       else // no - put it in the 'all keys' map
457         theKeysHere.put(theKeys[i], theKeys[i]);
458     }//for
459   }//readObject
460 } //SimpleMapImpl
461