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

Last change on this file since 5674 was 5674, checked in by jttt, 11 years ago

Move IdHash to Storage class and rename to PrimitiveIdHash to make it easier for other classes to use Storage class

  • Property svn:eol-style set to native
File size: 14.5 KB
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
90 public static class PrimitiveIdHash implements Hash<PrimitiveId, PrimitiveId> {
91
92 public int getHashCode(PrimitiveId k) {
93 return (int)k.getUniqueId() ^ k.getType().hashCode();
94 }
95
96 public boolean equals(PrimitiveId key, PrimitiveId value) {
97 if (key == null || value == null) return false;
98 return key.getUniqueId() == value.getUniqueId() && key.getType() == value.getType();
99 }
100 }
101
102 private final Hash<? super T,? super T> hash;
103 private T[] data;
104 private int mask;
105 private int size;
106 private transient volatile int modCount = 0;
107 private float loadFactor = 0.6f;
108 private static final int DEFAULT_CAPACITY = 16;
109 private final boolean safeIterator;
110 private boolean arrayCopyNecessary;
111
112 public Storage() {
113 this(Storage.<T>defaultHash(), DEFAULT_CAPACITY, false);
114 }
115
116 public Storage(int capacity) {
117 this(Storage.<T>defaultHash(), capacity, false);
118 }
119
120 public Storage(Hash<? super T,? super T> ha) {
121 this(ha, DEFAULT_CAPACITY, false);
122 }
123
124 public Storage(boolean safeIterator) {
125 this(Storage.<T>defaultHash(), DEFAULT_CAPACITY, safeIterator);
126 }
127
128 public Storage(int capacity, boolean safeIterator) {
129 this(Storage.<T>defaultHash(), capacity, safeIterator);
130 }
131
132 public Storage(Hash<? super T,? super T> ha, boolean safeIterator) {
133 this(ha, DEFAULT_CAPACITY, safeIterator);
134 }
135
136 public Storage(Hash<? super T, ? super T> ha, int capacity) {
137 this(ha, capacity, false);
138 }
139 /**
140 * constructor
141 * @param ha
142 * @param capacity
143 * @param safeIterator If set to false, you must not modify the Storage
144 * while iterating over it. If set to true, you can savely
145 * modify, but the read-only iteration will happen on a copy
146 * of the unmodified Storage.
147 * This is similar to CopyOnWriteArrayList.
148 */
149 public Storage(Hash<? super T, ? super T> ha, int capacity, boolean safeIterator) {
150 this.hash = ha;
151 int cap = 1 << (int)(Math.ceil(Math.log(capacity/loadFactor) / Math.log(2)));
152 @SuppressWarnings("unchecked") T[] newData = (T[]) new Object[cap];
153 data = newData;
154 mask = data.length - 1;
155 this.safeIterator = safeIterator;
156 }
157
158 private void copyArray() {
159 if (arrayCopyNecessary) {
160 @SuppressWarnings("unchecked") T[] newData = (T[]) new Object[data.length];
161 System.arraycopy(data, 0, newData, 0, data.length);
162 data = newData;
163 arrayCopyNecessary = false;
164 }
165 }
166
167 // --------------- Collection implementation ------------------------
168 @Override
169 public synchronized int size() {
170 return size;
171 }
172
173 @Override
174 public synchronized Iterator<T> iterator() {
175 if (safeIterator) {
176 arrayCopyNecessary = true;
177 return new SafeReadonlyIter(data);
178 } else
179 return new Iter();
180
181 }
182
183 @Override
184 public synchronized boolean contains(Object o) {
185 @SuppressWarnings("unchecked") T t = (T) o;
186 int bucket = getBucket(hash, t);
187 return bucket >= 0;
188 }
189
190 @Override
191 public synchronized boolean add(T t) {
192 T orig = putUnique(t);
193 return orig == t;
194 }
195
196 @Override
197 public synchronized boolean remove(Object o) {
198 @SuppressWarnings("unchecked") T t = (T) o;
199 T tOrig = removeElem(t);
200 return tOrig != null;
201 }
202
203 @Override
204 public synchronized void clear() {
205 copyArray();
206 modCount++;
207 size = 0;
208 for (int i = 0; i<data.length; i++) {
209 data[i] = null;
210 }
211 }
212
213 @Override
214 public synchronized int hashCode() {
215 int h = 0;
216 for (T t : this) {
217 h += hash.getHashCode(t);
218 }
219 return h;
220 }
221
222 // ----------------- Extended API ----------------------------
223
224 public synchronized T put(T t) {
225 copyArray();
226 modCount++;
227 ensureSpace();
228
229 int bucket = getBucket(hash, t);
230 if (bucket < 0) {
231 size++;
232 bucket = ~bucket;
233 assert data[bucket] == null;
234 }
235
236 T old = data[bucket];
237 data[bucket] = t;
238
239 return old;
240 }
241
242 public synchronized T get(T t) {
243 int bucket = getBucket(hash, t);
244 return bucket < 0 ? null : data[bucket];
245 }
246
247 public synchronized T putUnique(T t) {
248 copyArray();
249 modCount++;
250 ensureSpace();
251
252 int bucket = getBucket(hash, t);
253 if (bucket < 0) { // unique
254 size++;
255 assert data[~bucket] == null;
256 data[~bucket] = t;
257 return t;
258 }
259
260 return data[bucket];
261 }
262
263 public synchronized T removeElem(T t) {
264 copyArray();
265 modCount++;
266 int bucket = getBucket(hash, t);
267 return bucket < 0 ? null : doRemove(bucket);
268 }
269
270 public <K> Map<K,T> foreignKey(Hash<K,? super T> h) {
271 return new FMap<K>(h);
272 }
273
274 // ---------------- Implementation
275
276 /**
277 * Additional mixing of hash
278 */
279 private int rehash(int h) {
280 //return 54435761*h;
281 return 1103515245*h >> 2;
282 }
283
284 /**
285 * Finds a bucket for given key.
286 *
287 * @param key The key to compare
288 * @return the bucket equivalent to the key or -(bucket) as an empty slot
289 * where such an entry can be stored.
290 */
291 private <K> int getBucket(Hash<K,? super T> ha, K key) {
292 T entry;
293 int hcode = rehash(ha.getHashCode(key));
294 int bucket = hcode & mask;
295 while ((entry = data[bucket]) != null) {
296 if (ha.equals(key, entry))
297 return bucket;
298 bucket = (bucket+1) & mask;
299 }
300 return ~bucket;
301 }
302
303 private T doRemove(int slot) {
304 T t = data[slot];
305 assert t != null;
306
307 fillTheHole(slot); // fill the hole (or null it)
308 size--;
309 return t;
310 }
311
312 private void fillTheHole(int hole) {
313 int bucket = (hole+1) & mask;
314 T entry;
315
316 while ((entry = data[bucket]) != null) {
317 int right = rehash(hash.getHashCode(entry)) & mask;
318 // if the entry should be in <hole+1,bucket-1> (circular-wise)
319 // we can't move it. The move can be proved safe otherwise,
320 // because the entry safely belongs to <previous_null+1,hole>
321 if ((bucket < right && (right <= hole || hole <= bucket)) ||
322 (right <=hole && hole <= bucket)) {
323
324 data[hole] = data[bucket];
325 hole = bucket;
326 }
327 bucket = (bucket+1) & mask;
328 }
329
330 // no entry belongs here, just null out the slot
331 data[hole] = null;
332 }
333
334 private void ensureSpace() {
335 if (size > data.length*loadFactor) { // rehash
336 @SuppressWarnings("unchecked") T[] big = (T[]) new Object[data.length * 2];
337 int nMask = big.length - 1;
338
339 for (T o : data) {
340 if (o == null) {
341 continue;
342 }
343 int bucket = rehash(hash.getHashCode(o)) & nMask;
344 while (big[bucket] != null) {
345 bucket = (bucket+1) & nMask;
346 }
347 big[bucket] = o;
348 }
349
350 data = big;
351 mask = nMask;
352 }
353 }
354
355 // -------------- factories --------------------
356 /**
357 * A factory for default hash implementation.
358 * @return a hash implementation that just delegates to object's own
359 * hashCode and equals.
360 */
361 public static <O> Hash<O,O> defaultHash() {
362 return new Hash<O,O>() {
363 public int getHashCode(O t) {
364 return t.hashCode();
365 }
366 public boolean equals(O t1, O t2) {
367 return t1.equals(t2);
368 }
369 };
370 }
371 /*
372 public static <O> Hash<O,O> identityHash() {
373 return new Hash<O,O>() {
374 public int getHashCode(O t) {
375 return System.identityHashCode(t);
376 }
377 public boolean equals(O t1, O t2) {
378 return t1 == t2;
379 }
380 };
381 }
382 */
383
384 private class FMap<K> implements Map<K,T> {
385 Hash<K,? super T> fHash;
386
387 private FMap(Hash<K,? super T> h) {
388 fHash = h;
389 }
390
391 public int size() {
392 return Storage.this.size();
393 }
394
395 public boolean isEmpty() {
396 return Storage.this.isEmpty();
397 }
398
399 public boolean containsKey(Object o) {
400 @SuppressWarnings("unchecked") K key = (K) o;
401 int bucket = getBucket(fHash, key);
402 return bucket >= 0;
403 }
404
405 public boolean containsValue(Object value) {
406 return Storage.this.contains(value);
407 }
408
409 public T get(Object o) {
410 @SuppressWarnings("unchecked") K key = (K) o;
411 int bucket = getBucket(fHash, key);
412 return bucket < 0 ? null : data[bucket];
413 }
414
415 public T put(K key, T value) {
416 if (!fHash.equals(key, value)) throw new IllegalArgumentException("inconsistent key");
417 return Storage.this.put(value);
418 }
419
420 public T remove(Object o) {
421 modCount++;
422 @SuppressWarnings("unchecked") K key = (K) o;
423 int bucket = getBucket(fHash, key);
424
425 return bucket < 0 ? null : doRemove(bucket);
426 }
427
428 public void putAll(Map<? extends K, ? extends T> m) {
429 if (m instanceof Storage.FMap) {
430 Storage.this.addAll(((Storage.FMap)m).values());
431 } else {
432 for (Map.Entry<? extends K, ? extends T> e : m.entrySet()) {
433 put(e.getKey(), e.getValue());
434 }
435 }
436 }
437
438 public void clear() {
439 Storage.this.clear();
440 }
441
442 public Set<K> keySet() {
443 throw new UnsupportedOperationException();
444 }
445
446 public Collection<T> values() {
447 return Storage.this;
448 }
449
450 public Set<Entry<K, T>> entrySet() {
451 throw new UnsupportedOperationException();
452 }
453 }
454
455 private final class SafeReadonlyIter implements Iterator<T> {
456 final T[] data;
457 int slot = 0;
458
459 SafeReadonlyIter(T[] data) {
460 this.data = data;
461 }
462
463 public boolean hasNext() {
464 align();
465 return slot < data.length;
466 }
467
468 public T next() {
469 if (!hasNext()) throw new NoSuchElementException();
470 return data[slot++];
471 }
472
473 public void remove() {
474 throw new UnsupportedOperationException();
475 }
476
477 private void align() {
478 while (slot < data.length && data[slot] == null) {
479 slot++;
480 }
481 }
482 }
483
484
485 private final class Iter implements Iterator<T> {
486 private final int mods;
487 int slot = 0;
488 int removeSlot = -1;
489
490 Iter() {
491 mods = modCount;
492 }
493
494 public boolean hasNext() {
495 align();
496 return slot < data.length;
497 }
498
499 public T next() {
500 if (!hasNext()) throw new NoSuchElementException();
501 removeSlot = slot;
502 return data[slot++];
503 }
504
505 public void remove() {
506 if (removeSlot == -1) throw new IllegalStateException();
507
508 doRemove(removeSlot);
509 slot = removeSlot; // some entry might have been relocated here
510 removeSlot = -1;
511 }
512
513 private void align() {
514 if (mods != modCount)
515 throw new ConcurrentModificationException();
516 while (slot < data.length && data[slot] == null) {
517 slot++;
518 }
519 }
520 }
521
522}
Note: See TracBrowser for help on using the repository browser.