KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > riotfamily > cachius > Cache


1 /* ***** BEGIN LICENSE BLOCK *****
2  * Version: MPL 1.1
3  * The contents of this file are subject to the Mozilla Public License Version
4  * 1.1 (the "License"); you may not use this file except in compliance with
5  * the License. You may obtain a copy of the License at
6  * http://www.mozilla.org/MPL/
7  *
8  * Software distributed under the License is distributed on an "AS IS" basis,
9  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
10  * for the specific language governing rights and limitations under the
11  * License.
12  *
13  * The Original Code is Riot.
14  *
15  * The Initial Developer of the Original Code is
16  * Neteye GmbH.
17  * Portions created by the Initial Developer are Copyright (C) 2006
18  * the Initial Developer. All Rights Reserved.
19  *
20  * Contributor(s):
21  * Felix Gnass [fgnass at neteye dot de]
22  *
23  * ***** END LICENSE BLOCK ***** */

24 package org.riotfamily.cachius;
25
26 import java.io.File JavaDoc;
27 import java.io.FileInputStream JavaDoc;
28 import java.io.FileOutputStream JavaDoc;
29 import java.io.IOException JavaDoc;
30 import java.io.InvalidClassException JavaDoc;
31 import java.io.ObjectInputStream JavaDoc;
32 import java.io.ObjectOutputStream JavaDoc;
33 import java.io.Serializable JavaDoc;
34 import java.util.HashMap JavaDoc;
35
36 import org.apache.commons.logging.Log;
37 import org.apache.commons.logging.LogFactory;
38
39 /**
40  * The cachius cache.
41  *
42  * @author Felix Gnass
43  */

