| /******************************************************************************* |
| * Copyright (c) 2009, 2018 Borland Software Corporation and others. |
| * |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v2.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v20.html |
| * |
| * Contributors: |
| * Borland Software Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.m2m.internal.qvt.oml.editor.ui.colorer; |
| |
| 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.TextPresentation; |
| import org.eclipse.jface.text.source.SourceViewer; |
| import org.eclipse.m2m.internal.qvt.oml.blackbox.BlackboxUnit; |
| import org.eclipse.m2m.internal.qvt.oml.compiler.CompiledUnit; |
| import org.eclipse.m2m.internal.qvt.oml.editor.ui.Activator; |
| import org.eclipse.m2m.internal.qvt.oml.editor.ui.IQVTReconcilingListener; |
| import org.eclipse.m2m.internal.qvt.oml.editor.ui.QvtEditor; |
| import org.eclipse.m2m.internal.qvt.oml.editor.ui.colorer.QVTColorManager.ColorDescriptor; |
| import org.eclipse.m2m.internal.qvt.oml.editor.ui.colorer.QVTColorManager.Highlighting; |
| import org.eclipse.m2m.internal.qvt.oml.editor.ui.colorer.SemanticHighlightingManager.HighlightedPosition; |
| import org.eclipse.ocl.cst.CSTNode; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Shell; |
| import org.eclipse.ui.IWorkbenchPartSite; |
| import org.eclipse.ui.PlatformUI; |
| |
| /** |
| * Semantic highlighting reconciler - Background thread implementation. |
| */ |
| class SemanticHighlightingReconciler implements IQVTReconcilingListener, ITextInputListener { |
| |
| public class PositionCollector { |
| |
| private SemanticToken fToken = new SemanticToken(); |
| |
| private final QVTSemanticHighlighter fHighlighter; |
| |
| private PositionCollector(QVTSemanticHighlighter highlighter) { |
| fHighlighter = highlighter; |
| fHighlighter.setCollector(this); |
| } |
| |
| public boolean isEnabled(int highlighting) { |
| if (highlighting >= 0 && highlighting < fJobSemanticHighlightings.length) { |
| ColorDescriptor semanticHighlighting = fJobSemanticHighlightings[highlighting]; |
| Highlighting hl = semanticHighlighting.getHighlighting(); |
| return hl.isEnabled(); |
| } |
| return false; |
| } |
| |
| private boolean isValidNode(CSTNode node) { |
| int offset = node.getStartOffset(); |
| return !(offset == 0 && node.eContainer() != null); |
| } |
| |
| public boolean visitToken(CSTNode node, int highlighting) { |
| if(!isValidNode(node)) { |
| return false; |
| } |
| |
| fToken.update(node, null); |
| |
| if (highlighting >= 0 && highlighting < fJobSemanticHighlightings.length) { |
| ColorDescriptor semanticColor = fJobSemanticHighlightings[highlighting]; |
| Highlighting hl = semanticColor.getHighlighting(); |
| if (hl.isEnabled()) { |
| int offset = node.getStartOffset(); |
| int length = node.getEndOffset() - offset + 1; |
| if (offset > -1 && length > 0) { |
| addPosition(offset, length, hl); |
| } |
| } |
| } |
| fToken.clear(); |
| return false; |
| } |
| |
| public boolean visitToken(CSTNode node, int offset, int length, int highlighting) { |
| if(!isValidNode(node)) { |
| return false; |
| } |
| |
| fToken.update(node, null); |
| |
| if (highlighting >= 0 && highlighting < fJobSemanticHighlightings.length) { |
| ColorDescriptor semanticColor = fJobSemanticHighlightings[highlighting]; |
| Highlighting hl = semanticColor.getHighlighting(); |
| if (hl.isEnabled()) { |
| if (offset > -1 && length > 0) { |
| addPosition(offset, length, hl); |
| } |
| } |
| } |
| fToken.clear(); |
| return false; |
| } |
| |
| /** |
| * Add a position with the given range and highlighting if it does not exist already. |
| */ |
| public 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 = fRemovedPositions.get(i); |
| if (position == null) { |
| continue; |
| } |
| if (position.isEqual(offset, length, highlighting)) { |
| isExisting = true; |
| fRemovedPositions.set(i, null); |
| fNOfRemovedPositions--; |
| break; |
| } |
| } |
| |
| if (!isExisting) { |
| HighlightedPosition position = fJobPresenter.createHighlightedPosition(offset, length, highlighting); |
| if (fAddedPositions.size() == 0 || fAddedPositions.get(fAddedPositions.size() - 1).offset < offset) { |
| fAddedPositions.add(position); |
| } else { |
| int insertIndex = fAddedPositions.size(); |
| while (insertIndex > 0 && fAddedPositions.get(insertIndex - 1).getOffset() > offset) { |
| insertIndex--; |
| } |
| fAddedPositions.add(insertIndex, position); |
| } |
| } |
| } |
| |
| /** |
| * Retain the positions completely contained in the given range. |
| */ |
| public void retainPositions(int offset, int length) { |
| // TODO: use binary search |
| for (int i = 0, n = fRemovedPositions.size(); i < n; i++) { |
| HighlightedPosition position = fRemovedPositions.get(i); |
| if (position != null && position.isContained(offset, length)) { |
| fRemovedPositions.set(i, null); |
| fNOfRemovedPositions--; |
| } |
| } |
| } |
| |
| public void enumerate(CompiledUnit unit) { |
| fHighlighter.visit(unit); |
| } |
| } |
| |
| private PositionCollector fCollector; |
| |
| private QvtEditor fEditor; |
| |
| /** |
| * The semantic highlighting presenter |
| */ |
| private SemanticHighlightingPresenter fPresenter; |
| /** |
| * Semantic highlighting |
| */ |
| private ColorDescriptor[] fSemanticHighlightings; |
| |
| /** |
| * Background job's added highlighted positions |
| */ |
| private final ArrayList<HighlightedPosition> fAddedPositions = new ArrayList<HighlightedPosition>(); |
| /** |
| * Background job's removed highlighted positions |
| */ |
| private List<HighlightedPosition> fRemovedPositions = new ArrayList<HighlightedPosition>(); |
| /** |
| * 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 |
| */ |
| private boolean fIsReconciling = false; |
| |
| /** |
| * The semantic highlighting presenter - cache for background thread, only valid during {@link #reconciled(BlackboxUnit, boolean, IProgressMonitor)} |
| */ |
| private SemanticHighlightingPresenter fJobPresenter; |
| /** |
| * Semantic highlightings - cache for background thread, only valid during {@link #reconciled(BlackboxUnit, boolean, IProgressMonitor)} |
| */ |
| private ColorDescriptor[] fJobSemanticHighlightings; |
| |
| public SemanticHighlightingReconciler() { |
| super(); |
| } |
| |
| public void aboutToBeReconciled() { |
| // Do nothing |
| } |
| |
| public void reconciled(CompiledUnit model, 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; |
| |
| try { |
| if (fJobPresenter == null || fJobSemanticHighlightings == null) { |
| return; |
| } |
| |
| fJobPresenter.setCanceled(progressMonitor.isCanceled()); |
| |
| if (model == null || fJobPresenter.isCanceled()) { |
| return; |
| } |
| |
| startReconcilingPositions(); |
| |
| if (!fJobPresenter.isCanceled()) { |
| reconcilePositions(model); |
| } |
| |
| TextPresentation textPresentation = null; |
| if (!fJobPresenter.isCanceled()) { |
| textPresentation = fJobPresenter.createPresentation(fAddedPositions, fRemovedPositions); |
| } |
| |
| if (!fJobPresenter.isCanceled()) { |
| updatePresentation(textPresentation, fAddedPositions, fRemovedPositions); |
| } |
| |
| stopReconcilingPositions(); |
| } catch(RuntimeException e) { |
| Activator.log(e); |
| } finally { |
| fJobPresenter = null; |
| fJobSemanticHighlightings = null; |
| synchronized (fReconcileLock) { |
| fIsReconciling = false; |
| } |
| } |
| } |
| |
| /** |
| * Start reconciling positions |
| */ |
| private void startReconcilingPositions() { |
| fJobPresenter.addAllPositions(fRemovedPositions); |
| fNOfRemovedPositions = fRemovedPositions.size(); |
| } |
| |
| /** |
| * Reconcile positions based on the AST subtrees |
| */ |
| private void reconcilePositions(CompiledUnit model) { |
| if (model.getUnitCST() == null || fCollector == null) { |
| return; |
| } |
| fCollector.enumerate(model); |
| List<HighlightedPosition> oldPositions = fRemovedPositions; |
| List<HighlightedPosition> newPositions = new ArrayList<HighlightedPosition>(fNOfRemovedPositions); |
| for (int i = 0, n = oldPositions.size(); i < n; i++) { |
| HighlightedPosition current = oldPositions.get(i); |
| if (current != null) { |
| newPositions.add(current); |
| } |
| } |
| fRemovedPositions = newPositions; |
| } |
| |
| /** |
| * Update the presentation. |
| */ |
| private void updatePresentation(TextPresentation textPresentation, List<HighlightedPosition> addedPositions, |
| List<HighlightedPosition> removedPositions) { |
| Runnable runnable = fJobPresenter.createUpdateRunnable(textPresentation, addedPositions, removedPositions); |
| if (runnable == null) { |
| return; |
| } |
| |
| Display display = null; |
| QvtEditor editor = fEditor; |
| if (editor != null) { |
| IWorkbenchPartSite site = editor.getSite(); |
| if (site != null) { |
| Shell shell = site.getShell(); |
| if (shell == null || shell.isDisposed()) { |
| return; |
| } |
| display = shell.getDisplay(); |
| } |
| } |
| |
| if(display == null) { |
| display = PlatformUI.getWorkbench().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. |
| */ |
| public void install(QvtEditor editor, SourceViewer sourceViewer, |
| SemanticHighlightingPresenter presenter, |
| ColorDescriptor[] semanticHighlightings) { |
| |
| fPresenter = presenter; |
| fSemanticHighlightings = semanticHighlightings; |
| |
| fEditor = editor; |
| fCollector = new PositionCollector(new QVTSemanticHighlighter(semanticHighlightings)); |
| |
| if (fEditor != null) { |
| fEditor.addReconcilingListener(this); |
| } |
| } |
| |
| /** |
| * Uninstall this reconciler from the editor |
| */ |
| public void uninstall() { |
| if (fPresenter != null) { |
| fPresenter.setCanceled(true); |
| } |
| |
| if (fEditor != null) { |
| fEditor.removeReconcilingListener(this); |
| fEditor = null; |
| } |
| |
| fCollector = null; |
| fSemanticHighlightings = 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; |
| } |
| |
| fJob = new Job(Messages.SemanticHighlightingReconciler_JobName0) { |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| if (oldJob != null) { |
| try { |
| oldJob.join(); |
| } catch (InterruptedException e) { |
| Activator.log(e); |
| return Status.CANCEL_STATUS; |
| } |
| } |
| if (monitor.isCanceled()) { |
| return Status.CANCEL_STATUS; |
| } |
| CompiledUnit model = getModel(); |
| reconciled(model, 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(); |
| } |
| } |
| |
| public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) { |
| synchronized (fJobLock) { |
| if (fJob != null) { |
| fJob.cancel(); |
| fJob = null; |
| } |
| } |
| } |
| |
| public void inputDocumentChanged(IDocument oldInput, IDocument newInput) { |
| if (newInput != null) { |
| scheduleJob(); |
| } |
| } |
| |
| /** |
| * Refreshes the highlighting. |
| */ |
| public void refresh() { |
| scheduleJob(); |
| } |
| |
| protected CompiledUnit getModel() { |
| return fEditor.getValidCompiledModule(5000); |
| } |
| } |