001    /**
002     * JDBM LICENSE v1.00
003     *
004     * Redistribution and use of this software and associated documentation
005     * ("Software"), with or without modification, are permitted provided
006     * that the following conditions are met:
007     *
008     * 1. Redistributions of source code must retain copyright
009     *    statements and notices.  Redistributions must also contain a
010     *    copy of this document.
011     *
012     * 2. Redistributions in binary form must reproduce the
013     *    above copyright notice, this list of conditions and the
014     *    following disclaimer in the documentation and/or other
015     *    materials provided with the distribution.
016     *
017     * 3. The name "JDBM" must not be used to endorse or promote
018     *    products derived from this Software without prior written
019     *    permission of Cees de Groot.  For written permission,
020     *    please contact cg@cdegroot.com.
021     *
022     * 4. Products derived from this Software may not be called "JDBM"
023     *    nor may "JDBM" appear in their names without prior written
024     *    permission of Cees de Groot.
025     *
026     * 5. Due credit should be given to the JDBM Project
027     *    (http://jdbm.sourceforge.net/).
028     *
029     * THIS SOFTWARE IS PROVIDED BY THE JDBM PROJECT AND CONTRIBUTORS
030     * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
031     * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
032     * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
033     * CEES DE GROOT OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
034     * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
035     * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
036     * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
037     * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
038     * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
039     * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
040     * OF THE POSSIBILITY OF SUCH DAMAGE.
041     *
042     * Copyright 2000 (C) Cees de Groot. All Rights Reserved.
043     * Contributions are Copyright (C) 2000 by their associated contributors.
044     *
045     * $Id: MRU.java,v 1.8 2005/06/25 23:12:31 doomdark Exp $
046     */
047    
048    package jdbm.helper;
049    
050    import java.util.Enumeration;
051    import java.util.Hashtable;
052    import java.util.Vector;
053    
054    
055    /**
056     *  MRU - Most Recently Used cache policy.
057     *
058     *  Methods are *not* synchronized, so no concurrent access is allowed.
059     *
060     * @author <a href="mailto:boisvert@intalio.com">Alex Boisvert</a>
061     * @version $Id: MRU.java,v 1.8 2005/06/25 23:12:31 doomdark Exp $
062     */
063    public class MRU implements CachePolicy {
064    
065        /** Cached object hashtable */
066        Hashtable _hash = new Hashtable();
067    
068        /**
069         * Maximum number of objects in the cache.
070         */
071        int _max;
072    
073        /**
074         * Beginning of linked-list of cache elements.  First entry is element
075         * which has been used least recently.
076         */
077        CacheEntry _first;
078    
079        /**
080         * End of linked-list of cache elements.  Last entry is element
081         * which has been used most recently.
082         */
083        CacheEntry _last;
084    
085    
086        /**
087         * Cache eviction listeners
088         */
089        Vector listeners = new Vector();
090    
091    
092        /**
093         * Construct an MRU with a given maximum number of objects.
094         */
095        public MRU(int max) {
096            if (max <= 0) {
097                throw new IllegalArgumentException("MRU cache must contain at least one entry");
098            }
099            _max = max;
100        }
101    
102    
103        /**
104         * Place an object in the cache.
105         */
106        public void put(Object key, Object value) throws CacheEvictionException {
107            CacheEntry entry = (CacheEntry)_hash.get(key);
108            if (entry != null) {
109                entry.setValue(value);
110                touchEntry(entry);
111            } else {
112    
113                if (_hash.size() == _max) {
114                    // purge and recycle entry
115                    entry = purgeEntry();
116                    entry.setKey(key);
117                    entry.setValue(value);
118                } else {
119                    entry = new CacheEntry(key, value);
120                }
121                addEntry(entry);
122                _hash.put(entry.getKey(), entry);
123            }
124        }
125    
126    
127        /**
128         * Obtain an object in the cache
129         */
130        public Object get(Object key) {
131            CacheEntry entry = (CacheEntry)_hash.get(key);
132            if (entry != null) {
133                touchEntry(entry);
134                return entry.getValue();
135            } else {
136                return null;
137            }
138        }
139    
140    
141        /**
142         * Remove an object from the cache
143         */
144        public void remove(Object key) {
145            CacheEntry entry = (CacheEntry)_hash.get(key);
146            if (entry != null) {
147                removeEntry(entry);
148                _hash.remove(entry.getKey());
149            }
150        }
151    
152    
153        /**
154         * Remove all objects from the cache
155         */
156        public void removeAll() {
157            _hash = new Hashtable();
158            _first = null;
159            _last = null;
160        }
161    
162    
163        /**
164         * Enumerate elements' values in the cache
165         */
166        public Enumeration elements() {
167            return new MRUEnumeration(_hash.elements());
168        }
169    
170        /**
171         * Add a listener to this cache policy
172         *
173         * @param listener Listener to add to this policy
174         */
175        public void addListener(CachePolicyListener listener) {
176            if (listener == null) {
177                throw new IllegalArgumentException("Cannot add null listener.");
178            }
179            if ( ! listeners.contains(listener)) {
180                listeners.addElement(listener);
181            }
182        }
183    
184        /**
185         * Remove a listener from this cache policy
186         *
187         * @param listener Listener to remove from this policy
188         */
189        public void removeListener(CachePolicyListener listener) {
190            listeners.removeElement(listener);
191        }
192    
193        /**
194         * Add a CacheEntry.  Entry goes at the end of the list.
195         */
196        protected void addEntry(CacheEntry entry) {
197            if (_first == null) {
198                _first = entry;
199                _last = entry;
200            } else {
201                _last.setNext(entry);
202                entry.setPrevious(_last);
203                _last = entry;
204            }
205        }
206    
207    
208        /**
209         * Remove a CacheEntry from linked list
210         */
211        protected void removeEntry(CacheEntry entry) {
212            if (entry == _first) {
213                _first = entry.getNext();
214            }
215            if (_last == entry) {
216                _last = entry.getPrevious();
217            }
218            CacheEntry previous = entry.getPrevious();
219            CacheEntry next = entry.getNext();
220            if (previous != null) {
221                previous.setNext(next);
222            }
223            if (next != null) {
224                next.setPrevious(previous);
225            }
226            entry.setPrevious(null);
227            entry.setNext(null);
228        }
229    
230        /**
231         * Place entry at the end of linked list -- Most Recently Used
232         */
233        protected void touchEntry(CacheEntry entry) {
234            if (_last == entry) {
235                return;
236            }
237            removeEntry(entry);
238            addEntry(entry);
239        }
240    
241        /**
242         * Purge least recently used object from the cache
243         *
244         * @return recyclable CacheEntry
245         */
246        protected CacheEntry purgeEntry() throws CacheEvictionException {
247            CacheEntry entry = _first;
248    
249            // Notify policy listeners first. if any of them throw an
250            // eviction exception, then the internal data structure
251            // remains untouched.
252            CachePolicyListener listener;
253            for (int i=0; i<listeners.size(); i++) {
254                listener = (CachePolicyListener)listeners.elementAt(i);
255                listener.cacheObjectEvicted(entry.getValue());
256            }
257    
258            removeEntry(entry);
259            _hash.remove(entry.getKey());
260    
261            entry.setValue(null);
262            return entry;
263        }
264    
265    }
266    
267    /**
268     * State information for cache entries.
269     */
270    class CacheEntry {
271        private Object _key;
272        private Object _value;
273    
274        private CacheEntry _previous;
275        private CacheEntry _next;
276    
277        CacheEntry(Object key, Object value) {
278            _key = key;
279            _value = value;
280        }
281    
282        Object getKey() {
283            return _key;
284        }
285    
286        void setKey(Object obj) {
287            _key = obj;
288        }
289    
290        Object getValue() {
291            return _value;
292        }
293    
294        void setValue(Object obj) {
295            _value = obj;
296        }
297    
298        CacheEntry getPrevious() {
299            return _previous;
300        }
301    
302        void setPrevious(CacheEntry entry) {
303            _previous = entry;
304        }
305    
306        CacheEntry getNext() {
307            return _next;
308        }
309    
310        void setNext(CacheEntry entry) {
311            _next = entry;
312        }
313    }
314    
315    /**
316     * Enumeration wrapper to return actual user objects instead of
317     * CacheEntries.
318     */
319    class MRUEnumeration implements Enumeration {
320        Enumeration _enum;
321    
322        MRUEnumeration(Enumeration enume) {
323            _enum = enume;
324        }
325    
326        public boolean hasMoreElements() {
327            return _enum.hasMoreElements();
328        }
329    
330        public Object nextElement() {
331            CacheEntry entry = (CacheEntry)_enum.nextElement();
332            return entry.getValue();
333        }
334    }