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

Last change on this file since 11369 was 11342, checked in by Don-vip, 7 years ago

javadoc

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