diff --git a/.mvn/maven.config b/.mvn/maven.config index 456572324..0413a95ed 100644 --- a/.mvn/maven.config +++ b/.mvn/maven.config @@ -1 +1 @@ --Drevision=2024.01.001-SNAPSHOT +-Drevision=2024.02.001-alpha-SNAPSHOT diff --git a/cli/src/main/java/com/devonfw/tools/ide/common/Tag.java b/cli/src/main/java/com/devonfw/tools/ide/common/Tag.java new file mode 100644 index 000000000..d7ef85640 --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/common/Tag.java @@ -0,0 +1,564 @@ +package com.devonfw.tools.ide.common; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * A {@link Tag} represents a classifier or category. A tool and plugin can be associated with {@link Tag}s allowing end + * users to find them. + */ +public final class Tag { + + private static final Tag[] NO_TAGS = new Tag[0]; + + private static final Map TAG_MAP = new HashMap<>(128); + + private static final Collection ALL_TAGS = Collections.unmodifiableCollection(TAG_MAP.values()); + + /** The root {@link Tag}. */ + public static final Tag ROOT = new Tag(); + + static { + TAG_MAP.put(ROOT.id, ROOT); + } + + /** {@link #getParent() Parent} for miscellaneous (undefined) tags. */ + public static final Tag MISC = create("miscellaneous", ROOT, true, "misc"); + + /** {@link #getParent() Parent} for programming-languages. */ + public static final Tag LANGUAGE = create("language", ROOT, true, "programming"); + + /** {@link Tag} for JVM (Java Virtual Machine). */ + public static final Tag JVM = create("java-virtual-machine", LANGUAGE, false, "jvm"); + + /** {@link Tag} for Java. */ + public static final Tag JAVA = create("java", JVM); + + /** {@link Tag} for Kotlin. */ + public static final Tag KOTLIN = create("kotlin", JVM); + + /** {@link Tag} for Scala. */ + public static final Tag SCALA = create("scala", JVM); + + /** {@link Tag} for DotNet (.NET). */ + public static final Tag DOTNET = create("dotnet", LANGUAGE, false, "net"); + + /** {@link Tag} for C#. */ + public static final Tag CS = create("c#", DOTNET, false, "cs", "csharp"); + + /** {@link Tag} for C. */ + public static final Tag C = create("c", LANGUAGE); + + /** {@link Tag} for Rust. */ + public static final Tag RUST = create("rust", LANGUAGE, false, "rs"); + + /** {@link Tag} for C++. */ + public static final Tag CPP = create("c++", LANGUAGE, false, "cpp"); + + /** {@link Tag} for Python. */ + public static final Tag PYTHON = create("python", LANGUAGE); + + /** {@link Tag} for Ruby. */ + public static final Tag RUBY = create("ruby", LANGUAGE); + + /** {@link Tag} for Perl. */ + public static final Tag PERL = create("perl", LANGUAGE); + + /** {@link Tag} for Shell scripting. */ + public static final Tag SHELL = create("shell", JVM, false, "script"); + + /** {@link Tag} for Bash. */ + public static final Tag BASH = create("bash", SHELL, false, "terminal"); + + /** {@link Tag} for TypeScript. */ + public static final Tag JAVA_SCRIPT = create("javascript", LANGUAGE, false, "js"); + + /** {@link Tag} for TypeScript. */ + public static final Tag TYPE_SCRIPT = create("typescript", JAVA_SCRIPT, false, "ts"); + + /** {@link #getParent() Parent} for programming-languages. */ + public static final Tag IDE = create("ide", ROOT); + + /** {@link Tag} for Eclipse. */ + public static final Tag ECLIPSE = create("eclipse", IDE); + + /** {@link Tag} for IDEA (JetBrains IDE Platform). */ + public static final Tag IDEA = create("idea", IDE); + + /** {@link Tag} for IntelliJ. */ + public static final Tag INTELLIJ = create("intellij", IDEA); + + /** {@link Tag} for IntelliJ. */ + public static final Tag ANDROID_STUDIO = create("android-studio", IDEA); + + /** {@link Tag} for VS-Code. */ + public static final Tag VS_CODE = create("vscode", IDE, false, "visualstudiocode"); + + /** {@link Tag} for (code-)generators (including template-engines, etc.). */ + public static final Tag GENERATOR = create("generator", ROOT); + + /** {@link #getParent() Parent} for frameworks. */ + public static final Tag FRAMEWORK = create("framework", ROOT, true); + + /** {@link Tag} for Spring(framework). */ + public static final Tag SPRING = create("spring", FRAMEWORK, false, new String[] { "springframework", "springboot" }, + JAVA); + + /** {@link Tag} for Quarkus. */ + public static final Tag QUARKUS = create("quarkus", FRAMEWORK, false, null, JAVA); + + /** {@link Tag} for Micronaut. */ + public static final Tag MICRONAUT = create("micronaut", FRAMEWORK, false, null, JAVA); + + /** {@link Tag} for Angular. */ + public static final Tag ANGULAR = create("angular", FRAMEWORK, false, new String[] { "ng", "angularjs" }, + TYPE_SCRIPT); + + /** {@link Tag} for React. */ + public static final Tag REACT = create("react", FRAMEWORK, false, null, TYPE_SCRIPT); + + /** {@link Tag} for Vue. */ + public static final Tag VUE = create("vue", FRAMEWORK, false, null, TYPE_SCRIPT); + + /** {@link Tag} for Cordova. */ + public static final Tag CORDOVA = create("cordova", FRAMEWORK, false, null, JAVA_SCRIPT); + + /** {@link Tag} for Ionic. */ + public static final Tag IONIC = create("ionic", CORDOVA, false); + + /** {@link #getParent() Parent} for quality-assurance. */ + public static final Tag QA = create("quality-assurance", ROOT, false, "qa", "quality"); + + /** {@link Tag} for everything related to testing. */ + public static final Tag TEST = create("testing", QA, false, "test"); + + /** {@link Tag} for everything related to testing. */ + public static final Tag MOCK = create("mocking", TEST, false, "mock"); + + /** {@link Tag} for everything related to testing. */ + public static final Tag CODE_QA = create("static-code-analysis", QA, false, "codeqa"); + + /** {@link #getParent() Parent} for linters. */ + public static final Tag LINTING = create("linter", QA, false, "lint", "linting"); + + /** {@link Tag} for everything related to documentation. */ + public static final Tag DOCUMENTATION = create("documentation", ROOT, false, "doc"); + + /** {@link #getParent() Parent} for file formats. */ + public static final Tag FORMAT = create("format", ROOT, true); + + /** {@link Tag} for JSON. */ + public static final Tag JSON = create("json", FORMAT); + + /** {@link Tag} for YAML. */ + public static final Tag YAML = create("yaml", FORMAT, false, "yml"); + + /** {@link Tag} for CSS. */ + public static final Tag CSS = create("css", FORMAT); + + /** {@link Tag} for Properties. */ + public static final Tag PROPERTIES = create("properties", FORMAT); + + /** {@link Tag} for AsciiDoc. */ + public static final Tag ASCII_DOC = create("ascii-doc", FORMAT, false, new String[] { "adoc" }, DOCUMENTATION); + + /** {@link Tag} for MarkDown. */ + public static final Tag MARK_DOWN = create("markdown", DOCUMENTATION, false, new String[] { "md" }, DOCUMENTATION); + + /** {@link Tag} for YAML. */ + public static final Tag PDF = create("pdf", FORMAT, false, null, DOCUMENTATION); + + /** {@link Tag} for HTML. */ + public static final Tag HTML = create("html", FORMAT, false, null, DOCUMENTATION); + + /** {@link Tag} for machine-learning. */ + public static final Tag MACHINE_LEARNING = create("machine-learning", ROOT, false, "ml"); + + /** {@link Tag} for artificial-intelligence. */ + public static final Tag ARTIFICIAL_INTELLIGENCE = create("artificial-intelligence", MACHINE_LEARNING, false, "ai"); + + /** {@link Tag} for data-science. */ + public static final Tag DATA_SCIENCE = create("data-science", ROOT); + + /** {@link Tag} for business-intelligence. */ + public static final Tag BUSINESS_INTELLIGENCE = create("business-intelligence", ROOT, false, "bi", "datawarehouse", + "dwh"); + + /** {@link #Tag} for productivity. */ + public static final Tag ARCHITECTURE = create("architecture", ROOT); + + /** {@link Tag} for AsciiDoc. */ + public static final Tag UML = create("uml", ARCHITECTURE, false, null, DOCUMENTATION); + + /** {@link #Tag} for security. */ + public static final Tag SECURITY = create("security", ROOT, false, "cve"); + + /** {@link #Tag} for collaboration. */ + public static final Tag COLLABORATION = create("collaboration", ROOT, false, "collab"); + + /** {@link #Tag} for virtualization. */ + public static final Tag VIRTUALIZATION = create("virtualization", ROOT, false, "vm"); + + /** {@link #Tag} for docker. */ + public static final Tag DOCKER = create("docker", VIRTUALIZATION); + + /** {@link #Tag} for docker. */ + public static final Tag KUBERNETES = create("kubernetes", DOCKER, false, "k8s"); + + /** {@link #Tag} for WSL. */ + public static final Tag WSL = create("wsl", VIRTUALIZATION); + + /** {@link #Tag} for network. */ + public static final Tag NETWORK = create("network", ROOT, false, "remote"); + + /** {@link #Tag} for HTTP. */ + public static final Tag HTTP = create("http", NETWORK); + + /** {@link #Tag} for REST. */ + public static final Tag REST = create("rest", HTTP); + + /** {@link #Tag} for secure-shell. */ + public static final Tag SSH = create("secure-shell", NETWORK, false, "ssh", "scp"); + + /** {@link #Tag} for capture. */ + public static final Tag CAPTURE = create("capture", ROOT, false, "capturing"); + + /** {@link #Tag} for capture. */ + public static final Tag SCREENSHOT = create("screenshot", CAPTURE); + + /** {@link #Tag} for capture. */ + public static final Tag SCREEN_RECORDING = create("screenrecording", CAPTURE, false, "videocapture"); + + /** {@link #Tag} for productivity. */ + public static final Tag PRODUCTIVITY = create("productivity", ROOT); + + /** {@link #Tag} for regular-expression. */ + public static final Tag REGEX = create("regular-expression", PRODUCTIVITY, false, "regex", "regexp"); + + /** {@link #Tag} for search. */ + public static final Tag SEARCH = create("search", PRODUCTIVITY, false, "find"); + + /** {@link #Tag} for spellchecker. */ + public static final Tag SPELLCHECKER = create("spellchecker", PRODUCTIVITY, false, "spellcheck", "spellchecking"); + + /** {@link #Tag} for analyse. */ + public static final Tag ANALYSE = create("analyse", ROOT, false, "analyze", "analysis"); + + /** {@link #Tag} for monitoring. */ + public static final Tag MONITORING = create("monitoring", ANALYSE, false, "monitor"); + + /** {@link #Tag} for formatter. */ + public static final Tag FORMATTER = create("formatter", ROOT, false, "codeformat", "codeformatter"); + + /** {@link #Tag} for user-experience. */ + public static final Tag UX = create("user-experience", PRODUCTIVITY, false, "ux"); + + /** {@link #Tag} for style. */ + public static final Tag STYLE = create("style", UX, false, "theme", "icon", "skin"); + + /** {@link #Tag} for style. */ + public static final Tag KEYBINDING = create("keybinding", UX, false, "keybindings", "keymap"); + + /** {@link #Tag} for draw(ing). */ + public static final Tag DRAW = create("draw", UX, false, "diagram", "paint"); + + /** {@link #Tag} for cloud. */ + public static final Tag CLOUD = create("cloud", ROOT); + + /** {@link #Tag} for infrastructure-as-code. */ + public static final Tag IAC = create("infrastructure-as-code", CLOUD, false, "iac"); + + /** {@link #Tag} for software-configuration-management. */ + public static final Tag CONFIG_MANAGEMENT = create("software-configuration-management", ROOT, false, + "configmanagement", "configurationmanagement"); + + /** {@link #Tag} for build-management. */ + public static final Tag BUILD = create("build-management", CONFIG_MANAGEMENT, false, "build"); + + /** {@link #Tag} for version-control. */ + public static final Tag VCS = create("version-control", CONFIG_MANAGEMENT, false, "vcs", "versioncontrolsystem"); + + /** {@link #Tag} for issue-management. */ + public static final Tag ISSUE = create("issue-management", CONFIG_MANAGEMENT, false, "issue"); + + /** {@link #Tag} for git. */ + public static final Tag GIT = create("git", VCS); + + /** {@link #Tag} for github. */ + public static final Tag GITHUB = create("github", GIT); + + /** {@link #Tag} for diff (tools that compare files and determine the difference). */ + public static final Tag DIFF = create("diff", CONFIG_MANAGEMENT, false, "patch"); + + /** {@link #Tag} for diff (tools that compare files and determine the difference). */ + public static final Tag RUNTIME = create("runtime", ROOT); + + /** {@link #getParent() Parent} for operating-system. */ + public static final Tag OS = create("operating-system", ROOT, true, "os"); + + /** {@link #Tag} for Windows. */ + public static final Tag WINDOWS = create("windows", OS); + + /** {@link #Tag} for Mac. */ + public static final Tag MAC = create("mac", OS, false, "macos", "osx"); + + /** {@link #Tag} for Linux. */ + public static final Tag LINUX = create("linux", OS, false); + + private final String id; + + private final Tag parent; + + private final boolean isAbstract; + + private final Tag[] additionalParents; + + private Tag() { + + this.id = ""; + this.parent = null; + this.isAbstract = true; + this.additionalParents = NO_TAGS; + } + + private Tag(String id, Tag parent, boolean isAbstract, Tag... additionalParents) { + + super(); + Objects.requireNonNull(id); + Objects.requireNonNull(parent); + assert (id.toLowerCase(Locale.ROOT).equals(id)); + this.id = id; + this.parent = parent; + this.isAbstract = isAbstract; + this.additionalParents = additionalParents; + } + + /** + * @return the identifier and name of this tag. + */ + public String getId() { + + return this.id; + } + + /** + * @return the parent {@link Tag} or {@code null} if this is the root tag. + */ + public Tag getParent() { + + return this.parent; + } + + /** + * @param i the index of the requested parent. Should be in the range from {@code 0} to + * {@link #getParentCount()}-1. + * @return the requested {@link Tag}. + */ + public Tag getParent(int i) { + + if (i == 0) { + return this.parent; + } + return this.additionalParents[i - 1]; + } + + /** + * @return the number of {@link #getParent(int) parents} available. + */ + public int getParentCount() { + + if (this.parent == null) { + return 0; + } + return this.additionalParents.length + 1; + } + + /** + * @return {@code true} if this {@link Tag} is abstract and cannot be selected since it is just a generic parent + * container, {@code false} otherwise. + */ + public boolean isAbstract() { + + return this.isAbstract; + } + + public boolean isAncestorOf(Tag tag) { + + return isAncestorOf(tag, false); + } + + /** + * @param tag the {@link Tag} to check. + * @param includeAdditionalParents - {@code true} if {@link #getParent(int) additional parents} should be included, + * {@code false} otherwise (only consider {@link #getParent() primary parent}). + * @return {@code true} if the given {@link Tag} is an ancestor of this tag, {@code false} otherwise. An ancestor is + * a direct or indirect {@link #getParent() parent}. Therefore, if {@link #ROOT} is given as {@link Tag} parameter, + * this method should always return {@code true}. + */ + public boolean isAncestorOf(Tag tag, boolean includeAdditionalParents) { + + Tag ancestor = this.parent; + while (ancestor != null) { + if (ancestor == tag) { + return true; + } + ancestor = ancestor.parent; + } + if (includeAdditionalParents) { + for (Tag p : this.additionalParents) { + do { + if (p == tag) { + return true; + } + p = p.parent; + } while (p != null); + } + } + return false; + } + + @Override + public String toString() { + + return this.id; + } + + /** + * @param id the {@link #getId() ID} of the tag. + * @param parent the {@link #getParent() parent tag}. + * @param isAbstract the {@link #isAbstract() abstract flag}. + * @return the new {@link Tag}. + */ + static Tag create(String id, Tag parent) { + + return create(id, parent, false); + } + + /** + * @param id the {@link #getId() ID} of the tag. + * @param parent the {@link #getParent() parent tag}. + * @param isAbstract the {@link #isAbstract() abstract flag}. + * @return the new {@link Tag}. + */ + static Tag create(String id, Tag parent, boolean isAbstract) { + + return create(id, parent, isAbstract, null, NO_TAGS); + } + + /** + * @param id the {@link #getId() ID} of the tag. + * @param parent the {@link #getParent() parent tag}. + * @param isAbstract the {@link #isAbstract() abstract flag}. + * @return the new {@link Tag}. + */ + static Tag create(String id, Tag parent, boolean isAbstract, String synonym) { + + return create(id, parent, isAbstract, new String[] { synonym }, NO_TAGS); + } + + /** + * @param id the {@link #getId() ID} of the tag. + * @param parent the {@link #getParent() parent tag}. + * @param isAbstract the {@link #isAbstract() abstract flag}. + * @return the new {@link Tag}. + */ + static Tag create(String id, Tag parent, boolean isAbstract, String... synonyms) { + + return create(id, parent, isAbstract, synonyms, NO_TAGS); + } + + /** + * @param id the {@link #getId() ID} of the tag. + * @param parent the {@link #getParent() parent tag}. + * @param isAbstract the {@link #isAbstract() abstract flag}. + * @return the new {@link Tag}. + */ + static Tag create(String id, Tag parent, boolean isAbstract, String[] synonyms, Tag... additionalParents) { + + Tag tag = new Tag(id, parent, isAbstract, additionalParents); + add(id, tag); + if (synonyms != null) { + for (String synonym : synonyms) { + add(synonym, tag); + } + } + return tag; + } + + private static void add(String key, Tag tag) { + + Tag duplicate = TAG_MAP.put(normalizeKey(key), tag); + if (duplicate != null) { + throw new IllegalStateException("Duplicate tag for " + key); + } + } + + private static String normalizeKey(String key) { + + return key.replace("-", "").replace(".", ""); + } + + private static Tag require(String key) { + + Tag tag = TAG_MAP.get(normalizeKey(key)); + if (tag == null) { + throw new IllegalStateException("Could not find required tag " + key); + } + return tag; + } + + /** + * @param id the {@link #getId() ID} of the requested {@link Tag}. + * @return the {@link Tag} with the given {@link #getId() ID}. Will be lazily created as child of {@link #MISC} if not + * already exists. + */ + public static Tag of(String id) { + + final String tagId = id.trim(); + int slash = tagId.indexOf('/'); + if (slash >= 0) { + String parentId = tagId.substring(0, slash); + Tag parent = require(parentId); + String childId = tagId.substring(slash + 1); + return TAG_MAP.computeIfAbsent(normalizeKey(childId), i -> new Tag(childId, parent, false, NO_TAGS)); + } + return TAG_MAP.computeIfAbsent(normalizeKey(tagId), i -> new Tag(tagId, MISC, false, NO_TAGS)); + } + + /** + * @return the {@link Collections} of all available {@link Tag}s. + */ + public static Collection getAll() { + + return ALL_TAGS; + } + + /** + * @param tagsCsv the tags as {@link String} in CSV format («first-tag»,...,«last-tag»). May be {@code null} or empty. + * @return the parsed {@link Set} of {@link Tag}s. + */ + public static Set parseCsv(String tagsCsv) { + + if (tagsCsv == null) { + return Collections.emptySet(); + } + tagsCsv = tagsCsv.trim().toLowerCase(Locale.ROOT); + if (tagsCsv.isEmpty()) { + return Collections.emptySet(); + } + String[] tagArray = tagsCsv.split(","); + Set tags = new HashSet<>(tagArray.length); + for (String tag : tagArray) { + tags.add(of(tag)); + } + assert (tags.size() == tagArray.length); + return Set.of(tags.toArray(new Tag[tags.size()])); + } + +} diff --git a/cli/src/main/java/com/devonfw/tools/ide/common/Tags.java b/cli/src/main/java/com/devonfw/tools/ide/common/Tags.java index ae80a5891..7f7fdfea9 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/common/Tags.java +++ b/cli/src/main/java/com/devonfw/tools/ide/common/Tags.java @@ -3,65 +3,14 @@ import java.util.Set; /** - * TODO hohwille This type ... - * + * Interface for an object that {@link #getTags() has} {@link Tag}s. */ public interface Tags { - /** {@link #getTags() Tag} for Java and JVM related tools. */ - String TAG_JAVA = "java"; - - /** {@link #getTags() Tag} for build tools. */ - String TAG_BUILD = "build"; - - /** {@link #getTags() Tag} for quality assurance (QA) tools. */ - String TAG_QA = "qa"; - - /** {@link #getTags() Tag} for artificial intelligence (AI) and machine learning (ML) tools. */ - String TAG_AI = "ai"; - - /** {@link #getTags() Tag} for documentation tools. */ - String TAG_DOCUMENTATION = "doc"; - - /** {@link #getTags() Tag} for tools supporting AsciiDoc. */ - String TAG_ASCIIDOC = "adoc"; - - /** {@link #getTags() Tag} for angular related tools. */ - String TAG_ANGULAR = "angular"; - - /** {@link #getTags() Tag} for TypeScript related tools. */ - String TAG_TYPE_SCRIPT = "ts"; - - /** {@link #getTags() Tag} for generic tools that increase productivity. */ - String TAG_PRODUCTIVITY = "productivity"; - - /** {@link #getTags() Tag} for DotNet related tools. */ - String TAG_DOT_NET = ".net"; - - /** {@link #getTags() Tag} for Python related tools. */ - String TAG_PYTHON = "python"; - - /** {@link #getTags() Tag} for tools that actually represent an IDE (Integrated Development Environment). */ - String TAG_IDE = "ide"; - - /** {@link #getTags() Tag} for tools providing a runtime environment (the core of a programming language). */ - String TAG_RUNTIME = "runtime"; - /** - * {@link #getTags() Tag} for cloud tools (e.g. CLI to manage infrastructure in the cloud). This is not limited to the - * hyper-scalers but also used for other platforms providing automation like openshift or even github. + * @return a {@link Set} with the tags classifying this object. E.g. for mvn (maven) the tags {@link Tag#JAVA java} + * and {@link Tag#BUILD build} could be associated. */ - String TAG_CLOUD = "cloud"; + Set getTags(); - /** {@link #getTags() Tag} for infrastructure-as-code (IAC) tools. */ - String TAG_IAC = "iac"; - - /** {@link #getTags() Tag} for frameworks. */ - String TAG_FRAMEWORK = "framework"; - - /** - * @return a {@link Set} with the tags classifying this object. E.g. for mvn (maven) the tags {@link #TAG_JAVA java} - * and {@link #TAG_BUILD build} could be associated. - */ - Set getTags(); } diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java index 8bda33623..d9945a70a 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java +++ b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java @@ -601,7 +601,7 @@ public void gitPullOrClone(Path target, String gitRepoUrl) { if (!gitRepoUrl.startsWith("http")) { throw new IllegalArgumentException("Invalid git URL '" + gitRepoUrl + "'!"); } - ProcessContext pc = newProcess().directory(target).executable("git"); + ProcessContext pc = newProcess().directory(target).executable("git").withEnvVar("GIT_TERMINAL_PROMPT", "0"); if (Files.isDirectory(target.resolve(".git"))) { ProcessResult result = pc.addArg("remote").run(true); List remotes = result.getOut(); diff --git a/cli/src/main/java/com/devonfw/tools/ide/environment/AbstractEnvironmentVariables.java b/cli/src/main/java/com/devonfw/tools/ide/environment/AbstractEnvironmentVariables.java index dad65e308..4b8bd6551 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/environment/AbstractEnvironmentVariables.java +++ b/cli/src/main/java/com/devonfw/tools/ide/environment/AbstractEnvironmentVariables.java @@ -28,6 +28,8 @@ public abstract class AbstractEnvironmentVariables implements EnvironmentVariabl // Variable surrounded with "${" and "}" such as "${JAVA_HOME}" 1......2........ private static final Pattern VARIABLE_SYNTAX = Pattern.compile("(\\$\\{([^}]+)})"); + private static final String SELF_REFERENCING_NOT_FOUND = ""; + private static final int MAX_RECURSION = 9; private static final String VARIABLE_PREFIX = "${"; @@ -161,19 +163,44 @@ public EnvironmentVariables resolved() { @Override public String resolve(String string, Object src) { - return resolve(string, src, 0, src, string); + return resolve(string, src, 0, src, string, this); } - private String resolve(String value, Object src, int recursion, Object rootSrc, String rootValue) { + /** + * 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 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. + * @return the given {@link String} with the variables resolved. + */ + private String resolve(String value, Object src, int recursion, Object rootSrc, String rootValue, + AbstractEnvironmentVariables resolvedVars) { if (value == null) { return null; } if (recursion > MAX_RECURSION) { - throw new IllegalStateException("Reached maximum recursion resolving " + value + " for root valiable " + rootSrc + throw new IllegalStateException("Reached maximum recursion resolving " + value + " for root variable " + rootSrc + " with value '" + rootValue + "'."); } recursion++; + Matcher matcher = VARIABLE_SYNTAX.matcher(value); if (!matcher.find()) { return value; @@ -181,16 +208,43 @@ private String resolve(String value, Object src, int recursion, Object rootSrc, StringBuilder sb = new StringBuilder(value.length() + EXTRA_CAPACITY); do { String variableName = matcher.group(2); - String variableValue = getValue(variableName); + String variableValue = resolvedVars.getValue(variableName); if (variableValue == null) { this.context.warning("Undefined variable {} in '{}={}' for root '{}={}'", variableName, src, value, rootSrc, rootValue); - } else { - String replacement = resolve(variableValue, variableName, recursion, rootSrc, rootValue); + continue; + } + EnvironmentVariables lowestFound = findVariable(variableName); + boolean isNotSelfReferencing = lowestFound == null || !lowestFound.getFlat(variableName).equals(value); + + if (isNotSelfReferencing) { + // looking for "variableName" starting from resolved upwards the hierarchy + String replacement = resolvedVars.resolve(variableValue, variableName, recursion, rootSrc, rootValue, + resolvedVars); + matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement)); + } else { // is self referencing + // finding next occurrence of "variableName" up the hierarchy of EnvironmentVariablesType + EnvironmentVariables next = lowestFound.getParent(); + while (next != null) { + if (next.getFlat(variableName) != null) { + break; + } + next = next.getParent(); + } + if (next == null) { + matcher.appendReplacement(sb, Matcher.quoteReplacement(SELF_REFERENCING_NOT_FOUND)); + continue; + } + // 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); matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement)); + } } while (matcher.find()); matcher.appendTail(sb); + String resolved = sb.toString(); return resolved; } diff --git a/cli/src/main/java/com/devonfw/tools/ide/environment/EnvironmentVariables.java b/cli/src/main/java/com/devonfw/tools/ide/environment/EnvironmentVariables.java index 5e5fc6aff..d99e67d1f 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/environment/EnvironmentVariables.java +++ b/cli/src/main/java/com/devonfw/tools/ide/environment/EnvironmentVariables.java @@ -186,7 +186,7 @@ default EnvironmentVariables findVariable(String name) { * @param source 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. - * @return the the given {@link String} with the variables resolved. + * @return the given {@link String} with the variables resolved. * @see com.devonfw.tools.ide.tool.ide.IdeToolCommandlet */ String resolve(String string, Object source); diff --git a/cli/src/main/java/com/devonfw/tools/ide/environment/EnvironmentVariablesPropertiesFile.java b/cli/src/main/java/com/devonfw/tools/ide/environment/EnvironmentVariablesPropertiesFile.java index a106f5f3b..963a67996 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/environment/EnvironmentVariablesPropertiesFile.java +++ b/cli/src/main/java/com/devonfw/tools/ide/environment/EnvironmentVariablesPropertiesFile.java @@ -222,9 +222,9 @@ public String set(String name, String value, boolean export) { String oldValue = this.variables.put(name, value); boolean flagChanged = export != this.exportedVariables.contains(name); if (Objects.equals(value, oldValue) && !flagChanged) { - this.context.trace("Set valiable '{}={}' caused no change in {}", name, value, this.propertiesFilePath); + this.context.trace("Set variable '{}={}' caused no change in {}", name, value, this.propertiesFilePath); } else { - this.context.debug("Set valiable '{}={}' in {}", name, value, this.propertiesFilePath); + this.context.debug("Set variable '{}={}' in {}", name, value, this.propertiesFilePath); this.modifiedVariables.add(name); if (export && (value != null)) { this.exportedVariables.add(name); diff --git a/cli/src/main/java/com/devonfw/tools/ide/io/FileAccess.java b/cli/src/main/java/com/devonfw/tools/ide/io/FileAccess.java index 8d2874471..d20361128 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/io/FileAccess.java +++ b/cli/src/main/java/com/devonfw/tools/ide/io/FileAccess.java @@ -59,10 +59,28 @@ public interface FileAccess { void move(Path source, Path targetDir); /** - * @param source the source {@link Path} to link to. + * Creates a symbolic link. If the given {@code targetLink} already exists and is a symbolic link or a Windows + * junction, it will be replaced. In case of missing privileges, Windows Junctions may be used as fallback, which must + * point to absolute paths. Therefore, the created link will be absolute instead of relative. + * + * @param source the source {@link Path} to link to, may be relative or absolute. * @param targetLink the {@link Path} where the symbolic link shall be created pointing to {@code source}. + * @param relative - {@code true} if the symbolic link shall be relative, {@code false} if it shall be absolute. */ - void symlink(Path source, Path targetLink); + void symlink(Path source, Path targetLink, boolean relative); + + /** + * Creates a relative symbolic link. If the given {@code targetLink} already exists and is a symbolic link or a + * Windows junction, it will be replaced. In case of missing privileges, Windows Junctions may be used as fallback, + * which must point to absolute paths. Therefore, the created link will be absolute instead of relative. + * + * @param source the source {@link Path} to link to, may be relative or absolute. + * @param targetLink the {@link Path} where the symbolic link shall be created pointing to {@code source}. + */ + default void symlink(Path source, Path targetLink) { + + symlink(source, targetLink, true); + } /** * @param source the source {@link Path file or folder} to copy. diff --git a/cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java b/cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java index e341ba43a..08985d6d5 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java @@ -13,8 +13,11 @@ import java.net.http.HttpResponse; import java.nio.file.FileSystemException; import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.attribute.BasicFileAttributes; import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -290,28 +293,158 @@ private void copyRecursive(Path source, Path target, FileCopyMode mode) throws I } } + /** + * Deletes the given {@link Path} if it is a symbolic link or a Windows junction. And throws an + * {@link IllegalStateException} if there is a file at the given {@link Path} that is neither a symbolic link nor a + * Windows junction. + * + * @param path the {@link Path} to delete. + * @throws IOException if the actual {@link Files#delete(Path) deletion} fails. + */ + private void deleteLinkIfExists(Path path) throws IOException { + + boolean exists = false; + boolean isJunction = false; + if (this.context.getSystemInfo().isWindows()) { + try { // since broken junctions are not detected by Files.exists(brokenJunction) + BasicFileAttributes attr = Files.readAttributes(path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS); + exists = true; + isJunction = attr.isOther() && attr.isDirectory(); + } catch (NoSuchFileException e) { + // ignore, since there is no previous file at the location, so nothing to delete + return; + } + } + exists = exists || Files.exists(path); // "||" since broken junctions are not detected by + // Files.exists(brokenJunction) + boolean isSymlink = exists && Files.isSymbolicLink(path); + + assert !(isSymlink && isJunction); + + if (exists) { + if (isJunction || isSymlink) { + this.context.info("Deleting previous " + (isJunction ? "junction" : "symlink") + " at " + path); + Files.delete(path); + } else { + throw new IllegalStateException( + "The file at " + path + " was not deleted since it is not a symlink or a Windows junction"); + } + } + } + + /** + * Adapts the given {@link Path} to be relative or absolute depending on the given {@code relative} flag. + * Additionally, {@link Path#toRealPath(LinkOption...)} is applied to {@code source}. + * + * @param source the {@link Path} to adapt. + * @param targetLink the {@link Path} used to calculate the relative path to the {@code source} if {@code relative} is + * set to {@code true}. + * @param relative the {@code relative} flag. + * @return the adapted {@link Path}. + * @see FileAccessImpl#symlink(Path, Path, boolean) + */ + private Path adaptPath(Path source, Path targetLink, boolean relative) throws IOException { + + if (source.isAbsolute()) { + try { + source = source.toRealPath(LinkOption.NOFOLLOW_LINKS); // to transform ../d1/../d2 to ../d2 + } catch (IOException e) { + throw new IOException( + "Calling toRealPath() on the source (" + source + ") in method FileAccessImpl.adaptPath() failed.", e); + } + if (relative) { + source = targetLink.getParent().relativize(source); + // to make relative links like this work: dir/link -> dir + source = (source.toString().isEmpty()) ? Paths.get(".") : source; + } + } else { // source is relative + if (relative) { + // even though the source is already relative, toRealPath should be called to transform paths like + // this ../d1/../d2 to ../d2 + source = targetLink.getParent() + .relativize(targetLink.resolveSibling(source).toRealPath(LinkOption.NOFOLLOW_LINKS)); + source = (source.toString().isEmpty()) ? Paths.get(".") : source; + } else { // !relative + try { + source = targetLink.resolveSibling(source).toRealPath(LinkOption.NOFOLLOW_LINKS); + } catch (IOException e) { + throw new IOException("Calling toRealPath() on " + targetLink + ".resolveSibling(" + source + + ") in method FileAccessImpl.adaptPath() failed.", e); + } + } + } + return source; + } + + /** + * Creates a Windows junction at {@code targetLink} pointing to {@code source}. + * + * @param source must be another Windows junction or a directory. + * @param targetLink the location of the Windows junction. + */ + private void createWindowsJunction(Path source, Path targetLink) { + + this.context.trace("Creating a Windows junction at " + targetLink + " with " + source + " as source."); + Path fallbackPath; + if (!source.isAbsolute()) { + this.context.warning( + "You are on Windows and you do not have permissions to create symbolic links. Junctions are used as an " + + "alternative, however, these can not point to relative paths. So the source (" + source + + ") is interpreted as an absolute path."); + try { + fallbackPath = targetLink.resolveSibling(source).toRealPath(LinkOption.NOFOLLOW_LINKS); + } catch (IOException e) { + throw new IllegalStateException( + "Since Windows junctions are used, the source must be an absolute path. The transformation of the passed " + + "source (" + source + ") to an absolute path failed.", + e); + } + + } else { + fallbackPath = source; + } + if (!Files.isDirectory(fallbackPath)) { // if source is a junction. This returns true as well. + throw new IllegalStateException( + "These junctions can only point to directories or other junctions. Please make sure that the source (" + + fallbackPath + ") is one of these."); + } + this.context.newProcess().executable("cmd") + .addArgs("/c", "mklink", "/d", "/j", targetLink.toString(), fallbackPath.toString()).run(); + } + @Override - public void symlink(Path source, Path targetLink) { + public void symlink(Path source, Path targetLink, boolean relative) { - this.context.trace("Creating symbolic link {} pointing to {}", targetLink, source); + Path adaptedSource = null; try { - if (Files.exists(targetLink) && Files.isSymbolicLink(targetLink)) { - this.context.debug("Deleting symbolic link to be re-created at {}", targetLink); - Files.delete(targetLink); - } - Files.createSymbolicLink(targetLink, source); + adaptedSource = adaptPath(source, targetLink, relative); + } catch (IOException e) { + throw new IllegalStateException("Failed to adapt source for source (" + source + ") target (" + targetLink + + ") and relative (" + relative + ")", e); + } + this.context.trace("Creating {} symbolic link {} pointing to {}", adaptedSource.isAbsolute() ? "" : "relative", + targetLink, adaptedSource); + + try { + deleteLinkIfExists(targetLink); + } catch (IOException e) { + throw new IllegalStateException("Failed to delete previous symlink or Windows junction at " + targetLink, e); + } + + try { + Files.createSymbolicLink(targetLink, adaptedSource); } catch (FileSystemException e) { if (this.context.getSystemInfo().isWindows()) { - this.context.info( - "Due to lack of permissions, Microsofts mklink with junction has to be used to create a Symlink. See https://github.com/devonfw/IDEasy/blob/main/documentation/symlinks.asciidoc for further details. Error was: " - + e.getMessage()); - this.context.newProcess().executable("cmd") - .addArgs("/c", "mklink", "/d", "/j", targetLink.toString(), source.toString()).run(); + this.context.info("Due to lack of permissions, Microsoft's mklink with junction had to be used to create " + + "a Symlink. See https://github.com/devonfw/IDEasy/blob/main/documentation/symlinks.asciidoc for " + + "further details. Error was: " + e.getMessage()); + createWindowsJunction(adaptedSource, targetLink); } else { throw new RuntimeException(e); } } catch (IOException e) { - throw new IllegalStateException("Failed to create a symbolic link " + targetLink + " pointing to " + source, e); + throw new IllegalStateException("Failed to create a " + (adaptedSource.isAbsolute() ? "" : "relative") + + "symbolic link " + targetLink + " pointing to " + source, e); } } @@ -398,8 +531,7 @@ public void delete(Path path) { try { if (Files.isSymbolicLink(path)) { Files.delete(path); - } - else { + } else { deleteRecursive(path); } } catch (IOException e) { diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java index 85eb99632..bcab1cfb9 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java +++ b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java @@ -111,6 +111,19 @@ default ProcessContext addArgs(List... args) { return this; } + /** + * Sets or overrides the specified environment variable only for the planned {@link #run() process execution}. Please + * note that the environment variables are initialized when the {@link ProcessContext} is created. This method + * explicitly set an additional or overrides an existing environment and will have effect for each {@link #run() + * process execution} invoked from this {@link ProcessContext} instance. Be aware of such side-effects when reusing + * the same {@link ProcessContext} to {@link #run() run} multiple commands. + * + * @param key the name of the environment variable (E.g. "PATH"). + * @param value the value of the environment variable. + * @return this {@link ProcessContext} for fluent API calls. + */ + ProcessContext withEnvVar(String key, String value); + /** * Runs the previously configured {@link #executable(Path) command} with the configured {@link #addArgs(String...) * arguments}. Will reset the {@link #addArgs(String...) arguments} but not the {@link #executable(Path) command} for diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java index 596f9c316..21f6a9a0e 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java @@ -1,9 +1,9 @@ package com.devonfw.tools.ide.process; import java.io.BufferedReader; +import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.IOException; import java.lang.ProcessBuilder.Redirect; import java.nio.file.Files; import java.nio.file.Path; @@ -89,6 +89,13 @@ public ProcessContext addArg(String arg) { return this; } + @Override + public ProcessContext withEnvVar(String key, String value) { + + this.processBuilder.environment().put(key, value); + return this; + } + @Override public ProcessResult run(boolean capture) { @@ -195,7 +202,7 @@ private String createCommandMessage(String suffix) { String message = sb.toString(); return message; } - + private boolean hasSheBang(Path file) { try (InputStream in = Files.newInputStream(file)) { @@ -211,6 +218,7 @@ private boolean hasSheBang(Path file) { } private String findBashOnWindows() { + // Check if Git Bash exists in the default location Path defaultPath = Paths.get("C:\\Program Files\\Git\\bin\\bash.exe"); if (Files.exists(defaultPath)) { @@ -218,8 +226,8 @@ private String findBashOnWindows() { } // If not found in the default location, try the registry query - String[] bashVariants = {"GitForWindows", "Cygwin\\setup"}; - String[] registryKeys = {"HKEY_LOCAL_MACHINE","HKEY_CURRENT_USER"}; + String[] bashVariants = { "GitForWindows", "Cygwin\\setup" }; + String[] registryKeys = { "HKEY_LOCAL_MACHINE", "HKEY_CURRENT_USER" }; String regQueryResult; for (String bashVariant : bashVariants) { for (String registryKey : registryKeys) { @@ -256,9 +264,8 @@ private String findBashOnWindows() { } } } - //no bash found + // no bash found throw new IllegalStateException("Could not find Bash. Please install Git for Windows and rerun."); } - } diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/GlobalToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/GlobalToolCommandlet.java index 981dceff2..547696b2d 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/GlobalToolCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/GlobalToolCommandlet.java @@ -1,5 +1,6 @@ package com.devonfw.tools.ide.tool; +import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.io.FileAccess; import com.devonfw.tools.ide.process.ProcessContext; @@ -24,7 +25,7 @@ public abstract class GlobalToolCommandlet extends ToolCommandlet { * @param tags the {@link #getTags() tags} classifying the tool. Should be created via {@link Set#of(Object) Set.of} * method. */ - public GlobalToolCommandlet(IdeContext context, String tool, Set tags) { + public GlobalToolCommandlet(IdeContext context, String tool, Set tags) { super(context, tool, tags); } diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java index cbb5e57f0..f86c6a732 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java @@ -1,6 +1,13 @@ package com.devonfw.tools.ide.tool; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Set; + import com.devonfw.tools.ide.commandlet.Commandlet; +import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.io.FileAccess; import com.devonfw.tools.ide.io.FileCopyMode; @@ -8,12 +15,6 @@ import com.devonfw.tools.ide.repo.ToolRepository; import com.devonfw.tools.ide.version.VersionIdentifier; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.Set; - /** * {@link ToolCommandlet} that is installed locally into the IDE. */ @@ -27,12 +28,11 @@ public abstract class LocalToolCommandlet extends ToolCommandlet { * @param tags the {@link #getTags() tags} classifying the tool. Should be created via {@link Set#of(Object) Set.of} * method. */ - public LocalToolCommandlet(IdeContext context, String tool, Set tags) { + public LocalToolCommandlet(IdeContext context, String tool, Set tags) { super(context, tool, tags); } - /** * @return the {@link Path} where the tool is located (installed). */ @@ -81,7 +81,7 @@ protected boolean doInstall(boolean silent) { if (installedVersion == null) { this.context.success("Successfully installed {} in version {}", this.tool, resolvedVersion); } else { - this.context.success("Successfully installed {} in version {} replacing previous version {]", this.tool, + this.context.success("Successfully installed {} in version {} replacing previous version {}", this.tool, resolvedVersion, installedVersion); } postInstall(); diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java index 05755cff4..6d57525b8 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java @@ -12,6 +12,7 @@ import com.devonfw.tools.ide.url.model.file.json.UrlSecurityWarning; import com.devonfw.tools.ide.cli.CliException; import com.devonfw.tools.ide.commandlet.Commandlet; +import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.common.Tags; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.environment.EnvironmentVariables; @@ -34,7 +35,7 @@ public abstract class ToolCommandlet extends Commandlet implements Tags { /** @see #getName() */ protected final String tool; - private final Set tags; + private final Set tags; /** The commandline arguments to pass to the tool. */ public final StringListProperty arguments; @@ -49,7 +50,7 @@ public abstract class ToolCommandlet extends Commandlet implements Tags { * @param tags the {@link #getTags() tags} classifying the tool. Should be created via {@link Set#of(Object) Set.of} * method. */ - public ToolCommandlet(IdeContext context, String tool, Set tags) { + public ToolCommandlet(IdeContext context, String tool, Set tags) { super(context); this.tool = tool; @@ -76,7 +77,7 @@ protected String getBinaryName() { } @Override - public final Set getTags() { + public final Set getTags() { return this.tags; } diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/az/Azure.java b/cli/src/main/java/com/devonfw/tools/ide/tool/az/Azure.java index 12937026a..195ccf922 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/az/Azure.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/az/Azure.java @@ -3,6 +3,7 @@ import java.nio.file.Paths; import java.util.Set; +import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.environment.EnvironmentVariables; import com.devonfw.tools.ide.environment.EnvironmentVariablesType; @@ -22,7 +23,7 @@ public class Azure extends LocalToolCommandlet { */ public Azure(IdeContext context) { - super(context, "az", Set.of(TAG_CLOUD)); + super(context, "az", Set.of(Tag.CLOUD)); } @Override @@ -34,6 +35,6 @@ public void postInstall() { EnvironmentVariables typeVariables = variables.getByType(EnvironmentVariablesType.CONF); typeVariables.set("AZURE_CONFIG_DIR", this.context.getConfPath().resolve(".azure").toString(), true); typeVariables.save(); - this.context.getFileAccess().symlink(Paths.get("wbin"), this.getToolPath().resolve("bin")); + this.context.getFileAccess().symlink(Paths.get("wbin"), getToolPath().resolve("bin")); } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java b/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java index 38116d067..5d91ad304 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java @@ -11,6 +11,7 @@ import com.devonfw.tools.ide.cli.CliArgument; import com.devonfw.tools.ide.cli.CliException; +import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.log.IdeLogLevel; import com.devonfw.tools.ide.process.ProcessContext; @@ -32,7 +33,7 @@ public class Eclipse extends IdeToolCommandlet { */ public Eclipse(IdeContext context) { - super(context, "eclipse", Set.of(TAG_JAVA, TAG_IDE)); + super(context, "eclipse", Set.of(Tag.ECLIPSE)); } @Override diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/EclipseJeeUrlUpdater.java b/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/EclipseJeeUrlUpdater.java new file mode 100644 index 000000000..c8c905212 --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/EclipseJeeUrlUpdater.java @@ -0,0 +1,12 @@ +package com.devonfw.tools.ide.tool.eclipse; + +/** + * {@link EclipseUrlUpdater} for "jee" (C++) edition of Eclipse. + */ +public class EclipseJeeUrlUpdater extends EclipseUrlUpdater { + @Override + protected String getEdition() { + + return "jee"; + } +} diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/EclipseUrlUpdater.java b/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/EclipseUrlUpdater.java index 63552aacb..f53b6175a 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/EclipseUrlUpdater.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/EclipseUrlUpdater.java @@ -86,7 +86,7 @@ protected Pattern getVersionPattern() { @Override protected String mapVersion(String version) { - // TODO remove this hack and get versiosn from reliable API + // TODO remove this hack and get versions from reliable API return super.mapVersion(version.replace(" ", "-")); } diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/gh/Gh.java b/cli/src/main/java/com/devonfw/tools/ide/tool/gh/Gh.java index 65383ed92..f9d2af057 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/gh/Gh.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/gh/Gh.java @@ -2,6 +2,7 @@ import java.util.Set; +import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; @@ -18,7 +19,7 @@ public class Gh extends LocalToolCommandlet { */ public Gh(IdeContext context) { - super(context, "gh", Set.of(TAG_CLOUD)); + super(context, "gh", Set.of(Tag.CLOUD)); } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/gradle/Gradle.java b/cli/src/main/java/com/devonfw/tools/ide/tool/gradle/Gradle.java index f0405ce14..476cc1ce8 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/gradle/Gradle.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/gradle/Gradle.java @@ -2,6 +2,7 @@ import java.util.Set; +import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; @@ -19,7 +20,7 @@ public class Gradle extends LocalToolCommandlet { */ public Gradle(IdeContext context) { - super(context, "gradle", Set.of(TAG_JAVA, TAG_BUILD)); + super(context, "gradle", Set.of(Tag.JAVA, Tag.BUILD)); } @Override diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/helm/Helm.java b/cli/src/main/java/com/devonfw/tools/ide/tool/helm/Helm.java index 1f4a2a41b..8125315df 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/helm/Helm.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/helm/Helm.java @@ -1,11 +1,12 @@ package com.devonfw.tools.ide.tool.helm; +import java.util.Set; + +import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; -import java.util.Set; - /** * {@link ToolCommandlet} for Helm, the package manager for Kubernetes. */ @@ -17,7 +18,7 @@ public class Helm extends LocalToolCommandlet { */ public Helm(IdeContext context) { - super(context, "helm", Set.of(TAG_CLOUD)); + super(context, "helm", Set.of(Tag.KUBERNETES)); } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/ide/IdeToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/ide/IdeToolCommandlet.java index 44c2cdd3f..2cd8ec18b 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/ide/IdeToolCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/ide/IdeToolCommandlet.java @@ -12,6 +12,7 @@ import java.util.stream.Stream; import com.devonfw.tools.ide.cli.CliException; +import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.io.FileAccess; import com.devonfw.tools.ide.tool.LocalToolCommandlet; @@ -38,10 +39,20 @@ public abstract class IdeToolCommandlet extends LocalToolCommandlet { * @param tags the {@link #getTags() tags} classifying the tool. Should be created via {@link Set#of(Object) Set.of} * method. */ - public IdeToolCommandlet(IdeContext context, String tool, Set tags) { + public IdeToolCommandlet(IdeContext context, String tool, Set tags) { super(context, tool, tags); - assert (tags.contains(TAG_IDE)); + assert (hasIde(tags)); + } + + private boolean hasIde(Set tags) { + + for (Tag tag : tags) { + if (tag.isAncestorOf(Tag.IDE)) { + return true; + } + } + throw new IllegalStateException("Tags of IdeTool hat to be connected with tag IDE: " + tags); } private Map getPluginsMap() { diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/ide/PluginDescriptorImpl.java b/cli/src/main/java/com/devonfw/tools/ide/tool/ide/PluginDescriptorImpl.java index 3abf8d70c..6b32d1061 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/ide/PluginDescriptorImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/ide/PluginDescriptorImpl.java @@ -4,11 +4,11 @@ import java.io.Reader; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; import java.util.Locale; import java.util.Properties; import java.util.Set; +import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.log.IdeLogger; @@ -25,7 +25,7 @@ public class PluginDescriptorImpl implements PluginDescriptor { private final boolean active; - private final Set tags; + private final Set tags; /** * The constructor. @@ -36,7 +36,7 @@ public class PluginDescriptorImpl implements PluginDescriptor { * @param active the {@link #isActive() active flag}. * @param tags the {@link #getTags() tags}. */ - public PluginDescriptorImpl(String id, String name, String url, boolean active, Set tags) { + public PluginDescriptorImpl(String id, String name, String url, boolean active, Set tags) { super(); this.id = id; @@ -71,7 +71,7 @@ public boolean isActive() { } @Override - public Set getTags() { + public Set getTags() { return this.tags; } @@ -100,12 +100,7 @@ public static PluginDescriptor of(Path propertiesFile, IdeLogger logger, boolean } boolean active = getBoolean(properties, "active", "plugin_active", propertiesFile, logger); String tagsCsv = getString(properties, "tags", "plugin_tags"); - Set tags; - if ((tagsCsv == null) || tagsCsv.isBlank()) { - tags = Collections.emptySet(); - } else { - tags = Set.of(tagsCsv.split(",")); - } + Set tags = Tag.parseCsv(tagsCsv); return new PluginDescriptorImpl(id, name, url, active, tags); } diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/intellij/Intellij.java b/cli/src/main/java/com/devonfw/tools/ide/tool/intellij/Intellij.java index cbab7deef..2c1f6b5cc 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/intellij/Intellij.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/intellij/Intellij.java @@ -2,6 +2,7 @@ import java.util.Set; +import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.tool.ide.IdeToolCommandlet; import com.devonfw.tools.ide.tool.ide.PluginDescriptor; @@ -19,7 +20,7 @@ public class Intellij extends IdeToolCommandlet { */ public Intellij(IdeContext context) { - super(context, "intellij", Set.of(TAG_JAVA, TAG_IDE)); + super(context, "intellij", Set.of(Tag.INTELLIJ)); } @Override diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/java/Java.java b/cli/src/main/java/com/devonfw/tools/ide/tool/java/Java.java index e26c7308a..e40c1707c 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/java/Java.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/java/Java.java @@ -2,6 +2,7 @@ import java.util.Set; +import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; @@ -18,7 +19,7 @@ public class Java extends LocalToolCommandlet { */ public Java(IdeContext context) { - super(context, "java", Set.of(TAG_JAVA, TAG_RUNTIME)); + super(context, "java", Set.of(Tag.JAVA, Tag.RUNTIME)); } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/kotlinc/Kotlinc.java b/cli/src/main/java/com/devonfw/tools/ide/tool/kotlinc/Kotlinc.java index 744d051f0..c0d32d2bf 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/kotlinc/Kotlinc.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/kotlinc/Kotlinc.java @@ -2,12 +2,14 @@ import java.util.Set; +import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; /** - * {@link ToolCommandlet} for Kotlin command-line compiler (kotlinc). + * {@link ToolCommandlet} for Kotlin command-line compiler + * (kotlinc). */ public class Kotlinc extends LocalToolCommandlet { @@ -18,6 +20,6 @@ public class Kotlinc extends LocalToolCommandlet { */ public Kotlinc(IdeContext context) { - super(context, "kotlinc", Set.of(TAG_JAVA, TAG_RUNTIME)); + super(context, "kotlinc", Set.of(Tag.KOTLIN, Tag.RUNTIME)); } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/kotlinc/KotlincNative.java b/cli/src/main/java/com/devonfw/tools/ide/tool/kotlinc/KotlincNative.java index b8ed74b58..cc9073a33 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/kotlinc/KotlincNative.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/kotlinc/KotlincNative.java @@ -2,12 +2,14 @@ import java.util.Set; +import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; /** - * {@link ToolCommandlet} for Kotlin Native (kotlincnative). + * {@link ToolCommandlet} for Kotlin Native + * (kotlincnative). */ public class KotlincNative extends LocalToolCommandlet { @@ -18,6 +20,6 @@ public class KotlincNative extends LocalToolCommandlet { */ public KotlincNative(IdeContext context) { - super(context, "kotlincnative", Set.of(TAG_JAVA, TAG_RUNTIME)); + super(context, "kotlincnative", Set.of(Tag.KOTLIN, Tag.RUNTIME)); } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/Mvn.java b/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/Mvn.java index 5ef339f7c..d99aa227c 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/Mvn.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/Mvn.java @@ -2,6 +2,7 @@ import java.util.Set; +import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; @@ -19,7 +20,7 @@ public class Mvn extends LocalToolCommandlet { */ public Mvn(IdeContext context) { - super(context, "mvn", Set.of(TAG_JAVA, TAG_BUILD)); + super(context, "mvn", Set.of(Tag.JAVA, Tag.BUILD)); } @Override diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/node/Node.java b/cli/src/main/java/com/devonfw/tools/ide/tool/node/Node.java index a3f9c976e..7d4ad94fa 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/node/Node.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/node/Node.java @@ -2,6 +2,7 @@ import java.util.Set; +import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; @@ -18,7 +19,7 @@ public class Node extends LocalToolCommandlet { */ public Node(IdeContext context) { - super(context, "node", Set.of(TAG_RUNTIME)); + super(context, "node", Set.of(Tag.JAVA_SCRIPT, Tag.RUNTIME)); } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/oc/Oc.java b/cli/src/main/java/com/devonfw/tools/ide/tool/oc/Oc.java index 31a7f1b9f..527b40bbc 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/oc/Oc.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/oc/Oc.java @@ -1,11 +1,12 @@ package com.devonfw.tools.ide.tool.oc; +import java.util.Set; + +import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; -import java.util.Set; - /** * {@link ToolCommandlet} for Openshift CLI. */ @@ -18,7 +19,7 @@ public class Oc extends LocalToolCommandlet { */ public Oc(IdeContext context) { - super(context, "oc", Set.of(TAG_CLOUD)); + super(context, "oc", Set.of(Tag.CLOUD)); } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/quarkus/Quarkus.java b/cli/src/main/java/com/devonfw/tools/ide/tool/quarkus/Quarkus.java index d135d9abc..70c8d7263 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/quarkus/Quarkus.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/quarkus/Quarkus.java @@ -1,11 +1,12 @@ package com.devonfw.tools.ide.tool.quarkus; +import java.util.Set; + +import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; -import java.util.Set; - /** * {@link ToolCommandlet} for Quarkus. */ @@ -17,7 +18,7 @@ public class Quarkus extends LocalToolCommandlet { */ public Quarkus(IdeContext context) { - super(context, "quarkus", Set.of(TAG_CLOUD, TAG_FRAMEWORK)); + super(context, "quarkus", Set.of(Tag.JAVA, Tag.FRAMEWORK)); } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/terraform/Terraform.java b/cli/src/main/java/com/devonfw/tools/ide/tool/terraform/Terraform.java index 56174c0c7..745ececde 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/terraform/Terraform.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/terraform/Terraform.java @@ -2,6 +2,7 @@ import java.util.Set; +import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; @@ -18,7 +19,7 @@ public class Terraform extends LocalToolCommandlet { */ public Terraform(IdeContext context) { - super(context, "terraform", Set.of(TAG_CLOUD, TAG_IAC)); + super(context, "terraform", Set.of(Tag.IAC)); } @Override diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/vscode/Vscode.java b/cli/src/main/java/com/devonfw/tools/ide/tool/vscode/Vscode.java index a7e85da00..563a6586a 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/vscode/Vscode.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/vscode/Vscode.java @@ -2,6 +2,7 @@ import java.util.Set; +import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; @@ -16,9 +17,9 @@ public class Vscode extends LocalToolCommandlet { * * @param context the {@link IdeContext}. */ - public Vscode (IdeContext context) { + public Vscode(IdeContext context) { - super(context, "vscode", Set.of(TAG_IDE)); + super(context, "vscode", Set.of(Tag.VS_CODE)); } @Override diff --git a/cli/src/main/java/com/devonfw/tools/ide/url/model/UrlMetadata.java b/cli/src/main/java/com/devonfw/tools/ide/url/model/UrlMetadata.java index 183cf300e..1597c7ec2 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/url/model/UrlMetadata.java +++ b/cli/src/main/java/com/devonfw/tools/ide/url/model/UrlMetadata.java @@ -115,7 +115,10 @@ public UrlVersion getVersionFolder(String tool, String edition, VersionIdentifie VersionIdentifier resolvedVersion = getVersion(tool, edition, version); UrlVersion urlVersion = getEdition(tool, edition).getChild(resolvedVersion.toString()); - Objects.requireNonNull(urlVersion); + if (urlVersion == null) { + throw new IllegalArgumentException( + "Version " + version + " for tool " + tool + " does not exist in edition " + edition + "."); + } return urlVersion; } diff --git a/cli/src/main/java/com/devonfw/tools/ide/url/updater/UpdateManager.java b/cli/src/main/java/com/devonfw/tools/ide/url/updater/UpdateManager.java index 0fe20d176..29e8b4555 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/url/updater/UpdateManager.java +++ b/cli/src/main/java/com/devonfw/tools/ide/url/updater/UpdateManager.java @@ -16,6 +16,7 @@ import com.devonfw.tools.ide.tool.docker.DockerRancherDesktopUrlUpdater; import com.devonfw.tools.ide.tool.dotnet.DotNetUrlUpdater; import com.devonfw.tools.ide.tool.eclipse.EclipseCppUrlUpdater; +import com.devonfw.tools.ide.tool.eclipse.EclipseJeeUrlUpdater; import com.devonfw.tools.ide.tool.eclipse.EclipseJavaUrlUpdater; import com.devonfw.tools.ide.tool.gcloud.GCloudUrlUpdater; import com.devonfw.tools.ide.tool.gcviewer.GcViewerUrlUpdater; @@ -58,13 +59,13 @@ public class UpdateManager extends AbstractProcessorWithTimeout { private final List updaters = Arrays.asList(new AndroidStudioUrlUpdater(), new AwsUrlUpdater(), new AzureUrlUpdater(), new CobigenUrlUpdater(), new DockerDesktopUrlUpdater(), new DotNetUrlUpdater(), - new EclipseCppUrlUpdater(), new EclipseJavaUrlUpdater(), new GCloudUrlUpdater(), new GcViewerUrlUpdater(), - new GhUrlUpdater(), new GraalVmCommunityUpdater(), new GraalVmOracleUrlUpdater(), new GradleUrlUpdater(), - new HelmUrlUpdater(), new IntellijUrlUpdater(), new JavaUrlUpdater(), new JenkinsUrlUpdater(), - new JmcUrlUpdater(), new KotlincUrlUpdater(), new KotlincNativeUrlUpdater(), new LazyDockerUrlUpdater(), - new MvnUrlUpdater(), new NodeUrlUpdater(), new NpmUrlUpdater(), new OcUrlUpdater(), new PipUrlUpdater(), - new PythonUrlUpdater(), new QuarkusUrlUpdater(), new DockerRancherDesktopUrlUpdater(), new SonarUrlUpdater(), - new TerraformUrlUpdater(), new TomcatUrlUpdater(), new VsCodeUrlUpdater()); + new EclipseCppUrlUpdater(), new EclipseJeeUrlUpdater(), new EclipseJavaUrlUpdater(), new GCloudUrlUpdater(), + new GcViewerUrlUpdater(), new GhUrlUpdater(), new GraalVmCommunityUpdater(), new GraalVmOracleUrlUpdater(), + new GradleUrlUpdater(), new HelmUrlUpdater(), new IntellijUrlUpdater(), new JavaUrlUpdater(), + new JenkinsUrlUpdater(), new JmcUrlUpdater(), new KotlincUrlUpdater(), new KotlincNativeUrlUpdater(), + new LazyDockerUrlUpdater(), new MvnUrlUpdater(), new NodeUrlUpdater(), new NpmUrlUpdater(), new OcUrlUpdater(), + new PipUrlUpdater(), new PythonUrlUpdater(), new QuarkusUrlUpdater(), new DockerRancherDesktopUrlUpdater(), + new SonarUrlUpdater(), new TerraformUrlUpdater(), new TomcatUrlUpdater(), new VsCodeUrlUpdater()); /** * The constructor. diff --git a/cli/src/main/java/com/devonfw/tools/ide/version/BoundaryType.java b/cli/src/main/java/com/devonfw/tools/ide/version/BoundaryType.java index c8abed59a..8dbb1f7dc 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/version/BoundaryType.java +++ b/cli/src/main/java/com/devonfw/tools/ide/version/BoundaryType.java @@ -15,5 +15,67 @@ public enum BoundaryType { LEFT_OPEN, /** Right open interval - includes the lower bound but excludes the upper bound. */ - RIGHT_OPEN -} + RIGHT_OPEN; + + static final String START_EXCLUDING_PREFIX = "("; + + static final String START_INCLUDING_PREFIX = "["; + + static final String END_EXCLUDING_SUFFIX = ")"; + + static final String END_INCLUDING_SUFFIX = "]"; + + /** + * @return {@code true} if left exclusive, {@code false} otherwise (left inclusive). + */ + public boolean isLeftExclusive() { + + return (this == LEFT_OPEN) || (this == OPEN); + } + + /** + * @return {@code true} if right exclusive, {@code false} otherwise (right inclusive). + */ + public boolean isRightExclusive() { + + return (this == RIGHT_OPEN) || (this == OPEN); + } + + /** + * @return the prefix (left parenthesis or bracket for {@link #isLeftExclusive()}). + */ + public String getPrefix() { + + return isLeftExclusive() ? START_EXCLUDING_PREFIX : START_INCLUDING_PREFIX; + } + + /** + * @return the suffix (right parenthesis or bracket for {@link #isRightExclusive()}). + */ + public String getSuffix() { + + return isRightExclusive() ? END_EXCLUDING_SUFFIX : END_INCLUDING_SUFFIX; + } + + /** + * @param leftExclusive the {@link #isLeftExclusive() left exclusive flag}. + * @param rightExclusive the {@link #isRightExclusive() right exclusive flag}. + * @return the {@link BoundaryType} with the specified values. + */ + public static BoundaryType of(boolean leftExclusive, boolean rightExclusive) { + + if (leftExclusive) { + if (rightExclusive) { + return OPEN; + } else { + return LEFT_OPEN; + } + } else { + if (rightExclusive) { + return RIGHT_OPEN; + } else { + return CLOSED; + } + } + } +} \ No newline at end of file diff --git a/cli/src/main/java/com/devonfw/tools/ide/version/VersionPhase.java b/cli/src/main/java/com/devonfw/tools/ide/version/VersionPhase.java index b49e5584c..cc8a3dd44 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/version/VersionPhase.java +++ b/cli/src/main/java/com/devonfw/tools/ide/version/VersionPhase.java @@ -54,7 +54,7 @@ public enum VersionPhase implements AbstractVersionPhase { /** A bug-fix version from the previous release including important fix(es). */ BUG_FIX(Boolean.TRUE, "bugfix", "fix", "quickfix"), - /** An fix release, similar to {@link #BUG_FIX} but more urgent. */ + /** A fix release, similar to {@link #BUG_FIX} but more urgent. */ HOT_FIX(Boolean.TRUE, "hotfix", "hf"); private final Boolean hasNumber; diff --git a/cli/src/main/java/com/devonfw/tools/ide/version/VersionRange.java b/cli/src/main/java/com/devonfw/tools/ide/version/VersionRange.java index 70f32932e..4714ae073 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/version/VersionRange.java +++ b/cli/src/main/java/com/devonfw/tools/ide/version/VersionRange.java @@ -1,5 +1,7 @@ package com.devonfw.tools.ide.version; +import java.util.Objects; + import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; @@ -14,44 +16,9 @@ public final class VersionRange implements Comparable { private final VersionIdentifier max; - private final boolean leftIsExclusive; - - private final boolean rightIsExclusive; - - private static final String VERSION_SEPARATOR = ">"; - - private static final String START_EXCLUDING_PREFIX = "("; - - private static final String START_INCLUDING_PREFIX = "["; - - private static final String END_EXCLUDING_SUFFIX = ")"; - - private static final String END_INCLUDING_SUFFIX = "]"; - - public static String getVersionSeparator() { - - return VERSION_SEPARATOR; - } - - public static String getStartExcludingPrefix() { - - return START_EXCLUDING_PREFIX; - } - - public static String getStartIncludingPrefix() { - - return START_INCLUDING_PREFIX; - } - - public static String getEndExcludingSuffix() { - - return END_EXCLUDING_SUFFIX; - } - - public static String getEndIncludingSuffix() { + private final BoundaryType boundaryType; - return END_INCLUDING_SUFFIX; - } + private static final String VERSION_SEPARATOR = ","; /** * The constructor. @@ -61,11 +28,7 @@ public static String getEndIncludingSuffix() { */ public VersionRange(VersionIdentifier min, VersionIdentifier max) { - super(); - this.min = min; - this.max = max; - this.leftIsExclusive = false; - this.rightIsExclusive = false; + this(min, max, BoundaryType.CLOSED); } /** @@ -79,27 +42,18 @@ public VersionRange(VersionIdentifier min, VersionIdentifier max) { public VersionRange(VersionIdentifier min, VersionIdentifier max, BoundaryType boundaryType) { super(); + Objects.requireNonNull(boundaryType); this.min = min; this.max = max; - this.leftIsExclusive = BoundaryType.LEFT_OPEN.equals(boundaryType) || BoundaryType.OPEN.equals(boundaryType); - this.rightIsExclusive = BoundaryType.RIGHT_OPEN.equals(boundaryType) || BoundaryType.OPEN.equals(boundaryType); - } - - /** - * The constructor. - * - * @param min the {@link #getMin() minimum}. - * @param max the {@link #getMax() maximum}. - * @param leftIsExclusive - {@code true} if the {@link #getMin() minimum} is exclusive, {@code false} otherwise. - * @param rightIsExclusive - {@code true} if the {@link #getMax() maximum} is exclusive, {@code false} otherwise. - */ - public VersionRange(VersionIdentifier min, VersionIdentifier max, boolean leftIsExclusive, boolean rightIsExclusive) { + this.boundaryType = boundaryType; + if ((min != null) && (max != null) && min.isGreater(max)) { + throw new IllegalArgumentException(toString()); + } else if ((min == null) && !boundaryType.isLeftExclusive()) { + throw new IllegalArgumentException(toString()); + } else if ((max == null) && !boundaryType.isRightExclusive()) { + throw new IllegalArgumentException(toString()); + } - super(); - this.min = min; - this.max = max; - this.leftIsExclusive = leftIsExclusive; - this.rightIsExclusive = rightIsExclusive; } /** @@ -118,36 +72,12 @@ public VersionIdentifier getMax() { return this.max; } - /** - * @return {@code true} if the {@link #getMin() minimum} is exclusive, {@code false} otherwise. - */ - public boolean isLeftExclusive() { - - return this.leftIsExclusive; - } - - /** - * @return {@code true} if the {@link #getMax() maximum} is exclusive, {@code false} otherwise. - */ - public boolean isRightExclusive() { - - return this.rightIsExclusive; - } - /** * @return the {@link BoundaryType} defining whether the boundaries of the range are inclusive or exclusive. */ public BoundaryType getBoundaryType() { - if (this.leftIsExclusive && this.rightIsExclusive) { - return BoundaryType.OPEN; - } else if (this.leftIsExclusive) { - return BoundaryType.LEFT_OPEN; - } else if (this.rightIsExclusive) { - return BoundaryType.RIGHT_OPEN; - } else { - return BoundaryType.CLOSED; - } + return this.boundaryType; } /** @@ -158,18 +88,18 @@ public BoundaryType getBoundaryType() { public boolean contains(VersionIdentifier version) { if (this.min != null) { - if (this.min.equals(version)) { - return !this.leftIsExclusive; - } - if (version.isLess(this.min)) { + VersionComparisonResult compareMin = version.compareVersion(this.min); + if (compareMin.isLess()) { + return false; + } else if (compareMin.isEqual() && this.boundaryType.isLeftExclusive()) { return false; } } if (this.max != null) { - if (this.max.equals(version)) { - return !this.rightIsExclusive; - } - if (version.isGreater(this.max)) { + VersionComparisonResult compareMax = version.compareVersion(this.max); + if (compareMax.isGreater()) { + return false; + } else if (compareMax.isEqual() && this.boundaryType.isRightExclusive()) { return false; } } @@ -189,7 +119,8 @@ public int compareTo(VersionRange o) { } int compareMins = this.min.compareTo(o.min); if (compareMins == 0) { - return this.leftIsExclusive == o.leftIsExclusive ? 0 : this.leftIsExclusive ? 1 : -1; + return this.boundaryType.isLeftExclusive() == o.boundaryType.isLeftExclusive() ? 0 + : this.boundaryType.isLeftExclusive() ? 1 : -1; } else { return compareMins; } @@ -198,26 +129,20 @@ public int compareTo(VersionRange o) { @Override public boolean equals(Object obj) { - if (this == obj) + if (this == obj) { return true; - - if (obj == null || getClass() != obj.getClass()) + } else if ((obj == null) || (getClass() != obj.getClass())) { return false; - - VersionRange o = (VersionRange) obj; - - if (this.min == null && this.max == null) { - return o.min == null && o.max == null; - } - if (this.min == null) { - return o.min == null && this.max.equals(o.max) && this.rightIsExclusive == o.rightIsExclusive; } - if (this.max == null) { - return this.min.equals(o.min) && o.max == null && this.leftIsExclusive == o.leftIsExclusive; + VersionRange o = (VersionRange) obj; + if (this.boundaryType != o.boundaryType) { + return false; + } else if (!Objects.equals(this.min, o.min)) { + return false; + } else if (!Objects.equals(this.max, o.max)) { + return false; } - return this.min.equals(o.min) && this.leftIsExclusive == o.leftIsExclusive && this.max.equals(o.max) - && this.rightIsExclusive == o.rightIsExclusive; - + return true; } @Override @@ -225,7 +150,7 @@ public boolean equals(Object obj) { public String toString() { StringBuilder sb = new StringBuilder(); - sb.append(this.leftIsExclusive ? START_EXCLUDING_PREFIX : START_INCLUDING_PREFIX); + sb.append(this.boundaryType.getPrefix()); if (this.min != null) { sb.append(this.min); } @@ -233,7 +158,7 @@ public String toString() { if (this.max != null) { sb.append(this.max); } - sb.append(this.rightIsExclusive ? END_EXCLUDING_SUFFIX : END_INCLUDING_SUFFIX); + sb.append(this.boundaryType.getSuffix()); return sb.toString(); } @@ -244,40 +169,45 @@ public String toString() { @JsonCreator public static VersionRange of(String value) { - boolean leftIsExclusive = value.startsWith(START_EXCLUDING_PREFIX); - boolean rightIsExclusive = value.endsWith(END_EXCLUDING_SUFFIX); - value = removeAffixes(value); - - int index = value.indexOf(VERSION_SEPARATOR); - if (index == -1) { - return null; // log warning? + Boolean isleftExclusive = null; + Boolean isRightExclusive = null; + if (value.startsWith(BoundaryType.START_EXCLUDING_PREFIX)) { + isleftExclusive = Boolean.TRUE; + value = value.substring(BoundaryType.START_EXCLUDING_PREFIX.length()); + } else if (value.startsWith(BoundaryType.START_INCLUDING_PREFIX)) { + isleftExclusive = Boolean.FALSE; + value = value.substring(BoundaryType.START_INCLUDING_PREFIX.length()); } - - VersionIdentifier min = null; - if (index > 0) { - min = VersionIdentifier.of(value.substring(0, index)); + if (value.endsWith(BoundaryType.END_EXCLUDING_SUFFIX)) { + isRightExclusive = Boolean.TRUE; + value = value.substring(0, value.length() - BoundaryType.END_EXCLUDING_SUFFIX.length()); + } else if (value.endsWith(BoundaryType.END_INCLUDING_SUFFIX)) { + isRightExclusive = Boolean.FALSE; + value = value.substring(0, value.length() - BoundaryType.END_INCLUDING_SUFFIX.length()); } + VersionIdentifier min = null; VersionIdentifier max = null; - String maxString = value.substring(index + 1); - if (!maxString.isEmpty()) { - max = VersionIdentifier.of(maxString); + int index = value.indexOf(VERSION_SEPARATOR); + if (index < 0) { + min = VersionIdentifier.of(value); + max = min; + } else { + String minString = value.substring(0, index); + if (!minString.isEmpty()) { + min = VersionIdentifier.of(minString); + } + String maxString = value.substring(index + 1); + if (!maxString.isEmpty()) { + max = VersionIdentifier.of(maxString); + } } - return new VersionRange(min, max, leftIsExclusive, rightIsExclusive); - } - - private static String removeAffixes(String value) { - - if (value.startsWith(START_EXCLUDING_PREFIX)) { - value = value.substring(START_EXCLUDING_PREFIX.length()); - } else if (value.startsWith(START_INCLUDING_PREFIX)) { - value = value.substring(START_INCLUDING_PREFIX.length()); + if (isleftExclusive == null) { + isleftExclusive = Boolean.valueOf(min == null); } - if (value.endsWith(END_EXCLUDING_SUFFIX)) { - value = value.substring(0, value.length() - END_EXCLUDING_SUFFIX.length()); - } else if (value.endsWith(END_INCLUDING_SUFFIX)) { - value = value.substring(0, value.length() - END_EXCLUDING_SUFFIX.length()); + if (isRightExclusive == null) { + isRightExclusive = Boolean.valueOf(max == null); } - return value; + return new VersionRange(min, max, BoundaryType.of(isleftExclusive.booleanValue(), isRightExclusive.booleanValue())); } -} +} \ No newline at end of file diff --git a/cli/src/main/java/com/devonfw/tools/ide/version/VersionSegment.java b/cli/src/main/java/com/devonfw/tools/ide/version/VersionSegment.java index 1bb3d01f0..0c84b68e2 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/version/VersionSegment.java +++ b/cli/src/main/java/com/devonfw/tools/ide/version/VersionSegment.java @@ -33,7 +33,6 @@ public class VersionSegment implements VersionObject { * @param separator the {@link #getSeparator() separator}. * @param letters the {@link #getLettersString() letters}. * @param digits the {@link #getDigits() digits}. - * @param pattern the {@link #getPattern() pattern}. */ VersionSegment(String separator, String letters, String digits) { diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/InstallCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/InstallCommandletTest.java new file mode 100644 index 000000000..6633aff01 --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/InstallCommandletTest.java @@ -0,0 +1,112 @@ +package com.devonfw.tools.ide.commandlet; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import com.devonfw.tools.ide.context.AbstractIdeContextTest; +import com.devonfw.tools.ide.context.IdeContext; +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; + +/** + * Integration test of {@link InstallCommandlet}. + */ + +public class InstallCommandletTest extends AbstractIdeContextTest { + + private static WireMockServer server; + + private static Path resourcePath = Paths.get("src/test/resources"); + + @BeforeAll + static void setUp() throws IOException { + + server = new WireMockServer(WireMockConfiguration.wireMockConfig().port(1111)); + server.start(); + } + + @AfterAll + static void tearDown() throws IOException { + + server.shutdownServer(); + } + + private void mockWebServer() throws IOException { + + Path windowsFilePath = resourcePath.resolve("__files").resolve("java-17.0.6-windows-x64.zip"); + String windowsLength = String.valueOf(Files.size(windowsFilePath)); + server.stubFor( + get(urlPathEqualTo("/installTest/windows")).willReturn(aResponse().withHeader("Content-Type", "application/zip") + .withHeader("Content-Length", windowsLength).withStatus(200).withBodyFile("java-17.0.6-windows-x64.zip"))); + + Path linuxFilePath = resourcePath.resolve("__files").resolve("java-17.0.6-linux-x64.tgz"); + String linuxLength = String.valueOf(Files.size(linuxFilePath)); + server.stubFor( + get(urlPathEqualTo("/installTest/linux")).willReturn(aResponse().withHeader("Content-Type", "application/tgz") + .withHeader("Content-Length", linuxLength).withStatus(200).withBodyFile("java-17.0.6-linux-x64.tgz"))); + + server.stubFor( + get(urlPathEqualTo("/installTest/macOS")).willReturn(aResponse().withHeader("Content-Type", "application/tgz") + .withHeader("Content-Length", linuxLength).withStatus(200).withBodyFile("java-17.0.6-linux-x64.tgz"))); + } + + /** + * Test of {@link InstallCommandlet} run, when Installed Version is null. + */ + @Test + public void testInstallCommandletRunWithVersion() throws IOException { + + // arrange + String path = "workspaces/foo-test/my-git-repo"; + IdeContext context = newContext("basic", path, true); + InstallCommandlet install = context.getCommandletManager().getCommandlet(InstallCommandlet.class); + install.tool.setValueAsString("java"); + mockWebServer(); + // act + install.run(); + // assert + assertTestInstall(context); + } + + /** + * Test of {@link InstallCommandlet} run, when Installed Version is set. + */ + @Test + public void testInstallCommandletRunWithVersionAndVersionIdentifier() throws IOException { + + // arrange + String path = "workspaces/foo-test/my-git-repo"; + IdeContext context = newContext("basic", path, true); + InstallCommandlet install = context.getCommandletManager().getCommandlet(InstallCommandlet.class); + install.tool.setValueAsString("java"); + install.version.setValueAsString("17.0.6"); + mockWebServer(); + + // act + install.run(); + // assert + assertTestInstall(context); + } + + private void assertTestInstall(IdeContext context) { + + assertThat(context.getSoftwarePath().resolve("java")).exists(); + assertThat(context.getSoftwarePath().resolve("java/InstallTest.txt")).hasContent("This is a test file."); + assertThat(context.getSoftwarePath().resolve("java/bin/HelloWorld.txt")).hasContent("Hello World!"); + if(context.getSystemInfo().isWindows()){ + assertThat(context.getSoftwarePath().resolve("java/bin/java.cmd")).exists(); + } else if (context.getSystemInfo().isLinux() || context.getSystemInfo().isMac()) { + assertThat(context.getSoftwarePath().resolve("java/bin/java")).exists(); + } + } +} diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/VersionSetCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/VersionSetCommandletTest.java index bc82d1f35..a43227b28 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/commandlet/VersionSetCommandletTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/VersionSetCommandletTest.java @@ -44,6 +44,15 @@ public void testVersionSetCommandletRun() throws IOException { IDE_TOOLS=mvn,eclipse BAR=bar-${SOME} - """); + + TEST_ARGS1=${TEST_ARGS1} settings1 + TEST_ARGS4=${TEST_ARGS4} settings4 + TEST_ARGS5=${TEST_ARGS5} settings5 + TEST_ARGS6=${TEST_ARGS6} settings6 + TEST_ARGS7=${TEST_ARGS7} settings7 + TEST_ARGS8=settings8 + TEST_ARGS9=settings9 + TEST_ARGSb=${TEST_ARGS10} settingsb ${TEST_ARGSa} ${TEST_ARGSb} + TEST_ARGSc=${TEST_ARGSc} settingsc"""); } } diff --git a/cli/src/test/java/com/devonfw/tools/ide/common/TagTest.java b/cli/src/test/java/com/devonfw/tools/ide/common/TagTest.java new file mode 100644 index 000000000..bfa7d1ce0 --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/common/TagTest.java @@ -0,0 +1,151 @@ +package com.devonfw.tools.ide.common; + +import java.util.Collection; +import java.util.Set; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Test of {@link Tag}. + */ +public class TagTest extends Assertions { + + /** + * Test various predefined {@link Tag}s. + */ + @Test + public void testTags() { + + checkTag(Tag.ROOT, "", null, true); + checkTag(Tag.MACHINE_LEARNING, "machine-learning", Tag.ROOT); + checkTag(Tag.ARTIFICIAL_INTELLIGENCE, "artificial-intelligence", Tag.MACHINE_LEARNING); + } + + private void checkTag(Tag tag, String id, Tag parent) { + + checkTag(tag, id, parent, false); + } + + private void checkTag(Tag tag, String id, Tag parent, boolean isAbstract) { + + assertThat(tag.getId()).isEqualTo(id); + assertThat(tag.getParent()).isSameAs(parent); + assertThat(tag.isAbstract()).isEqualTo(isAbstract); + assertThat(tag.toString()).isEqualTo(id); + } + + /** + * Test of {@link Tag#of(String)}. + */ + @Test + public void testOf() { + + checkOf(Tag.ROOT); + checkOf(Tag.MACHINE_LEARNING, "ml", "machinelearning"); + checkOf(Tag.ARTIFICIAL_INTELLIGENCE, "ai", "artificialintelligence"); + } + + private void checkOf(Tag tag, String... synonyms) { + + assertThat(Tag.of(tag.getId())).isSameAs(tag); + for (String synonym : synonyms) { + assertThat(Tag.of(synonym)).isSameAs(tag); + } + } + + /** + * Test of {@link Tag#of(String)} with new tag that do not yet exist. + */ + @Test + public void testOfNew() { + + // arrange + String id = "undefined"; + // act + Tag tag = Tag.of(id); + // assert + checkTag(tag, id, Tag.MISC); + } + + /** + * Test of {@link Tag#of(String)} with new tags that do not yet exist and given parent. + */ + @Test + public void testOfNewWithParent() { + + // arrange + String id = "brandnew"; + // act + Tag tag = Tag.of("ide/" + id); + // assert + checkTag(tag, id, Tag.IDE); + } + + /** + * Test of {@link Tag#getAll()}. + */ + @Test + public void testGetAll() { + + // act + Collection tags = Tag.getAll(); + // assert + assertThat(tags).contains(Tag.ROOT, Tag.ANDROID_STUDIO, Tag.ECLIPSE, Tag.IDE, Tag.VS_CODE); + assertThat(tags.size()).isGreaterThan(150); + } + + /** + * Test of {@link Tag#parseCsv(String)}. + */ + @Test + public void testParseCsv() { + + // arrange + String csv = " c,c++, c# "; // also test trimming + // act + Set tags = Tag.parseCsv(csv); + // assert + assertThat(tags).containsExactlyInAnyOrder(Tag.C, Tag.CPP, Tag.CS); + } + + /** + * Test of {@link Tag#parseCsv(String)} with empty CSV. + */ + @Test + public void testParseCsvEmpty() { + + assertThat(Tag.parseCsv(null)).isEmpty(); + assertThat(Tag.parseCsv("")).isEmpty(); + assertThat(Tag.parseCsv(" ")).isEmpty(); + } + + /** + * Test of {@link Tag#getParent(int)} and {@link Tag#getParentCount()}. + */ + @Test + public void testGetParents() { + + assertThat(Tag.ROOT.getParentCount()).isZero(); + assertThat(Tag.DOCUMENTATION.getParentCount()).isOne(); + assertThat(Tag.DOCUMENTATION.getParent(0)).isSameAs(Tag.ROOT); + assertThat(Tag.ASCII_DOC.getParentCount()).isEqualTo(2); + assertThat(Tag.ASCII_DOC.getParent(0)).isSameAs(Tag.FORMAT); + assertThat(Tag.ASCII_DOC.getParent(1)).isSameAs(Tag.DOCUMENTATION); + } + + /** + * Test of {@link Tag#isAncestorOf(Tag)}. + */ + @Test + public void testIsAncestorOf() { + + assertThat(Tag.QUARKUS.isAncestorOf(Tag.ROOT)).isTrue(); + assertThat(Tag.QUARKUS.isAncestorOf(Tag.DOCUMENTATION)).isFalse(); + assertThat(Tag.QUARKUS.isAncestorOf(Tag.JAVA)).isFalse(); + assertThat(Tag.QUARKUS.isAncestorOf(Tag.QUARKUS)).isFalse(); + boolean includeAdditionalParents = true; + assertThat(Tag.QUARKUS.isAncestorOf(Tag.JAVA, includeAdditionalParents)).isTrue(); + assertThat(Tag.QUARKUS.isAncestorOf(Tag.LANGUAGE, includeAdditionalParents)).isTrue(); + } +} diff --git a/cli/src/test/java/com/devonfw/tools/ide/environment/EnvironmentVariablesTest.java b/cli/src/test/java/com/devonfw/tools/ide/environment/EnvironmentVariablesTest.java new file mode 100644 index 000000000..7f001c490 --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/environment/EnvironmentVariablesTest.java @@ -0,0 +1,60 @@ +package com.devonfw.tools.ide.environment; + +import com.devonfw.tools.ide.context.AbstractIdeContextTest; +import com.devonfw.tools.ide.context.IdeTestContext; +import org.junit.jupiter.api.Test; + +/** + * Test of {@link EnvironmentVariables}. + */ +public class EnvironmentVariablesTest extends AbstractIdeContextTest { + + /** + * Test of {@link EnvironmentVariables#resolve(String, Object)} with self referencing variables. + */ + @Test + public void testProperEvaluationOfVariables() { + + // arrange + String path = "workspaces/foo-test/my-git-repo"; + IdeTestContext context = newContext(PROJECT_BASIC, path, false); + EnvironmentVariables variables = context.getVariables(); + + // act + String TEST_ARGS1 = variables.get("TEST_ARGS1"); + String TEST_ARGS2 = variables.get("TEST_ARGS2"); + String TEST_ARGS3 = variables.get("TEST_ARGS3"); + String TEST_ARGS4 = variables.get("TEST_ARGS4"); + String TEST_ARGS5 = variables.get("TEST_ARGS5"); + String TEST_ARGS6 = variables.get("TEST_ARGS6"); + String TEST_ARGS7 = variables.get("TEST_ARGS7"); + String TEST_ARGS8 = variables.get("TEST_ARGS8"); + String TEST_ARGS9 = variables.get("TEST_ARGS9"); + String TEST_ARGS10 = variables.get("TEST_ARGS10"); + // some more advanced cases + String TEST_ARGSa = variables.get("TEST_ARGSa"); + String TEST_ARGSb = variables.get("TEST_ARGSb"); + String TEST_ARGSc = variables.get("TEST_ARGSc"); + String TEST_ARGSd = variables.get("TEST_ARGSd"); + + // assert + assertThat(TEST_ARGS1).isEqualTo(" user1 settings1 workspace1 conf1"); + assertThat(TEST_ARGS2).isEqualTo(" user2 conf2"); + assertThat(TEST_ARGS3).isEqualTo(" user3 workspace3"); + assertThat(TEST_ARGS4).isEqualTo(" settings4"); + assertThat(TEST_ARGS5).isEqualTo(" settings5 conf5"); + assertThat(TEST_ARGS6).isEqualTo(" settings6 workspace6 conf6"); + + assertThat(TEST_ARGS7).isEqualTo("user7 settings7 workspace7 conf7"); + assertThat(TEST_ARGS8).isEqualTo("settings8 workspace8 conf8"); + assertThat(TEST_ARGS9).isEqualTo("settings9 workspace9"); + assertThat(TEST_ARGS10).isEqualTo("user10 workspace10"); + + assertThat(TEST_ARGSa).isEqualTo(" user1 settings1 workspace1 conf1 user3 workspace3 confa"); + assertThat(TEST_ARGSb) + .isEqualTo("user10 workspace10 settingsb user1 settings1 workspace1 conf1 user3 workspace3 confa userb"); + + assertThat(TEST_ARGSc).isEqualTo(" user1 settings1 workspace1 conf1 userc settingsc confc"); + assertThat(TEST_ARGSd).isEqualTo(" user1 settings1 workspace1 conf1 userd workspaced"); + } +} diff --git a/cli/src/test/java/com/devonfw/tools/ide/io/FileAccessImplTest.java b/cli/src/test/java/com/devonfw/tools/ide/io/FileAccessImplTest.java new file mode 100644 index 000000000..c2f0bae8c --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/io/FileAccessImplTest.java @@ -0,0 +1,472 @@ +package com.devonfw.tools.ide.io; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import com.devonfw.tools.ide.context.AbstractIdeContextTest; +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.context.IdeTestContextMock; + +/** + * Test of {@link FileAccessImpl}. + */ +public class FileAccessImplTest extends AbstractIdeContextTest { + + /** + * Checks if Windows junctions are used. + * + * @param context the {@link IdeContext} to get system info and file access from. + * @param dir the {@link Path} to the directory which is used as temp directory. + * @return {@code true} if Windows junctions are used, {@code false} otherwise. + */ + private boolean windowsJunctionsAreUsed(IdeContext context, Path dir) { + + if (!context.getSystemInfo().isWindows()) { + return false; + } + + Path source = dir.resolve("checkIfWindowsJunctionsAreUsed"); + Path link = dir.resolve("checkIfWindowsJunctionsAreUsedLink"); + context.getFileAccess().mkdirs(source); + try { + Files.createSymbolicLink(link, source); + return false; + } catch (IOException e) { + return true; + } + } + + /** + * Test of {@link FileAccessImpl#symlink(Path, Path, boolean)} with "relative = false". Passing absolute paths as + * source. + */ + @Test + public void testSymlinkAbsolute(@TempDir Path tempDir) { + + // relative links are checked in testRelativeLinksWorkAfterMoving + + // arrange + IdeContext context = IdeTestContextMock.get(); + FileAccess fileAccess = new FileAccessImpl(context); + Path dir = tempDir.resolve("parent"); + createDirs(fileAccess, dir); + boolean readLinks = !windowsJunctionsAreUsed(context, tempDir); + boolean relative = false; + + // act + createSymlinks(fileAccess, dir, relative); + + // assert + assertSymlinksExist(dir); + assertSymlinksWork(dir, readLinks); + } + + /** + * Test of {@link FileAccessImpl#symlink(Path, Path, boolean)} with "relative = false". Passing relative paths as + * source. + */ + @Test + public void testSymlinkAbsolutePassingRelativeSource(@TempDir Path tempDir) { + + // arrange + IdeContext context = IdeTestContextMock.get(); + FileAccess fileAccess = new FileAccessImpl(context); + Path dir = tempDir.resolve("parent"); + createDirs(fileAccess, dir); + boolean readLinks = !windowsJunctionsAreUsed(context, tempDir); + boolean relative = false; + + // act + createSymlinksByPassingRelativeSource(fileAccess, dir, relative); + + // assert + assertSymlinksExist(dir); + assertSymlinksWork(dir, readLinks); + } + + /** + * Test of {@link FileAccessImpl#symlink(Path, Path, boolean)} with "relative = true". But Windows junctions are used + * and therefore the fallback from relative to absolute paths is tested. + */ + @Test + public void testSymlinkAbsoluteAsFallback(@TempDir Path tempDir) { + + // arrange + IdeContext context = IdeTestContextMock.get(); + if (!windowsJunctionsAreUsed(context, tempDir)) { + context.info( + "Can not check the Test: testSymlinkAbsoluteAsFallback since windows junctions are not used and fallback " + + "from relative to absolute paths as link target is not used."); + return; + } + FileAccess fileAccess = new FileAccessImpl(context); + Path dir = tempDir.resolve("parent"); + createDirs(fileAccess, dir); + boolean readLinks = false; // bc windows junctions are used, which can't be read with Files.readSymbolicLink(link); + boolean relative = true; // set to true, such that the fallback to absolute paths is used since junctions are used + + // act + createSymlinks(fileAccess, dir, relative); + + // assert + assertSymlinksExist(dir); + assertSymlinksWork(dir, readLinks); + } + + /** + * Test of {@link FileAccessImpl#symlink(Path, Path, boolean)} with "relative = false". Furthermore, it is tested that + * the links are broken after moving them. + */ + @Test + public void testSymlinkAbsoluteBreakAfterMoving(@TempDir Path tempDir) throws IOException { + + // arrange + IdeContext context = IdeTestContextMock.get(); + FileAccess fileAccess = new FileAccessImpl(context); + Path dir = tempDir.resolve("parent"); + createDirs(fileAccess, dir); + boolean relative = false; + createSymlinks(fileAccess, dir, relative); + boolean readLinks = !windowsJunctionsAreUsed(context, tempDir); + + // act + Path sibling = dir.resolveSibling("parent2"); + fileAccess.move(dir, sibling); + + // assert + assertSymlinksExist(sibling); + assertSymlinksAreBroken(sibling, readLinks); + } + + /** + * Test of {@link FileAccessImpl#symlink(Path, Path, boolean)} with "relative = true". Furthermore, it is tested that + * the links still work after moving them. Passing relative paths as source. + */ + @Test + public void testSymlinkRelativeWorkAfterMovingPassingRelativeSource(@TempDir Path tempDir) { + + // arrange + IdeContext context = IdeTestContextMock.get(); + if (windowsJunctionsAreUsed(context, tempDir)) { + context.info("Can not check the Test: testRelativeLinksWorkAfterMoving since windows junctions are used."); + return; + } + FileAccess fileAccess = new FileAccessImpl(context); + Path dir = tempDir.resolve("parent"); + createDirs(fileAccess, dir); + boolean relative = true; + createSymlinksByPassingRelativeSource(fileAccess, dir, relative); + boolean readLinks = true; // junctions are not used, so links can be read with Files.readSymbolicLink(link); + + // act + Path sibling = dir.resolveSibling("parent2"); + fileAccess.move(dir, sibling); + + // assert + assertSymlinksExist(sibling); + assertSymlinksWork(sibling, readLinks); + } + + /** + * Test of {@link FileAccessImpl#symlink(Path, Path, boolean)} with "relative = true". Furthermore, it is tested that + * the links still work after moving them. + */ + @Test + public void testSymlinkRelativeWorkAfterMoving(@TempDir Path tempDir) { + + // arrange + IdeContext context = IdeTestContextMock.get(); + if (windowsJunctionsAreUsed(context, tempDir)) { + context.info("Can not check the Test: testRelativeLinksWorkAfterMoving since windows junctions are used."); + return; + } + FileAccess fileAccess = new FileAccessImpl(context); + Path dir = tempDir.resolve("parent"); + createDirs(fileAccess, dir); + boolean relative = true; + createSymlinks(fileAccess, dir, relative); + boolean readLinks = true; // junctions are not used, so links can be read with Files.readSymbolicLink(link); + + // act + Path sibling = dir.resolveSibling("parent2"); + fileAccess.move(dir, sibling); + + // assert + assertSymlinksExist(sibling); + assertSymlinksWork(sibling, readLinks); + } + + /** + * Test of {@link FileAccessImpl#symlink(Path, Path, boolean)} when Windows junctions are used and the source is a + * file. + */ + @Test + public void testSymlinkWindowsJunctionsCanNotPointToFiles(@TempDir Path tempDir) throws IOException { + + // arrange + IdeContext context = IdeTestContextMock.get(); + if (!windowsJunctionsAreUsed(context, tempDir)) { + context + .info("Can not check the Test: testWindowsJunctionsCanNotPointToFiles since windows junctions are not used."); + return; + } + Path file = tempDir.resolve("file"); + Files.createFile(file); + FileAccess fileAccess = new FileAccessImpl(context); + + // act & assert + IllegalStateException e1 = assertThrows(IllegalStateException.class, () -> { + fileAccess.symlink(file, tempDir.resolve("linkToFile")); + }); + assertThat(e1).hasMessageContaining("These junctions can only point to directories or other junctions"); + } + + /** + * Test of {@link FileAccessImpl#symlink(Path, Path, boolean)} and whether the source paths are simplified correctly + * by {@link Path#toRealPath(LinkOption...)}. + */ + @Test + public void testSymlinkShortcutPaths(@TempDir Path tempDir) { + + // arrange + IdeContext context = IdeTestContextMock.get(); + FileAccess fileAccess = new FileAccessImpl(context); + Path dir = tempDir.resolve("parent"); + createDirs(fileAccess, dir); + fileAccess.mkdirs(dir.resolve("d3")); + boolean readLinks = !windowsJunctionsAreUsed(context, tempDir); + + // act + fileAccess.symlink(dir.resolve("d3/../d1"), dir.resolve("link1"), false); + fileAccess.symlink(Path.of("d3/../d1"), dir.resolve("link2"), false); + fileAccess.symlink(dir.resolve("d3/../d1"), dir.resolve("link3"), true); + fileAccess.symlink(Path.of("d3/../d1"), dir.resolve("link4"), true); + fileAccess.delete(dir.resolve("d3")); + + // assert + assertSymlinkToRealPath(dir.resolve("link1"), dir.resolve("d1")); + assertSymlinkToRealPath(dir.resolve("link2"), dir.resolve("d1")); + assertSymlinkToRealPath(dir.resolve("link3"), dir.resolve("d1")); + assertSymlinkToRealPath(dir.resolve("link4"), dir.resolve("d1")); + if (readLinks) { + assertSymlinkRead(dir.resolve("link1"), dir.resolve("d1")); + assertSymlinkRead(dir.resolve("link2"), dir.resolve("d1")); + assertSymlinkRead(dir.resolve("link3"), dir.resolve("d1")); + assertSymlinkRead(dir.resolve("link4"), dir.resolve("d1")); + } + } + + private void createDirs(FileAccess fileAccess, Path dir) { + + fileAccess.mkdirs(dir.resolve("d1/d11/d111/d1111")); + fileAccess.mkdirs(dir.resolve("d2/d22/d222")); + } + + /** + * Creates the symlinks with passing relative paths as source. This is used by the tests of + * {@link FileAccessImpl#symlink(Path, Path, boolean)}. + * + * @param fa the {@link FileAccess} to use. + * @param dir the {@link Path} to the directory where the symlinks shall be created. + * @param relative - {@code true} if the symbolic link shall be relative, {@code false} if it shall be absolute. + */ + private void createSymlinksByPassingRelativeSource(FileAccess fa, Path dir, boolean relative) { + + fa.symlink(Path.of("."), dir.resolve("d1/d11/link_to_d1"), relative); + // test if symbolic links or junctions can be overwritten with symlink() + fa.symlink(Path.of(".."), dir.resolve("d1/d11/link_to_d1"), relative); + + fa.symlink(Path.of("."), dir.resolve("d1/d11/link_to_d11"), relative); + fa.symlink(Path.of("d111"), dir.resolve("d1/d11/link_to_d111"), relative); + fa.symlink(Path.of("d111/d1111"), dir.resolve("d1/d11/link_to_d1111"), relative); + fa.symlink(Path.of("../../d1/../d2"), dir.resolve("d1/d11/link_to_d2"), relative); + fa.symlink(Path.of("../../d2/d22"), dir.resolve("d1/d11/link_to_d22"), relative); + fa.symlink(Path.of("../../d2/d22/d222"), dir.resolve("d1/d11/link_to_d222"), relative); + + fa.symlink(Path.of("../../d1/d11/link_to_d1"), dir.resolve("d2/d22/link_to_link_to_d1"), relative); + fa.symlink(Path.of("../d1/d11/link_to_d1"), dir.resolve("d2/another_link_to_link_to_d1"), relative); + fa.symlink(Path.of("d2/another_link_to_link_to_d1"), dir.resolve("link_to_another_link_to_link_to_d1"), relative); + } + + /** + * Creates the symlinks with passing absolute paths as source. This is used by the tests of + * {@link FileAccessImpl#symlink(Path, Path, boolean)}. + * + * @param fa the {@link FileAccess} to use. + * @param dir the {@link Path} to the directory where the symlinks shall be created. + * @param relative - {@code true} if the symbolic link shall be relative, {@code false} if it shall be absolute. + */ + private void createSymlinks(FileAccess fa, Path dir, boolean relative) { + + fa.symlink(dir.resolve("d1/d11"), dir.resolve("d1/d11/link_to_d1"), relative); + // test if symbolic links or junctions can be overwritten with symlink() + fa.symlink(dir.resolve("d1"), dir.resolve("d1/d11/link_to_d1"), relative); + + fa.symlink(dir.resolve("d1/d11"), dir.resolve("d1/d11/link_to_d11"), relative); + fa.symlink(dir.resolve("d1/d11/d111"), dir.resolve("d1/d11/link_to_d111"), relative); + fa.symlink(dir.resolve("d1/d11/d111/d1111"), dir.resolve("d1/d11/link_to_d1111"), relative); + fa.symlink(dir.resolve("d1/../d2"), dir.resolve("d1/d11/link_to_d2"), relative); + fa.symlink(dir.resolve("d2/d22"), dir.resolve("d1/d11/link_to_d22"), relative); + fa.symlink(dir.resolve("d2/d22/d222"), dir.resolve("d1/d11/link_to_d222"), relative); + + fa.symlink(dir.resolve("d1/d11/link_to_d1"), dir.resolve("d2/d22/link_to_link_to_d1"), relative); + fa.symlink(dir.resolve("d1/d11/link_to_d1"), dir.resolve("d2/another_link_to_link_to_d1"), relative); + fa.symlink(dir.resolve("d2/another_link_to_link_to_d1"), dir.resolve("link_to_another_link_to_link_to_d1"), + relative); + } + + /** + * Checks if the symlinks exist. This is used by the tests of {@link FileAccessImpl#symlink(Path, Path, boolean)}. + * + * @param dir the {@link Path} to the directory where the symlinks are expected. + */ + private void assertSymlinksExist(Path dir) { + + assertThat(dir.resolve("d1/d11/link_to_d1")).existsNoFollowLinks(); + assertThat(dir.resolve("d1/d11/link_to_d11")).existsNoFollowLinks(); + assertThat(dir.resolve("d1/d11/link_to_d111")).existsNoFollowLinks(); + assertThat(dir.resolve("d1/d11/link_to_d1111")).existsNoFollowLinks(); + assertThat(dir.resolve("d1/d11/link_to_d2")).existsNoFollowLinks(); + assertThat(dir.resolve("d1/d11/link_to_d22")).existsNoFollowLinks(); + assertThat(dir.resolve("d1/d11/link_to_d222")).existsNoFollowLinks(); + assertThat(dir.resolve("d2/d22/link_to_link_to_d1")).existsNoFollowLinks(); + assertThat(dir.resolve("d2/another_link_to_link_to_d1")).existsNoFollowLinks(); + assertThat(dir.resolve("link_to_another_link_to_link_to_d1")).existsNoFollowLinks(); + } + + /** + * Checks if the symlinks are broken. This is used by the tests of + * {@link FileAccessImpl#symlink(Path, Path, boolean)}. + * + * @param dir the {@link Path} to the directory where the symlinks are expected. + * @param readLinks - {@code true} if the symbolic link shall be read with {@link Files#readSymbolicLink(Path)}, this + * does not work for Windows junctions. + */ + private void assertSymlinksAreBroken(Path dir, boolean readLinks) throws IOException { + + assertSymlinkIsBroken(dir.resolve("d1/d11/link_to_d1"), readLinks); + assertSymlinkIsBroken(dir.resolve("d1/d11/link_to_d11"), readLinks); + assertSymlinkIsBroken(dir.resolve("d1/d11/link_to_d111"), readLinks); + assertSymlinkIsBroken(dir.resolve("d1/d11/link_to_d1111"), readLinks); + assertSymlinkIsBroken(dir.resolve("d1/d11/link_to_d2"), readLinks); + assertSymlinkIsBroken(dir.resolve("d1/d11/link_to_d22"), readLinks); + assertSymlinkIsBroken(dir.resolve("d1/d11/link_to_d222"), readLinks); + assertSymlinkIsBroken(dir.resolve("d2/d22/link_to_link_to_d1"), readLinks); + assertSymlinkIsBroken(dir.resolve("d2/another_link_to_link_to_d1"), readLinks); + assertSymlinkIsBroken(dir.resolve("link_to_another_link_to_link_to_d1"), readLinks); + } + + /** + * Checks if the symlink is broken. This is used by the tests of {@link FileAccessImpl#symlink(Path, Path, boolean)}. + * + * @param link the {@link Path} to the link. + * @param readLinks - {@code true} if the symbolic link shall be read with {@link Files#readSymbolicLink(Path)}, this + * does not work for Windows junctions. + */ + private void assertSymlinkIsBroken(Path link, boolean readLinks) throws IOException { + + try { + Path realPath = link.toRealPath(); + if (Files.exists(realPath)) { + fail("The link target " + realPath + " (from toRealPath) should not exist"); + } + } catch (IOException e) { // toRealPath() throws exception for junctions + assertThat(e).isInstanceOf(NoSuchFileException.class); + } + if (readLinks) { + Path readPath = Files.readSymbolicLink(link); + if (Files.exists(readPath)) { + fail("The link target " + readPath + " (from readSymbolicLink) should not exist"); + } + } + } + + /** + * Checks if the symlinks work. This is used by the tests of {@link FileAccessImpl#symlink(Path, Path, boolean)}. + * + * @param dir the {@link Path} to the directory where the symlinks are expected. + * @param readLinks - {@code true} if the symbolic link shall be read with {@link Files#readSymbolicLink(Path)}, this + * does not work for Windows junctions. + */ + private void assertSymlinksWork(Path dir, boolean readLinks) { + + assertSymlinkToRealPath(dir.resolve("d1/d11/link_to_d1"), dir.resolve("d1")); + assertSymlinkToRealPath(dir.resolve("d1/d11/link_to_d11"), dir.resolve("d1/d11")); + assertSymlinkToRealPath(dir.resolve("d1/d11/link_to_d111"), dir.resolve("d1/d11/d111")); + assertSymlinkToRealPath(dir.resolve("d1/d11/link_to_d1111"), dir.resolve("d1/d11/d111/d1111")); + assertSymlinkToRealPath(dir.resolve("d1/d11/link_to_d2"), dir.resolve("d2")); + assertSymlinkToRealPath(dir.resolve("d1/d11/link_to_d22"), dir.resolve("d2/d22")); + assertSymlinkToRealPath(dir.resolve("d1/d11/link_to_d222"), dir.resolve("d2/d22/d222")); + assertSymlinkToRealPath(dir.resolve("d2/d22/link_to_link_to_d1"), dir.resolve("d1")); + assertSymlinkToRealPath(dir.resolve("d2/another_link_to_link_to_d1"), dir.resolve("d1")); + assertSymlinkToRealPath(dir.resolve("link_to_another_link_to_link_to_d1"), dir.resolve("d1")); + + if (readLinks) { + assertSymlinkRead(dir.resolve("d1/d11/link_to_d1"), dir.resolve("d1")); + assertSymlinkRead(dir.resolve("d1/d11/link_to_d11"), dir.resolve("d1/d11")); + assertSymlinkRead(dir.resolve("d1/d11/link_to_d111"), dir.resolve("d1/d11/d111")); + assertSymlinkRead(dir.resolve("d1/d11/link_to_d1111"), dir.resolve("d1/d11/d111/d1111")); + assertSymlinkRead(dir.resolve("d1/d11/link_to_d2"), dir.resolve("d2")); + assertSymlinkRead(dir.resolve("d1/d11/link_to_d22"), dir.resolve("d2/d22")); + assertSymlinkRead(dir.resolve("d1/d11/link_to_d222"), dir.resolve("d2/d22/d222")); + assertSymlinkRead(dir.resolve("d2/d22/link_to_link_to_d1"), dir.resolve("d1/d11/link_to_d1")); + assertSymlinkRead(dir.resolve("d2/another_link_to_link_to_d1"), dir.resolve("d1/d11/link_to_d1")); + assertSymlinkRead(dir.resolve("link_to_another_link_to_link_to_d1"), + dir.resolve("d2/another_link_to_link_to_d1")); + } + } + + /** + * Checks if the symlink works by checking {@link Path#toRealPath(LinkOption...)}} against the {@code trueTarget}. . + * This is used by the tests of {@link FileAccessImpl#symlink(Path, Path, boolean)}. + * + * @param link the {@link Path} to the link. + * @param trueTarget the {@link Path} to the true target. + */ + private void assertSymlinkToRealPath(Path link, Path trueTarget) { + + Path realPath = null; + try { + realPath = link.toRealPath(); + } catch (IOException e) { + fail("In method assertSymlinkToRealPath() could not call toRealPath() on link " + link, e); + } + assertThat(realPath).exists(); + assertThat(realPath).existsNoFollowLinks(); + assertThat(realPath).isEqualTo(trueTarget); + } + + /** + * Checks if the symlink works by checking {@link Files#readSymbolicLink(Path)} against the {@code trueTarget}. This + * is used by the tests of {@link FileAccessImpl#symlink(Path, Path, boolean)}. Only call this method if junctions are + * not used, since junctions can not be read with {@link Files#readSymbolicLink(Path)}. + * + * @param link the {@link Path} to the link. + * @param trueTarget the {@link Path} to the true target. + */ + private void assertSymlinkRead(Path link, Path trueTarget) { + + Path readPath = null; + try { + readPath = Files.readSymbolicLink(link); + } catch (IOException e) { + fail("In method assertSymlinkRead() could not call readSymbolicLink() on link " + link, e); + } + assertThat(link.resolveSibling(readPath)).existsNoFollowLinks(); + assertThat(link.resolveSibling(readPath)).exists(); + try { + assertThat(link.resolveSibling(readPath).toRealPath(LinkOption.NOFOLLOW_LINKS)).isEqualTo(trueTarget); + } catch (IOException e) { + fail("In method assertSymlinkRead() could not call toRealPath() on link.resolveSibling(readPath) for link " + link + + " and readPath " + readPath, e); + } + } +} diff --git a/cli/src/test/java/com/devonfw/tools/ide/version/VersionIdentifierTest.java b/cli/src/test/java/com/devonfw/tools/ide/version/VersionIdentifierTest.java index 9256e6e7d..20fa0ed8d 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/version/VersionIdentifierTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/version/VersionIdentifierTest.java @@ -93,7 +93,7 @@ public void testIllegal() { for (String version : illegalVersions) { try { VersionIdentifier.of(version); - fail("Illegal verion '" + version + "' did not cause an exception!"); + fail("Illegal version '" + version + "' did not cause an exception!"); } catch (Exception e) { assertThat(e).isInstanceOf(IllegalArgumentException.class); assertThat(e).hasMessageContaining(version); diff --git a/cli/src/test/java/com/devonfw/tools/ide/version/VersionRangeTest.java b/cli/src/test/java/com/devonfw/tools/ide/version/VersionRangeTest.java index 0a305f8a6..6ebc37195 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/version/VersionRangeTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/version/VersionRangeTest.java @@ -8,16 +8,14 @@ */ public class VersionRangeTest extends Assertions { - /** - * Test of {@link VersionRange#of(String)}. - */ + /** Test of {@link VersionRange#of(String)}. */ @Test public void testOf() { // arrange - String v1String = "1.2>3"; - String v2String = "1>)"; - String v3String = "(1.2>3.4]"; + String v1String = "1.2,3"; + String v2String = "1,)"; + String v3String = "(1.2,3.4]"; // act VersionRange v1 = VersionRange.of(v1String); @@ -28,60 +26,56 @@ public void testOf() { // v1 assertThat(v1.getMin()).isEqualTo(VersionIdentifier.of("1.2")); assertThat(v1.getMax()).isEqualTo(VersionIdentifier.of("3")); - assertThat(v1.isLeftExclusive()).isFalse(); - assertThat(v1.isRightExclusive()).isFalse(); + assertThat(v1.getBoundaryType().isLeftExclusive()).isFalse(); + assertThat(v1.getBoundaryType().isRightExclusive()).isFalse(); // v2 assertThat(v2.getMin()).isEqualTo(VersionIdentifier.of("1")); assertThat(v2.getMax()).isEqualTo(null); - assertThat(v2.isLeftExclusive()).isFalse(); - assertThat(v2.isRightExclusive()).isTrue(); + assertThat(v2.getBoundaryType().isLeftExclusive()).isFalse(); + assertThat(v2.getBoundaryType().isRightExclusive()).isTrue(); // v3 assertThat(v3.getMin()).isEqualTo(VersionIdentifier.of("1.2")); assertThat(v3.getMax()).isEqualTo(VersionIdentifier.of("3.4")); - assertThat(v3.isLeftExclusive()).isTrue(); - assertThat(v3.isRightExclusive()).isFalse(); + assertThat(v3.getBoundaryType().isLeftExclusive()).isTrue(); + assertThat(v3.getBoundaryType().isRightExclusive()).isFalse(); } - /** - * Test of {@link VersionRange#toString()}. - */ + /** Test of {@link VersionRange#toString()}. */ @Test public void testToString() { - assertThat(VersionRange.of("1.2>3").toString()).isEqualTo("[1.2>3]"); - assertThat(VersionRange.of("1>)").toString()).isEqualTo("[1>)"); - assertThat(VersionRange.of("(1.2>3.4]").toString()).isEqualTo("(1.2>3.4]"); + assertThat(VersionRange.of("1.2,3").toString()).isEqualTo("[1.2,3]"); + assertThat(VersionRange.of("1,)").toString()).isEqualTo("[1,)"); + assertThat(VersionRange.of("(1.2,3.4]").toString()).isEqualTo("(1.2,3.4]"); + assertThat(VersionRange.of(",").toString()).isEqualTo("(,)"); } - /** - * Test of {@link VersionRange#equals(Object)}. - */ + /** Test of {@link VersionRange#equals(Object)}. */ @Test public void testEquals() { // assert // equals - assertThat(VersionRange.of("1.2>")).isEqualTo(VersionRange.of("1.2>")); - assertThat(VersionRange.of("(1.2>")).isEqualTo(VersionRange.of("(1.2>)")); - assertThat(VersionRange.of("1.2>3")).isEqualTo(VersionRange.of("1.2>3")); - assertThat(VersionRange.of("[1.2>3")).isEqualTo(VersionRange.of("1.2>3]")); - assertThat(VersionRange.of(">3)")).isEqualTo(VersionRange.of(">3)")); - assertThat(VersionRange.of(">")).isEqualTo(VersionRange.of(">")); - assertThat(VersionRange.of("[>)")).isEqualTo(VersionRange.of("(>]")); - assertThat(VersionRange.of("8u302b08>11.0.14_9")).isEqualTo(VersionRange.of("8u302b08>11.0.14_9")); + assertThat(VersionRange.of("1.2,")).isEqualTo(VersionRange.of("1.2,")); + assertThat(VersionRange.of("(1.2,")).isEqualTo(VersionRange.of("(1.2,)")); + assertThat(VersionRange.of("1.2,3")).isEqualTo(VersionRange.of("1.2,3")); + assertThat(VersionRange.of("[1.2,3")).isEqualTo(VersionRange.of("1.2,3]")); + assertThat(VersionRange.of(",3)")).isEqualTo(VersionRange.of(",3)")); + assertThat(VersionRange.of(",")).isEqualTo(VersionRange.of("(,)")); + assertThat(VersionRange.of("8u302b08,11.0.14_9")).isEqualTo(VersionRange.of("8u302b08,11.0.14_9")); // not equals - assertThat(VersionRange.of("1>")).isNotEqualTo(null); - assertThat(VersionRange.of("1.2>")).isNotEqualTo(VersionRange.of("1>")); - assertThat(VersionRange.of("1.2>3")).isNotEqualTo(VersionRange.of("1.2>")); - assertThat(VersionRange.of("(1.2>3")).isNotEqualTo(VersionRange.of("1.2.3>")); - assertThat(VersionRange.of("1.2>3")).isNotEqualTo(VersionRange.of(">3")); - assertThat(VersionRange.of("[1.2>")).isNotEqualTo(VersionRange.of("[1.2>3")); - assertThat(VersionRange.of(">3")).isNotEqualTo(VersionRange.of("1.2>3")); - assertThat(VersionRange.of(">3")).isNotEqualTo(VersionRange.of(">")); - assertThat(VersionRange.of(">")).isNotEqualTo(VersionRange.of(">3")); - assertThat(VersionRange.of("8u302b08>11.0.14_9")).isNotEqualTo(VersionRange.of("(8u302b08>11.0.14_9)")); - assertThat(VersionRange.of("8u302b08>11.0.14_9")).isNotEqualTo(VersionRange.of("8u302b08>11.0.15_9")); - assertThat(VersionRange.of("8u302b08>11.0.14_9")).isNotEqualTo(VersionRange.of("8u302b08>11.0.14_0")); + assertThat(VersionRange.of("1,")).isNotEqualTo(null); + assertThat(VersionRange.of("1.2,")).isNotEqualTo(VersionRange.of("1,")); + assertThat(VersionRange.of("1.2,3")).isNotEqualTo(VersionRange.of("1.2,")); + assertThat(VersionRange.of("(1.2,3")).isNotEqualTo(VersionRange.of("1.2.3,")); + assertThat(VersionRange.of("1.2,3")).isNotEqualTo(VersionRange.of(",3")); + assertThat(VersionRange.of("[1.2,")).isNotEqualTo(VersionRange.of("[1.2,3")); + assertThat(VersionRange.of(",3")).isNotEqualTo(VersionRange.of("1.2,3")); + assertThat(VersionRange.of(",3")).isNotEqualTo(VersionRange.of(",")); + assertThat(VersionRange.of(",")).isNotEqualTo(VersionRange.of(",3")); + assertThat(VersionRange.of("8u302b08,11.0.14_9")).isNotEqualTo(VersionRange.of("(8u302b08,11.0.14_9)")); + assertThat(VersionRange.of("8u302b08,11.0.14_9")).isNotEqualTo(VersionRange.of("8u302b08,11.0.15_9")); + assertThat(VersionRange.of("8u302b08,11.0.14_9")).isNotEqualTo(VersionRange.of("8u302b08,11.0.14_0")); } /** @@ -92,13 +86,13 @@ public void testEquals() { public void testContains() { // assert - assertThat(VersionRange.of("1.2>3.4").contains(VersionIdentifier.of("1.2"))).isTrue(); - assertThat(VersionRange.of("1.2>3.4").contains(VersionIdentifier.of("2"))).isTrue(); - assertThat(VersionRange.of("1.2>3.4").contains(VersionIdentifier.of("3.4"))).isTrue(); + assertThat(VersionRange.of("1.2,3.4").contains(VersionIdentifier.of("1.2"))).isTrue(); + assertThat(VersionRange.of("1.2,3.4").contains(VersionIdentifier.of("2"))).isTrue(); + assertThat(VersionRange.of("1.2,3.4").contains(VersionIdentifier.of("3.4"))).isTrue(); - assertThat(VersionRange.of("(1.2>3.4)").contains(VersionIdentifier.of("1.2.1"))).isTrue(); - assertThat(VersionRange.of("(1.2>3.4)").contains(VersionIdentifier.of("2"))).isTrue(); - assertThat(VersionRange.of("(1.2>3.4)").contains(VersionIdentifier.of("3.3.9"))).isTrue(); + assertThat(VersionRange.of("(1.2,3.4)").contains(VersionIdentifier.of("1.2.1"))).isTrue(); + assertThat(VersionRange.of("(1.2,3.4)").contains(VersionIdentifier.of("2"))).isTrue(); + assertThat(VersionRange.of("(1.2,3.4)").contains(VersionIdentifier.of("3.3.9"))).isTrue(); } /** @@ -109,44 +103,60 @@ public void testContains() { public void testNotContains() { // assert - assertThat(VersionRange.of("1.2>3.4").contains(VersionIdentifier.of("1.1"))).isFalse(); - assertThat(VersionRange.of("1.2>3.4").contains(VersionIdentifier.of("3.4.1"))).isFalse(); + assertThat(VersionRange.of("1.2,3.4").contains(VersionIdentifier.of("1.1"))).isFalse(); + assertThat(VersionRange.of("1.2,3.4").contains(VersionIdentifier.of("3.4.1"))).isFalse(); - assertThat(VersionRange.of("(1.2>3.4)").contains(VersionIdentifier.of("1.2"))).isFalse(); - assertThat(VersionRange.of("(1.2>3.4)").contains(VersionIdentifier.of("3.4"))).isFalse(); + assertThat(VersionRange.of("(1.2,3.4)").contains(VersionIdentifier.of("1.2"))).isFalse(); + assertThat(VersionRange.of("(1.2,3.4)").contains(VersionIdentifier.of("3.4"))).isFalse(); } - /** - * Test of {@link VersionRange#compareTo(VersionRange)} and testing if versions are compared to be the same. - */ + /** Test of {@link VersionRange#compareTo(VersionRange)} and testing if versions are compared to be the same. */ @Test public void testCompareToIsSame() { // assert - assertThat(VersionRange.of("1.2>3").compareTo(VersionRange.of("1.2>3"))).isEqualTo(0); - assertThat(VersionRange.of("(1.2>3").compareTo(VersionRange.of("(1.2>3"))).isEqualTo(0); - assertThat(VersionRange.of("[1.2>3]").compareTo(VersionRange.of("[1.2>4)"))).isEqualTo(0); + assertThat(VersionRange.of("1.2,3").compareTo(VersionRange.of("1.2,3"))).isEqualTo(0); + assertThat(VersionRange.of("(1.2,3").compareTo(VersionRange.of("(1.2,3"))).isEqualTo(0); + assertThat(VersionRange.of("[1.2,3]").compareTo(VersionRange.of("[1.2,4)"))).isEqualTo(0); } - /** - * Test of {@link VersionRange#compareTo(VersionRange)} and testing if first version is smaller than second. - */ + /** Test of {@link VersionRange#compareTo(VersionRange)} and testing if first version is smaller than second. */ @Test public void testCompareToIsSmaller() { // assert - assertThat(VersionRange.of("1.1.2>3").compareTo(VersionRange.of("1.2>3"))).isEqualTo(-1); - assertThat(VersionRange.of("[1.2>3").compareTo(VersionRange.of("(1.2>4"))).isEqualTo(-1); + assertThat(VersionRange.of("1.1.2,3").compareTo(VersionRange.of("1.2,3"))).isEqualTo(-1); + assertThat(VersionRange.of("[1.2,3").compareTo(VersionRange.of("(1.2,4"))).isEqualTo(-1); } - /** - * Test of {@link VersionRange#compareTo(VersionRange)} and testing if first version is larger than second. - */ + /** Test of {@link VersionRange#compareTo(VersionRange)} and testing if first version is larger than second. */ @Test public void testCompareToIsLarger() { // assert - assertThat(VersionRange.of("1.2.1>3").compareTo(VersionRange.of("1.2>3"))).isEqualTo(1); - assertThat(VersionRange.of("(1.2>3").compareTo(VersionRange.of("1.2>4"))).isEqualTo(1); + assertThat(VersionRange.of("1.2.1,3").compareTo(VersionRange.of("1.2,3"))).isEqualTo(1); + assertThat(VersionRange.of("(1.2,3").compareTo(VersionRange.of("1.2,4"))).isEqualTo(1); + } + + /** Test of {@link VersionRange#of(String)} with illegal syntax. */ + @Test + public void testIllegalSyntax() { + + checkIllegalRange("[,)"); + checkIllegalRange("(,]"); + checkIllegalRange("[,]"); + checkIllegalRange("[,1.0)"); + checkIllegalRange("(1.0,]"); + checkIllegalRange("(1.1,1.0)"); + } + + private void checkIllegalRange(String range) { + + try { + VersionRange.of(range); + failBecauseExceptionWasNotThrown(IllegalArgumentException.class); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage()).isEqualTo(range); + } } -} +} \ No newline at end of file diff --git a/cli/src/test/resources/__files/java-17.0.6-linux-x64.tgz b/cli/src/test/resources/__files/java-17.0.6-linux-x64.tgz new file mode 100644 index 000000000..9a592c991 Binary files /dev/null and b/cli/src/test/resources/__files/java-17.0.6-linux-x64.tgz differ diff --git a/cli/src/test/resources/__files/java-17.0.6-windows-x64.zip b/cli/src/test/resources/__files/java-17.0.6-windows-x64.zip new file mode 100644 index 000000000..eb4f3482e Binary files /dev/null and b/cli/src/test/resources/__files/java-17.0.6-windows-x64.zip differ diff --git a/cli/src/test/resources/ide-projects/_ide/urls/java/java/17.0.6/linux_x64.sha256 b/cli/src/test/resources/ide-projects/_ide/urls/java/java/17.0.6/linux_x64.sha256 new file mode 100644 index 000000000..438dd2a03 --- /dev/null +++ b/cli/src/test/resources/ide-projects/_ide/urls/java/java/17.0.6/linux_x64.sha256 @@ -0,0 +1 @@ +c2de7dfbd9f8faaa21b4cdd8518f826dd558c9ab24a0616b3ed28437a674a97b \ No newline at end of file diff --git a/cli/src/test/resources/ide-projects/_ide/urls/java/java/17.0.6/linux_x64.urls b/cli/src/test/resources/ide-projects/_ide/urls/java/java/17.0.6/linux_x64.urls new file mode 100644 index 000000000..42e8cf6cc --- /dev/null +++ b/cli/src/test/resources/ide-projects/_ide/urls/java/java/17.0.6/linux_x64.urls @@ -0,0 +1 @@ +http://localhost:1111/installTest/linux \ No newline at end of file diff --git a/cli/src/test/resources/ide-projects/_ide/urls/java/java/17.0.6/mac_x64.sha256 b/cli/src/test/resources/ide-projects/_ide/urls/java/java/17.0.6/mac_x64.sha256 new file mode 100644 index 000000000..438dd2a03 --- /dev/null +++ b/cli/src/test/resources/ide-projects/_ide/urls/java/java/17.0.6/mac_x64.sha256 @@ -0,0 +1 @@ +c2de7dfbd9f8faaa21b4cdd8518f826dd558c9ab24a0616b3ed28437a674a97b \ No newline at end of file diff --git a/cli/src/test/resources/ide-projects/_ide/urls/java/java/17.0.6/mac_x64.urls b/cli/src/test/resources/ide-projects/_ide/urls/java/java/17.0.6/mac_x64.urls new file mode 100644 index 000000000..384fe79a4 --- /dev/null +++ b/cli/src/test/resources/ide-projects/_ide/urls/java/java/17.0.6/mac_x64.urls @@ -0,0 +1 @@ +http://localhost:1111/installTest/macOS \ No newline at end of file diff --git a/cli/src/test/resources/ide-projects/_ide/urls/java/java/17.0.6/status.json b/cli/src/test/resources/ide-projects/_ide/urls/java/java/17.0.6/status.json new file mode 100644 index 000000000..b58452d90 --- /dev/null +++ b/cli/src/test/resources/ide-projects/_ide/urls/java/java/17.0.6/status.json @@ -0,0 +1,20 @@ +{ + "manual" : true, + "urls" : { + "-680270697" : { + "success" : { + "timestamp" : "2023-04-28T16:27:32.819394600Z" + } + }, + "-896197542" : { + "success" : { + "timestamp" : "2023-04-28T16:27:47.658175400Z" + } + }, + "-310367019" : { + "success" : { + "timestamp" : "2023-04-28T16:28:02.221367500Z" + } + } + } +} \ No newline at end of file diff --git a/cli/src/test/resources/ide-projects/_ide/urls/java/java/17.0.6/windows_x64.urls b/cli/src/test/resources/ide-projects/_ide/urls/java/java/17.0.6/windows_x64.urls new file mode 100644 index 000000000..93010df08 --- /dev/null +++ b/cli/src/test/resources/ide-projects/_ide/urls/java/java/17.0.6/windows_x64.urls @@ -0,0 +1 @@ +http://localhost:1111/installTest/windows \ No newline at end of file diff --git a/cli/src/test/resources/ide-projects/_ide/urls/java/java/17.0.6/windows_x64.urls.sha256 b/cli/src/test/resources/ide-projects/_ide/urls/java/java/17.0.6/windows_x64.urls.sha256 new file mode 100644 index 000000000..fe3cecaad --- /dev/null +++ b/cli/src/test/resources/ide-projects/_ide/urls/java/java/17.0.6/windows_x64.urls.sha256 @@ -0,0 +1 @@ +aa64bee5f7ba56fbbd60d766f3a652600f81571ae5e996804694c69bf731af8b \ No newline at end of file diff --git a/cli/src/test/resources/ide-projects/basic/conf/ide.properties b/cli/src/test/resources/ide-projects/basic/conf/ide.properties index 7b2a83438..224a2cf25 100644 --- a/cli/src/test/resources/ide-projects/basic/conf/ide.properties +++ b/cli/src/test/resources/ide-projects/basic/conf/ide.properties @@ -4,4 +4,13 @@ M2_REPO=~/.m2/repository -SOME=some-${UNDEFINED} \ No newline at end of file +SOME=some-${UNDEFINED} + +TEST_ARGS1=${TEST_ARGS1} conf1 +TEST_ARGS2=${TEST_ARGS2} conf2 +TEST_ARGS5=${TEST_ARGS5} conf5 +TEST_ARGS6=${TEST_ARGS6} conf6 +TEST_ARGS7=${TEST_ARGS7} conf7 +TEST_ARGS8=${TEST_ARGS8} conf8 +TEST_ARGSa=${TEST_ARGS1} ${TEST_ARGS3} confa +TEST_ARGSc=${TEST_ARGSc} confc \ No newline at end of file diff --git a/cli/src/test/resources/ide-projects/basic/home/.ide/ide.properties b/cli/src/test/resources/ide-projects/basic/home/.ide/ide.properties index a61d5066f..ec0c3e7f6 100644 --- a/cli/src/test/resources/ide-projects/basic/home/.ide/ide.properties +++ b/cli/src/test/resources/ide-projects/basic/home/.ide/ide.properties @@ -3,4 +3,13 @@ #******************************************************************************** DOCKER_EDITION=docker -FOO=foo-${BAR} \ No newline at end of file +FOO=foo-${BAR} + +TEST_ARGS1=${TEST_ARGS1} user1 +TEST_ARGS2=${TEST_ARGS2} user2 +TEST_ARGS3=${TEST_ARGS3} user3 +TEST_ARGS7=user7 +TEST_ARGS10=user10 +TEST_ARGSb=userb +TEST_ARGSc=${TEST_ARGS1} userc +TEST_ARGSd=${TEST_ARGS1} userd \ No newline at end of file diff --git a/cli/src/test/resources/ide-projects/basic/settings/ide.properties b/cli/src/test/resources/ide-projects/basic/settings/ide.properties index bfa447f6a..c80f1e604 100644 --- a/cli/src/test/resources/ide-projects/basic/settings/ide.properties +++ b/cli/src/test/resources/ide-projects/basic/settings/ide.properties @@ -9,4 +9,14 @@ INTELLIJ_EDITION=ultimate IDE_TOOLS=mvn,eclipse -BAR=bar-${SOME} \ No newline at end of file +BAR=bar-${SOME} + +TEST_ARGS1=${TEST_ARGS1} settings1 +TEST_ARGS4=${TEST_ARGS4} settings4 +TEST_ARGS5=${TEST_ARGS5} settings5 +TEST_ARGS6=${TEST_ARGS6} settings6 +TEST_ARGS7=${TEST_ARGS7} settings7 +TEST_ARGS8=settings8 +TEST_ARGS9=settings9 +TEST_ARGSb=${TEST_ARGS10} settingsb ${TEST_ARGSa} ${TEST_ARGSb} +TEST_ARGSc=${TEST_ARGSc} settingsc \ No newline at end of file diff --git a/cli/src/test/resources/ide-projects/basic/workspaces/foo-test/ide.properties b/cli/src/test/resources/ide-projects/basic/workspaces/foo-test/ide.properties new file mode 100644 index 000000000..fefd6bede --- /dev/null +++ b/cli/src/test/resources/ide-projects/basic/workspaces/foo-test/ide.properties @@ -0,0 +1,12 @@ +#******************************************************************************** +# Type of {@link EnvironmentVariables} from the +# {@link com.devonfw.tools.ide.context.IdeContext#getWorkspacePath() workspace directory}. +#******************************************************************************** +TEST_ARGS1=${TEST_ARGS1} workspace1 +TEST_ARGS3=${TEST_ARGS3} workspace3 +TEST_ARGS6=${TEST_ARGS6} workspace6 +TEST_ARGS7=${TEST_ARGS7} workspace7 +TEST_ARGS8=${TEST_ARGS8} workspace8 +TEST_ARGS9=${TEST_ARGS9} workspace9 +TEST_ARGS10=${TEST_ARGS10} workspace10 +TEST_ARGSd=${TEST_ARGSd} workspaced \ No newline at end of file diff --git a/documentation/IDEasy-contribution-rules-and-guidelines.asciidoc b/documentation/IDEasy-contribution-rules-and-guidelines.asciidoc new file mode 100644 index 000000000..0e5d26b24 --- /dev/null +++ b/documentation/IDEasy-contribution-rules-and-guidelines.asciidoc @@ -0,0 +1,110 @@ +:toc: macro +toc::[] + +== Project Board +The IDEasy *Project Board* with its Columns should be used as followed: + +* *New*: [.underline]#Issues# that are newly created and have yet to be +refined +* *Backlog*: [.underline]#Issues# that are refined but not yet being +worked on +* *Research*: [.underline]#Issues# that are blocked or need complex +research/analysis to make progress. Typically, these are issues that +somebody tried to solve but it turned out to be hard to make progress +and find a solution. +* *In Progress*: [.underline]#Issues# that are currently being worked +on, they must be assigned to the person (or people) working on it. You can +see on the board if there is a pull-request linked to it. If not, the +developer is still working on the story “in the dark”. Otherwise, there +is already a solution implemented. The PR may be in draft state, +otherwise the PR should be in one of the following two columns. +* *Team Review*: [.underline]#Pull Request# that is to be or currently +under review by a member of the dev team. The reviewer is the assignee +of the PR. +* *In Review*: [.underline]#Pull Request# that is to be or currently +under review by a final reviewer that should also be the assignee of the +PR. Typically the final reviewer is the Project Owner (currently +hohwille) but it may also be done by team members with longer experience +in the project. +* *Done*: [.underline]#Issues# and [.underline]#Pull Request# that have +been completed and merged. + +To better organize the board and avoid overload, only pull request are allowed in the review columns. +Issues remain `in progress` until completed via merge of PR. + +General conventions for contributions to devonfw can be found +https://github.com/devonfw/.github/blob/master/CONTRIBUTING.asciidoc#code-changes[here]. +The following conventions are added on top by the IDEasy team from the +learnings & retros for our best way of working together: + +== Pull Request + +* The title starts with a hashtag and the corresponding issue number (in +case there is no issue to the PR, either create one or use a fitting +epic) +* The title describes what the PR contains, if an issue is fully +completed with the PR, the issue title could be used for example. +* The description starts with a link to the related issue, if this PR +finishes the issue, the following keyword should be used to +automatically link and close the issue upon merging the PR: +** Closes +** For more info see +(https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) +* The description can also contain additional information for the PR and +changed files. +* More complicated pull request should contain a short summary of the +changed files and commits. +* In addition, other issue or PRs can be linked, most commonly with +keyword like: +** Blocked by +** Related +** Merge first +* Comments requested changes and other conversations in a pull request +should only be resolved by the person that started them and NOT the +creator of the pull request. (As the permissions might not always allow +this, a final comment by the creator of the conversations, saying it can +be resolved is the second 0ption.) +* Conversations should be answered by the owner of the pull request, so +to show that the suggestion was either implemented or acknowledged. +* The pull request should be assigned to the person that has work on the +PR, either the reviewer or the owner depending on whether a review is to +be done or the review needs to be addressed. +* A pull request should not be a draft when it is in a review. + +== Commit + +* Commit messages should always start with the issue number and a +hashtag, so to automatically link the corresponding issue. +* The title of a commit should be kept short and informative. +* The description of a commit message can be used to elaborate on the +title. + +== Issue + +* Issues should be written clearly and easy to comprehend. +* Issues should use the existing template. +* The goal or requirements of the issue should be explained first. +* Potential or desired implementations can or should be described after +the preview point. +* A very good practice and nice to have, are acceptance criteria for the +issue. +* Other issues can be linked using a hashtag and the issue number, most +commonly used keywords: +** Related to +** Blocked by + +== Review + +* The reviewer should be assigned to the PR, if a review is needed, or +the requested changes need to be checked and conversations need to be +resolved. +* After completing the review, the owner of the PR should be assigned. +* After the team review is finished the PO (hohwille) should be +assigned. +* While reviewing a useful tool is the web ide provided by github. +Simply open the `files changed` tab and press `.` on the keyboard. +* Another useful tool is to use the feature “insert a suggestion” while +writing a comment (for more detail see +https://haacked.com/archive/2019/06/03/suggested-changes/) (This feature +does not reformat the code, so be aware that you need to manually add +the spaces etc.) diff --git a/security/src/main/java/com/devonfw/tools/security/BuildSecurityJsonFiles.java b/security/src/main/java/com/devonfw/tools/security/BuildSecurityJsonFiles.java index 0363e2e54..4a8ebcc0a 100644 --- a/security/src/main/java/com/devonfw/tools/security/BuildSecurityJsonFiles.java +++ b/security/src/main/java/com/devonfw/tools/security/BuildSecurityJsonFiles.java @@ -198,10 +198,10 @@ private static void addVulnerabilityToSecurityFile(Vulnerability vulnerability, if (referenceUrls.isEmpty()) { referenceUrls.add("No references found, try searching for the CVE name (" + cveName + ") on the web."); } - boolean toLowSeverity = hasV3Severity ? severity.compareTo(minV3Severity) < 0 + boolean tooLowSeverity = hasV3Severity ? severity.compareTo(minV3Severity) < 0 : severity.compareTo(minV2Severity) < 0; - if (toLowSeverity) { + if (tooLowSeverity) { return; } VersionRange versionRange = getVersionRangeFromVulnerability(vulnerability, urlUpdater, cpeToUrlVersion); @@ -265,6 +265,14 @@ static VersionRange getVersionRangeFromVulnerability(Vulnerability vulnerability return affectedRange; } + /** + * TODO + * + * @param cpeVersion + * @param urlUpdater + * @param cpeToUrlVersion + * @return + */ private static String getUrlVersion(String cpeVersion, AbstractUrlUpdater urlUpdater, Map cpeToUrlVersion) { @@ -274,7 +282,6 @@ private static String getUrlVersion(String cpeVersion, AbstractUrlUpdater urlUpd urlVersion = cpeToUrlVersion.get(cpeVersion); } else { urlVersion = urlUpdater.mapCpeVersionToUrlVersion(cpeVersion); - } } return urlVersion; diff --git a/security/src/main/java/com/devonfw/tools/security/UrlAnalyzer.java b/security/src/main/java/com/devonfw/tools/security/UrlAnalyzer.java index 746e72549..70e2c3efa 100644 --- a/security/src/main/java/com/devonfw/tools/security/UrlAnalyzer.java +++ b/security/src/main/java/com/devonfw/tools/security/UrlAnalyzer.java @@ -7,7 +7,6 @@ import org.owasp.dependencycheck.Engine; import org.owasp.dependencycheck.analyzer.AbstractFileTypeAnalyzer; import org.owasp.dependencycheck.analyzer.AnalysisPhase; -import org.owasp.dependencycheck.analyzer.exception.AnalysisException; import org.owasp.dependencycheck.dependency.Confidence; import org.owasp.dependencycheck.dependency.Dependency; import org.owasp.dependencycheck.dependency.Evidence;