File: HashCache.java

package info (click to toggle)
libgroboutils-java 5-2
  • links: PTS, VCS
  • area: main
  • in suites: wheezy
  • size: 8,496 kB
  • sloc: java: 59,880; xml: 12,762; sh: 377; perl: 104; makefile: 20
file content (450 lines) | stat: -rw-r--r-- 12,299 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
/*
 * @(#)HashCache.java
 *
 * Copyright (C) 2003 Matt Albrecht
 * groboclown@users.sourceforge.net
 * http://groboutils.sourceforge.net
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a
 *  copy of this software and associated documentation files (the "Software"),
 *  to deal in the Software without restriction, including without limitation
 *  the rights to use, copy, modify, merge, publish, distribute, sublicense,
 *  and/or sell copies of the Software, and to permit persons to whom the 
 *  Software is furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in 
 *  all copies or substantial portions of the Software. 
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL 
 *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
 *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 *  DEALINGS IN THE SOFTWARE.
 */

package net.sourceforge.groboutils.util.datastruct.v1;


import java.util.Hashtable;
import java.util.Enumeration;


/**
 * Stores objects that are created via a unique key in a limited-sized
 * cache.  The objects are retrieved and created through the key.  If the
 * object with a requested key exists in the cache, then it is returned
 * and the object is marked as the latest accessed object.  If a request
 * is made for an object whose key is not in the cache, then the object
 * is created, put into the cache, and returned.  If the cache is full
 * when a new object is created, then the oldest item in the cache is
 * removed.
 * <P>
 * This class is NOT thread safe.  All thread safety issues will need to
 * be covered by the calling class.
 * <P>
 * Future versions should use the ObjectCache to cache the node instances.
 *
 * @author     Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
 * @since      May 23, 2003
 * @version    $Date: 2003/05/23 22:37:57 $
 */
public class HashCache
{
    /**
     * An interface which needs to be implemented and given to the
     * cache in order to create new instances.  It also allows for
     * created objects to be cleaned up.
     */
    public static interface ObjectManager
    {
        /**
         * Called when a new object needs to be created.
         *
         * @param key the key associated with the created object.
         * @return the newly created object
         */
        public Object createObject( Object key );
        
        
        /**
         * Called when the given object is being removed from the cache.
         *
         * @param key the key associated with the object.
         * @param obj the object being cleaned up.
         */
        public void cleanUpObject( Object key, Object obj );
    }
    
    
    /**
     * An inner class used for maintaining the chain of ownership.
     * Uses a doubly-linked list, so that each node can remove itself.
     */
    private static class Node
    {
        public Object key;
        public Object value;
        
        public Node next;
        public Node prev;
        
        public Node( Object key, Object value )
        {
            this.key = key;
            this.value = value;
        }
        
        
        /* used with the printChain method below
        public String toString()
        {
            return "node ["+this.value+"]";
        }
        */
    }
    
    
    /**
     * We also need to keep the object created.
     */
    private ObjectManager mgr;
    
    /**
     * Maximum size of the cache.
     */
    private int maxSize;
    
    
    /**
     * The head of our list of cached elements.
     */
    private Node head;
    
    
    /**
     * The tail of our list of cached elements.
     */
    private Node tail;
    
    
    /**
     * Our quick-reference to the list elements.
     */
    private Hashtable keyToNode = new Hashtable();
    
    
    /**
     * For metrics.
     */
    private int overflowCount = 0;
    
    
    //-----------------------------------------------------
    // Constructors
    
    
    /**
     * Create a new HashCache.
     *
     * @param om the manager for creating and cleaning up the objects.  This
     *      cannot be null.
     * @param maxSize the maximum size of the cache.  This must be &gt; 1
     *      (this is for performance reasons).
     */
    public HashCache( ObjectManager om, int maxSize )
    {
        if (om == null)
        {
            throw new IllegalArgumentException("no null args");
        }
        if (maxSize <= 1)
        {
            throw new IllegalArgumentException("size must be > 1");
        }
        
        this.mgr = om;
        this.maxSize = maxSize;
    }
    

    
    //-----------------------------------------------------
    // Public methods
    
    
    /**
     * Retrieves an item from the cache with the given key.  If the
     * object doesn't exist, then it is created, added to the cache,
     * and returned.  If it does exist, the cached version is returned
     * and marked as the most recently found.  If it was created,
     * and the cache is full, then the oldest retrieved object is
     * removed and cleaned up.
     */
    public Object get( Object key )
    {
        Node n = (Node)this.keyToNode.get( key );
        if (n == null)
        {
            // must include the equals, because we're going to add
            // another element to the list.  An "if" statement would
            // take roughly the same amount of time, but is less safe.
            while (getSize() >= getMaxSize())
            {
                Node last = removeTail();
                this.keyToNode.remove( last.key );
                cleanup( last );
                ++this.overflowCount;
            }
            n = createObject( key );
            this.keyToNode.put( key, n );
        }
        freshenNode( n );
        return n.value;
    }
    
    
    /**
     * Retrieves the current number of elements in the cache.
     *
     * @return the current size of the cache.
     */
    public int getSize()
    {
        //System.out.println("size = "+this.keyToNode.size());
        return this.keyToNode.size();
    }
    
    
    /**
     * Retrieves the maximum cache size.
     *
     * @return the maximum cache size.
     */
    public int getMaxSize()
    {
        //System.out.println("maxsize = "+this.maxSize);
        return this.maxSize;
    }
    
    
    /**
     * Retrieves the metric that corresponds to the number of objects
     * that were removed from the cache.
     *
     * @return the overflow count.
     */
    public int getOverflowCount()
    {
        return this.overflowCount;
    }
    
    
    /**
     * Resets the overflow count metric.
     */
    public void resetOverflowCount()
    {
        this.overflowCount = 0;
    }
    
    
    /**
     * Cleans out the data structure, calling the clean-up on all items.
     * The manager and max size will not be changed, nor will the overflow
     * count.
     */
    public void clear()
    {
        this.head = null;
        this.tail = null;
        Enumeration enum = this.keyToNode.keys();
        while (enum.hasMoreElements())
        {
            Object key = enum.nextElement();
            Node n = (Node)this.keyToNode.remove( key );
            cleanup( n );
        }
    }
    
    
    
