| /*=============================================================================# |
| # 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; |
| } |
| |
| } |