/******************************************************************************* * 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 * Philipp Bumann <bumannp@gmail.com> - Bug 477602 *******************************************************************************/ package org.eclipse.e4.ui.progress.internal; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import javax.annotation.PostConstruct; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.e4.core.di.annotations.Creatable; import org.eclipse.e4.ui.model.application.MApplication; import org.eclipse.e4.ui.progress.IProgressConstants; import org.eclipse.e4.ui.progress.UIJob; import org.eclipse.e4.ui.progress.internal.legacy.PlatformUI; /** * The ProgressViewUpdater is the singleton that updates viewers. */ @Creatable @Singleton public class ProgressViewUpdater implements IJobProgressManagerListener { private IProgressUpdateCollector[] collectors; Job updateJob; UpdatesInfo currentInfo = new UpdatesInfo(); Object updateLock = new Object(); @Inject ProgressManager progressManager; class MutableBoolean { boolean value; } /* * True when update job is scheduled or running. This is used to limit the * update job to no more than once every 100 ms. See bug 258352 and 395645. */ MutableBoolean updateScheduled = new MutableBoolean(); /** * The UpdatesInfo is a private class for keeping track of the updates * required. */ class UpdatesInfo { Collection<JobTreeElement> additions = new HashSet<>(); Collection<JobTreeElement> deletions = new HashSet<>(); Collection<JobTreeElement> refreshes = new HashSet<>(); boolean updateAll = false; private UpdatesInfo() { //Create a new instance of the info } /** * Add an add update * * @param addition */ void add(JobTreeElement addition) { additions.add(addition); } /** * Add a remove update * * @param removal */ void remove(JobTreeElement removal) { deletions.add(removal); } /** * Add a refresh update * * @param refresh */ void refresh(JobTreeElement refresh) { refreshes.add(refresh); } /** * Reset the caches after completion of an update. */ void reset() { additions.clear(); deletions.clear(); refreshes.clear(); updateAll = false; } void 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<>(); Iterator<JobTreeElement> refreshIterator = refreshes.iterator(); while (refreshIterator.hasNext()) { JobTreeElement treeElement = refreshIterator.next(); 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); } } /** * Create a new instance of the receiver. */ ProgressViewUpdater() { createUpdateJob(); collectors = new IProgressUpdateCollector[0]; } @PostConstruct void init(MApplication application) { progressManager.addListener(this); application.getContext().set(ProgressViewUpdater.class, this); } /** * Add the new collector to the list of collectors. * * @param newCollector */ void addCollector(IProgressUpdateCollector newCollector) { IProgressUpdateCollector[] newCollectors = new IProgressUpdateCollector[collectors.length + 1]; System.arraycopy(collectors, 0, newCollectors, 0, collectors.length); newCollectors[collectors.length] = newCollector; collectors = newCollectors; } /** * Remove the collector from the list of collectors. * * @param provider */ void removeCollector(IProgressUpdateCollector provider) { HashSet<IProgressUpdateCollector> newCollectors = new HashSet<>(); for (int i = 0; i < collectors.length; i++) { if (!collectors[i].equals(provider)) { newCollectors.add(collectors[i]); } } IProgressUpdateCollector[] newArray = new IProgressUpdateCollector[newCollectors .size()]; newCollectors.toArray(newArray); collectors = newArray; //Remove ourselves if there is nothing to update if (collectors.length == 0) { progressManager.removeListener(this); } } /** * Schedule an update. */ void scheduleUpdate() { if (PlatformUI.isWorkbenchRunning()) { // make sure we don't schedule too often boolean scheduleUpdate = false; synchronized (updateScheduled) { if (!updateScheduled.value || updateJob.getState() == Job.NONE) { updateScheduled.value = scheduleUpdate = true; } } if (scheduleUpdate) updateJob.schedule(100); } } /** * Create the update job that handles the updatesInfo. */ private void createUpdateJob() { updateJob = new UIJob(ProgressMessages.ProgressContentProvider_UpdateProgressJob) { @Override public IStatus runInUIThread(IProgressMonitor monitor) { synchronized (updateScheduled) { // updates requested while we are running should cause it to // be rescheduled updateScheduled.value = false; } // Abort the job if there isn't anything if (collectors.length == 0) { return Status.CANCEL_STATUS; } if (currentInfo.updateAll) { synchronized (updateLock) { currentInfo.reset(); } for (IProgressUpdateCollector collector : collectors) { collector.refresh(); } } else { // Lock while getting local copies of the caches. Object[] updateItems; Object[] additionItems; Object[] deletionItems; synchronized (updateLock) { currentInfo.processForUpdate(); updateItems = currentInfo.refreshes.toArray(); additionItems = currentInfo.additions.toArray(); deletionItems = currentInfo.deletions.toArray(); 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); } } } return Status.OK_STATUS; } @Override protected void canceling() { synchronized (updateScheduled) { updateScheduled.value = false; } } }; updateJob.setSystem(true); updateJob.setPriority(Job.DECORATE); updateJob.setProperty(ProgressManagerUtil.INFRASTRUCTURE_PROPERTY, new Object()); } /** * Get the updates info that we are using in the receiver. * * @return Returns the currentInfo. */ UpdatesInfo getCurrentInfo() { return currentInfo; } /** * Refresh the supplied JobInfo. * @param info */ public void refresh(JobInfo info) { if (isUpdateJob(info.getJob())) { return; } synchronized (updateLock) { currentInfo.refresh(info); GroupInfo group = info.getGroupInfo(); if (group != null) { currentInfo.refresh(group); } } //Add in a 100ms delay so as to keep priority low scheduleUpdate(); } @Override public void refreshJobInfo(JobInfo info) { if (isUpdateJob(info.getJob())) { return; } synchronized (updateLock) { currentInfo.refresh(info); } //Add in a 100ms delay so as to keep priority low scheduleUpdate(); } @Override public void refreshGroup(GroupInfo info) { synchronized (updateLock) { currentInfo.refresh(info); } //Add in a 100ms delay so as to keep priority low scheduleUpdate(); } @Override public void addGroup(GroupInfo info) { synchronized (updateLock) { currentInfo.add(info); } scheduleUpdate(); } @Override public void refreshAll() { synchronized (updateLock) { currentInfo.updateAll = true; } //Add in a 100ms delay so as to keep priority low scheduleUpdate(); } @Override public void addJob(JobInfo info) { if (isUpdateJob(info.getJob())) { return; } synchronized (updateLock) { GroupInfo group = info.getGroupInfo(); if (group == null) { currentInfo.add(info); } else { currentInfo.refresh(group); } } scheduleUpdate(); } @Override public void removeJob(JobInfo info) { if (isUpdateJob(info.getJob())) { return; } synchronized (updateLock) { GroupInfo group = info.getGroupInfo(); if (group == null) { currentInfo.remove(info); } else { currentInfo.refresh(group); } } scheduleUpdate(); } @Override public void removeGroup(GroupInfo group) { synchronized (updateLock) { currentInfo.remove(group); } scheduleUpdate(); } @Override public boolean showsDebug() { return Preferences.getBoolean(IProgressConstants.SHOW_SYSTEM_JOBS); } /** * Return whether or not this is the update job. This is used to determine * if a final refresh is required. * * @param job * @return boolean <code>true</true> if this is the * update job */ boolean isUpdateJob(Job job) { return job.equals(updateJob); } }