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 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                 @SuppressWarnings("unchecked")
 137                 Hashtable<Object,Object> tmp = (Hashtable<Object,Object>)table;
 138                 tmp.put(key, value);
 139             }
 140         }
 141     }
 142 
 143     /*
 144      * Gets the value for key
 145      */
 146     public Object get(Object key) {
 147         Object value = null;
 148         if (table !=null) {
 149             if (isArray()) {
 150                 Object[] array = (Object[])table;
 151                 for (int i = 0; i<array.length-1; i+=2) {
 152                     if (array[i].equals(key)) {
 153                         value = array[i+1];
 154                         break;
 155                     }
 156                 }
 157             } else {
 158                 value = ((Hashtable)table).get(key);
 159             }
 160         }
 161         return value;
 162     }
 163 
 164     /*
 165      * Returns the number of pairs in storage
 166      */
 167     public int size() {
 168         int size;
 169         if (table==null)
 170             return 0;
 171         if (isArray()) {
 172             size = ((Object[])table).length/2;
 173         } else {
 174             size = ((Hashtable)table).size();
 175         }
 176         return size;
 177     }
 178 
 179     /*
 180      * Returns true if we have a value for the key
 181      */
 182     public boolean containsKey(Object key) {
 183         boolean contains = false;
 184         if (table !=null) {
 185             if (isArray()) {
 186                 Object[] array = (Object[])table;
 187                 for (int i = 0; i<array.length-1; i+=2) {
 188                     if (array[i].equals(key)) {
 189                         contains = true;
 190                         break;
 191                     }
 192                 }
 193             } else {
 194                 contains = ((Hashtable)table).containsKey(key);
 195             }
 196         }
 197         return contains;
 198     }
 199 
 200     /*
 201      * Removes the key and its value
 202      * Returns the value for the pair removed
 203      */
 204     public Object remove(Object key){
 205         Object value = null;
 206         if (key==null) {
 207             return null;
 208         }
 209         if (table !=null) {
 210             if (isArray()){
 211                 // Is key on the list?
 212                 int index = -1;
 213                 Object[] array = (Object[])table;
 214                 for (int i = array.length-2; i>=0; i-=2) {
 215                     if (array[i].equals(key)) {
 216                         index = i;
 217                         value = array[i+1];
 218                         break;
 219                     }
 220                 }
 221 
 222                 // If so,  remove it
 223                 if (index != -1) {
 224                     Object[] tmp = new Object[array.length-2];
 225                     // Copy the list up to index
 226                     System.arraycopy(array, 0, tmp, 0, index);
 227                     // Copy from two past the index, up to
 228                     // the end of tmp (which is two elements
 229                     // shorter than the old list)
 230                     if (index < tmp.length)
 231                         System.arraycopy(array, index+2, tmp, index,
 232                                          tmp.length - index);
 233                     // set the listener array to the new array or null
 234                     table = (tmp.length == 0) ? null : tmp;
 235                 }
 236             } else {
 237                 value = ((Hashtable)table).remove(key);
 238             }
 239             if (size()==ARRAY_BOUNDARY - 1 && !isArray()) {
 240                 shrink();
 241             }
 242         }
 243         return value;
 244     }
 245 
 246     /**
 247      * Removes all the mappings.
 248      */
 249     public void clear() {
 250         table = null;
 251     }
 252 
 253     /*
 254      * Returns a clone of the <code>ArrayTable</code>.
 255      */
 256     public Object clone() {
 257         ArrayTable newArrayTable = new ArrayTable();
 258         if (isArray()) {
 259             Object[] array = (Object[])table;
 260             for (int i = 0 ;i < array.length-1 ; i+=2) {
 261                 newArrayTable.put(array[i], array[i+1]);
 262             }
 263         } else {
 264             Hashtable<?,?> tmp = (Hashtable)table;
 265             Enumeration<?> keys = tmp.keys();
 266             while (keys.hasMoreElements()) {
 267                 Object o = keys.nextElement();
 268                 newArrayTable.put(o,tmp.get(o));
 269             }
 270         }
 271         return newArrayTable;
 272     }
 273 
 274     /**
 275      * Returns the keys of the table, or <code>null</code> if there
 276      * are currently no bindings.
 277      * @param keys  array of keys
 278      * @return an array of bindings
 279      */
 280     public Object[] getKeys(Object[] keys) {
 281         if (table == null) {
 282             return null;
 283         }
 284         if (isArray()) {
 285             Object[] array = (Object[])table;
 286             if (keys == null) {
 287                 keys = new Object[array.length / 2];
 288             }
 289             for (int i = 0, index = 0 ;i < array.length-1 ; i+=2,
 290                      index++) {
 291                 keys[index] = array[i];
 292             }
 293         } else {
 294             Hashtable<?,?> tmp = (Hashtable)table;
 295             Enumeration<?> enum_ = tmp.keys();
 296             int counter = tmp.size();
 297             if (keys == null) {
 298                 keys = new Object[counter];
 299             }
 300             while (counter > 0) {
 301                 keys[--counter] = enum_.nextElement();
 302             }
 303         }
 304         return keys;
 305     }
 306 
 307     /*
 308      * Returns true if the current storage mechanism is
 309      * an array of alternating key-value pairs.
 310      */
 311     private boolean isArray(){
 312         return (table instanceof Object[]);
 313     }
 314 
 315     /*
 316      * Grows the storage from an array to a hashtable.
 317      */
 318     private void grow() {
 319         Object[] array = (Object[])table;
 320         Hashtable<Object, Object> tmp = new Hashtable<Object, Object>(array.length/2);
 321         for (int i = 0; i<array.length; i+=2) {
 322             tmp.put(array[i], array[i+1]);
 323         }
 324         table = tmp;
 325     }
 326 
 327     /*
 328      * Shrinks the storage from a hashtable to an array.
 329      */
 330     private void shrink() {
 331         Hashtable<?,?> tmp = (Hashtable)table;
 332         Object[] array = new Object[tmp.size()*2];
 333         Enumeration<?> keys = tmp.keys();
 334         int j = 0;
 335 
 336         while (keys.hasMoreElements()) {
 337             Object o = keys.nextElement();
 338             array[j] = o;
 339             array[j+1] = tmp.get(o);
 340             j+=2;
 341         }
 342         table = array;
 343     }
 344 }