44 public final class Cache implements Serializable JavaDoc {
45
46     private static final String JavaDoc CACHE_FILE = "cache-info";
47
48     private static Log log = LogFactory.getLog(Cache.class);
49
50     private int size;
51     private int capacity;
52
53     private transient CacheItem first;
54     private transient CacheItem last;
55
56     private transient HashMap JavaDoc map;
57     private transient File JavaDoc cacheDir = null;
58     private transient File JavaDoc[] dirs = null;
59
60     private transient int numberOfDirs;
61     private transient int currentDir = 0;
62
63     /**
64      * Create the cache.
65      */

66     private Cache(int capacity, File JavaDoc cacheDir) {
67         this.size = 0;
68         this.cacheDir = cacheDir;
69         this.map = new HashMap JavaDoc(capacity);
70         setCapacity(capacity);
71         clear();
72     }
73
74     public File JavaDoc getCacheDir() {
75         return cacheDir;
76     }
77
78     public void setCacheDir(File JavaDoc cacheDir) {
79         if (this.cacheDir != null) {
80             clear();
81         }
82         this.cacheDir = cacheDir;
83     }
84
85     /**
86      * Returns the CacheItem with the given key or creates a new one, if no
87      * entry with that key exists.
88      *
89      * @param key The cache key
90      * @return The CacheItem for the given key
91      */

92     public synchronized CacheItem getItem(String JavaDoc key) {
93         CacheItem item = (CacheItem) map.get(key);
94         if (item == null) {
95             item = newItem(key);
96         }
97         else {
98             touch(item);
99         }
100         return item;
101     }
102
103     /**
104      * Sets the cache capacity. If <code>capacity</code> is lower than the
105      * current capacity, items are removed to fit the new size.
106      */

107     public synchronized void setCapacity(int capacity) {
108         this.capacity = capacity;
109         numberOfDirs = capacity / 1000;
110         dirs = new File JavaDoc[numberOfDirs];
111         while (size > capacity) {
112             map.remove(last.getKey());
113             unlink(last);
114             size--;
115         }
116     }
117
118     /**
119      * Touches the given item, i.e. unlinks it and re-inserts it as the first
120      * element of the list.
121      */

122     private void touch(CacheItem item) {
123         unlink(item);
124         link(item);
125     }
126
127     /**
128      * Creates a new item for the given key and adds it to the cache. If due
129      * to that operation the capacity is exeeded, the last item is removed.
130      */

131     private CacheItem newItem(String JavaDoc key) {
132         try {
133             if (capacity == 0) {
134                 return null;
135             }
136             log.debug("Creating new cache item for " + key);
137             CacheItem item = createCacheItem(key);
138
139             if (size >= capacity) {
140                 log.debug("Maximum size exceeded. Removing: " + last.getKey());
141                 map.remove(last.getKey());
142                 last.delete();
143                 unlink(last);
144             }
145             else {
146                 size++;
147             }
148
149             link(item);
150             map.put(key, item);
151
152             return item;
153         }
154         catch (IOException JavaDoc e) {
155             log.error("Error creating item.", e);
156             return null;
157         }
158     }
159
160     /**
161      * Inserts the given item at the begining of the item list.
162      */

163     private void link(CacheItem item) {
164         item.setPrevious(null);
165         item.setNext(first);
166         if (first == null) {
167             last = item;
168         }
169         else {
170             first.setPrevious(item);
171         }
172
173         first = item;
174     }
175
176     /**
177      * Removes the give item from the item list.
178      */

179     private void unlink(CacheItem item) {
180         CacheItem previous = item.getPrevious();
181         CacheItem next = item.getNext();
182
183         if (previous != null) {
184             previous.setNext(next);
185         }
186         else {
187             first = next;
188         }
189
190         if (next != null) {
191             next.setPrevious(previous);
192         }
193         else {
194             last = previous;
195         }
196     }
197
198     protected File JavaDoc getNextDir() {
199         cacheDir.mkdirs();
200         if (numberOfDirs <= 1) {
201             return cacheDir;
202         }
203         if (currentDir >= numberOfDirs) {
204             currentDir = 0;
205         }
206         File JavaDoc dir = dirs[currentDir];
207         if (dir == null) {
208             dir = new File JavaDoc(cacheDir, String.valueOf(currentDir));
209             dir.mkdir();
210             dirs[currentDir] = dir;
211         }
212         currentDir++;
213         return dir;
214     }
215
216     public CacheItem createCacheItem(String JavaDoc key) throws IOException JavaDoc {
217         return new CacheItem(key, getNextDir());
218     }
219
220     private synchronized void clear() {
221         log.info("Removing all cache entries ...");
222         File JavaDoc[] entries = cacheDir.listFiles();
223         for (int i = 0; i < entries.length; i++) {
224             deleteFile(entries[i]);
225         }
226     }
227
228     private void deleteFile(File JavaDoc f) {
229         if (f.isDirectory()) {
230             File JavaDoc[] entries = f.listFiles();
231             for (int i = 0; i < entries.length; i++) {
232                 deleteFile(entries[i]);
233             }
234         }
235         f.delete();
236     }
237
238     public void invalidateTaggedItems(String JavaDoc tag) {
239         log.debug("Invalidating items taged as " + tag);
240         CacheItem item = first;
241         while (item != null) {
242             if (item.hasTag(tag)) {
243                 log.debug("Deleting CacheItem " + item.getKey());
244                 item.invalidate();
245             }
246             item = item.getNext();
247         }
248     }
249
250     /**
251      * Factory method to create a new cache. If restore is <code>true</code>
252      * and a cache file exists in the given directory, the method will try to
253      * deserialize it. If no file is found or the deserialization fails, a
254      * new cache is created.
255      */

256     public static Cache newInstance(int capacity, File JavaDoc cacheDir,
257             boolean restore) throws IOException JavaDoc {
258
259         if (!cacheDir.exists() && !cacheDir.mkdirs()) {
260             throw new IOException JavaDoc("Can't create cache directory: " + cacheDir);
261         }
262         File JavaDoc f = new File JavaDoc(cacheDir, CACHE_FILE);
263         if (restore && f.exists()) {
264             log.info("Trying to build cache from file: " + f);
265             try {
266                 ObjectInputStream JavaDoc in = new ObjectInputStream JavaDoc(
267                          new FileInputStream JavaDoc(f));
268
269                 Cache cache = (Cache) in.readObject();
270                 in.close();
271                 f.delete();
272                 cache.setCacheDir(cacheDir);
273                 cache.setCapacity(capacity);
274                 log.info("Cache has been successfully deserialized. " +
275                         "Number of items: " + cache.size);
276
277                 return cache;
278             }
279             catch (InvalidClassException JavaDoc e) {
280                 log.info("Serialized cache has been discarded due to " +
281                         "version incompatibilies.");
282             }
283             catch (IOException JavaDoc e) {
284                 log.warn("Deserialization failed.");
285             }
286             catch (ClassNotFoundException JavaDoc e) {
287                 log.warn("Deserialization failed.", e);
288             }
289         }
290         log.info("Building new cache in: " + cacheDir);
291         return new Cache(capacity, cacheDir);
292     }
293
294     /**
295      * Serializes the cache state to disk.
296      */

297     public synchronized void persist() {
298         File JavaDoc f = new File JavaDoc(cacheDir, CACHE_FILE);
299         if (!f.exists()) {
300             try {
301                  log.info("Persisting the cache state ...");
302                  ObjectOutputStream JavaDoc out = new ObjectOutputStream JavaDoc(
303                          new FileOutputStream JavaDoc(f));
304
305                  out.writeObject(this);
306                  out.close();
307                  log.info("Cache state saved in " + f);
308             }
309             catch (IOException JavaDoc e) {
310                 log.error("Can't save cache state", e);
311             }
312         }
313     }
314
315     private void writeObject(ObjectOutputStream JavaDoc out) throws IOException JavaDoc {
316         out.defaultWriteObject();
317         CacheItem item = first;
318         while (item != null) {
319             out.writeObject(item);
320             item = item.getNext();
321         }
322     }
323
324     private void readObject(ObjectInputStream JavaDoc in) throws IOException JavaDoc,
325             ClassNotFoundException JavaDoc {
326
327         in.defaultReadObject();
328         map = new HashMap JavaDoc(capacity);
329         if (size > 0) {
330             CacheItem item = (CacheItem) in.readObject();
331             first = item;
332             CacheItem prev = item;
333             for (int i = 1; i < size; i++) {
334                 item = (CacheItem) in.readObject();
335                 item.setPrevious(prev);
336                 prev.setNext(item);
337                 map.put(item.getKey(), item);
338                 prev = item;
339             }
340             last = item;
341         }
342     }
343
344 }
345
Popular Tags