source: josm/trunk/src/org/openstreetmap/josm/data/osm/Storage.java @ 5241

Revision 5170, 14.1 KB checked in by Don-vip, 6 weeks ago (diff)

cleanup svn:mime-type properties preventing Java sources from being viewed as such on Trac

  • Property svn:eol-style set to native
Line 
1/*
2 *  JOSMng - a Java Open Street Map editor, the next generation.
3 *
4 *  Copyright (C) 2008 Petr Nejedly <P.Nejedly@sh.cvut.cz>
5 *
6 *  This program is free software; you can redistribute it and/or modify
7 *  it under the terms of the GNU General Public License as published by
8 *  the Free Software Foundation; either version 2 of the License, or
9 *  (at your option) any later version.
10 *
11 *  This program is distributed in the hope that it will be useful,
12 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 *  GNU General Public License for more details.
15
16 *  You should have received a copy of the GNU General Public License along
17 *  with this program; if not, write to the Free Software Foundation, Inc.,
18 *  51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
19 */
20
21package org.openstreetmap.josm.data.osm;
22
23import java.util.AbstractSet;
24import java.util.Collection;
25import java.util.ConcurrentModificationException;
26import java.util.Iterator;
27import java.util.Map;
28import java.util.NoSuchElementException;
29import java.util.Set;
30
31/**
32 * A Set-like class that allows looking up equivalent preexising instance.
33 * It is useful whereever one would use self-mapping construct like
34 * <code>Map<T,T>.put(t,t), that is, for caches, uniqueness filters or similar.
35 *
36 * The semantics of equivalency can be external to the object, using the
37 * {@link Hash} interface. The set also supports querying for entries using
38 * different key type, in case you can provide a Hash implementation
39 * that can resolve the equality.
40 *
41 * <h2>Examples</h2>
42 * <ul><li>A String cache:
43 * <pre>
44 * Storage<String> cache = new Storage(); // use default Hash
45 * for (String input : data) {
46 *     String onlyOne = cache.putIfUnique(input);
47 *     ....
48 * }
49 * </pre></li>
50 * <li>Identity-based set:
51 * <pre>
52 * Storage<Object> identity = new Storage(new Hash<Object,Object> {
53 *     public int getHashCode(Object o) {
54 *         return System.identityHashCode(o);
55 *     }
56 *     public boolean equals(Object o1, Object o2) {
57 *         return o1 == o2;
58 *     }
59 *  });
60 * </pre></li>
61 * <li>An object with int ID and id-based lookup:
62 * <pre>
63 * class Thing { int id; }
64 * Storage<Thing> things = new Storage(new Hash<Thing,Thing>() {
65 *     public int getHashCode(Thing t) {
66 *         return t.id;
67 *     }
68 *     public boolean equals(Thing t1, Thing t2) {
69 *         return t1 == t2;
70 *     }
71 *  });
72 * Map<Integer,Thing> fk = things.foreignKey(new Hash<Integer,Thing>() {
73 *     public int getHashCode(Integer i) {
74 *         return i.getIntValue();
75 *     }
76 *     public boolean equals(Integer k, Thing t) {
77 *         return t.id == k.getIntvalue();
78 *     }
79 * }
80 *
81 * things.put(new Thing(3));
82 * assert things.get(new Thing(3)) == fk.get(3);
83 * </pre></li>
84 *
85 *
86 * @author nenik
87 */
88public class Storage<T> extends AbstractSet<T> {
89    private final Hash<? super T,? super T> hash;
90    private T[] data;
91    private int mask;
92    private int size;
93    private transient volatile int modCount = 0;
94    private float loadFactor = 0.6f;
95    private static final int DEFAULT_CAPACITY = 16;
96    private final boolean safeIterator;
97    private boolean arrayCopyNecessary;
98
99    public Storage() {
100        this(Storage.<T>defaultHash(), DEFAULT_CAPACITY, false);
101    }
102
103    public Storage(int capacity) {
104        this(Storage.<T>defaultHash(), capacity, false);
105    }
106
107    public Storage(Hash<? super T,? super T> ha) {
108        this(ha, DEFAULT_CAPACITY, false);
109    }
110
111    public Storage(boolean safeIterator) {
112        this(Storage.<T>defaultHash(), DEFAULT_CAPACITY, safeIterator);
113    }
114
115    public Storage(int capacity, boolean safeIterator) {
116        this(Storage.<T>defaultHash(), capacity, safeIterator);
117    }
118
119    public Storage(Hash<? super T,? super T> ha, boolean safeIterator) {
120        this(ha, DEFAULT_CAPACITY, safeIterator);
121    }
122
123    public Storage(Hash<? super T, ? super T> ha, int capacity) {
124        this(ha, capacity, false);
125    }
126    /**
127     * constructor
128     * @param ha
129     * @param capacity
130     * @param safeIterator If set to false, you must not modify the Storage
131     *          while iterating over it. If set to true, you can savely
132     *          modify, but the read-only iteration will happen on a copy
133     *          of the unmodified Storage.
134     *          This is similar to CopyOnWriteArrayList.
135     */
136    public Storage(Hash<? super T, ? super T> ha, int capacity, boolean safeIterator) {
137        this.hash = ha;
138        int cap = 1 << (int)(Math.ceil(Math.log(capacity/loadFactor) / Math.log(2)));
139        @SuppressWarnings("unchecked") T[] newData = (T[]) new Object[cap];
140        data = newData;
141        mask = data.length - 1;
142        this.safeIterator = safeIterator;
143    }
144
145    private void copyArray() {
146        if (arrayCopyNecessary) {
147            @SuppressWarnings("unchecked") T[] newData = (T[]) new Object[data.length];
148            System.arraycopy(data, 0, newData, 0, data.length);
149            data = newData;
150            arrayCopyNecessary = false;
151        }
152    }
153
154    // --------------- Collection implementation ------------------------
155    @Override
156    public synchronized int size() {
157        return size;
158    }
159
160    @Override
161    public synchronized Iterator<T> iterator() {
162        if (safeIterator) {
163            arrayCopyNecessary = true;
164            return new SafeReadonlyIter(data);
165        } else
166            return new Iter();
167
168    }
169
170    @Override
171    public synchronized boolean contains(Object o) {
172        @SuppressWarnings("unchecked") T t = (T) o;
173        int bucket = getBucket(hash, t);
174        return bucket >= 0;
175    }
176
177    @Override
178    public synchronized boolean add(T t) {
179        T orig = putUnique(t);
180        return orig == t;
181    }
182
183    @Override
184    public synchronized boolean remove(Object o) {
185        @SuppressWarnings("unchecked") T t = (T) o;
186        T tOrig = removeElem(t);
187        return tOrig != null;
188    }
189
190    @Override
191    public synchronized void clear() {
192        copyArray();
193        modCount++;
194        size = 0;
195        for (int i = 0; i<data.length; i++) {
196            data[i] = null;
197        }
198    }
199
200    @Override
201    public synchronized int hashCode() {
202        int h = 0;
203        for (T t : this) {
204            h += hash.getHashCode(t);
205        }
206        return h;
207    }
208
209    // ----------------- Extended API ----------------------------
210
211    public synchronized T put(T t) {
212        copyArray();
213        modCount++;
214        ensureSpace();
215
216        int bucket = getBucket(hash, t);
217        if (bucket < 0) {
218            size++;
219            bucket = ~bucket;
220            assert data[bucket] == null;
221        }
222
223        T old = data[bucket];
224        data[bucket] = t;
225
226        return old;
227    }
228
229    public synchronized T get(T t) {
230        int bucket = getBucket(hash, t);
231        return bucket < 0 ? null : data[bucket];
232    }
233
234    public synchronized T putUnique(T t) {
235        copyArray();
236        modCount++;
237        ensureSpace();
238
239        int bucket = getBucket(hash, t);
240        if (bucket < 0) { // unique
241            size++;
242            assert data[~bucket] == null;
243            data[~bucket] = t;
244            return t;
245        }
246
247        return data[bucket];
248    }
249
250    public synchronized T removeElem(T t) {
251        copyArray();
252        modCount++;
253        int bucket = getBucket(hash, t);
254        return bucket < 0 ? null : doRemove(bucket);
255    }
256
257    public <K> Map<K,T> foreignKey(Hash<K,? super T> h) {
258        return new FMap<K>(h);
259    }
260
261    // ---------------- Implementation
262
263    /**
264     * Additional mixing of hash
265     */
266    private int rehash(int h) {
267        //return 54435761*h;
268        return 1103515245*h >> 2;
269    }
270
271    /**
272     * Finds a bucket for given key.
273     *
274     * @param key The key to compare
275     * @return the bucket equivalent to the key or -(bucket) as an empty slot
276     * where such an entry can be stored.
277     */
278    private <K> int getBucket(Hash<K,? super T> ha, K key) {
279        T entry;
280        int hcode = rehash(ha.getHashCode(key));
281        int bucket = hcode & mask;
282        while ((entry = data[bucket]) != null) {
283            if (ha.equals(key, entry))
284                return bucket;
285            bucket = (bucket+1) & mask;
286        }
287        return ~bucket;
288    }
289
290    private T doRemove(int slot) {
291        T t = data[slot];
292        assert t != null;
293
294        fillTheHole(slot); // fill the hole (or null it)
295        size--;
296        return t;
297    }
298
299    private void fillTheHole(int hole) {
300        int bucket = (hole+1) & mask;
301        T entry;
302
303        while ((entry = data[bucket]) != null) {
304            int right = rehash(hash.getHashCode(entry)) & mask;
305            // if the entry should be in <hole+1,bucket-1> (circular-wise)
306            // we can't move it. The move can be proved safe otherwise,
307            // because the entry safely belongs to <previous_null+1,hole>
308            if ((bucket < right && (right <= hole || hole <= bucket)) ||
309                    (right <=hole && hole <= bucket)) {
310
311                data[hole] = data[bucket];
312                hole = bucket;
313            }
314            bucket = (bucket+1) & mask;
315        }
316
317        // no entry belongs here, just null out the slot
318        data[hole] = null;
319    }
320
321    private void ensureSpace() {
322        if (size > data.length*loadFactor) { // rehash
323            @SuppressWarnings("unchecked") T[] big = (T[]) new Object[data.length * 2];
324            int nMask = big.length - 1;
325
326            for (T o : data) {
327                if (o == null) {
328                    continue;
329                }
330                int bucket = rehash(hash.getHashCode(o)) & nMask;
331                while (big[bucket] != null) {
332                    bucket = (bucket+1) & nMask;
333                }
334                big[bucket] = o;
335            }
336
337            data = big;
338            mask = nMask;
339        }
340    }
341
342    // -------------- factories --------------------
343    /**
344     * A factory for default hash implementation.
345     * @return a hash implementation that just delegates to object's own
346     * hashCode and equals.
347     */
348    public static <O> Hash<O,O> defaultHash() {
349        return new Hash<O,O>() {
350            public int getHashCode(O t) {
351                return t.hashCode();
352            }
353            public boolean equals(O t1, O t2) {
354                return t1.equals(t2);
355            }
356        };
357    }
358    /*
359    public static <O> Hash<O,O> identityHash() {
360        return new Hash<O,O>() {
361            public int getHashCode(O t) {
362                return System.identityHashCode(t);
363            }
364            public boolean equals(O t1, O t2) {
365                return t1 == t2;
366            }
367        };
368    }
369     */
370
371    private class FMap<K> implements Map<K,T> {
372        Hash<K,? super T> fHash;
373
374        private FMap(Hash<K,? super T> h) {
375            fHash = h;
376        }
377
378        public int size() {
379            return Storage.this.size();
380        }
381
382        public boolean isEmpty() {
383            return Storage.this.isEmpty();
384        }
385
386        public boolean containsKey(Object o) {
387            @SuppressWarnings("unchecked") K key = (K) o;
388            int bucket = getBucket(fHash, key);
389            return bucket >= 0;
390        }
391
392        public boolean containsValue(Object value) {
393            return Storage.this.contains(value);
394        }
395
396        public T get(Object o) {
397            @SuppressWarnings("unchecked") K key = (K) o;
398            int bucket = getBucket(fHash, key);
399            return bucket < 0 ? null : data[bucket];
400        }
401
402        public T put(K key, T value) {
403            if (!fHash.equals(key, value)) throw new IllegalArgumentException("inconsistent key");
404            return Storage.this.put(value);
405        }
406
407        public T remove(Object o) {
408            modCount++;
409            @SuppressWarnings("unchecked") K key = (K) o;
410            int bucket = getBucket(fHash, key);
411
412            return bucket < 0 ? null : doRemove(bucket);
413        }
414
415        public void putAll(Map<? extends K, ? extends T> m) {
416            if (m instanceof Storage.FMap) {
417                Storage.this.addAll(((Storage.FMap)m).values());
418            } else {
419                for (Map.Entry<? extends K, ? extends T> e : m.entrySet()) {
420                    put(e.getKey(), e.getValue());
421                }
422            }
423        }
424
425        public void clear() {
426            Storage.this.clear();
427        }
428
429        public Set<K> keySet() {
430            throw new UnsupportedOperationException();
431        }
432
433        public Collection<T> values() {
434            return Storage.this;
435        }
436
437        public Set<Entry<K, T>> entrySet() {
438            throw new UnsupportedOperationException();
439        }
440    }
441
442    private final class SafeReadonlyIter implements Iterator<T> {
443        final T[] data;
444        int slot = 0;
445
446        SafeReadonlyIter(T[] data) {
447            this.data = data;
448        }
449
450        public boolean hasNext() {
451            align();
452            return slot < data.length;
453        }
454
455        public T next() {
456            if (!hasNext()) throw new NoSuchElementException();
457            return data[slot++];
458        }
459
460        public void remove() {
461            throw new UnsupportedOperationException();
462        }
463
464        private void align() {
465            while (slot < data.length && data[slot] == null) {
466                slot++;
467            }
468        }
469    }
470
471
472    private final class Iter implements Iterator<T> {
473        private final int mods;
474        int slot = 0;
475        int removeSlot = -1;
476
477        Iter() {
478            mods = modCount;
479        }
480
481        public boolean hasNext() {
482            align();
483            return slot < data.length;
484        }
485
486        public T next() {
487            if (!hasNext()) throw new NoSuchElementException();
488            removeSlot = slot;
489            return data[slot++];
490        }
491
492        public void remove() {
493            if (removeSlot == -1) throw new IllegalStateException();
494
495            doRemove(removeSlot);
496            slot = removeSlot; // some entry might have been relocated here
497            removeSlot = -1;
498        }
499
500        private void align() {
501            if (mods != modCount)
502                throw new ConcurrentModificationException();
503            while (slot < data.length && data[slot] == null) {
504                slot++;
505            }
506        }
507    }
508
509}
Note: See TracBrowser for help on using the repository browser.