source: trunk/abcl/src/org/armedbear/lisp/HashTable.java

Last change on this file was 13440, checked in by ehuelsmann, 13 years ago

Rename writeToString() to printObject() since that's what it's being used for.
Additionally, create princToString() for use in error messages, making the

required replacement where appropriate.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 13.7 KB
Line 
1/*
2 * HashTable.java
3 *
4 * Copyright (C) 2002-2007 Peter Graves
5 * Copyright (C) 2010 Erik Huelsmann
6 * $Id: HashTable.java 13440 2011-08-05 21:25:10Z ehuelsmann $
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 *
22 * As a special exception, the copyright holders of this library give you
23 * permission to link this library with independent modules to produce an
24 * executable, regardless of the license terms of these independent
25 * modules, and to copy and distribute the resulting executable under
26 * terms of your choice, provided that you also meet, for each linked
27 * independent module, the terms and conditions of the license of that
28 * module.  An independent module is a module which is not derived from
29 * or based on this library.  If you modify this library, you may extend
30 * this exception to your version of the library, but you are not
31 * obligated to do so.  If you do not wish to do so, delete this
32 * exception statement from your version.
33 */
34package org.armedbear.lisp;
35
36import java.util.concurrent.locks.ReentrantLock;
37import static org.armedbear.lisp.Lisp.*;
38
39public class HashTable 
40    extends LispObject
41    implements org.armedbear.lisp.protocol.Hashtable
42{
43
44    protected static final float loadFactor = 0.75f;
45    protected final LispObject rehashSize;
46    protected final LispObject rehashThreshold;
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    // Array containing the actual key-value mappings.
51    @SuppressWarnings("VolatileArrayField")
52    protected volatile HashEntry[] buckets;
53    // The number of key-value pairs.
54    protected volatile int count;
55    final Comparator comparator;
56    final private ReentrantLock lock = new ReentrantLock();
57
58    protected HashTable(Comparator c, int size, LispObject rehashSize,
59            LispObject rehashThreshold) {
60        this.rehashSize = rehashSize;
61        this.rehashThreshold = rehashThreshold;
62        buckets = new HashEntry[size];
63        threshold = (int) (size * loadFactor);
64        comparator = c;
65    }
66
67    protected static int calculateInitialCapacity(int size) {
68        int capacity = 1;
69        while (capacity < size) {
70            capacity <<= 1;
71        }
72        return capacity;
73    }
74
75    public static HashTable newEqHashTable(int size, LispObject rehashSize,
76            LispObject rehashThreshold) {
77        return new HashTable(new Comparator(), size, rehashSize, rehashThreshold);
78    }
79
80    public static HashTable newEqlHashTable(int size, LispObject rehashSize,
81            LispObject rehashThreshold) {
82        return new HashTable(new EqlComparator(), size, rehashSize, rehashThreshold);
83    }
84
85    public static HashTable newEqualHashTable(int size, LispObject rehashSize,
86            LispObject rehashThreshold) {
87        return new HashTable(new EqualComparator(), size, rehashSize, rehashThreshold);
88    }
89
90    public static LispObject newEqualpHashTable(int size, LispObject rehashSize,
91            LispObject rehashThreshold) {
92        return new HashTable(new EqualpComparator(), size, rehashSize, rehashThreshold);
93    }
94
95    public final LispObject getRehashSize() {
96        return rehashSize;
97    }
98
99    public final LispObject getRehashThreshold() {
100        return rehashThreshold;
101    }
102
103    public int getSize() {
104        return buckets.length;
105    }
106
107    public int getCount() {
108        return count;
109    }
110
111    @Override
112    public LispObject typeOf() {
113        return Symbol.HASH_TABLE;
114    }
115
116    @Override
117    public LispObject classOf() {
118        return BuiltInClass.HASH_TABLE;
119    }
120
121    @Override
122    public LispObject typep(LispObject type) {
123        if (type == Symbol.HASH_TABLE) {
124            return T;
125        }
126        if (type == BuiltInClass.HASH_TABLE) {
127            return T;
128        }
129        return super.typep(type);
130    }
131
132    @Override
133    public boolean equalp(LispObject obj) {
134        if (this == obj) {
135            return true;
136        }
137        if (obj instanceof HashTable) {
138            HashTable ht = (HashTable) obj;
139            if (count != ht.count) {
140                return false;
141            }
142            if (getTest() != ht.getTest()) {
143                return false;
144            }
145            LispObject entries = ENTRIES();
146            while (entries != NIL) {
147                LispObject entry = entries.car();
148                LispObject key = entry.car();
149                LispObject value = entry.cdr();
150                if (!value.equalp(ht.get(key))) {
151                    return false;
152                }
153                entries = entries.cdr();
154            }
155            return true;
156        }
157        return false;
158    }
159
160    @Override
161    public LispObject getParts() {
162        // No need to take out a read lock, for the same reason as MAPHASH
163        HashEntry[] b = buckets;
164        LispObject parts = NIL;
165        for (int i = 0; i < b.length; i++) {
166            HashEntry e = b[i];
167            while (e != null) {
168                parts = parts.push(new Cons("KEY [bucket " + i + "]", e.key));
169                parts = parts.push(new Cons("VALUE", e.value));
170                e = e.next;
171            }
172        }
173        return parts.nreverse();
174    }
175
176    public void clear() {
177        lock.lock();
178        try {
179            buckets = new HashEntry[buckets.length];
180            count = 0;
181        } finally {
182            lock.unlock();
183        }
184    }
185
186    // gethash key hash-table &optional default => value, present-p
187    public LispObject gethash(LispObject key) {
188        LispObject value = get(key);
189        final LispObject presentp;
190        if (value == null) {
191            value = presentp = NIL;
192        } else {
193            presentp = T;
194        }
195        return LispThread.currentThread().setValues(value, presentp);
196    }
197
198    // gethash key hash-table &optional default => value, present-p
199    public LispObject gethash(LispObject key, LispObject defaultValue) {
200        LispObject value = get(key);
201        final LispObject presentp;
202        if (value == null) {
203            value = defaultValue;
204            presentp = NIL;
205        } else {
206            presentp = T;
207        }
208        return LispThread.currentThread().setValues(value, presentp);
209    }
210
211    public LispObject gethash1(LispObject key) {
212        final LispObject value = get(key);
213        return value != null ? value : NIL;
214    }
215
216    public LispObject puthash(LispObject key, LispObject newValue) {
217        put(key, newValue);
218        return newValue;
219    }
220
221    // remhash key hash-table => generalized-boolean
222    public LispObject remhash(LispObject key) {
223        // A value in a Lisp hash table can never be null, so...
224        return remove(key) != null ? T : NIL;
225    }
226
227    @Override
228    public String printObject() {
229        if (Symbol.PRINT_READABLY.symbolValue(LispThread.currentThread()) != NIL) {
230            error(new PrintNotReadable(list(Keyword.OBJECT, this)));
231            return null; // Not reached.
232        }
233        StringBuilder sb = new StringBuilder(getTest().princToString());
234        sb.append(' ');
235        sb.append(Symbol.HASH_TABLE.princToString());
236        sb.append(' ');
237        sb.append(count);
238        if (count == 1) {
239            sb.append(" entry");
240        } else {
241            sb.append(" entries");
242        }
243        sb.append(", ");
244        sb.append(buckets.length);
245        sb.append(" buckets");
246        return unreadableString(sb.toString());
247    }
248
249    public Symbol getTest() {
250        return comparator.getTest();
251    }
252
253    protected HashEntry getEntry(LispObject key) {
254        HashEntry[] b = buckets;
255        int hash = comparator.hash(key);
256        HashEntry e = b[hash & (b.length - 1)];
257        while (e != null) {
258            if (hash == e.hash &&
259                    (key == e.key || comparator.keysEqual(key, e.key))) {
260                return e;
261            }
262            e = e.next;
263        }
264        return null;
265    }
266
267    public LispObject get(LispObject key) {
268        HashEntry e = getEntry(key);
269        LispObject v = (e == null) ? null : e.value;
270
271        if (e == null || v != null) {
272            return v;
273        }
274
275        lock.lock();
276        try {
277            return e.value;
278        } finally {
279            lock.unlock();
280        }
281    }
282
283    public void put(LispObject key, LispObject value) {
284        lock.lock();
285        try {
286            HashEntry e = getEntry(key);
287            if (e != null) {
288                e.value = value;
289            } else {
290                // Not found. We need to add a new entry.
291                if (++count > threshold) {
292                    rehash();
293                }
294
295                int hash = comparator.hash(key);
296                int index = hash & (buckets.length - 1);
297                buckets[index] = new HashEntry(key, hash, value, buckets[index]);
298            }
299        } finally {
300            lock.unlock();
301        }
302    }
303
304    public LispObject remove(LispObject key) {
305        lock.lock();
306        try {
307            int index = comparator.hash(key) & (buckets.length - 1);
308
309            HashEntry e = buckets[index];
310            HashEntry last = null;
311            while (e != null) {
312                if (comparator.keysEqual(key, e.key)) {
313                    if (last == null) {
314                        buckets[index] = e.next;
315                    } else {
316                        last.next = e.next;
317                    }
318                    --count;
319                    return e.value;
320                }
321                last = e;
322                e = e.next;
323            }
324            return null;
325        } finally {
326            lock.unlock();
327        }
328    }
329
330    protected void rehash() {
331        lock.lock();
332        try {
333            final int newCapacity = buckets.length * 2;
334            threshold = (int) (newCapacity * loadFactor);
335            int mask = newCapacity - 1;
336            HashEntry[] newBuckets = new HashEntry[newCapacity];
337
338            for (int i = buckets.length; i-- > 0;) {
339                HashEntry e = buckets[i];
340                while (e != null) {
341                    final int index = comparator.hash(e.key) & mask;
342                    newBuckets[index] = new HashEntry(e.key, e.hash, e.value,
343                            newBuckets[index]);
344                    e = e.next;
345                }
346            }
347            buckets = newBuckets;
348        } finally {
349            lock.unlock();
350        }
351    }
352
353
354    public LispObject ENTRIES() {
355        return getEntries();
356    }
357
358    // Returns a list of (key . value) pairs.       
359    public LispObject getEntries() {
360        // No need to take out a read lock, for the same reason as MAPHASH
361        HashEntry[] b = buckets;
362        LispObject list = NIL;
363        for (int i = b.length; i-- > 0;) {
364            HashEntry e = b[i];
365            while (e != null) {
366                list = new Cons(new Cons(e.key, e.value), list);
367                e = e.next;
368            }
369        }
370        return list;
371    }
372
373    public LispObject MAPHASH(LispObject function) {
374        // Don't take out a read lock: it can't be upgraded to a write
375        // lock, which would block the scenario where put() is called to
376        // set the value of the current entry
377
378        HashEntry[] b = buckets;
379        for (int i = b.length; i-- > 0;) {
380            HashEntry e = b[i];
381            while (e != null) {
382                function.execute(e.key, e.value);
383                e = e.next;
384            }
385        }
386        return NIL;
387    }
388
389    protected static class Comparator {
390
391        Symbol getTest() {
392            return Symbol.EQ;
393        }
394
395        boolean keysEqual(LispObject key1, LispObject key2) {
396            return key1 == key2;
397        }
398
399        int hash(LispObject key) {
400            return key.sxhash();
401        }
402    }
403
404    protected static class EqlComparator extends Comparator {
405
406        @Override
407        Symbol getTest() {
408            return Symbol.EQL;
409        }
410
411        @Override
412        boolean keysEqual(LispObject key1, LispObject key2) {
413            return key1.eql(key2);
414        }
415    }
416
417    protected static class EqualComparator extends Comparator {
418
419        @Override
420        Symbol getTest() {
421            return Symbol.EQUAL;
422        }
423
424        @Override
425        boolean keysEqual(LispObject key1, LispObject key2) {
426            return key1.equal(key2);
427        }
428    }
429
430    protected static class EqualpComparator extends Comparator {
431
432        @Override
433        Symbol getTest() {
434            return Symbol.EQUALP;
435        }
436
437        @Override
438        boolean keysEqual(LispObject key1, LispObject key2) {
439            return key1.equalp(key2);
440        }
441
442        @Override
443        int hash(LispObject key) {
444            return key.psxhash();
445        }
446    }
447
448    protected static class HashEntry {
449
450        LispObject key;
451        int hash;
452        volatile LispObject value;
453        HashEntry next;
454
455        HashEntry(LispObject key, int hash, LispObject value, HashEntry next) {
456            this.key = key;
457            this.hash = hash;
458            this.value = value;
459            this.next = next;
460        }
461    }
462
463    // For EQUALP hash tables.
464    @Override
465    public int psxhash() {
466        long result = 2062775257; // Chosen at random.
467        result = mix(result, count);
468        result = mix(result, getTest().sxhash());
469        return (int) (result & 0x7fffffff);
470    }
471}
Note: See TracBrowser for help on using the repository browser.