blob: e1c16df0497f2b795f04eefa639baad14a8a176d [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2017, 2021 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.rj.server.util;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.rj.RjInvalidConfigurationException;
public class LocalREnv {
public static final int OS_WIN= 1;
public static final int OS_NIX= 2;
public static final int OS_MAC= 3;
private static final Logger LOGGER= Logger.getLogger("org.eclipse.statet.rj.server"); //$NON-NLS-1$
private static final int OS_TYPE;
static {
final String osname= System.getProperty("os.name").toLowerCase(); //$NON-NLS-1$
if (osname.contains("win")) { //$NON-NLS-1$
OS_TYPE= OS_WIN;
}
else if (osname.contains("mac")) { //$NON-NLS-1$
OS_TYPE= OS_MAC;
}
else {
OS_TYPE= OS_NIX;
}
}
private static final String PATH_SPLITTER= Pattern.compile(File.pathSeparator, Pattern.LITERAL).pattern();
private static final String DEFAULT_HOME_TEMPLATE= "${R_HOME}"; //$NON-NLS-1$
private static final List<String> DEFAULT_PATHS_TEMPLATES= Arrays.asList(
"${R_LIBS}", //$NON-NLS-1$
"${R_LIBS_USER}", //$NON-NLS-1$
"${R_LIBS_SITE}" , //$NON-NLS-1$
"${R_HOME}/library" ); //$NON-NLS-1$
private static void add(final List<Path> paths, final Path pathToAdd) {
if (pathToAdd != null && !paths.contains(pathToAdd)) {
paths.add(pathToAdd);
}
}
private static void addAll(final List<Path> paths, final @Nullable List<Path> pathsToAdd) {
if (pathsToAdd != null) {
for (final Path path : pathsToAdd) {
add(paths, path);
}
}
}
/**
* R home path.
*/
private final Path rHomePath;
/**
* R library path (list with directories containing R packages).
*/
private final List<Path> rLibPaths;
public LocalREnv(final String rHomeTemplate, final List<String> rLibPathsTemplates,
@Nullable Function<String, @Nullable String> varResolver)
throws RjInvalidConfigurationException {
if (rHomeTemplate == null) {
throw new NullPointerException("rHomeTemplate"); //$NON-NLS-1$
}
if (rLibPathsTemplates == null) {
throw new NullPointerException("rLibPathsTemplates"); //$NON-NLS-1$
}
if (varResolver == null) {
varResolver= System::getenv;
}
this.rHomePath= checkPath(resolveTemplate(rHomeTemplate, varResolver));
this.rLibPaths= resolveRLibPaths(rLibPathsTemplates, varResolver);
checkSpec();
}
public LocalREnv() throws RjInvalidConfigurationException {
this(DEFAULT_HOME_TEMPLATE, DEFAULT_PATHS_TEMPLATES, null);
}
public LocalREnv(final Function<String, @Nullable String> varResolver)
throws RjInvalidConfigurationException {
this(DEFAULT_HOME_TEMPLATE, DEFAULT_PATHS_TEMPLATES, varResolver);
}
public LocalREnv(final String rHome, final List<String> rLibPaths)
throws RjInvalidConfigurationException {
if (rHome == null) {
throw new NullPointerException("rHome"); //$NON-NLS-1$
}
if (rLibPaths == null) {
throw new NullPointerException("rLibPaths"); //$NON-NLS-1$
}
this.rHomePath= checkPath(rHome);
this.rLibPaths= resolveRLibPaths(rLibPaths, null);
checkSpec();
}
public LocalREnv(final Path rHomePath, final List<Path> rLibPaths)
throws RjInvalidConfigurationException {
if (rHomePath == null) {
throw new NullPointerException("rHome"); //$NON-NLS-1$
}
if (rLibPaths == null) {
throw new NullPointerException("rLibPaths"); //$NON-NLS-1$
}
this.rHomePath= rHomePath;
this.rLibPaths= rLibPaths;
checkSpec();
}
protected List<Path> resolveRLibPaths(final List<String> rLibPathsTemplates,
final @Nullable Function<String, @Nullable String> varResolver) {
final List<Path> paths= new ArrayList<>();
for (final String template : rLibPathsTemplates) {
final String value= (varResolver != null) ?
resolveTemplate(template, varResolver) :
template;
try {
addAll(paths, checkPathList(value));
}
catch (final Exception e) {
LOGGER.log(Level.WARNING, "An error occurred when adding '" + template + "' to R library paths.", e);
}
}
return paths;
}
protected String resolveTemplate(final String s,
final Function<String, @Nullable String> varResolver) {
if (s != null && s.indexOf('$') >= 0) {
final StringBuilder sb= new StringBuilder();
int startIdx= 0;
int endIdx= 0;
while (true) {
startIdx= s.indexOf("${", endIdx);
if (startIdx >= 0) {
sb.append(s, endIdx, startIdx);
startIdx+= 2;
endIdx= s.indexOf('}', startIdx);
if (endIdx >= 0) {
final String value= varResolver.apply(s.substring(startIdx, endIdx));
if (value != null) {
sb.append(value);
endIdx++;
continue;
}
}
break;
}
else {
sb.append(s, endIdx, s.length());
return sb.toString();
}
}
}
return s;
}
protected void checkSpec() throws RjInvalidConfigurationException {
if (LOGGER.isLoggable(Level.CONFIG)) {
final StringBuilder sb= new StringBuilder();
sb.append("RJClassLoader - R home path= "); //$NON-NLS-1$
if (this.rHomePath != null) {
sb.append(this.rHomePath);
}
else {
sb.append("<missing>"); //$NON-NLS-1$
}
sb.append('\n');
sb.append("RJClassLoader - R library paths= "); //$NON-NLS-1$
if (this.rLibPaths != null) {
if (this.rLibPaths.isEmpty()) {
sb.append("<empty>"); //$NON-NLS-1$
}
else {
ServerUtils.prettyPrint(this.rLibPaths, sb);
}
}
else {
sb.append("<missing>"); //$NON-NLS-1$
}
LOGGER.log(Level.CONFIG, sb.toString());
}
if (this.rHomePath == null) {
throw new RjInvalidConfigurationException("Spec of R home path is missing."); //$NON-NLS-1$
}
if (this.rLibPaths.isEmpty()) {
throw new RjInvalidConfigurationException("Spec of R library paths is empty"); //$NON-NLS-1$
}
}
public @Nullable Path checkPath(String path) {
if (path != null) {
path= path.trim();
if (!path.isEmpty()) {
return Path.of(path).normalize();
}
}
return null;
}
public @Nullable List<Path> checkPathList(@Nullable String pathList) {
if (pathList != null) {
pathList= pathList.trim();
if (!pathList.isEmpty()) {
final String[] split= pathList.split(PATH_SPLITTER);
final ArrayList<Path> list= new ArrayList<>(split.length);
for (int i= 0; i < split.length; i++) {
final Path path= checkPath(split[i]);
if (path != null) {
list.add(path);
}
}
return list;
}
}
return null;
}
public int getOSType() {
return OS_TYPE;
}
public Path getRHomePath() {
return this.rHomePath;
}
public List<Path> getRLibPaths() {
return Collections.unmodifiableList(this.rLibPaths);
}
public @Nullable Path searchRPkg(final String name) {
for (final Path path : this.rLibPaths) {
try {
final Path packagePath= path.resolve(name);
if (Files.isRegularFile(packagePath.resolve("DESCRIPTION"))) {
return packagePath;
}
}
catch (final Exception e) {}
}
return null;
}
}