/******************************************************************************* * Copyright (c) 2000, 2011 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.internal.ui.javaeditor; import java.util.ArrayList; import java.util.List; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.ITextInputListener; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.TextPresentation; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.ui.IWorkbenchPartSite; import org.eclipse.jdt.core.ITypeRoot; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.BooleanLiteral; import org.eclipse.jdt.core.dom.CharacterLiteral; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.ConstructorInvocation; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.NumberLiteral; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.SuperConstructorInvocation; import org.eclipse.jdt.internal.corext.dom.GenericVisitor; import org.eclipse.jdt.ui.SharedASTProvider; import org.eclipse.jdt.internal.ui.JavaPlugin; import org.eclipse.jdt.internal.ui.javaeditor.SemanticHighlightingManager.HighlightedPosition; import org.eclipse.jdt.internal.ui.javaeditor.SemanticHighlightingManager.Highlighting; import org.eclipse.jdt.internal.ui.javaeditor.SemanticHighlightings.DeprecatedMemberHighlighting; import org.eclipse.jdt.internal.ui.text.java.IJavaReconcilingListener; /** * Semantic highlighting reconciler - Background thread implementation. * * @since 3.0 */ public class SemanticHighlightingReconciler implements IJavaReconcilingListener, ITextInputListener { /** * Collects positions from the AST. */ private class PositionCollector extends GenericVisitor { /** The semantic token */ private SemanticToken fToken= new SemanticToken(); /* * @see org.eclipse.jdt.internal.corext.dom.GenericVisitor#visitNode(org.eclipse.jdt.core.dom.ASTNode) */ @Override protected boolean visitNode(ASTNode node) { if ((node.getFlags() & ASTNode.MALFORMED) == ASTNode.MALFORMED) { retainPositions(node.getStartPosition(), node.getLength()); return false; } return true; } /* * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.BooleanLiteral) */ @Override public boolean visit(BooleanLiteral node) { return visitLiteral(node); } /* * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.CharacterLiteral) */ @Override public boolean visit(CharacterLiteral node) { return visitLiteral(node); } /* * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.NumberLiteral) */ @Override public boolean visit(NumberLiteral node) { return visitLiteral(node); } private boolean visitLiteral(Expression node) { fToken.update(node); for (int i= 0, n= fJobSemanticHighlightings.length; i < n; i++) { SemanticHighlighting semanticHighlighting= fJobSemanticHighlightings[i]; if (fJobHighlightings[i].isEnabled() && semanticHighlighting.consumesLiteral(fToken)) { int offset= node.getStartPosition(); int length= node.getLength(); if (offset > -1 && length > 0) addPosition(offset, length, fJobHighlightings[i]); break; } } fToken.clear(); return false; } /* * @see org.eclipse.jdt.internal.corext.dom.GenericVisitor#visit(org.eclipse.jdt.core.dom.ConstructorInvocation) * @since 3.5 */ @Override public boolean visit(ConstructorInvocation node) { // XXX Hack for performance reasons (should loop over fJobSemanticHighlightings can call consumes(*)) if (fJobDeprecatedMemberHighlighting != null) { IMethodBinding constructorBinding= node.resolveConstructorBinding(); if (constructorBinding != null && constructorBinding.isDeprecated()) { int offset= node.getStartPosition(); int length= 4; if (offset > -1 && length > 0) addPosition(offset, length, fJobDeprecatedMemberHighlighting); } } return true; } /* * @see org.eclipse.jdt.internal.corext.dom.GenericVisitor#visit(org.eclipse.jdt.core.dom.ConstructorInvocation) * @since 3.5 */ @Override public boolean visit(SuperConstructorInvocation node) { // XXX Hack for performance reasons (should loop over fJobSemanticHighlightings can call consumes(*)) if (fJobDeprecatedMemberHighlighting != null) { IMethodBinding constructorBinding= node.resolveConstructorBinding(); if (constructorBinding != null && constructorBinding.isDeprecated()) { int offset= node.getStartPosition(); int length= 5; if (offset > -1 && length > 0) addPosition(offset, length, fJobDeprecatedMemberHighlighting); } } return true; } /* * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.SimpleName) */ @Override public boolean visit(SimpleName node) { fToken.update(node); for (int i= 0, n= fJobSemanticHighlightings.length; i < n; i++) { SemanticHighlighting semanticHighlighting= fJobSemanticHighlightings[i]; if (fJobHighlightings[i].isEnabled() && semanticHighlighting.consumes(fToken)) { int offset= node.getStartPosition(); int length= node.getLength(); if (offset > -1 && length > 0) addPosition(offset, length, fJobHighlightings[i]); break; } } fToken.clear(); return false; } /** * Add a position with the given range and highlighting iff it does not exist already. * @param offset The range offset * @param length The range length * @param highlighting The highlighting */ private void addPosition(int offset, int length, Highlighting highlighting) { boolean isExisting= false; // TODO: use binary search for (int i= 0, n= fRemovedPositions.size(); i < n; i++) { HighlightedPosition position= (HighlightedPosition) fRemovedPositions.get(i); if (position == null) continue; if (position.isEqual(offset, length, highlighting)) { isExisting= true; fRemovedPositions.set(i, null); fNOfRemovedPositions--; break; } } if (!isExisting) { Position position= fJobPresenter.createHighlightedPosition(offset, length, highlighting); fAddedPositions.add(position); } } /** * Retain the positions completely contained in the given range. * @param offset The range offset * @param length The range length */ private void retainPositions(int offset, int length) { // TODO: use binary search for (int i= 0, n= fRemovedPositions.size(); i < n; i++) { HighlightedPosition position= (HighlightedPosition) fRemovedPositions.get(i); if (position != null && position.isContained(offset, length)) { fRemovedPositions.set(i, null); fNOfRemovedPositions--; } } } } /** Position collector */ private PositionCollector fCollector= new PositionCollector(); /** The Java editor this semantic highlighting reconciler is installed on */ private JavaEditor fEditor; /** The source viewer this semantic highlighting reconciler is installed on */ private ISourceViewer fSourceViewer; /** The semantic highlighting presenter */ private SemanticHighlightingPresenter fPresenter; /** Semantic highlightings */ private SemanticHighlighting[] fSemanticHighlightings; /** Highlightings */ private Highlighting[] fHighlightings; /** Background job's added highlighted positions */ private List<Position> fAddedPositions= new ArrayList<Position>(); /** Background job's removed highlighted positions */ private List<Position> fRemovedPositions= new ArrayList<Position>(); /** Number of removed positions */ private int fNOfRemovedPositions; /** Background job */ private Job fJob; /** Background job lock */ private final Object fJobLock= new Object(); /** * Reconcile operation lock. * @since 3.2 */ private final Object fReconcileLock= new Object(); /** * <code>true</code> if any thread is executing * <code>reconcile</code>, <code>false</code> otherwise. * @since 3.2 */ private boolean fIsReconciling= false; /** The semantic highlighting presenter - cache for background thread, only valid during {@link #reconciled(CompilationUnit, boolean, IProgressMonitor)} */ private SemanticHighlightingPresenter fJobPresenter; /** Semantic highlightings - cache for background thread, only valid during {@link #reconciled(CompilationUnit, boolean, IProgressMonitor)} */ private SemanticHighlighting[] fJobSemanticHighlightings; /** Highlightings - cache for background thread, only valid during {@link #reconciled(CompilationUnit, boolean, IProgressMonitor)} */ private Highlighting[] fJobHighlightings; /** * XXX Hack for performance reasons (should loop over fJobSemanticHighlightings can call consumes(*)) * @since 3.5 */ private Highlighting fJobDeprecatedMemberHighlighting; /* * @see org.eclipse.jdt.internal.ui.text.java.IJavaReconcilingListener#aboutToBeReconciled() */ public void aboutToBeReconciled() { // Do nothing } /* * @see org.eclipse.jdt.internal.ui.text.java.IJavaReconcilingListener#reconciled(CompilationUnit, boolean, IProgressMonitor) */ public void reconciled(CompilationUnit ast, boolean forced, IProgressMonitor progressMonitor) { // ensure at most one thread can be reconciling at any time synchronized (fReconcileLock) { if (fIsReconciling) return; else fIsReconciling= true; } fJobPresenter= fPresenter; fJobSemanticHighlightings= fSemanticHighlightings; fJobHighlightings= fHighlightings; try { if (fJobPresenter == null || fJobSemanticHighlightings == null || fJobHighlightings == null) return; fJobPresenter.setCanceled(progressMonitor.isCanceled()); if (ast == null || fJobPresenter.isCanceled()) return; ASTNode[] subtrees= getAffectedSubtrees(ast); if (subtrees.length == 0) return; startReconcilingPositions(); if (!fJobPresenter.isCanceled()) { fJobDeprecatedMemberHighlighting= null; for (int i= 0, n= fJobSemanticHighlightings.length; i < n; i++) { SemanticHighlighting semanticHighlighting= fJobSemanticHighlightings[i]; if (fJobHighlightings[i].isEnabled() && semanticHighlighting instanceof DeprecatedMemberHighlighting) { fJobDeprecatedMemberHighlighting= fJobHighlightings[i]; break; } } reconcilePositions(subtrees); } TextPresentation textPresentation= null; if (!fJobPresenter.isCanceled()) textPresentation= fJobPresenter.createPresentation(fAddedPositions, fRemovedPositions); if (!fJobPresenter.isCanceled()) updatePresentation(textPresentation, fAddedPositions, fRemovedPositions); stopReconcilingPositions(); } finally { fJobPresenter= null; fJobSemanticHighlightings= null; fJobHighlightings= null; fJobDeprecatedMemberHighlighting= null; synchronized (fReconcileLock) { fIsReconciling= false; } } } /** * @param node Root node * @return Array of subtrees that may be affected by past document changes */ private ASTNode[] getAffectedSubtrees(ASTNode node) { // TODO: only return nodes which are affected by document changes - would require an 'anchor' concept for taking distant effects into account return new ASTNode[] { node }; } /** * Start reconciling positions. */ private void startReconcilingPositions() { fJobPresenter.addAllPositions(fRemovedPositions); fNOfRemovedPositions= fRemovedPositions.size(); } /** * Reconcile positions based on the AST subtrees * * @param subtrees the AST subtrees */ private void reconcilePositions(ASTNode[] subtrees) { // FIXME: remove positions not covered by subtrees for (int i= 0, n= subtrees.length; i < n; i++) subtrees[i].accept(fCollector); List<Position> oldPositions= fRemovedPositions; List<Position> newPositions= new ArrayList<Position>(fNOfRemovedPositions); for (int i= 0, n= oldPositions.size(); i < n; i ++) { Position current= oldPositions.get(i); if (current != null) newPositions.add(current); } fRemovedPositions= newPositions; } /** * Update the presentation. * * @param textPresentation the text presentation * @param addedPositions the added positions * @param removedPositions the removed positions */ private void updatePresentation(TextPresentation textPresentation, List<Position> addedPositions, List<Position> removedPositions) { Runnable runnable= fJobPresenter.createUpdateRunnable(textPresentation, addedPositions, removedPositions); if (runnable == null) return; JavaEditor editor= fEditor; if (editor == null) return; IWorkbenchPartSite site= editor.getSite(); if (site == null) return; Shell shell= site.getShell(); if (shell == null || shell.isDisposed()) return; Display display= shell.getDisplay(); if (display == null || display.isDisposed()) return; display.asyncExec(runnable); } /** * Stop reconciling positions. */ private void stopReconcilingPositions() { fRemovedPositions.clear(); fNOfRemovedPositions= 0; fAddedPositions.clear(); } /** * Install this reconciler on the given editor, presenter and highlightings. * @param editor the editor * @param sourceViewer the source viewer * @param presenter the semantic highlighting presenter * @param semanticHighlightings the semantic highlightings * @param highlightings the highlightings */ public void install(JavaEditor editor, ISourceViewer sourceViewer, SemanticHighlightingPresenter presenter, SemanticHighlighting[] semanticHighlightings, Highlighting[] highlightings) { fPresenter= presenter; fSemanticHighlightings= semanticHighlightings; fHighlightings= highlightings; fEditor= editor; fSourceViewer= sourceViewer; if (fEditor instanceof CompilationUnitEditor) { ((CompilationUnitEditor)fEditor).addReconcileListener(this); } else if (fEditor == null) { fSourceViewer.addTextInputListener(this); scheduleJob(); } } /** * Uninstall this reconciler from the editor */ public void uninstall() { if (fPresenter != null) fPresenter.setCanceled(true); if (fEditor != null) { if (fEditor instanceof CompilationUnitEditor) ((CompilationUnitEditor)fEditor).removeReconcileListener(this); else fSourceViewer.removeTextInputListener(this); fEditor= null; } fSourceViewer= null; fSemanticHighlightings= null; fHighlightings= null; fPresenter= null; } /** * Schedule a background job for retrieving the AST and reconciling the Semantic Highlighting model. */ private void scheduleJob() { final ITypeRoot element= fEditor.getInputJavaElement(); synchronized (fJobLock) { final Job oldJob= fJob; if (fJob != null) { fJob.cancel(); fJob= null; } if (element != null) { fJob= new Job(JavaEditorMessages.SemanticHighlighting_job) { @Override protected IStatus run(IProgressMonitor monitor) { if (oldJob != null) { try { oldJob.join(); } catch (InterruptedException e) { JavaPlugin.log(e); return Status.CANCEL_STATUS; } } if (monitor.isCanceled()) return Status.CANCEL_STATUS; CompilationUnit ast= SharedASTProvider.getAST(element, SharedASTProvider.WAIT_YES, monitor); reconciled(ast, false, monitor); synchronized (fJobLock) { // allow the job to be gc'ed if (fJob == this) fJob= null; } return Status.OK_STATUS; } }; fJob.setSystem(true); fJob.setPriority(Job.DECORATE); fJob.schedule(); } } } /* * @see org.eclipse.jface.text.ITextInputListener#inputDocumentAboutToBeChanged(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.IDocument) */ public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) { synchronized (fJobLock) { if (fJob != null) { fJob.cancel(); fJob= null; } } } /* * @see org.eclipse.jface.text.ITextInputListener#inputDocumentChanged(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.IDocument) */ public void inputDocumentChanged(IDocument oldInput, IDocument newInput) { if (newInput != null) scheduleJob(); } /** * Refreshes the highlighting. * * @since 3.2 */ public void refresh() { scheduleJob(); } }