| /******************************************************************************* |
| * 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; |
| } |
| |
| } |