    //-----------------------------------------------------
    // Protected methods
    
    
    /**
     * Generates an Object for the cache, but does not add it to the
     * hashtable or lists.
     */
    private Node createObject( Object key )
    {
        Object o = this.mgr.createObject( key );
        Node n = new Node( key, o );
        return n;
    }
    
    
    /**
     * Call the cleanup on the given node.
     */
    private void cleanup( Node n )
    {
        this.mgr.cleanUpObject( n.key, n.value );
    }
    
    
    /**
     * Removes the tail node.  If the list is empty or has 1 element, then an
     * IllegalStateException is thrown.
     *
     * @return the last node in the list.
     */
    private Node removeTail()
    {
        /*
        // temp test code
        if (this.tail == null)
        {
            throw new IllegalStateException("list is empty");
        }
        */
        
        Node n = this.tail;
        this.tail = n.prev;
        n.prev = null;
        tail.next = null;
        
        /*
        // temp test code
        if (this.tail == null)
        {
            throw new IllegalStateException("invalid tail setup: size < 1");
        }
        if (n.next != null)
        {
            throw new IllegalStateException("next of old tail node "+n+" is not null, but "+n.next);
        }
        */
        
        return n;
    }
    
    
    /**
     * Move the given node to the top of the list.  This works even if the
     * given node is not in the list.
     */
    private void freshenNode( Node n )
    {
        /*
        // temp test code
        if (n == null)
        {
            throw new IllegalArgumentException("no null args");
        }
        */
        
        if (n == this.head)
        {
            // we're done!
            return;
        }
        
        if (this.head == null)
        {
            // n's next and prev better be null!
            this.head = n;
            this.tail = n;
            
            /*
            // temp test code
            if (n.next != null || n.prev != null)
            {
                throw new IllegalStateException("new head&tail node "+n+" next is "+n.next+", prev is "+n.prev);
            }
            */
        }
        else
        {
            // setup tail, and next and prev of n's next and prev
            if (n.prev != null)
            {
                n.prev.next = n.next;
            }
            
            if (n.next != null)
            {
                n.next.prev = n.prev;
            }
            else
            if (this.tail == n)
            {
                this.tail = n.prev;
                
                // n.prev better not be null!  that should have been trapped
                // above.
                /*
                // temp test code
                if (n.prev == null)
                {
                    throw new IllegalStateException("new tail's previous is null!");
                }
                */
            }
            
            n.prev = null;
            
            // the head is going to be not null, due to the if block above
            this.head.prev = n;
            n.next = this.head;
            
            this.head = n;
        }
    }
    
    
    /* Integrety check.  Uncomment when doing testing.
    protected void printChain()
    {
        if (this.head == null)
        {
            System.out.println("[Empty chain]");
            return;
        }
        Hashtable seen = new Hashtable();
        
        System.out.println("Chain:");
        Node n = this.head;
        while (n != null)
        {
            seen.put( n, new Object() );
            System.out.println("    "+n);
            Node n2 = n.next;
            if (n2 != null && n2.prev != n)
            {
                throw new IllegalStateException("Bad back chain! next = "+n2+
                    ", but its prev = "+n2.prev);
            }
            else
            if (n2 == null && n != this.tail)
            {
                throw new IllegalStateException("Broken Chain!  next is null but tail isn't this node (it's "+this.tail+")");
            }
            else
            if (n2 != null && seen.contains( n2 ))
            {
                throw new IllegalStateException("node "+n2+" in the list twice.");
            }
            if (n2 != null && n == this.tail)
            {
                throw new IllegalStateException("Tail in a loop!");
            }
            n = n2;
        }
        System.out.println("[End chain]");
    }
    */
}