| /*=============================================================================# |
| # Copyright (c) 2017, 2022 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.Locale; |
| 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(Locale.ROOT); //$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; |
| } |
| |
| } |