001: /*BEGIN_COPYRIGHT_BLOCK
002: *
003: * Copyright (c) 2001-2007, JavaPLT group at Rice University (javaplt@rice.edu)
004: * All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions are met:
008: * * Redistributions of source code must retain the above copyright
009: * notice, this list of conditions and the following disclaimer.
010: * * Redistributions in binary form must reproduce the above copyright
011: * notice, this list of conditions and the following disclaimer in the
012: * documentation and/or other materials provided with the distribution.
013: * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
014: * names of its contributors may be used to endorse or promote products
015: * derived from this software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
018: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
019: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
020: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
021: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
022: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
023: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
024: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
025: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
026: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
027: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028: *
029: * This software is Open Source Initiative approved Open Source Software.
030: * Open Source Initative Approved is a trademark of the Open Source Initiative.
031: *
032: * This file is part of DrJava. Download the current version of this project
033: * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
034: *
035: * END_COPYRIGHT_BLOCK*/
036:
037: package edu.rice.cs.drjava.model.cache;
038:
039: import javax.swing.event.DocumentListener;
040: import javax.swing.text.BadLocationException;
041: import java.util.*;
042: import java.io.IOException;
043:
044: import edu.rice.cs.drjava.model.definitions.DefinitionsDocument;
045: import edu.rice.cs.drjava.model.OpenDefinitionsDocument;
046: import edu.rice.cs.drjava.model.FileMovedException;
047:
048: import edu.rice.cs.util.Log;
049: import edu.rice.cs.util.UnexpectedException;
050: import edu.rice.cs.util.swing.Utilities;
051: import edu.rice.cs.util.OrderedHashSet;
052:
053: /** The document cache is a structure that maps OpenDefinitionsDocuments to DefinitionsDocuments (which contain
054: * the actual document text). Since the latter can consume a lot of memory, the cache virtualizes some of them
055: * using DefinitionsDocument reconstructors (DDReconstructor). It tries to limit the number of
056: * DefinitionsDocuments loaded in memory at one time, but it must of course retain all modified
057: * DefinitionsDocuments.
058: * <p>
059: * The cache creates a DocManager for each OpenDefinitionsDocument entered (registered) in the cache. The managers
060: * maintain the actual links to DefinitionsDocuments. Since the Managers themselves implement the DCacheAdapter
061: * interface, the model goes directly to the manager to get the instance of the DefinitionsDocument.
062: * <p>
063: * When a document is accessed through the document manager by the model, the cache informs the manager, which
064: * tells the active queue to add the manager to the end of the queue--if it isn't already in the queue. If the
065: * active queue had already reached maximum size, it deletes the last document in the queue to keep the queue from
066: * growing larger than its maximum size.
067: * <p>
068: * The resident queue only contains documents that have not been modified since their last save (except in the process
069: * of responding to notification that a document has been modified). When a document is modified for the first time,
070: * it is immediately removed from the resident queue and marked as UNMANAGED by its document manager. An
071: * UNMANAGED document remains in memory until it is saved or closed without being saved. If such a document is
072: * saved, it is inserted again in the resident queue.
073: * <p>
074: * Since the cache and document managers can both be concurrently accessed from multiple threads, the methods in the
075: * DocumentCache and DocManager classes are synchronized. Some operations require locks on both the cache and a
076: * document manager, but the code is written so that none of require these locks to be held simultaneously.
077: */
078:
079: public class DocumentCache {
080:
081: /** Log file. */
082: private static final Log _log = new Log("DocumentCache.txt", false);
083:
084: private static final int INIT_CACHE_SIZE = 32;
085:
086: /** @invariant _residentQueue.size() <= CACHE_SIZE */
087: private int CACHE_SIZE;
088:
089: private OrderedHashSet<DocManager> _residentQueue;
090:
091: private Object _cacheLock = new Object();
092:
093: /* General constructor. Not currently used except when called by default constructor. */
094: public DocumentCache(int size) {
095: // Utilities.showDebug("DocumentCache created with size = " + size);
096: CACHE_SIZE = size;
097: _residentQueue = new OrderedHashSet<DocManager>();
098: }
099:
100: /* Default constructor; uses default cache size. */
101: public DocumentCache() {
102: this (INIT_CACHE_SIZE);
103: }
104:
105: /** Returns a cache adapter corresponding to the owner of the given reconstructor.
106: * @param odd The open definitions document that is registering. (Useful for debugging purposes.)
107: * @param rec A reconstructor from which to create the document that is to be managed in this cache
108: * @return an adapter that allows its owner to access its definitions document
109: */
110: public DCacheAdapter register(OpenDefinitionsDocument odd,
111: DDReconstructor rec) {
112: DocManager mgr = new DocManager(rec, odd.isUntitled());
113: notifyRegistrationListeners(odd, mgr); // runs synchronously; only used in tests
114: // System.err.println("register(" + odd + ", " + rec + ") called");
115: return mgr;
116: }
117:
118: /** Changes the number of <b>unmodified</b> documents allowed in the cache at one time. <b> Note: modified documents
119: * are not managed in the cache except in transitional situations when a queue document becomes modified. Only
120: * used in tests.
121: */
122: public void setCacheSize(int size) {
123: if (size <= 0)
124: throw new IllegalArgumentException(
125: "Cannot set the cache size to zero or less.");
126: int diff;
127: DocManager[] removed = null; // bogus initialization makes javac happy
128: synchronized (_cacheLock) { // lock the cache so entries can be removed if necessary
129: CACHE_SIZE = size;
130: diff = _residentQueue.size() - CACHE_SIZE;
131: if (diff > 0) {
132: removed = new DocManager[diff];
133: for (int i = 0; i < diff; i++)
134: removed[i] = _residentQueue.remove(0);
135: }
136: if (diff > 0)
137: kickOut(removed);
138: }
139: }
140:
141: /** Kicks out all documents in removed. Assumes that _cacheLock is already held. */
142: private void kickOut(DocManager[] removed) {
143: for (DocManager dm : removed)
144: dm.kickOut();
145: }
146:
147: public int getCacheSize() {
148: return CACHE_SIZE;
149: }
150:
151: public int getNumInCache() {
152: return _residentQueue.size();
153: }
154:
155: public String toString() {
156: return _residentQueue.toString();
157: }
158:
159: ///////////////////////////// DocManager //////////////////////////
160:
161: private static final int IN_QUEUE = 0; // In the resident queue and hence subject to virtualization
162: private static final int UNTITLED = 1; // An untitled document not in queue (may or may not be modified)
163: private static final int NOT_IN_QUEUE = 2; // Virtualized and not in the QUEUE
164: private static final int UNMANAGED = 3; // A modified, titled document not in the queue
165:
166: /** Note: before extending this table, check that the extension does not conflict with isUnmangedOrUntitled() */
167:
168: /** Manages the retrieval of a document for a corresponding open definitions document. This manager only
169: * maintains its document data if it contained in _residentQueue, which is maintained using a round-robin
170: * replacement scheme.
171: */
172: private class DocManager implements DCacheAdapter {
173:
174: private final DDReconstructor _rec;
175: private volatile int _stat; // I know, this is not very OO
176: private volatile DefinitionsDocument _doc;
177:
178: /** Instantiates a manager for the documents that are produced by the given document reconstructor.
179: * @param rec The reconstructor used to create the document
180: */
181: public DocManager(DDReconstructor rec, boolean isUntitled) {
182: // Utilities.showDebug("DocManager(" + rec + ", " + fn + ", " + isUntitled + ")");
183: _rec = rec;
184: if (isUntitled)
185: _stat = UNTITLED;
186: else
187: _stat = NOT_IN_QUEUE;
188: _doc = null;
189: // System.err.println(this + " constructed");
190: }
191:
192: /** Adds DocumentListener to the reconstructor. */
193: public void addDocumentListener(DocumentListener l) {
194: _rec.addDocumentListener(l);
195: }
196:
197: /** Makes this document; assumes that cacheLock is already held. */
198: private DefinitionsDocument makeDocument() {
199: try { // _doc is not in memory
200: _doc = _rec.make();
201: assert _doc != null;
202: } catch (Exception e) {
203: throw new UnexpectedException(e);
204: }
205: // Utilities.showDebug("Document " + _doc + " reconstructed; _stat = " + _stat);
206: // System.err.println("Making document for " + this);
207: if (_stat == NOT_IN_QUEUE)
208: add(); // add this to queue
209: return _doc;
210: }
211:
212: /** Gets the physical document (DD) for this manager. If DD is not in memory, it loads it from its image in its
213: * DDReconstructor and returns it. If the document has been modified in memory since it was last fetched, make
214: * it "unmanaged", removing it from the queue. It will remain in memory until saved. If a document is not in
215: * the queue, add it.
216: * @return the physical document that is managed by this adapter
217: */
218: public DefinitionsDocument getDocument() throws IOException,
219: FileMovedException {
220: // Utilities.showDebug("getDocument called on " + this + " with _stat = " + _stat);
221:
222: // The following double-check idiom is safe in Java 1.4 and later JVMs provided that _doc is volatile.
223: final DefinitionsDocument doc = _doc; // create a snapshot of _doc
224: if (doc != null)
225: return doc;
226: synchronized (_cacheLock) { // lock the cache so that this DocManager's state can be updated
227: if (_doc != null)
228: return _doc; // _doc may have changed since test outside of _cacheLock
229: return makeDocument();
230: }
231: }
232:
233: /** Gets the length of this document using (i) cached _doc or (ii) reconstructor (which may force the document
234: * to be loaded. */
235: public int getLength() {
236: final DefinitionsDocument doc = _doc; // create a snapshot of _doc
237: if (doc != null)
238: return doc.getLength();
239: return _rec.getText().length(); // There is a technical race here; _doc could be set and modified before here
240: }
241:
242: /** Gets the text of this document using in order of preference (i) cached _doc; (ii) cached reconstructor _image;
243: * and (iii) the document after forcing it to be loaded. */
244: public String getText() {
245: final DefinitionsDocument doc = _doc; // create a snapshot of _doc
246: if (doc != null)
247: return doc.getText();
248: return _rec.getText(); // There is a technical race here; _doc could be set and modified before here
249: }
250:
251: /* Gets the specified substring of this document; throws an exception if the specification is ill-formed. */
252: public String getText(int offset, int len) {
253: String text = getText();
254: // _log.log("getText(" + offset + ", " + len + ") called on '" + text + "' which has " + text.length() + " chars");
255: return text.substring(offset, offset + len);
256: }
257:
258: /** Checks whether the document is resident (in the cache or modified).
259: * @return if the document is resident.
260: */
261: public boolean isReady() {
262: return _doc != null;
263: } // _doc is volatile so synchronization is unnecessary
264:
265: /** Closes the corresponding document for this adapter. Done when a document is closed by the navigator. */
266: public void close() {
267: // Utilities.showDebug("close() called on " + this);
268: synchronized (_cacheLock) {
269: _residentQueue.remove(this );
270: closingKickOut();
271: }
272: }
273:
274: public void documentModified() {
275: synchronized (_cacheLock) {
276: _residentQueue.remove(this ); // remove modified document from queue if present
277: _stat = UNMANAGED;
278: }
279: }
280:
281: public void documentReset() {
282: synchronized (_cacheLock) {
283: if (_stat == UNMANAGED)
284: add(); // add document to queue if it was formerly unmanaged
285: }
286: }
287:
288: /** Updates status of this document in the cache. */
289: public void documentSaved() {
290: // Utilities.showDebug("Document " + _doc + " has been saved");
291: // System.err.println("Document " + _doc + " has been saved");
292: synchronized (_cacheLock) { // lock the document manager so that document manager fields can be updated
293: if (isUnmanagedOrUntitled()) {
294: add(); // add formerly unmanaged/untitled document to queue
295: }
296: }
297: }
298:
299: /** Adds this DocManager to the queue and sets status to IN_QUEUE. Assumes _cacheLock is already held. */
300: private void add() {
301: // Utilities.showDebug("add " + this + " to the QUEUE\n" + "QUEUE = " + _residentQueue);
302: // System.err.println("adding " + this + " to the QUEUE\n" + "QUEUE = " + _residentQueue);
303: if (!_residentQueue.contains(this )) {
304: _residentQueue.add(this );
305: _stat = IN_QUEUE;
306: }
307: if (_residentQueue.size() > CACHE_SIZE)
308: _residentQueue.get(0).remove();
309: }
310:
311: /** Removes this DocManager from the queue and sets status to NOT_IN_QUEUE. Assumes _cacheLock is already held. */
312: private void remove() {
313: boolean removed = _residentQueue.remove(this );
314: kickOut();
315: }
316:
317: /** All of the following private methods presume that _cacheLock is held */
318: private boolean isUnmanagedOrUntitled() {
319: return (_stat & 0x1) != 0;
320: } // tests if _stat is odd
321:
322: /** Called by the cache when the document is removed from the active queue and subject to virtualization.
323: * Assumes cacheLock is already held.
324: */
325: void kickOut() {
326: kickOut(false);
327: }
328:
329: /** Called by the cache when the document is being closed. Note that _doc can be null in this case!
330: * Assumes cacheLock is already held.
331: */
332: void closingKickOut() {
333: kickOut(true);
334: }
335:
336: /** Performs the actual kickOut operation. Assumes cacheLock is already held. */
337: private void kickOut(boolean isClosing) {
338: // Utilities.showDebug("kickOut(" + isClosing + ") called on " + this);
339: if (!isClosing) {
340: /* virtualize this document */
341: // Utilities.showDebug("Virtualizing " + _doc);
342: _rec.saveDocInfo(_doc);
343: }
344: if (_doc != null) {
345: _doc.close();
346: _doc = null;
347: }
348: _stat = NOT_IN_QUEUE;
349: }
350:
351: public String toString() {
352: return "DocManager for " + _rec.toString() + "[stat = "
353: + _stat + "]";
354: }
355: }
356:
357: ////////////////////////////////////////
358:
359: /** This interface allows the unit tests to get a handle on what's going on since the work is spread
360: * between the ODD, the cache, and the Adapters.
361: */
362: public interface RegistrationListener {
363: public void registered(OpenDefinitionsDocument odd,
364: DCacheAdapter man);
365: }
366:
367: private LinkedList<RegistrationListener> _regListeners = new LinkedList<RegistrationListener>();
368:
369: public void addRegistrationListener(RegistrationListener list) {
370: synchronized (_regListeners) {
371: _regListeners.add(list);
372: }
373: }
374:
375: public void removeRegistrationListener(RegistrationListener list) {
376: synchronized (_regListeners) {
377: _regListeners.remove(list);
378: }
379: }
380:
381: public void clearRegistrationListeners() {
382: _regListeners.clear();
383: }
384:
385: // Only used in DocumentCacheTest; must be synchronous for test to succeed.
386: private void notifyRegistrationListeners(
387: final OpenDefinitionsDocument odd, final DocManager man) {
388: synchronized (_regListeners) {
389: if (_regListeners.isEmpty())
390: return;
391: Utilities.invokeAndWait(new Runnable() {
392: public void run() {
393: for (RegistrationListener list : _regListeners) {
394: list.registered(odd, man);
395: }
396: }
397: });
398: }
399: }
400: }
|