1   
2   package st.ata.util;
3   
4   import java.io.IOException;
5   import java.io.InputStream;
6   import java.io.Serializable;
7   import java.util.Arrays;
8   import java.util.Date;
9   import java.util.Hashtable;
10  import java.util.Iterator;
11  import java.util.NoSuchElementException;
12  
13  
14  // Tested by TestHashtableAList
15  
16  /*** Implementation of {@link AList} using simple hashtable. */
17  @SuppressWarnings({"unchecked"})
18  public class HashtableAList implements MutableAList, Serializable {
19      private static final long serialVersionUID = 3670660167336648644L;
20      
21      private final Hashtable mTable = new Hashtable();
22  
23      private static class DateArray {
24          public Date[] values;
25          public DateArray(Date[] v) { values = v; }
26          public boolean equals(Object obj) {
27              if (! (obj instanceof DateArray)) return false;
28              return Arrays.equals(values, ((DateArray)obj).values);
29          }
30      }
31  
32  
33      /*** Remove all key-value mappings. */
34      public void clear() {
35          close();
36          mTable.clear();
37      }
38  
39      public boolean containsKey(String key) {
40          return mTable.containsKey(key);
41      }
42  
43      /***
44       * Deep Clone.
45       *
46       * Limited implementation
47       * @return The cloned object.
48       */
49      public Object clone() {
50          HashtableAList copy = new HashtableAList();
51          String[] keys = getKeyArray();
52          for (int i=0; i<keys.length; i++) {
53              Object me=getObject(keys[i]);
54              if (me instanceof AList)
55                  copy.putObject(keys[i], ((AList)me).clone());
56              else  if (me instanceof AList[]) {
57                  AList[] from = (AList[])me;
58                  int count=from.length;
59                  for (int j=0; j<from.length; j++) {
60                      if (from[j]==null) {
61                          count--;
62                      }
63                  }
64  
65                  AList[] copyAList = new AList[count];
66                  for (int j=0; j<count; j++) {
67                      if (from[j]==null) continue;
68                      copyAList[j]=(AList)from[j].clone();
69                  }
70                  copy.putObject(keys[i], copyAList);
71              } else  if (me instanceof String[]) {
72                  String[] from = (String[])me;
73                  String[] copyA = new String[from.length];
74                  for (int j=0; j<from.length; j++)
75                      copyA[j]=from[j];
76                  copy.putObject(keys[i], copyA);
77              }
78              else if (me instanceof Long) {
79                  copy.putObject(keys[i], new Long(((Long)me).longValue()));
80              } else if (me instanceof String) {
81                  copy.putObject(keys[i], me);
82              } else
83                  X.noimpl();
84          }
85          return copy;
86      }
87  
88      /*** 
89       * Shallow copy of fields of <code>other</code> into <code>this</code>.
90       * @param other AList to copy from.
91       */
92      public void copyFrom(AList other) {
93          Iterator keys = other.getKeys();
94          while (keys.hasNext()) {
95              String key = (String)keys.next();
96              switch (other.getType(key)) {
97              case T_ALIST:
98                  putAList(key, other.getAList(key));
99                  break;
100             case T_DATE:
101                 putDate(key, other.getDate(key));
102                 break;
103             case T_INT:
104                 putInt(key, other.getInt(key));
105                 break;
106             case T_LONG:
107                 putLong(key, other.getLong(key));
108                 break;
109             case T_STRING:
110                 putString(key, other.getString(key));
111                 break;
112             case T_INPUTSTREAM:
113                 putInputStream(key, other.getInputStream(key));
114                 break;
115             case F_ARRAY | T_ALIST:
116                 putAListArray(key, other.getAListArray(key));
117                 break;
118             case F_ARRAY | T_DATE:
119                 putDateArray(key, other.getDateArray(key));
120                 break;
121             case F_ARRAY | T_INT:
122                 putIntArray(key, other.getIntArray(key));
123                 break;
124             case F_ARRAY | T_LONG:
125                 putLongArray(key, other.getLongArray(key));
126                 break;
127             case F_ARRAY | T_STRING:
128                 putStringArray(key, other.getStringArray(key));
129                 break;
130             case F_ARRAY_ARRAY | T_STRING:
131                 putStringArrayArray(key, other.getStringArrayArray(key));
132                 break;
133             case F_ARRAY | T_INPUTSTREAM:
134                 putInputStreamArray(key, other.getInputStreamArray(key));
135                 break;
136             default:
137                 X.fail("Unexpected case");
138             }
139         }
140     }
141     
142     public void copyKeysFrom(Iterator keys, AList other) {
143         for (; keys.hasNext();) {
144             String key = (String)keys.next();
145             Object value = other.getObject(key);
146             // TODO: consider shallow or deep copy in some cases?
147             // perhaps controlled by a third parameter?
148             if(value!=null) {
149                 putObject(key,value);
150             }
151         }
152     }
153     
154     public Object getObject(String key) {
155         return mTable.get(key);
156     }
157 
158     public void putObject(String key, Object val) {
159         mTable.put(key, val);
160     }
161     public void remove(String key) {
162         mTable.remove(key);
163     }
164     public Iterator getKeys() {
165         return mTable.keySet().iterator();
166     }
167 
168     public String[] getKeyArray() {
169         int i = 0;
170         String keys[] = new String[mTable.size()];
171         for(Iterator it = getKeys(); it.hasNext(); ++i)
172             keys[i] = (String)it.next();
173         return keys;
174     }
175 
176     public int getInt(String key) {
177         Integer v = (Integer)mTable.get(key);
178         if (v == null) throw new NoSuchElementException(key);
179         return v.intValue();
180     }
181 
182     public long getLong(String key) {
183         Long v = (Long)mTable.get(key);
184         if (v == null) throw new NoSuchElementException(key);
185         return v.longValue();
186     }
187 
188     public String getString(String key) {
189         String v = (String)mTable.get(key);
190         if (v == null) throw new NoSuchElementException(key);
191         return v;
192     }
193 
194     public AList getAList(String key) {
195         AList a = (AList) mTable.get(key);
196         if (a == null) throw new NoSuchElementException(key);
197         return a;
198     }
199 
200     public Date getDate(String key) {
201         Date v = (Date)mTable.get(key);
202         if (v == null) throw new NoSuchElementException(key);
203         return v;
204     }
205 
206     public InputStream getInputStream(String key) {
207         InputStream v = (InputStream)mTable.get(key);
208         if (v == null) throw new NoSuchElementException(key);
209         return v;
210     }
211 
212     public int[] getIntArray(String key) {
213         int[] a = (int[]) mTable.get(key);
214         if (a == null) throw new NoSuchElementException(key);
215         return a;
216     }
217 
218     public long[] getLongArray(String key) {
219         long[] a = (long[]) mTable.get(key);
220         if (a == null) throw new NoSuchElementException(key);
221         return a;
222     }
223 
224     public String[] getStringArray(String key) {
225         String[] a = (String[]) mTable.get(key);
226         if (a == null) throw new NoSuchElementException(key);
227         return a;
228     }
229 
230     public AList[] getAListArray(String key) {
231         AList[] a = (AList[]) mTable.get(key);
232         if (a == null) throw new NoSuchElementException(key);
233         return a;
234     }
235 
236     public Date[] getDateArray(String key) {
237         DateArray a = (DateArray) mTable.get(key);
238         if (a == null) throw new NoSuchElementException(key);
239         return a.values;
240     }
241 
242     public InputStream[] getInputStreamArray(String key) {
243         InputStream v[] = (InputStream [])mTable.get(key);
244         if (v == null) throw new NoSuchElementException(key);
245         return v;
246     }
247 
248     public String[][] getStringArrayArray(String key) {
249         String[][] a = (String[][]) mTable.get(key);
250         if (a == null) throw new NoSuchElementException(key);
251         return a;
252     }
253 
254 
255     public void putInt(String key, int value) {
256         mTable.put(key, new Integer(value));
257     }
258 
259     public void putLong(String key, long value) {
260         mTable.put(key, new Long(value));
261     }
262 
263     public void putString(String key, String value) {
264         mTable.put(key, value);
265     }
266 
267     public void putAList(String key, AList value) {
268         mTable.put(key, value);
269     }
270 
271     public void putDate(String key, Date value) {
272         mTable.put(key, value);
273     }
274 
275     public void putInputStream(String key, InputStream value) {
276         mTable.put(key, value);
277     }
278 
279     public void putIntArray(String key, int[] value) {
280         mTable.put(key, value);
281     }
282 
283     public void putLongArray(String key, long[] value) {
284         mTable.put(key, value);
285     }
286 
287     public void putStringArray(String key, String[] value) {
288         mTable.put(key, value);
289     }
290 
291     public void putAListArray(String key, AList[] value) {
292         mTable.put(key, value);
293     }
294 
295     public void putDateArray(String key, Date[] value) {
296         mTable.put(key, new DateArray(value));
297     }
298 
299     public void putInputStreamArray(String key, InputStream[] value) {
300         mTable.put(key, value);
301     }
302 
303     public void putStringArrayArray(String key, String[][] value) {
304         mTable.put(key, value);
305     }
306 
307 
308     /*** Deep equals.  Arrays need to have same values in same order to
309      *  be considered equal.
310      * @param obj
311      * @return True if equals.
312      */
313     public boolean equals(Object obj) {
314         if (! (obj instanceof HashtableAList)) return false;
315         HashtableAList o = (HashtableAList)obj;
316         for (Iterator i = o.getKeys(); i.hasNext(); ) {
317             if (mTable.get(i.next()) == null) return false;
318         }
319         for (Iterator i = getKeys(); i.hasNext(); ) {
320             Object k = i.next();
321             Object v1 = mTable.get(k);
322             Object v2 = o.mTable.get(k);
323             if (! v1.equals(v2)) {
324                 if (v1 instanceof AList[]) {
325                     if (! (v2 instanceof AList[])) return false;
326                     if (! Arrays.equals((Object[])v1, (Object[])v2))
327                         return false;
328                 } else if (v1 instanceof int[]) {
329                     if (! (v2 instanceof int[])) return false;
330                     if (! Arrays.equals((int[])v1, (int[])v2)) return false;
331                 } else if (v1 instanceof long[]) {
332                     if (! (v2 instanceof long[])) return false;
333                     if (! Arrays.equals((long[])v1, (long[])v2)) return false;
334                 } else if (v1 instanceof String[]) {
335                     if (! (v2 instanceof String[])) return false;
336                     if (! Arrays.equals((String[])v1, (String[])v2))
337                         return false;
338                 } else return false;
339             }
340         }
341         return true;
342     }
343 
344     public int getType(String key) {
345         Object o = mTable.get(key);
346         if (o == null) return T_UNDEFINED;
347         else if (o instanceof AList) return T_ALIST;
348         else if (o instanceof Date) return T_DATE;
349         else if (o instanceof Integer) return T_INT;
350         else if (o instanceof Long) return T_LONG;
351         else if (o instanceof String) return T_STRING;
352         else if (o instanceof InputStream) return T_INPUTSTREAM;
353         else if (o instanceof AList[]) return T_ALIST | F_ARRAY;
354         else if (o instanceof DateArray) return T_DATE | F_ARRAY;
355         else if (o instanceof int[]) return T_INT | F_ARRAY;
356         else if (o instanceof long[]) return T_LONG | F_ARRAY;
357         else if (o instanceof String[]) return T_STRING | F_ARRAY;
358         else if (o instanceof InputStream[]) return T_INPUTSTREAM | F_ARRAY;
359         else if (o instanceof String[][]) return T_STRING | F_ARRAY_ARRAY;
360         else if (o instanceof Object[]) return T_OBJECT | F_ARRAY;
361         else if (o instanceof Object) return T_OBJECT;
362         else X.fail("Should not get here " + o);
363         return -1;
364     }
365 
366     /*** Useful for creating test-tables for debugging.  The object
367         should be one of an {@link AList}, {@link Date}, {@link
368         Integer}, {@link Long}, {@link String}, {@link AList}[],
369         {@link Date}[], <code>int[]</code>, <code>long[]</code>,
370         <code>{@link String}[]</code>, <code>ZE[]</code>
371         or <code>ZE[][]</code>.  In the case of <code>ZE[]</code>,
372         the entry is treated as an {@link AList}. Similaryly
373         if the entry is <code>ZE[][]</code> it is treated as
374         {@link AList}[]. */
375     public static class ZE {
376         public final String key;
377         public final Object val;
378         public ZE(String k, Object v) { key = k; val = v; }
379     }
380 
381     public void zInsert(ZE[] entries) {
382         for (int i = 0; i < entries.length; i++) {
383             zInsert(entries[i]);
384         }
385     }
386 
387     public void zInsert(ZE entry) {
388         if (entry.val instanceof Date[]) {
389             mTable.put(entry.key, new DateArray((Date[])entry.val));
390         } else if (entry.val instanceof ZE[]) {
391             HashtableAList v = new HashtableAList();
392             v.zInsert((ZE[])entry.val);
393             mTable.put(entry.key, v);
394         } else if (entry.val instanceof ZE[][]) {
395             AList v[] = new AList[((ZE[][])entry.val).length];
396             for(int j = 0; j < v.length; ++j) {
397                 HashtableAList h = new HashtableAList();
398                 h.zInsert(((ZE[][])entry.val)[j]);
399                 v[j] = h;
400             }
401             mTable.put(entry.key, v);
402         } else {
403             mTable.put(entry.key, entry.val);
404         }
405     }
406 
407     public void close() {
408         String[] keys = getKeyArray();
409         try {
410             for (int i = 0; i < keys.length; i++) {
411                 if (getType(keys[i]) == T_INPUTSTREAM) {
412                     getInputStream(keys[i]).close();
413                 } else if (getType(keys[i]) == (T_INPUTSTREAM | F_ARRAY)) {
414                     InputStream[] ins = getInputStreamArray(keys[i]);
415                     for (int j = 0; j < ins.length; j++) {
416                         ins[j].close();
417                     }
418                 } else if (getType(keys[i]) == T_ALIST) {
419                     getAList(keys[i]).close();
420                 } else if (getType(keys[i]) == (T_ALIST | F_ARRAY)) {
421                     AList[] als = getAListArray(keys[i]);
422                     for (int j = 0; j < als.length; j++) {
423                         als[j].close();
424                     }
425                 }
426             }
427         } catch (IOException e) {
428             throw X.toRTE(e);
429         }
430     }
431 
432     public AList newAList() {
433         return new HashtableAList();
434     }
435 
436     public String toString() {
437         return mTable.toString();
438     }
439 
440     /***
441      * Enhance given object's default String display for appearing
442      * nested in a pretty AList String.
443      * 
444      * @param obj Object to prettify
445      * @return prettified String
446      */
447     protected String prettyString(Object obj) {
448         if (obj instanceof AList) return ((AList)obj).toPrettyString();
449         else if (obj instanceof AList[]) return prettyString((AList[])obj);
450         else return "<"+obj+">"; 
451     }
452     
453     /* (non-Javadoc)
454      * @see st.ata.util.AList#toPrettyString()
455      */
456     public String toPrettyString() {
457         StringBuilder builder = new StringBuilder();
458         builder.append("{ ");
459         boolean needsComma = false; 
460         for( String key : getKeyArray()) {
461             if(needsComma) {
462                 builder.append(", ");
463             }
464             builder.append(key);
465             builder.append(": ");
466             builder.append(prettyString(mTable.get(key)));
467             needsComma = true; 
468         }
469         builder.append(" }");
470         return builder.toString();
471     }
472     
473     /***
474      * Provide a slightly-improved String of AList[]
475      * 
476      * @param alists
477      * @return prettified (in square brackets) of AList[]
478      */
479     protected String prettyString(AList[] alists) {
480         StringBuilder builder = new StringBuilder();
481         builder.append("[ ");
482         boolean needsComma = false; 
483         for( AList alist : alists) {
484             if(alist==null) continue;
485             if(needsComma) {
486                 builder.append(", ");
487             }
488             builder.append(alist.toPrettyString());
489             needsComma = true; 
490         }
491         builder.append(" ]");
492         return builder.toString();
493     }
494 }