/*=============================================================================#
# Copyright (c) 2008-2016 Stephan Wahlbrink (WalWare.de) 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:
# Stephan Wahlbrink - initial API and implementation
#=============================================================================*/
package de.walware.ecommons.ltk.ui;
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.viewers.IPostSelectionProvider;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.ui.statushandlers.StatusManager;
import de.walware.ecommons.FastList;
import de.walware.ecommons.ICommonStatusConstants;
import de.walware.ecommons.ltk.IModelElementDelta;
import de.walware.ecommons.ltk.core.model.IModelElement;
import de.walware.ecommons.ltk.core.model.ISourceUnit;
import de.walware.ecommons.ltk.internal.ui.LTKUIPlugin;
/**
* Controller implementation combining {@link IPostSelectionProvider} and
* {@link IModelElementInputProvider} to provide support for
* {@link ISelectionWithElementInfoListener}.
*/
public class PostSelectionWithElementInfoController {
private class SelectionTask extends Job {
private final class Data extends LTKInputData {
int fStateNr;
Data(final ISourceUnit input, final SelectionChangedEvent currentSelection, final int runNr) {
super(input, (currentSelection != null) ? currentSelection.getSelection() : null);
fStateNr = runNr;
}
@Override
public boolean isStillValid() {
return (fCurrentNr == fStateNr);
}
}
private int fLastNr;
public SelectionTask() {
super("PostSelection with Model Updater"); // //$NON-NLS-1$
setPriority(Job.SHORT);
setSystem(true);
setUser(false);
fLastNr = fCurrentNr = Integer.MIN_VALUE;
}
@Override
protected synchronized IStatus run(final IProgressMonitor monitor) {
ISourceUnit input = null;
try {
checkNewInput();
final Data run;
IgnoreActivation[] ignore = null;
synchronized (fInputLock) {
run = new Data(fInput, fCurrentSelection, fCurrentNr);
if (run.fInputElement == null || run.fSelection == null
|| (fLastNr == run.fStateNr && fNewListeners.isEmpty())) {
return Status.OK_STATUS;
}
if (!fIgnoreList.isEmpty()) {
int num = fIgnoreList.size();
ignore = fIgnoreList.toArray(new IgnoreActivation[num]);
for (int i = num-1; i >= 0; i--) {
if (ignore[i].marked && ignore[i].nr != run.fStateNr) {
fIgnoreList.remove(i);
ignore[i] = null;
num--;
}
}
if (num == 0) {
ignore = null;
}
}
input = run.fInputElement;
input.connect(monitor);
}
if (run.getInputInfo() == null
|| run.getInputInfo().getStamp().getSourceStamp() != input.getDocument(null).getModificationStamp()) {
return Status.OK_STATUS;
}
ISelectionWithElementInfoListener[] listeners = fNewListeners.clear();
if (run.fStateNr != fLastNr) {
listeners = fListeners.toArray();
fLastNr = run.fStateNr;
}
ITER_LISTENER : for (int i = 0; i < listeners.length; i++) {
if (ignore != null) {
for (int j = 0; j < ignore.length; j++) {
if (ignore[j] != null && ignore[j].listener == listeners[i]) {
continue ITER_LISTENER;
}
}
}
if (!run.isStillValid()) {
return Status.CANCEL_STATUS;
}
try {
listeners[i].stateChanged(run);
}
catch (final Exception e) {
logListenerError(e);
}
}
}
finally {
if (input != null) {
input.disconnect(monitor);
}
}
return Status.OK_STATUS;
}
private void checkNewInput() {
if (fInputChanged) {
synchronized (fInputLock) {
fInputChanged = false;
}
final ISelectionWithElementInfoListener[] listeners = fListeners.toArray();
for (int i = 0; i < listeners.length; i++) {
try {
listeners[i].inputChanged();
}
catch (final Exception e) {
logListenerError(e);
}
}
}
}
}
private class SelectionListener implements ISelectionChangedListener {
private boolean active;
@Override
public void selectionChanged(final SelectionChangedEvent event) {
if (!active) {
return;
}
synchronized (PostSelectionWithElementInfoController.this) {
if (fCurrentSelection != null && fCurrentSelection.getSelection().equals(event.getSelection())) {
return;
}
fCurrentNr++;
fCurrentSelection = event;
fUpdateJob.schedule();
}
}
};
public class IgnoreActivation {
private final ISelectionWithElementInfoListener listener;
private boolean marked;
private int nr;
private IgnoreActivation(final ISelectionWithElementInfoListener listener) {
this.listener = listener;
}
public void deleteNext() {
nr = fCurrentNr;
marked = true;
}
public void delete() {
nr = fCurrentNr-1;
marked = true;
synchronized (fInputLock) {
fIgnoreList.remove(this);
}
}
}
private final IPostSelectionProvider fSelectionProvider;
private final IModelElementInputProvider fModelProvider;
private final FastList<ISelectionWithElementInfoListener> fListeners = new FastList<>(ISelectionWithElementInfoListener.class);
private final FastList<ISelectionWithElementInfoListener> fNewListeners = new FastList<>(ISelectionWithElementInfoListener.class);
private final Object fInputLock = new Object();
private final IModelElementInputListener fElementChangeListener;
private final SelectionListener fSelectionListener;
private final SelectionListener fPostSelectionListener;
private PostSelectionCancelExtension fCancelExtension;
private final List<IgnoreActivation> fIgnoreList = new ArrayList<>();
private ISourceUnit fInput; // current input
private boolean fInputChanged;
private SelectionChangedEvent fCurrentSelection; // current selection
private volatile int fCurrentNr; // stamp to check, if information still up-to-date
private final SelectionTask fUpdateJob = new SelectionTask();
public PostSelectionWithElementInfoController(final IModelElementInputProvider modelProvider,
final IPostSelectionProvider selectionProvider, final PostSelectionCancelExtension cancelExt) {
fSelectionProvider = selectionProvider;
fModelProvider = modelProvider;
fElementChangeListener = new IModelElementInputListener() {
@Override
public void elementChanged(final IModelElement element) {
synchronized (fInputLock) {
if (fUpdateJob.getState() == Job.WAITING) {
fUpdateJob.cancel();
}
fInput = (ISourceUnit) element;
fInputChanged = true;
fCurrentNr++;
fCurrentSelection = null;
fUpdateJob.schedule();
}
}
@Override
public void elementInitialInfo(final IModelElement element) {
checkUpdate(element);
}
@Override
public void elementUpdatedInfo(final IModelElement element, final IModelElementDelta delta) {
checkUpdate(element);
}
private void checkUpdate(final IModelElement element) {
synchronized (fInputLock) {
fCurrentNr++;
if (fCurrentSelection == null) {
return;
}
}
fUpdateJob.run(null);
}
};
fSelectionListener = new SelectionListener();
fSelectionListener.active = false;
fSelectionProvider.addSelectionChangedListener(fSelectionListener);
fPostSelectionListener = new SelectionListener();
fPostSelectionListener.active = true;
fSelectionProvider.addPostSelectionChangedListener(fPostSelectionListener);
fModelProvider.addListener(fElementChangeListener);
if (cancelExt != null) {
fCancelExtension = cancelExt;
fCancelExtension.fController = this;
fCancelExtension.init();
}
}
public void setUpdateOnSelection(final boolean active) {
fSelectionListener.active = active;
}
public void setUpdateOnPostSelection(final boolean active) {
fPostSelectionListener.active = active;
}
public void cancel() {
synchronized (fInputLock) {
fCurrentNr++;
fCurrentSelection = null;
}
}
public void dispose() {
cancel();
fModelProvider.removeListener(fElementChangeListener);
fSelectionProvider.removeSelectionChangedListener(fSelectionListener);
fSelectionProvider.removePostSelectionChangedListener(fPostSelectionListener);
if (fCancelExtension != null) {
fCancelExtension.dispose();
}
fNewListeners.clear();
fListeners.clear();
}
public void addListener(final ISelectionWithElementInfoListener listener) {
fListeners.add(listener);
fNewListeners.add(listener);
fUpdateJob.schedule();
}
public void removeListener(final ISelectionWithElementInfoListener listener) {
fNewListeners.remove(listener);
fListeners.remove(listener);
}
public IgnoreActivation ignoreNext(final ISelectionWithElementInfoListener listener) {
final IgnoreActivation control = new IgnoreActivation(listener);
fIgnoreList.add(control);
return control;
}
private void logListenerError(final Throwable e) {
StatusManager.getManager().handle(new Status(
IStatus.ERROR, LTKUIPlugin.PLUGIN_ID, ICommonStatusConstants.INTERNAL_PLUGGED_IN,
"An error occurred when calling a registered listener.", e)); //$NON-NLS-1$
}
}