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

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

Checkstyle:

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