|
|||||||||||||||||||
| Source file | Conditionals | Statements | Methods | TOTAL | |||||||||||||||
| DocumentCache.java | 84.2% | 85.3% | 81.2% | 84.3% |
|
||||||||||||||
| 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 | } |
|
||||||||||