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.compiler;
038:
039: import java.io.File;
040: import java.io.IOException;
041: import javax.swing.text.*;
042: import java.util.Arrays;
043: import java.util.List;
044: import java.util.LinkedList;
045: import java.util.HashMap;
046:
047: import edu.rice.cs.util.StringOps;
048: import edu.rice.cs.util.UnexpectedException;
049: import edu.rice.cs.drjava.model.DummyGlobalModel;
050: import edu.rice.cs.drjava.model.FileGroupingState;
051: import edu.rice.cs.drjava.model.GlobalModel;
052: import edu.rice.cs.util.OperationCanceledException;
053: import edu.rice.cs.drjava.model.OpenDefinitionsDocument;
054: import edu.rice.cs.drjava.model.FileMovedException;
055:
056: /**
057: * Contains the CompilerErrors for a particular file after
058: * a compile has ended.
059: * @version $Id: CompilerErrorModel.java 4255 2007-08-28 19:17:37Z mgricken $
060: */
061: public class CompilerErrorModel {
062: private static final String newLine = StringOps.EOL;
063: /** An array of errors to be displayed in the CompilerErrorPanel associated with this model. After model
064: * construction, this array should be sorted in this order:
065: * (i) Errors with no file.
066: * (ii) Errors for each file in path-alphabetical order.
067: * Within each file:
068: * errors with no line number
069: * errors with line numbers, in order
070: * In all cases, where all else is equal, warnings are sorted below errors.
071: */
072: private final CompilerError[] _errors;
073:
074: /** An array of file offsets, parallel to the _errors array. NOTE: If there is no position associated with an error,
075: * its entry here should be set to null.
076: */
077: private final Position[] _positions;
078:
079: /** The size of _errors and _positions. This should never change after model construction*/
080: private final int _numErrors;
081:
082: /** The number of compile errors. Used for display purposes only.*/
083: private int _numCompilerErrors;
084:
085: /** The number of compile errors. Used for display purposes only.*/
086: private int _numWarnings;
087:
088: /** Cached result of hasOnlyWarnings.
089: * Three-state enum:
090: * -1 => result has not been computed
091: * 0 => false
092: * 1 => true
093: */
094: private int _onlyWarnings = -1;
095:
096: /** Used internally in building _positions. The file used as the index *must* be a canonical file, or else
097: * errors won't always be associated with the right documents.
098: */
099: private final HashMap<File, StartAndEndIndex> _filesToIndexes = new HashMap<File, StartAndEndIndex>();
100:
101: /** The global model which created/controls this object. */
102: private final GlobalModel _model;
103:
104: /** Constructs an empty CompilerErrorModel with no errors and a dummy global model. */
105: public CompilerErrorModel() {
106: _model = new DummyGlobalModel() {
107: public OpenDefinitionsDocument getDocumentForFile(File file) {
108: throw new IllegalStateException("No documents to get!");
109: }
110:
111: public boolean isAlreadyOpen(File file) {
112: return false;
113: }
114:
115: public List<OpenDefinitionsDocument> getOpenDefinitionsDocuments() {
116: return new LinkedList<OpenDefinitionsDocument>();
117: }
118:
119: public boolean hasModifiedDocuments() {
120: return false;
121: }
122:
123: public boolean hasUntitledDocuments() {
124: return false;
125: }
126: };
127: _errors = new CompilerError[0];
128: _numErrors = 0;
129: _numWarnings = 0;
130: _numCompilerErrors = 0;
131: _positions = new Position[0];
132: }
133:
134: /** Constructs a new CompilerErrorModel with specified global model. Performed in DefaultGlobalModel construction
135: * and after compilation has been performed.
136: * @param errors the list of CompilerError's (or a subclass).
137: * @param model is the model to find documents from
138: */
139: public CompilerErrorModel(CompilerError[] errors, GlobalModel model) {
140: _model = model;
141:
142: // TODO: If we move to NextGen-style generics, ensure _errors is non-null.
143: _errors = errors;
144:
145: _numErrors = errors.length;
146: _positions = new Position[errors.length];
147:
148: _numWarnings = 0;
149: _numCompilerErrors = 0;
150: for (int i = 0; i < errors.length; i++) {
151: if (errors[i].isWarning())
152: _numWarnings++;
153: else
154: _numCompilerErrors++;
155: }
156:
157: // Sort the errors by file and position
158: Arrays.sort(_errors);
159:
160: // Populates _positions.
161: _calculatePositions();
162: }
163:
164: /**
165: * Accessor for errors maintained here.
166: * @param idx the index of the error to retrieve
167: * @return the error at index idx
168: * @throws NullPointerException if this object was improperly initialized
169: * @throws ArrayIndexOutOfBoundsException if !(0 <= idx < this.getNumErrors())
170: */
171: public CompilerError getError(int idx) {
172: return _errors[idx];
173: }
174:
175: /** Returns the position of the given error in the document representing its file. */
176: public Position getPosition(CompilerError error) {
177: int spot = Arrays.binarySearch(_errors, error);
178: return _positions[spot];
179: }
180:
181: /** Returns the number of CompilerErrors. */
182: public int getNumErrors() {
183: return _numErrors;
184: }
185:
186: /** Returns the number of CompilerErrors that are compiler errors */
187: public int getNumCompErrors() {
188: return _numCompilerErrors;
189: }
190:
191: /** Returns the number of CompilerErrors that are warnings */
192: public int getNumWarnings() {
193: return _numWarnings;
194: }
195:
196: /** Prints out this model's errors. */
197: public String toString() {
198: final StringBuilder buf = new StringBuilder();
199: buf.append(this .getClass().toString() + ":\n ");
200: for (int i = 0; i < _numErrors; i++) {
201: buf.append(_errors[i].toString());
202: buf.append("\n ");
203: }
204: return buf.toString();
205: }
206:
207: /** This method finds and returns the error that is at the given offset
208: * @param odd the OpenDefinitionsDocument where you want to find the error at the caret
209: * @param offset the offset into the document
210: * @return the CompilerError at the given offset, null if no error corresponds to this location
211: */
212: public CompilerError getErrorAtOffset(OpenDefinitionsDocument odd,
213: int offset) {
214: File file;
215: try {
216: file = odd.getFile();
217: if (file == null)
218: return null;
219: } catch (FileMovedException e) {
220: file = e.getFile();
221: }
222:
223: // Use the canonical file if possible
224: try {
225: file = file.getCanonicalFile();
226: } catch (IOException ioe) {
227: // Oh well, we'll look for it as is.
228: }
229:
230: StartAndEndIndex saei = _filesToIndexes.get(file);
231: if (saei == null)
232: return null;
233: int start = saei.getStartPos();
234: int end = saei.getEndPos();
235: if (start == end)
236: return null;
237:
238: // check if the dot is on a line with an error.
239: // Find the first error that is on or after the dot. If this comes
240: // before the newline after the dot, it's on the same line.
241: int errorAfter; // index of the first error after the dot
242: for (errorAfter = start; errorAfter < end; errorAfter++) {
243: if (_positions[errorAfter] == null) {
244: //This indicates something wrong, but it was happening before so...
245: return null;
246: }
247: if (_positions[errorAfter].getOffset() >= offset)
248: break;
249: }
250:
251: // index of the first error before the dot
252: int errorBefore = errorAfter - 1;
253:
254: // this will be set to what we want to select, or -1 if nothing
255: int shouldSelect = -1;
256:
257: if (errorBefore >= start) { // there's an error before the dot
258: int errPos = _positions[errorBefore].getOffset();
259: try {
260: String betweenDotAndErr = odd.getText(errPos, offset
261: - errPos);
262: if (betweenDotAndErr.indexOf('\n') == -1)
263: shouldSelect = errorBefore;
264: } catch (BadLocationException e) { /* source document has been edited; fail silently */
265: }
266: }
267:
268: if ((shouldSelect == -1) && (errorAfter < end)) {// (errorAfter != _positions.length)) {
269: // we found an error on/after the dot
270: // if there's a newline between dot and error,
271: // then it's not on this line
272: int errPos = _positions[errorAfter].getOffset();
273: try {
274: String betweenDotAndErr = odd.getText(offset, errPos
275: - offset);
276: if (betweenDotAndErr.indexOf('\n') == -1)
277: shouldSelect = errorAfter;
278: } catch (BadLocationException e) { /* source document has been edited; fail silently */
279: }
280: }
281:
282: if (shouldSelect == -1)
283: return null;
284: return _errors[shouldSelect];
285: }
286:
287: /** This function tells if there are errors with source locations associated with the given file. */
288: public boolean hasErrorsWithPositions(OpenDefinitionsDocument odd) {
289: File file = null;
290: try {
291: file = odd.getFile();
292: if (file == null)
293: return false;
294: } catch (FileMovedException fme) {
295: file = fme.getFile();
296: }
297:
298: // Try to use the canonical file
299: try {
300: file = file.getCanonicalFile();
301: } catch (IOException ioe) { /* Oh well, look for the file as is.*/
302: }
303:
304: StartAndEndIndex saei = _filesToIndexes.get(file);
305: if (saei == null)
306: return false;
307: if (saei.getStartPos() == saei.getEndPos())
308: return false;
309: return true;
310: }
311:
312: /** Checks whether all CompilerErrors contained here are actually warnings. This would indicate that there were no
313: * "real" errors, so output is valid.
314: * @return false if any error contained here is not a warning, true otherwise
315: */
316: public boolean hasOnlyWarnings() {
317: // Check for a cached value.
318: if (_onlyWarnings == 0)
319: return false;
320: if (_onlyWarnings == 1)
321: return true;
322: else {
323: // If there was no cached value, compute it.
324: boolean clean = true;
325: for (int i = 0; clean && (i < _numErrors); i++) {
326: clean = _errors[i].isWarning();
327: }
328: // Cache the value.
329: _onlyWarnings = clean ? 1 : 0;
330: return clean;
331: }
332: }
333:
334: /** Create array of positions where each error occurred. Positions are related their corresponding documents. */
335: private void _calculatePositions() {
336: try {
337: int curError = 0;
338:
339: // for (; numProcessed < _numErrors; numProcessed++) {
340: while ((curError < _numErrors)) {
341: // find the next error with a line number (skipping others)
342: curError = nextErrorWithLine(curError);
343: if (curError >= _numErrors) {
344: break;
345: }
346:
347: //Now find the file and document we are working on
348: File file = _errors[curError].file();
349: OpenDefinitionsDocument document;
350: try {
351: document = _model.getDocumentForFile(file);
352: } catch (Exception e) {
353: // This is intended to catch IOException or OperationCanceledException
354: if ((e instanceof IOException)
355: || (e instanceof OperationCanceledException)) {
356: // skip positions for these errors if the document couldn't be loaded
357: do {
358: curError++;
359: } while ((curError < _numErrors)
360: && (_errors[curError].file()
361: .equals(file)));
362:
363: //If the document couldn't be loaded, start the loop over at the top
364: continue;
365: } else
366: throw new UnexpectedException(e);
367: }
368: if (curError >= _numErrors)
369: break;
370:
371: // curError is the first error in a file, and its document is open.
372: final int fileStartIndex = curError;
373: final int defsLength = document.getLength();
374: final String defsText = document.getText(0, defsLength);
375: int curLine = 0;
376: int offset = 0; // offset is number of chars from beginning of file
377:
378: // offset is always pointing to the first character in the line
379: // containing an error (or the last line of the previous file) at the top of this loop
380: while ((curError < _numErrors) && // we still have errors to find
381: file.equals(_errors[curError].file()) && // the next error is in this file
382: (offset <= defsLength)) { // we haven't gone past the end of the file
383: // create new positions for all errors on this line
384: boolean didNotAdvance = false;
385: if (_errors[curError].lineNumber() != curLine) {
386: // if this happens, then we will not advance to the next error in the loop below.
387: // that means we have to advance curError when we reach the end of the document
388: // or we get stuck in an infinite loop (bug 1679178)
389: // this seems to be a problem with incompatible line endings (Windows vs. Unix)
390: didNotAdvance = true;
391: } else {
392: while ((curError < _numErrors)
393: && file
394: .equals(_errors[curError]
395: .file()) && // we are still in this file
396: (_errors[curError].lineNumber() == curLine)) {
397: _positions[curError] = document
398: .createPosition(offset
399: + _errors[curError]
400: .startColumn());
401: curError++;
402: }
403: }
404:
405: // At this point, offset is the starting index of the previous error's line.
406: // Update offset to be appropriate for the current error.
407: // ... but don't bother looking if it isn't in this file.
408: // ... or if we're done with all errors already.
409: if (curError < _numErrors) {
410: int curErrorLine = _errors[curError]
411: .lineNumber();
412: int nextNewline = 0;
413: while ((curLine != curErrorLine)
414: && (nextNewline != -1)
415: && (file.equals(_errors[curError]
416: .file()))) {
417: nextNewline = defsText.indexOf(newLine,
418: offset);
419: if (nextNewline == -1)
420: nextNewline = defsText.indexOf("\n",
421: offset);
422: if (nextNewline != -1) {
423: curLine++;
424: offset = nextNewline + 1;
425: } else {
426: // we're at the end of the document
427: if (didNotAdvance) {
428: // we did not advance to the next error above, so unless we want to
429: // get stuck in an infinite loop (bug 1679178), we have to advance now.
430: // otherwise we would never leave the while loop and keep processing
431: // the same error.
432: // this situation probably means that the line number information of the
433: // compiler is different from our line number information;
434: // probably a Windows vs. Unix line ending problem
435: _positions[curError] = null;
436: curError++;
437: }
438: }
439: }
440: }
441: }
442:
443: //Remember the indexes in the _errors and _positions arrays that
444: // are for the errors in this file
445: int fileEndIndex = curError;
446: if (fileEndIndex != fileStartIndex) {
447: // Try to use the canonical file if possible
448: try {
449: file = file.getCanonicalFile();
450: } catch (IOException ioe) { /* Oh well, store it as is */
451: }
452: _filesToIndexes.put(file, new StartAndEndIndex(
453: fileStartIndex, fileEndIndex));
454: }
455: }
456: } catch (BadLocationException ble) {
457: throw new UnexpectedException(ble);
458: }
459: }
460:
461: /** Finds the first error after numProcessed which has a file and line number.
462: * @param idx the starting index of the search
463: * @return the index of the found error
464: */
465: private int nextErrorWithLine(int idx) {
466: while (idx < _numErrors
467: && (_errors[idx].hasNoLocation() || _errors[idx].file() == null))
468: idx++;
469: return idx;
470: }
471:
472: /** This class is used only to track where the errors with positions for a file begin and end. The beginning index
473: * is inclusive, the ending index is exclusive.
474: */
475: private static class StartAndEndIndex {
476: private int startPos;
477: private int endPos;
478:
479: public StartAndEndIndex(int startPos, int endPos) {
480: this .startPos = startPos;
481: this .endPos = endPos;
482: }
483:
484: public int getStartPos() {
485: return startPos;
486: }
487:
488: public int getEndPos() {
489: return endPos;
490: }
491: }
492: }
|