| 1 | /* |
|---|
| 2 | * HashTable.java |
|---|
| 3 | * |
|---|
| 4 | * Copyright (C) 2002-2007 Peter Graves |
|---|
| 5 | * $Id: HashTable.java 12431 2010-02-08 08:05:15Z mevenson $ |
|---|
| 6 | * |
|---|
| 7 | * This program is free software; you can redistribute it and/or |
|---|
| 8 | * modify it under the terms of the GNU General Public License |
|---|
| 9 | * as published by the Free Software Foundation; either version 2 |
|---|
| 10 | * of the License, or (at your option) any later version. |
|---|
| 11 | * |
|---|
| 12 | * This program is distributed in the hope that it will be useful, |
|---|
| 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 15 | * GNU General Public License for more details. |
|---|
| 16 | * |
|---|
| 17 | * You should have received a copy of the GNU General Public License |
|---|
| 18 | * along with this program; if not, write to the Free Software |
|---|
| 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|---|
| 20 | * |
|---|
| 21 | * As a special exception, the copyright holders of this library give you |
|---|
| 22 | * permission to link this library with independent modules to produce an |
|---|
| 23 | * executable, regardless of the license terms of these independent |
|---|
| 24 | * modules, and to copy and distribute the resulting executable under |
|---|
| 25 | * terms of your choice, provided that you also meet, for each linked |
|---|
| 26 | * independent module, the terms and conditions of the license of that |
|---|
| 27 | * module. An independent module is a module which is not derived from |
|---|
| 28 | * or based on this library. If you modify this library, you may extend |
|---|
| 29 | * this exception to your version of the library, but you are not |
|---|
| 30 | * obligated to do so. If you do not wish to do so, delete this |
|---|
| 31 | * exception statement from your version. |
|---|
| 32 | */ |
|---|
| 33 | |
|---|
| 34 | package org.armedbear.lisp; |
|---|
| 35 | |
|---|
| 36 | import static org.armedbear.lisp.Lisp.*; |
|---|
| 37 | |
|---|
| 38 | public abstract class HashTable extends LispObject |
|---|
| 39 | { |
|---|
| 40 | private static final int DEFAULT_SIZE = 16; |
|---|
| 41 | |
|---|
| 42 | protected static final float loadFactor = 0.75f; |
|---|
| 43 | |
|---|
| 44 | protected final LispObject rehashSize; |
|---|
| 45 | protected final LispObject rehashThreshold; |
|---|
| 46 | |
|---|
| 47 | // The rounded product of the capacity and the load factor. When the number |
|---|
| 48 | // of elements exceeds the threshold, the implementation calls rehash(). |
|---|
| 49 | protected int threshold; |
|---|
| 50 | |
|---|
| 51 | // Array containing the actual key-value mappings. |
|---|
| 52 | protected HashEntry[] buckets; |
|---|
| 53 | |
|---|
| 54 | // The number of key-value pairs. |
|---|
| 55 | protected int count; |
|---|
| 56 | |
|---|
| 57 | protected HashTable() |
|---|
| 58 | { |
|---|
| 59 | rehashSize = new SingleFloat(1.5f); // FIXME |
|---|
| 60 | rehashThreshold = new SingleFloat(0.75f); // FIXME |
|---|
| 61 | buckets = new HashEntry[DEFAULT_SIZE]; |
|---|
| 62 | threshold = (int) (DEFAULT_SIZE * loadFactor); |
|---|
| 63 | } |
|---|
| 64 | |
|---|
| 65 | protected HashTable(int size, LispObject rehashSize, |
|---|
| 66 | LispObject rehashThreshold) |
|---|
| 67 | { |
|---|
| 68 | this.rehashSize = rehashSize; |
|---|
| 69 | this.rehashThreshold = rehashThreshold; |
|---|
| 70 | buckets = new HashEntry[size]; |
|---|
| 71 | threshold = (int) (size * loadFactor); |
|---|
| 72 | } |
|---|
| 73 | |
|---|
| 74 | protected static int calculateInitialCapacity(int size) |
|---|
| 75 | { |
|---|
| 76 | int capacity = 1; |
|---|
| 77 | while (capacity < size) |
|---|
| 78 | capacity <<= 1; |
|---|
| 79 | return capacity; |
|---|
| 80 | } |
|---|
| 81 | |
|---|
| 82 | public final LispObject getRehashSize() |
|---|
| 83 | { |
|---|
| 84 | return rehashSize; |
|---|
| 85 | } |
|---|
| 86 | |
|---|
| 87 | public final LispObject getRehashThreshold() |
|---|
| 88 | { |
|---|
| 89 | return rehashThreshold; |
|---|
| 90 | } |
|---|
| 91 | |
|---|
| 92 | public int getSize() |
|---|
| 93 | { |
|---|
| 94 | return buckets.length; |
|---|
| 95 | } |
|---|
| 96 | |
|---|
| 97 | public int getCount() |
|---|
| 98 | { |
|---|
| 99 | return count; |
|---|
| 100 | } |
|---|
| 101 | |
|---|
| 102 | public abstract Symbol getTest(); |
|---|
| 103 | |
|---|
| 104 | @Override |
|---|
| 105 | public LispObject typeOf() |
|---|
| 106 | { |
|---|
| 107 | return Symbol.HASH_TABLE; |
|---|
| 108 | } |
|---|
| 109 | |
|---|
| 110 | @Override |
|---|
| 111 | public LispObject classOf() |
|---|
| 112 | { |
|---|
| 113 | return BuiltInClass.HASH_TABLE; |
|---|
| 114 | } |
|---|
| 115 | |
|---|
| 116 | @Override |
|---|
| 117 | public LispObject typep(LispObject type) |
|---|
| 118 | { |
|---|
| 119 | if (type == Symbol.HASH_TABLE) |
|---|
| 120 | return T; |
|---|
| 121 | if (type == BuiltInClass.HASH_TABLE) |
|---|
| 122 | return T; |
|---|
| 123 | return super.typep(type); |
|---|
| 124 | } |
|---|
| 125 | |
|---|
| 126 | @Override |
|---|
| 127 | public boolean equalp(LispObject obj) |
|---|
| 128 | { |
|---|
| 129 | if (this == obj) |
|---|
| 130 | return true; |
|---|
| 131 | if (obj instanceof HashTable) |
|---|
| 132 | { |
|---|
| 133 | HashTable ht = (HashTable) obj; |
|---|
| 134 | if (count != ht.count) |
|---|
| 135 | return false; |
|---|
| 136 | if (getTest() != ht.getTest()) |
|---|
| 137 | return false; |
|---|
| 138 | LispObject entries = ENTRIES(); |
|---|
| 139 | while (entries != NIL) |
|---|
| 140 | { |
|---|
| 141 | LispObject entry = entries.car(); |
|---|
| 142 | LispObject key = entry.car(); |
|---|
| 143 | LispObject value = entry.cdr(); |
|---|
| 144 | if (!value.equalp(ht.get(key))) |
|---|
| 145 | return false; |
|---|
| 146 | entries = entries.cdr(); |
|---|
| 147 | } |
|---|
| 148 | return true; |
|---|
| 149 | } |
|---|
| 150 | return false; |
|---|
| 151 | } |
|---|
| 152 | |
|---|
| 153 | @Override |
|---|
| 154 | public LispObject getParts() |
|---|
| 155 | { |
|---|
| 156 | LispObject parts = NIL; |
|---|
| 157 | for (int i = 0; i < buckets.length; i++) |
|---|
| 158 | { |
|---|
| 159 | HashEntry e = buckets[i]; |
|---|
| 160 | while (e != null) |
|---|
| 161 | { |
|---|
| 162 | parts = parts.push(new Cons("KEY [bucket " + i + "]", e.key)); |
|---|
| 163 | parts = parts.push(new Cons("VALUE", e.value)); |
|---|
| 164 | e = e.next; |
|---|
| 165 | } |
|---|
| 166 | } |
|---|
| 167 | return parts.nreverse(); |
|---|
| 168 | } |
|---|
| 169 | |
|---|
| 170 | public synchronized void clear() |
|---|
| 171 | { |
|---|
| 172 | for (int i = buckets.length; i-- > 0;) |
|---|
| 173 | buckets[i] = null; |
|---|
| 174 | count = 0; |
|---|
| 175 | } |
|---|
| 176 | |
|---|
| 177 | // gethash key hash-table &optional default => value, present-p |
|---|
| 178 | public synchronized LispObject gethash(LispObject key) |
|---|
| 179 | |
|---|
| 180 | { |
|---|
| 181 | LispObject value = get(key); |
|---|
| 182 | final LispObject presentp; |
|---|
| 183 | if (value == null) |
|---|
| 184 | value = presentp = NIL; |
|---|
| 185 | else |
|---|
| 186 | presentp = T; |
|---|
| 187 | return LispThread.currentThread().setValues(value, presentp); |
|---|
| 188 | } |
|---|
| 189 | |
|---|
| 190 | // gethash key hash-table &optional default => value, present-p |
|---|
| 191 | public synchronized LispObject gethash(LispObject key, |
|---|
| 192 | LispObject defaultValue) |
|---|
| 193 | |
|---|
| 194 | { |
|---|
| 195 | LispObject value = get(key); |
|---|
| 196 | final LispObject presentp; |
|---|
| 197 | if (value == null) |
|---|
| 198 | { |
|---|
| 199 | value = defaultValue; |
|---|
| 200 | presentp = NIL; |
|---|
| 201 | } |
|---|
| 202 | else |
|---|
| 203 | presentp = T; |
|---|
| 204 | return LispThread.currentThread().setValues(value, presentp); |
|---|
| 205 | } |
|---|
| 206 | |
|---|
| 207 | public synchronized LispObject gethash1(LispObject key) |
|---|
| 208 | |
|---|
| 209 | { |
|---|
| 210 | final LispObject value = get(key); |
|---|
| 211 | return value != null ? value : NIL; |
|---|
| 212 | } |
|---|
| 213 | |
|---|
| 214 | public synchronized LispObject puthash(LispObject key, LispObject newValue) |
|---|
| 215 | |
|---|
| 216 | { |
|---|
| 217 | put(key, newValue); |
|---|
| 218 | return newValue; |
|---|
| 219 | } |
|---|
| 220 | |
|---|
| 221 | // remhash key hash-table => generalized-boolean |
|---|
| 222 | public synchronized LispObject remhash(LispObject key) |
|---|
| 223 | |
|---|
| 224 | { |
|---|
| 225 | // A value in a Lisp hash table can never be null, so... |
|---|
| 226 | return remove(key) != null ? T : NIL; |
|---|
| 227 | } |
|---|
| 228 | |
|---|
| 229 | @Override |
|---|
| 230 | public String writeToString() |
|---|
| 231 | { |
|---|
| 232 | if (Symbol.PRINT_READABLY.symbolValue(LispThread.currentThread()) != NIL) |
|---|
| 233 | { |
|---|
| 234 | error(new PrintNotReadable(list(Keyword.OBJECT, this))); |
|---|
| 235 | return null; // Not reached. |
|---|
| 236 | } |
|---|
| 237 | StringBuilder sb = new StringBuilder(getTest().writeToString()); |
|---|
| 238 | sb.append(' '); |
|---|
| 239 | sb.append(Symbol.HASH_TABLE.writeToString()); |
|---|
| 240 | sb.append(' '); |
|---|
| 241 | sb.append(count); |
|---|
| 242 | if (count == 1) |
|---|
| 243 | sb.append(" entry"); |
|---|
| 244 | else |
|---|
| 245 | sb.append(" entries"); |
|---|
| 246 | sb.append(", "); |
|---|
| 247 | sb.append(buckets.length); |
|---|
| 248 | sb.append(" buckets"); |
|---|
| 249 | return unreadableString(sb.toString()); |
|---|
| 250 | } |
|---|
| 251 | |
|---|
| 252 | public abstract LispObject get(LispObject key); |
|---|
| 253 | |
|---|
| 254 | public abstract void put(LispObject key, LispObject value) |
|---|
| 255 | ; |
|---|
| 256 | |
|---|
| 257 | public abstract LispObject remove(LispObject key); |
|---|
| 258 | |
|---|
| 259 | protected abstract void rehash(); |
|---|
| 260 | |
|---|
| 261 | // Returns a list of (key . value) pairs. |
|---|
| 262 | public LispObject ENTRIES() |
|---|
| 263 | { |
|---|
| 264 | LispObject list = NIL; |
|---|
| 265 | for (int i = buckets.length; i-- > 0;) |
|---|
| 266 | { |
|---|
| 267 | HashEntry e = buckets[i]; |
|---|
| 268 | while (e != null) |
|---|
| 269 | { |
|---|
| 270 | list = new Cons(new Cons(e.key, e.value), list); |
|---|
| 271 | e = e.next; |
|---|
| 272 | } |
|---|
| 273 | } |
|---|
| 274 | return list; |
|---|
| 275 | } |
|---|
| 276 | |
|---|
| 277 | public LispObject MAPHASH(LispObject function) |
|---|
| 278 | { |
|---|
| 279 | for (int i = buckets.length; i-- > 0;) |
|---|
| 280 | { |
|---|
| 281 | HashEntry e = buckets[i]; |
|---|
| 282 | while (e != null) |
|---|
| 283 | { |
|---|
| 284 | function.execute(e.key, e.value); |
|---|
| 285 | e = e.next; |
|---|
| 286 | } |
|---|
| 287 | } |
|---|
| 288 | return NIL; |
|---|
| 289 | } |
|---|
| 290 | |
|---|
| 291 | protected static class HashEntry |
|---|
| 292 | { |
|---|
| 293 | LispObject key; |
|---|
| 294 | LispObject value; |
|---|
| 295 | HashEntry next; |
|---|
| 296 | |
|---|
| 297 | HashEntry(LispObject key, LispObject value) |
|---|
| 298 | { |
|---|
| 299 | this.key = key; |
|---|
| 300 | this.value = value; |
|---|
| 301 | } |
|---|
| 302 | } |
|---|
| 303 | |
|---|
| 304 | // For EQUALP hash tables. |
|---|
| 305 | @Override |
|---|
| 306 | public int psxhash() |
|---|
| 307 | { |
|---|
| 308 | long result = 2062775257; // Chosen at random. |
|---|
| 309 | result = mix(result, count); |
|---|
| 310 | result = mix(result, getTest().sxhash()); |
|---|
| 311 | return (int) (result & 0x7fffffff); |
|---|
| 312 | } |
|---|
| 313 | } |
|---|