blob: 904cb4d86ee0c18171eca9957c03de1928475ad5 [file] [log] [blame]
/*******************************************************************************
* 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);
}
}