1 /*
   2  * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javax.swing;
  26 
  27 import java.io.IOException;
  28 import java.io.ObjectOutputStream;
  29 import java.io.Serializable;
  30 import java.util.Enumeration;
  31 import java.util.Hashtable;
  32 
  33 /*
  34  * Private storage mechanism for Action key-value pairs.
  35  * In most cases this will be an array of alternating
  36  * key-value pairs.  As it grows larger it is scaled
  37  * up to a Hashtable.
  38  * <p>
  39  * This does no synchronization, if you need thread safety synchronize on
  40  * another object before calling this.
  41  *
  42  * @author Georges Saab
  43  * @author Scott Violet
  44  */
  45 class ArrayTable implements Cloneable {
  46     // Our field for storage
  47     private Object table = null;
  48     private static final int ARRAY_BOUNDARY = 8;
  49 
  50 
  51     /**
  52      * Writes the passed in ArrayTable to the passed in ObjectOutputStream.
  53      * The data is saved as an integer indicating how many key/value
  54      * pairs are being archived, followed by the the key/value pairs. If
  55      * <code>table</code> is null, 0 will be written to <code>s</code>.
  56      * <p>
  57      * This is a convenience method that ActionMap/InputMap and
  58      * AbstractAction use to avoid having the same code in each class.
  59      */
  60     static void writeArrayTable(ObjectOutputStream s, ArrayTable table) throws IOException {
  61         Object keys[];
  62 
  63         if (table == null || (keys = table.getKeys(null)) == null) {
  64             s.writeInt(0);
  65         }
  66         else {
  67             // Determine how many keys have Serializable values, when
  68             // done all non-null values in keys identify the Serializable
  69             // values.
  70             int validCount = 0;
  71 
  72             for (int counter = 0; counter < keys.length; counter++) {
  73                 Object key = keys[counter];
  74 
  75                 /* include in Serialization when both keys and values are Serializable */
  76                 if (    (key instanceof Serializable
  77                          && table.get(key) instanceof Serializable)
  78                              ||
  79                          /* include these only so that we get the appropriate exception below */
  80                         (key instanceof ClientPropertyKey
  81                          && ((ClientPropertyKey)key).getReportValueNotSerializable())) {
  82 
  83                     validCount++;
  84                 } else {
  85                     keys[counter] = null;
  86                 }
  87             }
  88             // Write ou the Serializable key/value pairs.
  89             s.writeInt(validCount);
  90             if (validCount > 0) {
  91                 for (Object key : keys) {
  92                     if (key != null) {
  93                         s.writeObject(key);
  94                         s.writeObject(table.get(key));
  95                         if (--validCount == 0) {
  96                             break;
  97                         }
  98                     }
  99                 }
 100             }
 101         }
 102     }
 103 
 104 
 105     /*
 106      * Put the key-value pair into storage
 107      */
 108     public void put(Object key, Object value){
 109         if (table==null) {
 110             table = new Object[] {key, value};
 111         } else {
 112             int size = size();
 113             if (size < ARRAY_BOUNDARY) {              // We are an array
 114                 if (containsKey(key)) {
 115                     Object[] tmp = (Object[])table;
 116                     for (int i = 0; i<tmp.length-1; i+=2) {
 117                         if (tmp[i].equals(key)) {
 118                             tmp[i+1]=value;
 119                             break;
 120                         }
 121                     }
 122                 } else {
 123                     Object[] array = (Object[])table;
 124                     int i = array.length;
 125                     Object[] tmp = new Object[i+2];
 126                     System.arraycopy(array, 0, tmp, 0, i);
 127 
 128                     tmp[i] = key;
 129                     tmp[i+1] = value;
 130                     table = tmp;
 131                 }
 132             } else {                 // We are a hashtable
 133                 if ((size==ARRAY_BOUNDARY) && isArray()) {
 134                     grow();
 135                 }
 136                 ((Hashtable<Object,Object>)table).put(key, value);
 137             }
 138         }
 139     }
 140 
 141     /*
 142      * Gets the value for key
 143      */
 144     public Object get(Object key) {
 145         Object value = null;
 146         if (table !=null) {
 147             if (isArray()) {
 148                 Object[] array = (Object[])table;
 149                 for (int i = 0; i<array.length-1; i+=2) {
 150                     if (array[i].equals(key)) {
 151                         value = array[i+1];
 152                         break;
 153                     }
 154                 }
 155             } else {
 156                 value = ((Hashtable)table).get(key);
 157             }
 158         }
 159         return value;
 160     }
 161 
 162     /*
 163      * Returns the number of pairs in storage
 164      */
 165     public int size() {
 166         int size;
 167         if (table==null)
 168             return 0;
 169         if (isArray()) {
 170             size = ((Object[])table).length/2;
 171         } else {
 172             size = ((Hashtable)table).size();
 173         }
 174         return size;
 175     }
 176 
 177     /*
 178      * Returns true if we have a value for the key
 179      */
 180     public boolean containsKey(Object key) {
 181         boolean contains = false;
 182         if (table !=null) {
 183             if (isArray()) {
 184                 Object[] array = (Object[])table;
 185                 for (int i = 0; i<array.length-1; i+=2) {
 186                     if (array[i].equals(key)) {
 187                         contains = true;
 188                         break;
 189                     }
 190                 }
 191             } else {
 192                 contains = ((Hashtable)table).containsKey(key);
 193             }
 194         }
 195         return contains;
 196     }
 197 
 198     /*
 199      * Removes the key and its value
 200      * Returns the value for the pair removed
 201      */
 202     public Object remove(Object key){
 203         Object value = null;
 204         if (key==null) {
 205             return null;
 206         }
 207         if (table !=null) {
 208             if (isArray()){
 209                 // Is key on the list?
 210                 int index = -1;
 211                 Object[] array = (Object[])table;
 212                 for (int i = array.length-2; i>=0; i-=2) {
 213                     if (array[i].equals(key)) {
 214                         index = i;
 215                         value = array[i+1];
 216                         break;
 217                     }
 218                 }
 219 
 220                 // If so,  remove it
 221                 if (index != -1) {
 222                     Object[] tmp = new Object[array.length-2];
 223                     // Copy the list up to index
 224                     System.arraycopy(array, 0, tmp, 0, index);
 225                     // Copy from two past the index, up to
 226                     // the end of tmp (which is two elements
 227                     // shorter than the old list)
 228                     if (index < tmp.length)
 229                         System.arraycopy(array, index+2, tmp, index,
 230                                          tmp.length - index);
 231                     // set the listener array to the new array or null
 232                     table = (tmp.length == 0) ? null : tmp;
 233                 }
 234             } else {
 235                 value = ((Hashtable)table).remove(key);
 236             }
 237             if (size()==ARRAY_BOUNDARY - 1 && !isArray()) {
 238                 shrink();
 239             }
 240         }
 241         return value;
 242     }
 243 
 244     /**
 245      * Removes all the mappings.
 246      */
 247     public void clear() {
 248         table = null;
 249     }
 250 
 251     /*
 252      * Returns a clone of the <code>ArrayTable</code>.
 253      */
 254     public Object clone() {
 255         ArrayTable newArrayTable = new ArrayTable();
 256         if (isArray()) {
 257             Object[] array = (Object[])table;
 258             for (int i = 0 ;i < array.length-1 ; i+=2) {
 259                 newArrayTable.put(array[i], array[i+1]);
 260             }
 261         } else {
 262             Hashtable<?,?> tmp = (Hashtable)table;
 263             Enumeration<?> keys = tmp.keys();
 264             while (keys.hasMoreElements()) {
 265                 Object o = keys.nextElement();
 266                 newArrayTable.put(o,tmp.get(o));
 267             }
 268         }
 269         return newArrayTable;
 270     }
 271 
 272     /**
 273      * Returns the keys of the table, or <code>null</code> if there
 274      * are currently no bindings.
 275      * @param keys  array of keys
 276      * @return an array of bindings
 277      */
 278     public Object[] getKeys(Object[] keys) {
 279         if (table == null) {
 280             return null;
 281         }
 282         if (isArray()) {
 283             Object[] array = (Object[])table;
 284             if (keys == null) {
 285                 keys = new Object[array.length / 2];
 286             }
 287             for (int i = 0, index = 0 ;i < array.length-1 ; i+=2,
 288                      index++) {
 289                 keys[index] = array[i];
 290             }
 291         } else {
 292             Hashtable<?,?> tmp = (Hashtable)table;
 293             Enumeration<?> enum_ = tmp.keys();
 294             int counter = tmp.size();
 295             if (keys == null) {
 296                 keys = new Object[counter];
 297             }
 298             while (counter > 0) {
 299                 keys[--counter] = enum_.nextElement();
 300             }
 301         }
 302         return keys;
 303     }
 304 
 305     /*
 306      * Returns true if the current storage mechanism is
 307      * an array of alternating key-value pairs.
 308      */
 309     private boolean isArray(){
 310         return (table instanceof Object[]);
 311     }
 312 
 313     /*
 314      * Grows the storage from an array to a hashtable.
 315      */
 316     private void grow() {
 317         Object[] array = (Object[])table;
 318         Hashtable<Object, Object> tmp = new Hashtable<Object, Object>(array.length/2);
 319         for (int i = 0; i<array.length; i+=2) {
 320             tmp.put(array[i], array[i+1]);
 321         }
 322         table = tmp;
 323     }
 324 
 325     /*
 326      * Shrinks the storage from a hashtable to an array.
 327      */
 328     private void shrink() {
 329         Hashtable<?,?> tmp = (Hashtable)table;
 330         Object[] array = new Object[tmp.size()*2];
 331         Enumeration<?> keys = tmp.keys();
 332         int j = 0;
 333 
 334         while (keys.hasMoreElements()) {
 335             Object o = keys.nextElement();
 336             array[j] = o;
 337             array[j+1] = tmp.get(o);
 338             j+=2;
 339         }
 340         table = array;
 341     }
 342 }