blob: 7e88c24268f62b4bf59f5cd7c04e72b159320e72 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2008, 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.model.core.impl;
import java.util.HashMap;
import java.util.LinkedList;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.statet.jcommons.collections.ImIdentityList;
import org.eclipse.statet.jcommons.lang.NonNullByDefault;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.internal.ltk.core.LtkCorePlugin;
import org.eclipse.statet.ltk.core.Ltk;
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.LtkModelUtils;
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;
import org.eclipse.statet.ltk.model.core.element.SourceUnitModelInfo;
/**
* Abstract model update event job
*/
@NonNullByDefault
public abstract class AbstractModelEventJob<TModelElement extends LtkModelElement<?>, InfoType extends SourceUnitModelInfo> extends Job {
private static class SafeRunnable implements ISafeRunnable {
final ElementChangedEvent event;
ElementChangedListener listener;
public SafeRunnable(final ElementChangedEvent event) {
this.event= event;
}
@Override
public void run() {
this.listener.elementChanged(this.event);
}
@Override
public void handleException(final Throwable e) {
LtkCorePlugin.log(new Status(IStatus.ERROR, Ltk.BUNDLE_ID, -1,
"An error occured while notifying an ElementChangedListener.", e )); //$NON-NLS-1$
}
}
protected class Task {
private final TModelElement element;
private InfoType oldInfo;
private InfoType newInfo;
public Task(final TModelElement element) {
this.element= element;
}
public TModelElement getElement() {
return this.element;
}
public InfoType getOldInfo() {
return this.oldInfo;
}
public InfoType getNewInfo() {
return this.newInfo;
}
void run() {
final LtkModelElementDelta delta= createDelta(this);
fireDelta(delta);
}
}
private final AbstractModelManager modelManager;
private final Object tasksLock= new Object();
private final LinkedList<TModelElement> taskQueue= new LinkedList<>();
private final HashMap<TModelElement, Task> taskDetail= new HashMap<>();
private boolean working= false;
private boolean stop= false;
public AbstractModelEventJob(final AbstractModelManager manager) {
super("Model Events for " + manager.getModelTypeId()); //$NON-NLS-1$
setPriority(BUILD);
setSystem(true);
setUser(false);
this.modelManager= manager;
}
public void addUpdate(final TModelElement element,
final InfoType oldModel, final InfoType newModel) {
synchronized (this.tasksLock) {
Task task= this.taskDetail.get(element);
if (task == null) {
task= new Task(element);
task.oldInfo= oldModel;
this.taskDetail.put(element, task);
}
else {
this.taskQueue.remove(element);
}
task.newInfo= newModel;
this.taskQueue.add(element);
if (!this.working) {
schedule();
}
}
}
protected abstract LtkModelElementDelta createDelta(Task task);
@Override
protected IStatus run(final IProgressMonitor monitor) {
while (true) {
Task task;
synchronized (this.tasksLock) {
final @Nullable TModelElement element= (!this.taskQueue.isEmpty()) ? this.taskQueue.removeFirst() : null;
if (element == null || this.stop) {
this.working= false;
return Status.OK_STATUS;
}
this.working= true;
task= this.taskDetail.remove(element);
}
try {
task.run();
}
catch (final Throwable e) {
LtkCorePlugin.log(new Status(IStatus.ERROR, Ltk.BUNDLE_ID, -1,
"An error occurred when firing model event for " + this.modelManager.getModelTypeId() + ".", //$NON-NLS-1$
e ));
}
}
}
protected void dispose() {
synchronized (this.tasksLock) {
this.stop= true;
this.taskQueue.clear();
this.taskDetail.clear();
}
}
private void fireDelta(final LtkModelElementDelta delta) {
final SourceUnit su= LtkModelUtils.getSourceUnit(delta.getModelElement());
if (su == null) {
return;
}
final WorkingContext context= su.getWorkingContext();
final ElementChangedEvent event= new ElementChangedEvent(delta, context);
final SafeRunnable runnable= new SafeRunnable(event);
final ImIdentityList<ElementChangedListener> listeners= this.modelManager.getElementChangedListeners(context);
for (final ElementChangedListener listener : listeners) {
runnable.listener= listener;
SafeRunner.run(runnable);
}
}
}