| /*=============================================================================# |
| # Copyright (c) 2007, 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 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.jcommons.lang.Nullable; |
| |
| 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.ElementChangedListener; |
| import org.eclipse.statet.ltk.model.core.ModelManager; |
| import org.eclipse.statet.ltk.model.core.element.LtkModelElementDelta; |
| import org.eclipse.statet.ltk.model.core.element.SourceUnit; |
| |
| |
| /** |
| * Controller implementation for input of a part and its model updates. |
| */ |
| public class ElementInfoController implements ModelElementInputProvider, Disposable { |
| |
| private static int NEWINPUT_DELAY= 100; |
| |
| |
| private final ModelManager modelProvider; |
| private final WorkingContext modelContext; |
| private final ElementChangedListener elementChangeListener; |
| |
| private final CopyOnWriteIdentityListSet<ModelElementInputListener> listenerList= new CopyOnWriteIdentityListSet<>(); |
| private final CopyOnWriteIdentityListSet<ModelElementInputListener> newListenerList= new CopyOnWriteIdentityListSet<>(); |
| |
| private final Object inputLock= new Object(); |
| private volatile SourceUnit input; |
| private @Nullable SourceUnit 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) { |
| SourceUnit input; |
| ImIdentityList<ModelElementInputListener> 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 ModelManager manager, final WorkingContext context) { |
| this.elementChangeListener= new ElementChangedListener() { |
| @Override |
| public void elementChanged(final ElementChangedEvent event) { |
| SourceUnit input; |
| ImIdentityList<ModelElementInputListener> 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 @Nullable SourceUnit 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<ModelElementInputListener> checkNewListeners() { |
| final ImIdentityList<ModelElementInputListener> listeners= this.newListenerList.clearToList(); |
| for (final ModelElementInputListener listener : listeners) { |
| this.listenerList.add(listener); |
| } |
| return listeners; |
| } |
| |
| private void notifyChanged(final ImIdentityList<ModelElementInputListener> listeners, |
| final @Nullable SourceUnit input) { |
| for (final ModelElementInputListener listener : listeners) { |
| listener.elementChanged(input); |
| } |
| } |
| |
| private void notifyInitial(final ImIdentityList<ModelElementInputListener> listeners, |
| final SourceUnit input, |
| final IProgressMonitor monitor) { |
| if (listeners.isEmpty() || input != this.input) { |
| return; |
| } |
| try { |
| input.connect(monitor); |
| for (final ModelElementInputListener listener : listeners) { |
| if (input != this.input) { |
| return; |
| } |
| listener.elementInitialInfo(input); |
| } |
| } |
| finally { |
| input.disconnect(monitor); |
| } |
| } |
| |
| private void notifyUpdated(final ImIdentityList<ModelElementInputListener> listeners, |
| final SourceUnit input, final LtkModelElementDelta delta, |
| final IProgressMonitor monitor) { |
| if (input != this.input) { |
| return; |
| } |
| try { |
| input.connect(monitor); |
| for (final ModelElementInputListener listener : listeners) { |
| if (input != this.input) { |
| return; |
| } |
| listener.elementUpdatedInfo(input, delta); |
| } |
| } |
| finally { |
| input.disconnect(monitor); |
| } |
| } |
| |
| @Override |
| public SourceUnit getInput() { |
| return this.input; |
| } |
| |
| @Override |
| public void addListener(final ModelElementInputListener listener) { |
| synchronized (this.inputLock) { |
| SourceUnit 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 ModelElementInputListener listener) { |
| this.newListenerList.remove(listener); |
| this.listenerList.remove(listener); |
| } |
| |
| } |