blob: e6aff7fa46fc40dbdeb6d73c34ae76cd21f54c7f [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2015, 2019 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.console.core;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.statet.jcommons.collections.IntArrayList;
import org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.collections.IntList;
import org.eclipse.statet.jcommons.status.ErrorStatus;
import org.eclipse.statet.jcommons.status.ProgressMonitor;
import org.eclipse.statet.jcommons.status.StatusException;
import org.eclipse.statet.jcommons.status.Statuses;
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.r.console.core.AbstractRController;
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.core.data.CombinedRList;
import org.eclipse.statet.r.core.model.RElementName;
import org.eclipse.statet.r.nico.ICombinedRDataAdapter;
import org.eclipse.statet.rj.data.RCharacterStore;
import org.eclipse.statet.rj.data.RDataUtils;
import org.eclipse.statet.rj.data.REnvironment;
import org.eclipse.statet.rj.data.RObject;
import org.eclipse.statet.rj.data.RReference;
import org.eclipse.statet.rj.data.RVector;
import org.eclipse.statet.rj.data.UnexpectedRDataException;
import org.eclipse.statet.rj.services.RService;
public class RObjectDB {
private static final Set<Long> NO_ENVS_SET= Collections.emptySet();
private static class NamespaceEntry {
private volatile VirtualMissingVar na;
private REnvironmentVar namespaceEnv;
private REnvironmentVar namespaceExports;
private REnvironmentVar packageEnv;
}
private static class EnvironmentEntry {
private final REnvironmentVar env;
private byte checking;
private byte checked;
public EnvironmentEntry(final REnvironmentVar env) {
this.env= env;
}
}
private static boolean isNamespaceEnv(final RElementName elementName) {
return (elementName != null && elementName.getType() == RElementName.SCOPE_NS_INT
&& elementName.getNextSegment() == null );
}
private static CombinedRElement first(final CombinedRElement first, final CombinedRElement second) {
return (first != null) ? first : second;
}
private static final byte UPTODATE= RWorkspace.RESOLVE_UPTODATE;
private static final byte RECURSIVE= RWorkspace.RESOLVE_RECURSIVE;
private static final byte RECURSIVE_UPTODATE= RECURSIVE << 1;
/** workspace flags -> this mode */
public static byte toResolveMode(int resolve) {
resolve&= (UPTODATE | RECURSIVE);
if (resolve == (UPTODATE | RECURSIVE)) {
resolve|= RECURSIVE_UPTODATE;
}
return (byte) resolve;
}
private final RWorkspace workspace;
private final ConcurrentHashMap<Long, EnvironmentEntry> envsMap= new ConcurrentHashMap<>();
private int searchEnvsStamp;
private List<REnvironmentVar> searchEnvs;
private List<? extends RProcessREnvironment> searchEnvsPublic;
private int lazyEnvsStamp;
private Set<Long> lazyEnvs;
private final ConcurrentHashMap<String, RObjectDB.NamespaceEntry> namespaceMap= new ConcurrentHashMap<>();
private List<String> forceUpdatePkgNames;
private RObjectDB previousDB;
private byte resolveMode;
private ICombinedRDataAdapter r;
public RObjectDB(final RWorkspace workspace, final int stamp,
final AbstractRController r, final ProgressMonitor m) {
this.workspace= r.getWorkspaceData();
this.searchEnvsStamp= stamp;
this.searchEnvsPublic= Collections.emptyList();
this.lazyEnvs= NO_ENVS_SET;
updateLazyEnvs(r, m);
}
public List<? extends RProcessREnvironment> getSearchEnvs() {
return this.searchEnvsPublic;
}
public int getSearchEnvsStamp() {
return this.searchEnvsStamp;
}
public int getSearchEnvsElementCount() {
int count= 0;
for (final REnvironmentVar env : this.searchEnvs) {
if (env != null) {
final long l= env.getLength();
if (l > 0) {
count+= l;
}
}
}
return count;
}
public REnvironmentVar getEnv(final Long handle) {
final EnvironmentEntry entry= this.envsMap.get(handle);
return (entry != null) ? entry.env : null;
}
public CombinedRElement getNamespaceEnv(final String name) {
if (name != null) {
final RObjectDB.NamespaceEntry entry= this.namespaceMap.get(name);
if (entry != null) {
return first(entry.na, entry.namespaceEnv);
}
}
return null;
}
public CombinedRElement getNamespacePub(final String name) {
if (name != null) {
final RObjectDB.NamespaceEntry entry= this.namespaceMap.get(name);
if (entry != null) {
return first(entry.na, entry.namespaceExports);
}
}
return null;
}
public boolean isNamespaceLoaded(final String name) {
if (name != null) {
final RObjectDB.NamespaceEntry entry= this.namespaceMap.get(name);
return (entry != null && entry.na == null);
}
return false;
}
public CombinedRElement getPackageEnv(final String name) {
if (name != null) {
final RObjectDB.NamespaceEntry entry= this.namespaceMap.get(name);
if (entry != null) {
return first(entry.na, entry.packageEnv);
}
}
return null;
}
public REnvironmentVar getSearchEnv(final String name) {
if (name != null) {
for (final REnvironmentVar env : this.searchEnvs) {
final RElementName elementName= env.getElementName();
if (elementName != null && elementName.getType() == RElementName.SCOPE_SEARCH_ENV
&& name.equals(elementName.getSegmentName())) {
return env;
}
}
}
return null;
}
public CombinedRElement getByName(final RElementName name) {
switch (name.getType()) {
case RElementName.SCOPE_NS:
return getNamespacePub(name.getSegmentName());
case RElementName.SCOPE_NS_INT:
return getNamespaceEnv(name.getSegmentName());
case RElementName.SCOPE_SEARCH_ENV:
return getSearchEnv(name.getSegmentName());
case RElementName.SCOPE_PACKAGE:
return getPackageEnv(name.getSegmentName());
default:
return null;
}
}
public int getLazyEnvsStamp() {
return this.lazyEnvsStamp;
}
public void updateLazyEnvs(final AbstractRController r, final ProgressMonitor m) {
if (this.envsMap != null && !this.envsMap.isEmpty() && !this.lazyEnvs.isEmpty()) {
this.envsMap.keySet().removeAll(this.lazyEnvs);
}
this.lazyEnvsStamp= r.getChangeStamp();
final Set<Long> list= r.getLazyEnvironments(m);
this.lazyEnvs= (list != null && !list.isEmpty()) ? list : NO_ENVS_SET;
}
public List<REnvironmentVar> update(
final Set<RElementName> envs, RObjectDB previous, final boolean force,
final ICombinedRDataAdapter r, final ProgressMonitor m) throws StatusException {
this.r= r;
try {
updateSearchList(m);
updateNamespaceList(m);
if (m.isCanceled()) {
return null;
}
List<String> forcePkgNames= null;
if (previous != null) {
if (force) {
previous= null;
}
else {
forcePkgNames= previous.forceUpdatePkgNames;
}
}
final IntList updateList= createUpdateIdxs(envs, previous, forcePkgNames);
final List<REnvironmentVar> updateEnvs= createUpdateEnvs(updateList, m);
updateEnvMap(updateEnvs, previous, forcePkgNames, m);
if (previous != null) {
for (final Map.Entry<String, RObjectDB.NamespaceEntry> entry : previous.namespaceMap.entrySet()) {
final String name= entry.getKey();
if (forcePkgNames != null && forcePkgNames.contains(name)) {
continue;
}
final RObjectDB.NamespaceEntry oldEntry= entry.getValue();
final RObjectDB.NamespaceEntry newEntry= this.namespaceMap.get(name);
if (oldEntry == null || oldEntry.na != null || newEntry == null) {
continue;
}
if (newEntry.namespaceEnv == null) {
newEntry.namespaceEnv= oldEntry.namespaceEnv;
}
if (newEntry.namespaceExports == null) {
newEntry.namespaceExports= oldEntry.namespaceExports;
}
}
}
return updateEnvs;
}
catch (final UnexpectedRDataException e) {
throw new StatusException(new ErrorStatus(RConsoleCorePlugin.BUNDLE_ID,
"Unexpected return value from R.",
e ));
}
finally {
this.r= null;
}
}
private void updateSearchList(final ProgressMonitor m) throws StatusException,
UnexpectedRDataException {
this.searchEnvsStamp= this.r.getChangeStamp();
this.searchEnvs= new ArrayList<>();
this.searchEnvsPublic= Collections.unmodifiableList(this.searchEnvs);
final RVector<RCharacterStore> searchObj= RDataUtils.checkRCharVector(
this.r.evalData("base::search()", m)); //$NON-NLS-1$
final RCharacterStore namesData= searchObj.getData();
for (int i= 0; i < namesData.getLength(); i++) {
if (namesData.isNA(i)) {
continue;
}
final String name= namesData.getChar(i);
this.searchEnvs.add(new REnvironmentVar(name, true, null, null));
}
}
private void updateNamespaceList(final ProgressMonitor m)
throws StatusException, UnexpectedRDataException {
final RVector<RCharacterStore> searchObj= RDataUtils.checkRCharVector(
this.r.evalData("base::loadedNamespaces()", m )); //$NON-NLS-1$
final RCharacterStore namesData= searchObj.getData();
for (int i= 0; i < namesData.getLength(); i++) {
if (namesData.isNA(i)) {
continue;
}
final String name= namesData.getChar(i);
getNamespaceEntry(name);
}
}
public IntList createUpdateIdxs(final Set<RElementName> envs,
final RObjectDB previous, final List<String> forcePkgNames) {
final IntList updateIdxs= new IntArrayList(this.searchEnvs.size());
if (previous == null) {
for (int newIdx= 0; newIdx < this.searchEnvs.size(); newIdx++) {
updateIdxs.add(newIdx);
}
}
else {
// reuse environments until we found a new or any none-package item
int newIdx= this.searchEnvs.size() - 1;
for (int oldIdx= previous.searchEnvs.size(); newIdx >= 0; newIdx--) {
final REnvironmentVar current= this.searchEnvs.get(newIdx);
final String pkgName;
if (current.getSpecialType() > 0 && current.getSpecialType() <= REnvironment.ENVTYPE_PACKAGE
&& (forcePkgNames == null || (pkgName= getPkgName(current)) == null
|| !forcePkgNames.contains(pkgName) )) {
final int j= previous.searchEnvs.indexOf(current);
if (j >= 0 && j < oldIdx) {
oldIdx= j;
if (envs != null && envs.contains(current.getElementName())) {
updateIdxs.add(newIdx);
}
else {
this.searchEnvs.set(newIdx, previous.searchEnvs.get(oldIdx));
}
continue;
}
}
break;
}
for (; newIdx >= 0; newIdx--) {
updateIdxs.add(newIdx);
}
}
return updateIdxs;
}
private List<REnvironmentVar> createUpdateEnvs(final IntList updateIdxs,
final ProgressMonitor m) throws StatusException {
final ArrayList<REnvironmentVar> updateEnvs= new ArrayList<>(updateIdxs.size());
for (int idx= 0; idx < updateIdxs.size(); idx++) {
if (m.isCanceled()) {
throw new StatusException(Statuses.CANCEL_STATUS);
}
// Debug code
// if (item.getName().equals("methods")) {
{ final REnvironmentVar env= this.searchEnvs.get(idx);
// final RVector<RCharacterStore> ls= (RVector<RCharacterStore>) tools.evalData("ls(name=\""+item.getId()+"\", all.names=TRUE)", monitor);
// final RCharacterStore lsData= ls.getData();
// for (int i= 0; i < lsData.getLength(); i++) {
// final String elementName= lsData.getChar(i);
//// final String elementName= lsData.getChar(133);
// System.out.println(item.getId() + " " + elementName);
// final RObject element= tools.evalStruct("as.environment(\""+item.getId()+"\")$\""+elementName+"\"", monitor);
// System.out.println(element);
// }
// Regular code
final RElementName elementName= env.getElementName();
try {
// long start= System.currentTimeMillis();
final int loadOptions= RService.LOAD_PROMISE;
final RObject robject= this.r.evalCombinedStruct(elementName,
loadOptions, RService.DEPTH_INFINITE,
m );
// long end= System.currentTimeMillis();
// System.out.println("update " + elementName.getDisplayName() + ": " + (end-start));
// System.out.println(robject);
if (robject != null && robject.getRObjectType() == RObject.TYPE_ENVIRONMENT) {
final REnvironmentVar newEnv= (REnvironmentVar) robject;
this.searchEnvs.set(idx, newEnv);
updateEnvs.add(newEnv);
continue;
}
}
catch (final StatusException e) {
RConsoleCorePlugin.logError("Error update environment "+ elementName, e);
if (this.r.getTool().isTerminated() || m.isCanceled()) {
throw e;
}
}
env.setError("update error");
updateEnvs.add(env);
// final RObject test= tools.evalStruct("yy", monitor);
// System.out.println(test);
}
}
return updateEnvs;
}
private RObjectDB.NamespaceEntry getNamespaceEntry(final String name) {
RObjectDB.NamespaceEntry entry= this.namespaceMap.get(name);
if (entry == null) {
entry= new NamespaceEntry();
this.namespaceMap.put(name, entry);
}
return entry;
}
private String getPkgName(final REnvironmentVar env) {
switch (env.getSpecialType()) {
case REnvironment.ENVTYPE_BASE:
return REnvironment.ENVNAME_BASE;
case REnvironment.ENVTYPE_PACKAGE:
return env.getEnvironmentName().substring(8);
case REnvironment.ENVTYPE_NAMESPACE:
return env.getEnvironmentName();
case REnvironment.ENVTYPE_NAMESPACE_EXPORTS:
return env.getEnvironmentName();
default:
return null;
}
}
private EnvironmentEntry registerEnv(final Long handle, final REnvironmentVar env,
final boolean isUptodate) {
final EnvironmentEntry environmentEntry;
if (handle != null) {
environmentEntry= new EnvironmentEntry(env);
this.envsMap.put(handle, environmentEntry);
}
else {
environmentEntry= null;
}
final String name= getPkgName(env);
if (name != null) {
final RObjectDB.NamespaceEntry entry= getNamespaceEntry(name);
switch (env.getSpecialType()) {
case REnvironment.ENVTYPE_BASE:
case REnvironment.ENVTYPE_PACKAGE:
if (isUptodate || (entry.na == null && entry.packageEnv == null)) {
entry.packageEnv= env;
}
break;
case REnvironment.ENVTYPE_NAMESPACE:
if (isUptodate || (entry.na == null && entry.namespaceEnv == null)) {
entry.namespaceEnv= env;
}
break;
case REnvironment.ENVTYPE_NAMESPACE_EXPORTS:
if (isUptodate || (entry.na == null && entry.namespaceExports == null)) {
entry.namespaceExports= env;
}
break;
default:
return environmentEntry;
}
if (isUptodate) {
entry.na= null;
}
}
return environmentEntry;
}
private void registerNA(final VirtualMissingVar na) {
final RObjectDB.NamespaceEntry entry= getNamespaceEntry(na.getElementName().getSegmentName());
entry.na= na;
entry.namespaceEnv= null;
entry.namespaceExports= null;
}
private void updateEnvMap(final List<REnvironmentVar> updateEnvs, final RObjectDB previous,
final List<String> forcePkgNames,
final ProgressMonitor m) throws StatusException {
if (m.isCanceled()) {
return;
}
this.previousDB= null;
this.resolveMode= RECURSIVE;
for (final REnvironmentVar env : this.searchEnvs) {
registerEnv(Long.valueOf(env.getHandle()), env, true);
}
for (final REnvironmentVar env : updateEnvs) {
final Long handle= Long.valueOf(env.getHandle());
EnvironmentEntry entry= this.envsMap.get(handle);
if (entry == null) {
entry= registerEnv(handle, env, true);
}
check(entry, m);
}
this.previousDB= previous;
this.forceUpdatePkgNames= forcePkgNames;
for (final REnvironmentVar env : this.searchEnvs) {
if (!updateEnvs.contains(env)) {
check(env, m);
}
}
this.previousDB= null;
this.forceUpdatePkgNames= null;
return;
}
public CombinedRElement resolve(final RReferenceVar ref, final int resolve,
final int loadOptions,
final ICombinedRDataAdapter r, final ProgressMonitor m) throws StatusException {
this.r= r;
this.resolveMode= toResolveMode(resolve);
try {
final CombinedRElement resolved= evalResolve(ref, ref.getElementName(),
loadOptions, m );
if (resolved != null) {
checkDirectAccessName(resolved, ref.getElementName());
}
return resolved;
}
finally {
this.r= null;
}
}
private CombinedRElement evalResolve(final RReferenceVar ref, final RElementName fullName,
int loadOptions,
final ProgressMonitor m) throws StatusException {
Long handle= (ref.getHandle() != 0) ? Long.valueOf(ref.getHandle()) : null;
final byte mode= this.resolveMode;
try {
this.resolveMode= ((mode & RECURSIVE_UPTODATE) != 0) ? mode : (byte) (mode & ~UPTODATE);
boolean lazy= false;
if (ref.getReferencedRObjectType() == RObject.TYPE_ENVIRONMENT && handle != null) {
ref.setResolver(this.workspace);
{ final EnvironmentEntry entry= this.envsMap.get(handle);
if (entry != null) {
if ((mode & UPTODATE) == 0
|| entry.env.getStamp() == this.r.getChangeStamp() ) {
check(entry, m);
return entry.env;
}
// we are about to replace an environment because of wrong stamp
// to be save, resolve all (for object browser), but correct stamp
// can be loaded later (like this request)
this.resolveMode|= RECURSIVE;
}
}
lazy= this.lazyEnvs.contains(handle);
if (!lazy && this.previousDB != null) {
final EnvironmentEntry prevEntry= this.previousDB.envsMap.get(handle);
final List<String> forcePkgNames= this.forceUpdatePkgNames;
final String pkgName;
if (prevEntry != null
&& ((mode & UPTODATE) == 0
|| (prevEntry.env.getStamp() == this.r.getChangeStamp()))
&& (forcePkgNames == null || (pkgName= getPkgName(prevEntry.env)) == null
|| !forcePkgNames.contains(pkgName) )) {
final EnvironmentEntry entry= registerEnv(handle, prevEntry.env, false);
if (entry != null) {
check(entry, m);
return entry.env;
}
}
}
}
CombinedRElement element= null;
if (ref.getReferencedRObjectType() == RObject.TYPE_ENVIRONMENT && handle != null) {
if (!(lazy || isNamespaceEnv(ref.getElementName()) )) {
loadOptions|= RService.LOAD_PROMISE;
}
element= this.r.evalCombinedStruct(ref,
loadOptions, RService.DEPTH_INFINITE, null, m );
}
else if (fullName != null) {
if (!(lazy || isNamespaceEnv(fullName) )) {
loadOptions|= RService.LOAD_PROMISE;
}
final RElementName symbolName= fullName.getLastSegment();
if (symbolName.getType() == RElementName.MAIN_DEFAULT) {
final RElementName envName= RElementName.create(fullName, symbolName, true);
try {
element= this.r.findCombinedStruct(symbolName, envName, false,
loadOptions, RService.DEPTH_INFINITE, m );
}
catch (final StatusException e) {
}
}
if (element == null) {
element= this.r.evalCombinedStruct(fullName,
loadOptions, RService.DEPTH_INFINITE, m );
}
}
else {
throw new StatusException(new ErrorStatus(RConsoleCorePlugin.BUNDLE_ID,
"Unsupported ref: " + ref ));
}
if (element != null && element.getRObjectType() == RObject.TYPE_ENVIRONMENT) {
final REnvironmentVar env= (REnvironmentVar) element;
if (handle == null && env.getHandle() != 0) {
handle= Long.valueOf(env.getHandle());
}
final EnvironmentEntry entry= registerEnv(handle, env, true);
if (entry != null) {
check(entry, m);
return env;
}
}
if (element instanceof CombinedRList) {
check((CombinedRList) element, m);
}
return element;
}
catch (final StatusException e) {
if (fullName != null
&&fullName.getNextSegment() == null
&& RElementName.isPackageFacetScopeType(fullName.getType()) ) {
final VirtualMissingVar na= new VirtualMissingVar(fullName,
this.r.getTool(), this.r.getChangeStamp());
registerNA(na);
}
RConsoleCorePlugin.logError("Error update ref: " + ref.getElementName(), e);
if (this.r.getTool().isTerminated() || m.isCanceled()) {
throw e;
}
return null;
}
finally {
this.resolveMode= mode;
}
}
private void checkDirectAccessName(final CombinedRElement var, final RElementName name) {
if (name == null || name.getNextSegment() != null) {
return;
}
if (var instanceof REnvironmentVar && RElementName.isScopeType(name.getType())) {
((REnvironmentVar) var).setElementName(name);
}
}
private void check(final EnvironmentEntry entry,
final ProgressMonitor m) throws StatusException {
final byte mode= (byte) (UPTODATE | this.resolveMode); // UPTODATE -> check to setResolver
if ((mode & ~(entry.checked | entry.checking)) != 0) {
final byte was= entry.checking;
entry.checking|= mode;
try {
check(entry.env, m);
entry.checked|= mode;
}
finally {
entry.checking= was;
}
}
}
private void check(final CombinedRList list,
final ProgressMonitor m) throws StatusException {
if (list.hasModelChildren(null)) {
final long length= list.getLength();
if (length <= Integer.MAX_VALUE) {
final int l= (int) length;
ITER_CHILDREN : for (int i= 0; i < l; i++) {
final RObject object= list.get(i);
if (object != null) {
switch (object.getRObjectType()) {
case RObject.TYPE_REFERENCE:
if ((this.resolveMode & RECURSIVE) != 0
&& ((RReference) object).getReferencedRObjectType() == RObject.TYPE_ENVIRONMENT) {
evalResolve((RReferenceVar) object, null, 0, m);
}
else {
((RReferenceVar) object).setResolver(this.workspace);
}
continue ITER_CHILDREN;
case RObject.TYPE_LIST:
case RObject.TYPE_S4OBJECT:
check((CombinedRList) object, m);
continue ITER_CHILDREN;
default:
continue ITER_CHILDREN;
}
}
}
}
else {
ITER_CHILDREN : for (long i= 0; i < length; i++) {
final RObject object= list.get(i);
if (object != null) {
switch (object.getRObjectType()) {
case RObject.TYPE_REFERENCE:
if ((this.resolveMode & RECURSIVE) != 0
&& ((RReference) object).getReferencedRObjectType() == RObject.TYPE_ENVIRONMENT) {
evalResolve((RReferenceVar) object, null, 0, m);
}
else {
((RReferenceVar) object).setResolver(this.workspace);
}
continue ITER_CHILDREN;
case RObject.TYPE_LIST:
case RObject.TYPE_S4OBJECT:
check((CombinedRList) object, m);
continue ITER_CHILDREN;
default:
continue ITER_CHILDREN;
}
}
}
}
}
}
public void handleRPkgChange(final List<String> names) {
this.forceUpdatePkgNames= (this.forceUpdatePkgNames != null) ?
ImCollections.concatList(this.forceUpdatePkgNames, names) :
names;
}
}