Home | History | Annotate | Download | only in util
      1 /*
      2  *  Licensed to the Apache Software Foundation (ASF) under one or more
      3  *  contributor license agreements.  See the NOTICE file distributed with
      4  *  this work for additional information regarding copyright ownership.
      5  *  The ASF licenses this file to You under the Apache License, Version 2.0
      6  *  (the "License"); you may not use this file except in compliance with
      7  *  the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  *  Unless required by applicable law or agreed to in writing, software
     12  *  distributed under the License is distributed on an "AS IS" BASIS,
     13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  *  See the License for the specific language governing permissions and
     15  *  limitations under the License.
     16  */
     17 
     18 package org.apache.harmony.luni.util;
     19 
     20 import java.util.AbstractCollection;
     21 import java.util.AbstractMap;
     22 import java.util.AbstractSet;
     23 import java.util.Arrays;
     24 import java.util.Collection;
     25 import java.util.ConcurrentModificationException;
     26 import java.util.Iterator;
     27 import java.util.Map;
     28 import java.util.NoSuchElementException;
     29 import java.util.Set;
     30 
     31 /**
     32  *
     33  * Reductive hash with two keys
     34  *
     35  */
     36 public class TwoKeyHashMap<E, K, V> extends AbstractMap<String, V> {
     37 
     38     static final float DEFAULT_LOAD_FACTOR = 0.75f;
     39     static final int DEFAULT_INITIAL_SIZE = 16;
     40 
     41     private Set<Map.Entry<String, V>> entrySet;
     42     private Collection<V> values;
     43     private int size;
     44     private int arrSize;
     45     private int modCount;
     46 
     47     private Entry<E, K, V>[] arr;
     48 
     49     private float loadFactor;
     50     int threshold = 0;
     51 
     52     /**
     53      * Constructs an empty HashMap
     54      */
     55     public TwoKeyHashMap() {
     56         this(DEFAULT_INITIAL_SIZE, DEFAULT_LOAD_FACTOR);
     57     }
     58 
     59     /**
     60      * Constructs an empty HashMap
     61      *
     62      * @param initialCapacity
     63      */
     64     public TwoKeyHashMap(int initialCapacity) {
     65         this(initialCapacity, DEFAULT_LOAD_FACTOR);
     66     }
     67 
     68     /**
     69      * Constructs an empty HashMap
     70      *
     71      * @param initialCapacity
     72      * @param initialLoadFactor
     73      */
     74     @SuppressWarnings("unchecked")
     75     public TwoKeyHashMap(int initialCapacity, float initialLoadFactor) {
     76         if (initialCapacity < 0) {
     77             throw new IllegalArgumentException("initialCapacity should be >= 0");
     78         }
     79         if (initialLoadFactor <= 0) {
     80             throw new IllegalArgumentException(
     81                     "initialLoadFactor should be > 0");
     82         }
     83         loadFactor = initialLoadFactor;
     84         if (initialCapacity == Integer.MAX_VALUE) {
     85             initialCapacity--;
     86         }
     87         arrSize = initialCapacity > 0 ? initialCapacity : 1;
     88         threshold = (int) (arrSize * loadFactor);
     89         arr = new Entry[arrSize + 1];
     90     }
     91 
     92     /**
     93      * Returns a collection view of the values
     94      */
     95     public Collection<V> values() {
     96         if (values == null) {
     97             values = new ValuesCollectionImpl();
     98         }
     99         return values;
    100     }
    101 
    102     /**
    103      * Returns a collection view of the mappings
    104      */
    105     public Set<Map.Entry<String, V>> entrySet() {
    106         if (entrySet == null) {
    107             entrySet = new EntrySetImpl();
    108         }
    109         return entrySet;
    110     }
    111 
    112     /**
    113      * Clears the map
    114      */
    115     public void clear() {
    116         modCount++;
    117         size = 0;
    118         Arrays.fill(arr, 0, arr.length, null);
    119     }
    120 
    121     /**
    122      * Removes the mapping for the keys
    123      *
    124      * @param key1
    125      * @param key2
    126      * @return
    127      */
    128     public V remove(Object key1, Object key2) {
    129         Entry<E, K, V> e = removeEntry(key1, key2);
    130         return null != e ? e.value : null;
    131     }
    132 
    133     /**
    134      * Associates the specified value with the specified keys in this map
    135      *
    136      * @param key1
    137      * @param key2
    138      * @param value
    139      * @return
    140      */
    141     public V put(E key1, K key2, V value) {
    142         if (key1 == null && key2 == null) {
    143             int index = arrSize;
    144             if (arr[index] == null) {
    145                 arr[index] = createEntry(0, null, null, value, null);
    146                 size++;
    147                 modCount++;
    148                 return null;
    149             } else {
    150                 V oldValue = arr[index].value;
    151                 arr[index].value = value;
    152                 return oldValue;
    153             }
    154         }
    155 
    156         int hash = key1.hashCode() + key2.hashCode();
    157         int index = (hash & 0x7fffffff) % arrSize;
    158         Entry<E, K, V> e = arr[index];
    159 
    160         while (e != null) {
    161             if (hash == e.hash && key1.equals(e.getKey1())
    162                     && key2.equals(e.getKey2())) {
    163                 V oldValue = e.value;
    164                 e.value = value;
    165                 return oldValue;
    166             }
    167             e = e.next;
    168         }
    169 
    170         arr[index] = createEntry(hash, key1, key2, value, arr[index]);
    171         size++;
    172         modCount++;
    173 
    174         if (size > threshold) {
    175             rehash();
    176         }
    177         return null;
    178     }
    179 
    180     /**
    181      * Rehash the map
    182      *
    183      */
    184     @SuppressWarnings("unchecked")
    185     void rehash() {
    186         int newArrSize = (arrSize + 1) * 2 + 1;
    187         if (newArrSize < 0) {
    188             newArrSize = Integer.MAX_VALUE - 1;
    189         }
    190         Entry<E, K, V>[] newArr = new Entry[newArrSize + 1];
    191 
    192         for (int i = 0; i < arr.length - 1; i++) {
    193             Entry<E, K, V> entry = arr[i];
    194             while (entry != null) {
    195                 Entry<E, K, V> next = entry.next;
    196 
    197                 int newIndex = (entry.hash & 0x7fffffff) % newArrSize;
    198                 entry.next = newArr[newIndex];
    199                 newArr[newIndex] = entry;
    200 
    201                 entry = next;
    202             }
    203         }
    204         newArr[newArrSize] = arr[arrSize]; // move null entry
    205         arrSize = newArrSize;
    206 
    207         // The maximum array size is reached, increased loadFactor
    208         // will keep array from further growing
    209         if (arrSize == Integer.MAX_VALUE) {
    210             loadFactor *= 10;
    211         }
    212         threshold = (int) (arrSize * loadFactor);
    213         arr = newArr;
    214     }
    215 
    216     /**
    217      * Returns true if this map contains a mapping for {@code key1} and {@code key2}.
    218      */
    219     public boolean containsKey(Object key1, Object key2) {
    220         return findEntry(key1, key2) != null;
    221     }
    222 
    223     /**
    224      * Return the value by keys
    225      *
    226      * @param key1
    227      * @param key2
    228      * @return
    229      */
    230     public V get(Object key1, Object key2) {
    231         Entry<E, K, V> e = findEntry(key1, key2);
    232         if (e != null) {
    233             return e.value;
    234         }
    235         return null;
    236     }
    237 
    238     /**
    239      * Returns true if this map contains no key-value mappings
    240      */
    241     public boolean isEmpty() {
    242         return size == 0;
    243     }
    244 
    245     /**
    246      * Returns the number of mappings
    247      */
    248     public int size() {
    249         return size;
    250     }
    251 
    252     /**
    253      * Creates new entry
    254      *
    255      * @param hashCode
    256      * @param key1
    257      * @param key2
    258      * @param value
    259      * @param next
    260      * @return
    261      */
    262     Entry<E, K, V> createEntry(int hashCode, E key1, K key2, V value,
    263             Entry<E, K, V> next) {
    264         return new Entry<E, K, V>(hashCode, key1, key2, value, next);
    265     }
    266 
    267     /**
    268      * Creates entries iterator
    269      *
    270      * @return
    271      */
    272     Iterator<Map.Entry<String, V>> createEntrySetIterator() {
    273         return new EntryIteratorImpl();
    274     }
    275 
    276     /**
    277      * Creates values iterator
    278      *
    279      * @return
    280      */
    281     Iterator<V> createValueCollectionIterator() {
    282         return new ValueIteratorImpl();
    283     }
    284 
    285     /**
    286      * Entry implementation for the TwoKeyHashMap class
    287      *
    288      */
    289     public static class Entry<E, K, V> implements Map.Entry<String, V> {
    290         int hash;
    291         E key1;
    292         K key2;
    293         V value;
    294         Entry<E, K, V> next;
    295 
    296         public Entry(int hash, E key1, K key2, V value, Entry<E, K, V> next) {
    297             this.hash = hash;
    298             this.key1 = key1;
    299             this.key2 = key2;
    300             this.value = value;
    301             this.next = next;
    302         }
    303 
    304         public String getKey() {
    305             return key1.toString() + key2.toString();
    306         }
    307 
    308         public E getKey1() {
    309             return key1;
    310         }
    311 
    312         public K getKey2() {
    313             return key2;
    314         }
    315 
    316         public V getValue() {
    317             return value;
    318         }
    319 
    320         public V setValue(V value) {
    321             V oldValue = this.value;
    322             this.value = value;
    323             return oldValue;
    324         }
    325 
    326         public boolean equals(Object obj) {
    327             if (!(obj instanceof Entry)) {
    328                 return false;
    329             }
    330 
    331             Entry<?, ?, ?> e = (Entry<?, ?, ?>) obj;
    332             Object getKey1 = e.getKey1();
    333             Object getKey2 = e.getKey2();
    334             Object getValue = e.getValue();
    335             if ((key1 == null && getKey1 != null)
    336                     || (key2 == null && getKey2 != null)
    337                     || (value == null && getValue != null)
    338                     || !key1.equals(e.getKey1()) || !key2.equals(e.getKey2())
    339                     || !value.equals(getValue)) {
    340                 return false;
    341             }
    342             return true;
    343         }
    344 
    345         public int hashCode() {
    346             int hash1 = (key1 == null ? 0 : key1.hashCode());
    347             int hash2 = (key2 == null ? 0 : key2.hashCode());
    348             return (hash1 + hash2) ^ (value == null ? 0 : value.hashCode());
    349         }
    350 
    351     }
    352 
    353     class EntrySetImpl extends AbstractSet<Map.Entry<String, V>> {
    354         public int size() {
    355             return size;
    356         }
    357 
    358         public void clear() {
    359             TwoKeyHashMap.this.clear();
    360         }
    361 
    362         public boolean isEmpty() {
    363             return size == 0;
    364         }
    365 
    366         public boolean contains(Object obj) {
    367             if (!(obj instanceof Entry)) {
    368                 return false;
    369             }
    370 
    371             Entry<?, ?, ?> entry = (Entry<?, ?, ?>) obj;
    372             Entry<E, K, V> entry2 = findEntry(entry.getKey1(), entry.getKey2());
    373             if (entry2 == null) {
    374                 return false;
    375             }
    376             Object value = entry.getValue();
    377             Object value2 = entry2.getValue();
    378             return value == null ? value2 == null : value.equals(value2);
    379         }
    380 
    381         public boolean remove(Object obj) {
    382             if (!(obj instanceof Entry)) {
    383                 return false;
    384             }
    385             return removeEntry(((Entry) obj).getKey1(), ((Entry) obj).getKey2()) != null;
    386         }
    387 
    388         public Iterator<Map.Entry<String, V>> iterator() {
    389             return createEntrySetIterator();
    390         }
    391     }
    392 
    393     // Iterates Entries inside the Map
    394     class EntryIteratorImpl implements Iterator<Map.Entry<String, V>> {
    395         private int startModCount;
    396         private boolean found;
    397         private int curr = -1;
    398         private int returned_index = -1;
    399         private Entry<E, K, V> curr_entry;
    400         private Entry<E, K, V> returned_entry;
    401 
    402         EntryIteratorImpl() {
    403             startModCount = modCount;
    404         }
    405 
    406         public boolean hasNext() {
    407             if (found) {
    408                 return true;
    409             }
    410             if (curr_entry != null) {
    411                 curr_entry = curr_entry.next;
    412             }
    413             if (curr_entry == null) {
    414                 for (curr++; curr < arr.length && arr[curr] == null; curr++) {
    415                 }
    416 
    417                 if (curr < arr.length) {
    418                     curr_entry = arr[curr];
    419                 }
    420             }
    421             return found = (curr_entry != null);
    422         }
    423 
    424         public Map.Entry<String, V> next() {
    425             if (modCount != startModCount) {
    426                 throw new ConcurrentModificationException();
    427             }
    428             if (!hasNext()) {
    429                 throw new NoSuchElementException();
    430             }
    431 
    432             found = false;
    433             returned_index = curr;
    434             returned_entry = curr_entry;
    435             return (Map.Entry<String, V>) curr_entry;
    436         }
    437 
    438         public void remove() {
    439             if (returned_index == -1) {
    440                 throw new IllegalStateException();
    441             }
    442 
    443             if (modCount != startModCount) {
    444                 throw new ConcurrentModificationException();
    445             }
    446 
    447             Entry<E, K, V> p = null;
    448             Entry<E, K, V> e = arr[returned_index];
    449             while (e != returned_entry) {
    450                 p = e;
    451                 e = e.next;
    452             }
    453             if (p != null) {
    454                 p.next = returned_entry.next;
    455             } else {
    456                 arr[returned_index] = returned_entry.next;
    457             }
    458             size--;
    459             modCount++;
    460             startModCount++;
    461             returned_index = -1;
    462         }
    463     }
    464 
    465     private final Entry<E, K, V> findEntry(Object key1, Object key2) {
    466         if (key1 == null && key2 == null) {
    467             return arr[arrSize];
    468         }
    469 
    470         int hash = key1.hashCode() + key2.hashCode();
    471         int index = (hash & 0x7fffffff) % arrSize;
    472         Entry<E, K, V> e = arr[index];
    473 
    474         while (e != null) {
    475             if (hash == e.hash && key1.equals(e.getKey1())
    476                     && key2.equals(e.getKey2())) {
    477                 return e;
    478             }
    479             e = e.next;
    480         }
    481         return null;
    482     }
    483 
    484     // Removes entry
    485     private final Entry<E, K, V> removeEntry(Object key1, Object key2) {
    486         if (key1 == null && key2 == null) {
    487             int index = arrSize;
    488             if (arr[index] != null) {
    489                 Entry<E, K, V> ret = arr[index];
    490                 arr[index] = null;
    491                 size--;
    492                 modCount++;
    493                 return ret;
    494             }
    495             return null;
    496         }
    497 
    498         int hash = key1.hashCode() + key2.hashCode();
    499         int index = (hash & 0x7fffffff) % arrSize;
    500 
    501         Entry<E, K, V> e = arr[index];
    502         Entry<E, K, V> prev = e;
    503         while (e != null) {
    504             if (hash == e.hash && key1.equals(e.getKey1())
    505                     && key2.equals(e.getKey2())) {
    506                 if (prev == e) {
    507                     arr[index] = e.next;
    508                 } else {
    509                     prev.next = e.next;
    510                 }
    511                 size--;
    512                 modCount++;
    513                 return e;
    514             }
    515 
    516             prev = e;
    517             e = e.next;
    518         }
    519         return null;
    520     }
    521 
    522     /**
    523      * An instance is returned by the values() call.
    524      */
    525     class ValuesCollectionImpl extends AbstractCollection<V> {
    526         public int size() {
    527             return size;
    528         }
    529 
    530         public void clear() {
    531             TwoKeyHashMap.this.clear();
    532         }
    533 
    534         public boolean isEmpty() {
    535             return size == 0;
    536         }
    537 
    538         public Iterator<V> iterator() {
    539             return createValueCollectionIterator();
    540         }
    541 
    542         public boolean contains(Object obj) {
    543             return containsValue(obj);
    544         }
    545     }
    546 
    547     class ValueIteratorImpl implements Iterator<V> {
    548         private EntryIteratorImpl itr;
    549 
    550         ValueIteratorImpl() {
    551             super();
    552             this.itr = new EntryIteratorImpl();
    553         }
    554 
    555         public V next() {
    556             return itr.next().getValue();
    557         }
    558 
    559         public void remove() {
    560             itr.remove();
    561         }
    562 
    563         public boolean hasNext() {
    564             return itr.hasNext();
    565         }
    566     }
    567 }
    568