blob: 6d729363c3dd2e96c80fdcd074f5784b9e5431d4 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2003, 2015 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.ui.internal.progress;
import java.time.Duration;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import org.eclipse.jface.util.Throttler;
import org.eclipse.ui.IWorkbenchPreferenceConstants;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.progress.FinishedJobs.KeptJobsListener;
import org.eclipse.ui.internal.util.PrefUtil;
/**
* The ProgressViewUpdater is the singleton that updates viewers.
*/
class ProgressViewUpdater implements IJobProgressManagerListener {
private static ProgressViewUpdater singleton;
/**
* Registered collectors to be feed with throttled updates. The value remembers
* if the collector wants to collect updates for finished jobs
* (<code>true</code>) or not.
*/
private Map<IProgressUpdateCollector, Boolean> collectors;
final UpdatesInfo currentInfo = new UpdatesInfo();
boolean debug;
Throttler throttledUpdate = new Throttler(PlatformUI.getWorkbench().getDisplay(), Duration.ofMillis(100),
this::update);
final KeptJobsListener finishedJobsListener = new FinishedJobsListener();
/**
* The UpdatesInfo is a private class for keeping track of the updates required.
*/
static class UpdatesInfo {
Collection<JobTreeElement> additions = new LinkedHashSet<>();
Collection<JobTreeElement> deletions = new LinkedHashSet<>();
Collection<JobTreeElement> refreshes = new LinkedHashSet<>();
Collection<JobTreeElement> keptFinished = new LinkedHashSet<>();
Collection<JobTreeElement> keptRemoved = new LinkedHashSet<>();
volatile boolean updateAll;
private UpdatesInfo() {
// Create a new instance of the info
}
/**
* Add an add update
*
* @param addition
*/
synchronized void add(JobTreeElement addition) {
additions.add(addition);
}
/**
* Add a remove update
*
* @param removal
*/
synchronized void remove(JobTreeElement removal) {
deletions.add(removal);
}
/**
* Add a refresh update
*
* @param refresh
*/
synchronized void refresh(JobTreeElement refresh) {
refreshes.add(refresh);
}
/**
* Add an update for a job which has finished and should be kept
*
* @param finished
*/
synchronized void keptFinished(JobTreeElement finished) {
keptFinished.add(finished);
}
/**
* Add an update for a job which was kept and is removed now
*
* @param removed
*/
synchronized void keptRemoved(JobTreeElement removed) {
keptRemoved.add(removed);
}
/**
* Reset the caches after completion of an update.
*/
synchronized void reset() {
additions.clear();
deletions.clear();
refreshes.clear();
keptFinished.clear();
keptRemoved.clear();
updateAll = false;
}
/**
* @return array containing updated, added and deleted items
*/
synchronized JobTreeElement[][] processForUpdate() {
HashSet<JobTreeElement> staleAdditions = new HashSet<>();
Iterator<JobTreeElement> additionsIterator = additions.iterator();
while (additionsIterator.hasNext()) {
JobTreeElement treeElement = additionsIterator.next();
if (!treeElement.isActive()) {
if (deletions.contains(treeElement)) {
staleAdditions.add(treeElement);
}
}
}
additions.removeAll(staleAdditions);
HashSet<JobTreeElement> obsoleteRefresh = new HashSet<>();
for (JobTreeElement treeElement : refreshes) {
if (deletions.contains(treeElement) || additions.contains(treeElement)) {
obsoleteRefresh.add(treeElement);
}
// Also check for groups that are being added
Object parent = treeElement.getParent();
if (parent != null && (deletions.contains(parent) || additions.contains(parent))) {
obsoleteRefresh.add(treeElement);
}
if (!treeElement.isActive()) {
// If it is done then delete it
obsoleteRefresh.add(treeElement);
deletions.add(treeElement);
}
}
refreshes.removeAll(obsoleteRefresh);
JobTreeElement[] updateItems = refreshes.toArray(new JobTreeElement[0]);
JobTreeElement[] additionItems = additions.toArray(new JobTreeElement[0]);
JobTreeElement[] deletionItems = deletions.toArray(new JobTreeElement[0]);
JobTreeElement[] keptFinishedItems = keptFinished.toArray(new JobTreeElement[0]);
JobTreeElement[] keptRemovedItems = keptRemoved.toArray(new JobTreeElement[0]);
return new JobTreeElement[][] { updateItems, additionItems, deletionItems, keptFinishedItems,
keptRemovedItems };
}
}
class FinishedJobsListener implements KeptJobsListener {
@Override
public void finished(JobTreeElement jte) {
currentInfo.keptFinished(jte);
throttledUpdate.throttledExec();
}
@Override
public void removed(JobTreeElement jte) {
if (jte == null) {
currentInfo.updateAll = true;
} else {
currentInfo.keptRemoved(jte);
}
throttledUpdate.throttledExec();
}
}
/**
* Return a new instance of the receiver.
*
* @return ProgressViewUpdater
*/
static ProgressViewUpdater getSingleton() {
if (singleton == null) {
singleton = new ProgressViewUpdater();
}
return singleton;
}
/**
* Return whether or not there is a singleton for updates to avoid creating
* extra listeners.
*
* @return boolean <code>true</code> if there is already a singleton
*/
static boolean hasSingleton() {
return singleton != null;
}
static void clearSingleton() {
if (singleton != null) {
ProgressManager.getInstance().removeListener(singleton);
}
singleton = null;
}
/**
* Create a new instance of the receiver.
*/
private ProgressViewUpdater() {
collectors = new LinkedHashMap<>();
ProgressManager.getInstance().addListener(this);
debug = PrefUtil.getAPIPreferenceStore().getBoolean(IWorkbenchPreferenceConstants.SHOW_SYSTEM_JOBS);
}
/**
* Add the new collector to the list of collectors. Collector will not receive
* updates from {@link FinishedJobs}.
*
* @param newCollector
*/
void addCollector(IProgressUpdateCollector newCollector) {
addCollector(newCollector, false);
}
/**
* Add the new collector to the list of collectors.
*
* @param newCollector
* @param includeFinished if <code>true</code> the collector will receive
* updates from {@link FinishedJobs}.
*/
void addCollector(IProgressUpdateCollector newCollector, boolean includeFinished) {
collectors.put(newCollector, includeFinished);
if (includeFinished) {
FinishedJobs.getInstance().addListener(finishedJobsListener);
}
}
/**
* Remove the collector from the list of collectors.
*
* @param provider
*/
void removeCollector(IProgressUpdateCollector provider) {
collectors.remove(provider);
// Remove listener if there is no more collector interested in finished jobs
if (!collectors.containsValue(Boolean.TRUE)) {
FinishedJobs.getInstance().removeListener(finishedJobsListener);
}
// Remove ourselves if there is nothing to update
if (collectors.isEmpty()) {
clearSingleton();
}
}
/** Running in UI thread by throttledUpdate */
private void update() {
// Abort the update if there isn't anything
if (collectors.isEmpty()) {
return;
}
if (currentInfo.updateAll) {
currentInfo.reset();
for (IProgressUpdateCollector collector : collectors.keySet()) {
collector.refresh();
}
} else {
JobTreeElement[][] elements;
synchronized (currentInfo) {
elements = currentInfo.processForUpdate();
currentInfo.reset();
}
JobTreeElement[] updateItems = elements[0];
JobTreeElement[] additionItems = elements[1];
JobTreeElement[] deletionItems = elements[2];
JobTreeElement[] keptFinsihedItems = elements[3];
JobTreeElement[] keptRemovedItems = elements[4];
for (IProgressUpdateCollector collector : collectors.keySet()) {
if (updateItems.length > 0) {
collector.refresh(updateItems);
}
if (additionItems.length > 0) {
collector.add(additionItems);
}
if (deletionItems.length > 0) {
collector.remove(deletionItems);
}
if (keptFinsihedItems.length > 0) {
collector.refresh(keptFinsihedItems);
}
if (keptRemovedItems.length > 0) {
collector.remove(keptRemovedItems);
}
}
}
}
@Override
public void refreshJobInfo(JobInfo info) {
currentInfo.refresh(info);
// Add in a 100ms delay so as to keep priority low
throttledUpdate.throttledExec();
}
@Override
public void refreshGroup(GroupInfo info) {
currentInfo.refresh(info);
// Add in a 100ms delay so as to keep priority low
throttledUpdate.throttledExec();
}
@Override
public void addGroup(GroupInfo info) {
currentInfo.add(info);
throttledUpdate.throttledExec();
}
@Override
public void refreshAll() {
currentInfo.updateAll = true;
// Add in a 100ms delay so as to keep priority low
throttledUpdate.throttledExec();
}
@Override
public void addJob(JobInfo info) {
GroupInfo group = info.getGroupInfo();
if (group == null) {
currentInfo.add(info);
} else {
currentInfo.refresh(group);
}
throttledUpdate.throttledExec();
}
@Override
public void removeJob(JobInfo info) {
GroupInfo group = info.getGroupInfo();
if (group == null) {
currentInfo.remove(info);
} else {
currentInfo.refresh(group);
}
throttledUpdate.throttledExec();
}
@Override
public void removeGroup(GroupInfo group) {
currentInfo.remove(group);
throttledUpdate.throttledExec();
}
@Override
public boolean showsDebug() {
return debug;
}
}