blob: ca99a98053f5d36ef486e42edfebf1ee5db1970a [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.internal.ltk.core.LTKCorePlugin;
import org.eclipse.statet.ltk.core.LTK;
import org.eclipse.statet.ltk.core.LTKUtils;
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.elements.IModelElement;
import org.eclipse.statet.ltk.model.core.elements.ISourceUnit;
import org.eclipse.statet.ltk.model.core.elements.ISourceUnitModelInfo;
/**
* Abstract model update event job
*/
public abstract class AbstractModelEventJob<ElementType extends IModelElement, InfoType extends ISourceUnitModelInfo> extends Job {
private static class SafeRunnable implements ISafeRunnable {
final ElementChangedEvent event;
IElementChangedListener 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 ElementType element;
private InfoType oldInfo;
private InfoType newInfo;
public Task(final ElementType element) {
this.element= element;
}
public ElementType getElement() {
return this.element;
}
public InfoType getOldInfo() {
return this.oldInfo;
}
public InfoType getNewInfo() {
return this.newInfo;
}
void run() {
final IModelElementDelta delta= createDelta(this);
fireDelta(delta);
}
}
private final AbstractModelManager modelManager;
private final Object tasksLock= new Object();
private final LinkedList<IModelElement> taskQueue= new LinkedList<>();
private final HashMap<IModelElement, 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 ElementType 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 IModelElementDelta createDelta(Task task);
@Override
protected IStatus run(final IProgressMonitor monitor) {
while (true) {
Task task;
synchronized (this.tasksLock) {
final IModelElement 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 IModelElementDelta delta) {
final ISourceUnit su= LTKUtils.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<IElementChangedListener> listeners= this.modelManager.getElementChangedListeners(context);
for (final IElementChangedListener listener : listeners) {
runnable.listener= listener;
SafeRunner.run(runnable);
}
}
}