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

Last change on this file since 3202 was 3202, checked in by bastiK, 14 years ago

preliminary fix for (see #4861) (Don't raise exception, just give warning on the console)

  • Property svn:eol-style set to native
  • Property svn:mime-type set to text/plain
File size: 11.4 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 private final Hash<? super T,? super T> hash;
90 private Object[] data;
91 private int mask;
92 private int size;
93 private transient volatile int modCount = 0;
94 private float loadFactor = 0.6f;
95
96 public Storage() {
97 this(Storage.<T>defaultHash());
98 }
99
100 public Storage(int capacity) {
101 this(Storage.<T>defaultHash(), capacity);
102 }
103
104 public Storage(Hash<? super T,? super T> ha) {
105 this(ha, 16);
106 }
107
108 public Storage(Hash<? super T, ? super T> ha, int capacity) {
109 this.hash = ha;
110 int cap = 1 << (int)(Math.ceil(Math.log(capacity/loadFactor) / Math.log(2)));
111 data = new Object[cap];
112 mask = data.length - 1;
113 }
114
115 // --------------- Collection implementation ------------------------
116 @Override
117 public int size() {
118 return size;
119 }
120
121 @Override
122 public Iterator<T> iterator() {
123 return new Iter();
124 }
125
126 public @Override boolean contains(Object o) {
127 int bucket = getBucket(hash, (T)o);
128 return bucket >= 0;
129 }
130
131 public @Override boolean add(T t) {
132 T orig = putUnique(t);
133 return orig == t;
134 }
135
136 public @Override boolean remove(Object o) {
137 T orig = removeElem((T)o);
138 return orig != null;
139 }
140
141 public @Override void clear() {
142 modCount++;
143 size = 0;
144 for (int i = 0; i<data.length; i++) {
145 data[i] = null;
146 }
147 }
148
149 public @Override int hashCode() {
150 int h = 0;
151 for (T t : this) {
152 h += hash.getHashCode(t);
153 }
154 return h;
155 }
156
157 // ----------------- Extended API ----------------------------
158
159 public T put(T t) {
160 modCount++;
161 ensureSpace();
162
163 int bucket = getBucket(hash, t);
164 if (bucket < 0) {
165 size++;
166 bucket = ~bucket;
167 assert data[bucket] == null;
168 }
169
170 T old = (T)data[bucket];
171 data[bucket] = t;
172
173 return old;
174 }
175
176 public T get(T t) {
177 int bucket = getBucket(hash, t);
178 return bucket < 0 ? null : (T)data[bucket];
179 }
180
181 public T putUnique(T t) {
182 modCount++;
183 ensureSpace();
184
185 int bucket = getBucket(hash, t);
186 if (bucket < 0) { // unique
187 size++;
188 assert data[~bucket] == null;
189 data[~bucket] = t;
190 return t;
191 }
192
193 return (T)data[bucket];
194 }
195
196 public T removeElem(T t) {
197 modCount++;
198 int bucket = getBucket(hash, t);
199 return bucket < 0 ? null : doRemove(bucket);
200 }
201
202 public <K> Map<K,T> foreignKey(Hash<K,? super T> h) {
203 return new FMap(h);
204 }
205
206 // ---------------- Implementation
207
208 /**
209 * Additional mixing of hash
210 */
211 private int rehash(int h) {
212 //return 54435761*h;
213 return 1103515245*h >> 2;
214 }
215
216 /**
217 * Finds a bucket for given key.
218 *
219 * @param key The key to compare
220 * @return the bucket equivalent to the key or -(bucket) as an empty slot
221 * where such an entry can be stored.
222 */
223 private <K> int getBucket(Hash<K,? super T> ha, K key) {
224 T entry;
225 int hcode = rehash(ha.getHashCode(key));
226 int bucket = hcode & mask;
227 while ((entry = (T)data[bucket]) != null) {
228 if (ha.equals(key, entry))
229 return bucket;
230 bucket = (bucket+1) & mask;
231 }
232 return ~bucket;
233 }
234
235 private T doRemove(int slot) {
236 T t = (T)data[slot];
237 assert t != null;
238
239 fillTheHole(slot); // fill the hole (or null it)
240 size--;
241 return t;
242 }
243
244 private void fillTheHole(int hole) {
245 int bucket = (hole+1) & mask;
246 T entry;
247
248 while ((entry = (T)data[bucket]) != null) {
249 int right = rehash(hash.getHashCode(entry)) & mask;
250 // if the entry should be in <hole+1,bucket-1> (circular-wise)
251 // we can't move it. The move can be proved safe otherwise,
252 // because the entry safely belongs to <previous_null+1,hole>
253 if ((bucket < right && (right <= hole || hole <= bucket)) ||
254 (right <=hole && hole <= bucket)) {
255
256 data[hole] = data[bucket];
257 hole = bucket;
258 }
259 bucket = (bucket+1) & mask;
260 }
261
262 // no entry belongs here, just null out the slot
263 data[hole] = null;
264 }
265
266 private void ensureSpace() {
267 if (size > data.length*loadFactor) { // rehash
268 Object[] big = new Object[data.length * 2];
269 int nMask = big.length - 1;
270
271 for (Object o : data) {
272 if (o == null) {
273 continue;
274 }
275 int bucket = rehash(hash.getHashCode((T)o)) & nMask;
276 while (big[bucket] != null) {
277 bucket = (bucket+1) & nMask;
278 }
279 big[bucket] = o;
280 }
281
282 data = big;
283 mask = nMask;
284 }
285 }
286
287 // -------------- factories --------------------
288 /**
289 * A factory for default hash implementation.
290 * @return a hash implementation that just delegates to object's own
291 * hashCode and equals.
292 */
293 public static <O> Hash<O,O> defaultHash() {
294 return new Hash<O,O>() {
295 public int getHashCode(O t) {
296 return t.hashCode();
297 }
298 public boolean equals(O t1, O t2) {
299 return t1.equals(t2);
300 }
301 };
302 }
303 /*
304 public static <O> Hash<O,O> identityHash() {
305 return new Hash<O,O>() {
306 public int getHashCode(O t) {
307 return System.identityHashCode(t);
308 }
309 public boolean equals(O t1, O t2) {
310 return t1 == t2;
311 }
312 };
313 }
314 */
315
316 private class FMap<K> implements Map<K,T> {
317 Hash<K,? super T> fHash;
318
319 private FMap(Hash<K,? super T> h) {
320 fHash = h;
321 }
322
323 public int size() {
324 return Storage.this.size();
325 }
326
327 public boolean isEmpty() {
328 return Storage.this.isEmpty();
329 }
330
331 public boolean containsKey(Object key) {
332 int bucket = getBucket(fHash, (K)key);
333 return bucket >= 0;
334 }
335
336 public boolean containsValue(Object value) {
337 return Storage.this.contains(value);
338 }
339
340 public T get(Object key) {
341 int bucket = getBucket(fHash, (K)key);
342 return bucket < 0 ? null : (T)data[bucket];
343 }
344
345 public T put(K key, T value) {
346 if (!fHash.equals(key, value)) throw new IllegalArgumentException("inconsistent key");
347 return Storage.this.put(value);
348 }
349
350 public T remove(Object key) {
351 modCount++;
352 int bucket = getBucket(fHash,(K)key);
353
354 return bucket < 0 ? null : doRemove(bucket);
355 }
356
357 public void putAll(Map<? extends K, ? extends T> m) {
358 if (m instanceof Storage.FMap) {
359 Storage.this.addAll(((Storage.FMap)m).values());
360 } else {
361 for (Map.Entry<? extends K, ? extends T> e : m.entrySet()) {
362 put(e.getKey(), e.getValue());
363 }
364 }
365 }
366
367 public void clear() {
368 Storage.this.clear();
369 }
370
371 public Set<K> keySet() {
372 throw new UnsupportedOperationException();
373 }
374
375 public Collection<T> values() {
376 return Storage.this;
377 }
378
379 public Set<Entry<K, T>> entrySet() {
380 throw new UnsupportedOperationException();
381 }
382 }
383
384 private final class Iter implements Iterator<T> {
385 private final int mods;
386 int slot = 0;
387 int removeSlot = -1;
388
389 Iter() {
390 mods = modCount;
391 }
392
393 public boolean hasNext() {
394 align();
395 return slot < data.length;
396 }
397
398 public T next() {
399 if (!hasNext()) throw new NoSuchElementException();
400 removeSlot = slot;
401 return (T)data[slot++];
402 }
403
404 public void remove() {
405 if (removeSlot == -1) throw new IllegalStateException();
406
407 doRemove(removeSlot);
408 slot = removeSlot; // some entry might have been relocated here
409 removeSlot = -1;
410 }
411
412 private void align() {
413 if (mods != modCount) {
414 System.err.println("Warning: ConcurrentModification");
415 Thread.dumpStack();
416 //throw new ConcurrentModificationException();
417 }
418 while (slot < data.length && data[slot] == null) {
419 slot++;
420 }
421 }
422 }
423
424}
Note: See TracBrowser for help on using the repository browser.