blob: 399ad8944d36d4ee0721ae2127ab97c1d77d73d4 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2012, 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.core.pkgmanager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.eclipse.osgi.util.NLS;
import org.eclipse.statet.jcommons.lang.Nullable;
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.r.core.RCore;
import org.eclipse.statet.r.core.pkgmanager.IRPkgData;
import org.eclipse.statet.r.core.pkgmanager.IRPkgInfo;
import org.eclipse.statet.r.core.pkgmanager.ISelectedRepos;
import org.eclipse.statet.r.core.pkgmanager.RPkgInfo;
import org.eclipse.statet.r.core.pkgmanager.RPkgUtils;
import org.eclipse.statet.r.core.pkgmanager.RRepo;
import org.eclipse.statet.rj.data.RArray;
import org.eclipse.statet.rj.data.RCharacterStore;
import org.eclipse.statet.rj.data.RDataUtils;
import org.eclipse.statet.rj.data.UnexpectedRDataException;
import org.eclipse.statet.rj.renv.core.BasicRPkg;
import org.eclipse.statet.rj.renv.core.BasicRPkgCompilation;
import org.eclipse.statet.rj.renv.core.REnv;
import org.eclipse.statet.rj.renv.core.RLibLocation;
import org.eclipse.statet.rj.renv.core.RNumVersion;
import org.eclipse.statet.rj.renv.core.RPkg;
import org.eclipse.statet.rj.renv.core.RPkgCompilation;
import org.eclipse.statet.rj.renv.core.RPkgList;
import org.eclipse.statet.rj.renv.runtime.RLibLocationInfo;
import org.eclipse.statet.rj.renv.runtime.RuntimeRLibPaths;
import org.eclipse.statet.rj.services.FunctionCall;
import org.eclipse.statet.rj.services.RService;
final class RPkgScanner {
private static final String AVAIL_LIST_FNAME= "rj:::renv.getAvailPkgs"; //$NON-NLS-1$
private static final int AVAIL_LIST_COUNT1= 9;
private static final int AVAIL_LIST_IDX1_NAME= 0;
private static final int AVAIL_LIST_IDX1_VERSION= 1;
private static final int AVAIL_LIST_IDX1_PRIORITY= 2;
private static final int AVAIL_LIST_IDX1_LICENSE= 3;
private static final int AVAIL_LIST_IDX1_DEPENDS= 4;
private static final int AVAIL_LIST_IDX1_IMPORTS= 5;
private static final int AVAIL_LIST_IDX1_LINKINGTO= 6;
private static final int AVAIL_LIST_IDX1_SUGGESTS= 7;
private static final int AVAIL_LIST_IDX1_ENHANCES= 8;
private static final String INST_LIST_FNAME= "rj:::renv.getInstPkgs"; //$NON-NLS-1$
private static final int INST_LIST_COUNT1= 4;
private static final int INST_LIST_IDX1_NAME= 0;
private static final int INST_LIST_IDX1_VERSION= 1;
private static final int INST_LIST_IDX1_TITLE= 2;
private static final int INST_LIST_IDX1_BUILT= 3;
private static final String INST_DETAIL_FNAME= "rj:::renv.getInstPkgDetail"; //$NON-NLS-1$
private static final int INST_DETAIL_LENGTH= 7;
private static final int INST_DETAIL_IDX_PRIORITY= 0;
private static final int INST_DETAIL_IDX_LICENSE= 1;
private static final int INST_DETAIL_IDX_DEPENDS= 2;
private static final int INST_DETAIL_IDX_IMPORTS= 3;
private static final int INST_DETAIL_IDX_LINKINGTO= 4;
private static final int INST_DETAIL_IDX_SUGGESTS= 5;
private static final int INST_DETAIL_IDX_ENHANCES= 6;
private static @Nullable String decodeDescrField(final @Nullable String s) {
if (s == null) {
return null;
}
int mode= 0; // 1= single space, 2= chars to replace
int idxMode= 0;
int idxDone= 0;
StringBuilder sb= null;
for (int idx = 0; idx < s.length(); idx++) {
switch (s.charAt(idx)) {
case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07:
case 0x08: case 0x09: case 0x0A: case 0x0B: case 0x0C: case 0x0D: case 0x0E: case 0x0F:
case 0x7F:
switch (mode) {
case 0:
mode= 2;
idxMode= idx;
continue;
case 1:
mode= 2;
continue;
// case 2:
default:
continue;
}
case ' ':
switch (mode) {
case 0:
mode= 1;
idxMode= idx;
continue;
case 1:
mode= 2;
continue;
// case 2:
default:
continue;
}
default:
switch (mode) {
case 0:
continue;
case 1:
mode= 0;
continue;
// case 2:
default:
if (sb == null) {
sb= new StringBuilder(s.length());
}
sb.append(s, idxDone, idxMode);
sb.append(' ');
idxDone= idx + 1;
mode= 0;
continue;
}
}
}
if (sb != null) {
sb.append(s, idxDone, (mode == 0) ? s.length() : idxMode);
return sb.toString();
}
else {
return (mode == 0) ? s : s.substring(0, idxMode);
}
}
private static List<RPkg> parsePkgRefs(final String s) {
if (s == null || s.isEmpty()) {
return Collections.emptyList();
}
final List<RPkg> list= new ArrayList<>(4);
String name= null;
String version= null;
boolean ws;
final StringBuilder sb= new StringBuilder();
ITER_S: for (int i= 0; i <= s.length();) {
int c= (i < s.length()) ? s.charAt(i) : -1;
if (c == -1 || c == ',') {
if (name != null) {
list.add(new BasicRPkg(name, RNumVersion.create(version)));
}
name= null;
version= null;
i++;
continue ITER_S;
}
if (Character.isLetterOrDigit(c)) {
if (name != null) {
// ?
}
name= null;
version= null;
int j= i + 1;
while (true) {
c= (j < s.length()) ? s.charAt(j) : -1;
if (c < 0 || Character.isWhitespace(c) || c == '(' || c == ',') {
name= s.substring(i, j).intern();
i= j;
continue ITER_S;
}
j++;
}
}
if (c == '(') {
if (name == null || version != null) {
// ?
name= null;
version= null;
}
sb.setLength(0);
ws= false;
int j= ++i;
while (true) {
c= (j < s.length()) ? s.charAt(j) : -1;
if (c < 0 || c == ')') {
version= sb.toString();
i= j + 1;
continue ITER_S;
}
if (Character.isWhitespace(c)) {
ws= true;
}
else {
if (ws) {
sb.append(' ');
ws= false;
}
sb.append((char) c);
}
j++;
}
}
i++;
continue ITER_S;
}
return list;
}
private final BasicRPkgCompilation<IRPkgData> expectedPkgs= new BasicRPkgCompilation<IRPkgData>(4) {
@Override
protected RPkgList<IRPkgData> newPkgList() {
return new RPkgListImpl<>(4);
}
};
public RPkgScanner() {
}
void addExpectedPkg(final RLibLocation location, final IRPkgData pkg) {
final String path= location.getDirectory();
final RPkgListImpl<IRPkgData> list= (RPkgListImpl<IRPkgData>) this.expectedPkgs.getOrAdd(path);
list.set(pkg);
}
private void clearExpected() {
final List<RPkgList<IRPkgData>> all= this.expectedPkgs.getAll();
for (final RPkgList<IRPkgData> list : all) {
((RPkgListImpl<IRPkgData>) list).clear();
}
}
RPkgCompilation<IRPkgData> loadAvailable(final REnv rEnv, final ISelectedRepos repoSettings,
final RService r, final ProgressMonitor m) throws StatusException {
m.beginSubTask("Loading available R packages...");
try {
final RCharacterStore repos= RDataUtils.checkRCharVector(r.evalData(
"options('repos')[[1L]]", m)).getData(); //$NON-NLS-1$
final int l= RDataUtils.checkIntLength(repos);
final BasicRPkgCompilation<IRPkgData> newAvailable= new BasicRPkgCompilation<>(l);
for (int idxRepos= 0; idxRepos < l; idxRepos++) {
final String repoURL= repos.getChar(idxRepos);
if (repoURL == null || repoURL.isEmpty()) {
continue;
}
final RRepo repo= Util.getRepoByURL(repoSettings.getRepos(), repoURL);
m.beginSubTask(NLS.bind("Loading available R packages from {0}...", repoURL));
RArray<RCharacterStore> data;
{ final FunctionCall call= r.createFunctionCall(AVAIL_LIST_FNAME);
call.addChar("repo", repoURL); //$NON-NLS-1$
if (repo.getPkgType() != null) {
final String key= RPkgUtils.getPkgTypeInstallKey(r.getPlatform(), repo.getPkgType());
if (key == null) {
continue;
}
}
data= RDataUtils.checkRCharArray(call.evalData(m), 2);
RDataUtils.checkColumnCountEqual(data, AVAIL_LIST_COUNT1);
}
final RCharacterStore store= data.getData();
final int nPkgs= data.getDim().getInt(0);
final RPkgListImpl<IRPkgData> list= new RPkgListImpl<>(nPkgs);
newAvailable.add(repo.getId(), list);
for (int idxPkgs= 0; idxPkgs < nPkgs; idxPkgs++) {
String name= store.getChar(RDataUtils.getDataIdx(nPkgs, idxPkgs, AVAIL_LIST_IDX1_NAME));
final String version= store.getChar(RDataUtils.getDataIdx(nPkgs, idxPkgs, AVAIL_LIST_IDX1_VERSION));
if (name != null && !name.isEmpty() && version != null) {
name= name.intern();
final RPkgData pkg= new RPkgData(name, RNumVersion.create(version), repo.getId());
pkg.setLicense(store.getChar(RDataUtils.getDataIdx(nPkgs, idxPkgs, AVAIL_LIST_IDX1_LICENSE)));
pkg.setDepends(RPkgScanner.parsePkgRefs(store.getChar(RDataUtils.getDataIdx(nPkgs, idxPkgs, AVAIL_LIST_IDX1_DEPENDS))));
pkg.setImports(RPkgScanner.parsePkgRefs(store.getChar(RDataUtils.getDataIdx(nPkgs, idxPkgs, AVAIL_LIST_IDX1_IMPORTS))));
pkg.setLinkingTo(RPkgScanner.parsePkgRefs(store.getChar(RDataUtils.getDataIdx(nPkgs, idxPkgs, AVAIL_LIST_IDX1_LINKINGTO))));
pkg.setSuggests(RPkgScanner.parsePkgRefs(store.getChar(RDataUtils.getDataIdx(nPkgs, idxPkgs, AVAIL_LIST_IDX1_SUGGESTS))));
pkg.setEnhances(RPkgScanner.parsePkgRefs(store.getChar(RDataUtils.getDataIdx(nPkgs, idxPkgs, AVAIL_LIST_IDX1_ENHANCES))));
pkg.setPriority(store.getChar(RDataUtils.getDataIdx(nPkgs, idxPkgs, AVAIL_LIST_IDX1_PRIORITY)));
list.add(pkg);
}
}
}
return newAvailable;
}
catch (final UnexpectedRDataException | StatusException e) {
throw new StatusException(new ErrorStatus(RCore.BUNDLE_ID,
"An error occurred when loading list of available R packages.",
e ));
}
}
RPkgCompilation<IRPkgInfo> loadInstalled(final RuntimeRLibPaths rLibPaths, final boolean[] update,
final Change event,
final RService r, final ProgressMonitor m) throws StatusException {
m.beginSubTask("Updating installed R packages...");
try {
final BasicRPkgCompilation<IRPkgInfo> newInstalled= new BasicRPkgCompilation<>(
rLibPaths.getRLibLocations().size() );
final RPkgCompilation<IRPkgInfo> prevInstalled= (RPkgCompilation<IRPkgInfo>)
((event.oldPkgs != null) ? event.oldPkgs.getInstalled() : null);
final RPkgChangeSet changeSet= new RPkgChangeSet();
event.installedPkgs= changeSet;
for (final RLibLocation libLocation : rLibPaths.getRLibLocations()) {
final RLibLocationInfo locationInfo= rLibPaths.getInfo(libLocation);
if (locationInfo == null || locationInfo.getLibPathsIndex() < 0) {
continue;
}
if (newInstalled.getBySource(libLocation.getDirectory()) != null) {
continue;
}
if (update == null || update[locationInfo.getLibPathsIndex()] || prevInstalled == null) {
final RArray<RCharacterStore> data;
{ final FunctionCall call= r.createFunctionCall(INST_LIST_FNAME);
call.addChar("lib", locationInfo.getDirectoryRPath()); //$NON-NLS-1$
data= RDataUtils.checkRCharArray(call.evalData(m), 2);
RDataUtils.checkColumnCountEqual(data, INST_LIST_COUNT1);
}
final RCharacterStore store= data.getData();
final int nPkgs= data.getDim().getInt(0);
final RPkgList<IRPkgInfo> oldList= (prevInstalled != null) ?
prevInstalled.getBySource(libLocation.getDirectory()) : null;
final RPkgListImpl<IRPkgInfo> newList= new RPkgListImpl<>(nPkgs);
final RPkgList<IRPkgData> expectedList= this.expectedPkgs.getBySource(libLocation.getDirectory());
for (int idxPkg= 0; idxPkg < nPkgs; idxPkg++) {
String name= store.getChar(RDataUtils.getDataIdx(nPkgs, idxPkg, INST_LIST_IDX1_NAME));
final String version= store.getChar(RDataUtils.getDataIdx(nPkgs, idxPkg, INST_LIST_IDX1_VERSION));
if (name != null && !name.isEmpty() && version != null && !version.isEmpty()) {
name= name.intern();
String built= store.getChar(RDataUtils.getDataIdx(nPkgs, idxPkg, INST_LIST_IDX1_BUILT));
if (built == null) {
built= ""; //$NON-NLS-1$
}
final IRPkgInfo oldPkg= (oldList != null) ? oldList.get(name) : null;
final IRPkgInfo newPkg;
final boolean changed= (oldPkg == null
|| !oldPkg.getVersion().toString().equals(version)
|| !oldPkg.getBuilt().equals(built) );
if (!changed) {
newPkg= oldPkg;
}
else {
final IRPkgData expectedData =
(expectedList != null) ? expectedList.get(name) : null;
newPkg= new RPkgInfo(name, RNumVersion.create(version), built,
decodeDescrField(
store.getChar(RDataUtils.getDataIdx(nPkgs, idxPkg, INST_LIST_IDX1_TITLE)) ),
libLocation,
0,
event.stamp,
(expectedData != null) ? expectedData.getRepoId() : null );
changeSet.names.add(name);
if (oldPkg == null) {
changeSet.added.add(newPkg);
}
else {
changeSet.changed.add(oldPkg);
}
}
newList.add(newPkg);
}
}
if (oldList != null) {
int i= 0, j= 0;
final int in= oldList.size(), jn= newList.size();
while (i < in) {
final IRPkgInfo oldPkg= oldList.get(i++);
final String name= oldPkg.getName();
if (j < jn) {
if (newList.get(j).getName() == name) {
j++;
continue;
}
final int idx= newList.indexOf(name, j + 1);
if (idx >= 0) {
j= idx + 1;
continue;
}
}
changeSet.names.add(name);
changeSet.deleted.add(oldPkg);
}
}
newInstalled.add(libLocation.getDirectory(), newList);
}
else {
final RPkgList<IRPkgInfo> prevList= prevInstalled.getBySource(libLocation.getDirectory());
if (prevList != null) {
newInstalled.add(libLocation.getDirectory(), prevList);
}
else {
// error/warning?
}
}
}
clearExpected();
return newInstalled;
}
catch (final UnexpectedRDataException | StatusException e) {
throw new StatusException(new ErrorStatus(RCore.BUNDLE_ID,
"An error occurred when loading list of installed R packages.",
e ));
}
}
void updateInstFull(final RuntimeRLibPaths rLibPaths, final boolean[] update,
final FullRPkgSet newPkgs, final Change event,
final RService r, final ProgressMonitor m) throws StatusException {
m.beginSubTask("Updating installed R packages...");
try {
final FullRPkgSet oldFullPkgs= (event.oldPkgs instanceof FullRPkgSet) ?
(FullRPkgSet) event.oldPkgs : null;
final RPkgChangeSet changeSet= new RPkgChangeSet();
event.installedPkgs= changeSet;
for (final RLibLocation location : rLibPaths.getRLibLocations()) {
final RLibLocationInfo locationInfo= rLibPaths.getInfo(location);
if (locationInfo == null || locationInfo.getLibPathsIndex() < 0) {
continue;
}
if (update == null || update[locationInfo.getLibPathsIndex()] || oldFullPkgs == null) {
final RArray<RCharacterStore> data;
{ final FunctionCall call= r.createFunctionCall(INST_LIST_FNAME);
call.addChar("lib", locationInfo.getDirectoryRPath()); //$NON-NLS-1$
data= RDataUtils.checkRCharArray(call.evalData(m), 2);
RDataUtils.checkColumnCountEqual(data, INST_LIST_COUNT1);
}
final RCharacterStore store= data.getData();
final int nPkgs= data.getDim().getInt(0);
final RPkgList<? extends IRPkgInfo> oldList= (event.oldPkgs != null) ?
event.oldPkgs.getInstalled().getBySource(location.getDirectory()) : null;
final RPkgListImpl<RPkgInfoAndData> newList= new RPkgListImpl<>(nPkgs);
final RPkgList<IRPkgData> expectedList= this.expectedPkgs.getBySource(location.getDirectory());
for (int idxPkg= 0; idxPkg < nPkgs; idxPkg++) {
String name= store.getChar(RDataUtils.getDataIdx(nPkgs, idxPkg, INST_LIST_IDX1_NAME));
final String version= store.getChar(RDataUtils.getDataIdx(nPkgs, idxPkg, INST_LIST_IDX1_VERSION));
if (name != null && !name.isEmpty() && version != null && !version.isEmpty()) {
name= name.intern();
String built= store.getChar(RDataUtils.getDataIdx(nPkgs, idxPkg, INST_LIST_IDX1_BUILT));
if (built == null) {
built= ""; //$NON-NLS-1$
}
final IRPkgInfo oldPkg= (oldList != null) ? oldList.get(name) : null;
final RPkgInfoAndData newPkg;
final boolean changed= (oldPkg == null
|| !oldPkg.getVersion().toString().equals(version)
|| !oldPkg.getBuilt().equals(built) );
if (!changed && (oldPkg instanceof RPkgInfoAndData)) {
newPkg= (RPkgInfoAndData) oldPkg;
}
else {
final IRPkgData expectedData= (changed && expectedList != null) ?
expectedList.get(name) : null;
newPkg= new RPkgInfoAndData(name,
(!changed) ? oldPkg.getVersion() : RNumVersion.create(version),
built,
decodeDescrField(
store.getChar(RDataUtils.getDataIdx(nPkgs, idxPkg, INST_LIST_IDX1_TITLE)) ),
location,
(!changed) ? oldPkg.getFlags() : 0,
(!changed) ? oldPkg.getInstallStamp() : event.stamp,
(!changed) ? oldPkg.getRepoId() : ((expectedData != null) ? expectedData.getRepoId() : null) );
RCharacterStore detail;
{ final FunctionCall call= r.createFunctionCall(INST_DETAIL_FNAME);
call.addChar("lib", locationInfo.getDirectoryRPath()); //$NON-NLS-1$
call.addChar("name", name); //$NON-NLS-1$
detail= RDataUtils.checkRCharVector(call.evalData(m)).getData();
RDataUtils.checkLengthEqual(detail, INST_DETAIL_LENGTH);
}
newPkg.setPriority(detail.getChar(INST_DETAIL_IDX_PRIORITY));
newPkg.setLicense(detail.getChar(INST_DETAIL_IDX_LICENSE));
newPkg.setDepends(RPkgScanner.parsePkgRefs(detail.getChar(INST_DETAIL_IDX_DEPENDS)));
newPkg.setImports(RPkgScanner.parsePkgRefs(detail.getChar(INST_DETAIL_IDX_IMPORTS)));
newPkg.setLinkingTo(RPkgScanner.parsePkgRefs(detail.getChar(INST_DETAIL_IDX_LINKINGTO)));
newPkg.setSuggests(RPkgScanner.parsePkgRefs(detail.getChar(INST_DETAIL_IDX_SUGGESTS)));
newPkg.setEnhances(RPkgScanner.parsePkgRefs(detail.getChar(INST_DETAIL_IDX_ENHANCES)));
if (changed) {
changeSet.names.add(name);
if (oldPkg == null) {
changeSet.added.add(newPkg);
}
else {
changeSet.changed.add(oldPkg);
}
}
}
newList.add(newPkg);
}
}
if (oldList != null) {
int i= 0, j= 0;
final int in= oldList.size(), jn= newList.size();
while (i < in) {
final IRPkgInfo oldPkg= oldList.get(i++);
final String name= oldPkg.getName();
if (j < jn) {
if (newList.get(j).getName() == name) {
j++;
continue;
}
final int idx= newList.indexOf(name, j + 1);
if (idx >= 0) {
j= idx + 1;
continue;
}
}
changeSet.names.add(name);
changeSet.deleted.add(oldPkg);
}
}
newPkgs.getInstalled().add(location.getDirectory(), newList);
}
else {
newPkgs.getInstalled().add(location.getDirectory(),
oldFullPkgs.getInstalled().getBySource(location.getDirectory()) );
}
}
return;
}
catch (final UnexpectedRDataException | StatusException e) {
throw new StatusException(new ErrorStatus(RCore.BUNDLE_ID,
"An error occurred when loading list of installed R packages.",
e ));
}
}
}