/******************************************************************************* * Copyright (c) 2014, 2015 Cisco Systems, Inc. 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 * *******************************************************************************/ package com.cisco.yangide.editor.editors; import java.util.ArrayList; import java.util.List; 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.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IWorkbenchPartSite; import com.cisco.yangide.core.YangModelException; import com.cisco.yangide.core.dom.ASTNode; import com.cisco.yangide.core.dom.ASTVisitor; import com.cisco.yangide.core.dom.GroupingDefinition; import com.cisco.yangide.core.dom.Module; import com.cisco.yangide.core.dom.TypeDefinition; import com.cisco.yangide.core.dom.TypeReference; import com.cisco.yangide.core.dom.UsesNode; import com.cisco.yangide.editor.YangEditorPlugin; import com.cisco.yangide.editor.editors.SemanticHighlightingManager.HighlightedPosition; import com.cisco.yangide.editor.editors.SemanticHighlightingManager.Highlighting; /** * Semantic highlighting reconciler - Background thread implementation. * * @author Alexey Kholupko */ public class SemanticHighlightingReconciler implements ITextInputListener { /** * Collects positions from the AST. */ private class PositionCollector extends ASTVisitor { /** The semantic token */ private ASTNode fToken = null; /* * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.SimpleName) */ @Override public boolean visit(Module node) { return true; } /* * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.SimpleName) */ @Override public boolean visit(TypeReference node) { if ((node.getFlags() & ASTNode.MALFORMED) == ASTNode.MALFORMED) { retainPositions(node.getStartPosition(), node.getLength()); return false; } fToken = node; for (int i = 0, n = fJobSemanticHighlightings.length; i < n; i++) { SemanticHighlighting semanticHighlighting = fJobSemanticHighlightings[i]; if (fJobHighlightings[i].isEnabled() && semanticHighlighting.consumes(fToken)) { int offset = semanticHighlighting.getHiglightingOffset(node); int length = semanticHighlighting.getHiglightingLength(node); if (offset > -1 && length > 0) { addPosition(offset, length, fJobHighlightings[i]); // break; } } } return true; } @Override public boolean visit(UsesNode node) { if ((node.getFlags() & ASTNode.MALFORMED) == ASTNode.MALFORMED) { retainPositions(node.getStartPosition(), node.getLength()); return false; } fToken = node; for (int i = 0, n = fJobSemanticHighlightings.length; i < n; i++) { SemanticHighlighting semanticHighlighting = fJobSemanticHighlightings[i]; if (fJobHighlightings[i].isEnabled() && semanticHighlighting.consumes(fToken)) { int offset = semanticHighlighting.getHiglightingOffset(node); int length = semanticHighlighting.getHiglightingLength(node); if (offset > -1 && length > 0) { addPosition(offset, length, fJobHighlightings[i]); // break; } } } return true; } @Override public boolean visit(GroupingDefinition node) { if ((node.getFlags() & ASTNode.MALFORMED) == ASTNode.MALFORMED) { retainPositions(node.getStartPosition(), node.getLength()); return false; } fToken = node; for (int i = 0, n = fJobSemanticHighlightings.length; i < n; i++) { SemanticHighlighting semanticHighlighting = fJobSemanticHighlightings[i]; if (fJobHighlightings[i].isEnabled() && semanticHighlighting.consumes(fToken)) { int offset = semanticHighlighting.getHiglightingOffset(node); int length = semanticHighlighting.getHiglightingLength(node); if (offset > -1 && length > 0) { addPosition(offset, length, fJobHighlightings[i]); // break; } } } return true; } @Override public boolean visit(TypeDefinition node) { if ((node.getFlags() & ASTNode.MALFORMED) == ASTNode.MALFORMED) { retainPositions(node.getStartPosition(), node.getLength()); return false; } fToken = node; for (int i = 0, n = fJobSemanticHighlightings.length; i < n; i++) { SemanticHighlighting semanticHighlighting = fJobSemanticHighlightings[i]; if (fJobHighlightings[i].isEnabled() && semanticHighlighting.consumes(fToken)) { int offset = semanticHighlighting.getHiglightingOffset(node); int length = semanticHighlighting.getHiglightingLength(node); if (offset > -1 && length > 0) { addPosition(offset, length, fJobHighlightings[i]); } break; } } return true; } /** * 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 YangEditor 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. */ private final Object fReconcileLock = new Object(); /** * <code>true</code> if any thread is executing <code>reconcile</code>, <code>false</code> * otherwise. */ private boolean fIsReconciling = false; /** * The semantic highlighting presenter - cache for background thread, only valid during */ private SemanticHighlightingPresenter fJobPresenter; /** * Semantic highlightings - cache for background thread, only valid during */ private SemanticHighlighting[] fJobSemanticHighlightings; /** * Highlightings - cache for background thread, only valid during */ private Highlighting[] fJobHighlightings; /* * @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(ASTNode 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()) { 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; 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; } YangEditor 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(YangEditor editor, ISourceViewer sourceViewer, SemanticHighlightingPresenter presenter, SemanticHighlighting[] semanticHighlightings, Highlighting[] highlightings) { fPresenter = presenter; fSemanticHighlightings = semanticHighlightings; fHighlightings = highlightings; fEditor = editor; fSourceViewer = sourceViewer; fSourceViewer.addTextInputListener(this); // fEditor.addReconcileListener(this); fEditor.updateSemanticHigliting(); if (fEditor == null) { scheduleJob(); } } /** * Uninstall this reconciler from the editor */ public void uninstall() { if (fPresenter != null) { fPresenter.setCanceled(true); } if (fEditor != null) { 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() { synchronized (fJobLock) { final Job oldJob = fJob; if (fJob != null) { fJob.cancel(); fJob = null; } // TODO some checks here // final ITypeRoot element= fEditor.getInputJavaElement(); // if (element != null) { fJob = new Job("JavaEditorMessages.SemanticHighlighting_job") { @Override protected IStatus run(IProgressMonitor monitor) { if (oldJob != null) { try { oldJob.join(); } catch (InterruptedException e) { YangEditorPlugin.log(e); return Status.CANCEL_STATUS; } } if (monitor.isCanceled()) { return Status.CANCEL_STATUS; } Module ast; try { ast = fEditor.getModule(); } catch (YangModelException e) { YangEditorPlugin.log(e); return Status.CANCEL_STATUS; } 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) */ @Override 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) */ @Override public void inputDocumentChanged(IDocument oldInput, IDocument newInput) { if (newInput != null) { scheduleJob(); } } /** * Refreshes the highlighting. */ public void refresh() { scheduleJob(); } }