blob: e9b7236d7050c1a80460e54db696f40e2c495b8c [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2007, 2020 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 org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.statet.jcommons.collections.CopyOnWriteIdentityListSet;
import org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.collections.ImIdentityList;
import org.eclipse.statet.jcommons.lang.Disposable;
import org.eclipse.statet.ltk.ast.core.AstInfo;
import org.eclipse.statet.ltk.core.WorkingContext;
import org.eclipse.statet.ltk.model.core.ElementChangedEvent;
import org.eclipse.statet.ltk.model.core.IElementChangedListener;
import org.eclipse.statet.ltk.model.core.IModelElementDelta;
import org.eclipse.statet.ltk.model.core.IModelManager;
import org.eclipse.statet.ltk.model.core.elements.ISourceUnit;
/**
* Controller implementation for input of a part and its model updates.
*/
public class ElementInfoController implements IModelElementInputProvider, Disposable {
private static int NEWINPUT_DELAY= 100;
private final IModelManager modelProvider;
private final WorkingContext modelContext;
private final IElementChangedListener elementChangeListener;
private final CopyOnWriteIdentityListSet<IModelElementInputListener> listenerList= new CopyOnWriteIdentityListSet<>();
private final CopyOnWriteIdentityListSet<IModelElementInputListener> newListenerList= new CopyOnWriteIdentityListSet<>();
private final Object inputLock= new Object();
private volatile ISourceUnit input;
private ISourceUnit newInput;
private final NewInputUpdater newInputJob= new NewInputUpdater();
private class NewInputUpdater extends Job implements ISchedulingRule {
public NewInputUpdater() {
super("ViewPart Model Element Updater"); // //$NON-NLS-1$
setPriority(Job.SHORT);
setRule(this);
setSystem(true);
setUser(false);
}
@Override
public boolean contains(final ISchedulingRule rule) {
return (rule == this);
}
@Override
public boolean isConflicting(final ISchedulingRule rule) {
return (rule == this);
}
@Override
protected IStatus run(final IProgressMonitor monitor) {
ISourceUnit input;
ImIdentityList<IModelElementInputListener> listeners;
synchronized (ElementInfoController.this.inputLock) {
if (monitor.isCanceled()
|| (ElementInfoController.this.input == null && ElementInfoController.this.newInput == null)) {
return Status.CANCEL_STATUS;
}
if (ElementInfoController.this.newInput == null) {
listeners= checkNewListeners();
}
else {
final AstInfo astInfo= ElementInfoController.this.newInput.getAstInfo(null, false, null);
if (astInfo == null || (astInfo.getLevel() & AstInfo.DEFAULT_LEVEL_MASK) < 1) {
return Status.CANCEL_STATUS;
}
ElementInfoController.this.input= ElementInfoController.this.newInput;
ElementInfoController.this.newInput= null;
checkNewListeners();
listeners= ElementInfoController.this.listenerList.toList();
}
input= ElementInfoController.this.input;
}
if (listeners != null) {
notifyInitial(listeners, input, monitor);
}
return Status.OK_STATUS;
}
@Override
protected void canceling() {
ElementInfoController.this.notifyMonitor.setCanceled(true);
}
}
private final IProgressMonitor notifyMonitor= new NullProgressMonitor();
public ElementInfoController(final IModelManager manager, final WorkingContext context) {
this.elementChangeListener= new IElementChangedListener() {
@Override
public void elementChanged(final ElementChangedEvent event) {
ISourceUnit input;
ImIdentityList<IModelElementInputListener> listeners;
synchronized (ElementInfoController.this.inputLock) {
if (ElementInfoController.this.newInput != null && ElementInfoController.this.newInput.equals(event.delta.getModelElement())) {
if (ElementInfoController.this.newInputJob.getState() != Job.WAITING) {
ElementInfoController.this.newInputJob.schedule();
}
return;
}
if (ElementInfoController.this.input == null || !ElementInfoController.this.input.equals(event.delta.getModelElement())) {
return;
}
input= ElementInfoController.this.input;
listeners= ElementInfoController.this.listenerList.toList();
}
try {
final IProgressMonitor monitor= new NullProgressMonitor();
Job.getJobManager().beginRule(ElementInfoController.this.newInputJob, monitor);
notifyUpdated(listeners, input, event.delta, monitor);
}
finally {
Job.getJobManager().endRule(ElementInfoController.this.newInputJob);
}
}
};
this.modelProvider= manager;
this.modelContext= context;
this.modelProvider.addElementChangedListener(this.elementChangeListener, this.modelContext);
}
@Override
public void dispose() {
this.modelProvider.removeElementChangedListener(this.elementChangeListener, this.modelContext);
}
public void setInput(final ISourceUnit input) {
synchronized (this.inputLock) {
this.input= null;
this.newInput= input;
checkNewListeners();
this.newInputJob.cancel();
notifyChanged(this.listenerList.toList(), input);
}
this.newInputJob.schedule(NEWINPUT_DELAY);
}
private ImIdentityList<IModelElementInputListener> checkNewListeners() {
final ImIdentityList<IModelElementInputListener> listeners= this.newListenerList.clearToList();
for (final IModelElementInputListener listener : listeners) {
this.listenerList.add(listener);
}
return listeners;
}
private void notifyChanged(final ImIdentityList<IModelElementInputListener> listeners, final ISourceUnit input) {
for (final IModelElementInputListener listener : listeners) {
listener.elementChanged(input);
}
}
private void notifyInitial(final ImIdentityList<IModelElementInputListener> listeners, final ISourceUnit input,
final IProgressMonitor monitor) {
if (listeners.isEmpty() || input != this.input) {
return;
}
try {
input.connect(monitor);
for (final IModelElementInputListener listener : listeners) {
if (input != this.input) {
return;
}
listener.elementInitialInfo(input);
}
}
finally {
input.disconnect(monitor);
}
}
private void notifyUpdated(final ImIdentityList<IModelElementInputListener> listeners, final ISourceUnit input, final IModelElementDelta delta,
final IProgressMonitor monitor) {
if (input != this.input) {
return;
}
try {
input.connect(monitor);
for (final IModelElementInputListener listener : listeners) {
if (input != this.input) {
return;
}
listener.elementUpdatedInfo(input, delta);
}
}
finally {
input.disconnect(monitor);
}
}
@Override
public ISourceUnit getInput() {
return this.input;
}
@Override
public void addListener(final IModelElementInputListener listener) {
synchronized (this.inputLock) {
ISourceUnit input= this.newInput;
if (input == null) {
input= this.input;
}
if (input != null) {
notifyChanged(ImCollections.newIdentityList(listener), input);
}
if (input == null || this.newInput == input) {
this.listenerList.add(listener);
return;
}
this.newListenerList.add(listener);
}
if (this.newInputJob.getState() != Job.WAITING) {
this.newInputJob.schedule();
}
}
@Override
public void removeListener(final IModelElementInputListener listener) {
this.newListenerList.remove(listener);
this.listenerList.remove(listener);
}
}