blob: 0c1a922f46d9fe2ea24f881dae5ffc8ebbdfb774 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2008, 2021 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.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 org.eclipse.statet.jcommons.collections.CopyOnWriteIdentityListSet;
import org.eclipse.statet.jcommons.collections.ImIdentityList;
import org.eclipse.statet.internal.ltk.ui.LTKUIPlugin;
import org.eclipse.statet.ltk.model.core.element.LtkModelElement;
import org.eclipse.statet.ltk.model.core.element.LtkModelElementDelta;
import org.eclipse.statet.ltk.model.core.element.SourceUnit;
/**
* Controller implementation combining {@link IPostSelectionProvider} and
* {@link ModelElementInputProvider} to provide support for
* {@link SelectionWithElementInfoListener}.
*/
public class PostSelectionWithElementInfoController {
private class SelectionTask extends Job {
private final class Data extends LTKInputData {
int stateNr;
Data(final SourceUnit input, final SelectionChangedEvent currentSelection, final int runNr) {
super(input, (currentSelection != null) ? currentSelection.getSelection() : null);
this.stateNr= runNr;
}
@Override
public boolean isStillValid() {
return (PostSelectionWithElementInfoController.this.currentNr == this.stateNr);
}
}
private int lastNr;
public SelectionTask() {
super("PostSelection with Model Updater"); // //$NON-NLS-1$
setPriority(Job.SHORT);
setSystem(true);
setUser(false);
this.lastNr= PostSelectionWithElementInfoController.this.currentNr= Integer.MIN_VALUE;
}
@Override
protected synchronized IStatus run(final IProgressMonitor monitor) {
SourceUnit input= null;
try {
checkNewInput();
final Data run;
IgnoreActivation[] ignore= null;
synchronized (PostSelectionWithElementInfoController.this.inputLock) {
run= new Data(PostSelectionWithElementInfoController.this.input, PostSelectionWithElementInfoController.this.currentSelection, PostSelectionWithElementInfoController.this.currentNr);
if (run.inputElement == null || run.selection == null
|| (this.lastNr == run.stateNr && PostSelectionWithElementInfoController.this.newListeners.isEmpty())) {
return Status.OK_STATUS;
}
if (!PostSelectionWithElementInfoController.this.ignoreList.isEmpty()) {
int num= PostSelectionWithElementInfoController.this.ignoreList.size();
ignore= PostSelectionWithElementInfoController.this.ignoreList.toArray(new IgnoreActivation[num]);
for (int i= num-1; i >= 0; i--) {
if (ignore[i].marked && ignore[i].nr != run.stateNr) {
PostSelectionWithElementInfoController.this.ignoreList.remove(i);
ignore[i]= null;
num--;
}
}
if (num == 0) {
ignore= null;
}
}
input= run.inputElement;
input.connect(monitor);
}
if (run.getInputInfo() == null
|| run.getInputInfo().getStamp().getContentStamp() != input.getDocument(null).getModificationStamp()) {
return Status.OK_STATUS;
}
ImIdentityList<SelectionWithElementInfoListener> listeners= PostSelectionWithElementInfoController.this.newListeners.clearToList();
if (run.stateNr != this.lastNr) {
listeners= PostSelectionWithElementInfoController.this.listeners.toList();
this.lastNr= run.stateNr;
}
ITER_LISTENER: for (final SelectionWithElementInfoListener listener : listeners) {
if (ignore != null) {
for (int j= 0; j < ignore.length; j++) {
if (ignore[j] != null && ignore[j].listener == listener) {
continue ITER_LISTENER;
}
}
}
if (!run.isStillValid()) {
return Status.CANCEL_STATUS;
}
try {
listener.stateChanged(run);
}
catch (final Exception e) {
logListenerError(e);
}
}
}
finally {
if (input != null) {
input.disconnect(monitor);
}
}
return Status.OK_STATUS;
}
private void checkNewInput() {
if (PostSelectionWithElementInfoController.this.inputChanged) {
synchronized (PostSelectionWithElementInfoController.this.inputLock) {
PostSelectionWithElementInfoController.this.inputChanged= false;
}
final ImIdentityList<SelectionWithElementInfoListener> listeners= PostSelectionWithElementInfoController.this.listeners.toList();
for (final SelectionWithElementInfoListener listener : listeners) {
try {
listener.inputChanged();
}
catch (final Exception e) {
logListenerError(e);
}
}
}
}
}
private class SelectionListener implements ISelectionChangedListener {
private boolean active;
@Override
public void selectionChanged(final SelectionChangedEvent event) {
if (!this.active) {
return;
}
synchronized (PostSelectionWithElementInfoController.this) {
if (PostSelectionWithElementInfoController.this.currentSelection != null && PostSelectionWithElementInfoController.this.currentSelection.getSelection().equals(event.getSelection())) {
return;
}
PostSelectionWithElementInfoController.this.currentNr++;
PostSelectionWithElementInfoController.this.currentSelection= event;
PostSelectionWithElementInfoController.this.updateJob.schedule();
}
}
}
public class IgnoreActivation {
private final SelectionWithElementInfoListener listener;
private boolean marked;
private int nr;
private IgnoreActivation(final SelectionWithElementInfoListener listener) {
this.listener= listener;
}
public void deleteNext() {
this.nr= PostSelectionWithElementInfoController.this.currentNr;
this.marked= true;
}
public void delete() {
this.nr= PostSelectionWithElementInfoController.this.currentNr-1;
this.marked= true;
synchronized (PostSelectionWithElementInfoController.this.inputLock) {
PostSelectionWithElementInfoController.this.ignoreList.remove(this);
}
}
}
private final IPostSelectionProvider selectionProvider;
private final ModelElementInputProvider modelProvider;
private final CopyOnWriteIdentityListSet<SelectionWithElementInfoListener> listeners= new CopyOnWriteIdentityListSet<>();
private final CopyOnWriteIdentityListSet<SelectionWithElementInfoListener> newListeners= new CopyOnWriteIdentityListSet<>();
private final Object inputLock= new Object();
private final ModelElementInputListener elementChangeListener;
private final SelectionListener selectionListener;
private final SelectionListener postSelectionListener;
private PostSelectionCancelExtension cancelExtension;
private final List<IgnoreActivation> ignoreList= new ArrayList<>();
private SourceUnit input; // current input
private boolean inputChanged;
private SelectionChangedEvent currentSelection; // current selection
private volatile int currentNr; // stamp to check, if information still up-to-date
private final SelectionTask updateJob= new SelectionTask();
public PostSelectionWithElementInfoController(final ModelElementInputProvider modelProvider,
final IPostSelectionProvider selectionProvider, final PostSelectionCancelExtension cancelExt) {
this.selectionProvider= selectionProvider;
this.modelProvider= modelProvider;
this.elementChangeListener= new ModelElementInputListener() {
@Override
public void elementChanged(final LtkModelElement element) {
synchronized (PostSelectionWithElementInfoController.this.inputLock) {
if (PostSelectionWithElementInfoController.this.updateJob.getState() == Job.WAITING) {
PostSelectionWithElementInfoController.this.updateJob.cancel();
}
PostSelectionWithElementInfoController.this.input= (SourceUnit) element;
PostSelectionWithElementInfoController.this.inputChanged= true;
PostSelectionWithElementInfoController.this.currentNr++;
PostSelectionWithElementInfoController.this.currentSelection= null;
PostSelectionWithElementInfoController.this.updateJob.schedule();
}
}
@Override
public void elementInitialInfo(final LtkModelElement element) {
checkUpdate(element);
}
@Override
public void elementUpdatedInfo(final LtkModelElement element, final LtkModelElementDelta delta) {
checkUpdate(element);
}
private void checkUpdate(final LtkModelElement element) {
synchronized (PostSelectionWithElementInfoController.this.inputLock) {
PostSelectionWithElementInfoController.this.currentNr++;
if (PostSelectionWithElementInfoController.this.currentSelection == null) {
return;
}
}
PostSelectionWithElementInfoController.this.updateJob.run(null);
}
};
this.selectionListener= new SelectionListener();
this.selectionListener.active= false;
this.selectionProvider.addSelectionChangedListener(this.selectionListener);
this.postSelectionListener= new SelectionListener();
this.postSelectionListener.active= true;
this.selectionProvider.addPostSelectionChangedListener(this.postSelectionListener);
this.modelProvider.addListener(this.elementChangeListener);
if (cancelExt != null) {
this.cancelExtension= cancelExt;
this.cancelExtension.controller= this;
this.cancelExtension.init();
}
}
public void setUpdateOnSelection(final boolean active) {
this.selectionListener.active= active;
}
public void setUpdateOnPostSelection(final boolean active) {
this.postSelectionListener.active= active;
}
public void cancel() {
synchronized (this.inputLock) {
this.currentNr++;
this.currentSelection= null;
}
}
public void dispose() {
cancel();
this.modelProvider.removeListener(this.elementChangeListener);
this.selectionProvider.removeSelectionChangedListener(this.selectionListener);
this.selectionProvider.removePostSelectionChangedListener(this.postSelectionListener);
if (this.cancelExtension != null) {
this.cancelExtension.dispose();
}
this.newListeners.clear();
this.listeners.clear();
}
public void addListener(final SelectionWithElementInfoListener listener) {
this.listeners.add(listener);
this.newListeners.add(listener);
this.updateJob.schedule();
}
public void removeListener(final SelectionWithElementInfoListener listener) {
this.newListeners.remove(listener);
this.listeners.remove(listener);
}
public IgnoreActivation ignoreNext(final SelectionWithElementInfoListener listener) {
final IgnoreActivation control= new IgnoreActivation(listener);
this.ignoreList.add(control);
return control;
}
private void logListenerError(final Throwable e) {
StatusManager.getManager().handle(new Status(
IStatus.ERROR, LTKUIPlugin.BUNDLE_ID, 0,
"An error occurred when calling a registered listener.", e )); //$NON-NLS-1$
}
}