001/* ***** BEGIN LICENSE BLOCK ***** 002 * Version: MPL 1.1/GPL 2.0/LGPL 2.1 003 * 004 * The contents of this file are subject to the Mozilla Public License Version 005 * 1.1 (the "License"); you may not use this file except in compliance with 006 * the License. You may obtain a copy of the License at 007 * http://www.mozilla.org/MPL/ 008 * 009 * Software distributed under the License is distributed on an "AS IS" basis, 010 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 011 * for the specific language governing rights and limitations under the 012 * License. 013 * 014 * The Original Code is part of dcm4che, an implementation of DICOM(TM) in 015 * Java(TM), hosted at https://github.com/gunterze/dcm4che. 016 * 017 * The Initial Developer of the Original Code is 018 * Agfa Healthcare. 019 * Portions created by the Initial Developer are Copyright (C) 2011 020 * the Initial Developer. All Rights Reserved. 021 * 022 * Contributor(s): 023 * See @authors listed below 024 * 025 * Alternatively, the contents of this file may be used under the terms of 026 * either the GNU General Public License Version 2 or later (the "GPL"), or 027 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 028 * in which case the provisions of the GPL or the LGPL are applicable instead 029 * of those above. If you wish to allow use of your version of this file only 030 * under the terms of either the GPL or the LGPL, and not to allow others to 031 * use your version of this file under the terms of the MPL, indicate your 032 * decision by deleting the provisions above and replace them with the notice 033 * and other provisions required by the GPL or the LGPL. If you do not delete 034 * the provisions above, a recipient may use your version of this file under 035 * the terms of any one of the MPL, the GPL or the LGPL. 036 * 037 * ***** END LICENSE BLOCK ***** */ 038 039package org.dcm4che3.util; 040 041import java.util.Arrays; 042 043/** 044 * @author Gunter Zeilinger <gunterze@gmail.com> 045 */ 046public class IntHashMap<V> implements Cloneable, java.io.Serializable { 047 048 private static final int DEFAULT_CAPACITY = 32; 049 private static final int MINIMUM_CAPACITY = 4; 050 private static final int MAXIMUM_CAPACITY = 1 << 30; 051 052 private static final byte FREE = 0; 053 private static final byte FULL = 1; 054 private static final byte REMOVED = -1; 055 056 private transient int[] keys; 057 private transient Object[] values; 058 private transient byte[] states; 059 private transient int free; 060 private transient int size; 061 062 public IntHashMap() { 063 init(DEFAULT_CAPACITY); 064 } 065 066 public IntHashMap(int expectedMaxSize) { 067 if (expectedMaxSize < 0) 068 throw new IllegalArgumentException( 069 "expectedMaxSize is negative: " + expectedMaxSize); 070 071 init(capacity(expectedMaxSize)); 072 } 073 074 private int capacity(int expectedMaxSize) { 075 int minCapacity = expectedMaxSize << 1; 076 if (minCapacity > MAXIMUM_CAPACITY) 077 return MAXIMUM_CAPACITY; 078 079 int capacity = MINIMUM_CAPACITY; 080 while (capacity < minCapacity) 081 capacity <<= 1; 082 083 return capacity; 084 } 085 086 private void init(int initCapacity) { 087 keys = new int[initCapacity]; 088 values = new Object[initCapacity]; 089 states = new byte[initCapacity]; 090 free = initCapacity >>> 1; 091 } 092 093 public int size() { 094 return size; 095 } 096 097 public boolean isEmpty() { 098 return size == 0; 099 } 100 101 102 @SuppressWarnings("unchecked") 103 public V get(int key) { 104 byte[] states = this.states; 105 int[] keys = this.keys; 106 int mask = keys.length - 1; 107 int i = key & mask; 108 while (states[i] != FREE) { 109 if (keys[i] == key) 110 return (V) values[i]; 111 i = (i + 1) & mask; 112 } 113 return null; 114 } 115 116 public boolean containsKey(int key) { 117 byte[] states = this.states; 118 int[] keys = this.keys; 119 int mask = keys.length - 1; 120 int i = key & mask; 121 while (states[i] != FREE) { 122 if (keys[i] == key) 123 return states[i] > FREE; // states[i] == FULL 124 i = (i + 1) & mask; 125 } 126 return false; 127 } 128 129 @SuppressWarnings("unchecked") 130 public V put(int key, V value) { 131 byte[] states = this.states; 132 int[] keys = this.keys; 133 int mask = keys.length - 1; 134 int i = key & mask; 135 136 while (states[i] > FREE) { // states[i] == FULL 137 if (keys[i] == key) { 138 V oldValue = (V) values[i]; 139 values[i] = value; 140 return oldValue; 141 } 142 i = (i + 1) & mask; 143 } 144 byte oldState = states[i]; 145 states[i] = FULL; 146 keys[i] = key; 147 values[i] = value; 148 ++size; 149 if (oldState == FREE && --free < 0) 150 resize(Math.max(capacity(size), keys.length)); 151 return null; 152 } 153 154 public void trimToSize() { 155 resize(capacity(size)); 156 } 157 158 public void rehash() { 159 resize(keys.length); 160 } 161 162 private void resize(int newLength) { 163 if (newLength > MAXIMUM_CAPACITY) 164 throw new IllegalStateException("Capacity exhausted."); 165 166 int[] oldKeys = keys; 167 Object[] oldValues = values; 168 byte[] oldStates = states; 169 int[] newKeys = new int[newLength]; 170 Object[] newValues = new Object[newLength]; 171 byte[] newStates = new byte[newLength]; 172 int mask = newLength - 1; 173 174 for (int j = 0; j < oldKeys.length; j++) { 175 if (oldStates[j] > 0) { // states[i] == FULL 176 int key = oldKeys[j]; 177 int i = key & mask; 178 while (newStates[i] != FREE) 179 i = (i + 1) & mask; 180 newStates[i] = FULL; 181 newKeys[i] = key; 182 newValues[i] = oldValues[j]; 183 oldValues[j] = null; 184 } 185 } 186 keys = newKeys; 187 values = newValues; 188 states = newStates; 189 free = (newLength >>> 1) - size; 190 } 191 192 @SuppressWarnings("unchecked") 193 public V remove(int key) { 194 byte[] states = this.states; 195 int[] keys = this.keys; 196 int mask = keys.length - 1; 197 int i = key & mask; 198 while (states[i] != FREE) { 199 if (keys[i] == key) { 200 if (states[i] < FREE) // states[i] == REMOVED 201 return null; 202 203 states[i] = REMOVED; 204 V oldValue = (V) values[i]; 205 values[i] = null; 206 size--; 207 return oldValue; 208 } 209 i = (i + 1) & mask; 210 } 211 return null; 212 } 213 214 public void clear() { 215 Arrays.fill(values, null); 216 Arrays.fill(states, FREE); 217 size = 0; 218 free = keys.length >>> 1; 219 } 220 221 @SuppressWarnings("unchecked") 222 public Object clone() { 223 try { 224 IntHashMap<V> m = (IntHashMap<V>) super.clone(); 225 m.states = states.clone(); 226 m.keys = keys.clone(); 227 m.values = values.clone(); 228 return m; 229 } catch (CloneNotSupportedException e) { 230 throw new InternalError(); 231 } 232 } 233 234 public interface Visitor<V> { 235 boolean visit(int key, V value); 236 } 237 238 @SuppressWarnings("unchecked") 239 public boolean accept(Visitor<V> visitor) { 240 for (int i = 0; i < states.length; i++) 241 if (states[i] > FREE) // states[i] == FULL 242 if (!visitor.visit(keys[i], (V) values[i])) 243 return false; 244 return true; 245 } 246 247 private static final long serialVersionUID = 9153226350279204066L; 248 249 private void writeObject(java.io.ObjectOutputStream s) 250 throws java.io.IOException { 251 s.defaultWriteObject(); 252 253 byte[] states = this.states; 254 int[] keys = this.keys; 255 Object[] values = this.values; 256 s.writeInt(size); 257 for (int i = 0; i < states.length; i++) { 258 if (states[i] > FREE) { // states[i] == FULL 259 s.writeInt(keys[i]); 260 s.writeObject(values[i]); 261 } 262 } 263 } 264 265 private void readObject(java.io.ObjectInputStream s) 266 throws java.io.IOException, ClassNotFoundException { 267 s.defaultReadObject(); 268 269 int count = s.readInt(); 270 init(capacity(count)); 271 size = count; 272 free -= count; 273 274 byte[] states = this.states; 275 int[] keys = this.keys; 276 Object[] values = this.values; 277 int mask = keys.length - 1; 278 279 while (count-- > 0) { 280 int key = s.readInt(); 281 int i = key & mask; 282 while (states[i] != FREE) 283 i = (i + 1) & mask; 284 states[i] = FULL; 285 keys[i] = key; 286 values[i] = s.readObject(); 287 } 288 } 289}