KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > edu > rice > cs > drjava > model > cache > DocumentCache


1 /*BEGIN_COPYRIGHT_BLOCK
2  *
3  * This file is part of DrJava. Download the current version of this project from http://www.drjava.org/
4  * or http://sourceforge.net/projects/drjava/
5  *
6  * DrJava Open Source License
7  *
8  * Copyright (C) 2001-2005 JavaPLT group at Rice University (javaplt@rice.edu). All rights reserved.
9  *
10  * Developed by: Java Programming Languages Team, Rice University, http://www.cs.rice.edu/~javaplt/
11  *
12  * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
13  * documentation files (the "Software"), to deal with the Software without restriction, including without limitation
14  * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
15  * to permit persons to whom the Software is furnished to do so, subject to the following conditions:
16  *
17  * - Redistributions of source code must retain the above copyright notice, this list of conditions and the
18  * following disclaimers.
19  * - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
20  * following disclaimers in the documentation and/or other materials provided with the distribution.
21  * - Neither the names of DrJava, the JavaPLT, Rice University, nor the names of its contributors may be used to
22  * endorse or promote products derived from this Software without specific prior written permission.
23  * - Products derived from this software may not be called "DrJava" nor use the term "DrJava" as part of their
24  * names without prior written permission from the JavaPLT group. For permission, write to javaplt@rice.edu.
25  *
26  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
27  * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28  * CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
29  * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
30  * WITH THE SOFTWARE.
31  *
32  *END_COPYRIGHT_BLOCK*/

33
34 package edu.rice.cs.drjava.model.cache;
35
36 import javax.swing.text.BadLocationException JavaDoc;
37 import java.util.*;
38 import java.io.IOException JavaDoc;
39
40 import edu.rice.cs.drjava.model.definitions.DefinitionsDocument;
41 import edu.rice.cs.drjava.model.OpenDefinitionsDocument;
42 import edu.rice.cs.drjava.model.FileMovedException;
43 import edu.rice.cs.util.UnexpectedException;
44 import edu.rice.cs.util.swing.Utilities;
45 import edu.rice.cs.util.OrderedHashSet;
46
47 /** The document cache is a structure that maps OpenDefinitionsDocuments to DefinitionsDocuments (which contain
48  * the actual document text). Since the latter can consume a lot of memory, the cache virtualizes some of them
49  * using DefinitionsDocument reconstructors (DDReconstructor). It tries to limit the number of
50  * DefinitionsDocuments loaded in memory at one time, but it must of course retain all modified
51  * DefinitionsDocuments.
52  * <p>
53  * The cache creates a DocManager for each OpenDefinitionsDocument entered (registered) in the cache. The managers
54  * maintain the actual links to DefinitionsDocuments. Since the Managers themselves implement the DCacheAdapter
55  * interface, the model goes directly to the manager to get the instance of the DefinitionsDocument.
56  * <p>
57  * When a document is accessed through the document manager by the model, the cache informs the manager, which
58  * tells the active queue to add the manager to the end of the queue--if it isn't already in the queue. If the
59  * active queue had already reached maximum size, it deletes the last document in the queue to keep the queue from
60  * growing larger than its maximum size.
61  * <p>
62  * The resident queue only contains documents that have not been modified since their last save (except in the process
63  * of responding to notification that a document has been modified). When a document is modified for the first time,
64  * it is immediately removed from the resident queue and marked as UNMANAGED by its document manager. An
65  * UNMANAGED document remains in memory until it is saved or closed without being saved. If such a document is
66  * saved, it is inserted again in the resident queue.
67  * <p>
68  * Since the cache and document managers can both be concurrently accessed from multiple threads, the methods in the
69  * DocumentCache and DocManager classes are synchronized. Some operations require locks on both the cache and a
70  * document manager, but the code is written so that none of require these locks to be held simultaneously.
71  */

