Clover coverage report - DrJava Test Coverage (drjava-20120304-r5456)
Coverage timestamp: Sun Mar 4 2012 03:13:23 CST
file stats: LOC: 361   Methods: 32
NCLOC: 174   Classes: 3
 
 Source file Conditionals Statements Methods TOTAL
DocumentCache.java 84.2% 85.3% 81.2% 84.3%
coverage coverage
 1    /*BEGIN_COPYRIGHT_BLOCK
 2    *
 3    * Copyright (c) 2001-2010, JavaPLT group at Rice University (drjava@rice.edu)
 4    * All rights reserved.
 5    *
 6    * Redistribution and use in source and binary forms, with or without
 7    * modification, are permitted provided that the following conditions are met:
 8    * * Redistributions of source code must retain the above copyright
 9    * notice, this list of conditions and the following disclaimer.
 10    * * Redistributions in binary form must reproduce the above copyright
 11    * notice, this list of conditions and the following disclaimer in the
 12    * documentation and/or other materials provided with the distribution.
 13    * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
 14    * names of its contributors may be used to endorse or promote products
 15    * derived from this software without specific prior written permission.
 16    *
 17    * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18    * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 19    * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 20    * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 21    * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 22    * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 23    * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 24    * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 25    * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 26    * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 27    * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 28    *
 29    * This software is Open Source Initiative approved Open Source Software.
 30    * Open Source Initative Approved is a trademark of the Open Source Initiative.
 31    *
 32    * This file is part of DrJava. Download the current version of this project
 33    * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
 34    *
 35    * END_COPYRIGHT_BLOCK*/
 36   
 37    package edu.rice.cs.drjava.model.cache;
 38   
 39    import javax.swing.event.DocumentListener;
 40    import javax.swing.text.BadLocationException;
 41    import java.util.*;
 42    import java.io.IOException;
 43   
 44    import edu.rice.cs.drjava.model.definitions.DefinitionsDocument;
 45    import edu.rice.cs.drjava.model.OpenDefinitionsDocument;
 46    import edu.rice.cs.drjava.model.FileMovedException;
 47   
 48    import edu.rice.cs.util.Log;
 49    import edu.rice.cs.util.UnexpectedException;
 50    import edu.rice.cs.util.swing.Utilities;
 51    import edu.rice.cs.plt.iter.IterUtil;
 52   
 53    /** The document cache is a structure that maps OpenDefinitionsDocuments to DefinitionsDocuments (which contain
 54    * the actual document text). Since the latter can consume a lot of memory, the cache virtualizes some of them
 55    * using DefinitionsDocument reconstructors (DDReconstructor). It tries to limit the number of
 56    * DefinitionsDocuments loaded in memory at one time, but it must of course retain all modified
 57    * DefinitionsDocuments.
 58    * <p>
 59    * The cache creates a DocManager for each OpenDefinitionsDocument entered (registered) in the cache. The managers
 60    * maintain the actual links to DefinitionsDocuments. Since the Managers themselves implement the DCacheAdapter
 61    * interface, the model goes directly to the manager to get the instance of the DefinitionsDocument.
 62    * <p>
 63    * When a document is accessed through the document manager by the model, the cache informs the manager, which
 64    * tells the active queue to add the manager to the end of the queue--if it isn't already in the queue. If the
 65    * active queue had already reached maximum size, it deletes the last document in the queue to keep the queue from
 66    * growing larger than its maximum size.
 67    * <p>
 68    * The resident queue only contains documents that have not been modified since their last save (except in the process
 69    * of responding to notification that a document has been modified). When a document is modified for the first time,
 70    * it is immediately removed from the resident queue and marked as UNMANAGED by its document manager. An
 71    * UNMANAGED document remains in memory until it is saved or closed without being saved. If such a document is
 72    * saved, it is inserted again in the resident queue.
 73    * <p>
 74    * Since the cache and document managers can both be concurrently accessed from multiple threads, the methods in the
 75    * DocumentCache and DocManager classes are synchronized. Some operations require locks on both the cache and a
 76    * document manager, but the code is written so that none of require these locks to be held simultaneously.
 77    */
 78   
 79    public class DocumentCache {
 80   
 81    /** Log file. */
 82    private static final Log _log = new Log("DocumentCache.txt", false);
 83   
 84    private static final int INIT_CACHE_SIZE = 32;
 85   
 86    /** invariant _residentQueue.size() <= CACHE_SIZE */
 87    private int CACHE_SIZE;
 88   
 89    private LinkedHashSet<DocManager> _residentQueue;
 90   
 91    private Object _cacheLock = new Object();
 92   
 93    /* General constructor. Not currently used except when called by default constructor. */
 94  159 public DocumentCache(int size) {
 95    // Utilities.showDebug("DocumentCache created with size = " + size);
 96  159 CACHE_SIZE = size;
 97  159 _residentQueue = new LinkedHashSet<DocManager>();
 98    }
 99   
 100    /* Default constructor; uses default cache size. */
 101  159 public DocumentCache() { this(INIT_CACHE_SIZE); }
 102   
 103    /** Returns a cache adapter corresponding to the owner of the given reconstructor.
 104    * @param odd The open definitions document that is registering. (Useful for debugging purposes.)
 105    * @param rec A reconstructor from which to create the document that is to be managed in this cache
 106    * @return an adapter that allows its owner to access its definitions document
 107    */
 108  502 public DCacheAdapter register(OpenDefinitionsDocument odd, DDReconstructor rec) {
 109  502 DocManager mgr = new DocManager(rec, odd.isUntitled());
 110  502 notifyRegistrationListeners(odd, mgr); // runs synchronously; only used in tests
 111    // System.err.println("register(" + odd + ", " + rec + ") called");
 112  502 return mgr;
 113    }
 114   
 115    /** Changes the number of <b>unmodified</b> documents allowed in the cache at one time. <b> Note: modified documents
 116    * are not managed in the cache except in transitional situations when a queue document becomes modified. Only
 117    * used in tests.
 118    */
 119  13 public void setCacheSize(int size) {
 120  2 if (size <= 0) throw new IllegalArgumentException("Cannot set the cache size to zero or less.");
 121  11 synchronized(_cacheLock) { // lock the cache so entries can be removed if necessary
 122  11 CACHE_SIZE = size;
 123  11 int diff = _residentQueue.size() - CACHE_SIZE;
 124  11 if (diff > 0) {
 125  1 Iterable<DocManager> toRemove = IterUtil.snapshot(IterUtil.truncate(_residentQueue, diff));
 126  2 for (DocManager dm : toRemove) { _residentQueue.remove(dm); dm.kickOut(); }
 127    }
 128    }
 129    }
 130   
 131  6 public int getCacheSize() { return CACHE_SIZE; }
 132  35 public int getNumInCache() { return _residentQueue.size(); }
 133   
 134  0 public String toString() { return _residentQueue.toString(); }
 135   
 136   
 137    ///////////////////////////// DocManager //////////////////////////
 138   
 139    private static final int IN_QUEUE = 0; // In the resident queue and hence subject to virtualization
 140    private static final int UNTITLED = 1; // An untitled document not in queue (may or may not be modified)
 141    private static final int NOT_IN_QUEUE = 2; // Virtualized and not in the QUEUE
 142    private static final int UNMANAGED = 3; // A modified, titled document not in the queue
 143    /** Note: before extending this table, check that the extension does not conflict with isUnmangedOrUntitled() */
 144   
 145    /** Manages the retrieval of a document for a corresponding open definitions document. This manager only
 146    * maintains its document data if it contained in _residentQueue, which is maintained using a round-robin
 147    * replacement scheme.
 148    */
 149    private class DocManager implements DCacheAdapter {
 150   
 151    private final DDReconstructor _rec;
 152    /** Set of keywords if they were updated since the document had been kicked out, or null if not updated. */
 153    private volatile HashSet<String> _keywords = null;
 154   
 155    private volatile int _stat; // I know, this is not very OO
 156    private volatile DefinitionsDocument _doc;
 157   
 158    /** Instantiates a manager for the documents that are produced by the given document reconstructor.
 159    * @param rec The reconstructor used to create the document
 160    */
 161  502 public DocManager(DDReconstructor rec, boolean isUntitled) {
 162    // Utilities.showDebug("DocManager(" + rec + ", " + fn + ", " + isUntitled + ")");
 163  502 _rec = rec;
 164  423 if (isUntitled) _stat = UNTITLED;
 165  79 else _stat = NOT_IN_QUEUE;
 166  502 _doc = null;
 167    // System.err.println(this + " constructed");
 168    }
 169   
 170    /** Adds DocumentListener to the reconstructor. */
 171  126 public void addDocumentListener(DocumentListener l) { _rec.addDocumentListener(l); }
 172   
 173    /** Makes this document; assumes that cacheLock is already held. */
 174  343 private DefinitionsDocument makeDocument() {
 175  343 try { // _doc is not in memory
 176  343 _doc = _rec.make();
 177  343 assert _doc != null;
 178    // update documents if necessary
 179  343 if (_keywords != null) { // copy cached keywords to new copy of doc
 180  270 _doc.setKeywords(_keywords); _keywords.clear(); _keywords = null;
 181    }
 182    }
 183  0 catch(IOException e) { throw new UnexpectedException(e); }
 184  0 catch(BadLocationException e) { throw new UnexpectedException(e); }
 185    // Utilities.showDebug("Document " + _doc + " reconstructed; _stat = " + _stat);
 186    // System.err.println("Making document for " + this);
 187  66 if (_stat == NOT_IN_QUEUE) add(); // add this to queue
 188  343 return _doc;
 189    }
 190   
 191    /** Gets the physical document (DD) for this manager. If DD is not in memory, it loads it from its image in its
 192    * DDReconstructor and returns it. If the document has been modified in memory since it was last fetched, make
 193    * it "unmanaged", removing it from the queue. It will remain in memory until saved. If a document is not in
 194    * the queue, add it.
 195    * @return the physical document that is managed by this adapter
 196    */
 197  5670 public DefinitionsDocument getDocument() throws IOException, FileMovedException {
 198    // Utilities.showDebug("getDocument called on " + this + " with _stat = " + _stat);
 199   
 200    // The following double-check idiom is safe in Java 1.4 and later JVMs provided that _doc is volatile.
 201  5670 final DefinitionsDocument doc = _doc; // create a snapshot of _doc
 202  5327 if (doc != null) return doc;
 203  343 synchronized(_cacheLock) { // lock the cache so that this DocManager's state can be updated
 204  0 if (_doc != null) return _doc; // _doc may have changed since test outside of _cacheLock
 205  343 return makeDocument();
 206    }
 207    }
 208   
 209    /** Gets the length of this document using (i) cached _doc or (ii) reconstructor (which may force the document
 210    * to be loaded. */
 211  1784 public int getLength() {
 212  1784 final DefinitionsDocument doc = _doc; // create a snapshot of _doc
 213  203 if (doc == null /* || ! doc.isModifiedSinceSave()*/) return _rec.getText().length();
 214  1581 return doc.getLength();
 215    }
 216   
 217    /** Gets the text of this document using the cached reconstructor if document is not resident or it is unchanged.
 218    * If document is not locked, may return stale data. */
 219  224 public String getText() {
 220  224 final DefinitionsDocument doc = _doc; // create a snapshot of _doc
 221  4 if (doc == null /* || ! doc.isModifiedSinceSave() */) return _rec.getText();
 222    // if (doc == null) return _rec.getText();
 223  220 return doc.getText();
 224    }
 225   
 226    /* Gets the specified substring of this document; throws BadLocationException if the specification is ill-formed. */
 227  657 public String getText(int offset, int len) throws BadLocationException {
 228  657 final DefinitionsDocument doc = _doc; // create a snapshot of _doc
 229  657 if (doc == null) {
 230  0 try { return _rec.getText().substring(offset, offset + len); }
 231  0 catch(IndexOutOfBoundsException e) { throw new BadLocationException(e.getMessage(), offset); }
 232    }
 233    // _log.log("getText(" + offset + ", " + len + ") called on '" + text + "' which has " + text.length() + " chars");
 234  657 return doc.getText(offset, len);
 235    }
 236   
 237    /** Checks whether the document is resident (in the cache or modified).
 238    * @return if the document is resident.
 239    */
 240  4708 public boolean isReady() { return _doc != null; } // _doc is volatile so synchronization is unnecessary
 241   
 242    /** Closes the corresponding document for this adapter. Done when a document is closed by the navigator. */
 243  185 public void close() {
 244    // Utilities.showDebug("close() called on " + this);
 245  185 synchronized(_cacheLock) {
 246  185 _residentQueue.remove(this);
 247  185 closingKickOut();
 248    }
 249    }
 250   
 251  218 public void documentModified() {
 252  218 synchronized(_cacheLock) {
 253  218 _residentQueue.remove(this); // remove modified document from queue if present
 254  218 _stat = UNMANAGED;
 255    }
 256    }
 257   
 258  431 public void documentReset() {
 259  431 synchronized(_cacheLock) {
 260  133 if (_stat == UNMANAGED) add(); // add document to queue if it was formerly unmanaged
 261    }
 262    }
 263   
 264    /** Updates status of this document in the cache. */
 265  0 public void documentSaved() {
 266    // Utilities.showDebug("Document " + _doc + " has been saved");
 267    // System.err.println("Document " + _doc + " has been saved");
 268  0 synchronized(_cacheLock) { // lock the document manager so that document manager fields can be updated
 269  0 if (isUnmanagedOrUntitled()) {
 270  0 add(); // add formerly unmanaged/untitled document to queue
 271    }
 272    }
 273    }
 274   
 275    /** Adds this DocManager to the queue and sets status to IN_QUEUE. Assumes _cacheLock is already held. */
 276  199 private void add() {
 277    // Utilities.showDebug("add " + this + " to the QUEUE\n" + "QUEUE = " + _residentQueue);
 278    // System.err.println("adding " + this + " to the QUEUE\n" + "QUEUE = " + _residentQueue);
 279  199 if (! _residentQueue.contains(this)) {
 280  199 _residentQueue.add(this);
 281  199 _stat = IN_QUEUE;
 282    }
 283  27 if (_residentQueue.size() > CACHE_SIZE) IterUtil.first(_residentQueue).remove();
 284    }
 285   
 286    /** Removes this DocManager from the queue and sets status to NOT_IN_QUEUE. Assumes _cacheLock is already held. */
 287  27 private void remove() {
 288  27 _residentQueue.remove(this);
 289  27 kickOut();
 290    }
 291   
 292    /** All of the following private methods presume that _cacheLock is held */
 293  0 private boolean isUnmanagedOrUntitled() { return (_stat & 0x1) != 0; } // tests if _stat is odd
 294   
 295    /** Called by the cache when the document is removed from the active queue and subject to virtualization.
 296    * Assumes cacheLock is already held.
 297    */
 298  29 void kickOut() { kickOut(false); }
 299   
 300    /** Called by the cache when the document is being closed. Note that _doc can be null in this case!
 301    * Assumes cacheLock is already held.
 302    */
 303  185 void closingKickOut() { kickOut(true); }
 304   
 305    /** Performs the actual kickOut operation. Assumes cacheLock is already held. */
 306  214 private void kickOut(boolean isClosing) {
 307    // Utilities.showDebug("kickOut(" + isClosing + ") called on " + this);
 308  214 if (! isClosing) {
 309    /* virtualize this document */
 310    // Utilities.showDebug("Virtualizing " + _doc);
 311  29 _rec.saveDocInfo(_doc);
 312    }
 313  214 if (_doc != null) {
 314  117 _doc.close();
 315  117 _doc = null;
 316    }
 317  214 _stat = NOT_IN_QUEUE;
 318    }
 319   
 320  0 public String toString() { return "DocManager for " + _rec.toString() + "[stat = " + _stat + "]"; }
 321   
 322    /** Set the specified keywords as keywords for syntax highlighting.
 323    * @param keywords keywords to highlight */
 324  477 public void setKeywords(Set<String> keywords) {
 325  477 synchronized(_cacheLock) {
 326  477 if (_doc != null) {
 327    // resident
 328  89 _doc.setKeywords(keywords);
 329    }
 330    else {
 331    // virtualized
 332  388 _keywords = new HashSet<String>(keywords);
 333    }
 334    }
 335    }
 336    }
 337   
 338    ////////////////////////////////////////
 339   
 340    /** This interface allows the unit tests to get a handle on what's going on since the work is spread
 341    * between the ODD, the cache, and the Adapters.
 342    */
 343    public interface RegistrationListener {
 344    public void registered(OpenDefinitionsDocument odd, DCacheAdapter man);
 345    }
 346   
 347    private LinkedList<RegistrationListener> _regListeners = new LinkedList<RegistrationListener>();
 348   
 349  7 public void addRegistrationListener(RegistrationListener list) { synchronized(_regListeners) { _regListeners.add(list); } }
 350  0 public void removeRegistrationListener(RegistrationListener list) { synchronized(_regListeners) { _regListeners.remove(list); } }
 351  0 public void clearRegistrationListeners() { _regListeners.clear(); }
 352    // Only used in DocumentCacheTest; must be synchronous for test to succeed.
 353  502 private void notifyRegistrationListeners(final OpenDefinitionsDocument odd, final DocManager man) {
 354  502 synchronized(_regListeners) {
 355  473 if (_regListeners.isEmpty()) return;
 356  29 Utilities.invokeAndWait(new Runnable() {
 357  29 public void run() { for (RegistrationListener list : _regListeners) { list.registered(odd, man); } }
 358    });
 359    }
 360    }
 361    }