/*=============================================================================# # Copyright (c) 2009-2016 Stephan Wahlbrink (WalWare.de) 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: # Stephan Wahlbrink - initial API and implementation #=============================================================================*/ package de.walware.statet.r.internal.objectbrowser; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; 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.ui.dialogs.SearchPattern; import org.eclipse.ui.progress.IWorkbenchSiteProgressService; import de.walware.jcommons.collections.ImCollections; import de.walware.ecommons.ltk.core.model.IModelElement; import de.walware.ecommons.ui.util.UIAccess; import de.walware.statet.nico.core.runtime.ToolWorkspace; import de.walware.rj.data.REnvironment; import de.walware.statet.r.console.core.RProcess; import de.walware.statet.r.console.core.RWorkspace; import de.walware.statet.r.console.core.RWorkspace.ICombinedREnvironment; import de.walware.statet.r.core.data.ICombinedRElement; import de.walware.statet.r.ui.util.RNameSearchPattern; class ContentJob extends Job implements ToolWorkspace.Listener { static class ContentFilter implements IModelElement.Filter { private final boolean filterInternal; private final boolean filterNoPattern; private final SearchPattern searchPattern; public ContentFilter(final boolean filterInternal, final SearchPattern pattern) { this.filterInternal = filterInternal; this.filterNoPattern = (pattern == null); this.searchPattern = pattern; } @Override public boolean include(final IModelElement element) { final String name = element.getElementName().getSegmentName(); if (name != null) { if (this.filterInternal && name.length() > 0 && name.charAt(0) == '.') { return false; } return (this.filterNoPattern || this.searchPattern.matches(name)); } else { return true; } } } private final ObjectBrowserView view; /** true if RefreshR is running */ private boolean forceOnWorkspaceChange; /** the process to update */ private RProcess updateProcess; /** the process of last update */ private RProcess lastProcess; /** update all environment */ private boolean force; /** environments to update, if force is false*/ private final Set<ICombinedREnvironment> updateSet= new HashSet<>(); private List<? extends ICombinedREnvironment> rawInput; private List<? extends ICombinedRElement> userspaceInput; private volatile boolean isScheduled; public ContentJob(final ObjectBrowserView view) { super("R Object Browser Update"); this.view = view; setSystem(true); setUser(false); } @Override public void propertyChanged(final ToolWorkspace workspace, final Map<String, Object> properties) { final RWorkspace rWorkspace = (RWorkspace) workspace; if (properties.containsKey("REnvironments")) { if (this.forceOnWorkspaceChange) { this.forceOnWorkspaceChange = false; final RProcess process = rWorkspace.getProcess(); forceUpdate(process); schedule(); } else { final List<RWorkspace.ICombinedREnvironment> envirs = (List<RWorkspace.ICombinedREnvironment>) properties.get("REnvironments"); schedule(rWorkspace.getProcess(), envirs); } } final Object autorefresh = properties.get("AutoRefresh.enabled"); if (autorefresh instanceof Boolean) { UIAccess.getDisplay().asyncExec(new Runnable() { @Override public void run() { if (rWorkspace.getProcess() != ContentJob.this.view.getTool()) { return; } ContentJob.this.view.updateAutoRefresh(((Boolean) autorefresh).booleanValue()); } }); } else { // autorefresh already updates dirty final Object dirty = properties.get("RObjectDB.dirty"); if (dirty instanceof Boolean) { UIAccess.getDisplay().asyncExec(new Runnable() { @Override public void run() { if (rWorkspace.getProcess() != ContentJob.this.view.getTool()) { return; } ContentJob.this.view.updateDirty(((Boolean) dirty).booleanValue()); } }); } } } public void forceUpdate(final RProcess process) { synchronized (this.view.processLock) { if (process != this.view.getTool()) { return; } this.updateProcess = process; this.force = true; this.updateSet.clear(); } } public void forceOnWorkspaceChange() { this.forceOnWorkspaceChange = true; } public void schedule(final RProcess process, final List<RWorkspace.ICombinedREnvironment> envirs) { if (envirs != null && process != null) { synchronized (this.view.processLock) { if (process != this.view.getTool()) { return; } this.updateProcess = process; if (!this.force) { this.updateSet.removeAll(envirs); this.updateSet.addAll(envirs); } } } schedule(); } @Override public boolean shouldSchedule() { this.isScheduled = true; return true; } @Override protected IStatus run(final IProgressMonitor monitor) { if (!this.isScheduled) { return Status.CANCEL_STATUS; } this.isScheduled = false; final IWorkbenchSiteProgressService progressService = (IWorkbenchSiteProgressService) this.view.getViewSite().getService(IWorkbenchSiteProgressService.class); if (progressService != null) { progressService.incrementBusy(); } try { final List<ICombinedREnvironment> updateList; final boolean force; final boolean updateInput; final RProcess process; synchronized (this.view.processLock) { force = this.force; updateInput = (force || this.updateProcess != null); process = (updateInput) ? this.updateProcess : this.view.getTool(); if (process != this.view.getTool() || this.forceOnWorkspaceChange) { return Status.OK_STATUS; } updateList= new ArrayList<>(this.updateSet.size()); updateList.addAll(this.updateSet); this.updateSet.clear(); this.updateProcess = null; this.force = false; } final ContentInput input = createHandler(process, updateInput); // Update input and refresh final List<ICombinedREnvironment> toUpdate; if (updateInput) { toUpdate = updateInput((!force) ? updateList : null, process, input); } else { toUpdate = null; } if (this.rawInput != null) { prescan(input); } else if (process != null) { input.processChanged = false; } synchronized (this.view.processLock) { if (process != this.view.getTool()) { return Status.CANCEL_STATUS; } if ((!input.processChanged && this.isScheduled) || monitor.isCanceled()) { this.updateSet.addAll(updateList); this.force |= force; if (updateInput && this.updateProcess == null) { this.updateProcess = process; } return Status.CANCEL_STATUS; } } UIAccess.getDisplay().syncExec(new Runnable() { @Override public void run() { if (process != ContentJob.this.view.getTool()) { return; } ContentJob.this.view.updateViewer(toUpdate, input); } }); if (this.rawInput != null) { this.lastProcess = process; } else { this.lastProcess = null; } return Status.OK_STATUS; } finally { if (progressService != null) { progressService.decrementBusy(); } } } private ContentInput createHandler(final RProcess process, final boolean updateInput) { final boolean processChanged = ((process != null) ? process != this.lastProcess : this.lastProcess != null); final boolean filterInternal = !this.view.getShowInternal(); final String filterText = this.view.getSearchText(); IModelElement.Filter envFilter; IModelElement.Filter otherFilter; if (filterText != null && filterText.length() > 0) { final SearchPattern filterPattern = new RNameSearchPattern(); filterPattern.setPattern(filterText); envFilter = new ContentFilter(filterInternal, filterPattern); otherFilter = (filterInternal) ? new ContentFilter(filterInternal, null) : null; } else if (filterInternal) { envFilter = new ContentFilter(filterInternal, null); otherFilter = new ContentFilter(filterInternal, null); } else { envFilter = null; otherFilter = null; } return new ContentInput(processChanged, updateInput, this.view.getShowConsenseUserspace(), envFilter, otherFilter ); } private List<ICombinedREnvironment> updateInput(final List<ICombinedREnvironment> updateList, final RProcess process, final ContentInput input) { if (process != null) { final List<? extends ICombinedREnvironment> oldInput = this.rawInput; final RWorkspace workspaceData = process.getWorkspaceData(); this.rawInput = workspaceData.getRSearchEnvironments(); if (this.rawInput == null || this.rawInput.size() == 0) { this.rawInput = null; this.userspaceInput = null; return null; } input.searchEnvirs = this.rawInput; // If search path (environments) is not changed and not in force mode, refresh only the updated entries List<ICombinedREnvironment> updateEntries = null; TRY_PARTIAL : if (!input.showCondensedUserspace && this.rawInput != null && oldInput != null && this.rawInput.size() == oldInput.size() && updateList != null && updateList.size() < this.rawInput.size() ) { updateEntries= new ArrayList<>(updateList.size()); for (int i = 0; i < this.rawInput.size(); i++) { final ICombinedREnvironment envir = this.rawInput.get(i); if (envir.equals(oldInput.get(i))) { if (updateList.remove(envir)) { updateEntries.add(envir); } } else { // search path is changed updateEntries = null; break TRY_PARTIAL; } } if (!updateList.isEmpty()) { updateEntries = null; break TRY_PARTIAL; } } // Prepare Userspace filter if (input.showCondensedUserspace) { int length = 0; final List<ICombinedREnvironment> userEntries= new ArrayList<>(this.rawInput.size()); for (final ICombinedREnvironment env : this.rawInput) { if (env.getSpecialType() > 0 && env.getSpecialType() <= REnvironment.ENVTYPE_PACKAGE) { continue; } userEntries.add(env); length += env.getLength(); } final List<IModelElement> elements= new ArrayList<>(length); for (final ICombinedREnvironment entry : userEntries) { elements.addAll(entry.getModelChildren((IModelElement.Filter) null)); } final ICombinedRElement[] array = elements.toArray(new ICombinedRElement[elements.size()]); Arrays.sort(array, ObjectBrowserView.ELEMENTNAME_COMPARATOR); this.userspaceInput= ImCollections.newList(array); } else { this.userspaceInput = null; } return updateEntries; } else { this.rawInput = null; this.userspaceInput = null; return null; } } private void prescan(final ContentInput input) { // Prescan filter if (!input.showCondensedUserspace) { final ICombinedRElement[] array = this.rawInput.toArray(new ICombinedRElement[this.rawInput.size()]); if (input.hasEnvFilter()) { for (int i = 0; i < array.length; i++) { input.getEnvFilterChildren(array[i]); } } input.rootElements = array; } else { final List<? extends ICombinedRElement> list; if (input.hasEnvFilter()) { list = input.filterEnvChildren(this.userspaceInput); } else { list = this.userspaceInput; } input.rootElements = list.toArray(new ICombinedRElement[list.size()]); } } }