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

Last change on this file since 9243 was 9243, checked in by Don-vip, 8 years ago

javadoc update

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