001: /*******************************************************************************
002: * Copyright (c) 2000, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.jdt.internal.ui.javaeditor;
011:
012: import java.util.ArrayList;
013: import java.util.List;
014:
015: import org.eclipse.core.runtime.IProgressMonitor;
016: import org.eclipse.core.runtime.IStatus;
017: import org.eclipse.core.runtime.Status;
018: import org.eclipse.core.runtime.jobs.Job;
019:
020: import org.eclipse.swt.widgets.Display;
021: import org.eclipse.swt.widgets.Shell;
022:
023: import org.eclipse.jface.text.IDocument;
024: import org.eclipse.jface.text.ITextInputListener;
025: import org.eclipse.jface.text.Position;
026: import org.eclipse.jface.text.TextPresentation;
027: import org.eclipse.jface.text.source.ISourceViewer;
028:
029: import org.eclipse.ui.IWorkbenchPartSite;
030:
031: import org.eclipse.jdt.core.ITypeRoot;
032: import org.eclipse.jdt.core.dom.ASTNode;
033: import org.eclipse.jdt.core.dom.BooleanLiteral;
034: import org.eclipse.jdt.core.dom.CharacterLiteral;
035: import org.eclipse.jdt.core.dom.CompilationUnit;
036: import org.eclipse.jdt.core.dom.Expression;
037: import org.eclipse.jdt.core.dom.NumberLiteral;
038: import org.eclipse.jdt.core.dom.SimpleName;
039:
040: import org.eclipse.jdt.internal.corext.dom.GenericVisitor;
041:
042: import org.eclipse.jdt.ui.SharedASTProvider;
043:
044: import org.eclipse.jdt.internal.ui.JavaPlugin;
045: import org.eclipse.jdt.internal.ui.javaeditor.SemanticHighlightingManager.HighlightedPosition;
046: import org.eclipse.jdt.internal.ui.javaeditor.SemanticHighlightingManager.Highlighting;
047: import org.eclipse.jdt.internal.ui.text.java.IJavaReconcilingListener;
048:
049: /**
050: * Semantic highlighting reconciler - Background thread implementation.
051: *
052: * @since 3.0
053: */
054: public class SemanticHighlightingReconciler implements
055: IJavaReconcilingListener, ITextInputListener {
056:
057: /**
058: * Collects positions from the AST.
059: */
060: private class PositionCollector extends GenericVisitor {
061:
062: /** The semantic token */
063: private SemanticToken fToken = new SemanticToken();
064:
065: /*
066: * @see org.eclipse.jdt.internal.corext.dom.GenericVisitor#visitNode(org.eclipse.jdt.core.dom.ASTNode)
067: */
068: protected boolean visitNode(ASTNode node) {
069: if ((node.getFlags() & ASTNode.MALFORMED) == ASTNode.MALFORMED) {
070: retainPositions(node.getStartPosition(), node
071: .getLength());
072: return false;
073: }
074: return true;
075: }
076:
077: /*
078: * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.BooleanLiteral)
079: */
080: public boolean visit(BooleanLiteral node) {
081: return visitLiteral(node);
082: }
083:
084: /*
085: * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.CharacterLiteral)
086: */
087: public boolean visit(CharacterLiteral node) {
088: return visitLiteral(node);
089: }
090:
091: /*
092: * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.NumberLiteral)
093: */
094: public boolean visit(NumberLiteral node) {
095: return visitLiteral(node);
096: }
097:
098: private boolean visitLiteral(Expression node) {
099: fToken.update(node);
100: for (int i = 0, n = fJobSemanticHighlightings.length; i < n; i++) {
101: SemanticHighlighting semanticHighlighting = fJobSemanticHighlightings[i];
102: if (fJobHighlightings[i].isEnabled()
103: && semanticHighlighting.consumesLiteral(fToken)) {
104: int offset = node.getStartPosition();
105: int length = node.getLength();
106: if (offset > -1 && length > 0)
107: addPosition(offset, length,
108: fJobHighlightings[i]);
109: break;
110: }
111: }
112: fToken.clear();
113: return false;
114: }
115:
116: /*
117: * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.SimpleName)
118: */
119: public boolean visit(SimpleName node) {
120: fToken.update(node);
121: for (int i = 0, n = fJobSemanticHighlightings.length; i < n; i++) {
122: SemanticHighlighting semanticHighlighting = fJobSemanticHighlightings[i];
123: if (fJobHighlightings[i].isEnabled()
124: && semanticHighlighting.consumes(fToken)) {
125: int offset = node.getStartPosition();
126: int length = node.getLength();
127: if (offset > -1 && length > 0)
128: addPosition(offset, length,
129: fJobHighlightings[i]);
130: break;
131: }
132: }
133: fToken.clear();
134: return false;
135: }
136:
137: /**
138: * Add a position with the given range and highlighting iff it does not exist already.
139: * @param offset The range offset
140: * @param length The range length
141: * @param highlighting The highlighting
142: */
143: private void addPosition(int offset, int length,
144: Highlighting highlighting) {
145: boolean isExisting = false;
146: // TODO: use binary search
147: for (int i = 0, n = fRemovedPositions.size(); i < n; i++) {
148: HighlightedPosition position = (HighlightedPosition) fRemovedPositions
149: .get(i);
150: if (position == null)
151: continue;
152: if (position.isEqual(offset, length, highlighting)) {
153: isExisting = true;
154: fRemovedPositions.set(i, null);
155: fNOfRemovedPositions--;
156: break;
157: }
158: }
159:
160: if (!isExisting) {
161: Position position = fJobPresenter
162: .createHighlightedPosition(offset, length,
163: highlighting);
164: fAddedPositions.add(position);
165: }
166: }
167:
168: /**
169: * Retain the positions completely contained in the given range.
170: * @param offset The range offset
171: * @param length The range length
172: */
173: private void retainPositions(int offset, int length) {
174: // TODO: use binary search
175: for (int i = 0, n = fRemovedPositions.size(); i < n; i++) {
176: HighlightedPosition position = (HighlightedPosition) fRemovedPositions
177: .get(i);
178: if (position != null
179: && position.isContained(offset, length)) {
180: fRemovedPositions.set(i, null);
181: fNOfRemovedPositions--;
182: }
183: }
184: }
185: }
186:
187: /** Position collector */
188: private PositionCollector fCollector = new PositionCollector();
189:
190: /** The Java editor this semantic highlighting reconciler is installed on */
191: private JavaEditor fEditor;
192: /** The source viewer this semantic highlighting reconciler is installed on */
193: private ISourceViewer fSourceViewer;
194: /** The semantic highlighting presenter */
195: private SemanticHighlightingPresenter fPresenter;
196: /** Semantic highlightings */
197: private SemanticHighlighting[] fSemanticHighlightings;
198: /** Highlightings */
199: private Highlighting[] fHighlightings;
200:
201: /** Background job's added highlighted positions */
202: private List fAddedPositions = new ArrayList();
203: /** Background job's removed highlighted positions */
204: private List fRemovedPositions = new ArrayList();
205: /** Number of removed positions */
206: private int fNOfRemovedPositions;
207:
208: /** Background job */
209: private Job fJob;
210: /** Background job lock */
211: private final Object fJobLock = new Object();
212: /**
213: * Reconcile operation lock.
214: * @since 3.2
215: */
216: private final Object fReconcileLock = new Object();
217: /**
218: * <code>true</code> if any thread is executing
219: * <code>reconcile</code>, <code>false</code> otherwise.
220: * @since 3.2
221: */
222: private boolean fIsReconciling = false;
223:
224: /** The semantic highlighting presenter - cache for background thread, only valid during {@link #reconciled(CompilationUnit, boolean, IProgressMonitor)} */
225: private SemanticHighlightingPresenter fJobPresenter;
226: /** Semantic highlightings - cache for background thread, only valid during {@link #reconciled(CompilationUnit, boolean, IProgressMonitor)} */
227: private SemanticHighlighting[] fJobSemanticHighlightings;
228: /** Highlightings - cache for background thread, only valid during {@link #reconciled(CompilationUnit, boolean, IProgressMonitor)} */
229: private Highlighting[] fJobHighlightings;
230:
231: /*
232: * @see org.eclipse.jdt.internal.ui.text.java.IJavaReconcilingListener#aboutToBeReconciled()
233: */
234: public void aboutToBeReconciled() {
235: // Do nothing
236: }
237:
238: /*
239: * @see org.eclipse.jdt.internal.ui.text.java.IJavaReconcilingListener#reconciled(CompilationUnit, boolean, IProgressMonitor)
240: */
241: public void reconciled(CompilationUnit ast, boolean forced,
242: IProgressMonitor progressMonitor) {
243: // ensure at most one thread can be reconciling at any time
244: synchronized (fReconcileLock) {
245: if (fIsReconciling)
246: return;
247: else
248: fIsReconciling = true;
249: }
250: fJobPresenter = fPresenter;
251: fJobSemanticHighlightings = fSemanticHighlightings;
252: fJobHighlightings = fHighlightings;
253:
254: try {
255: if (fJobPresenter == null
256: || fJobSemanticHighlightings == null
257: || fJobHighlightings == null)
258: return;
259:
260: fJobPresenter.setCanceled(progressMonitor.isCanceled());
261:
262: if (ast == null || fJobPresenter.isCanceled())
263: return;
264:
265: ASTNode[] subtrees = getAffectedSubtrees(ast);
266: if (subtrees.length == 0)
267: return;
268:
269: startReconcilingPositions();
270:
271: if (!fJobPresenter.isCanceled())
272: reconcilePositions(subtrees);
273:
274: TextPresentation textPresentation = null;
275: if (!fJobPresenter.isCanceled())
276: textPresentation = fJobPresenter.createPresentation(
277: fAddedPositions, fRemovedPositions);
278:
279: if (!fJobPresenter.isCanceled())
280: updatePresentation(textPresentation, fAddedPositions,
281: fRemovedPositions);
282:
283: stopReconcilingPositions();
284: } finally {
285: fJobPresenter = null;
286: fJobSemanticHighlightings = null;
287: fJobHighlightings = null;
288: synchronized (fReconcileLock) {
289: fIsReconciling = false;
290: }
291: }
292: }
293:
294: /**
295: * @param node Root node
296: * @return Array of subtrees that may be affected by past document changes
297: */
298: private ASTNode[] getAffectedSubtrees(ASTNode node) {
299: // TODO: only return nodes which are affected by document changes - would require an 'anchor' concept for taking distant effects into account
300: return new ASTNode[] { node };
301: }
302:
303: /**
304: * Start reconciling positions.
305: */
306: private void startReconcilingPositions() {
307: fJobPresenter.addAllPositions(fRemovedPositions);
308: fNOfRemovedPositions = fRemovedPositions.size();
309: }
310:
311: /**
312: * Reconcile positions based on the AST subtrees
313: *
314: * @param subtrees the AST subtrees
315: */
316: private void reconcilePositions(ASTNode[] subtrees) {
317: // FIXME: remove positions not covered by subtrees
318: for (int i = 0, n = subtrees.length; i < n; i++)
319: subtrees[i].accept(fCollector);
320: List oldPositions = fRemovedPositions;
321: List newPositions = new ArrayList(fNOfRemovedPositions);
322: for (int i = 0, n = oldPositions.size(); i < n; i++) {
323: Object current = oldPositions.get(i);
324: if (current != null)
325: newPositions.add(current);
326: }
327: fRemovedPositions = newPositions;
328: }
329:
330: /**
331: * Update the presentation.
332: *
333: * @param textPresentation the text presentation
334: * @param addedPositions the added positions
335: * @param removedPositions the removed positions
336: */
337: private void updatePresentation(TextPresentation textPresentation,
338: List addedPositions, List removedPositions) {
339: Runnable runnable = fJobPresenter.createUpdateRunnable(
340: textPresentation, addedPositions, removedPositions);
341: if (runnable == null)
342: return;
343:
344: JavaEditor editor = fEditor;
345: if (editor == null)
346: return;
347:
348: IWorkbenchPartSite site = editor.getSite();
349: if (site == null)
350: return;
351:
352: Shell shell = site.getShell();
353: if (shell == null || shell.isDisposed())
354: return;
355:
356: Display display = shell.getDisplay();
357: if (display == null || display.isDisposed())
358: return;
359:
360: display.asyncExec(runnable);
361: }
362:
363: /**
364: * Stop reconciling positions.
365: */
366: private void stopReconcilingPositions() {
367: fRemovedPositions.clear();
368: fNOfRemovedPositions = 0;
369: fAddedPositions.clear();
370: }
371:
372: /**
373: * Install this reconciler on the given editor, presenter and highlightings.
374: * @param editor the editor
375: * @param sourceViewer the source viewer
376: * @param presenter the semantic highlighting presenter
377: * @param semanticHighlightings the semantic highlightings
378: * @param highlightings the highlightings
379: */
380: public void install(JavaEditor editor, ISourceViewer sourceViewer,
381: SemanticHighlightingPresenter presenter,
382: SemanticHighlighting[] semanticHighlightings,
383: Highlighting[] highlightings) {
384: fPresenter = presenter;
385: fSemanticHighlightings = semanticHighlightings;
386: fHighlightings = highlightings;
387:
388: fEditor = editor;
389: fSourceViewer = sourceViewer;
390:
391: if (fEditor instanceof CompilationUnitEditor) {
392: ((CompilationUnitEditor) fEditor)
393: .addReconcileListener(this );
394: } else if (fEditor == null) {
395: fSourceViewer.addTextInputListener(this );
396: scheduleJob();
397: }
398: }
399:
400: /**
401: * Uninstall this reconciler from the editor
402: */
403: public void uninstall() {
404: if (fPresenter != null)
405: fPresenter.setCanceled(true);
406:
407: if (fEditor != null) {
408: if (fEditor instanceof CompilationUnitEditor)
409: ((CompilationUnitEditor) fEditor)
410: .removeReconcileListener(this );
411: else
412: fSourceViewer.removeTextInputListener(this );
413: fEditor = null;
414: }
415:
416: fSourceViewer = null;
417: fSemanticHighlightings = null;
418: fHighlightings = null;
419: fPresenter = null;
420: }
421:
422: /**
423: * Schedule a background job for retrieving the AST and reconciling the Semantic Highlighting model.
424: */
425: private void scheduleJob() {
426: final ITypeRoot element = fEditor.getInputJavaElement();
427:
428: synchronized (fJobLock) {
429: final Job oldJob = fJob;
430: if (fJob != null) {
431: fJob.cancel();
432: fJob = null;
433: }
434:
435: if (element != null) {
436: fJob = new Job(
437: JavaEditorMessages.SemanticHighlighting_job) {
438: protected IStatus run(IProgressMonitor monitor) {
439: if (oldJob != null) {
440: try {
441: oldJob.join();
442: } catch (InterruptedException e) {
443: JavaPlugin.log(e);
444: return Status.CANCEL_STATUS;
445: }
446: }
447: if (monitor.isCanceled())
448: return Status.CANCEL_STATUS;
449: CompilationUnit ast = SharedASTProvider.getAST(
450: element, SharedASTProvider.WAIT_YES,
451: monitor);
452: reconciled(ast, false, monitor);
453: synchronized (fJobLock) {
454: // allow the job to be gc'ed
455: if (fJob == this )
456: fJob = null;
457: }
458: return Status.OK_STATUS;
459: }
460: };
461: fJob.setSystem(true);
462: fJob.setPriority(Job.DECORATE);
463: fJob.schedule();
464: }
465: }
466: }
467:
468: /*
469: * @see org.eclipse.jface.text.ITextInputListener#inputDocumentAboutToBeChanged(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.IDocument)
470: */
471: public void inputDocumentAboutToBeChanged(IDocument oldInput,
472: IDocument newInput) {
473: synchronized (fJobLock) {
474: if (fJob != null) {
475: fJob.cancel();
476: fJob = null;
477: }
478: }
479: }
480:
481: /*
482: * @see org.eclipse.jface.text.ITextInputListener#inputDocumentChanged(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.IDocument)
483: */
484: public void inputDocumentChanged(IDocument oldInput,
485: IDocument newInput) {
486: if (newInput != null)
487: scheduleJob();
488: }
489:
490: /**
491: * Refreshes the highlighting.
492: *
493: * @since 3.2
494: */
495: public void refresh() {
496: scheduleJob();
497: }
498: }
|