72
73 public class DocumentCache {
74   
75   private static final int INIT_CACHE_SIZE = 32;
76   
77   /** @invariant _residentQueue.size() <= CACHE_SIZE */
78   private int CACHE_SIZE;
79   
80   private OrderedHashSet<DocManager> _residentQueue;
81   
82   private Object JavaDoc _cacheLock = new Object JavaDoc();
83     
84   /* General constructor. Not currently used except when called by default constructor. */
85   public DocumentCache(int size) {
86 // Utilities.showDebug("DocumentCache created with size = " + size);
87
CACHE_SIZE = size;
88     _residentQueue = new OrderedHashSet<DocManager>();
89   }
90   
91   /* Default constructor; uses default cache size. */
92   public DocumentCache() { this(INIT_CACHE_SIZE); }
93
94   /** Returns a cache adapter corresponding to the owner of the given reconstructor.
95    * @param odd The open definitions document that is registering. (Useful for debugging purposes.)
96    * @param rec A reconstructor from which to create the document that is to be managed in this cache
97    * @return an adapter that allows its owner to access its definitions document
98    */

99   public DCacheAdapter register(OpenDefinitionsDocument odd, DDReconstructor rec) {
100     DocManager mgr = new DocManager(rec, odd.toString(), odd.isUntitled());
101     notifyRegistrationListeners(odd, mgr); // runs synchronously; only used in tests
102
// System.err.println("register(" + odd + ", " + rec + ") called");
103
return mgr;
104   }
105   
106   /** Changes the number of <b>unmodified</b> documents allowed in the cache at one time. <b> Note: modified documents
107     * are not managed in the cache except in transitional situations when a queue document becomes modified. Only
108     * used in tests.
109     */

110   public void setCacheSize(int size) {
111     if (size <= 0) throw new IllegalArgumentException JavaDoc("Cannot set the cache size to zero or less.");
112     int diff;
113     DocManager[] removed = null; // bogus initialization makes javac happy
114
synchronized(_cacheLock) { // lock the cache so entries can be removed if necessary
115
CACHE_SIZE = size;
116       diff = _residentQueue.size() - CACHE_SIZE;
117       if (diff > 0) {
118         removed = new DocManager[diff];
119         for (int i = 0; i < diff; i++) removed[i] = _residentQueue.remove(0);
120       }
121       if (diff > 0) kickOut(removed);
122     }
123   }
124   
125   /** Kicks out all documents in removed. Assumes that _cacheLock is already held. */
126   private void kickOut(DocManager[] removed) {
127     for (DocManager dm: removed) dm.kickOut();
128   }
129     
130   public int getCacheSize() { return CACHE_SIZE; }
131   public int getNumInCache() { return _residentQueue.size(); }
132     
133   
134   
135   ///////////////////////////// DocManager //////////////////////////
136

137   private static final int IN_QUEUE = 0; // In the resident queue and hence subject to virtualization
138
private static final int UNTITLED = 1; // An untitled document not in queue (may or may not be modified)
139
private static final int NOT_IN_QUEUE = 2; // Virtualized and not in the QUEUE
140
private static final int UNMANAGED = 3; // A modified, titled document not in the queue
141
/** Note: before extending this table, check that the extension does not conflict with isUnmangedOrUntitled() */
142   
143   /** Manages the retrieval of a document for a corresponding open definitions document. This manager only
144    * maintains its document data if it contained in _residentQueue, which is maintained using a round-robin
145    * replacement scheme.
146    */

147   private class DocManager implements DCacheAdapter {
148     
149     private final DDReconstructor _rec;
150     private volatile int _stat; // I know, this is not very OO
151
private volatile DefinitionsDocument _doc;
152     private volatile String JavaDoc _fileName;
153     
154     /** Instantiates a manager for the documents that are produced by the given document reconstructor.
155      * @param rec The reconstructor used to create the document
156      */

157     public DocManager(DDReconstructor rec, String JavaDoc fn, boolean isUntitled) {
158 // Utilities.showDebug("DocManager(" + rec + ", " + fn + ", " + isUntitled + ")");
159
_rec = rec;
160       if (isUntitled) _stat = UNTITLED;
161       else _stat = NOT_IN_QUEUE;
162       _doc = null;
163      _fileName = fn;
164     }
165     
166     public DDReconstructor getReconstructor() { return _rec; }
167     
168     /** Gets the physical document (DD) for this manager. If DD is not in memory, it loads it into memory and returns
169      * it. If the document has been modified in memory since it was last fetched, make it "unmanaged", removing it from
170      * the queue. It will remain in memory until saved. If a document is not in the queue, add it.
171      * @return the physical document that is managed by this adapter
172      */

173     public DefinitionsDocument getDocument() throws IOException JavaDoc, FileMovedException {
174 // Utilities.showDebug("getDocument called on " + this + " with _stat = " + _stat);
175

176 // The following double-check idiom is safe in Java 1.4 and later JVMs provided that _doc is volatile.
177
if (_doc != null) return _doc;
178       synchronized(_cacheLock) { // lock the cache so that this DocManager's state can be updated
179
if (_doc != null) return _doc; // _doc may have changed since test outside of _cacheLock
180
try { // _doc is not in memory
181
_doc = _rec.make();
182           assert _doc != null;
183         }
184         catch(BadLocationException JavaDoc e) { throw new UnexpectedException(e); }
185 // Utilities.showDebug("Document " + _doc + " reconstructed; _stat = " + _stat);
186
if (_stat == NOT_IN_QUEUE) add(); // add this to queue
187
return _doc;
188       }
189     }
190     
191     /** Checks whether the document is resident (in the cache or modified).
192      * @return if the document is resident.
193      */

194     public boolean isReady() { synchronized (_cacheLock) { return _doc != null; } }
195   
196     /** Closes the corresponding document for this adapter. Done when a document is closed by the navigator. */
197     public void close() {
198 // Utilities.showDebug("close() called on " + this);
199
synchronized(_cacheLock) {
200         _residentQueue.remove(this);
201         closingKickOut();
202       }
203     }
204     
205     public void documentModified() {
206       synchronized(_cacheLock) {
207         _residentQueue.remove(this); // remove modified document from queue if present
208
_stat = UNMANAGED;
209       }
210     }
211     
212      public void documentReset() {
213       synchronized(_cacheLock) {
214         if (_stat == UNMANAGED) add(); // add document to queue if it was formerly unmanaged
215
}
216     }
217      
218     /** Updates status of this document in the cache. */
219     public void documentSaved(String JavaDoc fileName) {
220 // Utilities.showDebug("Document " + _doc + " has been saved as " + fileName);
221
synchronized(_cacheLock) { // lock the document manager so that document manager fields can be updated
222
if (isUnmanagedOrUntitled()) {
223           _fileName = fileName;
224           add(); // add formerly unmanaged/untitled document to queue
225
}
226       }
227     }
228     
229     /** Adds this DocManager to the queue and sets status to IN_QUEUE. Assumes _cacheLock is already held. */
230     private void add() {
231 // Utilities.showDebug("add " + this + " to the QUEUE\n" + "QUEUE = " + _residentQueue);
232
if (! _residentQueue.contains(this)) {
233         _residentQueue.add(this);
234         _stat = IN_QUEUE;
235       }
236       if (_residentQueue.size() > CACHE_SIZE) _residentQueue.get(0).remove();
237     }
238     
239     /** Removes this DocManager from the queue and sets status to NOT_IN_QUEUE. Assumes _cacheLock is already held. */
240     private void remove() {
241       boolean removed = _residentQueue.remove(this);
242       kickOut();
243     }
244     
245     /** All of the following private methods presume that _cacheLock is held */
246     private boolean isUnmanagedOrUntitled() { return (_stat & 0x1) != 0; } // tests if _stat is odd
247

248     /** Called by the cache when the document is removed from the active queue and subject to virtualization.
249       * Assumes cacheLock is already held.
250       */

251     void kickOut() { kickOut(false); }
252     
253     /** Called by the cache when the document is being closed. Note that _doc can be null in this case!
254       * Assumes cacheLock is already held.
255       */

256     void closingKickOut() { kickOut(true); }
257    
258     /** Performs the actual kickOut operation. Assumes cacheLock is already held. */
259     private void kickOut(boolean isClosing) {
260 // Utilities.showDebug("kickOut(" + isClosing + ") called on " + this);
261
if (! isClosing) {
262         /* virtualize this document */
263 // Utilities.showDebug("Virtualizing " + _doc);
264
_rec.saveDocInfo(_doc);
265       }
266       if (_doc != null) {
267         _doc.close();
268         _doc = null;
269       }
270       _stat = NOT_IN_QUEUE;
271     }
272     
273     public String JavaDoc toString() { return _fileName; }
274   }
275   
276   ////////////////////////////////////////
277

278   /** This interface allows the unit tests to get a handle on what's going on since the work is spread
279    * between the ODD, the cache, and the Adapters.
280    */

281   public interface RegistrationListener {
282     public void registered(OpenDefinitionsDocument odd, DCacheAdapter man);
283   }
284   
285   private LinkedList<RegistrationListener> _regListeners = new LinkedList<RegistrationListener>();
286   
287   public void addRegistrationListener(RegistrationListener list) { synchronized(_regListeners) { _regListeners.add(list); } }
288     public void removeRegistrationListener(RegistrationListener list) { synchronized(_regListeners) { _regListeners.remove(list); } }
289   public void clearRegistrationListeners() { _regListeners.clear(); }
290   // Only used in DocumentCacheTest; must be synchronous for test to succeed.
291
private void notifyRegistrationListeners(final OpenDefinitionsDocument odd, final DocManager man) {
292     synchronized(_regListeners) {
293       if (_regListeners.isEmpty()) return;
294       Utilities.invokeAndWait(new Runnable JavaDoc() {
295         public void run() { for (RegistrationListener list : _regListeners) { list.registered(odd, man); } }
296       });
297     }
298   }
299 }
300
Popular Tags