blob: c43ddc814f38e592542bf68a5c3f3a5e3872f65f [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2006, 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.r.console.core;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.variables.IStringVariable;
import org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.collections.ImList;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.jcommons.status.ProgressMonitor;
import org.eclipse.statet.jcommons.status.StatusException;
import org.eclipse.statet.internal.r.console.core.RObjectDB;
import org.eclipse.statet.internal.r.rdata.REnvironmentVar;
import org.eclipse.statet.internal.r.rdata.RReferenceVar;
import org.eclipse.statet.internal.r.rdata.VirtualMissingVar;
import org.eclipse.statet.nico.core.NicoVariables;
import org.eclipse.statet.nico.core.runtime.ConsoleService;
import org.eclipse.statet.nico.core.runtime.Prompt;
import org.eclipse.statet.nico.core.runtime.ToolWorkspace;
import org.eclipse.statet.r.core.RCore;
import org.eclipse.statet.r.core.data.CombinedRElement;
import org.eclipse.statet.r.core.model.RElementName;
import org.eclipse.statet.r.core.model.RModel;
import org.eclipse.statet.r.core.pkgmanager.IRPkgChangeSet;
import org.eclipse.statet.r.core.pkgmanager.IRPkgManager;
import org.eclipse.statet.r.core.pkgmanager.IRPkgManager.Event;
import org.eclipse.statet.r.core.tool.IRConsoleService;
import org.eclipse.statet.r.nico.ICombinedRDataAdapter;
import org.eclipse.statet.r.nico.RWorkspaceConfig;
import org.eclipse.statet.rj.data.RDataUtils;
import org.eclipse.statet.rj.data.RList;
import org.eclipse.statet.rj.data.RObject;
import org.eclipse.statet.rj.data.RReference;
import org.eclipse.statet.rj.renv.core.REnv;
import org.eclipse.statet.rj.services.RService;
/**
* R Tool Workspace
*/
public class RWorkspace extends ToolWorkspace {
public static final int REFRESH_AUTO= IRConsoleService.AUTO_CHANGE;
public static final int REFRESH_COMPLETE= 0x02;
public static final int REFRESH_PACKAGES= IRConsoleService.PACKAGE_CHANGE;
public static final int RESOLVE_UPTODATE= 1 << 1;
public static final int RESOLVE_FORCE= 1 << 2;
public static final int RESOLVE_RECURSIVE= 1 << 3;
public static final int RESOLVE_INDICATE_NA= 1 << 5;
public static final ImList<IStringVariable> ADDITIONAL_R_VARIABLES= ImCollections.<IStringVariable>newList(
NicoVariables.SESSION_STARTUP_DATE_VARIABLE,
NicoVariables.SESSION_STARTUP_TIME_VARIABLE,
NicoVariables.SESSION_CONNECTION_DATE_VARIABLE,
NicoVariables.SESSION_CONNECTION_TIME_VARIABLE,
NicoVariables.SESSION_STARTUP_WD_VARIABLE );
protected static final class Changes {
private final int changeFlags;
private final Set<RElementName> changedEnvirs;
public Changes(final int changeFlags, final Set<RElementName> changedEnvirs) {
this.changeFlags= changeFlags;
this.changedEnvirs= changedEnvirs;
}
}
private static RReferenceVar verifyVar(final @Nullable RElementName fullName,
final ICombinedRDataAdapter r, final ProgressMonitor m) {
if (fullName == null) {
return null;
}
try {
return (RReferenceVar) r.evalCombinedStruct(fullName, 0, RService.DEPTH_REFERENCE, m);
}
catch (final Exception e) {
return null;
}
}
private boolean rObjectDBEnabled;
private RObjectDB rObjectDB;
private boolean autoRefreshDirty;
private IRPkgManager pkgManager;
private IRPkgManager.Listener pkgManagerListener;
private final HashSet<RElementName> changedEnvirs= new HashSet<>();
public RWorkspace(final AbstractRController controller, final String remoteHost,
final RWorkspaceConfig config) {
super( controller,
new Prompt("> ", ConsoleService.META_PROMPT_DEFAULT), //$NON-NLS-1$
"\n", (char) 0,
remoteHost);
if (config != null) {
this.rObjectDBEnabled= config.getEnableObjectDB();
setAutoRefresh(config.getEnableAutoRefresh());
}
final REnv rEnv= getProcess().getREnv();
if (rEnv != null) {
this.pkgManager= RCore.getRPkgManager(rEnv);
if (this.pkgManager != null) {
this.pkgManagerListener= new IRPkgManager.Listener() {
@Override
public void handleChange(final Event event) {
if ((event.pkgsChanged() & IRPkgManager.INSTALLED) != 0) {
final IRPkgChangeSet changeSet= event.getInstalledPkgChangeSet();
if (changeSet != null && !changeSet.getNames().isEmpty()) {
final RObjectDB db= RWorkspace.this.rObjectDB;
if (db != null) {
db.handleRPkgChange(changeSet.getNames());
}
}
}
}
};
this.pkgManager.addListener(this.pkgManagerListener);
}
}
controlBriefChanged(null, RWorkspace.REFRESH_COMPLETE);
}
@Override
public RProcess getProcess() {
return (RProcess) super.getProcess();
}
private AbstractRController getController() {
return (AbstractRController) getProcess().getController();
}
@Override
public IFileStore toFileStore(final IPath toolPath) throws CoreException {
if (!toolPath.isAbsolute() && toolPath.getDevice() == null && "~".equals(toolPath.segment(0))) { //$NON-NLS-1$
final AbstractRController controller= getController();
if (controller != null) {
final IPath homePath= createToolPath(controller.getProperty("R:file.~")); //$NON-NLS-1$
if (homePath != null) {
return super.toFileStore(homePath.append(toolPath.removeFirstSegments(1)));
}
}
return null;
}
return super.toFileStore(toolPath);
}
@Override
public IFileStore toFileStore(final String toolPath) throws CoreException {
if (toolPath.startsWith("~/")) { //$NON-NLS-1$
return toFileStore(createToolPath(toolPath));
}
return super.toFileStore(toolPath);
}
public boolean hasRObjectDB() {
return (this.rObjectDBEnabled);
}
protected void enableRObjectDB(final boolean enable) {
this.rObjectDBEnabled= enable;
if (enable) {
final AbstractRController controller= getController();
if (controller != null) {
controller.briefChanged(REFRESH_COMPLETE);
}
}
else {
this.rObjectDB= null;
}
}
private int getStamp() {
final AbstractRController controller= getController();
return (controller != null) ? controller.getChangeStamp() : 0;
}
@Override
protected void controlBriefChanged(final Object obj, final int flags) {
if (obj instanceof Collection) {
final Collection<?> collection = (Collection<?>) obj;
for (final Object child : collection) {
controlBriefChanged(child, flags);
}
return;
}
if (obj instanceof RElementName) {
final RElementName name = (RElementName) obj;
if (RElementName.isSearchScopeType(name.getType())) {
this.changedEnvirs.add(name);
return;
}
}
super.controlBriefChanged(obj, flags);
}
protected Changes saveChanges() {
return new Changes(getChangeFlags(),
(!this.changedEnvirs.isEmpty()) ?
(Set<RElementName>) this.changedEnvirs.clone() :
Collections.EMPTY_SET );
}
protected void restoreChanges(final Changes changes) {
super.controlBriefChanged(null, changes.changeFlags);
this.changedEnvirs.addAll(changes.changedEnvirs);
}
private boolean hasBriefedChanges() {
return (getChangeFlags() != 0 || !this.changedEnvirs.isEmpty());
}
@Override
protected void clearBriefedChanges() {
super.clearBriefedChanges();
this.changedEnvirs.clear();
}
public List<? extends RProcessREnvironment> getRSearchEnvironments() {
final RObjectDB db= this.rObjectDB;
return (db != null) ? db.getSearchEnvs(): null;
}
private boolean checkResolve(final CombinedRElement resolved, final int resolve) {
final int stamp;
return ((resolve & RESOLVE_UPTODATE) == 0
|| (resolved instanceof REnvironmentVar
&& (stamp= getStamp()) != 0
&& ((REnvironmentVar) resolved).getStamp() == stamp ));
}
private CombinedRElement filterResolve(final CombinedRElement resolved, final int resolve) {
return ((resolve & RESOLVE_INDICATE_NA) == 0 && resolved instanceof VirtualMissingVar) ?
null : resolved;
}
private CombinedRElement doResolve(final RObjectDB db,
final RReference reference, final int resolve) {
CombinedRElement resolved;
resolved= db.getEnv(reference.getHandle());
if (resolved != null) {
if (checkResolve(resolved, resolve)) {
return resolved;
}
}
else if (reference instanceof CombinedRElement) {
resolved= db.getByName(((CombinedRElement) reference).getElementName());
if (resolved != null && checkResolve(resolved, resolve)) {
return resolved;
}
}
return null;
}
public CombinedRElement resolve(final RReference reference, final int resolve) {
final RObjectDB db= this.rObjectDB;
if (db != null) {
return filterResolve(doResolve(db, reference, resolve), resolve);
}
return null;
}
public CombinedRElement resolve(final RElementName name, final int resolve) {
final RObjectDB db= this.rObjectDB;
if (db != null) {
CombinedRElement resolved;
resolved= db.getByName(name);
if (resolved != null && checkResolve(resolved, resolve)) {
return filterResolve(resolved, resolve);
}
}
return null;
}
public boolean isNamespaceLoaded(final String name) {
final RObjectDB db= this.rObjectDB;
return (db != null && db.isNamespaceLoaded(name));
}
public boolean isUptodate(CombinedRElement element) {
final AbstractRController controller= getController();
if (controller != null) {
if (element instanceof VirtualMissingVar) {
final VirtualMissingVar var= (VirtualMissingVar) element;
return (var.getSource() == controller.getTool()
&& var.getStamp() == controller.getChangeStamp() );
}
while (element != null) {
if (element.getRObjectType() == RObject.TYPE_ENVIRONMENT) {
final REnvironmentVar var= (REnvironmentVar) element;
return (var.getSource() == controller.getTool()
&& var.getStamp() == controller.getChangeStamp() );
}
element= element.getModelParent();
}
}
return false;
}
public boolean isNA(final CombinedRElement element) {
return (element instanceof VirtualMissingVar);
}
public CombinedRElement resolve(final RReference reference, final int resolve,
final int loadOptions, final ProgressMonitor m) throws StatusException {
final AbstractRController controller= getController();
if (controller == null || !(controller instanceof ICombinedRDataAdapter)) {
return null;
}
RObjectDB db= this.rObjectDB;
if (db != null && (resolve & RESOLVE_FORCE) == 0) {
final CombinedRElement resolved= doResolve(db, reference, resolve);
if (resolved != null) {
return filterResolve(resolved, resolve);
}
}
RReferenceVar ref= null;
if (reference instanceof RReferenceVar) {
ref= (RReferenceVar) reference;
if (ref.getHandle() == 0 || !isUptodate(ref)) {
ref= verifyVar(RModel.getFQElementName(ref),
(ICombinedRDataAdapter) controller, m );
}
}
if (ref == null) {
return null;
}
if (db == null) {
db= new RObjectDB(this, controller.getChangeStamp() - 1000,
controller, m );
this.rObjectDB= db;
}
else if (db.getLazyEnvsStamp() != controller.getChangeStamp()) {
db.updateLazyEnvs(controller, m);
}
return db.resolve(ref, resolve, loadOptions,
(ICombinedRDataAdapter) controller, m );
}
public CombinedRElement resolve(final RElementName name, final int resolve,
final int loadOptions, final ProgressMonitor m) throws StatusException {
final AbstractRController controller= getController();
if (controller == null || !(controller instanceof ICombinedRDataAdapter)) {
return null;
}
RObjectDB db= this.rObjectDB;
if ((resolve & RESOLVE_FORCE) == 0 && db != null) {
CombinedRElement resolved;
resolved= db.getByName(name);
if (resolved != null && checkResolve(resolved, resolve)) {
return filterResolve(resolved, resolve);
}
}
final RReferenceVar ref;
if (name.getNextSegment() == null
&& name.getType() == RElementName.SCOPE_NS ) {
ref= new RReferenceVar(0, RObject.TYPE_ENVIRONMENT, RObject.CLASSNAME_ENVIRONMENT,
null, name );
}
else {
ref= verifyVar(name, (ICombinedRDataAdapter) controller, m);
if (ref != null && db != null) {
CombinedRElement resolved;
resolved= db.getEnv(ref.getHandle());
if (resolved != null && checkResolve(resolved, resolve)) {
return filterResolve(resolved, resolve);
}
}
}
if (ref == null) {
return null;
}
if (db == null) {
db= new RObjectDB(this, controller.getChangeStamp() - 1000,
controller, m );
this.rObjectDB= db;
}
else if (db.getLazyEnvsStamp() != controller.getChangeStamp()) {
db.updateLazyEnvs(controller, m);
}
return db.resolve(ref, resolve, loadOptions,
(ICombinedRDataAdapter) controller, m );
}
public boolean isRObjectDBDirty() {
return this.autoRefreshDirty;
}
@Override
protected final void autoRefreshFromTool(final ConsoleService adapter,
final ProgressMonitor m) throws StatusException {
final AbstractRController controller= getController();
if (hasBriefedChanges() || controller.isSuspended()) {
refreshFromTool(controller, getChangeFlags(), m);
}
}
@Override
protected final void refreshFromTool(int options, final ConsoleService adapter,
final ProgressMonitor m) throws StatusException {
final AbstractRController controller= getController();
if ((options & (REFRESH_AUTO | REFRESH_COMPLETE)) != 0) {
options |= getChangeFlags();
}
refreshFromTool(controller, options, m);
}
protected void refreshFromTool(final AbstractRController controller, final int options,
final ProgressMonitor m) throws StatusException {
m.beginSubTask("Update Workspace Data");
if (controller.getTool().isProvidingFeatureSet(RConsoleTool.R_DATA_FEATURESET_ID)) {
final IRDataAdapter r= (IRDataAdapter) controller;
updateWorkspaceDir(r, m);
updateOptions(r, m);
if (this.rObjectDBEnabled) {
final Set<RElementName> elements= this.changedEnvirs;
if ( ((options & REFRESH_COMPLETE) != 0)
|| ( ((((options & REFRESH_AUTO)) != 0) || !elements.isEmpty()
|| controller.isSuspended() )
&& isAutoRefreshEnabled() ) ) {
updateREnvironments(elements, ((options & REFRESH_COMPLETE) != 0), r, m);
clearBriefedChanges();
}
}
else {
clearBriefedChanges();
}
}
else {
clearBriefedChanges();
}
final boolean dirty= !isAutoRefreshEnabled() && hasBriefedChanges();
if (dirty != this.autoRefreshDirty) {
this.autoRefreshDirty= dirty;
addPropertyChanged("RObjectDB.dirty", dirty);
}
}
private void updateWorkspaceDir(
final IRDataAdapter r, final ProgressMonitor m) throws StatusException {
final RObject rWd= r.evalData("getwd()", m); //$NON-NLS-1$
if (RDataUtils.isSingleString(rWd)) {
final String wd= rWd.getData().getChar(0);
if (!isRemote()) {
controlSetWorkspaceDir(EFS.getLocalFileSystem().getStore(createToolPath(wd)));
}
else {
controlSetRemoteWorkspaceDir(createToolPath(wd));
}
}
}
private void updateOptions(
final IRDataAdapter r, final ProgressMonitor m) throws StatusException {
final RList rOptions= (RList) r.evalData("options(\"prompt\", \"continue\")", m); //$NON-NLS-1$
final RObject rPrompt= rOptions.get("prompt"); //$NON-NLS-1$
if (RDataUtils.isSingleString(rPrompt)) {
if (!rPrompt.getData().isNA(0)) {
((AbstractRController) r).setDefaultPromptTextL(rPrompt.getData().getChar(0));
}
}
final RObject rContinue= rOptions.get("continue"); //$NON-NLS-1$
if (RDataUtils.isSingleString(rContinue)) {
if (!rContinue.getData().isNA(0)) {
((AbstractRController) r).setContinuePromptText(rContinue.getData().getChar(0));
}
}
}
private void updateREnvironments(final Set<RElementName> envirs, boolean force,
final IRDataAdapter r, final ProgressMonitor m) throws StatusException {
if (!(r instanceof ICombinedRDataAdapter)) {
return;
}
final AbstractRController controller= getController();
// final long time= System.nanoTime();
// System.out.println(controller.getCounter() + " update");
final RObjectDB previous= this.rObjectDB;
force|= (previous == null || previous.getSearchEnvs() == null);
if (!force && previous.getSearchEnvsStamp() == controller.getChangeStamp() && envirs.isEmpty()) {
return;
}
final RObjectDB db= new RObjectDB(this, controller.getChangeStamp(), controller, m);
final List<REnvironmentVar> updateEnvs= db.update(
envirs, previous, force,
(ICombinedRDataAdapter) r, m );
if (m.isCanceled()) {
return;
}
this.rObjectDB= db;
addPropertyChanged("REnvironments", updateEnvs);
// System.out.println("RSearch Update: " + (System.nanoTime() - time));
// System.out.println("count: " + db.getSearchEnvsElementCount());
}
@Override
protected void dispose() {
if (this.pkgManagerListener != null) {
this.pkgManager.removeListener(this.pkgManagerListener);
this.pkgManagerListener= null;
}
super.dispose();
this.rObjectDB= null;
}
}