blob: 7d46244b8e24a4b90b126f6abf34d8b78a236968 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2007, 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.rj.renv.core;
import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert;
import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullElse;
import static org.eclipse.statet.jcommons.lang.SystemUtils.OS_WIN;
import static org.eclipse.statet.rj.renv.core.REnvUtils.standardizePathString;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.collections.ImList;
import org.eclipse.statet.jcommons.lang.NonNullByDefault;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.jcommons.lang.SystemUtils;
import org.eclipse.statet.jcommons.status.ErrorStatus;
import org.eclipse.statet.jcommons.status.MultiStatus;
import org.eclipse.statet.jcommons.status.Status;
import org.eclipse.statet.jcommons.status.Statuses;
import org.eclipse.statet.internal.rj.renv.core.REnvCoreInternals;
@NonNullByDefault
public abstract class BasicREnvConfiguration implements REnvConfiguration {
public static final String DEFAULT_R_DOC_DIRECTORY=
R_HOME_DIRECTORY_VAR_STRING + "/doc"; //$NON-NLS-1$
public static final String DEFAULT_R_SHARE_DIRECTORY=
R_HOME_DIRECTORY_VAR_STRING + "/share"; //$NON-NLS-1$
public static final String DEFAULT_R_INCLUDE_DIRECTORY=
R_HOME_DIRECTORY_VAR_STRING + "/include"; //$NON-NLS-1$
public static final ImList<String> DEFAULT_LIB_GROUP_IDS= ImCollections.newList(
RLibGroup.R_OTHER,
RLibGroup.R_USER,
RLibGroup.R_SITE,
RLibGroup.R_DEFAULT );
public static final String DEFAULT_R_DEFAULT_LOCATION_DIRECTORY=
R_HOME_DIRECTORY_VAR_STRING + "/library"; //$NON-NLS-1$
public static final String DEFAULT_R_SITE_LOCATION_DIRECTORY=
R_HOME_DIRECTORY_VAR_STRING + "/site-library"; //$NON-NLS-1$
protected static final String NAME_KEY= "name"; //$NON-NLS-1$
protected static final String R_HOME_DIRECTORY_PATH_KEY= "RHomeDirectory.path"; //$NON-NLS-1$
protected static final String R_ARCH_CODE_KEY= "RArch.code"; //$NON-NLS-1$
protected static final String R_SHARE_DIRECTORY_PATH_KEY= "RShareDirectory.path"; //$NON-NLS-1$
protected static final String R_INCLUDE_DIRECTORY_PATH_KEY= "RIncludeDirectory.path"; //$NON-NLS-1$
protected static final String R_DOC_DIRECTORY_PATH_KEY= "RDocDirectory.path"; //$NON-NLS-1$
protected static final String R_LIBS_KEY= "RLibs"; //$NON-NLS-1$
protected static final String R_LIBS_SITE_DIRECTORY_PATHS_KEY= R_LIBS_KEY + ".SiteDirectory.paths"; //$NON-NLS-1$
protected static final String R_LIBS_USER_DIRECTORY_PATHS_KEY= R_LIBS_KEY + ".UserDirectory.paths"; //$NON-NLS-1$
protected static final String R_LIBS_OTHER_DIRECTORY_PATHS_KEY= R_LIBS_KEY + ".OtherDirectory.paths"; //$NON-NLS-1$
protected static final String STATE_SHARED_TYPE_KEY= "State.Shared.type"; //$NON-NLS-1$
protected static final String STATE_SHARED_DIRECTORY_PATH_KEY= "State.Shared.Directory.path"; //$NON-NLS-1$
protected static final String STATE_SHARED_SERVER_URI_KEY= "State.Shared.Server.uri"; //$NON-NLS-1$
private static final Pattern PATHS_SEPARATOR_PATTERN= Pattern.compile(":,:", Pattern.LITERAL); //$NON-NLS-1$
protected static final Pattern R_HOME_DIRECTORY_VAR_PATTERN= Pattern.compile(R_HOME_DIRECTORY_VAR_STRING, Pattern.LITERAL);
protected static final int LOCAL= 1 << 0;
protected static final int REMOTE= 1 << 1;
protected static final int SPEC_SETUP= 1 << 2;
private static @Nullable String objToString(final @Nullable Object o) {
return (o instanceof String) ? (String) o : null;
}
private static List<String> objToPathStringList(final @Nullable Object o) {
Object[] array= null;
if (o instanceof String) {
array= PATHS_SEPARATOR_PATTERN.split((String) o);
}
else if (o instanceof List) {
array= ((List) o).toArray();
}
else if (o instanceof Object[]) {
array= (Object[]) o;
}
ARRAY: if (array != null && array.length > 0) {
final List<String> paths= new ArrayList<>(array.length);
for (int i= 0; i < array.length; i++) {
if (array[i] instanceof String) {
final String s= standardizePathString((String) array[i]);
if (s != null) {
paths.add((String) array[i]);
}
}
else {
break ARRAY;
}
}
return paths;
}
return ImCollections.emptyList();
}
private final REnv rEnv;
private int flags;
private String name;
private @Nullable String rHomeDirectory;
private @Nullable Path rHomeDirectoryPath;
private @Nullable String rArch;
private @Nullable String rDocDirectory;
private @Nullable Path rDocDirectoryPath;
private @Nullable String rShareDirectory;
private @Nullable Path rShareDirectoryPath;
private @Nullable String rIncludeDirectory;
private @Nullable Path rIncludeDirectoryPath;
private ImList<? extends RLibGroup> rLibGroups= ImCollections.emptyList();
private ImList<? extends RLibLocation> rLibLocations= ImCollections.emptyList();
private final Path stateRootDirectoryPath;
private String stateSharedType;
private @Nullable String stateSharedDirectory;
private @Nullable Path stateSharedDirectoryPath;
private @Nullable String stateSharedServer;
private @Nullable URI stateSharedServerUri;
private Path stateLocalDirectoryPath;
private final List<Status> pathStatus= new ArrayList<>();
private @Nullable Status status;
public BasicREnvConfiguration(final REnv rEnv,
final Path stateRootDirectoryPath) {
this.rEnv= nonNullAssert(rEnv);
this.name= ""; //$NON-NLS-1$
this.stateSharedType= SHARED_DIRECTORY;
this.stateRootDirectoryPath= stateRootDirectoryPath;
}
@Override
public REnv getREnv() {
return this.rEnv;
}
protected void onChanged(final String key) {
this.status= null;
}
protected final int getFlags() {
return this.flags;
}
protected void setFlags(final int flags) {
if (Integer.bitCount(flags & (LOCAL | REMOTE)) > 1) {
throw new IllegalArgumentException(String.format("flags= 0x%1$08X", flags));
}
if (flags != this.flags) {
this.flags= flags;
onChanged("flags");
}
}
@Override
public String getName() {
return this.name;
}
protected void setName(final String name) {
nonNullAssert(name);
if (!Objects.equals(name, this.name)) {
this.name= name;
onChanged(NAME_KEY);
}
}
@Override
public boolean isLocal() {
return ((this.flags & LOCAL) != 0);
}
@Override
public boolean isRemote() {
return ((this.flags & REMOTE) != 0);
}
@Override
public synchronized Status getValidationStatus() {
Status status= this.status;
if (status == null) {
resolvePaths();
status= nonNullAssert(this.status);
}
return status;
}
@Override
public @Nullable String getRHomeDirectory() {
return this.rHomeDirectory;
}
@Override
public @Nullable Path getRHomeDirectoryPath() {
return this.rHomeDirectoryPath;
}
protected void setRHomeDirectory(@Nullable String directory) {
directory= standardizePathString(directory);
if (!Objects.equals(directory, this.rHomeDirectory)) {
this.rHomeDirectory= directory;
onChanged(R_HOME_DIRECTORY_PATH_KEY);
}
}
@Override
public @Nullable String getRArch() {
return this.rArch;
}
protected void setRArch(@Nullable String arch) {
arch= normalizeRArch(arch);
if (!Objects.equals(arch, this.rArch)) {
this.rArch= arch;
onChanged(R_ARCH_CODE_KEY);
}
}
protected @Nullable String normalizeRArch(@Nullable String arch) {
if (arch == null || arch.isEmpty()) {
return null;
}
if (arch.charAt(0) == '/') {
if (arch.length() == 1) {
return null;
}
arch= arch.substring(1);
}
if (arch.equals("exec")) { //$NON-NLS-1$
return null;
}
if (SystemUtils.getLocalOs() == OS_WIN) {
final String knownArch= SystemUtils.getArch(arch);
if (knownArch != null) {
return knownArch;
}
}
return arch;
}
@Override
public @Nullable String getRShareDirectory() {
return this.rDocDirectory;
}
@Override
public @Nullable Path getRShareDirectoryPath() {
return this.rShareDirectoryPath;
}
protected void setRShareDirectory(@Nullable String directory) {
directory= standardizePathString(directory);
if (!Objects.equals(directory, this.rShareDirectory)) {
this.rShareDirectory= directory;
onChanged(R_SHARE_DIRECTORY_PATH_KEY);
}
}
@Override
public @Nullable String getRIncludeDirectory() {
return this.rIncludeDirectory;
}
@Override
public @Nullable Path getRIncludeDirectoryPath() {
return this.rIncludeDirectoryPath;
}
protected void setRIncludeDirectory(@Nullable String directory) {
directory= standardizePathString(directory);
if (!Objects.equals(directory, this.rIncludeDirectory)) {
this.rIncludeDirectory= directory;
onChanged(R_INCLUDE_DIRECTORY_PATH_KEY);
}
}
@Override
public @Nullable String getRDocDirectory() {
return this.rDocDirectory;
}
@Override
public @Nullable Path getRDocDirectoryPath() {
return this.rDocDirectoryPath;
}
protected void setRDocDirectory(@Nullable String directory) {
directory= standardizePathString(directory);
if (!Objects.equals(directory, this.rDocDirectory)) {
this.rDocDirectory= directory;
onChanged(R_DOC_DIRECTORY_PATH_KEY);
}
this.rDocDirectory= standardizePathString(directory);
}
@Override
public ImList<? extends RLibGroup> getRLibGroups() {
return this.rLibGroups;
}
@Override
public @Nullable RLibGroup getRLibGroup(final String id) {
for (final RLibGroup group : this.rLibGroups) {
if (group.getId() == id) {
return group;
}
}
return null;
}
@Override
public ImList<? extends RLibLocation> getRLibLocations() {
return this.rLibLocations;
}
protected void setRLibGroups(final ImList<? extends RLibGroup> libGroups) {
nonNullAssert(libGroups);
if (!Objects.equals(libGroups, this.rLibGroups)) {
this.rLibGroups= libGroups;
this.rLibLocations= REnvCoreInternals.listRLibLocations(libGroups);
onChanged(R_LIBS_KEY);
}
}
@Override
public String getStateSharedType() {
return this.stateSharedType;
}
protected void setStateSharedType(final String type) {
nonNullAssert(type);
if (!Objects.equals(type, this.stateSharedType)) {
this.stateSharedType= type.intern();
onChanged(STATE_SHARED_TYPE_KEY);
}
}
@Override
public @Nullable String getStateSharedDirectory() {
return this.stateSharedDirectory;
}
@Override
public @Nullable Path getStateSharedDirectoryPath() {
return this.stateSharedDirectoryPath;
}
protected void setStateSharedDirectory(@Nullable String directory) {
directory= standardizePathString(directory);
if (!Objects.equals(directory, this.stateSharedDirectory)) {
this.stateSharedDirectory= directory;
this.stateSharedDirectoryPath= null;
onChanged(STATE_SHARED_DIRECTORY_PATH_KEY);
}
}
@Override
public @Nullable String getStateSharedServer() {
return this.stateSharedServer;
}
@Override
public @Nullable URI getStateSharedServerUri() {
return this.stateSharedServerUri;
}
protected void setStateSharedServer(final @Nullable String uri) {
if (!Objects.equals(uri, this.stateSharedServer)) {
this.stateSharedServer= uri;
this.stateSharedServerUri= null;
onChanged(STATE_SHARED_SERVER_URI_KEY);
}
}
@Override
public Path getStateLocalDirectoryPath() {
return this.stateLocalDirectoryPath;
}
protected Path getDataRootDirectory() {
return this.stateRootDirectoryPath;
}
protected void resolvePaths() {
this.pathStatus.clear();
if ((this.flags & (LOCAL | SPEC_SETUP)) == (LOCAL | SPEC_SETUP)) {
this.rHomeDirectoryPath= resolveDirectoryPath(getRHomeDirectory(),
true, "R home directory" );
if (this.rHomeDirectoryPath != null) {
this.rDocDirectoryPath= resolveRDirectoryPath(this.rDocDirectory,
false, "R documentation installation directory" );
this.rShareDirectoryPath= resolveRDirectoryPath(this.rShareDirectory,
false, "R share installation directory" );
this.rIncludeDirectoryPath= resolveRDirectoryPath(this.rIncludeDirectory,
false, "R include installation directory" );
for (final RLibLocation libLocation : this.rLibLocations) {
if (libLocation instanceof BasicRLibLocation) {
((BasicRLibLocation) libLocation).setDirectoryPath(
resolveRDirectoryPath(libLocation.getDirectory(),
false, "R library location directory") );
}
}
}
else {
this.rDocDirectoryPath= null;
this.rShareDirectoryPath= null;
this.rIncludeDirectoryPath= null;
for (final RLibLocation libLocation : this.rLibLocations) {
if (libLocation instanceof BasicRLibLocation) {
((BasicRLibLocation) libLocation).setDirectoryPath(null);
}
}
}
}
if (getStateSharedType() == SHARED_DIRECTORY && this.stateSharedDirectory != null) {
this.stateSharedDirectoryPath= resolveDirectoryPath(this.stateSharedDirectory,
true, "shared state directory" );
}
else {
this.stateSharedDirectoryPath= getDataRootDirectory().resolve("shared");
}
this.stateLocalDirectoryPath= getDataRootDirectory().resolve("local");
if (getStateSharedType() == SHARED_SERVER) {
URI url= resolveURL(this.stateSharedServer,
true, "shared state server" );
if (url != null) {
try {
String scheme= url.getScheme();
if (scheme == null) {
scheme= "http";
}
String path= url.getPath();
if (path == null || path.isEmpty() || path.equals("/")) {
path= "/default"; //$NON-NLS-1$
}
else if (path.endsWith("/")) {
path= path.substring(0, path.length() - 1);
}
url= new URI(scheme, url.getUserInfo(), url.getHost(), url.getPort(), path, null, null);
}
catch (final URISyntaxException e) {
throw new RuntimeException(e);
}
}
this.stateSharedServerUri= url;
}
if (!this.pathStatus.isEmpty()) {
this.status= new MultiStatus(REnvCoreInternals.BUNDLE_ID,
String.format("Issues detected in R environment configuration '%1$s'.", getName() ),
null,
ImCollections.toList(this.pathStatus) );
REnvCoreInternals.log(this.status);
}
else {
this.status= Statuses.OK_STATUS;
}
}
protected @Nullable Path resolveDirectoryPath(final @Nullable String spec,
final boolean required, final String label) {
if (spec == null) {
if (required) {
this.pathStatus.add(new ErrorStatus(REnvCoreInternals.BUNDLE_ID,
String.format("%1$s: the specification of the resource path is missing.", label) ));
}
return null;
}
try {
return resolvePath(spec);
}
catch (final Exception e) {
this.pathStatus.add(Statuses.newStatus((required) ? Status.ERROR : Status.WARNING,
REnvCoreInternals.BUNDLE_ID, 0,
String.format("%1$s: failed to resolve the specified resource path '%2$s'.", label, spec),
e ));
return null;
}
}
protected @Nullable Path resolveRDirectoryPath(@Nullable String spec,
final boolean required, final String label) {
if (spec == null) {
if (required) {
this.pathStatus.add(new ErrorStatus(REnvCoreInternals.BUNDLE_ID,
String.format("%1$s: the specification of the resource path is missing.", label) ));
}
return null;
}
try {
final Matcher matcher= R_HOME_DIRECTORY_VAR_PATTERN.matcher(spec);
if (matcher.find()) {
spec= matcher.replaceAll(Matcher.quoteReplacement(
this.rHomeDirectoryPath.toString() ));
}
return resolvePath(spec);
}
catch (final Exception e) {
this.pathStatus.add(Statuses.newStatus((required) ? Status.ERROR : Status.WARNING,
REnvCoreInternals.BUNDLE_ID, 0,
String.format("%1$s: failed to resolve the specified resource path '%2$s'.", label, spec),
e ));
return null;
}
}
protected @Nullable URI resolveURL(final @Nullable String spec,
final boolean required, final String label) {
if (spec == null) {
if (required) {
this.pathStatus.add(new ErrorStatus(REnvCoreInternals.BUNDLE_ID,
String.format("%1$s: the specification of the url is missing.", label) ));
}
return null;
}
try {
final URI url= new URI(spec);
final String host= url.getHost();
if (host == null || host.isEmpty()) {
this.pathStatus.add(new ErrorStatus(REnvCoreInternals.BUNDLE_ID,
String.format("%1$s: The specified url '%2$s' is invalid, a host is required.", label, spec) ));
return null;
}
return url;
}
catch (final Exception e) {
this.pathStatus.add(new ErrorStatus(REnvCoreInternals.BUNDLE_ID,
String.format("%1$s: The specified url '%2$s' is invalid.", label, spec),
e ));
return null;
}
}
protected void load(final Map<?, ?> settings) {
setName(nonNullElse(objToString(settings.get(NAME_KEY)), "")); //$NON-NLS-1$
if ((getFlags() & SPEC_SETUP) != 0) {
setRHomeDirectory(objToString(settings.get(R_HOME_DIRECTORY_PATH_KEY)));
setRArch(objToString(settings.get(R_ARCH_CODE_KEY)));
setRShareDirectory(objToString(settings.get(R_SHARE_DIRECTORY_PATH_KEY)));
setRIncludeDirectory(objToString(settings.get(R_INCLUDE_DIRECTORY_PATH_KEY)));
setRDocDirectory(objToString(settings.get(R_DOC_DIRECTORY_PATH_KEY)));
final ImList<String> libGroupIds= DEFAULT_LIB_GROUP_IDS;
final List<RLibGroup> libGroups= new ArrayList<>(libGroupIds.size());
for (final String groupId : libGroupIds) {
final List<RLibLocation> locations= new ArrayList<>();
final String key= getLibGroupDirectoryPathsKey(groupId);
if (key != null) {
final List<String> pathList= objToPathStringList(settings.get(key));
for (final String path : pathList) {
locations.add(new BasicRLibLocation(RLibLocation.USER, path, null));
}
}
if (completeLibGroup(groupId, locations)) {
libGroups.add(new BasicRLibGroup(groupId,
getLibGroupLabel(groupId, libGroups.size() + 1),
ImCollections.toList(locations) ));
}
}
setRLibGroups(ImCollections.toList(libGroups));
setStateSharedType(nonNullElse(objToString(settings.get(STATE_SHARED_TYPE_KEY)), SHARED_DIRECTORY));
switch (getStateSharedType()) {
case SHARED_DIRECTORY:
setStateSharedDirectory(objToString(settings.get(STATE_SHARED_DIRECTORY_PATH_KEY)));
break;
case SHARED_SERVER:
setStateSharedServer(objToString(settings.get(STATE_SHARED_SERVER_URI_KEY)));
break;
default:
break;
}
}
}
protected @Nullable String getLibGroupDirectoryPathsKey(final String id) {
switch (id) {
case RLibGroup.R_SITE:
return R_LIBS_SITE_DIRECTORY_PATHS_KEY;
case RLibGroup.R_USER:
return R_LIBS_USER_DIRECTORY_PATHS_KEY;
case RLibGroup.R_OTHER:
return R_LIBS_OTHER_DIRECTORY_PATHS_KEY;
default:
return null;
}
}
protected String getLibGroupLabel(final String id, final int num) {
final String label= REnvCoreInternals.getRLibGroupLabel(id);
return (label != null) ?
label :
String.format("Libraries %1$s", num);
}
protected boolean completeLibGroup(final String id, final List<RLibLocation> locations) {
switch (id) {
case RLibGroup.R_DEFAULT:
locations.clear();
locations.add(new BasicRLibLocation(RLibLocation.R,
DEFAULT_R_DEFAULT_LOCATION_DIRECTORY, null ));
return true;
default:
return true;
}
}
protected @Nullable Path resolvePath(final String spec) throws Exception {
return Paths.get(spec);
}
@Override
public int hashCode() {
return this.rEnv.hashCode();
}
protected boolean equalsType(final REnvConfiguration other) {
return (other instanceof BasicREnvConfiguration
&& this.flags == ((BasicREnvConfiguration) other).flags );
}
@Override
public boolean equals(final @Nullable Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof REnvConfiguration) {
final REnvConfiguration other= (REnvConfiguration) obj;
return (this.rEnv.equals(other.getREnv())
&& equalsType(other)
&& ((this.flags & SPEC_SETUP) == 0 || (
(Objects.equals(this.rHomeDirectory, other.getRHomeDirectory())
&& Objects.equals(this.rArch, other.getRArch())
&& Objects.equals(this.rDocDirectory, other.getRDocDirectory())
&& Objects.equals(this.rShareDirectory, other.getRShareDirectory())
&& Objects.equals(this.rIncludeDirectory, other.getRIncludeDirectory())
&& Objects.equals(this.rLibGroups, other.getRLibGroups())) )
)
&& Objects.equals(this.stateSharedDirectory, other.getStateSharedDirectory())
&& Objects.equals(this.stateSharedServer, other.getStateSharedServer()) );
}
return false;
}
@Override
public String toString() {
return this.rEnv.getId() + " (" + getName() + ", " + getRHomeDirectory() + ")";
}
}