001    /**
002     *
003     * Copyright 2002-2005 The Apache Software Foundation
004     *
005     *  Licensed under the Apache License, Version 2.0 (the "License");
006     *  you may not use this file except in compliance with the License.
007     *  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    package org.apache.geronimo.kernel.basic;
018    
019    import java.lang.ref.Reference;
020    import java.lang.ref.WeakReference;
021    import java.lang.ref.ReferenceQueue;
022    import java.util.Collection;
023    import java.util.Map;
024    import java.util.Set;
025    
026    /**
027     * Streamlined version of a WeakIdentityHashMap. Provides Identity semantics with 
028     * Weak References to keys. This allows proxies to be GC'ed when no longer referenced
029     * by clients. <code>BasicProxymanager.destroyProxy()</code> need not be invoked when a
030     * proxy is no longer needed. Note that this is not a full Map implementation. 
031     * The iteration and collection capabilities of Map have been discarded to keep the 
032     * implementation lightweight.
033     * <p>
034     * Much of this code was cribbed from the Commons Collection 3.1 implementation of 
035     * <code>ReferenceIdentityMap</code> and <code>AbstractReferenceMap</code>.
036     */
037    public class BasicProxyMap implements Map {
038    
039        /** The default capacity to use. Always use a power of 2!!! */
040        private static final int DEFAULT_CAPACITY = 16;
041        /** The default load factor to use */
042        private static final float DEFAULT_LOAD_FACTOR = 0.75f;
043        /** The maximum capacity allowed */
044        private static final int MAXIMUM_CAPACITY = 1 << 30;
045        
046        /** Load factor, normally 0.75 */
047        private float loadFactor;
048        /** The size of the map */
049        private transient int size;
050        /** Map entries */
051        private transient ReferenceEntry[] data;
052        /** Size at which to rehash */
053        private transient int threshold;
054    
055        /**
056         * ReferenceQueue used to eliminate GC'ed entries.
057         */
058        private ReferenceQueue purgeQueue;
059    
060        public BasicProxyMap() {
061            this.loadFactor = DEFAULT_LOAD_FACTOR;
062            this.data = new ReferenceEntry[DEFAULT_CAPACITY];
063            this.threshold = calculateThreshold(DEFAULT_CAPACITY, loadFactor);
064            this.purgeQueue = new ReferenceQueue();
065        }
066        
067        /**
068         * Gets the size of the map.
069         * 
070         * @return the size
071         */
072        public int size() {
073            purge();
074            return size;
075        }
076    
077        /**
078         * Checks whether the map is currently empty.
079         * 
080         * @return true if the map is currently size zero
081         */
082        public boolean isEmpty() {
083            purge();
084            return (size == 0);
085        }
086    
087        /**
088         * Checks whether the map contains the specified key.
089         * 
090         * @param key  the key to search for
091         * @return true if the map contains the key
092         */
093        public boolean containsKey(Object key) {
094            purge();
095            ReferenceEntry entry = getEntry(key);
096            if (entry == null) {
097                return false;
098            }
099            return (entry.getValue() != null);
100        }
101    
102        /**
103         * Checks whether the map contains the specified value.
104         * 
105         * @param value  the value to search for
106         * @return true if the map contains the value
107         */
108        public boolean containsValue(Object value) {
109            purge();
110            if (value == null || size == 0) {
111                return false;
112            }
113            ReferenceEntry [] table = data;
114            for (int i = 0; i < table.length; i++) {
115                ReferenceEntry entry = table[i];
116                while (entry != null) {
117                    if (value.equals(entry.getValue())) {
118                        return true;
119                    }
120                    entry = entry.next;
121                }
122            }
123            return false;
124        }
125    
126        /**
127         * Gets the value mapped to the key specified.
128         * 
129         * @param key  the key
130         * @return the mapped value, null if no match
131         */
132        public Object get(Object key) {
133            purge();
134            ReferenceEntry entry = getEntry(key);
135            if (entry == null) {
136                return null;
137            }
138            return entry.getValue();
139        }
140    
141    
142        /**
143         * Puts a key-value entry into this map.
144         * Neither the key nor the value may be null.
145         * 
146         * @param key  the key to add, must not be null
147         * @param value  the value to add, must not be null
148         * @return the value previously mapped to this key, null if none
149         */
150        public Object put(Object key, Object value) {
151            assert key != null: "key is null";
152            assert value != null: "value is null";
153    
154            purge();
155    
156            int hashCode = hash(key);
157            int index = hashIndex(hashCode, data.length);
158            ReferenceEntry entry = data[index];
159            while (entry != null) {
160                if (entry.hashCode == hashCode && key == entry.getKey()) {
161                    return entry.setValue(value);
162                }
163                entry = entry.next;
164            }   
165                
166            createEntry(index, hashCode, key, value);
167            return null;
168        }
169        
170        /**
171         * Removes the specified mapping from this map.
172         * 
173         * @param key  the mapping to remove
174         * @return the value mapped to the removed key, null if key not in map
175         */
176        public Object remove(Object key) {
177            if (key == null) {
178                return null;
179            }
180            purge();
181            int hashCode = hash(key);
182            int index = hashIndex(hashCode, data.length);
183            ReferenceEntry entry = data[index];
184            ReferenceEntry previous = null;
185            while (entry != null) {
186                if (entry.hashCode == hashCode && (key == entry.getKey())) {
187                    Object oldValue = entry.getValue();
188                    removeEntry(entry, index, previous);
189                    return oldValue;
190                }
191                previous = entry;
192                entry = entry.next;
193            }
194            return null;
195        }
196    
197        /**
198         * Clears the map, resetting the size to zero and nullifying references
199         * to avoid garbage collection issues.
200         */
201        public void clear() {
202            ReferenceEntry[] data = this.data;
203            for (int i = data.length - 1; i >= 0; i--) {
204                data[i] = null;
205            }
206            size = 0;
207            while (purgeQueue.poll() != null) {} // drain the queue
208        }
209    
210        public Collection values() {
211            throw new UnsupportedOperationException();
212        }
213    
214        public void putAll(Map t) {
215            throw new UnsupportedOperationException();
216        }
217    
218        public Set entrySet() {
219            throw new UnsupportedOperationException();
220        }
221    
222        public Set keySet() {
223            throw new UnsupportedOperationException();
224        }
225    
226        // end of public methods
227        
228        /**
229         * Gets the entry mapped to the key specified.
230         * @param key  the key
231         * @return the entry, null if no match
232         */
233        private ReferenceEntry getEntry(Object key) {
234            if (key == null) {
235                return null;
236            }
237            int hashCode = hash(key);
238            ReferenceEntry entry = data[hashIndex(hashCode, data.length)];
239            while (entry != null) {
240                if (entry.hashCode == hashCode && (key == entry.getKey())) {
241                    return entry;
242                }
243                entry = entry.next;
244            }
245            return null;
246        }
247    
248        /**
249         * Creates a new ReferenceEntry.
250         * 
251         * @param index the index into the data map
252         * @param hashCode  the hash code for the new entry
253         * @param key  the key to store
254         * @param value  the value to store
255         * @return the newly created entry
256         */
257        private ReferenceEntry createEntry(int index, int hashCode, Object key, Object value) {
258            ReferenceEntry newEntry = new ReferenceEntry(this, data[index], hashCode, key, value);
259            data[index] = newEntry;
260            size++;
261            checkCapacity();
262            return newEntry;
263        }
264    
265        /**
266         * Removes an entry from the chain stored in a particular index.
267         * <p>
268         * This implementation removes the entry from the data storage table.
269         * The size is not updated.
270         * 
271         * @param entry  the entry to remove
272         * @param hashIndex  the index into the data structure
273         * @param previous  the previous entry in the chain
274         */
275        private void removeEntry(ReferenceEntry entry, int hashIndex, ReferenceEntry previous) {
276            if (previous == null) {
277                data[hashIndex] = entry.next;
278            } else {
279                previous.next = entry.next;
280            }
281            size--;
282            entry.next = null;
283            entry.clear();
284            entry.value = null;
285        }
286        
287        /**
288         * Checks the capacity of the map and enlarges it if necessary.
289         * <p>
290         * This implementation uses the threshold to check if the map needs enlarging
291         */
292        private void checkCapacity() {
293            if (size >= threshold) {
294                int newCapacity = data.length * 2;
295                if (newCapacity <= MAXIMUM_CAPACITY) {
296                    ensureCapacity(newCapacity);
297                }
298            }
299        }
300        
301        /**
302         * Changes the size of the data structure to the capacity proposed.
303         * 
304         * @param newCapacity  the new capacity of the array (a power of two, less or equal to max)
305         */
306        private void ensureCapacity(int newCapacity) {
307            int oldCapacity = data.length;
308            if (newCapacity <= oldCapacity) {
309                return;
310            }
311    
312            ReferenceEntry oldEntries[] = data;
313            ReferenceEntry newEntries[] = new ReferenceEntry[newCapacity];
314    
315            for (int i = oldCapacity - 1; i >= 0; i--) {
316                ReferenceEntry entry = oldEntries[i];
317                if (entry != null) {
318                    oldEntries[i] = null;  // gc
319                    do {
320                        ReferenceEntry next = entry.next;
321                        int index = hashIndex(entry.hashCode, newCapacity);  
322                        entry.next = newEntries[index];
323                        newEntries[index] = entry;
324                        entry = next;
325                    } while (entry != null);
326                }
327            }
328            threshold = calculateThreshold(newCapacity, loadFactor);
329            data = newEntries;
330        }
331    
332        /**
333         * Calculates the new threshold of the map, where it will be resized.
334         * This implementation uses the load factor.
335         * 
336         * @param newCapacity  the new capacity
337         * @param factor  the load factor
338         * @return the new resize threshold
339         */
340        private int calculateThreshold(int newCapacity, float factor) {
341            return (int) (newCapacity * factor);
342        }
343    
344        /**
345         * Gets the hash code for the key specified.
346         * <p>
347         * This implementation uses the identity hash code.
348         * 
349         * @param key  the key to get a hash code for
350         * @return the hash code
351         */
352        private int hash(Object key) {
353            return System.identityHashCode(key);
354        }
355    
356        /**
357         * Gets the index into the data storage for the hashCode specified.
358         * This implementation uses the least significant bits of the hashCode.
359         * 
360         * @param hashCode  the hash code to use
361         * @param dataSize  the size of the data to pick a bucket from
362         * @return the bucket index
363         */
364        private int hashIndex(int hashCode, int dataSize) {
365            return hashCode & (dataSize - 1);
366        }
367    
368        // Code that handles WeakReference cleanup... Invoked prior to 
369        // any operation accessing the ReferenceEntry array...
370        
371        /**
372         * Purges stale mappings from this map.
373         * <p>
374         * Note that this method is not synchronized!  Special
375         * care must be taken if, for instance, you want stale
376         * mappings to be removed on a periodic basis by some
377         * background thread.
378         */
379        private void purge() {
380            Reference entryRef = purgeQueue.poll();
381            while (entryRef != null) {
382                purge(entryRef);
383                entryRef = purgeQueue.poll();
384            }
385        }
386    
387        /**
388         * Purges the specified reference.
389         * 
390         * @param purgedEntry the reference to purge
391         */
392        private void purge(Reference purgedEntry) {
393            int hash = ((ReferenceEntry)purgedEntry).hashCode;
394            int index = hashIndex(hash, data.length);
395            ReferenceEntry previous = null;
396            ReferenceEntry currentEntry = data[index];
397            while (currentEntry != null) {
398                if (currentEntry == purgedEntry) {
399                    currentEntry.purged();
400                    if (previous == null) {
401                        data[index] = currentEntry.next;
402                    } else {
403                        previous.next = currentEntry.next;
404                    }
405                    this.size--;
406                    return;
407                }
408                previous = currentEntry;
409                currentEntry = currentEntry.next;
410            }
411        }
412    
413        /**
414         * Each entry in the Map is represented with a ReferenceEntry.
415         * <p>
416         * If getKey() or getValue() returns null, it means
417         * the mapping is stale and should be removed.
418         * 
419         * @since Commons Collections 3.1
420         */
421        private static class ReferenceEntry extends WeakReference {
422            /** The next entry in the hash chain */
423            private ReferenceEntry next;
424            /** The hash code of the key */
425            private int hashCode;
426            /** The value */
427            private Object value;
428    
429            /**
430             * Creates a new entry object for the ReferenceMap.
431             * 
432             * @param parent  the parent map
433             * @param next  the next entry in the hash bucket
434             * @param hashCode  the hash code of the key
435             * @param key  the key
436             * @param value  the value
437             */
438            private ReferenceEntry(BasicProxyMap parent, ReferenceEntry next, int hashCode, Object key, Object value) {
439                super(key, parent.purgeQueue);
440                this.next = next;
441                this.hashCode = hashCode;
442                this.value = value;
443            }
444    
445            /**
446             * Gets the key from the entry.
447             * This method dereferences weak and soft keys and thus may return null.
448             * 
449             * @return the key, which may be null if it was garbage collected
450             */
451            private Object getKey() {
452                return this.get();
453            }
454    
455            /**
456             * Gets the value from the entry.
457             * This method dereferences weak and soft value and thus may return null.
458             * 
459             * @return the value, which may be null if it was garbage collected
460             */
461            private Object getValue() {
462                return value;
463            }
464    
465            /**
466             * Sets the value of the entry.
467             * 
468             * @param obj  the object to store
469             * @return the previous value
470             */
471            private Object setValue(Object obj) {
472                Object old = getValue();
473                value = obj;
474                return old;
475            }
476    
477            /**
478             * Purges this entry.
479             */
480            private void purged() {
481                this.clear();
482                value = null;
483            }
484        }
485    }