0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041: package org.netbeans.modules.editor.hints;
0042:
0043: import java.awt.Color;
0044: import java.awt.Container;
0045: import java.awt.Dimension;
0046: import java.awt.Point;
0047: import java.beans.PropertyChangeEvent;
0048: import java.beans.PropertyChangeListener;
0049: import java.io.IOException;
0050: import java.lang.ref.Reference;
0051: import java.lang.ref.WeakReference;
0052: import java.util.ArrayList;
0053: import java.util.Arrays;
0054: import java.util.Collection;
0055: import java.util.Collections;
0056: import java.util.Comparator;
0057: import java.util.EnumMap;
0058: import java.util.HashMap;
0059: import java.util.HashSet;
0060: import java.util.Iterator;
0061: import java.util.LinkedList;
0062: import java.util.List;
0063: import java.util.Map;
0064: import java.util.Set;
0065: import java.util.SortedMap;
0066: import java.util.TreeMap;
0067: import java.util.logging.Level;
0068: import java.util.logging.Logger;
0069: import javax.swing.JEditorPane;
0070: import javax.swing.JViewport;
0071: import javax.swing.SwingUtilities;
0072: import javax.swing.event.ChangeEvent;
0073: import javax.swing.event.ChangeListener;
0074: import javax.swing.event.DocumentEvent;
0075: import javax.swing.event.DocumentListener;
0076: import javax.swing.text.AttributeSet;
0077: import javax.swing.text.BadLocationException;
0078: import javax.swing.text.BadLocationException;
0079: import javax.swing.text.Document;
0080: import javax.swing.text.JTextComponent;
0081: import javax.swing.text.Position;
0082: import javax.swing.text.StyledDocument;
0083: import javax.swing.text.StyledDocument;
0084: import org.netbeans.api.editor.settings.AttributesUtilities;
0085: import org.netbeans.api.editor.settings.EditorStyleConstants;
0086: import org.netbeans.editor.BaseDocument;
0087: import org.netbeans.editor.Utilities;
0088: import org.netbeans.spi.editor.highlighting.HighlightAttributeValue;
0089: import org.netbeans.spi.editor.highlighting.support.OffsetsBag;
0090: import org.netbeans.spi.editor.hints.ErrorDescription;
0091: import org.netbeans.spi.editor.hints.ErrorDescriptionFactory;
0092: import org.netbeans.spi.editor.hints.LazyFixList;
0093: import org.netbeans.spi.editor.hints.Severity;
0094: import org.openide.cookies.EditorCookie;
0095: import org.openide.filesystems.FileObject;
0096: import org.openide.loaders.DataObject;
0097: import org.openide.text.Annotation;
0098: import org.openide.text.NbDocument;
0099: import org.openide.util.RequestProcessor;
0100: import org.openide.util.RequestProcessor;
0101: import org.openide.util.WeakListeners;
0102: import org.openide.filesystems.FileUtil;
0103: import org.openide.text.PositionBounds;
0104: import org.openide.util.Exceptions;
0105:
0106: /**
0107: *
0108: * @author Jan Lahoda
0109: */
0110: public class AnnotationHolder implements ChangeListener,
0111: PropertyChangeListener, DocumentListener {
0112:
0113: final static Map<Severity, AttributeSet> COLORINGS;
0114:
0115: static {
0116: COLORINGS = new EnumMap<Severity, AttributeSet>(Severity.class);
0117: COLORINGS.put(Severity.ERROR, AttributesUtilities
0118: .createImmutable(
0119: EditorStyleConstants.WaveUnderlineColor,
0120: new Color(0xFF, 0x00, 0x00),
0121: EditorStyleConstants.Tooltip,
0122: new TooltipResolver()));
0123: COLORINGS.put(Severity.WARNING, AttributesUtilities
0124: .createImmutable(
0125: EditorStyleConstants.WaveUnderlineColor,
0126: new Color(0xC0, 0xC0, 0x00),
0127: EditorStyleConstants.Tooltip,
0128: new TooltipResolver()));
0129: COLORINGS.put(Severity.VERIFIER, AttributesUtilities
0130: .createImmutable(
0131: EditorStyleConstants.WaveUnderlineColor,
0132: new Color(0xFF, 0xD5, 0x55),
0133: EditorStyleConstants.Tooltip,
0134: new TooltipResolver()));
0135: COLORINGS.put(Severity.HINT, AttributesUtilities
0136: .createImmutable(EditorStyleConstants.Tooltip,
0137: new TooltipResolver()));
0138: };
0139:
0140: private Map<ErrorDescription, List<Position>> errors2Lines;
0141: private Map<Position, List<ErrorDescription>> line2Errors;
0142: private Map<Position, ParseErrorAnnotation> line2Annotations;
0143: private Map<String, List<ErrorDescription>> layer2Errors;
0144:
0145: private Set<JEditorPane> openedComponents;
0146: private EditorCookie.Observable editorCookie;
0147: private FileObject file;
0148: private DataObject od;
0149: private BaseDocument doc;
0150:
0151: private static Map<FileObject, AnnotationHolder> file2Holder = new HashMap<FileObject, AnnotationHolder>();
0152:
0153: public static synchronized AnnotationHolder getInstance(
0154: FileObject file) {
0155: if (file == null)
0156: return null;
0157:
0158: AnnotationHolder result = file2Holder.get(file);
0159:
0160: if (result == null) {
0161: try {
0162: DataObject od = DataObject.find(file);
0163: EditorCookie.Observable editorCookie = od
0164: .getCookie(EditorCookie.Observable.class);
0165:
0166: if (editorCookie == null) {
0167: Logger
0168: .getLogger("global")
0169: .log(
0170: Level.WARNING,
0171: "No EditorCookie.Observable for file: "
0172: + FileUtil
0173: .getFileDisplayName(file));
0174: } else {
0175: Document doc = editorCookie.getDocument();
0176:
0177: if (doc instanceof BaseDocument) {
0178: file2Holder.put(file,
0179: result = new AnnotationHolder(file, od,
0180: (BaseDocument) doc,
0181: editorCookie));
0182: }
0183: }
0184: } catch (IOException e) {
0185: Logger.getLogger("global").log(Level.INFO, null, e);
0186: }
0187: }
0188:
0189: return result;
0190: }
0191:
0192: /**temporary*/
0193: static synchronized Collection<FileObject> coveredFiles() {
0194: return new ArrayList<FileObject>(file2Holder.keySet());
0195: }
0196:
0197: private AnnotationHolder(FileObject file, DataObject od,
0198: BaseDocument doc, EditorCookie.Observable editorCookie)
0199: throws IOException {
0200: if (file == null)
0201: return;
0202:
0203: init();
0204:
0205: this .file = file;
0206: this .od = od;
0207: this .doc = doc;
0208:
0209: getBag(doc);
0210:
0211: this .doc.addDocumentListener(this );
0212: editorCookie.addPropertyChangeListener(WeakListeners
0213: .propertyChange(this , editorCookie));
0214: this .editorCookie = editorCookie;
0215:
0216: propertyChange(null);
0217:
0218: Logger.getLogger("TIMER").log(Level.FINE, "Annotation Holder",
0219: new Object[] { file, this });
0220: }
0221:
0222: private synchronized void init() {
0223: errors2Lines = new HashMap<ErrorDescription, List<Position>>();
0224: line2Errors = new HashMap<Position, List<ErrorDescription>>();
0225: line2Annotations = new HashMap<Position, ParseErrorAnnotation>();
0226: layer2Errors = new HashMap<String, List<ErrorDescription>>();
0227: openedComponents = new HashSet<JEditorPane>();
0228: }
0229:
0230: public void stateChanged(ChangeEvent evt) {
0231: updateVisibleRanges();
0232: }
0233:
0234: Attacher attacher = new NbDocumentAttacher();
0235:
0236: void attachAnnotation(Position line, ParseErrorAnnotation a)
0237: throws BadLocationException {
0238: attacher.attachAnnotation(line, a);
0239: }
0240:
0241: void detachAnnotation(Annotation a) {
0242: attacher.detachAnnotation(a);
0243: }
0244:
0245: static interface Attacher {
0246: public void attachAnnotation(Position line,
0247: ParseErrorAnnotation a) throws BadLocationException;
0248:
0249: public void detachAnnotation(Annotation a);
0250: }
0251:
0252: final class LineAttacher implements Attacher {
0253: public void attachAnnotation(Position line,
0254: ParseErrorAnnotation a) throws BadLocationException {
0255: throw new UnsupportedOperationException();
0256: // LineCookie lc = od.getCookie(LineCookie.class);
0257: // Line lineRef = lc.getLineSet().getCurrent(line);
0258: //
0259: // a.attach(lineRef);
0260: }
0261:
0262: public void detachAnnotation(Annotation a) {
0263: a.detach();
0264: }
0265: }
0266:
0267: final class NbDocumentAttacher implements Attacher {
0268: public void attachAnnotation(Position lineStart,
0269: ParseErrorAnnotation a) throws BadLocationException {
0270: NbDocument.addAnnotation((StyledDocument) doc, lineStart,
0271: -1, a);
0272: }
0273:
0274: public void detachAnnotation(Annotation a) {
0275: if (doc != null) {
0276: NbDocument.removeAnnotation((StyledDocument) doc, a);
0277: }
0278: }
0279: }
0280:
0281: private synchronized void clearAll() {
0282: //remove all annotations:
0283: for (ParseErrorAnnotation a : line2Annotations.values()) {
0284: detachAnnotation(a);
0285: }
0286:
0287: file2Holder.remove(file);
0288: doc.removeDocumentListener(this );
0289:
0290: getBag(doc).clear();
0291: }
0292:
0293: public void propertyChange(PropertyChangeEvent evt) {
0294: SwingUtilities.invokeLater(new Runnable() {
0295: public void run() {
0296: JEditorPane[] panes = editorCookie.getOpenedPanes();
0297:
0298: if (panes == null) {
0299: clearAll();
0300: return;
0301: }
0302:
0303: Set<JEditorPane> addedPanes = new HashSet<JEditorPane>(
0304: Arrays.asList(panes));
0305: Set<JEditorPane> removedPanes = new HashSet<JEditorPane>(
0306: openedComponents);
0307:
0308: removedPanes.removeAll(addedPanes);
0309: addedPanes.removeAll(openedComponents);
0310:
0311: for (JEditorPane pane : addedPanes) {
0312: Container parent = pane.getParent();
0313:
0314: if (parent instanceof JViewport) {
0315: JViewport viewport = (JViewport) parent;
0316:
0317: viewport
0318: .addChangeListener(WeakListeners
0319: .change(AnnotationHolder.this ,
0320: viewport));
0321: }
0322: }
0323:
0324: openedComponents.removeAll(removedPanes);
0325: openedComponents.addAll(addedPanes);
0326:
0327: updateVisibleRanges();
0328: return;
0329: }
0330: });
0331: }
0332:
0333: public synchronized void insertUpdate(DocumentEvent e) {
0334: try {
0335: int offset = Utilities.getRowStart(doc, e.getOffset());
0336:
0337: Set<Position> modifiedLines = new HashSet<Position>();
0338:
0339: int index = findPositionGE(offset);
0340:
0341: if (index == knownPositions.size())
0342: return;
0343:
0344: Position line = knownPositions.get(index).get();
0345:
0346: if (line == null)
0347: return;
0348:
0349: List<ErrorDescription> eds = getErrorsForLine(line, false);
0350:
0351: if (eds == null)
0352: return;
0353:
0354: eds = new LinkedList<ErrorDescription>(eds);
0355:
0356: for (ErrorDescription ed : eds) {
0357: for (Position i : errors2Lines.remove(ed)) {
0358: line2Errors.get(i).remove(ed);
0359: modifiedLines.add(i);
0360: }
0361: for (List<ErrorDescription> edsForLayer : layer2Errors
0362: .values()) {
0363: edsForLayer.remove(ed);
0364: }
0365: }
0366:
0367: line2Errors.remove(line);
0368:
0369: //make sure the highlights are removed even for multi-line inserts:
0370: try {
0371: int rowStart = e.getOffset();
0372: int rowEnd = Utilities.getRowEnd(doc, e.getOffset()
0373: + e.getLength());
0374:
0375: getBag(doc).removeHighlights(rowStart, rowEnd, false);
0376: } catch (BadLocationException ex) {
0377: throw (IOException) new IOException().initCause(ex);
0378: }
0379:
0380: for (Position lineToken : modifiedLines) {
0381: updateAnnotationOnLine(lineToken);
0382: updateHighlightsOnLine(lineToken);
0383: }
0384: } catch (IOException ex) {
0385: Exceptions.printStackTrace(ex);
0386: } catch (BadLocationException ex) {
0387: Exceptions.printStackTrace(ex);
0388: }
0389: }
0390:
0391: public synchronized void removeUpdate(DocumentEvent e) {
0392: try {
0393: Position current = null;
0394: int index = -1;
0395: int startOffset = Utilities.getRowStart(doc, e.getOffset());
0396:
0397: while (current == null) {
0398: index = findPositionGE(startOffset);
0399:
0400: if (knownPositions.size() == 0) {
0401: break;
0402: }
0403: if (index == knownPositions.size()) {
0404: return;
0405: }
0406: current = knownPositions.get(index).get();
0407: }
0408:
0409: if (current == null) {
0410: //nothing to do:
0411: return;
0412: }
0413:
0414: assert index != (-1);
0415:
0416: //find the first:
0417: while (index > 0) {
0418: Position minusOne = knownPositions.get(index - 1).get();
0419:
0420: if (minusOne == null) {
0421: index--;
0422: continue;
0423: }
0424:
0425: if (minusOne.getOffset() != current.getOffset()) {
0426: break;
0427: }
0428:
0429: index--;
0430: }
0431:
0432: Set<Position> modifiedLinesTokens = new HashSet<Position>();
0433:
0434: while (index < knownPositions.size()) {
0435: Position next = knownPositions.get(index).get();
0436:
0437: if (next == null) {
0438: index++;
0439: continue;
0440: }
0441:
0442: if (next.getOffset() != current.getOffset()) {
0443: break;
0444: }
0445:
0446: modifiedLinesTokens.add(next);
0447: index++;
0448: }
0449:
0450: for (Position line : new LinkedList<Position>(
0451: modifiedLinesTokens)) {
0452: List<ErrorDescription> eds = line2Errors.get(line);
0453:
0454: if (eds == null || eds.isEmpty()) {
0455: continue;
0456: }
0457: eds = new LinkedList<ErrorDescription>(eds);
0458:
0459: for (ErrorDescription ed : eds) {
0460: for (Position i : errors2Lines.remove(ed)) {
0461: line2Errors.get(i).remove(ed);
0462: modifiedLinesTokens.add(i);
0463: }
0464: for (List<ErrorDescription> edsForLayer : layer2Errors
0465: .values()) {
0466: edsForLayer.remove(ed);
0467: }
0468: }
0469:
0470: line2Errors.remove(line);
0471: }
0472:
0473: for (Position line : modifiedLinesTokens) {
0474: updateAnnotationOnLine(line);
0475: updateHighlightsOnLine(line);
0476: }
0477: } catch (IOException ex) {
0478: Exceptions.printStackTrace(ex);
0479: } catch (BadLocationException ex) {
0480: Exceptions.printStackTrace(ex);
0481: }
0482: }
0483:
0484: public void changedUpdate(DocumentEvent e) {
0485: //ignored
0486: }
0487:
0488: private void updateVisibleRanges() {
0489: SwingUtilities.invokeLater(new Runnable() {
0490: public void run() {
0491: long startTime = System.currentTimeMillis();
0492: final List<int[]> visibleRanges = new ArrayList<int[]>();
0493:
0494: doc.render(new Runnable() {
0495: public void run() {
0496: synchronized (AnnotationHolder.this ) {
0497: for (JEditorPane pane : openedComponents) {
0498: Container parent = pane.getParent();
0499:
0500: if (parent instanceof JViewport) {
0501: JViewport viewport = (JViewport) parent;
0502: Point start = viewport
0503: .getViewPosition();
0504: Dimension size = viewport
0505: .getExtentSize();
0506: Point end = new Point(start.x
0507: + size.width, start.y
0508: + size.height);
0509:
0510: int startPosition = pane
0511: .viewToModel(start);
0512: int endPosition = pane
0513: .viewToModel(end);
0514: //TODO: check differences against last:
0515: visibleRanges
0516: .add(new int[] {
0517: startPosition,
0518: endPosition });
0519: }
0520: }
0521: }
0522: }
0523: });
0524:
0525: INSTANCE.post(new Runnable() {
0526: public void run() {
0527: for (int[] span : visibleRanges) {
0528: updateAnnotations(span[0], span[1]);
0529: }
0530: }
0531: });
0532:
0533: long endTime = System.currentTimeMillis();
0534:
0535: Logger.getLogger(AnnotationHolder.class.getName()).log(
0536: Level.FINE, "updateVisibleRanges: time={0}",
0537: endTime - startTime);
0538: }
0539: });
0540: }
0541:
0542: private void updateAnnotations(final int startPosition,
0543: final int endPosition) {
0544: long startTime = System.currentTimeMillis();
0545: final List<ErrorDescription> errorsToUpdate = new ArrayList<ErrorDescription>();
0546:
0547: doc.render(new Runnable() {
0548: public void run() {
0549: synchronized (this ) {
0550: try {
0551: if (doc.getLength() == 0) {
0552: return;
0553: }
0554:
0555: int start = startPosition < doc.getLength() ? startPosition
0556: : (doc.getLength() - 1);
0557: int end = endPosition < doc.getLength() ? endPosition
0558: : (doc.getLength() - 1);
0559:
0560: if (start < 0)
0561: start = 0;
0562: if (end < 0)
0563: end = 0;
0564:
0565: int startLine = Utilities.getRowStart(doc,
0566: start);
0567: int endLine = Utilities.getRowEnd(doc, end) + 1;
0568:
0569: int index = findPositionGE(startLine);
0570:
0571: while (index < knownPositions.size()) {
0572: Reference<Position> r = knownPositions
0573: .get(index++);
0574: if (r == null)
0575: continue;
0576: Position lineToken = r.get();
0577:
0578: if (lineToken == null)
0579: continue;
0580:
0581: if (lineToken.getOffset() > endLine)
0582: break;
0583:
0584: List<ErrorDescription> errors = line2Errors
0585: .get(lineToken);
0586:
0587: if (errors != null) {
0588: errorsToUpdate.addAll(errors);
0589: }
0590: }
0591: } catch (BadLocationException e) {
0592: Exceptions.printStackTrace(e);
0593: }
0594: }
0595:
0596: }
0597: });
0598:
0599: Logger.getLogger(AnnotationHolder.class.getName()).log(
0600: Level.FINE, "updateAnnotations: errorsToUpdate={0}",
0601: errorsToUpdate);
0602:
0603: for (ErrorDescription e : errorsToUpdate) {
0604: LazyFixList l = e.getFixes();
0605:
0606: if (l.probablyContainsFixes() && !l.isComputed()) {
0607: l.getFixes();
0608: }
0609: }
0610:
0611: long endTime = System.currentTimeMillis();
0612:
0613: Logger.getLogger(AnnotationHolder.class.getName()).log(
0614: Level.FINE, "updateAnnotations: time={0}",
0615: endTime - startTime);
0616: }
0617:
0618: private List<ErrorDescription> getErrorsForLayer(String layer) {
0619: List<ErrorDescription> errors = layer2Errors.get(layer);
0620:
0621: if (errors == null) {
0622: layer2Errors.put(layer,
0623: errors = new ArrayList<ErrorDescription>());
0624: }
0625:
0626: return errors;
0627: }
0628:
0629: private List<ErrorDescription> getErrorsForLine(Position line,
0630: boolean create) {
0631: List<ErrorDescription> errors = line2Errors.get(line);
0632:
0633: if (errors == null && create) {
0634: line2Errors.put(line,
0635: errors = new ArrayList<ErrorDescription>());
0636: }
0637:
0638: if (errors != null && errors.isEmpty() && !create) {
0639: //clean:
0640: line2Errors.remove(line);
0641: errors = null;
0642: }
0643:
0644: return errors;
0645: }
0646:
0647: private static List<ErrorDescription> filter(
0648: List<ErrorDescription> errors, boolean onlyErrors) {
0649: List<ErrorDescription> result = new ArrayList<ErrorDescription>();
0650:
0651: for (ErrorDescription e : errors) {
0652: if (e.getSeverity() == Severity.ERROR) {
0653: if (onlyErrors)
0654: result.add(e);
0655: } else {
0656: if (!onlyErrors)
0657: result.add(e);
0658: }
0659: }
0660:
0661: return result;
0662: }
0663:
0664: private static void concatDescription(
0665: List<ErrorDescription> errors, StringBuffer description) {
0666: boolean first = true;
0667:
0668: for (ErrorDescription e : errors) {
0669: if (!first) {
0670: description.append("\n\n");
0671: }
0672: description.append(e.getDescription());
0673: first = false;
0674: }
0675: }
0676:
0677: private List<LazyFixList> computeFixes(List<ErrorDescription> errors) {
0678: List<LazyFixList> result = new ArrayList<LazyFixList>();
0679:
0680: for (ErrorDescription e : errors) {
0681: result.add(e.getFixes());
0682: }
0683:
0684: return result;
0685: }
0686:
0687: private void updateAnnotationOnLine(Position line)
0688: throws BadLocationException {
0689: List<ErrorDescription> errorDescriptions = getErrorsForLine(
0690: line, false);
0691:
0692: if (errorDescriptions == null) {
0693: //nothing to do, remove old:
0694: Annotation ann = line2Annotations.remove(line);
0695:
0696: detachAnnotation(ann);
0697: return;
0698: }
0699:
0700: errorDescriptions = getErrorsForLine(line, true);
0701:
0702: List<ErrorDescription> trueErrors = filter(errorDescriptions,
0703: true);
0704: List<ErrorDescription> others = filter(errorDescriptions, false);
0705: boolean hasErrors = !trueErrors.isEmpty();
0706:
0707: //build up the description of the annotation:
0708: StringBuffer description = new StringBuffer();
0709:
0710: concatDescription(trueErrors, description);
0711:
0712: if (!trueErrors.isEmpty() && !others.isEmpty()) {
0713: description.append("\n\n");
0714: }
0715:
0716: concatDescription(others, description);
0717:
0718: Severity mostImportantSeverity;
0719:
0720: if (hasErrors) {
0721: mostImportantSeverity = Severity.ERROR;
0722: } else {
0723: mostImportantSeverity = Severity.HINT;
0724:
0725: for (ErrorDescription e : others) {
0726: if (mostImportantSeverity.compareTo(e.getSeverity()) > 0) {
0727: mostImportantSeverity = e.getSeverity();
0728: }
0729: }
0730: }
0731:
0732: LazyFixList fixes = ErrorDescriptionFactory
0733: .lazyListForDelegates(computeFixes(hasErrors ? trueErrors
0734: : others));
0735:
0736: ParseErrorAnnotation pea = new ParseErrorAnnotation(
0737: mostImportantSeverity, fixes, description.toString(),
0738: line, this );
0739: Annotation previous = line2Annotations.put(line, pea);
0740:
0741: if (previous != null) {
0742: detachAnnotation(previous);
0743: }
0744:
0745: attachAnnotation(line, pea);
0746: }
0747:
0748: void updateHighlightsOnLine(Position line) throws IOException {
0749: List<ErrorDescription> errorDescriptions = getErrorsForLine(
0750: line, false);
0751:
0752: OffsetsBag bag = getBag(doc);
0753:
0754: updateHighlightsOnLine(bag, doc, line, errorDescriptions);
0755: }
0756:
0757: static void updateHighlightsOnLine(OffsetsBag bag,
0758: BaseDocument doc, Position line,
0759: List<ErrorDescription> errorDescriptions)
0760: throws IOException {
0761: try {
0762: int rowStart = line.getOffset();
0763: int rowEnd = Utilities.getRowEnd(doc, rowStart);
0764: int rowHighlightStart = Utilities.getRowFirstNonWhite(doc,
0765: rowStart);
0766: int rowHighlightEnd = Utilities.getRowLastNonWhite(doc,
0767: rowStart) + 1;
0768:
0769: bag.removeHighlights(rowStart, rowEnd, false);
0770:
0771: if (errorDescriptions != null) {
0772: bag.addAllHighlights(computeHighlights(doc,
0773: errorDescriptions).getHighlights(
0774: rowHighlightStart, rowHighlightEnd));
0775: }
0776: } catch (BadLocationException ex) {
0777: throw (IOException) new IOException().initCause(ex);
0778: }
0779: }
0780:
0781: static OffsetsBag computeHighlights(Document doc,
0782: List<ErrorDescription> errorDescriptions)
0783: throws IOException, BadLocationException {
0784: OffsetsBag bag = new OffsetsBag(doc);
0785: for (Severity s : Arrays.asList(Severity.VERIFIER,
0786: Severity.WARNING, Severity.ERROR)) {
0787: List<ErrorDescription> filteredDescriptions = new ArrayList<ErrorDescription>();
0788:
0789: for (ErrorDescription e : errorDescriptions) {
0790: if (e.getSeverity() == s) {
0791: filteredDescriptions.add(e);
0792: }
0793: }
0794:
0795: List<int[]> currentHighlights = new ArrayList<int[]>();
0796:
0797: for (ErrorDescription e : filteredDescriptions) {
0798: int beginOffset = e.getRange().getBegin().getPosition()
0799: .getOffset();
0800: int endOffset = e.getRange().getEnd().getPosition()
0801: .getOffset();
0802:
0803: if (endOffset < beginOffset) {
0804: //see issue #112566
0805: int swap = endOffset;
0806:
0807: endOffset = beginOffset;
0808: beginOffset = swap;
0809:
0810: Logger
0811: .getLogger(AnnotationHolder.class.getName())
0812: .warning(
0813: "Incorrect highlight in ErrorDescription, attach your messages.log to issue #112566: "
0814: + e.toString());
0815: }
0816:
0817: int[] h = new int[] { beginOffset, endOffset };
0818:
0819: OUT: for (Iterator<int[]> it = currentHighlights
0820: .iterator(); it.hasNext() && h != null;) {
0821: int[] hl = it.next();
0822:
0823: switch (detectCollisions(hl, h)) {
0824: case 0:
0825: break;
0826: case 1:
0827: it.remove();
0828: break;
0829: case 2:
0830: h = null; //nothing to add, hl is bigger:
0831: break OUT;
0832: case 4:
0833: case 3:
0834: int start = Math.min(hl[0], h[0]);
0835: int end = Math.max(hl[1], h[1]);
0836:
0837: h = new int[] { start, end };
0838: it.remove();
0839: break;
0840: }
0841: }
0842:
0843: if (h != null) {
0844: currentHighlights.add(h);
0845: }
0846: }
0847:
0848: for (int[] h : currentHighlights) {
0849: if (h[0] <= h[1]) {
0850: bag.addHighlight(h[0], h[1], COLORINGS.get(s));
0851: } else {
0852: //see issue #112566
0853: StringBuilder sb = new StringBuilder();
0854:
0855: for (ErrorDescription e : filteredDescriptions) {
0856: sb.append("[");
0857: sb.append(e.getRange().getBegin().getOffset());
0858: sb.append("-");
0859: sb.append(e.getRange().getEnd().getOffset());
0860: sb.append("]");
0861: }
0862:
0863: sb.append("=>");
0864:
0865: for (int[] h2 : currentHighlights) {
0866: sb.append("[");
0867: sb.append(h2[0]);
0868: sb.append("-");
0869: sb.append(h2[1]);
0870: sb.append("]");
0871: }
0872:
0873: Logger
0874: .getLogger(AnnotationHolder.class.getName())
0875: .warning(
0876: "Incorrect highlight computed, please reopen issue #112566 and attach the following output: "
0877: + sb.toString());
0878: }
0879: }
0880: }
0881:
0882: return bag;
0883: }
0884:
0885: private static int detectCollisions(int[] h1, int[] h2) {
0886: if (h2[1] < h1[0])
0887: return 0;//no collision
0888: if (h1[1] < h2[0])
0889: return 0;//no collision
0890: if (h2[0] < h1[0] && h2[1] > h1[1])
0891: return 1;//h2 encapsulates h1
0892: if (h1[0] < h2[0] && h1[1] > h2[1])
0893: return 2;//h1 encapsulates h2
0894:
0895: if (h1[0] < h2[0])
0896: return 3;//collides
0897: else
0898: return 4;
0899: }
0900:
0901: public void setErrorDescriptions(final String layer,
0902: final Collection<? extends ErrorDescription> errors) {
0903: doc.render(new Runnable() {
0904: public void run() {
0905: try {
0906: setErrorDescriptionsImpl(file, layer, errors);
0907: } catch (IOException e) {
0908: Logger.getLogger("global").log(Level.WARNING,
0909: e.getMessage(), e);
0910: }
0911: }
0912: });
0913: }
0914:
0915: private synchronized void setErrorDescriptionsImpl(FileObject file,
0916: String layer, Collection<? extends ErrorDescription> errors)
0917: throws IOException {
0918: long start = System.currentTimeMillis();
0919:
0920: try {
0921: if (file == null)
0922: return;
0923:
0924: List<ErrorDescription> layersErrors = getErrorsForLayer(layer);
0925:
0926: Set<Position> primaryLines = new HashSet<Position>();
0927: Set<Position> allLines = new HashSet<Position>();
0928:
0929: for (ErrorDescription ed : layersErrors) {
0930: List<Position> lines = errors2Lines.remove(ed);
0931: assert lines != null;
0932:
0933: boolean first = true;
0934:
0935: for (Position line : lines) {
0936: List<ErrorDescription> errorsForLine = getErrorsForLine(
0937: line, false);
0938:
0939: if (errorsForLine != null) {
0940: errorsForLine.remove(ed);
0941: }
0942:
0943: if (first) {
0944: primaryLines.add(line);
0945: }
0946:
0947: allLines.add(line);
0948: first = false;
0949: }
0950: }
0951:
0952: List<ErrorDescription> validatedErrors = new ArrayList<ErrorDescription>();
0953:
0954: for (ErrorDescription ed : errors) {
0955: if (ed == null) {
0956: Logger
0957: .getLogger(AnnotationHolder.class.getName())
0958: .log(
0959: Level.WARNING,
0960: "'null' ErrorDescription in layer {0}.",
0961: layer); //NOI18N
0962: continue;
0963: }
0964:
0965: if (ed.getRange() == null)
0966: continue;
0967:
0968: validatedErrors.add(ed);
0969:
0970: List<Position> lines = new ArrayList<Position>();
0971: int startLine = ed.getRange().getBegin().getLine();
0972: int endLine = ed.getRange().getEnd().getLine();
0973:
0974: for (int cntr = startLine; cntr <= endLine; cntr++) {
0975: Position p = getPosition(cntr, true);
0976: lines.add(p);
0977: }
0978:
0979: errors2Lines.put(ed, lines);
0980:
0981: boolean first = true;
0982:
0983: for (Position line : lines) {
0984: getErrorsForLine(line, true).add(ed);
0985:
0986: if (first) {
0987: primaryLines.add(line);
0988: }
0989:
0990: allLines.add(line);
0991: first = false;
0992: }
0993: }
0994:
0995: layersErrors.clear();
0996: layersErrors.addAll(validatedErrors);
0997:
0998: for (Position line : primaryLines) {
0999: updateAnnotationOnLine(line);
1000: }
1001:
1002: for (Position line : allLines) {
1003: updateHighlightsOnLine(line);
1004: }
1005:
1006: updateVisibleRanges();
1007: } catch (BadLocationException ex) {
1008: throw (IOException) new IOException().initCause(ex);
1009: } finally {
1010: long end = System.currentTimeMillis();
1011: Logger.getLogger("TIMER").log(Level.FINE,
1012: "Errors update for " + layer,
1013: new Object[] { file, end - start });
1014: }
1015: }
1016:
1017: private List<Reference<Position>> knownPositions = new ArrayList<Reference<Position>>();
1018:
1019: private static class Abort extends RuntimeException {
1020: @Override
1021: public synchronized Throwable fillInStackTrace() {
1022: return this ;
1023: }
1024: }
1025:
1026: private static RuntimeException ABORT = new Abort();
1027:
1028: private synchronized int findPositionGE(int offset) {
1029: while (true) {
1030: try {
1031: int index = Collections.binarySearch(knownPositions,
1032: offset, new PositionComparator());
1033:
1034: if (index >= 0) {
1035: return index;
1036: } else {
1037: return -(index + 1);
1038: }
1039: } catch (Abort a) {
1040: Logger.getLogger(AnnotationHolder.class.getName()).log(
1041: Level.FINE,
1042: "a null Position detected - clearing");
1043: int removedCount = 0;
1044: for (Iterator<Reference<Position>> it = knownPositions
1045: .iterator(); it.hasNext();) {
1046: if (it.next().get() == null) {
1047: removedCount++;
1048: it.remove();
1049: }
1050: }
1051: Logger.getLogger(AnnotationHolder.class.getName()).log(
1052: Level.FINE,
1053: "clearing finished, {0} positions cleared",
1054: removedCount);
1055: }
1056: }
1057: }
1058:
1059: private synchronized Position getPosition(int lineNumber,
1060: boolean create) throws BadLocationException {
1061: try {
1062: while (true) {
1063: int lineStart = Utilities.getRowStartFromLineOffset(
1064: doc, lineNumber);
1065: try {
1066: int index = Collections.binarySearch(
1067: knownPositions, lineStart,
1068: new PositionComparator());
1069:
1070: if (index >= 0) {
1071: Reference<Position> r = knownPositions
1072: .get(index);
1073: Position p = r.get();
1074:
1075: if (p != null) {
1076: return p;
1077: }
1078: }
1079:
1080: if (!create)
1081: return null;
1082:
1083: Position p = NbDocument.createPosition(doc,
1084: lineStart, Position.Bias.Forward);
1085:
1086: knownPositions.add(-(index + 1),
1087: new WeakReference<Position>(p));
1088:
1089: Logger.getLogger("TIMER").log(Level.FINE,
1090: "Annotation Holder - Line Token",
1091: new Object[] { file, p });
1092:
1093: return p;
1094: } catch (Abort a) {
1095: Logger
1096: .getLogger(AnnotationHolder.class.getName())
1097: .log(Level.FINE,
1098: "a null Position detected - clearing");
1099: int removedCount = 0;
1100: for (Iterator<Reference<Position>> it = knownPositions
1101: .iterator(); it.hasNext();) {
1102: if (it.next().get() == null) {
1103: removedCount++;
1104: it.remove();
1105: }
1106: }
1107: Logger
1108: .getLogger(AnnotationHolder.class.getName())
1109: .log(
1110: Level.FINE,
1111: "clearing finished, {0} positions cleared",
1112: removedCount);
1113: }
1114: }
1115: } finally {
1116: Logger.getLogger(AnnotationHolder.class.getName()).log(
1117: Level.FINE, "knownPositions.size={0}",
1118: knownPositions.size());
1119: }
1120: }
1121:
1122: public synchronized boolean hasErrors() {
1123: for (ErrorDescription e : errors2Lines.keySet()) {
1124: if (e.getSeverity() == Severity.ERROR)
1125: return true;
1126: }
1127:
1128: return false;
1129: }
1130:
1131: public synchronized List<ErrorDescription> getErrors() {
1132: return new ArrayList<ErrorDescription>(errors2Lines.keySet());
1133: }
1134:
1135: public synchronized List<Annotation> getAnnotations() {
1136: return new ArrayList<Annotation>(line2Annotations.values());
1137: }
1138:
1139: public synchronized List<ErrorDescription> getErrorsGE(int offset) {
1140: try {
1141: Position current = null;
1142: int index = -1;
1143: int startOffset = Utilities.getRowStart(doc, offset);
1144:
1145: while (current == null) {
1146: index = findPositionGE(startOffset);
1147:
1148: if (knownPositions.size() == 0) {
1149: break;
1150: }
1151: if (index == knownPositions.size()) {
1152: return Collections.emptyList();
1153: }
1154: current = knownPositions.get(index).get();
1155: }
1156:
1157: if (current == null) {
1158: //nothing to do:
1159: return Collections.emptyList();
1160: }
1161:
1162: assert index != (-1);
1163:
1164: List<ErrorDescription> errors = line2Errors.get(current);
1165:
1166: if (errors != null) {
1167: SortedMap<Integer, List<ErrorDescription>> sortedErrors = new TreeMap<Integer, List<ErrorDescription>>();
1168:
1169: for (ErrorDescription ed : errors) {
1170: List<ErrorDescription> errs = sortedErrors.get(ed
1171: .getRange().getBegin().getOffset());
1172:
1173: if (errs == null) {
1174: sortedErrors
1175: .put(
1176: ed.getRange().getBegin()
1177: .getOffset(),
1178: errs = new LinkedList<ErrorDescription>());
1179: }
1180:
1181: errs.add(ed);
1182: }
1183:
1184: SortedMap<Integer, List<ErrorDescription>> tail = sortedErrors
1185: .tailMap(offset);
1186:
1187: if (!tail.isEmpty()) {
1188: Integer k = tail.firstKey();
1189:
1190: return new LinkedList<ErrorDescription>(
1191: sortedErrors.get(k));
1192: }
1193: }
1194:
1195: //try next line:
1196: int endOffset = Utilities.getRowEnd(doc, offset);
1197:
1198: return getErrorsGE(endOffset + 1);
1199: } catch (BadLocationException ex) {
1200: Exceptions.printStackTrace(ex);
1201: return Collections.emptyList();
1202: }
1203: }
1204:
1205: private static final RequestProcessor INSTANCE = new RequestProcessor(
1206: "AnnotationHolder");
1207:
1208: public static OffsetsBag getBag(Document doc) {
1209: OffsetsBag ob = (OffsetsBag) doc
1210: .getProperty(AnnotationHolder.class);
1211:
1212: if (ob == null) {
1213: doc.putProperty(AnnotationHolder.class,
1214: ob = new OffsetsBag(doc));
1215: }
1216:
1217: return ob;
1218: }
1219:
1220: public int lineNumber(final Position offset) {
1221: final int[] result = new int[] { -1 };
1222:
1223: doc.render(new Runnable() {
1224: public void run() {
1225: try {
1226: result[0] = Utilities.getLineOffset(doc, offset
1227: .getOffset());
1228: } catch (BadLocationException ex) {
1229: Exceptions.printStackTrace(ex);
1230: }
1231: }
1232: });
1233:
1234: return result[0];
1235: }
1236:
1237: private static class PositionComparator implements
1238: Comparator<Object> {
1239:
1240: private PositionComparator() {
1241: }
1242:
1243: public int compare(Object o1, Object o2) {
1244: int left = -1;
1245:
1246: if (o1 instanceof Reference) {
1247: Position value = (Position) ((Reference) o1).get();
1248:
1249: if (value == null) {
1250: //already collected...
1251: throw ABORT;
1252: }
1253:
1254: left = value.getOffset();
1255: }
1256:
1257: if (o1 instanceof Integer) {
1258: left = ((Integer) o1);
1259: }
1260:
1261: assert left != -1;
1262:
1263: int right = -1;
1264:
1265: if (o2 instanceof Reference) {
1266: Position value = (Position) ((Reference) o2).get();
1267:
1268: if (value == null) {
1269: //already collected...
1270: throw ABORT;
1271: }
1272:
1273: right = value.getOffset();
1274: }
1275:
1276: if (o2 instanceof Integer) {
1277: right = ((Integer) o2);
1278: }
1279:
1280: assert right != -1;
1281:
1282: return left - right;
1283: }
1284: }
1285:
1286: private static final class TooltipResolver implements
1287: HighlightAttributeValue<String> {
1288:
1289: public String getValue(final JTextComponent component,
1290: final Document document, Object attributeKey,
1291: final int startOffset, final int endOffset) {
1292: final Object source = document
1293: .getProperty(Document.StreamDescriptionProperty);
1294:
1295: if (!(source instanceof DataObject)
1296: || !(document instanceof BaseDocument)) {
1297: return null;
1298: }
1299:
1300: final String[] result = new String[1];
1301:
1302: document.render(new Runnable() {
1303: public void run() {
1304: try {
1305: int lineNumber = Utilities.getLineOffset(
1306: (BaseDocument) document, startOffset);
1307:
1308: if (lineNumber < 0) {
1309: return;
1310: }
1311:
1312: AnnotationHolder h = AnnotationHolder
1313: .getInstance(((DataObject) source)
1314: .getPrimaryFile());
1315:
1316: synchronized (h) {
1317: Position p = h.getPosition(lineNumber,
1318: false);
1319:
1320: if (p == null) {
1321: return;
1322: }
1323:
1324: List<ErrorDescription> errors = h.line2Errors
1325: .get(p);
1326:
1327: if (errors == null || errors.isEmpty()) {
1328: return;
1329: }
1330:
1331: List<ErrorDescription> trueErrors = new LinkedList<ErrorDescription>();
1332: List<ErrorDescription> others = new LinkedList<ErrorDescription>();
1333:
1334: for (ErrorDescription ed : errors) {
1335: if (ed == null)
1336: continue;
1337:
1338: PositionBounds pb = ed.getRange();
1339:
1340: if (startOffset > pb.getEnd()
1341: .getOffset()
1342: || pb.getBegin().getOffset() > endOffset) {
1343: continue;
1344: }
1345:
1346: if (ed.getSeverity() == Severity.ERROR) {
1347: trueErrors.add(ed);
1348: } else {
1349: others.add(ed);
1350: }
1351: }
1352:
1353: //build up the description of the annotation:
1354: StringBuffer description = new StringBuffer();
1355:
1356: concatDescription(trueErrors, description);
1357:
1358: if (!trueErrors.isEmpty()
1359: && !others.isEmpty()) {
1360: description.append("\n\n");
1361: }
1362:
1363: concatDescription(others, description);
1364:
1365: result[0] = description.toString();
1366: }
1367: } catch (BadLocationException ex) {
1368: Exceptions.printStackTrace(ex);
1369: }
1370: }
1371: });
1372:
1373: return result[0];
1374: }
1375:
1376: }
1377:
1378: }
|