Clover coverage report - DrJava Test Coverage (drjava-20110828-r5448)
Coverage timestamp: Sun Aug 28 2011 03:13:33 CDT
file stats: LOC: 462   Methods: 22
NCLOC: 256   Classes: 2
 
 Source file Conditionals Statements Methods TOTAL
CompilerErrorModel.java 76.2% 80.8% 72.7% 78.8%
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.compiler;
 38   
 39    import java.io.File;
 40    import java.io.IOException;
 41    import javax.swing.text.*;
 42    import java.util.Arrays;
 43    import java.util.List;
 44    import java.util.LinkedList;
 45    import java.util.HashMap;
 46    import java.util.HashSet;
 47   
 48    import edu.rice.cs.drjava.model.DJError;
 49    import edu.rice.cs.drjava.model.DummyGlobalModel;
 50    import edu.rice.cs.drjava.model.GlobalModel;
 51    import edu.rice.cs.drjava.model.OpenDefinitionsDocument;
 52    import edu.rice.cs.drjava.model.FileMovedException;
 53    import edu.rice.cs.drjava.model.DrJavaFileUtils;
 54    import edu.rice.cs.util.FileOps;
 55    import edu.rice.cs.util.OperationCanceledException;
 56    import edu.rice.cs.util.StringOps;
 57    import edu.rice.cs.util.UnexpectedException;
 58    import edu.rice.cs.util.swing.Utilities;
 59   
 60    /** Contains the CompilerErrors for a set of compiled file after a compile has ended.
 61    * @version $Id: CompilerErrorModel.java 5439 2011-08-11 17:13:04Z rcartwright $
 62    */
 63    public class CompilerErrorModel {
 64    private static final String newLine = StringOps.EOL;
 65    /** An array of errors to be displayed in the CompilerErrorPanel associated with this model. After model
 66    * construction, this array should be sorted in this order:
 67    * (i) Errors with no file.
 68    * (ii) Errors for each file in path-alphabetical order.
 69    * Within each file:
 70    * (i) Errors with no line number.
 71    * (ii) Errors with line numbers, in order.
 72    * In all cases, where all else is equal, warnings are sorted below errors.
 73    */
 74    private final DJError[] _errors;
 75   
 76    /** An array of file offsets, parallel to the _errors array. NOTE: If there is no position associated with an error,
 77    * its entry here should be set to null.
 78    */
 79    private final Position[] _positions;
 80   
 81    /** The size of _errors and _positions. This should never change after model construction*/
 82    private final int _numErrors;
 83   
 84    /** The number of compile errors. Used for display purposes only.*/
 85    private volatile int _numCompilerErrors;
 86   
 87    /** The number of compile warnings. Used for display purposes only.*/
 88    private volatile int _numWarnings;
 89   
 90    /** Cached result of hasOnlyWarnings.
 91    * Three-state enum:
 92    * -1 => result has not been computed
 93    * 0 => false
 94    * 1 => true
 95    */
 96    private volatile int _onlyWarnings = -1;
 97   
 98    /** Used internally in building _positions. The file used as the index *must* be a canonical file, or else
 99    * errors won't always be associated with the right documents.
 100    */
 101    private final HashMap<File, StartAndEndIndex> _filesToIndexes = new HashMap<File, StartAndEndIndex>();
 102   
 103    /** The global model which created/controls this object. */
 104    private final GlobalModel _model;
 105   
 106    /** Constructs an empty CompilerErrorModel with no errors and a dummy global model. As a side effect, it
 107    * opens any documents containing errors/warnings that are not currently open. */
 108  2676 public CompilerErrorModel() {
 109  2676 _model = new DummyGlobalModel() {
 110  0 public OpenDefinitionsDocument getDocumentForFile(File file) {
 111  0 throw new IllegalStateException("No documents to get!");
 112    }
 113  0 public boolean isAlreadyOpen(File file) { return false; }
 114  0 public List<OpenDefinitionsDocument> getOpenDefinitionsDocuments() {
 115  0 return new LinkedList<OpenDefinitionsDocument>();
 116    }
 117  0 public boolean hasModifiedDocuments() { return false; }
 118  0 public boolean hasUntitledDocuments() { return false; }
 119    };
 120  2676 _errors = new DJError[0];
 121  2676 _numErrors = 0;
 122  2676 _numWarnings = 0;
 123  2676 _numCompilerErrors = 0;
 124  2676 _positions = new Position[0];
 125    }
 126   
 127    /** Constructs a new CompilerErrorModel with specified global model. Performed in DefaultGlobalModel construction
 128    * and after compilation has been performed.
 129    * @param errors the list of DJError's (or a subclass).
 130    * @param model is the model to find documents from
 131    */
 132  3255 public CompilerErrorModel(DJError[] errors, GlobalModel model) {
 133   
 134    // System.err.println("Constructing CompilerErrorModel for errors: " + Arrays.toString(errors));
 135  3255 _model = model;
 136   
 137    // legacy support for old .dj2 language level files:
 138    // see DrJava feature request 2990660
 139    // As of revisions 5225-5227, .dj2 files aren't converted by the LanguageLevelConverter anymore,
 140    // they are just copied. That means the compiler errors now happen in the .java file, not in the
 141    // .dj2 file anymore. When we get a compiler error in a .java file, and we have a corresponding
 142    // .dj2 file open, but not the .java file, then we change the error to refer to the .dj2 file
 143    // instead.
 144  3255 if (model!=null) {
 145  3236 HashSet<File> odds = new HashSet<File>();
 146  3236 for(OpenDefinitionsDocument odd: model.getOpenDefinitionsDocuments()) {
 147  3352 odds.add(odd.getRawFile());
 148    }
 149  3236 for(int i=0; i<errors.length; ++i) {
 150  2952 DJError e = errors[i];
 151  2952 if (e.fileName().endsWith(edu.rice.cs.drjava.config.OptionConstants.JAVA_FILE_EXTENSION)) {
 152    // only needs to be done for .java files
 153  26 File javaFile = e.file();
 154  26 if (!odds.contains(javaFile)) {
 155    // .java file is not open
 156  1 File dj2File = DrJavaFileUtils.getDJ2ForJavaFile(javaFile);
 157  1 if (odds.contains(dj2File)) {
 158    // but corresponding .dj2 file is open, change error to refer to .dj2 file
 159  0 errors[i] = new DJError(dj2File, e.lineNumber(), e.startColumn(), e.message(), e.isWarning());
 160    }
 161    }
 162    }
 163    }
 164    }
 165   
 166   
 167    // TODO: If we move to NextGen-style generics, ensure _errors is non-null.
 168  3255 _errors = errors;
 169   
 170  3255 _numErrors = errors.length;
 171  3255 _positions = new Position[errors.length];
 172   
 173  3255 _numWarnings = 0;
 174  3255 _numCompilerErrors = 0;
 175  3255 for (int i =0; i < errors.length; i++) {
 176  43 if (errors[i].isWarning()) _numWarnings++;
 177  2909 else _numCompilerErrors++;
 178    }
 179   
 180    // Sort the errors by file and position
 181  3255 Arrays.sort(_errors);
 182   
 183    // Populates _positions. Must run in event thread because it may open files.
 184  3255 Utilities.invokeLater(new Runnable() { public void run() { _calculatePositions(); } });
 185    }
 186   
 187    /** Accessor for errors maintained here.
 188    * @param idx the index of the error to retrieve
 189    * @return the error at index idx
 190    * @throws NullPointerException if this object was improperly initialized
 191    * @throws ArrayIndexOutOfBoundsException if !(0 <= idx < this.getNumErrors())
 192    */
 193  62 public DJError getError(int idx) { return _errors[idx]; }
 194   
 195    /** Returns the position of the given error in the document representing its file. */
 196  9 public Position getPosition(DJError error) {
 197  9 int spot = Arrays.binarySearch(_errors, error);
 198  9 return _positions[spot];
 199    }
 200   
 201    /** Returns the number of CompilerErrors. */
 202  272 public int getNumErrors() { return _numErrors; }
 203   
 204    /** Returns the number of CompilerErrors that are compiler errors */
 205  70 public int getNumCompErrors() { return _numCompilerErrors; }
 206   
 207    /** Returns the number of CompilerErrors that are warnings */
 208  13 public int getNumWarnings() { return _numWarnings; }
 209   
 210    /** Prints out this model's errors. */
 211  0 public String toString() {
 212  0 final StringBuilder buf = new StringBuilder();
 213  0 buf.append(this.getClass().toString() + ":\n ");
 214  0 for (int i = 0; i < _numErrors; i++) {
 215  0 buf.append(_errors[i].toString());
 216  0 buf.append("\n ");
 217    }
 218  0 return buf.toString();
 219    }
 220   
 221    /** This method finds and returns the error that is at the given offset
 222    * @param odd the OpenDefinitionsDocument where you want to find the error at the caret
 223    * @param offset the offset into the document
 224    * @return the DJError at the given offset, null if no error corresponds to this location
 225    */
 226  10 public DJError getErrorAtOffset(OpenDefinitionsDocument odd, int offset) {
 227  10 File file;
 228  10 try {
 229  10 file = odd.getFile();
 230  0 if (file == null) return null;
 231    }
 232  0 catch (FileMovedException e) { file = e.getFile(); }
 233   
 234    // Use the canonical file if possible
 235  10 try { file = file.getCanonicalFile(); }
 236    catch (IOException ioe) {
 237    // Oh well, we'll look for it as is.
 238    }
 239   
 240  10 StartAndEndIndex saei = _filesToIndexes.get(file);
 241  0 if (saei == null) return null;
 242  10 int start = saei.getStartPos();
 243  10 int end = saei.getEndPos();
 244  0 if (start == end) return null;
 245   
 246    // check if the dot is on a line with an error.
 247    // Find the first error that is on or after the dot. If this comes
 248    // before the newline after the dot, it's on the same line.
 249  10 int errorAfter; // index of the first error after the dot
 250  10 for (errorAfter = start; errorAfter < end; errorAfter++) {
 251  11 if (_positions[errorAfter] == null) {
 252    //This indicates something wrong, but it was happening before so...
 253  0 return null;
 254    }
 255  8 if (_positions[errorAfter].getOffset() >=offset) break;
 256    }
 257   
 258    // index of the first error before the dot
 259  10 int errorBefore = errorAfter - 1;
 260   
 261    // this will be set to what we want to select, or -1 if nothing
 262  10 int shouldSelect = -1;
 263   
 264  10 if (errorBefore >= start) { // there's an error before the dot
 265  3 int errPos = _positions[errorBefore].getOffset();
 266  3 try {
 267  3 String betweenDotAndErr = odd.getText(errPos, offset - errPos);
 268  0 if (betweenDotAndErr.indexOf('\n') == -1) shouldSelect = errorBefore;
 269    }
 270    catch (BadLocationException e) { /* source document has been edited; fail silently */ }
 271    catch (StringIndexOutOfBoundsException e) { /* source document has been edited; fail silently */ }
 272    }
 273   
 274  10 if ((shouldSelect == -1) && (errorAfter < end)) {// (errorAfter != _positions.length)) {
 275    // we found an error on/after the dot
 276    // if there's a newline between dot and error,
 277    // then it's not on this line
 278  8 int errPos = _positions[errorAfter].getOffset();
 279  8 try {
 280  8 String betweenDotAndErr = odd.getText(offset, errPos - offset);
 281  6 if (betweenDotAndErr.indexOf('\n') == -1) shouldSelect = errorAfter;
 282    }
 283    catch (BadLocationException e) { /* source document has been edited; fail silently */ }
 284    catch (StringIndexOutOfBoundsException e) { /* source document has been edited; fail silently */ }
 285    }
 286   
 287  4 if (shouldSelect == -1) return null;
 288  6 return _errors[shouldSelect];
 289    }
 290   
 291    /** This function tells if there are errors with source locations associated with the given file. */
 292  11 public boolean hasErrorsWithPositions(OpenDefinitionsDocument odd) {
 293  11 File file = FileOps.NULL_FILE;
 294  11 try {
 295  11 file = odd.getFile();
 296  0 if (file == null || file == FileOps.NULL_FILE) return false;
 297    }
 298  0 catch (FileMovedException fme) { file = fme.getFile(); }
 299   
 300    // Try to use the canonical file
 301  11 try { file = file.getCanonicalFile(); }
 302    catch (IOException ioe) { /* Oh well, look for the file as is.*/ }
 303   
 304  11 StartAndEndIndex saei = _filesToIndexes.get(file);
 305  3 if (saei == null) return false;
 306  0 if (saei.getStartPos() == saei.getEndPos()) return false;
 307  8 return true;
 308    }
 309   
 310    /** Checks whether all CompilerErrors contained here are actually warnings. This would indicate that there were no
 311    * "real" errors, so output is valid.
 312    * @return false if any error contained here is not a warning, true otherwise
 313    */
 314  21 public boolean hasOnlyWarnings() {
 315    // Check for a cached value.
 316  3 if (_onlyWarnings == 0) return false;
 317  0 if (_onlyWarnings == 1) return true;
 318    else {
 319    // If there was no cached value, compute it.
 320  18 boolean clean = true;
 321  18 for (int i = 0; clean && (i < _numErrors); i++) {
 322  18 clean = _errors[i].isWarning();
 323    }
 324    // Cache the value.
 325  18 _onlyWarnings = clean? 1: 0;
 326  18 return clean;
 327    }
 328    }
 329   
 330    /** Create array of positions where each error occurred. Positions are related their corresponding documents. */
 331  3255 private void _calculatePositions() {
 332  3255 try {
 333  3255 int curError = 0;
 334   
 335    // for (; numProcessed < _numErrors; numProcessed++) {
 336  3255 while ((curError < _numErrors)) {
 337    // find the next error with a line number (skipping others)
 338  2876 curError = nextErrorWithLine(curError);
 339  2830 if (curError >= _numErrors) {break;}
 340   
 341    //Now find the file and document we are working on
 342  46 File file = _errors[curError].file();
 343  46 OpenDefinitionsDocument document;
 344  46 try { document = _model.getDocumentForFile(file); }
 345    catch (Exception e) {
 346    // This is intended to catch IOException or OperationCanceledException
 347    // skip positions for these errors if the document couldn't be loaded
 348  0 if ((e instanceof IOException) || (e instanceof OperationCanceledException)) {
 349  0 document = null;
 350    }
 351  0 else throw new UnexpectedException(e);
 352    }
 353  46 if (document==null) {
 354  1 do { curError++;}
 355  1 while ((curError < _numErrors) && (_errors[curError].file().equals(file)));
 356    //If the document couldn't be loaded, start the loop over at the top
 357  1 continue;
 358    }
 359  0 if (curError >= _numErrors) break;
 360   
 361    // curError is the first error in a file, and its document is open.
 362  45 final int fileStartIndex = curError;
 363  45 final int defsLength = document.getLength();
 364  45 final String defsText = document.getText(0, defsLength);
 365  45 int curLine = 0;
 366  45 int offset = 0; // offset is number of chars from beginning of file
 367   
 368    // offset is always pointing to the first character in the line
 369    // containing an error (or the last line of the previous file) at the top of this loop
 370  45 while ((curError < _numErrors) && // we still have errors to find
 371    file.equals(_errors[curError].file()) && // the next error is in this file
 372    (offset <= defsLength)) { // we haven't gone past the end of the file
 373    // create new positions for all errors on this line
 374  101 boolean didNotAdvance = false;
 375  101 if (_errors[curError].lineNumber() != curLine) {
 376    // if this happens, then we will not advance to the next error in the loop below.
 377    // that means we have to advance curError when we reach the end of the document
 378    // or we get stuck in an infinite loop (bug 1679178)
 379    // this seems to be a problem with incompatible line endings (Windows vs. Unix)
 380  32 didNotAdvance = true;
 381    }
 382    else {
 383  69 while ((curError < _numErrors) &&
 384    file.equals(_errors[curError].file()) && // we are still in this file
 385    (_errors[curError].lineNumber() == curLine)) {
 386  74 _positions[curError] = document.createPosition(offset + _errors[curError].startColumn());
 387  74 curError++;
 388    }
 389    }
 390   
 391    // At this point, offset is the starting index of the previous error's line.
 392    // Update offset to be appropriate for the current error.
 393    // ... but don't bother looking if it isn't in this file.
 394    // ... or if we're done with all errors already.
 395  101 if (curError < _numErrors) {
 396  76 int curErrorLine = _errors[curError].lineNumber();
 397  76 int nextNewline = 0;
 398  76 while ((curLine != curErrorLine)
 399    && (nextNewline != -1)
 400    && (file.equals(_errors[curError].file()))) {
 401  143 nextNewline = defsText.indexOf(newLine, offset);
 402  0 if (nextNewline == -1) nextNewline = defsText.indexOf("\n", offset);
 403  143 if (nextNewline != -1) {
 404  143 curLine++;
 405  143 offset = nextNewline + 1;
 406    }
 407    else {
 408    // we're at the end of the document
 409  0 if (didNotAdvance) {
 410    // we did not advance to the next error above, so unless we want to
 411    // get stuck in an infinite loop (bug 1679178), we have to advance now.
 412    // otherwise we would never leave the while loop and keep processing
 413    // the same error.
 414    // this situation probably means that the line number information of the
 415    // compiler is different from our line number information;
 416    // probably a Windows vs. Unix line ending problem
 417  0 _positions[curError] = null;
 418  0 curError++;
 419    }
 420    }
 421    }
 422    }
 423    }
 424    //Remember the indexes in the _errors and _positions arrays that
 425    // are for the errors in this file
 426  45 int fileEndIndex = curError;
 427  45 if (fileEndIndex != fileStartIndex) {
 428    // Try to use the canonical file if possible
 429  45 try { file = file.getCanonicalFile(); }
 430    catch (IOException ioe) { /* Oh well, store it as is */ }
 431  45 _filesToIndexes.put(file, new StartAndEndIndex(fileStartIndex, fileEndIndex));
 432    }
 433    }
 434    }
 435  0 catch (BadLocationException ble) { throw new UnexpectedException(ble); }
 436  0 catch (StringIndexOutOfBoundsException e) { throw new UnexpectedException(e); }
 437    }
 438   
 439    /** Finds the first error after numProcessed which has a file and line number.
 440    * @param idx the starting index of the search
 441    * @return the index of the found error
 442    */
 443  2876 private int nextErrorWithLine(int idx) {
 444  2877 while (idx < _numErrors && (_errors[idx].hasNoLocation() || _errors[idx].file() == null)) idx++;
 445  2876 return idx;
 446    }
 447   
 448    /** This class is used only to track where the errors with positions for a file begin and end. The beginning index
 449    * is inclusive, the ending index is exclusive.
 450    */
 451    private static class StartAndEndIndex {
 452    private int startPos;
 453    private int endPos;
 454   
 455  45 public StartAndEndIndex(int startPos, int endPos) {
 456  45 this.startPos = startPos;
 457  45 this.endPos = endPos;
 458    }
 459  18 public int getStartPos() { return startPos; }
 460  18 public int getEndPos() { return endPos; }
 461    }
 462    }