Clover coverage report - DrJava Test Coverage (drjava-20110828-r5448)
Coverage timestamp: Sun Aug 28 2011 03:13:33 CDT
file stats: LOC: 440   Methods: 21
NCLOC: 188   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
ConcreteRegionManager.java 35.9% 48.9% 61.9% 46.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    package edu.rice.cs.drjava.model;
 37   
 38    import java.awt.EventQueue;
 39    import java.io.File;
 40    import java.util.ArrayList;
 41    import java.util.Collection;
 42    import java.util.Set;
 43    import java.util.HashMap;
 44    import java.util.HashSet;
 45    import java.util.Iterator;
 46    import java.util.LinkedList;
 47    import java.util.List;
 48    import java.util.SortedSet;
 49    import java.util.TreeSet;
 50   
 51    import edu.rice.cs.plt.lambda.Lambda;
 52    import edu.rice.cs.plt.tuple.Pair;
 53    import edu.rice.cs.util.swing.Utilities;
 54   
 55    /** Simple region manager for the entire model. Follows readers/writers locking protocol of EventNotifier.
 56    * TODO: fix the typing of regions. In many (all?) places, R should be OrderedDocumentRegion.
 57    */
 58    public class ConcreteRegionManager<R extends OrderedDocumentRegion> extends EventNotifier<RegionManagerListener<R>> implements
 59    RegionManager<R> {
 60   
 61    /** Hashtable mapping documents to collections of regions. Primitive operations are thread safe. */
 62    private volatile HashMap<OpenDefinitionsDocument, SortedSet<R>> _regions =
 63    new HashMap<OpenDefinitionsDocument, SortedSet<R>>();
 64   
 65    /** The domain of the _regions. This field can be extracted from _regions so it is provided to improve performance
 66    * Primitive operations are thread-safe.
 67    */
 68    private volatile Set<OpenDefinitionsDocument> _documents = new HashSet<OpenDefinitionsDocument>();
 69   
 70    /* Depending on default constructor */
 71   
 72    /** @return the set of documents containing regions. */
 73  7 public Set<OpenDefinitionsDocument> getDocuments() { return _documents; }
 74   
 75    private static final SortedSet<Object> EMPTY_SET = new TreeSet<Object>();
 76   
 77    /* Convinces the type checker to accept EMPTY_SET as a set of R. */
 78  23 @SuppressWarnings("unchecked")
 79  23 private <T> T emptySet() { return (T) EMPTY_SET; }
 80   
 81    /** Convinces the type checker to accept a DocumentRegion as an R. This works when you need an R object only for use
 82    * with compareTo because all implementations of OrderedDocumentRegion inherit from DocumentRegion and compareTo is
 83    * defined in DocumentRegion.
 84    */
 85  67 @SuppressWarnings("unchecked")
 86    private <T> T newDocumentRegion(OpenDefinitionsDocument odd, int start, int end) {
 87  67 return (T) new DocumentRegion(odd, start, end);
 88    }
 89   
 90    /** Gets the sorted set of regions less than r. */
 91  0 public SortedSet<R> getHeadSet(R r) {
 92  0 SortedSet<R> oddRegions = _regions.get(r.getDocument());
 93  0 if (oddRegions == null || oddRegions.isEmpty()) return emptySet();
 94  0 return oddRegions.headSet(r);
 95    }
 96   
 97    /** Gets the sorted set of regions greater than or equal to r. */
 98  67 public SortedSet<R> getTailSet(R r) {
 99  67 SortedSet<R> oddRegions = _regions.get(r.getDocument());
 100  23 if (oddRegions == null || oddRegions.isEmpty()) return emptySet();
 101  44 return oddRegions.tailSet(r);
 102    }
 103   
 104    // private static <R extends OrderedDocumentRegion> SortedSet<R> reverse(SortedSet<R> inputSet) {
 105    // if (inputSet.isEmpty()) return inputSet;
 106    // /* Create outputSet with reverse ordering. */
 107    // SortedSet<R> outputSet = new TreeSet<R>(new Comparator<OrderedDocumentRegion>() {
 108    // public int compare(OrderedDocumentRegion o1, OrderedDocumentRegion o2) { return - o1.compareTo(o2); }
 109    // });
 110    // for (R r: inputSet) outputSet.add(r);
 111    // return outputSet;
 112    // }
 113   
 114    /** Returns the region [start, end) containing offset. Since regions can never overlap, there is at most one such
 115    * region in the given document. (Degenerate regions can coalesce but they are empty implying that
 116    * they are never returned by this method.) Only runs in the event thread.
 117    * @param odd the document
 118    * @param offset the offset in the document
 119    * @return the DocumentRegion at the given offset, or null if it does not exist.
 120    */
 121  32 public R getRegionAt(OpenDefinitionsDocument odd, int offset) {
 122  32 /* */ assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 123   
 124    /* Get the tailSet consisting of the ordered set of regions [start, end) such that end > offset. */
 125  32 @SuppressWarnings("unchecked")
 126    SortedSet<R> tail = getTailSet((R) newDocumentRegion(odd, 0, offset + 1));
 127   
 128    /* If tail contains a match, it must be the first region, since all regions in a document are disjoint and no
 129    * degenerate region in tail can contain offset. (Every degenerate region is disjoint from every other region
 130    * because it is empty.) tail is sorted by [endOffset, startOffset]; tail may be empty. */
 131   
 132  24 if (tail.size() == 0) return null;
 133  8 R r = tail.first();
 134   
 135  6 if (r.getStartOffset() <= offset) return r;
 136  2 else return null;
 137    }
 138   
 139    /** Finds the interval of regions in odd such that the line label (excerpt) for the region contains offset. */
 140  0 public Pair<R, R> getRegionInterval(OpenDefinitionsDocument odd, int offset) {
 141  0 /* */ assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 142   
 143    // System.err.println("getRegionsNear(" + odd + ", " + offset + ") called");
 144   
 145    /* Get the interval of regions whose line label (excerpts) contain offset. The maximium size of the excerpt
 146    * enclosing a region is 120 characters; it begins at the start of the line containing the start of the region.
 147    * Since empty regions are ignore (and deleted as soon as they are found), the end of a containing region must be
 148    * less than 120 characters from offset. Find the tail set of all regions [start, end) where offset - 120 < end.
 149    */
 150  0 @SuppressWarnings("unchecked")
 151    SortedSet<R> tail = getTailSet((R) new DocumentRegion(odd, 0, offset - 119));
 152   
 153    /* Search tail, selecting first and last regions r such that r.getLineEnd() >= offset and r.getLineStart <= offset.
 154    * The tail is totally order on BOTH getLineStart() and getLineEnd() because the functions mapping start to lineStart
 155    * and end to lineEnd are monotonic and the tail is totally ordered on BOTH start and end (since regions do not
 156    * overlap). Hence, we can abandon the search as soon as we reach a region r such that r.getLineStart() > offset.
 157    * tail may be empty. */
 158   
 159  0 if (tail.size() == 0) return null;
 160   
 161    // Find the first and last regions whose bounds (using line boundaries) contain offset
 162  0 Iterator<R> it = tail.iterator();
 163  0 R first = null;
 164  0 R last = null;
 165   
 166    // Find first
 167  0 while (it.hasNext()) {
 168  0 R r = it.next();
 169    // System.err.println("Testing region '" + r.getString() + "'");
 170    /* Note: r may span more than one line. */
 171  0 int lineStart = r.getLineStartOffset();
 172    // System.err.println("lineStart = " + lineStart + " offset = " + offset);
 173  0 if (lineStart > offset) break; // first == null implying test following loop will return
 174  0 int lineEnd = r.getLineEndOffset();
 175    // System.err.println("lineEnd = " + lineEnd);
 176  0 if (lineStart - 1 <= offset && lineEnd >= offset) { // - 1 required to handle inserting wing comment chars
 177  0 first = r;
 178    // System.err.println("Found first region in getRegionInterval = '" + r.getString() + "'");
 179  0 break;
 180    }
 181    }
 182  0 if (first == null) return null;
 183   
 184    // Find last
 185  0 last = first;
 186  0 while (it.hasNext()) {
 187  0 R r = it.next();
 188  0 int lineStart = r.getLineStartOffset();
 189  0 if (lineStart > offset) break;
 190  0 int lineEnd = r.getLineEndOffset();
 191  0 if (lineStart <= offset && lineEnd >= offset) {
 192  0 last = r;
 193    }
 194    }
 195    // System.err.println("Found last region in getRegionInterval = '" + last + "'");
 196  0 return new Pair<R, R>(first, last);
 197    }
 198   
 199    // /** Returns the rightmost region in the given document that contains [startOffset, endOffset], or null if one does
 200    // * not exist. Only executes in the event thread. Otherwise offset args could be invalid.
 201    // * Note: this method could be implemented more cleanly using a revserseIterator on the headSet containing all
 202    // * regions preceding or equal to the selection. but this functionality was not added to TreeSet until Java 6.0.
 203    // * @param odd the document
 204    // * @param offset the offset in the document
 205    // * @return the DocumentRegion at the given offset, or null if it does not exist.
 206    // */
 207    // public Collection<R> getOverlappingRegions(OpenDefinitionsDocument odd, int startOffset, int endOffset) {
 208    //
 209    // assert EventQueue.isDispatchThread();
 210    //
 211    // /* First try finding the rightmost region on the same line containing the selection. Unnecessary in Java 6.0. */
 212    // int lineStart = odd._getLineStartPos(startOffset);
 213    //
 214    // @SuppressWarnings("unchecked")
 215    // SortedSet<R> tail = getTailSet((R) newDocumentRegion(odd, lineStart, endOffset));
 216    // // tail is sorted by <startOffset, endOffset>; tail may be empty
 217    // R match = null;
 218    // for (R r: tail) {
 219    // if (r.getStartOffset() <= startOffset) {
 220    // if (r.getEndOffset() >= endOffset) match = r;
 221    // }
 222    // else break; // for all remaining r : R (r.getStartOffset() > offset)
 223    // }
 224    // if (match != null) return match;
 225    //
 226    // /* No match found starting on same line; look for best match starting on preceding lines. */
 227    // @SuppressWarnings("unchecked")
 228    // SortedSet<R> revHead = reverse(getHeadSet((R) newDocumentRegion(odd, lineStart, lineStart))); // linear cost! Ugh!
 229    //
 230    // /* Find first match in revHead */
 231    // Iterator<R> it = revHead.iterator(); // In Java 6.0, it is computable in constant time from headSet using reverseIterator
 232    //
 233    // R next;
 234    // while (it.hasNext()) {
 235    // next = it.next();
 236    // if (next.getEndOffset() >= endOffset) { match = next; break; }
 237    // }
 238    //
 239    // if (match == null) return null; // no match found
 240    //
 241    // /* Try to improve the match by narrowing endOffset. */
 242    // while (it.hasNext()) {
 243    // next = it.next();
 244    // if (next.getStartOffset() < match.getStartOffset()) return match; // no more improvement possible
 245    // assert next.getStartOffset() == match.getStartOffset();
 246    // if (next.getEndOffset() >= endOffset) match = next; // improvement because next precedes match in getRegions(odd)
 247    // }
 248    //
 249    // return match; // last region in revHead was the best match
 250    // }
 251   
 252    /** Returns the set of regions in the given document that overlap the specified interval [startOffset, endOffset),
 253    * including degenerate regions [offset, offset) where [offset, offset] is a subset of (startOffset, endOffset).
 254    * Assumes that all regions in the document are disjoint. Note: degenerate empty regions with form [offset, offset)
 255    * vacuously satisfy this property. Only executes in the event thread.
 256    * Note: this method could be implemented more cleanly using a revserseIterator on the headSet containing all
 257    * regions preceding or equal to the selection. but this functionality was not added to TreeSet until Java 6.0.
 258    * @param odd the document
 259    * @param startOffset the left end of the specified interval
 260    * @param endOffset the right end of the specified interval
 261    * @return the Collection<DocumentRegion> of regions overlapping the interval.
 262    */
 263  39 public Collection<R> getRegionsOverlapping(OpenDefinitionsDocument odd, int startOffset, int endOffset) {
 264   
 265  39 /* */ assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 266  39 LinkedList<R> result = new LinkedList<R>();
 267  4 if (startOffset == endOffset) return result;
 268   
 269    /* Find all regions with an endPoint greater than startOffset. */
 270   
 271  35 @SuppressWarnings("unchecked")
 272    SortedSet<R> tail = getTailSet((R) newDocumentRegion(odd, 0, startOffset + 1));
 273   
 274    // tail is sorted by <startOffset, endOffset>; tail may be empty
 275   
 276  35 for (R r: tail) {
 277  3 if (r.getStartOffset() >= endOffset) break;
 278  19 else result.add(r);
 279    }
 280  35 return result;
 281    }
 282   
 283    /** Add the supplied DocumentRegion to the manager. Only runs in event thread after initialization?
 284    * @param region the DocumentRegion to be inserted into the manager
 285    */
 286  7 public void addRegion(final R region) {
 287  7 final OpenDefinitionsDocument odd = region.getDocument();
 288  7 SortedSet<R> docRegions = _regions.get(odd);
 289  7 if (docRegions == null) { // if necessary create a Hashtable entry for odd and insert it in the _documents set
 290  4 _documents.add(odd);
 291  4 docRegions = new TreeSet<R>();
 292  4 _regions.put(odd, docRegions);
 293    }
 294   
 295    // Check for duplicate region
 296  7 final boolean alreadyPresent = docRegions.contains(region);
 297  7 if (! alreadyPresent) { // region does not already exist in manager
 298  7 docRegions.add(region); // modifies docRegions, which is part of _regions
 299    }
 300   
 301  7 assert _documents.contains(odd);
 302   
 303    // only notify if the region was actually added
 304  7 if (! alreadyPresent) {
 305    // notify. invokeLater unnecessary if it only runs in the event thread
 306  7 _lock.startRead();
 307  5 try { for (RegionManagerListener<R> l: _listeners) { l.regionAdded(region); } }
 308  7 finally { _lock.endRead(); }
 309    }
 310    }
 311   
 312    /** Remove the given IDocumentRegion from the manager. If any document's regions are emptied, remove the document
 313    * from the keys in _regions. Notification removes the panel node for the region.
 314    * @param region the IDocumentRegion to be removed.
 315    */
 316  7 public void removeRegion(final R region) {
 317    // System.err.println("ConcreteRegionManager.removeRegion(" + region + ") called");
 318  7 OpenDefinitionsDocument doc = region.getDocument();
 319  7 SortedSet<R> docRegions = _regions.get(doc);
 320    // System.err.println("doc regions for " + doc + " = " + docRegions);
 321  0 if (docRegions == null) return; // since region is not stored in this region manager, exit!
 322  7 final boolean wasRemoved = docRegions.remove(region); // remove the region from the manager
 323  7 if (docRegions.isEmpty()) {
 324  4 _documents.remove(doc);
 325  4 _regions.remove(doc);
 326    }
 327   
 328    // only notify if the region was actually removed
 329  7 if (wasRemoved) _notifyRegionRemoved(region);
 330    }
 331   
 332    /** Invoke {@link #removeRegion} on all of the given regions. */
 333  0 public void removeRegions(Iterable<? extends R> regions) {
 334  0 for (R r: regions) removeRegion(r);
 335    }
 336   
 337  7 private void _notifyRegionRemoved(final R region) {
 338  7 _lock.startRead();
 339  5 try { for (RegionManagerListener<R> l: _listeners) { l.regionRemoved(region); } }
 340  7 finally { _lock.endRead(); }
 341    }
 342   
 343    /** Remove the specified document from _documents and _regions (removing all of its contained regions). */
 344  680 public void removeRegions(final OpenDefinitionsDocument doc) {
 345  680 assert doc != null;
 346    // System.err.println("Removing regions from ODD " + doc + " in " + this);
 347    // System.err.println("_documents = " + _documents);
 348  680 boolean found = _documents.remove(doc);
 349    // System.err.println("ODD " + doc + " exists in " + this);
 350  680 if (found) {
 351  0 final SortedSet<R> regions = _regions.get(doc);
 352    // System.err.println("Before removal, regions = " + regions);
 353    // The following ugly loop is dictated by the "fail fast" semantics of Java iterators
 354  0 while (! regions.isEmpty()) {
 355  0 R r = regions.first();
 356  0 regions.remove(r);
 357  0 _notifyRegionRemoved(r);
 358    }
 359    // System.err.println("After removal, regions = " + regions);
 360    }
 361    }
 362   
 363    /** @return a Vector<R> containing the DocumentRegion objects for document odd in this mangager. */
 364  0 public SortedSet<R> getRegions(OpenDefinitionsDocument odd) { return _regions.get(odd); }
 365   
 366  76 public int getRegionCount() {
 367  76 int regions = 0;
 368  0 for (OpenDefinitionsDocument odd: _documents) regions += _regions.get(odd).size();
 369  76 return regions;
 370    }
 371   
 372  9 public ArrayList<R> getRegions() {
 373  9 ArrayList<R> regions = new ArrayList<R>();
 374  5 for (OpenDefinitionsDocument odd: _documents) regions.addAll(_regions.get(odd));
 375  9 return regions;
 376    }
 377   
 378  1 public ArrayList<FileRegion> getFileRegions() {
 379  1 ArrayList<FileRegion> regions = new ArrayList<FileRegion>();
 380  1 for (OpenDefinitionsDocument odd: _documents) {
 381  0 File f = odd.getRawFile();
 382  0 for (R r: _regions.get(odd)) regions.add(new DummyDocumentRegion(f, r.getStartOffset(), r.getEndOffset()));
 383    }
 384  1 return regions;
 385    }
 386   
 387   
 388  0 public boolean contains(R region) {
 389  0 for (OpenDefinitionsDocument doc: _documents) {
 390  0 if (_regions.get(doc).contains(region)) return true;
 391    }
 392  0 return false;
 393    }
 394   
 395    /** Tells the manager to remove all regions. */
 396  0 public void clearRegions() {
 397  0 for (R r: getRegions()) removeRegion(r);
 398    // final ArrayList<R> regions = getRegions();
 399    //// System.err.println("ConcreteRegionManager.clearRegions() called with regions = " + regions);
 400    // // Notify all listeners for this manager that all regions are being removed; listener access _regions and _documents
 401    // _notifyRegionsRemoved(regions); // fails to close the associated panel because _documents not yet cleared.
 402    // // Remove all regions in this manager
 403    // _regions.clear();
 404    // _documents.clear();
 405    }
 406   
 407    // /** Set the current region.
 408    // * @param region new current region */
 409    // public void setCurrentRegion(final R region) { throw new UnsupportedOperationException(); }
 410   
 411    /** Apply the given command to the specified region to change it.
 412    * @param region the region to find and change
 413    * @param cmd command that mutates the region. */
 414  0 public void changeRegion(final R region, Lambda<R,Object> cmd) {
 415  0 cmd.value(region);
 416    // notify
 417  0 _lock.startRead();
 418  0 try { for (RegionManagerListener<R> l: _listeners) { l.regionChanged(region); } }
 419  0 finally { _lock.endRead(); }
 420    }
 421   
 422    /** Updates _lineStartPos, _lineEndPos of regions in the interval [firstRegion, lastRegion] using total ordering on
 423    * regions. Removes empty regions. firstRegion and lastRegion are not necessarily regions in this manager.
 424    */
 425  0 public void updateLines(R firstRegion, R lastRegion) {
 426  0 assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 427   
 428    /* Get the tailSet consisting of the ordered set of regions >= firstRegion. */
 429  0 SortedSet<R> tail = getTailSet(firstRegion);
 430  0 if (tail.size() == 0) return; // tail can be empty if firstRegion is a constructed DocumentRegion
 431   
 432  0 List<R> toBeRemoved = new ArrayList<R>(); // nonsense to avoid concurrent modification exception
 433  0 for (R region: tail) {
 434  0 if (region.compareTo(lastRegion) > 0) break;
 435  0 region.update(); // The bounds of this region must be recomputed.
 436  0 if (region.getStartOffset() == region.getEndOffset()) toBeRemoved.add(region);
 437    }
 438  0 removeRegions(toBeRemoved);
 439    }
 440    }