blob: f210477df96aa4fd9201de3f846099b2c10002d6 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2009, 2020 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.internal.r.objectbrowser;
import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert;
import java.util.ArrayList;
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 org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.lang.NonNull;
import org.eclipse.statet.jcommons.lang.NonNullByDefault;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.ecommons.ui.util.UIAccess;
import org.eclipse.statet.ltk.model.core.elements.IModelElement;
import org.eclipse.statet.nico.core.runtime.ToolWorkspace;
import org.eclipse.statet.r.console.core.RProcess;
import org.eclipse.statet.r.console.core.RProcessREnvironment;
import org.eclipse.statet.r.console.core.RWorkspace;
import org.eclipse.statet.r.core.data.CombinedRElement;
import org.eclipse.statet.r.ui.util.RNameSearchPattern;
import org.eclipse.statet.rj.data.REnvironment;
@NonNullByDefault
class ContentJob extends Job implements ToolWorkspace.Listener {
static class ContentFilter implements IModelElement.Filter {
private final boolean filterInternal;
private final @Nullable SearchPattern searchPattern;
public ContentFilter(final boolean filterInternal, final @Nullable SearchPattern pattern) {
this.filterInternal= filterInternal;
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.searchPattern == null || this.searchPattern.matches(name));
}
else {
return true;
}
}
}
private final ObjectBrowserView view;
private volatile boolean isScheduled;
/** true if RefreshR is running */
private boolean forceOnWorkspaceChange;
/** the process to update */
private @Nullable RProcess updateSource;
/** the process of last update */
private @Nullable RProcess lastSource;
/** update all environment */
private boolean force;
/** environments to update, if force is false*/
private final Set<RProcessREnvironment> updateSet= new HashSet<>();
private @Nullable List<? extends RProcessREnvironment> rawInput;
private @Nullable List<? extends CombinedRElement> userspaceInput;
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<RProcessREnvironment> envirs= (List<RProcessREnvironment>) 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 @Nullable RProcess process) {
synchronized (this.view.sourceLock) {
if (process != this.view.getTool()) {
return;
}
this.updateSource= process;
this.force= true;
this.updateSet.clear();
}
}
public void forceOnWorkspaceChange() {
this.forceOnWorkspaceChange= true;
}
public void schedule(final RProcess process, final List<RProcessREnvironment> envirs) {
if (envirs != null && process != null) {
synchronized (this.view.sourceLock) {
if (process != this.view.getTool()) {
return;
}
this.updateSource= 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;
}
final IWorkbenchSiteProgressService progressService= this.view.getViewSite().getService(IWorkbenchSiteProgressService.class);
if (progressService != null) {
progressService.incrementBusy();
}
try {
final RProcess process;
final boolean sourceChanged;
final boolean updateInput;
List<RProcessREnvironment> updateList= null;
synchronized (this.view.sourceLock) {
this.isScheduled= false;
process= this.view.getTool();
sourceChanged= (process != this.lastSource);
updateInput= (sourceChanged || this.updateSource != null);
if (this.forceOnWorkspaceChange) {
return Status.OK_STATUS;
}
if (!(sourceChanged || this.force)) {
updateList= new ArrayList<>(this.updateSet.size());
updateList.addAll(this.updateSet);
}
this.lastSource= process;
this.updateSource= null;
this.force= false;
this.updateSet.clear();
}
final ContentInput input= (process != null) ? createInput(process) : null;
// Update input and refresh
final List<RProcessREnvironment> toUpdate;
if (updateInput) {
toUpdate= updateFromSource(input, updateList);
}
else {
toUpdate= null;
}
prepare(input);
synchronized (this.view.sourceLock) {
if (process != this.view.getTool()) {
this.lastSource= null;
return Status.CANCEL_STATUS;
}
if ((!sourceChanged && this.isScheduled) || monitor.isCanceled()) {
if (updateList != null
&& (this.updateSource == process || this.updateSource == null) ) {
this.updateSource= process;
this.updateSet.addAll(updateList);
}
return Status.CANCEL_STATUS;
}
}
UIAccess.getDisplay().syncExec(() -> {
if (process != ContentJob.this.view.getTool()) {
return;
}
ContentJob.this.view.updateView(input, toUpdate);
});
return Status.OK_STATUS;
}
finally {
if (progressService != null) {
progressService.decrementBusy();
}
}
}
private ContentInput createInput(final RProcess source) {
final boolean filterInternal= !this.view.getFilterIncludeInternal();
final String filterText= this.view.getFilterSearchText();
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(source,
this.view.getShowCondensedUserspace(),
envFilter, otherFilter );
}
private @Nullable List<RProcessREnvironment> updateFromSource(final @Nullable ContentInput input,
final @Nullable List<RProcessREnvironment> updateList) {
final List<? extends RProcessREnvironment> oldInput= this.rawInput;
this.rawInput= null;
this.userspaceInput= null;
if (input == null) {
return null;
}
final RProcess process= input.getSource();
final RWorkspace workspaceData= process.getWorkspaceData();
final List<? extends RProcessREnvironment> rawInput= workspaceData.getRSearchEnvironments();
if (rawInput == null || rawInput.size() == 0) {
return null;
}
this.rawInput= rawInput;
input.searchEnvirs= rawInput;
// If search path (environments) is not changed and not in force mode, refresh only the updated entries
List<RProcessREnvironment> updateEntries= null;
TRY_PARTIAL : if (!input.isFilterUserspace()
&& oldInput != null && rawInput.size() == oldInput.size()
&& updateList != null && updateList.size() < rawInput.size() ) {
updateEntries= new ArrayList<>(updateList.size());
for (int i= 0; i < rawInput.size(); i++) {
final RProcessREnvironment envir= 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;
}
}
return updateEntries;
}
private List<? extends CombinedRElement> getUserspaceInput() {
if (this.userspaceInput == null) {
final List<? extends RProcessREnvironment> rawInput= nonNullAssert(this.rawInput);
final List<RProcessREnvironment> userEntries= new ArrayList<>(rawInput.size());
int length= 0;
for (final RProcessREnvironment env : rawInput) {
if (env.getSpecialType() > 0 && env.getSpecialType() <= REnvironment.ENVTYPE_PACKAGE) {
continue;
}
userEntries.add(env);
length+= env.getLength();
}
final List<CombinedRElement> elements= new ArrayList<>(length);
for (final RProcessREnvironment entry : userEntries) {
elements.addAll(entry.getModelChildren((IModelElement.Filter) null));
}
final CombinedRElement[] array= elements.toArray(new @NonNull CombinedRElement[elements.size()]);
this.userspaceInput= ImCollections.newList(array,
ObjectBrowserView.ELEMENTNAME_COMPARATOR );
}
return this.userspaceInput;
}
private void prepare(final @Nullable ContentInput input) {
final List<? extends RProcessREnvironment> rawInput= this.rawInput;
if (input == null || rawInput == null) {
return;
}
@NonNull CombinedRElement[] array;
if (input.isFilterUserspace()) {
// prepare userspace filter
List<? extends CombinedRElement> list= getUserspaceInput();
if (input.hasEnvFilter()) { // prepare env filter
list= input.filterEnvChildren(list);
}
array= list.toArray(new @NonNull CombinedRElement[list.size()]);
}
else {
array= rawInput.toArray(new @NonNull CombinedRElement[rawInput.size()]);
if (input.hasEnvFilter()) { // prepare env filter
for (int i= 0; i < array.length; i++) {
input.getEnvChildren(array[i]);
}
}
}
input.setRootElements(array);
}
}