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 */
17package org.apache.commons.math.util;
18
19import java.io.IOException;
20import java.io.ObjectInputStream;
21import java.io.Serializable;
22import java.lang.reflect.Array;
23import java.util.ConcurrentModificationException;
24import java.util.NoSuchElementException;
25
26import org.apache.commons.math.Field;
27import org.apache.commons.math.FieldElement;
28import org.apache.commons.math.MathRuntimeException;
29import org.apache.commons.math.exception.util.LocalizedFormats;
30
31/**
32 * Open addressed map from int to FieldElement.
33 * <p>This class provides a dedicated map from integers to FieldElements with a
34 * much smaller memory overhead than standard <code>java.util.Map</code>.</p>
35 * <p>This class is not synchronized. The specialized iterators returned by
36 * {@link #iterator()} are fail-fast: they throw a
37 * <code>ConcurrentModificationException</code> when they detect the map has been
38 * modified during iteration.</p>
39 * @param <T> the type of the field elements
40 * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
41 * @since 2.0
42 */
43public class OpenIntToFieldHashMap<T extends FieldElement<T>> implements Serializable {
44
45    /** Status indicator for free table entries. */
46    protected static final byte FREE    = 0;
47
48    /** Status indicator for full table entries. */
49    protected static final byte FULL    = 1;
50
51    /** Status indicator for removed table entries. */
52    protected static final byte REMOVED = 2;
53
54    /** Serializable version identifier. */
55    private static final long serialVersionUID = -9179080286849120720L;
56
57    /** Load factor for the map. */
58    private static final float LOAD_FACTOR = 0.5f;
59
60    /** Default starting size.
61     * <p>This must be a power of two for bit mask to work properly. </p>
62     */
63    private static final int DEFAULT_EXPECTED_SIZE = 16;
64
65    /** Multiplier for size growth when map fills up.
66     * <p>This must be a power of two for bit mask to work properly. </p>
67     */
68    private static final int RESIZE_MULTIPLIER = 2;
69
70    /** Number of bits to perturb the index when probing for collision resolution. */
71    private static final int PERTURB_SHIFT = 5;
72
73    /** Field to which the elements belong. */
74    private final Field<T> field;
75
76    /** Keys table. */
77    private int[] keys;
78
79    /** Values table. */
80    private T[] values;
81
82    /** States table. */
83    private byte[] states;
84
85    /** Return value for missing entries. */
86    private final T missingEntries;
87
88    /** Current size of the map. */
89    private int size;
90
91    /** Bit mask for hash values. */
92    private int mask;
93
94    /** Modifications count. */
95    private transient int count;
96
97    /**
98     * Build an empty map with default size and using zero for missing entries.
99     * @param field field to which the elements belong
100     */
101    public OpenIntToFieldHashMap(final Field<T>field) {
102        this(field, DEFAULT_EXPECTED_SIZE, field.getZero());
103    }
104
105    /**
106     * Build an empty map with default size
107     * @param field field to which the elements belong
108     * @param missingEntries value to return when a missing entry is fetched
109     */
110    public OpenIntToFieldHashMap(final Field<T>field, final T missingEntries) {
111        this(field,DEFAULT_EXPECTED_SIZE, missingEntries);
112    }
113
114    /**
115     * Build an empty map with specified size and using zero for missing entries.
116     * @param field field to which the elements belong
117     * @param expectedSize expected number of elements in the map
118     */
119    public OpenIntToFieldHashMap(final Field<T> field,final int expectedSize) {
120        this(field,expectedSize, field.getZero());
121    }
122
123    /**
124     * Build an empty map with specified size.
125     * @param field field to which the elements belong
126     * @param expectedSize expected number of elements in the map
127     * @param missingEntries value to return when a missing entry is fetched
128     */
129    public OpenIntToFieldHashMap(final Field<T> field,final int expectedSize,
130                                  final T missingEntries) {
131        this.field = field;
132        final int capacity = computeCapacity(expectedSize);
133        keys   = new int[capacity];
134        values = buildArray(capacity);
135        states = new byte[capacity];
136        this.missingEntries = missingEntries;
137        mask   = capacity - 1;
138    }
139
140    /**
141     * Copy constructor.
142     * @param source map to copy
143     */
144    public OpenIntToFieldHashMap(final OpenIntToFieldHashMap<T> source) {
145        field = source.field;
146        final int length = source.keys.length;
147        keys = new int[length];
148        System.arraycopy(source.keys, 0, keys, 0, length);
149        values = buildArray(length);
150        System.arraycopy(source.values, 0, values, 0, length);
151        states = new byte[length];
152        System.arraycopy(source.states, 0, states, 0, length);
153        missingEntries = source.missingEntries;
154        size  = source.size;
155        mask  = source.mask;
156        count = source.count;
157    }
158
159    /**
160     * Compute the capacity needed for a given size.
161     * @param expectedSize expected size of the map
162     * @return capacity to use for the specified size
163     */
164    private static int computeCapacity(final int expectedSize) {
165        if (expectedSize == 0) {
166            return 1;
167        }
168        final int capacity   = (int) FastMath.ceil(expectedSize / LOAD_FACTOR);
169        final int powerOfTwo = Integer.highestOneBit(capacity);
170        if (powerOfTwo == capacity) {
171            return capacity;
172        }
173        return nextPowerOfTwo(capacity);
174    }
175
176    /**
177     * Find the smallest power of two greater than the input value
178     * @param i input value
179     * @return smallest power of two greater than the input value
180     */
181    private static int nextPowerOfTwo(final int i) {
182        return Integer.highestOneBit(i) << 1;
183    }
184
185    /**
186     * Get the stored value associated with the given key
187     * @param key key associated with the data
188     * @return data associated with the key
189     */
190    public T get(final int key) {
191
192        final int hash  = hashOf(key);
193        int index = hash & mask;
194        if (containsKey(key, index)) {
195            return values[index];
196        }
197
198        if (states[index] == FREE) {
199            return missingEntries;
200        }
201
202        int j = index;
203        for (int perturb = perturb(hash); states[index] != FREE; perturb >>= PERTURB_SHIFT) {
204            j = probe(perturb, j);
205            index = j & mask;
206            if (containsKey(key, index)) {
207                return values[index];
208            }
209        }
210
211        return missingEntries;
212
213    }
214
215    /**
216     * Check if a value is associated with a key.
217     * @param key key to check
218     * @return true if a value is associated with key
219     */
220    public boolean containsKey(final int key) {
221
222        final int hash  = hashOf(key);
223        int index = hash & mask;
224        if (containsKey(key, index)) {
225            return true;
226        }
227
228        if (states[index] == FREE) {
229            return false;
230        }
231
232        int j = index;
233        for (int perturb = perturb(hash); states[index] != FREE; perturb >>= PERTURB_SHIFT) {
234            j = probe(perturb, j);
235            index = j & mask;
236            if (containsKey(key, index)) {
237                return true;
238            }
239        }
240
241        return false;
242
243    }
244
245    /**
246     * Get an iterator over map elements.
247     * <p>The specialized iterators returned are fail-fast: they throw a
248     * <code>ConcurrentModificationException</code> when they detect the map
249     * has been modified during iteration.</p>
250     * @return iterator over the map elements
251     */
252    public Iterator iterator() {
253        return new Iterator();
254    }
255
256    /**
257     * Perturb the hash for starting probing.
258     * @param hash initial hash
259     * @return perturbed hash
260     */
261    private static int perturb(final int hash) {
262        return hash & 0x7fffffff;
263    }
264
265    /**
266     * Find the index at which a key should be inserted
267     * @param key key to lookup
268     * @return index at which key should be inserted
269     */
270    private int findInsertionIndex(final int key) {
271        return findInsertionIndex(keys, states, key, mask);
272    }
273
274    /**
275     * Find the index at which a key should be inserted
276     * @param keys keys table
277     * @param states states table
278     * @param key key to lookup
279     * @param mask bit mask for hash values
280     * @return index at which key should be inserted
281     */
282    private static int findInsertionIndex(final int[] keys, final byte[] states,
283                                          final int key, final int mask) {
284        final int hash = hashOf(key);
285        int index = hash & mask;
286        if (states[index] == FREE) {
287            return index;
288        } else if (states[index] == FULL && keys[index] == key) {
289            return changeIndexSign(index);
290        }
291
292        int perturb = perturb(hash);
293        int j = index;
294        if (states[index] == FULL) {
295            while (true) {
296                j = probe(perturb, j);
297                index = j & mask;
298                perturb >>= PERTURB_SHIFT;
299
300                if (states[index] != FULL || keys[index] == key) {
301                    break;
302                }
303            }
304        }
305
306        if (states[index] == FREE) {
307            return index;
308        } else if (states[index] == FULL) {
309            // due to the loop exit condition,
310            // if (states[index] == FULL) then keys[index] == key
311            return changeIndexSign(index);
312        }
313
314        final int firstRemoved = index;
315        while (true) {
316            j = probe(perturb, j);
317            index = j & mask;
318
319            if (states[index] == FREE) {
320                return firstRemoved;
321            } else if (states[index] == FULL && keys[index] == key) {
322                return changeIndexSign(index);
323            }
324
325            perturb >>= PERTURB_SHIFT;
326
327        }
328
329    }
330
331    /**
332     * Compute next probe for collision resolution
333     * @param perturb perturbed hash
334     * @param j previous probe
335     * @return next probe
336     */
337    private static int probe(final int perturb, final int j) {
338        return (j << 2) + j + perturb + 1;
339    }
340
341    /**
342     * Change the index sign
343     * @param index initial index
344     * @return changed index
345     */
346    private static int changeIndexSign(final int index) {
347        return -index - 1;
348    }
349
350    /**
351     * Get the number of elements stored in the map.
352     * @return number of elements stored in the map
353     */
354    public int size() {
355        return size;
356    }
357
358
359    /**
360     * Remove the value associated with a key.
361     * @param key key to which the value is associated
362     * @return removed value
363     */
364    public T remove(final int key) {
365
366        final int hash  = hashOf(key);
367        int index = hash & mask;
368        if (containsKey(key, index)) {
369            return doRemove(index);
370        }
371
372        if (states[index] == FREE) {
373            return missingEntries;
374        }
375
376        int j = index;
377        for (int perturb = perturb(hash); states[index] != FREE; perturb >>= PERTURB_SHIFT) {
378            j = probe(perturb, j);
379            index = j & mask;
380            if (containsKey(key, index)) {
381                return doRemove(index);
382            }
383        }
384
385        return missingEntries;
386
387    }
388
389    /**
390     * Check if the tables contain an element associated with specified key
391     * at specified index.
392     * @param key key to check
393     * @param index index to check
394     * @return true if an element is associated with key at index
395     */
396    private boolean containsKey(final int key, final int index) {
397        return (key != 0 || states[index] == FULL) && keys[index] == key;
398    }
399
400    /**
401     * Remove an element at specified index.
402     * @param index index of the element to remove
403     * @return removed value
404     */
405    private T doRemove(int index) {
406        keys[index]   = 0;
407        states[index] = REMOVED;
408        final T previous = values[index];
409        values[index] = missingEntries;
410        --size;
411        ++count;
412        return previous;
413    }
414
415    /**
416     * Put a value associated with a key in the map.
417     * @param key key to which value is associated
418     * @param value value to put in the map
419     * @return previous value associated with the key
420     */
421    public T put(final int key, final T value) {
422        int index = findInsertionIndex(key);
423        T previous = missingEntries;
424        boolean newMapping = true;
425        if (index < 0) {
426            index = changeIndexSign(index);
427            previous = values[index];
428            newMapping = false;
429        }
430        keys[index]   = key;
431        states[index] = FULL;
432        values[index] = value;
433        if (newMapping) {
434            ++size;
435            if (shouldGrowTable()) {
436                growTable();
437            }
438            ++count;
439        }
440        return previous;
441
442    }
443
444    /**
445     * Grow the tables.
446     */
447    private void growTable() {
448
449        final int oldLength      = states.length;
450        final int[] oldKeys      = keys;
451        final T[] oldValues = values;
452        final byte[] oldStates   = states;
453
454        final int newLength = RESIZE_MULTIPLIER * oldLength;
455        final int[] newKeys = new int[newLength];
456        final T[] newValues = buildArray(newLength);
457        final byte[] newStates = new byte[newLength];
458        final int newMask = newLength - 1;
459        for (int i = 0; i < oldLength; ++i) {
460            if (oldStates[i] == FULL) {
461                final int key = oldKeys[i];
462                final int index = findInsertionIndex(newKeys, newStates, key, newMask);
463                newKeys[index]   = key;
464                newValues[index] = oldValues[i];
465                newStates[index] = FULL;
466            }
467        }
468
469        mask   = newMask;
470        keys   = newKeys;
471        values = newValues;
472        states = newStates;
473
474    }
475
476    /**
477     * Check if tables should grow due to increased size.
478     * @return true if  tables should grow
479     */
480    private boolean shouldGrowTable() {
481        return size > (mask + 1) * LOAD_FACTOR;
482    }
483
484    /**
485     * Compute the hash value of a key
486     * @param key key to hash
487     * @return hash value of the key
488     */
489    private static int hashOf(final int key) {
490        final int h = key ^ ((key >>> 20) ^ (key >>> 12));
491        return h ^ (h >>> 7) ^ (h >>> 4);
492    }
493
494
495    /** Iterator class for the map. */
496    public class Iterator {
497
498        /** Reference modification count. */
499        private final int referenceCount;
500
501        /** Index of current element. */
502        private int current;
503
504        /** Index of next element. */
505        private int next;
506
507        /**
508         * Simple constructor.
509         */
510        private Iterator() {
511
512            // preserve the modification count of the map to detect concurrent modifications later
513            referenceCount = count;
514
515            // initialize current index
516            next = -1;
517            try {
518                advance();
519            } catch (NoSuchElementException nsee) {
520                // ignored
521            }
522
523        }
524
525        /**
526         * Check if there is a next element in the map.
527         * @return true if there is a next element
528         */
529        public boolean hasNext() {
530            return next >= 0;
531        }
532
533        /**
534         * Get the key of current entry.
535         * @return key of current entry
536         * @exception ConcurrentModificationException if the map is modified during iteration
537         * @exception NoSuchElementException if there is no element left in the map
538         */
539        public int key()
540            throws ConcurrentModificationException, NoSuchElementException {
541            if (referenceCount != count) {
542                throw MathRuntimeException.createConcurrentModificationException(LocalizedFormats.MAP_MODIFIED_WHILE_ITERATING);
543            }
544            if (current < 0) {
545                throw MathRuntimeException.createNoSuchElementException(LocalizedFormats.ITERATOR_EXHAUSTED);
546            }
547            return keys[current];
548        }
549
550        /**
551         * Get the value of current entry.
552         * @return value of current entry
553         * @exception ConcurrentModificationException if the map is modified during iteration
554         * @exception NoSuchElementException if there is no element left in the map
555         */
556        public T value()
557            throws ConcurrentModificationException, NoSuchElementException {
558            if (referenceCount != count) {
559                throw MathRuntimeException.createConcurrentModificationException(LocalizedFormats.MAP_MODIFIED_WHILE_ITERATING);
560            }
561            if (current < 0) {
562                throw MathRuntimeException.createNoSuchElementException(LocalizedFormats.ITERATOR_EXHAUSTED);
563            }
564            return values[current];
565        }
566
567        /**
568         * Advance iterator one step further.
569         * @exception ConcurrentModificationException if the map is modified during iteration
570         * @exception NoSuchElementException if there is no element left in the map
571         */
572        public void advance()
573            throws ConcurrentModificationException, NoSuchElementException {
574
575            if (referenceCount != count) {
576                throw MathRuntimeException.createConcurrentModificationException(LocalizedFormats.MAP_MODIFIED_WHILE_ITERATING);
577            }
578
579            // advance on step
580            current = next;
581
582            // prepare next step
583            try {
584                while (states[++next] != FULL) {
585                    // nothing to do
586                }
587            } catch (ArrayIndexOutOfBoundsException e) {
588                next = -2;
589                if (current < 0) {
590                    throw MathRuntimeException.createNoSuchElementException(LocalizedFormats.ITERATOR_EXHAUSTED);
591                }
592            }
593
594        }
595
596    }
597
598    /**
599     * Read a serialized object.
600     * @param stream input stream
601     * @throws IOException if object cannot be read
602     * @throws ClassNotFoundException if the class corresponding
603     * to the serialized object cannot be found
604     */
605    private void readObject(final ObjectInputStream stream)
606        throws IOException, ClassNotFoundException {
607        stream.defaultReadObject();
608        count = 0;
609    }
610
611    /** Build an array of elements.
612     * @param length size of the array to build
613     * @return a new array
614     */
615    @SuppressWarnings("unchecked") // field is of type T
616    private T[] buildArray(final int length) {
617        return (T[]) Array.newInstance(field.getZero().getClass(), length);
618    }
619
620}
621