Skip to content

Commit

Permalink
#321: refactoring to improve path conversion logic (#393)
Browse files Browse the repository at this point in the history
  • Loading branch information
hohwille authored Jun 18, 2024
1 parent 967fb8b commit 158fe03
Show file tree
Hide file tree
Showing 16 changed files with 412 additions and 258 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package com.devonfw.tools.ide.commandlet;

import java.util.Collection;

import com.devonfw.tools.ide.context.AbstractIdeContext;
import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.environment.VariableLine;
import com.devonfw.tools.ide.os.WindowsPathSyntax;
import com.devonfw.tools.ide.property.FlagProperty;
import com.devonfw.tools.ide.variable.IdeVariables;

import java.util.Collection;

/**
* {@link Commandlet} to print the environment variables.
Expand Down Expand Up @@ -49,46 +49,22 @@ public boolean isSuppressStepSuccess() {
@Override
public void run() {

WindowsPathSyntax pathSyntax = null;
if (this.context.getSystemInfo().isWindows()) {
if (this.bash.isTrue()) {
pathSyntax = WindowsPathSyntax.MSYS;
} else {
pathSyntax = WindowsPathSyntax.WINDOWS;
}
}
((AbstractIdeContext) this.context).setPathSyntax(pathSyntax);
Collection<VariableLine> variables = this.context.getVariables().collectVariables();
for (VariableLine line : variables) {
if (this.context.getSystemInfo().isWindows()) {
line = normalizeWindowsValue(line);
}
String lineValue = line.getValue();
if (IdeVariables.PATH.getName().equals(line.getName())) {
lineValue = this.context.getPath().toString(this.bash.isTrue());
}
lineValue = "\"" + lineValue + "\"";
line = line.withValue(lineValue);
this.context.info(line.toString());
}
}

VariableLine normalizeWindowsValue(VariableLine line) {

String value = line.getValue();
String normalized = normalizeWindowsValue(value);
if (normalized != value) {
line = line.withValue(normalized);
}
return line;
}

String normalizeWindowsValue(String value) {

WindowsPathSyntax pathSyntax;
if (this.bash.isTrue()) {
pathSyntax = WindowsPathSyntax.MSYS;
} else {
pathSyntax = WindowsPathSyntax.WINDOWS;
}
String drive = WindowsPathSyntax.WINDOWS.getDrive(value);
if (drive == null) {
drive = WindowsPathSyntax.MSYS.getDrive(value);
}
if (drive != null) {
value = pathSyntax.replaceDrive(value, drive);
}
return value;
}
}
61 changes: 23 additions & 38 deletions cli/src/main/java/com/devonfw/tools/ide/common/SystemPath.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.os.SystemInfoImpl;
import com.devonfw.tools.ide.os.WindowsPathSyntax;
import com.devonfw.tools.ide.variable.IdeVariables;

import java.io.File;
Expand Down Expand Up @@ -30,6 +31,8 @@
*/
public class SystemPath {

private static final Pattern REGEX_WINDOWS_PATH = Pattern.compile("([a-zA-Z]:)?(\\\\[a-zA-Z0-9\\s_.-]+)+\\\\?");

private final String envPath;

private final char pathSeparator;
Expand Down Expand Up @@ -66,9 +69,9 @@ public SystemPath(IdeContext context, String envPath) {
/**
* The constructor.
*
* @param context {@link IdeContext} for the output of information.
* @param envPath the value of the PATH variable.
* @param ideRoot the {@link IdeContext#getIdeRoot() IDE_ROOT}.
* @param context {@link IdeContext} for the output of information.
* @param envPath the value of the PATH variable.
* @param ideRoot the {@link IdeContext#getIdeRoot() IDE_ROOT}.
* @param softwarePath the {@link IdeContext#getSoftwarePath() software path}.
*/
public SystemPath(IdeContext context, String envPath, Path ideRoot, Path softwarePath) {
Expand All @@ -79,10 +82,10 @@ public SystemPath(IdeContext context, String envPath, Path ideRoot, Path softwar
/**
* The constructor.
*
* @param context {@link IdeContext} for the output of information.
* @param envPath the value of the PATH variable.
* @param ideRoot the {@link IdeContext#getIdeRoot() IDE_ROOT}.
* @param softwarePath the {@link IdeContext#getSoftwarePath() software path}.
* @param context {@link IdeContext} for the output of information.
* @param envPath the value of the PATH variable.
* @param ideRoot the {@link IdeContext#getIdeRoot() IDE_ROOT}.
* @param softwarePath the {@link IdeContext#getSoftwarePath() software path}.
* @param pathSeparator the path separator char (';' for Windows and ':' otherwise).
*/
public SystemPath(IdeContext context, String envPath, Path ideRoot, Path softwarePath, char pathSeparator) {
Expand Down Expand Up @@ -219,62 +222,45 @@ public void setPath(String tool, Path path) {
@Override
public String toString() {

return toString(false);
return toString(null);
}

/**
* @param bash - {@code true} to convert the PATH to bash syntax (relevant for git-bash or cygwin on windows),
* {@code false} otherwise.
* @param pathSyntax the {@link WindowsPathSyntax} to convert to.
* @return this {@link SystemPath} as {@link String} for the PATH environment variable.
*/
public String toString(boolean bash) {
public String toString(WindowsPathSyntax pathSyntax) {

char separator;
if (bash) {
if (pathSyntax == WindowsPathSyntax.MSYS) {
separator = ':';
} else {
separator = this.pathSeparator;
}
StringBuilder sb = new StringBuilder(this.envPath.length() + 128);
for (Path path : this.tool2pathMap.values()) {
appendPath(path, sb, separator, bash);
appendPath(path, sb, separator, pathSyntax);
}
for (Path path : this.paths) {
appendPath(path, sb, separator, bash);
appendPath(path, sb, separator, pathSyntax);
}
return sb.toString();
}

private static void appendPath(Path path, StringBuilder sb, char separator, boolean bash) {
private static void appendPath(Path path, StringBuilder sb, char separator, WindowsPathSyntax pathSyntax) {

if (sb.length() > 0) {
sb.append(separator);
}
String pathString = path.toString();
if (bash && (pathString.length() > 3) && (pathString.charAt(1) == ':')) {
pathString = convertWindowsPathToUnixPath(pathString);
String pathString;
if (pathSyntax == null) {
pathString = path.toString();
} else {
pathString = pathSyntax.format(path);
}
sb.append(pathString);
}

/**
* Method to convert a valid Windows path string representation to its corresponding one in Unix format.
*
* @param pathString The Windows path string to convert.
* @return The converted Unix path string.
*/
public static String convertWindowsPathToUnixPath(String pathString) {

char slash = pathString.charAt(2);
if ((slash == '\\') || (slash == '/')) {
char drive = Character.toLowerCase(pathString.charAt(0));
if ((drive >= 'a') && (drive <= 'z')) {
pathString = "/" + drive + pathString.substring(2).replace('\\', '/');
}
}
return pathString;
}

/**
* Method to validate if a given path string is a Windows path or not
*
Expand All @@ -283,7 +269,6 @@ public static String convertWindowsPathToUnixPath(String pathString) {
*/
public static boolean isValidWindowsPath(String pathString) {

String windowsFilePathRegEx = "([a-zA-Z]:)?(\\\\[a-zA-Z0-9\\s_.-]+)+\\\\?";
return Pattern.matches(windowsFilePathRegEx, pathString);
return REGEX_WINDOWS_PATH.matcher(pathString).matches();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.devonfw.tools.ide.network.ProxyContext;
import com.devonfw.tools.ide.os.SystemInfo;
import com.devonfw.tools.ide.os.SystemInfoImpl;
import com.devonfw.tools.ide.os.WindowsPathSyntax;
import com.devonfw.tools.ide.process.ProcessContext;
import com.devonfw.tools.ide.process.ProcessContextImpl;
import com.devonfw.tools.ide.process.ProcessResult;
Expand Down Expand Up @@ -99,6 +100,8 @@ public abstract class AbstractIdeContext implements IdeContext {

private SystemPath path;

private WindowsPathSyntax pathSyntax;

private final SystemInfo systemInfo;

private EnvironmentVariables variables;
Expand Down Expand Up @@ -1037,4 +1040,16 @@ private String findBashOnWindows() {
throw new IllegalStateException("Could not find Bash. Please install Git for Windows and rerun.");
}

@Override
public WindowsPathSyntax getPathSyntax() {
return this.pathSyntax;
}

/**
* @param pathSyntax new value of {@link #getPathSyntax()}.
*/
public void setPathSyntax(WindowsPathSyntax pathSyntax) {

this.pathSyntax = pathSyntax;
}
}
16 changes: 14 additions & 2 deletions cli/src/main/java/com/devonfw/tools/ide/context/IdeContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.devonfw.tools.ide.merge.DirectoryMerger;
import com.devonfw.tools.ide.network.ProxyContext;
import com.devonfw.tools.ide.os.SystemInfo;
import com.devonfw.tools.ide.os.WindowsPathSyntax;
import com.devonfw.tools.ide.process.ProcessContext;
import com.devonfw.tools.ide.repo.CustomToolRepository;
import com.devonfw.tools.ide.repo.ToolRepository;
Expand Down Expand Up @@ -438,7 +439,14 @@ default String getMavenArgs() {
if (getIdeHome() != null) {
Path mvnSettingsFile = getConfPath().resolve(Mvn.MVN_CONFIG_FOLDER).resolve(Mvn.SETTINGS_FILE);
if (Files.exists(mvnSettingsFile)) {
return "-s " + mvnSettingsFile;
String settingsPath;
WindowsPathSyntax pathSyntax = null; // getPathSyntax();
if (pathSyntax == null) {
settingsPath = mvnSettingsFile.toString();
} else {
settingsPath = pathSyntax.format(mvnSettingsFile);
}
return "-s " + settingsPath;
}
}
return null;
Expand All @@ -454,7 +462,6 @@ default Path getMavenRepoEnvVariable() {
return getConfPath().resolve(Mvn.M2_CONFIG_FOLDER).resolve("repository");
}
return null;

}

/**
Expand Down Expand Up @@ -520,4 +527,9 @@ default void setIdeHome(Path ideHome) {
*/
String findBash();

/**
* @return the {@link WindowsPathSyntax} used for {@link Path} conversion or {@code null} for no such conversion (typically if not on Windows).
*/
WindowsPathSyntax getPathSyntax();

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,22 @@ public abstract class AbstractEnvironmentVariables implements EnvironmentVariabl

private static final String VARIABLE_SUFFIX = "}";

/** @see #getParent() */
/**
* @see #getParent()
*/
protected final AbstractEnvironmentVariables parent;

/** The {@link IdeContext} instance. */
/**
* The {@link IdeContext} instance.
*/
protected final IdeContext context;

private String source;

/**
* The constructor.
*
* @param parent the parent {@link EnvironmentVariables} to inherit from.
* @param parent the parent {@link EnvironmentVariables} to inherit from.
* @param context the {@link IdeContext}.
*/
public AbstractEnvironmentVariables(AbstractEnvironmentVariables parent, IdeContext context) {
Expand Down Expand Up @@ -122,7 +126,7 @@ private final Collection<VariableLine> collectVariables(boolean onlyExported) {
for (String name : variableNames) {
boolean export = isExported(name);
if (!onlyExported || export) {
String value = get(name);
String value = get(name, false);
if (value != null) {
variables.add(VariableLine.of(export, name, value));
}
Expand All @@ -143,7 +147,7 @@ protected void collectVariables(Set<String> variables) {

/**
* @param propertiesFilePath the {@link #getPropertiesFilePath() propertiesFilePath} of the child {@link EnvironmentVariables}.
* @param type the {@link #getType() type}.
* @param type the {@link #getType() type}.
* @return the new {@link EnvironmentVariables}.
*/
public AbstractEnvironmentVariables extend(Path propertiesFilePath, EnvironmentVariablesType type) {
Expand All @@ -168,21 +172,21 @@ public String resolve(String string, Object src) {
/**
* This method is called recursively. This allows you to resolve variables that are defined by other variables.
*
* @param value the {@link String} that potentially contains variables in the syntax "${«variable«}". Those will be resolved by this method and replaced with
* their {@link #get(String) value}.
* @param src the source where the {@link String} to resolve originates from. Should have a reasonable {@link Object#toString() string representation} that
* will be used in error or log messages if a variable could not be resolved.
* @param recursion the current recursion level. This is used to interrupt endless recursion.
* @param rootSrc the root source where the {@link String} to resolve originates from.
* @param rootValue the root value to resolve.
* @param value the {@link String} that potentially contains variables in the syntax "${«variable«}". Those will be resolved by this method and replaced with
* their {@link #get(String) value}.
* @param src the source where the {@link String} to resolve originates from. Should have a reasonable {@link Object#toString() string representation} that
* will be used in error or log messages if a variable could not be resolved.
* @param recursion the current recursion level. This is used to interrupt endless recursion.
* @param rootSrc the root source where the {@link String} to resolve originates from.
* @param rootValue the root value to resolve.
* @param resolvedVars this is a reference to an object of {@link EnvironmentVariablesResolved} being the lowest level in the
* {@link EnvironmentVariablesType hierarchy} of variables. In case of a self-referencing variable {@code x} the resolving has to continue one level higher in
* the {@link EnvironmentVariablesType hierarchy} to avoid endless recursion. The {@link EnvironmentVariablesResolved} is then used if another variable
* {@code y} must be resolved, since resolving this variable has to again start at the lowest level. For example: For levels {@code l1, l2} with
* {@code l1 < l2} and {@code x=${x} foo} and {@code y=bar} defined at level {@code l1} and {@code x=test ${y}} defined at level {@code l2}, {@code x} is
* first resolved at level {@code l1} and then up the {@link EnvironmentVariablesType hierarchy} at {@code l2} to avoid endless recursion. However, {@code y}
* must be resolved starting from the lowest level in the {@link EnvironmentVariablesType hierarchy} and therefore {@link EnvironmentVariablesResolved} is
* used.
* {@link EnvironmentVariablesType hierarchy} of variables. In case of a self-referencing variable {@code x} the resolving has to continue one level higher in
* the {@link EnvironmentVariablesType hierarchy} to avoid endless recursion. The {@link EnvironmentVariablesResolved} is then used if another variable
* {@code y} must be resolved, since resolving this variable has to again start at the lowest level. For example: For levels {@code l1, l2} with
* {@code l1 < l2} and {@code x=${x} foo} and {@code y=bar} defined at level {@code l1} and {@code x=test ${y}} defined at level {@code l2}, {@code x} is
* first resolved at level {@code l1} and then up the {@link EnvironmentVariablesType hierarchy} at {@code l2} to avoid endless recursion. However, {@code y}
* must be resolved starting from the lowest level in the {@link EnvironmentVariablesType hierarchy} and therefore {@link EnvironmentVariablesResolved} is
* used.
* @return the given {@link String} with the variables resolved.
*/
private String resolve(String value, Object src, int recursion, Object rootSrc, String rootValue, AbstractEnvironmentVariables resolvedVars) {
Expand Down Expand Up @@ -228,7 +232,7 @@ private String resolve(String value, Object src, int recursion, Object rootSrc,
// resolving a self referencing variable one level up the hierarchy of EnvironmentVariablesType, i.e. at "next",
// to avoid endless recursion
String replacement = ((AbstractEnvironmentVariables) next).resolve(next.getFlat(variableName), variableName, recursion, rootSrc, rootValue,
resolvedVars);
resolvedVars);
matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement));

}
Expand All @@ -242,9 +246,9 @@ private String resolve(String value, Object src, int recursion, Object rootSrc,
/**
* Like {@link #get(String)} but with higher-level features including to resolve {@link IdeVariables} with their default values.
*
* @param name the name of the variable to get.
* @param name the name of the variable to get.
* @param ignoreDefaultValue - {@code true} if the {@link VariableDefinition#getDefaultValue(IdeContext) default value} of a potential
* {@link VariableDefinition} shall be ignored, {@code false} to return default instead of {@code null}.
* {@link VariableDefinition} shall be ignored, {@code false} to return default instead of {@code null}.
* @return the value of the variable.
*/
protected String getValue(String name, boolean ignoreDefaultValue) {
Expand All @@ -254,14 +258,14 @@ protected String getValue(String name, boolean ignoreDefaultValue) {
if ((var != null) && var.isForceDefaultValue()) {
value = var.getDefaultValueAsString(this.context);
} else {
value = this.parent.get(name);
value = this.parent.get(name, false);
}
if ((value == null) && (var != null)) {
String key = var.getName();
if (!name.equals(key)) {
// try new name (e.g. IDE_TOOLS or IDE_HOME) if no value could be found by given legacy name (e.g.
// DEVON_IDE_TOOLS or DEVON_IDE_HOME)
value = this.parent.get(key);
value = this.parent.get(key, false);
}
if ((value == null) && !ignoreDefaultValue) {
value = var.getDefaultValueAsString(this.context);
Expand Down
Loading

0 comments on commit 158fe03

Please sign in to comment.