| /******************************************************************************* |
| * Copyright (c) 2003, 2015 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * 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.LinkedHashSet; |
| import java.util.Set; |
| import org.eclipse.rap.rwt.SingletonUtil; |
| import org.eclipse.rap.rwt.internal.lifecycle.LifeCycleUtil; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.ui.IWorkbenchPreferenceConstants; |
| import org.eclipse.ui.internal.util.PrefUtil; |
| |
| /** |
| * The ProgressViewUpdater is the singleton that updates viewers. |
| */ |
| //RAP [fappel]: ProgressViewUpdater needs to be a singleton per session |
| //class ProgressViewUpdater implements IJobProgressManagerListener { |
| // |
| // private static ProgressViewUpdater singleton; |
| public class ProgressViewUpdater implements IJobProgressManagerListener { |
| |
| // RAP [fappel]: This class will be instanciated using the |
| // SessionSingletonUtil#getInstance(class) method to have a |
| // replacement for the class variable holding the singleton in |
| // RCP. |
| public final static class ProgressViewUpdaterHolder { |
| private ProgressViewUpdaterHolder() { |
| // prevent from instance creation |
| } |
| |
| // this is the reference to the actual session singleton instance |
| public ProgressViewUpdater singleton; |
| } |
| |
| private Set<IProgressUpdateCollector> collectors; |
| |
| UpdatesInfo currentInfo = new UpdatesInfo(); |
| |
| boolean debug; |
| |
| // RAP [fappel]: |
| private Display display; |
| |
| Throttler throttledUpdate; |
| |
| /** |
| * 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<>(); |
| |
| 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); |
| } |
| |
| /** |
| * Reset the caches after completion of an update. |
| */ |
| synchronized void reset() { |
| additions.clear(); |
| deletions.clear(); |
| refreshes.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]); |
| return new JobTreeElement[][] { updateItems, additionItems, deletionItems }; |
| } |
| |
| } |
| |
| /** |
| * Return a new instance of the receiver. |
| * |
| * @return ProgressViewUpdater |
| */ |
| static ProgressViewUpdater getSingleton() { |
| // RAP [fappel]: session aware implementation |
| // if (singleton == null) { |
| // singleton = new ProgressViewUpdater(); |
| // } |
| // return singleton; |
| ProgressViewUpdaterHolder singletonHolder = getSingletonHolder(); |
| if( singletonHolder.singleton == null ) { |
| singletonHolder.singleton = new ProgressViewUpdater(); |
| singletonHolder.singleton.display = LifeCycleUtil.getSessionDisplay(); |
| singletonHolder.singleton.throttledUpdate = new Throttler(getSingleton().display, Duration.ofMillis(100), |
| singletonHolder.singleton::update); |
| } |
| return singletonHolder.singleton; |
| } |
| |
| private static ProgressViewUpdaterHolder getSingletonHolder() { |
| return SingletonUtil.getSessionInstance(ProgressViewUpdaterHolder.class); |
| } |
| |
| /** |
| * 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() { |
| // RAP [fappel]: |
| // return singleton != null; |
| return getSingletonHolder().singleton != null; |
| } |
| |
| static void clearSingleton() { |
| // RAP [fappel]: |
| // if (singleton != null) { |
| // ProgressManager.getInstance().removeListener(singleton); |
| // } |
| // singleton = null; |
| if( hasSingleton() ) { |
| ProgressManager.getInstance().removeListener(getSingleton()); |
| getSingletonHolder().singleton.display = null; |
| } |
| getSingletonHolder().singleton = null; |
| } |
| |
| /** |
| * Create a new instance of the receiver. |
| */ |
| private ProgressViewUpdater() { |
| collectors = new LinkedHashSet<>(); |
| ProgressManager.getInstance().addListener(this); |
| debug = |
| PrefUtil.getAPIPreferenceStore(). |
| getBoolean(IWorkbenchPreferenceConstants.SHOW_SYSTEM_JOBS); |
| } |
| |
| /** |
| * Add the new collector to the list of collectors. |
| * |
| * @param newCollector |
| */ |
| void addCollector(IProgressUpdateCollector newCollector) { |
| collectors.add(newCollector); |
| } |
| |
| /** |
| * Remove the collector from the list of collectors. |
| * |
| * @param provider |
| */ |
| void removeCollector(IProgressUpdateCollector provider) { |
| collectors.remove(provider); |
| //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) { |
| collector.refresh(); |
| } |
| |
| } else { |
| JobTreeElement[][] elements = currentInfo.processForUpdate(); |
| |
| JobTreeElement[] updateItems = elements[0]; |
| JobTreeElement[] additionItems = elements[1]; |
| JobTreeElement[] deletionItems = elements[2]; |
| |
| currentInfo.reset(); |
| |
| for (IProgressUpdateCollector collector : collectors) { |
| if (updateItems.length > 0) { |
| collector.refresh(updateItems); |
| } |
| if (additionItems.length > 0) { |
| collector.add(additionItems); |
| } |
| if (deletionItems.length > 0) { |
| collector.remove(deletionItems); |
| } |
| } |
| } |
| } |
| |
| @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; |
| } |
| |
| } |