PageRenderTime 52ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/projects/netbeans-7.3/spi.editor.hints/src/org/netbeans/modules/editor/hints/AnnotationHolder.java

https://gitlab.com/essere.lab.public/qualitas.class-corpus
Java | 1348 lines | 1023 code | 260 blank | 65 comment | 223 complexity | 0b582aa4f30dbdf5d0d999ec11e2de3d MD5 | raw file
  1. /*
  2. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  3. *
  4. * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved.
  5. *
  6. * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
  7. * Other names may be trademarks of their respective owners.
  8. *
  9. * The contents of this file are subject to the terms of either the GNU
  10. * General Public License Version 2 only ("GPL") or the Common
  11. * Development and Distribution License("CDDL") (collectively, the
  12. * "License"). You may not use this file except in compliance with the
  13. * License. You can obtain a copy of the License at
  14. * http://www.netbeans.org/cddl-gplv2.html
  15. * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
  16. * specific language governing permissions and limitations under the
  17. * License. When distributing the software, include this License Header
  18. * Notice in each file and include the License file at
  19. * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
  20. * particular file as subject to the "Classpath" exception as provided
  21. * by Oracle in the GPL Version 2 section of the License file that
  22. * accompanied this code. If applicable, add the following below the
  23. * License Header, with the fields enclosed by brackets [] replaced by
  24. * your own identifying information:
  25. * "Portions Copyrighted [year] [name of copyright owner]"
  26. *
  27. * Contributor(s):
  28. *
  29. * The Original Software is NetBeans. The Initial Developer of the Original
  30. * Software is Sun Microsystems, Inc. Portions Copyright 1997-2010 Sun
  31. * Microsystems, Inc. All Rights Reserved.
  32. *
  33. * If you wish your version of this file to be governed by only the CDDL
  34. * or only the GPL Version 2, indicate your decision by adding
  35. * "[Contributor] elects to include this software in this distribution
  36. * under the [CDDL or GPL Version 2] license." If you do not indicate a
  37. * single choice of license, a recipient has the option to distribute
  38. * your version of this file under either the CDDL, the GPL Version 2 or
  39. * to extend the choice of license to its licensees as provided above.
  40. * However, if you add GPL Version 2 code and therefore, elected the GPL
  41. * Version 2 license, then the option applies only if the new code is
  42. * made subject to such option by the copyright holder.
  43. */
  44. package org.netbeans.modules.editor.hints;
  45. import java.awt.Color;
  46. import java.awt.Container;
  47. import java.awt.Dimension;
  48. import java.awt.Point;
  49. import java.beans.PropertyChangeEvent;
  50. import java.beans.PropertyChangeListener;
  51. import java.io.IOException;
  52. import java.lang.ref.Reference;
  53. import java.lang.ref.WeakReference;
  54. import java.util.ArrayList;
  55. import java.util.Arrays;
  56. import java.util.Collection;
  57. import java.util.Collections;
  58. import java.util.Comparator;
  59. import java.util.EnumMap;
  60. import java.util.HashMap;
  61. import java.util.HashSet;
  62. import java.util.IdentityHashMap;
  63. import java.util.Iterator;
  64. import java.util.LinkedList;
  65. import java.util.List;
  66. import java.util.Map;
  67. import java.util.Map.Entry;
  68. import java.util.Set;
  69. import java.util.SortedMap;
  70. import java.util.TreeMap;
  71. import java.util.logging.Level;
  72. import java.util.logging.Logger;
  73. import javax.swing.JEditorPane;
  74. import javax.swing.JViewport;
  75. import javax.swing.SwingUtilities;
  76. import javax.swing.event.ChangeEvent;
  77. import javax.swing.event.ChangeListener;
  78. import javax.swing.event.DocumentEvent;
  79. import javax.swing.event.DocumentListener;
  80. import javax.swing.text.AttributeSet;
  81. import javax.swing.text.BadLocationException;
  82. import javax.swing.text.Document;
  83. import javax.swing.text.Element;
  84. import javax.swing.text.JTextComponent;
  85. import javax.swing.text.Position;
  86. import javax.swing.text.StyledDocument;
  87. import org.netbeans.api.editor.EditorRegistry;
  88. import org.netbeans.api.editor.mimelookup.MimeLookup;
  89. import org.netbeans.api.editor.settings.AttributesUtilities;
  90. import org.netbeans.api.editor.settings.EditorStyleConstants;
  91. import org.netbeans.api.editor.settings.FontColorSettings;
  92. import org.netbeans.editor.BaseDocument;
  93. import org.netbeans.editor.Utilities;
  94. import org.netbeans.lib.editor.util.swing.DocumentListenerPriority;
  95. import org.netbeans.lib.editor.util.swing.DocumentUtilities;
  96. import org.netbeans.spi.editor.highlighting.HighlightAttributeValue;
  97. import org.netbeans.spi.editor.highlighting.support.OffsetsBag;
  98. import org.netbeans.spi.editor.hints.ErrorDescription;
  99. import org.netbeans.spi.editor.hints.ErrorDescriptionFactory;
  100. import org.netbeans.spi.editor.hints.LazyFixList;
  101. import org.netbeans.spi.editor.hints.Severity;
  102. import org.openide.cookies.EditorCookie;
  103. import org.openide.filesystems.FileObject;
  104. import org.openide.loaders.DataObject;
  105. import org.openide.text.Annotation;
  106. import org.openide.text.NbDocument;
  107. import org.openide.util.RequestProcessor;
  108. import org.openide.util.WeakListeners;
  109. import org.openide.filesystems.FileUtil;
  110. import org.openide.text.PositionBounds;
  111. import org.openide.util.Exceptions;
  112. import org.openide.util.Lookup;
  113. import org.openide.util.LookupEvent;
  114. import org.openide.util.LookupListener;
  115. import org.openide.util.NbBundle;
  116. import org.openide.util.RequestProcessor.Task;
  117. /**
  118. *
  119. * @author Jan Lahoda
  120. */
  121. public final class AnnotationHolder implements ChangeListener, DocumentListener {
  122. static final Logger LOG = Logger.getLogger(AnnotationHolder.class.getName());
  123. // mimte-type --> coloring
  124. private static Map<String, Map<Severity, AttributeSet>> COLORINGS =
  125. Collections.synchronizedMap(new HashMap<String, Map<Severity, AttributeSet>>());
  126. // mime-type --> listener
  127. private static Map<String, LookupListener> COLORINGS_LISTENERS =
  128. Collections.synchronizedMap(new HashMap<String, LookupListener>());
  129. private static final AttributeSet DEFUALT_ERROR =
  130. AttributesUtilities.createImmutable(EditorStyleConstants.WaveUnderlineColor, new Color(0xFF, 0x00, 0x00));
  131. private static final AttributeSet DEFUALT_WARNING =
  132. AttributesUtilities.createImmutable(EditorStyleConstants.WaveUnderlineColor, new Color(0xC0, 0xC0, 0x00));
  133. private static final AttributeSet DEFUALT_VERIFIER =
  134. AttributesUtilities.createImmutable(EditorStyleConstants.WaveUnderlineColor, new Color(0xFF, 0xD5, 0x55));
  135. private static final AttributeSet TOOLTIP =
  136. AttributesUtilities.createImmutable(EditorStyleConstants.Tooltip, new TooltipResolver());
  137. private Map<ErrorDescription, List<Position>> errors2Lines;
  138. private Map<Position, List<ErrorDescription>> line2Errors;
  139. private Map<Position, ParseErrorAnnotation> line2Annotations;
  140. private Map<String, List<ErrorDescription>> layer2Errors;
  141. private Set<JEditorPane> openedComponents;
  142. private FileObject file;
  143. private DataObject od;
  144. private BaseDocument doc;
  145. private static Map<DataObject, AnnotationHolder> file2Holder = new HashMap<DataObject, AnnotationHolder>();
  146. static {
  147. EditorRegistry.addPropertyChangeListener(new PropertyChangeListener() {
  148. @Override public void propertyChange(PropertyChangeEvent evt) {
  149. if (evt.getPropertyName() == null || EditorRegistry.COMPONENT_REMOVED_PROPERTY.equals(evt.getPropertyName())) {
  150. resolveAllComponents();
  151. } else if (EditorRegistry.FOCUS_GAINED_PROPERTY.equals(evt.getPropertyName())) {
  152. JTextComponent c = EditorRegistry.focusedComponent();
  153. Object o = c.getDocument().getProperty(Document.StreamDescriptionProperty);
  154. @SuppressWarnings("element-type-mismatch")
  155. AnnotationHolder holder = file2Holder.get(o);
  156. if (holder != null) {
  157. holder.maybeAddComponent(c);
  158. }
  159. }
  160. }
  161. });
  162. }
  163. private static void resolveAllComponents() {
  164. Map<DataObject, Set<JTextComponent>> file2Components = new HashMap<DataObject, Set<JTextComponent>>();
  165. for (JTextComponent c : EditorRegistry.componentList()) {
  166. Object o = c.getDocument().getProperty(Document.StreamDescriptionProperty);
  167. if (!(o instanceof DataObject)) continue;
  168. DataObject od = (DataObject) o;
  169. Set<JTextComponent> components = file2Components.get(od);
  170. if (components == null) {
  171. file2Components.put(od, components = new HashSet<JTextComponent>());
  172. }
  173. components.add(c);
  174. }
  175. Map<DataObject, AnnotationHolder> file2HolderCopy = new HashMap<DataObject, AnnotationHolder>();
  176. synchronized (AnnotationHolder.class) {
  177. file2HolderCopy.putAll(file2Holder);
  178. }
  179. for (Entry<DataObject, AnnotationHolder> e : file2HolderCopy.entrySet()) {
  180. Set<JTextComponent> components = file2Components.get(e.getKey());
  181. if (components == null) components = Collections.emptySet();
  182. e.getValue().setComponents(components);
  183. }
  184. }
  185. public static synchronized AnnotationHolder getInstance(FileObject file) {
  186. if (file == null)
  187. return null;
  188. try {
  189. DataObject od = DataObject.find(file);
  190. AnnotationHolder result = file2Holder.get(od);
  191. if (result == null) {
  192. EditorCookie editorCookie = od.getCookie(EditorCookie.class);
  193. if (editorCookie == null) {
  194. LOG.log(Level.WARNING,
  195. "No EditorCookie.Observable for file: {0}", FileUtil.getFileDisplayName(file)); //NOI18N
  196. } else {
  197. Document doc = editorCookie.getDocument();
  198. if (doc instanceof BaseDocument) {
  199. file2Holder.put(od, result = new AnnotationHolder(file, od, (BaseDocument) doc));
  200. }
  201. }
  202. }
  203. return result;
  204. } catch (IOException e) {
  205. LOG.log(Level.INFO, null, e);
  206. return null;
  207. }
  208. }
  209. @SuppressWarnings("LeakingThisInConstructor")
  210. private AnnotationHolder(FileObject file, DataObject od, BaseDocument doc) {
  211. if (file == null)
  212. return ;
  213. init();
  214. this.file = file;
  215. this.od = od;
  216. this.doc = doc;
  217. getBag(doc);
  218. DocumentUtilities.addPriorityDocumentListener(this.doc, this, DocumentListenerPriority.AFTER_CARET_UPDATE);
  219. SwingUtilities.invokeLater(new Runnable() {
  220. @Override public void run() {
  221. resolveAllComponents();
  222. }
  223. });
  224. // LOG.log(Level.FINE, null, new Throwable("Creating AnnotationHolder for " + file.getPath()));
  225. Logger.getLogger("TIMER").log(Level.FINE, "Annotation Holder", //NOI18N
  226. new Object[] {file, this});
  227. }
  228. private synchronized void init() {
  229. errors2Lines = new IdentityHashMap<ErrorDescription, List<Position>>();
  230. line2Errors = new HashMap<Position, List<ErrorDescription>>();
  231. line2Annotations = new HashMap<Position, ParseErrorAnnotation>();
  232. layer2Errors = new HashMap<String, List<ErrorDescription>>();
  233. openedComponents = new HashSet<JEditorPane>();
  234. }
  235. public void stateChanged(ChangeEvent evt) {
  236. updateVisibleRanges();
  237. }
  238. Attacher attacher = new NbDocumentAttacher();
  239. void attachAnnotation(Position line, ParseErrorAnnotation a, boolean synchronous) throws BadLocationException {
  240. attacher.attachAnnotation(line, a, synchronous);
  241. }
  242. void detachAnnotation(ParseErrorAnnotation a, boolean synchronous) {
  243. attacher.detachAnnotation(a, synchronous);
  244. }
  245. static interface Attacher {
  246. public void attachAnnotation(Position line, ParseErrorAnnotation a, boolean synchronous) throws BadLocationException;
  247. public void detachAnnotation(ParseErrorAnnotation a, boolean synchronous);
  248. }
  249. final class NbDocumentAttacher implements Attacher {
  250. public void attachAnnotation(Position lineStart, ParseErrorAnnotation a, boolean synchronous) throws BadLocationException {
  251. addToToDo(new ToDo(lineStart, a), synchronous);
  252. }
  253. public void detachAnnotation(ParseErrorAnnotation a, boolean synchronous) {
  254. addToToDo(new ToDo(null, a), synchronous);
  255. }
  256. private void addToToDo(ToDo item, boolean synchronous) {
  257. if (synchronous) {
  258. attachDetach(item);
  259. return ;
  260. }
  261. synchronized (todoLock) {
  262. if (todo == null) {
  263. todo = new ArrayList<ToDo>();
  264. ATTACHER.schedule(50);
  265. }
  266. todo.add(item);
  267. }
  268. }
  269. }
  270. private static class ToDo {
  271. private final Position lineStart;
  272. private final ParseErrorAnnotation a;
  273. public ToDo(Position lineStart, ParseErrorAnnotation a) {
  274. this.lineStart = lineStart;
  275. this.a = a;
  276. }
  277. }
  278. private void attachDetach(ToDo t) {
  279. if (t.lineStart != null) {
  280. if (LOG.isLoggable(Level.FINE)) {
  281. LOG.fine("addAnnotation: pos=" + t.lineStart.getOffset() + ", a="+ t.a + ", doc=" +
  282. System.identityHashCode(doc) + "\n");
  283. }
  284. t.a.attachAnnotation((StyledDocument) doc, t.lineStart);
  285. } else {
  286. if (doc != null) {
  287. if (LOG.isLoggable(Level.FINE)) {
  288. LOG.fine("removeAnnotation: a=" + t.a + ", doc=" + System.identityHashCode(doc) + "\n");
  289. }
  290. t.a.detachAnnotation((StyledDocument) doc);
  291. }
  292. }
  293. }
  294. private static final RequestProcessor ATTACHING_THREAD = new RequestProcessor(AnnotationHolder.class.getName(), 1, false, false);
  295. private List<ToDo> todo;
  296. private final Object todoLock = new Object();
  297. private final Task ATTACHER = ATTACHING_THREAD.create(new Runnable() {
  298. @Override public void run() {
  299. List<ToDo> todo = null;
  300. synchronized (todoLock) {
  301. todo = AnnotationHolder.this.todo;
  302. AnnotationHolder.this.todo = null;
  303. }
  304. if (todo == null) return;
  305. for (ToDo t : todo) {
  306. attachDetach(t);
  307. }
  308. }
  309. });
  310. private synchronized void clearAll() {
  311. //remove all annotations:
  312. for (ParseErrorAnnotation a : line2Annotations.values()) {
  313. detachAnnotation(a, false);
  314. }
  315. line2Annotations.clear();
  316. file2Holder.remove(od);
  317. DocumentUtilities.removePriorityDocumentListener(this.doc, this, DocumentListenerPriority.AFTER_CARET_UPDATE);
  318. getBag(doc).clear();
  319. }
  320. private synchronized void maybeAddComponent(JTextComponent c) {
  321. if (!(c instanceof JEditorPane)) return;
  322. JEditorPane pane = (JEditorPane) c;
  323. if (!openedComponents.add(pane)) return;
  324. addViewportListener(pane);
  325. updateVisibleRanges();
  326. }
  327. private void addViewportListener(JEditorPane pane) {
  328. Container parent = pane.getParent();
  329. if (parent instanceof JViewport) {
  330. JViewport viewport = (JViewport) parent;
  331. viewport.addChangeListener(WeakListeners.change(AnnotationHolder.this, viewport));
  332. }
  333. }
  334. private synchronized void setComponents(Set<JTextComponent> newComponents) {
  335. if (newComponents.isEmpty()) {
  336. clearAll();
  337. return;
  338. }
  339. Set<JEditorPane> addedPanes = new HashSet<JEditorPane>();
  340. for (JTextComponent c : newComponents) {
  341. if (!(c instanceof JEditorPane)) continue;
  342. addedPanes.add((JEditorPane) c);
  343. }
  344. Set<JEditorPane> removedPanes = new HashSet<JEditorPane>(openedComponents);
  345. removedPanes.removeAll(addedPanes);
  346. addedPanes.removeAll(openedComponents);
  347. for (JEditorPane pane : addedPanes) {
  348. addViewportListener(pane);
  349. }
  350. openedComponents.removeAll(removedPanes);
  351. openedComponents.addAll(addedPanes);
  352. updateVisibleRanges();
  353. }
  354. public synchronized void insertUpdate(DocumentEvent e) {
  355. try {
  356. int offset = Utilities.getRowStart(doc, e.getOffset());
  357. Set<Position> modifiedLines = new HashSet<Position>();
  358. int index = findPositionGE(offset);
  359. if (index == knownPositions.size())
  360. return ;
  361. Position line = knownPositions.get(index).get();
  362. if (line == null)
  363. return ;
  364. int endOffset = Utilities.getRowEnd(doc, e.getOffset() + e.getLength());
  365. if (endOffset < line.getOffset())
  366. return;
  367. clearLineErrors(line, modifiedLines);
  368. //make sure the highlights are removed even for multi-line inserts:
  369. try {
  370. int rowStart = e.getOffset();
  371. int rowEnd = Utilities.getRowEnd(doc, e.getOffset() + e.getLength());
  372. getBag(doc).removeHighlights(rowStart, rowEnd, false);
  373. } catch (BadLocationException ex) {
  374. throw new IOException(ex);
  375. }
  376. for (Position lineToken : modifiedLines) {
  377. updateAnnotationOnLine(lineToken, false);
  378. updateHighlightsOnLine(lineToken);
  379. }
  380. } catch (IOException ex) {
  381. Exceptions.printStackTrace(ex);
  382. } catch (BadLocationException ex) {
  383. Exceptions.printStackTrace(ex);
  384. }
  385. }
  386. public synchronized void removeUpdate(DocumentEvent e) {
  387. try {
  388. Position current = null;
  389. int index = -1;
  390. int startOffset = Utilities.getRowStart(doc, e.getOffset());
  391. while (current == null) {
  392. index = findPositionGE(startOffset);
  393. if (knownPositions.isEmpty()) {
  394. break;
  395. }
  396. if (index == knownPositions.size()) {
  397. return;
  398. }
  399. current = knownPositions.get(index).get();
  400. }
  401. if (current == null) {
  402. //nothing to do:
  403. return;
  404. }
  405. int endOffset = Utilities.getRowEnd(doc, e.getOffset());
  406. if (endOffset < current.getOffset())
  407. return;
  408. assert index != (-1);
  409. //find the first:
  410. while (index > 0) {
  411. Position minusOne = knownPositions.get(index - 1).get();
  412. if (minusOne == null) {
  413. index--;
  414. continue;
  415. }
  416. if (minusOne.getOffset() != current.getOffset()) {
  417. break;
  418. }
  419. index--;
  420. }
  421. Set<Position> modifiedLinesTokens = new HashSet<Position>();
  422. while (index < knownPositions.size()) {
  423. Position next = knownPositions.get(index).get();
  424. if (next == null) {
  425. index++;
  426. continue;
  427. }
  428. if (next.getOffset() != current.getOffset()) {
  429. break;
  430. }
  431. modifiedLinesTokens.add(next);
  432. index++;
  433. }
  434. for (Position line : new LinkedList<Position>(modifiedLinesTokens)) {
  435. clearLineErrors(line, modifiedLinesTokens);
  436. }
  437. for (Position line : modifiedLinesTokens) {
  438. updateAnnotationOnLine(line, false);
  439. updateHighlightsOnLine(line);
  440. }
  441. } catch (IOException ex) {
  442. Exceptions.printStackTrace(ex);
  443. } catch (BadLocationException ex) {
  444. Exceptions.printStackTrace(ex);
  445. }
  446. }
  447. private void clearLineErrors(Position line, Set<Position> modifiedLinesTokens) {
  448. List<ErrorDescription> eds = getErrorsForLine(line, false);
  449. if (eds == null)
  450. return ;
  451. eds = new LinkedList<ErrorDescription>(eds);
  452. for (ErrorDescription ed : eds) {
  453. List<Position> lines = errors2Lines.remove(ed);
  454. if (lines == null) { //# 180222
  455. LOG.log(Level.WARNING, "Inconsistent error2Lines for file {1}.", new Object[] {file.getPath()}); // NOI18N
  456. continue;
  457. }
  458. for (Position i : lines) {
  459. line2Errors.get(i).remove(ed);
  460. modifiedLinesTokens.add(i);
  461. }
  462. for (List<ErrorDescription> edsForLayer : layer2Errors.values()) {
  463. edsForLayer.remove(ed);
  464. }
  465. }
  466. line2Errors.remove(line);
  467. }
  468. public void changedUpdate(DocumentEvent e) {
  469. //ignored
  470. }
  471. private void updateVisibleRanges() {
  472. SwingUtilities.invokeLater(new Runnable() {
  473. public void run() {
  474. long startTime = System.currentTimeMillis();
  475. final List<int[]> visibleRanges = new ArrayList<int[]>();
  476. doc.render(new Runnable() {
  477. public void run() {
  478. synchronized(AnnotationHolder.this) {
  479. for (JEditorPane pane : openedComponents) {
  480. Container parent = pane.getParent();
  481. if (parent instanceof JViewport) {
  482. JViewport viewport = (JViewport) parent;
  483. Point start = viewport.getViewPosition();
  484. Dimension size = viewport.getExtentSize();
  485. Point end = new Point(start.x + size.width, start.y + size.height);
  486. int startPosition = pane.viewToModel(start);
  487. int endPosition = pane.viewToModel(end);
  488. //TODO: check differences against last:
  489. visibleRanges.add(new int[]{startPosition, endPosition});
  490. }
  491. }
  492. }
  493. }
  494. });
  495. INSTANCE.post(new Runnable() {
  496. public void run() {
  497. for (int[] span : visibleRanges) {
  498. updateAnnotations(span[0], span[1]);
  499. }
  500. }
  501. });
  502. long endTime = System.currentTimeMillis();
  503. LOG.log(Level.FINE, "updateVisibleRanges: time={0}", endTime - startTime); //NOI18N
  504. }
  505. });
  506. }
  507. private void updateAnnotations(final int startPosition, final int endPosition) {
  508. long startTime = System.currentTimeMillis();
  509. final List<ErrorDescription> errorsToUpdate = new ArrayList<ErrorDescription>();
  510. doc.render(new Runnable() {
  511. public void run() {
  512. synchronized (AnnotationHolder.this) {
  513. try {
  514. if (doc.getLength() == 0) {
  515. return ;
  516. }
  517. int start = startPosition < doc.getLength() ? startPosition : (doc.getLength() - 1);
  518. int end = endPosition < doc.getLength() ? endPosition : (doc.getLength() - 1);
  519. if (start < 0) start = 0;
  520. if (end < 0) end = 0;
  521. int startLine = Utilities.getRowStart(doc, start);
  522. int endLine = Utilities.getRowEnd(doc, end) + 1;
  523. int index = findPositionGE(startLine);
  524. while (index < knownPositions.size()) {
  525. Reference<Position> r = knownPositions.get(index++);
  526. if (r==null)
  527. continue;
  528. Position lineToken = r.get();
  529. if (lineToken == null)
  530. continue;
  531. if (lineToken.getOffset() > endLine)
  532. break;
  533. List<ErrorDescription> errors = line2Errors.get(lineToken);
  534. if (errors != null) {
  535. errorsToUpdate.addAll(errors);
  536. }
  537. }
  538. } catch (BadLocationException e) {
  539. Exceptions.printStackTrace(e);
  540. }
  541. }
  542. }
  543. });
  544. LOG.log(Level.FINE, "updateAnnotations: errorsToUpdate={0}", errorsToUpdate); //NOI18N
  545. for (ErrorDescription e : errorsToUpdate) {
  546. //TODO: #115340: e can be for an unknown reason null:
  547. if (e == null) {
  548. continue;
  549. }
  550. LazyFixList l = e.getFixes();
  551. if (l.probablyContainsFixes() && !l.isComputed()) {
  552. l.getFixes();
  553. }
  554. }
  555. long endTime = System.currentTimeMillis();
  556. LOG.log(Level.FINE, "updateAnnotations: time={0}", endTime - startTime); //NOI18N
  557. }
  558. private List<ErrorDescription> getErrorsForLayer(String layer) {
  559. List<ErrorDescription> errors = layer2Errors.get(layer);
  560. if (errors == null) {
  561. layer2Errors.put(layer, errors = new ArrayList<ErrorDescription>());
  562. }
  563. return errors;
  564. }
  565. private List<ErrorDescription> getErrorsForLine(Position line, boolean create) {
  566. List<ErrorDescription> errors = line2Errors.get(line);
  567. if (errors == null && create) {
  568. line2Errors.put(line, errors = new ArrayList<ErrorDescription>());
  569. }
  570. if (errors != null && errors.isEmpty() && !create) {
  571. //clean:
  572. line2Errors.remove(line);
  573. errors = null;
  574. }
  575. return errors;
  576. }
  577. private static List<ErrorDescription> filter(List<ErrorDescription> errors, boolean onlyErrors) {
  578. List<ErrorDescription> result = new ArrayList<ErrorDescription>();
  579. for (ErrorDescription e : errors) {
  580. if (e.getSeverity() == Severity.ERROR) {
  581. if (onlyErrors)
  582. result.add(e);
  583. } else {
  584. if (!onlyErrors)
  585. result.add(e);
  586. }
  587. }
  588. return result;
  589. }
  590. private static void concatDescription(List<ErrorDescription> errors, StringBuffer description) {
  591. boolean first = true;
  592. for (ErrorDescription e : errors) {
  593. if (!first) {
  594. description.append("\n\n"); //NOI18N
  595. }
  596. description.append(e.getDescription());
  597. first = false;
  598. }
  599. }
  600. private LazyFixList computeFixes(List<ErrorDescription> errors) {
  601. List<LazyFixList> result = new ArrayList<LazyFixList>();
  602. for (ErrorDescription e : errors) {
  603. result.add(e.getFixes());
  604. }
  605. return ErrorDescriptionFactory.lazyListForDelegates(result);
  606. }
  607. private void updateAnnotationOnLine(Position line, boolean synchronous) throws BadLocationException {
  608. List<ErrorDescription> errorDescriptions = getErrorsForLine(line, false);
  609. if (errorDescriptions == null) {
  610. //nothing to do, remove old:
  611. ParseErrorAnnotation ann = line2Annotations.remove(line);
  612. if (ann != null) {
  613. detachAnnotation(ann, synchronous);
  614. }
  615. return;
  616. }
  617. errorDescriptions = getErrorsForLine(line, true);
  618. List<ErrorDescription> trueErrors = filter(errorDescriptions, true);
  619. List<ErrorDescription> others = filter(errorDescriptions, false);
  620. boolean hasErrors = !trueErrors.isEmpty();
  621. //build up the description of the annotation:
  622. StringBuffer description = new StringBuffer();
  623. concatDescription(trueErrors, description);
  624. if (!trueErrors.isEmpty() && !others.isEmpty()) {
  625. description.append("\n\n"); //NOI18N
  626. }
  627. concatDescription(others, description);
  628. Severity mostImportantSeverity;
  629. if (hasErrors) {
  630. mostImportantSeverity = Severity.ERROR;
  631. } else {
  632. mostImportantSeverity = Severity.HINT;
  633. for (ErrorDescription e : others) {
  634. if (mostImportantSeverity.compareTo(e.getSeverity()) > 0) {
  635. mostImportantSeverity = e.getSeverity();
  636. }
  637. }
  638. }
  639. FixData fixes = new FixData(computeFixes(trueErrors), computeFixes(others));
  640. ParseErrorAnnotation pea = new ParseErrorAnnotation(
  641. mostImportantSeverity,
  642. fixes,
  643. description.toString(),
  644. line,
  645. this);
  646. ParseErrorAnnotation previous = line2Annotations.put(line, pea);
  647. if (previous != null) {
  648. detachAnnotation(previous, synchronous);
  649. }
  650. attachAnnotation(line, pea, synchronous);
  651. }
  652. void updateHighlightsOnLine(Position line) throws IOException {
  653. List<ErrorDescription> errorDescriptions = getErrorsForLine(line, false);
  654. OffsetsBag bag = getBag(doc);
  655. updateHighlightsOnLine(bag, doc, line, errorDescriptions);
  656. }
  657. static void updateHighlightsOnLine(OffsetsBag bag, BaseDocument doc, Position line, List<ErrorDescription> errorDescriptions) throws IOException {
  658. try {
  659. int rowStart = line.getOffset();
  660. int rowEnd = Utilities.getRowEnd(doc, rowStart);
  661. int rowHighlightStart = Utilities.getRowFirstNonWhite(doc, rowStart);
  662. int rowHighlightEnd = Utilities.getRowLastNonWhite(doc, rowStart) + 1;
  663. if (rowStart <= rowEnd) {
  664. bag.removeHighlights(rowStart, rowEnd, false);
  665. }
  666. if (errorDescriptions != null) {
  667. bag.addAllHighlights(computeHighlights(doc, errorDescriptions).getHighlights(rowHighlightStart, rowHighlightEnd));
  668. }
  669. } catch (BadLocationException ex) {
  670. throw new IOException(ex);
  671. }
  672. }
  673. static OffsetsBag computeHighlights(Document doc, List<ErrorDescription> errorDescriptions) throws IOException, BadLocationException {
  674. OffsetsBag bag = new OffsetsBag(doc);
  675. for (Severity s : Arrays.asList(Severity.VERIFIER, Severity.WARNING, Severity.ERROR)) {
  676. List<ErrorDescription> filteredDescriptions = new ArrayList<ErrorDescription>();
  677. for (ErrorDescription e : errorDescriptions) {
  678. if (e.getSeverity() == s) {
  679. filteredDescriptions.add(e);
  680. }
  681. }
  682. List<int[]> currentHighlights = new ArrayList<int[]>();
  683. for (ErrorDescription e : filteredDescriptions) {
  684. int beginOffset = e.getRange().getBegin().getPosition().getOffset();
  685. int endOffset = e.getRange().getEnd().getPosition().getOffset();
  686. if (endOffset < beginOffset) {
  687. //see issue #112566
  688. int swap = endOffset;
  689. endOffset = beginOffset;
  690. beginOffset = swap;
  691. LOG.log(Level.WARNING, "Incorrect highlight in ErrorDescription, attach your messages.log to issue #112566: {0}", e.toString()); //NOI18N
  692. }
  693. int[] h = new int[] {beginOffset, endOffset};
  694. OUT: for (Iterator<int[]> it = currentHighlights.iterator(); it.hasNext() && h != null; ) {
  695. int[] hl = it.next();
  696. switch (detectCollisions(hl, h)) {
  697. case 0:
  698. break;
  699. case 1:
  700. it.remove();
  701. break;
  702. case 2:
  703. h = null; //nothing to add, hl is bigger:
  704. break OUT;
  705. case 4:
  706. case 3:
  707. int start = Math.min(hl[0], h[0]);
  708. int end = Math.max(hl[1], h[1]);
  709. h = new int[] {start, end};
  710. it.remove();
  711. break;
  712. }
  713. }
  714. if (h != null) {
  715. currentHighlights.add(h);
  716. }
  717. }
  718. for (int[] h : currentHighlights) {
  719. if (h[0] <= h[1]) {
  720. bag.addHighlight(h[0], h[1], getColoring(s, doc));
  721. } else {
  722. //see issue #112566
  723. StringBuilder sb = new StringBuilder();
  724. for (ErrorDescription e : filteredDescriptions) {
  725. sb.append("["); //NOI18N
  726. sb.append(e.getRange().getBegin().getOffset());
  727. sb.append("-"); //NOI18N
  728. sb.append(e.getRange().getEnd().getOffset());
  729. sb.append("]"); //NOI18N
  730. }
  731. sb.append("=>"); //NOI18N
  732. for (int[] h2 : currentHighlights) {
  733. sb.append("["); //NOI18N
  734. sb.append(h2[0]);
  735. sb.append("-"); //NOI18N
  736. sb.append(h2[1]);
  737. sb.append("]"); //NOI18N
  738. }
  739. LOG.log(Level.WARNING, "Incorrect highlight computed, please reopen issue #112566 and attach the following output: {0}", sb.toString()); //NOI18N
  740. }
  741. }
  742. }
  743. return bag;
  744. }
  745. static AttributeSet getColoring(Severity s, Document d) {
  746. final String mimeType = DocumentUtilities.getMimeType(d);
  747. Map<Severity, AttributeSet> coloring = COLORINGS.get(mimeType);
  748. if (coloring == null) {
  749. coloring = new EnumMap<Severity, AttributeSet>(Severity.class);
  750. Lookup lookup = MimeLookup.getLookup(mimeType);
  751. Lookup.Result<FontColorSettings> result = lookup.lookupResult(FontColorSettings.class);
  752. LookupListener lookupListener = COLORINGS_LISTENERS.get(mimeType);
  753. if (lookupListener == null) {
  754. lookupListener = new LookupListener() {
  755. @Override
  756. public void resultChanged(LookupEvent ev) {
  757. COLORINGS.remove(mimeType);
  758. }
  759. };
  760. COLORINGS_LISTENERS.put(mimeType, lookupListener);
  761. result.addLookupListener(
  762. WeakListeners.create(
  763. LookupListener.class,
  764. lookupListener,
  765. result
  766. )
  767. );
  768. }
  769. final Iterator<? extends FontColorSettings> it = result.allInstances().iterator();
  770. AttributeSet error;
  771. AttributeSet warning;
  772. AttributeSet verifier;
  773. if (it.hasNext()) {
  774. FontColorSettings fcs = it.next();
  775. AttributeSet attributes = fcs.getTokenFontColors("errors"); // NOI18N
  776. if (attributes != null) {
  777. error = attributes;
  778. } else {
  779. attributes = fcs.getTokenFontColors("error"); // NOI18N
  780. if (attributes != null) {
  781. error = attributes;
  782. } else {
  783. error = DEFUALT_ERROR;
  784. }
  785. }
  786. attributes = fcs.getTokenFontColors("warning"); // NOI18N
  787. if (attributes != null) {
  788. warning = attributes;
  789. verifier = attributes;
  790. } else {
  791. warning = DEFUALT_WARNING;
  792. verifier = DEFUALT_VERIFIER;
  793. }
  794. } else {
  795. error = DEFUALT_ERROR;
  796. warning = DEFUALT_WARNING;
  797. verifier = DEFUALT_VERIFIER;
  798. }
  799. coloring.put(Severity.ERROR, AttributesUtilities.createComposite(error, TOOLTIP));
  800. coloring.put(Severity.WARNING, AttributesUtilities.createComposite(warning, TOOLTIP));
  801. coloring.put(Severity.VERIFIER, AttributesUtilities.createComposite(verifier, TOOLTIP));
  802. coloring.put(Severity.HINT, TOOLTIP);
  803. COLORINGS.put(mimeType, coloring);
  804. }
  805. return coloring.get(s);
  806. }
  807. private static int detectCollisions(int[] h1, int[] h2) {
  808. if (h2[1] < h1[0])
  809. return 0;//no collision
  810. if (h1[1] < h2[0])
  811. return 0;//no collision
  812. if (h2[0] < h1[0] && h2[1] > h1[1])
  813. return 1;//h2 encapsulates h1
  814. if (h1[0] < h2[0] && h1[1] > h2[1])
  815. return 2;//h1 encapsulates h2
  816. if (h1[0] < h2[0])
  817. return 3;//collides
  818. else
  819. return 4;
  820. }
  821. public void setErrorDescriptions(final String layer, final Collection<? extends ErrorDescription> errors) {
  822. setErrorDescriptions(layer, errors, false);
  823. }
  824. private void setErrorDescriptions(final String layer, final Collection<? extends ErrorDescription> errors, final boolean synchronous) {
  825. doc.render(new Runnable() {
  826. public void run() {
  827. try {
  828. setErrorDescriptionsImpl(file, layer, errors, synchronous);
  829. } catch (IOException e) {
  830. LOG.log(Level.WARNING, e.getMessage(), e);
  831. }
  832. }
  833. });
  834. }
  835. private synchronized void setErrorDescriptionsImpl(FileObject file, String layer, Collection<? extends ErrorDescription> errors, boolean synchronous) throws IOException {
  836. long start = System.currentTimeMillis();
  837. try {
  838. if (file == null)
  839. return ;
  840. List<ErrorDescription> layersErrors = getErrorsForLayer(layer);
  841. Set<Position> primaryLines = new HashSet<Position>();
  842. Set<Position> allLines = new HashSet<Position>();
  843. for (ErrorDescription ed : layersErrors) {
  844. List<Position> lines = errors2Lines.remove(ed);
  845. if (lines == null) { //#134282
  846. LOG.log(Level.WARNING, "Inconsistent error2Lines for layer {0}, file {1}.", new Object[] {layer, file.getPath()}); // NOI18N
  847. continue;
  848. }
  849. boolean first = true;
  850. for (Position line : lines) {
  851. List<ErrorDescription> errorsForLine = getErrorsForLine(line, false);
  852. if (errorsForLine != null) {
  853. errorsForLine.remove(ed);
  854. }
  855. if (first) {
  856. primaryLines.add(line);
  857. }
  858. allLines.add(line);
  859. first = false;
  860. }
  861. }
  862. List<ErrorDescription> validatedErrors = new ArrayList<ErrorDescription>();
  863. for (ErrorDescription ed : errors) {
  864. if (ed == null) {
  865. LOG.log(Level.WARNING, "'null' ErrorDescription in layer {0}.", layer); //NOI18N
  866. continue;
  867. }
  868. if (ed.getRange() == null)
  869. continue;
  870. validatedErrors.add(ed);
  871. List<Position> lines = new ArrayList<Position>();
  872. int startLine = ed.getRange().getBegin().getLine();
  873. int endLine = ed.getRange().getEnd().getLine();
  874. for (int cntr = startLine; cntr <= endLine; cntr++) {
  875. Position p = getPosition(cntr, true);
  876. lines.add(p);
  877. }
  878. errors2Lines.put(ed, lines);
  879. boolean first = true;
  880. for (Position line : lines) {
  881. getErrorsForLine(line, true).add(ed);
  882. if (first) {
  883. primaryLines.add(line);
  884. }
  885. allLines.add(line);
  886. first = false;
  887. }
  888. }
  889. layersErrors.clear();
  890. layersErrors.addAll(validatedErrors);
  891. for (Position line : primaryLines) {
  892. updateAnnotationOnLine(line, synchronous);
  893. }
  894. for (Position line : allLines) {
  895. updateHighlightsOnLine(line);
  896. }
  897. updateVisibleRanges();
  898. SwingUtilities.invokeLater(new Runnable() {
  899. @Override public void run() {
  900. HintsUI.getDefault().caretUpdate(null);
  901. }
  902. });
  903. } catch (BadLocationException ex) {
  904. throw new IOException(ex);
  905. } finally {
  906. long end = System.currentTimeMillis();
  907. Logger.getLogger("TIMER").log(Level.FINE, "Errors update for " + layer, //NOI18N
  908. new Object[] {file, end - start});
  909. }
  910. }
  911. private List<Reference<Position>> knownPositions = new ArrayList<Reference<Position>>();
  912. private static class Abort extends RuntimeException {
  913. @Override
  914. public synchronized Throwable fillInStackTrace() {
  915. return this;
  916. }
  917. }
  918. private static RuntimeException ABORT = new Abort();
  919. private synchronized int findPositionGE(int offset) {
  920. while (true) {
  921. try {
  922. int index = Collections.binarySearch(knownPositions, offset, new PositionComparator());
  923. if (index >= 0) {
  924. return index;
  925. } else {
  926. return - (index + 1);
  927. }
  928. } catch (Abort a) {
  929. LOG.log(Level.FINE, "a null Position detected - clearing"); //NOI18N
  930. int removedCount = 0;
  931. for (Iterator<Reference<Position>> it = knownPositions.iterator(); it.hasNext(); ) {
  932. if (it.next().get() == null) {
  933. removedCount++;
  934. it.remove();
  935. }
  936. }
  937. LOG.log(Level.FINE, "clearing finished, {0} positions cleared", removedCount); //NOI18N
  938. }
  939. }
  940. }
  941. private synchronized Position getPosition(int lineNumber, boolean create) throws BadLocationException {
  942. try {
  943. while (true) {
  944. int lineStart = Utilities.getRowStartFromLineOffset(doc, lineNumber);
  945. if (lineStart < 0) {
  946. Element lineRoot = doc.getDefaultRootElement();
  947. int lineElementCount = lineRoot.getElementCount();
  948. LOG.info("AnnotationHolder: Invalid lineNumber=" + lineNumber + // NOI18N
  949. ", lineStartOffset=" + lineStart + ", lineElementCount=" + lineElementCount + // NOI18N
  950. ", docReadLocked=" + DocumentUtilities.isReadLocked(doc) + ", doc:\n" + doc + '\n'); // NOI18N
  951. // Correct the lineStart
  952. if (lineNumber < 0) {
  953. lineStart = 0;
  954. } else { // Otherwise use last line
  955. lineStart = lineRoot.getElement(lineRoot.getElementCount() - 1).getStartOffset();
  956. }
  957. }
  958. try {
  959. int index = Collections.binarySearch(knownPositions, lineStart, new PositionComparator());
  960. if (index >= 0) {
  961. Reference<Position> r = knownPositions.get(index);
  962. Position p = r.get();
  963. if (p != null) {
  964. return p;
  965. }
  966. }
  967. if (!create)
  968. return null;
  969. Position p = NbDocument.createPosition(doc, lineStart, Position.Bias.Forward);
  970. knownPositions.add(- (index + 1), new WeakReference<Position>(p));
  971. Logger.getLogger("TIMER").log(Level.FINE, "Annotation Holder - Line Token", //NOI18N
  972. new Object[] {file, p});
  973. return p;
  974. } catch (Abort a) {
  975. LOG.log(Level.FINE, "a null Position detected - clearing"); //NOI18N
  976. int removedCount = 0;
  977. for (Iterator<Reference<Position>> it = knownPositions.iterator(); it.hasNext(); ) {
  978. if (it.next().get() == null) {
  979. removedCount++;
  980. it.remove();
  981. }
  982. }
  983. LOG.log(Level.FINE, "clearing finished, {0} positions cleared", removedCount); //NOI18N
  984. }
  985. }
  986. } finally {
  987. LOG.log(Level.FINE, "knownPositions.size={0}", knownPositions.size()); //NOI18N
  988. }
  989. }
  990. public synchronized boolean hasErrors() {
  991. for (ErrorDescription e : errors2Lines.keySet()) {
  992. if (e.getSeverity() == Severity.ERROR)
  993. return true;
  994. }
  995. return false;
  996. }
  997. public synchronized List<ErrorDescription> getErrors() {
  998. return new ArrayList<ErrorDescription>(errors2Lines.keySet());
  999. }
  1000. public synchronized List<Annotation> getAnnotations() {
  1001. return new ArrayList<Annotation>(line2Annotations.values());
  1002. }
  1003. public void setErrorsForLine(final int offset, final Map<String, List<ErrorDescription>> errs) {
  1004. doc.render(new Runnable() {
  1005. public void run() {
  1006. try {
  1007. Position pos = getPosition(Utilities.getLineOffset(doc, offset), true);
  1008. List<ErrorDescription> errsForCurrentLine = getErrorsForLine(pos, true);
  1009. //for each layer
  1010. for (Entry<String, List<ErrorDescription>> e : errs.entrySet()) {
  1011. //get errors for this layer, all lines
  1012. Set<ErrorDescription> errorsForLayer = new HashSet<ErrorDescription>(getErrorsForLayer(e.getKey()));
  1013. errorsForLayer.removeAll(errsForCurrentLine); //remove all for current line
  1014. Set<ErrorDescription> toSet = new HashSet<ErrorDescription>();
  1015. toSet.addAll(e.getValue());
  1016. toSet.addAll(errorsForLayer);
  1017. e.getValue().clear();
  1018. e.getValue().addAll(toSet); //add the rest to those provided by refresher
  1019. }
  1020. } catch (BadLocationException ex) {
  1021. Exceptions.printStackTrace(ex);
  1022. }
  1023. }
  1024. });
  1025. for (Entry<String, List<ErrorDescription>> e : errs.entrySet()) {
  1026. final List<ErrorDescription> eds = e.getValue();
  1027. setErrorDescriptions(e.getKey(), eds, true); //set updated
  1028. }
  1029. }
  1030. public synchronized List<ErrorDescription> getErrorsGE(int offset) {
  1031. try {
  1032. int index = findPositionGE(Utilities.getRowStart(doc, offset));
  1033. if (index < 0) return Collections.emptyList();
  1034. while (index < knownPositions.size()) {
  1035. Position current = knownPositions.get(index++).get();
  1036. if (current == null) {
  1037. continue;
  1038. }
  1039. List<ErrorDescription> errors = line2Errors.get(current);
  1040. if (errors != null) {
  1041. SortedMap<Integer, List<ErrorDescription>> sortedErrors = new TreeMap<Integer, List<ErrorDescription>>();
  1042. for (ErrorDescription ed : errors) {
  1043. List<ErrorDescription> errs = sortedErrors.get(ed.getRange().getBegin().getOffset());
  1044. if (errs == null) {
  1045. sortedErrors.put(ed.getRange().getBegin().getOffset(), errs = new LinkedList<ErrorDescription>());
  1046. }
  1047. errs.add(ed);
  1048. }
  1049. SortedMap<Integer, List<ErrorDescription>> tail = sortedErrors.tailMap(offset);
  1050. if (!tail.isEmpty()) {
  1051. Integer k = tail.firstKey();
  1052. return new ArrayList<ErrorDescription>(sortedErrors.get(k));
  1053. }
  1054. }
  1055. }
  1056. return Collections.emptyList();
  1057. } catch (BadLocationException ex) {
  1058. Exceptions.printStackTrace(ex);
  1059. return Collections.emptyList();
  1060. }
  1061. }
  1062. private static final RequestProcessor INSTANCE = new RequestProcessor("AnnotationHolder"); //NOI18N
  1063. public static OffsetsBag getBag(Document doc) {
  1064. OffsetsBag ob = (OffsetsBag) doc.getProperty(AnnotationHolder.class);
  1065. if (ob == null) {
  1066. doc.putProperty(AnnotationHolder.class, ob = new OffsetsBag(doc));
  1067. }
  1068. return ob;
  1069. }
  1070. public int lineNumber(final Position offset) {
  1071. final int[] result = new int[] {-1};
  1072. doc.render(new Runnable() {
  1073. public void run() {
  1074. try {
  1075. result[0] = Utilities.getLineOffset(doc, offset.getOffset());
  1076. } catch (BadLocationException ex) {
  1077. Exceptions.printStackTrace(ex);
  1078. }
  1079. }
  1080. });
  1081. return result[0];
  1082. }
  1083. private static final boolean ENABLE_ASSERTS = Boolean.getBoolean(AnnotationHolder.class.getName() + ".enableAsserts200469");
  1084. private static class PositionComparator implements Comparator<Object> {
  1085. private PositionComparator() {
  1086. }
  1087. public int compare(Object o1, Object o2) {
  1088. int left = -1;