001    /*
002     * Copyright 2001-2004 The Apache Software Foundation
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *     http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.apache.commons.cache;
017    
018    import java.util.HashMap;
019    import java.util.ArrayList;
020    import java.util.NoSuchElementException;
021    import java.io.Serializable;
022    import java.io.ObjectInputStream;
023    import java.io.IOException;
024    import java.io.NotActiveException;
025    import org.apache.commons.cache.adt.Listable;
026    import org.apache.commons.cache.adt.WListableObject;
027    
028    /**
029     * tk.
030     * @version $Id: LRUEvictionPolicy.java 155435 2005-02-26 13:17:27Z dirkv $
031     * @author Rodney Waldhoff
032     */
033    public class LRUEvictionPolicy implements EvictionPolicy, StorageListener, RetrievalListener, CachedObjectIterator {
034      transient protected HashMap _hash = new HashMap();
035      transient protected WListableObject _leastRecent = null;
036      transient protected WListableObject _mostRecent = null;
037      transient protected WListableObject _current = null;
038    
039      protected boolean _canRemove = false;
040      public Cache _cache = null;
041      protected int _objsbetweennaps;
042      protected long _sleeptimemillis;
043    
044      public LRUEvictionPolicy() {
045        this(StaleObjectEvictor.DEFAULT_OBJECTS_BETWEEN_NAPS,StaleObjectEvictor.DEFAULT_SLEEP_TIME_MILLIS);
046      }
047    
048      public LRUEvictionPolicy(int objsbetweennaps, long sleeptimemillis) {
049        _objsbetweennaps = objsbetweennaps;
050        _sleeptimemillis = sleeptimemillis;
051        Thread t = new Thread(new StaleObjectEvictor(this,_objsbetweennaps,_sleeptimemillis));
052        t.setDaemon(true);
053        t.start();
054      }
055    
056      private void writeObject(java.io.ObjectOutputStream out) throws IOException {
057         out.defaultWriteObject();
058         WListableObject cur = _leastRecent;
059         while(null != cur) {
060            out.writeObject(cur.getValue());
061            cur =(WListableObject)(cur.getNext());
062         }
063         out.writeObject(null);
064      }
065    
066      private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
067        try {
068          in.defaultReadObject();
069          _hash = new HashMap();
070          for(;;) {
071             Object obj = in.readObject();
072             if(null == obj) {
073                break;
074             } else {
075                WListableObject temp = new WListableObject(obj,_mostRecent,null);
076                if(null == _leastRecent) {
077                   _leastRecent = temp;
078                   _current = temp;
079                }
080                if(null != _mostRecent) {
081                   _mostRecent.setNext(temp);
082                }
083                _mostRecent = temp;
084                _hash.put(((CachedObjectInfo)obj).getKey(),_mostRecent);
085             }
086          }
087    
088          Thread t = new Thread(new StaleObjectEvictor(this,_objsbetweennaps,_sleeptimemillis));
089          t.setDaemon(true);
090          t.start();
091        } catch(NotActiveException e) {
092          e.printStackTrace();
093        }
094      }
095    
096      protected synchronized void addToList(WListableObject cur) {
097        if(null == _mostRecent) {
098          _mostRecent = cur;
099          _leastRecent = cur;
100        } else {
101          _mostRecent.setNext(cur);
102          cur.setPrev(_mostRecent);
103          _mostRecent = cur;
104        }
105      }
106    
107      protected synchronized void removeFromList(WListableObject cur) {
108        // post condition: cur.next and cur.prev are both null
109        if(_current == cur) {
110          _current = (WListableObject)(_current.getPrev());
111          _canRemove = false;
112        }
113        if(cur.hasPrev()) {
114          if(cur.hasNext()) {
115            cur.getPrev().setNext(cur.getNext());
116            cur.getNext().setPrev(cur.getPrev());
117            cur.setNext(null);
118            cur.setPrev(null);
119          } else {
120            // else cur is the most recent and cur.next is null
121            _mostRecent = (WListableObject)(cur.getPrev());
122            cur.getPrev().setNext(null);
123            cur.setPrev(null);
124          }
125        } else {
126          // else cur is the least recent and cur.prev is null
127          _leastRecent = (WListableObject)(cur.getNext());
128          if(cur.hasNext()) {
129            cur.getNext().setPrev(null);
130            cur.setNext(null);
131          } else {
132            // else cur is both least and most recent
133            // and both next and prev are null
134            _leastRecent = null;
135            _mostRecent = null;
136          }
137        }
138      }
139    
140    
141      public synchronized boolean hasNext() {
142        return (null != _mostRecent);
143      }
144    
145      public synchronized CachedObjectInfo getNext() throws NoSuchElementException {
146        if(null != _current) {
147          _current = (WListableObject)(_current.getNext());
148        }
149        if(null == _current) { _current = _leastRecent; }
150        if(null == _current) {
151          throw new NoSuchElementException();
152        } else {
153          _canRemove = true;
154          return (CachedObjectInfo)(_current.getValue());
155        }
156      }
157    
158      public synchronized Object next() throws NoSuchElementException {
159        return getNext();
160      }
161    
162      public void remove() throws NoSuchElementException, IllegalStateException {
163        // grab a lock on _cache first, since _cache may try to grab a lock on this
164        synchronized(_cache) {
165          synchronized(this) {
166            if(null == _current) {
167              throw new IllegalStateException();
168            }
169            if(_canRemove) {
170              _cache.clear( ((CachedObjectInfo)(_current.getValue())).getKey() );
171            } else {
172              throw new IllegalStateException();
173            }
174          }
175        }
176      }
177    
178    
179      /** Sets my cache. */
180      public void setCache(Cache c) {
181        if(null != _cache) {
182          Object mutex = _cache;
183          synchronized(mutex) {
184            synchronized(c) {
185              synchronized(this) {
186                unsetCache();
187                _cache = c;
188                _cache.registerRetrievalListener(this);
189                _cache.registerStorageListener(this);
190              }
191            }
192          }
193        } else {
194          synchronized(c) {
195            synchronized(this) {
196              _cache = c;
197              _cache.registerRetrievalListener(this);
198              _cache.registerStorageListener(this);
199            }
200          }
201        }
202      }
203    
204      /** Unsets my cache. */
205      public void unsetCache() {
206        if(null != _cache) {
207          Object mutex = _cache;
208          synchronized(mutex) {
209            synchronized(this) {
210              _cache.unregisterRetrievalListener(this);
211              _cache.unregisterStorageListener(this);
212              _cache = null;
213            }
214          }
215        }
216      }
217    
218      public void storeRequested(Serializable key, Serializable val, Long expiresAt, Long cost, Serializable group) { }
219    
220      public void notStored(Serializable key, Serializable val, Long expiresAt, Long cost, Serializable group) { }
221    
222      public synchronized void stored(Serializable key, Serializable val, Long expiresAt, Long cost, Serializable group) {
223        WListableObject cur = (WListableObject)(_hash.get(key));
224        if(null == cur) {
225          cur = new WListableObject(new CachedObjectInfoImpl(key,expiresAt,cost));
226          addToList(cur);
227          _hash.put(key,cur);
228        } else {
229          removeFromList(cur);
230          addToList(cur);
231        }
232      }
233    
234      public synchronized void cleared(Serializable key) {
235        WListableObject cur = (WListableObject)(_hash.get(key));
236        if(cur != null) {
237          removeFromList(cur);
238          _hash.remove(key);
239        }
240      }
241    
242      public synchronized void cleared() {
243        _hash.clear();
244        _mostRecent = null;
245        _leastRecent = null;
246        _current = null;
247      }
248    
249      public void retrieveRequested(Serializable key) {
250      }
251    
252      public synchronized void retrieved(Serializable key) {
253        WListableObject cur = (WListableObject)(_hash.get(key));
254        if(null == cur) {
255          cur = new WListableObject(new CachedObjectInfoImpl(key,null,null));
256          addToList(cur);
257          _hash.put(key,cur);
258        } else {
259          removeFromList(cur);
260          addToList(cur);
261        }
262      }
263    
264      public void notRetrieved(Serializable key) {
265      }
266    
267      public synchronized Serializable getEvictionCandidate() {
268        try {
269          return ((CachedObjectInfoImpl)(_leastRecent.getValue())).getKey();
270        } catch(NullPointerException e) {
271          return null;
272        }
273      }
274    
275      public synchronized Serializable[] getEvictionCandidates(int n) {
276        ArrayList v = new ArrayList(n);
277        WListableObject c = _leastRecent;
278        while(n-- > 0) {
279          if(null != c) {
280            v.add(((CachedObjectInfoImpl)(c.getValue())).getKey());
281            c = (WListableObject)(c.getNext());
282          } else {
283            break;
284          }
285        }
286        return (Serializable[])(v.toArray(new Serializable[0]));
287      }
288    
289      public synchronized String toString() {
290        StringBuffer buf = new StringBuffer();
291        WListableObject c = _leastRecent;
292        while(c != null) {
293          if(c.hasPrev()) {
294            buf.append(", ");
295          }
296          buf.append(c.getValue());
297          c = (WListableObject)(c.getNext());
298        }
299        return buf.toString();
300      }
301    }