From 82f7803e0b56779f4bb0b1d41f76756825854759 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Mon, 12 Feb 2024 17:05:10 +0100 Subject: [PATCH 1/8] #180: commandlet to set edition (#194) --- .../ide/commandlet/CommandletManagerImpl.java | 3 + .../ide/commandlet/EditionGetCommandlet.java | 53 +++++++++++ .../ide/commandlet/EditionListCommandlet.java | 42 +++++++++ .../ide/commandlet/EditionSetCommandlet.java | 47 ++++++++++ .../ide/commandlet/VersionGetCommandlet.java | 4 +- .../ide/commandlet/VersionListCommandlet.java | 7 +- .../ide/environment/EnvironmentVariables.java | 9 ++ .../tools/ide/property/EditionProperty.java | 45 ++++++++++ .../tools/ide/tool/ToolCommandlet.java | 90 +++++++++++++++++++ .../tools/ide/url/model/UrlMetadata.java | 24 ++++- cli/src/main/resources/nls/Ide.properties | 5 ++ cli/src/main/resources/nls/Ide_de.properties | 5 ++ .../commandlet/EditionGetCommandletTest.java | 67 ++++++++++++++ .../commandlet/EditionListCommandletTest.java | 28 ++++++ .../commandlet/EditionSetCommandletTest.java | 58 ++++++++++++ .../commandlet/VersionGetCommandletTest.java | 2 +- .../az/az/testVersion/.ide.software.version | 1 + .../_ide/urls/mvn/secondMvnEdition/.gitkeep | 0 18 files changed, 482 insertions(+), 8 deletions(-) create mode 100644 cli/src/main/java/com/devonfw/tools/ide/commandlet/EditionGetCommandlet.java create mode 100644 cli/src/main/java/com/devonfw/tools/ide/commandlet/EditionListCommandlet.java create mode 100644 cli/src/main/java/com/devonfw/tools/ide/commandlet/EditionSetCommandlet.java create mode 100644 cli/src/main/java/com/devonfw/tools/ide/property/EditionProperty.java create mode 100644 cli/src/test/java/com/devonfw/tools/ide/commandlet/EditionGetCommandletTest.java create mode 100644 cli/src/test/java/com/devonfw/tools/ide/commandlet/EditionListCommandletTest.java create mode 100644 cli/src/test/java/com/devonfw/tools/ide/commandlet/EditionSetCommandletTest.java create mode 100644 cli/src/test/resources/ide-projects/_ide/software/default/az/az/testVersion/.ide.software.version create mode 100644 cli/src/test/resources/ide-projects/_ide/urls/mvn/secondMvnEdition/.gitkeep diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java index e8fbefaae..653b30076 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java @@ -60,6 +60,9 @@ public CommandletManagerImpl(IdeContext context) { add(new VersionSetCommandlet(context)); add(new VersionGetCommandlet(context)); add(new VersionListCommandlet(context)); + add(new EditionGetCommandlet(context)); + add(new EditionSetCommandlet(context)); + add(new EditionListCommandlet(context)); add(new VersionCommandlet(context)); add(new Gh(context)); add(new Helm(context)); diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/EditionGetCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/EditionGetCommandlet.java new file mode 100644 index 000000000..5c2f11526 --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/EditionGetCommandlet.java @@ -0,0 +1,53 @@ +package com.devonfw.tools.ide.commandlet; + +import com.devonfw.tools.ide.cli.CliException; +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.property.ToolProperty; +import com.devonfw.tools.ide.tool.ToolCommandlet; +import com.devonfw.tools.ide.version.VersionIdentifier; + +import static com.devonfw.tools.ide.process.ProcessResult.TOOL_NOT_INSTALLED; + +/** + * An internal {@link Commandlet} to get the installed edition for a tool. + * + * @see ToolCommandlet#getInstalledEdition() + */ +public class EditionGetCommandlet extends Commandlet { + + /** The tool to get the edition of. */ + public final ToolProperty tool; + + /** + * The constructor. + * + * @param context the {@link IdeContext}. + */ + public EditionGetCommandlet(IdeContext context) { + + super(context); + addKeyword(getName()); + this.tool = add(new ToolProperty("", true, "tool")); + } + + @Override + public String getName() { + + return "get-edition"; + } + + @Override + public void run() { + + ToolCommandlet commandlet = this.tool.getValue(); + VersionIdentifier installedVersion = commandlet.getInstalledVersion(); + if (installedVersion == null) { + this.context.info("The configured edition for tool {} is {}", commandlet.getName(), commandlet.getEdition()); + this.context.info("To install that edition call the following command:"); + this.context.info("ide install {}", commandlet.getName()); + return; + } + String installedEdition = commandlet.getInstalledEdition(); + this.context.info(installedEdition); + } +} diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/EditionListCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/EditionListCommandlet.java new file mode 100644 index 000000000..eea3a261b --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/EditionListCommandlet.java @@ -0,0 +1,42 @@ +package com.devonfw.tools.ide.commandlet; + +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.property.ToolProperty; +import com.devonfw.tools.ide.tool.ToolCommandlet; + +/** + * An internal {@link Commandlet} to list editions for a tool. + * + * @see ToolCommandlet#listEditions() + */ +public class EditionListCommandlet extends Commandlet { + + /** The tool to list the editions of. */ + public final ToolProperty tool; + + /** + * The constructor. + * + * @param context the {@link IdeContext}. + */ + public EditionListCommandlet(IdeContext context) { + + super(context); + addKeyword(getName()); + this.tool = add(new ToolProperty("", true, "tool")); + } + + @Override + public String getName() { + + return "list-editions"; + } + + @Override + public void run() { + + ToolCommandlet commandlet = this.tool.getValue(); + commandlet.listEditions(); + } + +} diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/EditionSetCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/EditionSetCommandlet.java new file mode 100644 index 000000000..ee851d040 --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/EditionSetCommandlet.java @@ -0,0 +1,47 @@ +package com.devonfw.tools.ide.commandlet; + +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.property.EditionProperty; +import com.devonfw.tools.ide.property.ToolProperty; +import com.devonfw.tools.ide.tool.ToolCommandlet; + +/** + * An internal {@link Commandlet} to set a tool edition. + */ +public class EditionSetCommandlet extends Commandlet { + + /** The tool to set the edition of. */ + public final ToolProperty tool; + + /** The edition to set. */ + public final EditionProperty edition; + + /** + * The constructor. + * + * @param context the {@link IdeContext}. + */ + public EditionSetCommandlet(IdeContext context) { + + super(context); + addKeyword(getName()); + this.tool = add(new ToolProperty("", true, "tool")); + this.edition = add(new EditionProperty("", true, "edition")); + } + + @Override + public String getName() { + + return "set-edition"; + } + + @Override + public void run() { + + ToolCommandlet commandlet = this.tool.getValue(); + String edition = this.edition.getValue(); + + commandlet.setEdition(edition); + } + +} diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/VersionGetCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/VersionGetCommandlet.java index 153d93c7d..c7042d02f 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/VersionGetCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/VersionGetCommandlet.java @@ -8,9 +8,9 @@ import com.devonfw.tools.ide.version.VersionIdentifier; /** - * An internal {@link Commandlet} to set a tool version. + * An internal {@link Commandlet} to get the installed version for a tool. * - * @see ToolCommandlet#setVersion(VersionIdentifier, boolean) + * @see ToolCommandlet#getInstalledVersion() */ public class VersionGetCommandlet extends Commandlet { diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/VersionListCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/VersionListCommandlet.java index 16d8b7d50..be8cd08d4 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/VersionListCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/VersionListCommandlet.java @@ -3,12 +3,11 @@ import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.property.ToolProperty; import com.devonfw.tools.ide.tool.ToolCommandlet; -import com.devonfw.tools.ide.version.VersionIdentifier; /** - * An internal {@link Commandlet} to set a tool version. + * An internal {@link Commandlet} to list versions for a tool. * - * @see ToolCommandlet#setVersion(VersionIdentifier, boolean) + * @see ToolCommandlet#listVersions()) */ public class VersionListCommandlet extends Commandlet { @@ -30,7 +29,7 @@ public VersionListCommandlet(IdeContext context) { @Override public String getName() { - return "list-version"; + return "list-versions"; } @Override 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 d99e67d1f..05fb41a40 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 @@ -226,4 +226,13 @@ static String getToolVersionVariable(String tool) { return tool.toUpperCase(Locale.ROOT) + "_VERSION"; } + /** + * @param tool the name of the tool. + * @return the name of the edition variable. + */ + static String getToolEditionVariable(String tool) { + + return tool.toUpperCase(Locale.ROOT) + "_EDITION"; + } + } diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/EditionProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/EditionProperty.java new file mode 100644 index 000000000..f2226254b --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/property/EditionProperty.java @@ -0,0 +1,45 @@ +package com.devonfw.tools.ide.property; + +import com.devonfw.tools.ide.context.IdeContext; + +import java.util.function.Consumer; + +public class EditionProperty extends Property { + + /** + * The constructor. + * + * @param name the {@link #getName() property name}. + * @param required the {@link #isRequired() required flag}. + * @param alias the {@link #getAlias() property alias}. + */ + public EditionProperty(String name, boolean required, String alias) { + + this(name, required, alias, null); + } + + /** + * The constructor. + * + * @param name the {@link #getName() property name}. + * @param required the {@link #isRequired() required flag}. + * @param alias the {@link #getAlias() property alias}. + * @param validator the {@link Consumer} used to {@link #validate() validate} the {@link #getValue() value}. + */ + public EditionProperty(String name, boolean required, String alias, Consumer validator) { + + super(name, required, alias, validator); + } + + @Override + public Class getValueType() { + + return String.class; + } + + @Override + public String parse(String valueAsString, IdeContext context) { + + return valueAsString; + } +} 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 4f8f0a0d8..68e7c0967 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 @@ -345,6 +345,51 @@ protected VersionIdentifier getInstalledVersion(Path toolPath) { } } + /** + * @return the installed edition of this tool or {@code null} if not installed. + */ + public String getInstalledEdition() { + + return getInstalledEdition(this.context.getSoftwarePath().resolve(getName())); + } + + /** + * @param toolPath the installation {@link Path} where to find currently installed tool. The name of the parent + * directory of the real path corresponding to the passed {@link Path path} must be the name of the edition. + * @return the installed edition of this tool or {@code null} if not installed. + */ + public String getInstalledEdition(Path toolPath) { + + if (!Files.isDirectory(toolPath)) { + this.context.debug("Tool {} not installed in {}", getName(), toolPath); + return null; + } + try { + String edition = toolPath.toRealPath().getParent().getFileName().toString(); + if (!this.context.getUrls().getSortedEditions(getName()).contains(edition)) { + edition = getEdition(); + } + return edition; + } catch (IOException e) { + throw new IllegalStateException("Couldn't determine the edition of " + getName() + + " from the directory structure of its software path " + toolPath + + ", assuming the name of the parent directory of the real path of the software path to be the edition " + + "of the tool.", e); + } + + } + + /** + * List the available editions of this tool. + */ + public void listEditions() { + + List editions = this.context.getUrls().getSortedEditions(getName()); + for (String edition : editions) { + this.context.info(edition); + } + } + /** * List the available versions of this tool. */ @@ -404,4 +449,49 @@ public void setVersion(VersionIdentifier version, boolean hint) { } } + /** + * Sets the tool edition in the environment variable configuration file. + * + * @param edition the edition to set. + */ + public void setEdition(String edition) { + + setEdition(edition, true); + } + + /** + * Sets the tool edition in the environment variable configuration file. + * + * @param edition the edition to set + * @param hint - {@code true} to print the installation hint, {@code false} otherwise. + */ + public void setEdition(String edition, boolean hint) { + + if ((edition == null) || edition.isBlank()) { + throw new IllegalStateException("Edition has to be specified!"); + } + + if (!Files.exists(this.context.getUrls().getEdition(getName(), edition).getPath())) { + this.context.warning("Edition {} seems to be invalid", edition); + + } + EnvironmentVariables variables = this.context.getVariables(); + EnvironmentVariables settingsVariables = variables.getByType(EnvironmentVariablesType.SETTINGS); + String name = EnvironmentVariables.getToolEditionVariable(this.tool); + settingsVariables.set(name, edition, false); + settingsVariables.save(); + + this.context.info("{}={} has been set in {}", name, edition, settingsVariables.getSource()); + EnvironmentVariables declaringVariables = variables.findVariable(name); + if ((declaringVariables != null) && (declaringVariables != settingsVariables)) { + this.context.warning( + "The variable {} is overridden in {}. Please remove the overridden declaration in order to make the change affect.", + name, declaringVariables.getSource()); + } + if (hint) { + this.context.info("To install that edition call the following command:"); + this.context.info("ide install {}", this.tool); + } + } + } 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 1597c7ec2..ccf5a6f0d 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 @@ -1,12 +1,15 @@ package com.devonfw.tools.ide.url.model; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; +import java.util.stream.Stream; import com.devonfw.tools.ide.cli.CliException; import com.devonfw.tools.ide.context.IdeContext; @@ -52,6 +55,25 @@ public UrlEdition getEdition(String tool, String edition) { return urlEdition; } + /** + * @param tool the name of the {@link UrlTool}. + * @return the sorted {@link List} of {@link String editions} . + */ + public List getSortedEditions(String tool) { + + List list = new ArrayList<>(); + try { + for (UrlEdition urlEdition : this.repository.getChild(tool).getChildren()) { + list.add(urlEdition.getName()); + } + } catch (NullPointerException e) { + this.context.warning("Can't get sorted editions for tool {} because it does not exist in {}.", tool, + this.repository.getPath()); + } + Collections.sort(list); + return Collections.unmodifiableList(list); + } + /** * @param tool the name of the {@link UrlTool}. * @param edition the name of the {@link UrlEdition}. diff --git a/cli/src/main/resources/nls/Ide.properties b/cli/src/main/resources/nls/Ide.properties index a07f3d352..af79759c5 100644 --- a/cli/src/main/resources/nls/Ide.properties +++ b/cli/src/main/resources/nls/Ide.properties @@ -8,6 +8,7 @@ cmd---version=Print the version of IDEasy. cmd-complete=Internal commandlet for bash auto-completion cmd-eclipse=Tool commandlet for Eclipse (IDE) cmd-env=Print the environment variables to set and export. +cmd-get-edition=Get the edition of the selected tool. cmd-get-version=Get the version of the selected tool. cmd-gh=Tool commandlet for Github CLI. cmd-gradle=Tool commandlet for Gradle (Build-Tool) @@ -18,16 +19,20 @@ cmd-java=Tool commandlet for Java (OpenJDK) cmd-kotlinc=Tool commandlet for Kotlin. cmd-kotlincnative=Tool commandlet for Kotlin-Native. cmd-list-version=List the available versions of the selected tool. +cmd-list-editions=List the available editions of the selected tool. +cmd-list-versions=List the available versions of the selected tool. cmd-mvn=Tool commandlet for Maven (Build-Tool) cmd-node=Tool commandlet for Node.js (JavaScript runtime) cmd-oc=Tool commandlet for Openshift CLI (Kubernetes Management Tool) cmd-quarkus=Tool commandlet for Quarkus (Framework for cloud-native apps) +cmd-set-edition=Set the edition of the selected tool. cmd-set-version=Set the version of the selected tool. cmd-shell=Commandlet to start built-in shell with advanced auto-completion. cmd-terraform=Tool commandlet for Terraform cmd-vscode=Tool commandlet for Visual Studio Code (IDE) cmd-cobigen=Tool commandlet for Cobigen val-args=The commandline arguments to pass to the tool. +val-edition=The tool edition. val-tool=The tool commandlet to select. val-version=The tool version val-set-version-version=The tool version to set. diff --git a/cli/src/main/resources/nls/Ide_de.properties b/cli/src/main/resources/nls/Ide_de.properties index 82b1711b8..7bf25b47f 100644 --- a/cli/src/main/resources/nls/Ide_de.properties +++ b/cli/src/main/resources/nls/Ide_de.properties @@ -7,6 +7,7 @@ cmd-az=Werkzeug Kommando fuer Azure Kommandoschnittstelle. cmd---version=Gibt die Version von IDEasy aus. cmd-eclipse=Werkzeug Kommando für Eclipse (IDE) cmd-env=Gibt die zu setztenden und exportierenden Umgebungsvariablen aus. +cmd-get-edition=Zeigt die Edition des selektierten Werkzeugs an. cmd-get-version=Zeigt die Version des selektierten Werkzeugs an. cmd-gh=Werkzeug Kommando für die Github Kommandoschnittstelle. cmd-helm=Werkzeug Kommando für Helm (Kubernetes Package Manager) @@ -16,15 +17,19 @@ cmd-java=Werkzeug Kommando für Java (OpenJDK) cmd-kotlinc=Werkzeug Kommando für Kotlin. cmd-kotlincnative=Werkzeug Kommando für Kotlin-Native. cmd-list-version=Listet die verfügbaren Versionen des selektierten Werkzeugs auf. +cmd-list-editions=Listet die verfügbaren Editionen des selektierten Werkzeugs auf. +cmd-list-versions=Listet die verfügbaren Versionen des selektierten Werkzeugs auf. cmd-mvn=Werkzeug Kommando für Maven (Build-Werkzeug) cmd-node=Werkzeug Kommando für Node.js (JavaScript Laufzeitumgebung) cmd-oc=Werkzeug Kommando für Openshift CLI (Kubernetes Management Tool) cmd-quarkus=Werkzeug Kommando für Quarkus (Framework für Cloud-native Anwendungen) +cmd-set-edition=Setzt die Edition des selektierten Werkzeugs. cmd-set-version=Setzt die Version des selektierten Werkzeugs. cmd-terraform=Werkzeug Kommando für Terraform. cmd-vscode=Werkzeug Kommando für Visual Studio Code (IDE) cmd-cobigen=Werkzeug Kommando für Cobigen. val-args=Die Kommandozeilen-Argumente zur Übergabe an das Werkzeug. +val-edition=Die Werkzeug Edition. val-tool=Das zu selektierende Werkzeug Kommando. val-version=Die Werkzeug Version. val-set-version-version=Die zu setztende Werkzeug Version. diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/EditionGetCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/EditionGetCommandletTest.java new file mode 100644 index 000000000..b00aec8b9 --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/EditionGetCommandletTest.java @@ -0,0 +1,67 @@ +package com.devonfw.tools.ide.commandlet; + +import com.devonfw.tools.ide.cli.CliException; +import com.devonfw.tools.ide.context.AbstractIdeContextTest; +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.context.IdeTestContext; +import com.devonfw.tools.ide.log.IdeLogLevel; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; +import java.util.List; + +/** Integration test of {@link EditionGetCommandlet}. */ + +public class EditionGetCommandletTest extends AbstractIdeContextTest { + + /** Test of {@link VersionGetCommandlet} run. */ + @Test + public void testEditionGetCommandletRun() { + + // arrange + String path = "workspaces/foo-test/my-git-repo"; + String tool = "az"; + IdeTestContext context = newContext("basic", path, true); + mockInstallTool(context, tool); + EditionGetCommandlet editionGet = context.getCommandletManager().getCommandlet(EditionGetCommandlet.class); + + // act + editionGet.tool.setValueAsString(tool, context); + editionGet.run(); + + // assert + List logs = context.level(IdeLogLevel.INFO).getMessages(); + assertThat(logs).contains("az"); + } + + /** + * Mocks the installation of a tool, since getEdition depends on symlinks which are not distributed with git + * + * @param context the {@link IdeContext} to use. + * @param tool the tool to mock install. + */ + private static void mockInstallTool(IdeTestContext context, String tool) { + + Path pathToInstallationOfDummyTool = context.getSoftwareRepositoryPath() + .resolve(context.getDefaultToolRepository().getId()).resolve(tool).resolve("az/testVersion"); + Path pathToLinkedSoftware = context.getSoftwarePath().resolve(tool); + context.getFileAccess().symlink(pathToInstallationOfDummyTool, pathToLinkedSoftware); + } + + /** Test of {@link VersionGetCommandlet} run, when Installed Version is null. */ + @Test + public void testVersionGetCommandletRunPrintConfiguredEdition() { + + // arrange + String path = "workspaces/foo-test/my-git-repo"; + IdeTestContext context = newContext("basic", path, false); + EditionGetCommandlet editionGet = context.getCommandletManager().getCommandlet(EditionGetCommandlet.class); + editionGet.tool.setValueAsString("java", context); + // act + editionGet.run(); + // assert + assertLogMessage(context, IdeLogLevel.INFO, "The configured edition for tool java is java"); + assertLogMessage(context, IdeLogLevel.INFO, "To install that edition call the following command:"); + assertLogMessage(context, IdeLogLevel.INFO, "ide install java"); + } +} diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/EditionListCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/EditionListCommandletTest.java new file mode 100644 index 000000000..16bde2315 --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/EditionListCommandletTest.java @@ -0,0 +1,28 @@ +package com.devonfw.tools.ide.commandlet; + +import com.devonfw.tools.ide.context.AbstractIdeContextTest; +import com.devonfw.tools.ide.context.IdeTestContext; +import com.devonfw.tools.ide.log.IdeLogLevel; +import org.junit.jupiter.api.Test; + +/** Integration test of {@link EditionListCommandlet}. */ +public class EditionListCommandletTest extends AbstractIdeContextTest { + + /** Test of {@link EditionListCommandlet} run. */ + @Test + public void testEditionListCommandletRun() { + + // arrange + String path = "workspaces/foo-test/my-git-repo"; + IdeTestContext context = newContext("basic", path, false); + EditionListCommandlet editionList = context.getCommandletManager().getCommandlet(EditionListCommandlet.class); + editionList.tool.setValueAsString("mvn", context); + + // act + editionList.run(); + + // assert + assertLogMessage(context, IdeLogLevel.INFO, "mvn"); + assertLogMessage(context, IdeLogLevel.INFO, "secondMvnEdition"); + } +} \ No newline at end of file diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/EditionSetCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/EditionSetCommandletTest.java new file mode 100644 index 000000000..bc70f23ad --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/EditionSetCommandletTest.java @@ -0,0 +1,58 @@ +package com.devonfw.tools.ide.commandlet; + +import com.devonfw.tools.ide.context.AbstractIdeContextTest; +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.context.IdeTestContext; +import com.devonfw.tools.ide.log.IdeLogLevel; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; +import java.util.List; + +/** Integration test of {@link EditionSetCommandlet}. */ +public class EditionSetCommandletTest extends AbstractIdeContextTest { + + /** Test of {@link VersionSetCommandlet} run. */ + @Test + public void testEditionSetCommandletRun() { + + // arrange + String path = "workspaces/foo-test/my-git-repo"; + IdeContext context = newContext("basic", path, true); + EditionSetCommandlet editionSet = context.getCommandletManager().getCommandlet(EditionSetCommandlet.class); + editionSet.tool.setValueAsString("mvn", context); + editionSet.edition.setValueAsString("setEdition", context); + + // act + editionSet.run(); + + // assert + List logs = ((IdeTestContext) context).level(IdeLogLevel.WARNING).getMessages(); + assertThat(logs).containsExactly("Edition setEdition seems to be invalid"); + Path settingsIdeProperties = context.getSettingsPath().resolve("ide.properties"); + assertThat(settingsIdeProperties).hasContent(""" + #******************************************************************************** + # This file contains project specific environment variables + #******************************************************************************** + + JAVA_VERSION=17* + MVN_VERSION=3.9.* + ECLIPSE_VERSION=2023-03 + INTELLIJ_EDITION=ultimate + + 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 + MVN_EDITION=setEdition"""); + } +} \ No newline at end of file diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/VersionGetCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/VersionGetCommandletTest.java index c03c68eb8..ffd46dc38 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/commandlet/VersionGetCommandletTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/VersionGetCommandletTest.java @@ -17,7 +17,7 @@ public class VersionGetCommandletTest extends AbstractIdeContextTest { * Test of {@link VersionGetCommandlet} run, when Installed Version is null. */ @Test - public void testVersionGetCommandletRunThrowsCliExeption() { + public void testVersionGetCommandletRunThrowsCliException() { // arrange String path = "workspaces/foo-test/my-git-repo"; diff --git a/cli/src/test/resources/ide-projects/_ide/software/default/az/az/testVersion/.ide.software.version b/cli/src/test/resources/ide-projects/_ide/software/default/az/az/testVersion/.ide.software.version new file mode 100644 index 000000000..62ec2f639 --- /dev/null +++ b/cli/src/test/resources/ide-projects/_ide/software/default/az/az/testVersion/.ide.software.version @@ -0,0 +1 @@ +testVersion diff --git a/cli/src/test/resources/ide-projects/_ide/urls/mvn/secondMvnEdition/.gitkeep b/cli/src/test/resources/ide-projects/_ide/urls/mvn/secondMvnEdition/.gitkeep new file mode 100644 index 000000000..e69de29bb From 2aa1fa4c4731db04f13b982f10d309dd46dd8cc9 Mon Sep 17 00:00:00 2001 From: MustaphaOuchen <98693422+MustaphaOuchen@users.noreply.github.com> Date: Mon, 12 Feb 2024 17:12:50 +0100 Subject: [PATCH 2/8] #27: implement tool commandlet for jmc (#197) --- .../ide/commandlet/CommandletManagerImpl.java | 4 +- .../tools/ide/context/AbstractIdeContext.java | 6 +- .../tools/ide/process/ProcessContext.java | 6 +- .../tools/ide/process/ProcessContextImpl.java | 7 +- .../tools/ide/tool/LocalToolCommandlet.java | 21 +++- .../tools/ide/tool/ToolCommandlet.java | 20 ++- .../tools/ide/tool/ToolInstallation.java | 4 +- .../tools/ide/tool/eclipse/Eclipse.java | 2 +- .../tools/ide/tool/ide/IdeToolCommandlet.java | 2 +- .../com/devonfw/tools/ide/tool/jmc/Jmc.java | 66 ++++++++++ .../tools/ide/tool/terraform/Terraform.java | 2 +- cli/src/main/resources/nls/Ide.properties | 1 + cli/src/main/resources/nls/Ide_de.properties | 1 + .../com/devonfw/tools/ide/Jmc/JmcTest.java | 117 ++++++++++++++++++ .../ide/context/AbstractIdeTestContext.java | 5 +- ....openjdk.jmc-8.3.0-linux.gtk.x86_64.tar.gz | Bin 0 -> 267 bytes ...enjdk.jmc-8.3.0-macosx.cocoa.x86_64.tar.gz | Bin 0 -> 449 bytes ...g.openjdk.jmc-8.3.0-win32.win32.x86_64.zip | Bin 0 -> 543 bytes .../_ide/urls/jmc/jmc/8.3.0/linux_x64.sha256 | 1 + .../_ide/urls/jmc/jmc/8.3.0/linux_x64.urls | 1 + .../_ide/urls/jmc/jmc/8.3.0/mac_x64.sha256 | 1 + .../_ide/urls/jmc/jmc/8.3.0/mac_x64.urls | 1 + .../_ide/urls/jmc/jmc/8.3.0/status.json | 20 +++ .../_ide/urls/jmc/jmc/8.3.0/windows_x64.urls | 1 + .../jmc/jmc/8.3.0/windows_x64.urls.sha256 | 1 + documentation/IDEasy-usage.asciidoc | 1 + documentation/jmc.asciidoc | 14 +++ 27 files changed, 284 insertions(+), 21 deletions(-) create mode 100644 cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java create mode 100644 cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java create mode 100644 cli/src/test/resources/__files/org.openjdk.jmc-8.3.0-linux.gtk.x86_64.tar.gz create mode 100644 cli/src/test/resources/__files/org.openjdk.jmc-8.3.0-macosx.cocoa.x86_64.tar.gz create mode 100644 cli/src/test/resources/__files/org.openjdk.jmc-8.3.0-win32.win32.x86_64.zip create mode 100644 cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/linux_x64.sha256 create mode 100644 cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/linux_x64.urls create mode 100644 cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/mac_x64.sha256 create mode 100644 cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/mac_x64.urls create mode 100644 cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/status.json create mode 100644 cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/windows_x64.urls create mode 100644 cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/windows_x64.urls.sha256 create mode 100644 documentation/jmc.asciidoc diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java index 653b30076..15c5cdbe0 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java @@ -11,12 +11,14 @@ import com.devonfw.tools.ide.property.Property; import com.devonfw.tools.ide.tool.aws.Aws; import com.devonfw.tools.ide.tool.az.Azure; +import com.devonfw.tools.ide.tool.cobigen.Cobigen; import com.devonfw.tools.ide.tool.eclipse.Eclipse; import com.devonfw.tools.ide.tool.gcviewer.GcViewer; import com.devonfw.tools.ide.tool.gh.Gh; import com.devonfw.tools.ide.tool.gradle.Gradle; import com.devonfw.tools.ide.tool.helm.Helm; import com.devonfw.tools.ide.tool.java.Java; +import com.devonfw.tools.ide.tool.jmc.Jmc; import com.devonfw.tools.ide.tool.kotlinc.Kotlinc; import com.devonfw.tools.ide.tool.kotlinc.KotlincNative; import com.devonfw.tools.ide.tool.mvn.Mvn; @@ -25,7 +27,6 @@ import com.devonfw.tools.ide.tool.quarkus.Quarkus; import com.devonfw.tools.ide.tool.terraform.Terraform; import com.devonfw.tools.ide.tool.vscode.Vscode; -import com.devonfw.tools.ide.tool.cobigen.Cobigen; /** * Implementation of {@link CommandletManager}. @@ -81,6 +82,7 @@ public CommandletManagerImpl(IdeContext context) { add(new Azure(context)); add(new Aws(context)); add(new Cobigen(context)); + add(new Jmc(context)); } private void add(Commandlet commandlet) { 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 c6387fc4d..7dc3fc134 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 @@ -5,6 +5,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.attribute.FileTime; import java.time.Duration; import java.util.HashMap; import java.util.Iterator; @@ -13,7 +14,6 @@ import java.util.Map; import java.util.Objects; import java.util.function.Function; -import java.nio.file.attribute.FileTime; import com.devonfw.tools.ide.cli.CliArgument; import com.devonfw.tools.ide.cli.CliArguments; @@ -613,7 +613,7 @@ public void gitPullOrClone(Path target, String gitRepoUrl) { } 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); + ProcessResult result = pc.addArg("remote").run(true, false); List remotes = result.getOut(); if (remotes.isEmpty()) { String message = "This is a local git repo with no remote - if you did this for testing, you may continue...\n" @@ -621,7 +621,7 @@ public void gitPullOrClone(Path target, String gitRepoUrl) { askToContinue(message); } else { pc.errorHandling(ProcessErrorHandling.WARNING); - result = pc.addArg("pull").run(false); + result = pc.addArg("pull").run(false, false); if (!result.isSuccessful()) { String message = "Failed to update git repository at " + target; if (this.offlineMode) { 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 bcab1cfb9..191ba706e 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 @@ -133,7 +133,7 @@ default ProcessContext addArgs(List... args) { */ default int run() { - return run(false).getExitCode(); + return run(false, false).getExitCode(); } /** @@ -144,8 +144,10 @@ default int run() { * @param capture - {@code true} to capture standard {@link ProcessResult#getOut() out} and * {@link ProcessResult#getErr() err} in the {@link ProcessResult}, {@code false} otherwise (to redirect out * and err). + * @param isBackgroundProcess {@code true}, the process of the command will be run as background process, + * {@code false} otherwise it will be run as foreground process. * @return the {@link ProcessResult}. */ - ProcessResult run(boolean capture); + ProcessResult run(boolean capture, boolean isBackgroundProcess); } 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 21f6a9a0e..ba0db249b 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 @@ -97,7 +97,12 @@ public ProcessContext withEnvVar(String key, String value) { } @Override - public ProcessResult run(boolean capture) { + public ProcessResult run(boolean capture, boolean isBackgroundProcess) { + + if (isBackgroundProcess) { + this.context + .warning("TODO https://github.com/devonfw/IDEasy/issues/9 Implement background process functionality"); + } if (this.executable == null) { throw new IllegalStateException("Missing executable to run process!"); 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 d1e58180a..61a5c4656 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 @@ -60,13 +60,14 @@ public Path getToolBinPath() { protected boolean doInstall(boolean silent) { VersionIdentifier configuredVersion = getConfiguredVersion(); + // get installed version before installInRepo actually may install the software + VersionIdentifier installedVersion = getInstalledVersion(); // install configured version of our tool in the software repository if not already installed ToolInstallation installation = installInRepo(configuredVersion); // check if we already have this version installed (linked) locally in IDE_HOME/software - VersionIdentifier installedVersion = getInstalledVersion(); VersionIdentifier resolvedVersion = installation.resolvedVersion(); - if (resolvedVersion.equals(installedVersion)) { + if (resolvedVersion.equals(installedVersion) && !installation.newInstallation()) { IdeLogLevel level = silent ? IdeLogLevel.DEBUG : IdeLogLevel.INFO; this.context.level(level).log("Version {} of tool {} is already installed", installedVersion, getToolWithEdition()); @@ -156,11 +157,13 @@ public ToolInstallation installInRepo(VersionIdentifier version, String edition, } catch (IOException e) { throw new IllegalStateException("Failed to write version file " + toolVersionFile, e); } - return createToolInstallation(toolPath, resolvedVersion, toolVersionFile); + // newInstallation results in above conditions to be true if isForceMode is true or if the tool version file was + // missing + return createToolInstallation(toolPath, resolvedVersion, toolVersionFile, true); } - private ToolInstallation createToolInstallation(Path rootDir, VersionIdentifier resolvedVersion, - Path toolVersionFile) { + private ToolInstallation createToolInstallation(Path rootDir, VersionIdentifier resolvedVersion, Path toolVersionFile, + boolean newInstallation) { Path linkDir = getMacOsHelper().findLinkDir(rootDir); Path binDir = linkDir; @@ -172,7 +175,13 @@ private ToolInstallation createToolInstallation(Path rootDir, VersionIdentifier assert (!linkDir.equals(rootDir)); this.context.getFileAccess().copy(toolVersionFile, linkDir, FileCopyMode.COPY_FILE_OVERRIDE); } - return new ToolInstallation(rootDir, linkDir, binDir, resolvedVersion); + return new ToolInstallation(rootDir, linkDir, binDir, resolvedVersion, newInstallation); + } + + private ToolInstallation createToolInstallation(Path rootDir, VersionIdentifier resolvedVersion, + Path toolVersionFile) { + + return createToolInstallation(rootDir, resolvedVersion, toolVersionFile, false); } } 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 68e7c0967..6171cb459 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 @@ -82,18 +82,20 @@ public final Set getTags() { @Override public void run() { - runTool(null, this.arguments.asArray()); + runTool(false, null, this.arguments.asArray()); } /** * Ensures the tool is installed and then runs this tool with the given arguments. * + * @param isBackgroundProcess {@code true}, the process of the command will be run as background process, + * {@code false} otherwise it will be run as foreground process. * @param toolVersion the explicit version (pattern) to run. Typically {@code null} to ensure the configured version * is installed and use that one. Otherwise, the specified version will be installed in the software repository * without touching and IDE installation and used to run. * @param args the commandline arguments to run the tool. */ - public void runTool(VersionIdentifier toolVersion, String... args) { + public void runTool(boolean isBackgroundProcess, VersionIdentifier toolVersion, String... args) { Path binaryPath; Path toolPath = Paths.get(getBinaryName()); @@ -105,7 +107,19 @@ public void runTool(VersionIdentifier toolVersion, String... args) { } ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.WARNING).executable(binaryPath) .addArgs(args); - pc.run(); + + pc.run(false, isBackgroundProcess); + } + + /** + * See {@link ToolCommandlet#runTool(boolean, VersionIdentifier, String...)} method. + * + * @param toolVersion + * @param args + */ + public void runTool(VersionIdentifier toolVersion, String... args) { + + runTool(false, toolVersion, args); } /** diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/ToolInstallation.java b/cli/src/main/java/com/devonfw/tools/ide/tool/ToolInstallation.java index 6522e92ff..4276b962c 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/ToolInstallation.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/ToolInstallation.java @@ -13,7 +13,9 @@ * @param binDir the {@link Path} relative to {@code linkDir} pointing to the directory containing the binaries that * should be put on the path (typically "bin"). * @param resolvedVersion the {@link VersionIdentifier} of the resolved tool version installed in {@code rootDir}. + * @param newInstallation {@code true} - if the tool should be installed newly, {@code true} - else */ -public record ToolInstallation(Path rootDir, Path linkDir, Path binDir, VersionIdentifier resolvedVersion) { +public record ToolInstallation(Path rootDir, Path linkDir, Path binDir, VersionIdentifier resolvedVersion, + boolean newInstallation) { } 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 5d91ad304..c98794ff4 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 @@ -81,7 +81,7 @@ protected ProcessResult runEclipse(boolean log, String... args) { Path javaPath = getCommandlet(Java.class).getToolBinPath(); pc.addArg("-vm").addArg(javaPath); pc.addArgs(args); - return pc.run(log); + return pc.run(log, false); } @Override 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 2cd8ec18b..4bb4800c8 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 @@ -207,7 +207,7 @@ public void run() { */ protected void runIde(String... args) { - runTool(null, args); + runTool(false,null, args); } /** diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java b/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java new file mode 100644 index 000000000..227461b65 --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java @@ -0,0 +1,66 @@ +package com.devonfw.tools.ide.tool.jmc; + +import java.io.File; +import java.nio.file.Path; +import java.util.Set; + +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; +import com.devonfw.tools.ide.tool.ToolCommandlet; +import com.devonfw.tools.ide.tool.java.Java; + +/** + * {@link ToolCommandlet} for JDK Mission + * Control, An advanced set of tools for managing, monitoring, profiling, and troubleshooting Java applications. + */ +public class Jmc extends LocalToolCommandlet { + + /** + * The constructor. + * + * @param context the {@link IdeContext}. method. + */ + public Jmc(IdeContext context) { + + super(context, "jmc", Set.of(Tag.JAVA, Tag.QA, Tag.ANALYSE, Tag.JVM)); + } + + @Override + public boolean doInstall(boolean silent) { + + getCommandlet(Java.class).install(); + return super.doInstall(silent); + } + + @Override + public void run() { + + runTool(true, null, this.arguments.asArray()); + } + + @Override + public void postInstall() { + + super.postInstall(); + + if (context.getSystemInfo().isWindows() || context.getSystemInfo().isLinux()) { + Path toolPath = getToolPath(); + Path oldBinaryPath = toolPath.resolve("JDK Mission Control"); + FileAccess fileAccess = context.getFileAccess(); + moveFilesAndDirs(oldBinaryPath.toFile(), toolPath.toFile()); + fileAccess.delete(oldBinaryPath); + } + + } + + private void moveFilesAndDirs(File oldBinaryDir, File toolPathDir) { + + FileAccess fileAccess = context.getFileAccess(); + for (File fileOrDir : oldBinaryDir.listFiles()) { + fileAccess.move(fileOrDir.toPath(), new File(toolPathDir, fileOrDir.getName()).toPath()); + } + } + +} \ No newline at end of file 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 745ececde..e99d1264b 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 @@ -26,6 +26,6 @@ public Terraform(IdeContext context) { protected void postInstall() { super.postInstall(); - runTool(null, "-install-autocomplete"); + runTool(false, null, "-install-autocomplete"); } } diff --git a/cli/src/main/resources/nls/Ide.properties b/cli/src/main/resources/nls/Ide.properties index af79759c5..ef6b3eda7 100644 --- a/cli/src/main/resources/nls/Ide.properties +++ b/cli/src/main/resources/nls/Ide.properties @@ -16,6 +16,7 @@ cmd-helm=Tool commandlet for Helm (Kubernetes Package Manager) cmd-help=Prints this help. cmd-install=Install the selected tool. cmd-java=Tool commandlet for Java (OpenJDK) +cmd-jmc=Tool commandlet for JDK Mission Control cmd-kotlinc=Tool commandlet for Kotlin. cmd-kotlincnative=Tool commandlet for Kotlin-Native. cmd-list-version=List the available versions of the selected tool. diff --git a/cli/src/main/resources/nls/Ide_de.properties b/cli/src/main/resources/nls/Ide_de.properties index 7bf25b47f..688bd923a 100644 --- a/cli/src/main/resources/nls/Ide_de.properties +++ b/cli/src/main/resources/nls/Ide_de.properties @@ -14,6 +14,7 @@ cmd-helm=Werkzeug Kommando für Helm (Kubernetes Package Manager) cmd-help=Zeigt diese Hilfe an. cmd-install=Installiert das selektierte Werkzeug. cmd-java=Werkzeug Kommando für Java (OpenJDK) +cmd-jmc=Werkzeug Kommando für JDK Mission Control cmd-kotlinc=Werkzeug Kommando für Kotlin. cmd-kotlincnative=Werkzeug Kommando für Kotlin-Native. cmd-list-version=Listet die verfügbaren Versionen des selektierten Werkzeugs auf. diff --git a/cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java b/cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java new file mode 100644 index 000000000..2132b9c8a --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java @@ -0,0 +1,117 @@ +package com.devonfw.tools.ide.Jmc; + +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.commandlet.InstallCommandlet; +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 com.devonfw.tools.ide.tool.jmc.Jmc}. + */ +public class JmcTest 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 { + + String windowsFilenameJmc = "org.openjdk.jmc-8.3.0-win32.win32.x86_64.zip"; + String linuxFilenameJmc = "org.openjdk.jmc-8.3.0-linux.gtk.x86_64.tar.gz"; + String macOSFilenameJmc = "org.openjdk.jmc-8.3.0-macosx.cocoa.x86_64.tar.gz"; + String windowsFilenameJava = "java-17.0.6-windows-x64.zip"; + String linuxFilenameJava = "java-17.0.6-linux-x64.tgz"; + String resourceFilesDirName = "__files"; + + Path windowsFilePathJmc = resourcePath.resolve(resourceFilesDirName).resolve(windowsFilenameJmc); + String windowsLengthJmc = String.valueOf(Files.size(windowsFilePathJmc)); + + Path linuxFilePathJmc = resourcePath.resolve(resourceFilesDirName).resolve(linuxFilenameJmc); + String linuxLengthJmc = String.valueOf(Files.size(linuxFilePathJmc)); + + Path macOSFilePathJmc = resourcePath.resolve(resourceFilesDirName).resolve(macOSFilenameJmc); + String maxOSLengthJmc = String.valueOf(Files.size(macOSFilePathJmc)); + + Path windowsFilePathJava = resourcePath.resolve(resourceFilesDirName).resolve(windowsFilenameJava); + String windowsLengthJava = String.valueOf(Files.size(windowsFilePathJava)); + + Path linuxFilePathJava = resourcePath.resolve(resourceFilesDirName).resolve(linuxFilenameJava); + String linuxLengthJava = String.valueOf(Files.size(linuxFilePathJava)); + + setupMockServerResponse("/jmcTest/windows", "application/zip", windowsLengthJmc, windowsFilenameJmc); + setupMockServerResponse("/jmcTest/linux", "application/gz", linuxLengthJmc, linuxFilenameJmc); + setupMockServerResponse("/jmcTest/macOS", "application/gz", maxOSLengthJmc, macOSFilenameJmc); + setupMockServerResponse("/installTest/windows", "application/zip", windowsLengthJava, windowsFilenameJava); + setupMockServerResponse("/installTest/linux", "application/tgz", linuxLengthJava, linuxFilenameJava); + setupMockServerResponse("/installTest/macOS", "application/tgz", linuxLengthJava, linuxFilenameJava); + + } + + private void setupMockServerResponse(String testUrl, String contentType, String contentLength, String bodyFile) { + + server.stubFor(get(urlPathEqualTo(testUrl)).willReturn(aResponse().withHeader("Content-Type", contentType) + .withHeader("Content-Length", contentLength).withStatus(200).withBodyFile(bodyFile))); + } + + @Test + public void jmcPostInstallShouldMoveFilesIfRequired() 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("jmc", context); + mockWebServer(); + // act + install.run(); + + // assert + assertThat(context.getSoftwarePath().resolve("jmc")).exists(); + assertThat(context.getSoftwarePath().resolve("jmc/InstallTest.txt")).hasContent("This is a test file."); + + if (context.getSystemInfo().isWindows()) { + assertThat(context.getSoftwarePath().resolve("jmc/jmc.cmd")).exists(); + } else if (context.getSystemInfo().isLinux()) { + assertThat(context.getSoftwarePath().resolve("jmc/jmc")).exists(); + } + + if (context.getSystemInfo().isWindows() || context.getSystemInfo().isLinux()) { + assertThat(context.getSoftwarePath().resolve("jmc/HelloWorld.txt")).hasContent("Hello World!"); + assertThat(context.getSoftwarePath().resolve("jmc/JDK Mission Control")).doesNotExist(); + } + + if (context.getSystemInfo().isMac()) { + assertThat(context.getSoftwarePath().resolve("jmc/JDK Mission Control.app")).exists(); + assertThat(context.getSoftwarePath().resolve("jmc/JDK Mission Control.app/Contents")).exists(); + } + + } + +} \ No newline at end of file diff --git a/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeTestContext.java b/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeTestContext.java index d47ab227f..97e63143a 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeTestContext.java +++ b/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeTestContext.java @@ -63,7 +63,10 @@ public IdeProgressBar prepareProgressBar(String taskName, long size) { IdeProgressBarTestImpl progressBar = new IdeProgressBarTestImpl(taskName, size); IdeProgressBarTestImpl duplicate = this.progressBarMap.put(taskName, progressBar); - assert duplicate == null; + // If we have multiple downloads, we may have an existing "Downloading" key + if (!taskName.equals("Downloading")) { + assert duplicate == null; + } return progressBar; } diff --git a/cli/src/test/resources/__files/org.openjdk.jmc-8.3.0-linux.gtk.x86_64.tar.gz b/cli/src/test/resources/__files/org.openjdk.jmc-8.3.0-linux.gtk.x86_64.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..931b8471369fdcad3ea9384c59feee0ee429b1d6 GIT binary patch literal 267 zcmV+m0rdVKiwFP#i@;?B0PWVhYQr!T2H-On`VOag07sH_>D~;zP`U&<8`mW&vIUk5 z>D%|jP}(8j#qkiI4}*+0^Xos>QdY8I% zP8+SPr2b1yTWDYbEARig{2!YT;(P4F$2`A;$^7NN`7bo(ua!_d|Ccakjbqq%Jone< zT>rn?8s;?Fe{D|vH%Qkwb-?z z?O;FO)44g_9PFd((B)|%p{JyT_I*w}X;DT=a_@>#l*5v310WEQ#u3wTg0f6%qm(r& z2y`0n(g^KdS@ zxuYj!>4KHKYj(TG~$6)XDc62D+ntAv6qXtf^TMVab|v=f^&XeNl|`|zDH_KPJVcP zQBDfTR4`8g%vFS##s$>J!0@ZeDdN^7D-A^;8-%5S7@KKXxygFTxhbj18TkrIE~UA- zl?p&11q(f6Jp%=xt>u|{Df#8aN&()COd<@pJqt9RfssK0L;(Ss*O4`2dQ%0g1)|v* z$j0VOR8Mn3En;MlU| literal 0 HcmV?d00001 diff --git a/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/linux_x64.sha256 b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/linux_x64.sha256 new file mode 100644 index 000000000..ba966d272 --- /dev/null +++ b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/linux_x64.sha256 @@ -0,0 +1 @@ +cf666655da9bc097a7413af6cc5e9d930bc1f9267410613707f5e4aa724e3bf9 \ No newline at end of file diff --git a/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/linux_x64.urls b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/linux_x64.urls new file mode 100644 index 000000000..a0da161ed --- /dev/null +++ b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/linux_x64.urls @@ -0,0 +1 @@ +http://localhost:1111/jmcTest/linux \ No newline at end of file diff --git a/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/mac_x64.sha256 b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/mac_x64.sha256 new file mode 100644 index 000000000..628ce170c --- /dev/null +++ b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/mac_x64.sha256 @@ -0,0 +1 @@ +44036f764b9b3ac0e788499ab9f3746bfac47ed09f4c464423a582f5698cabc8 \ No newline at end of file diff --git a/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/mac_x64.urls b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/mac_x64.urls new file mode 100644 index 000000000..ef4e100c8 --- /dev/null +++ b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/mac_x64.urls @@ -0,0 +1 @@ +http://localhost:1111/jmcTest/macOS \ No newline at end of file diff --git a/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/status.json b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/status.json new file mode 100644 index 000000000..b58452d90 --- /dev/null +++ b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/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/jmc/jmc/8.3.0/windows_x64.urls b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/windows_x64.urls new file mode 100644 index 000000000..ce3f2f5ad --- /dev/null +++ b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/windows_x64.urls @@ -0,0 +1 @@ +http://localhost:1111/jmcTest/windows \ No newline at end of file diff --git a/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/windows_x64.urls.sha256 b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/windows_x64.urls.sha256 new file mode 100644 index 000000000..83cc5866b --- /dev/null +++ b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/windows_x64.urls.sha256 @@ -0,0 +1 @@ +5cbb836ceb159788f03aed5d2da9debb8fa269139dc0e1f6ffff671ac5367e6b \ No newline at end of file diff --git a/documentation/IDEasy-usage.asciidoc b/documentation/IDEasy-usage.asciidoc index 95b182c73..3e49c261c 100644 --- a/documentation/IDEasy-usage.asciidoc +++ b/documentation/IDEasy-usage.asciidoc @@ -15,6 +15,7 @@ include::variables.asciidoc[leveloffset=2] include::cli.asciidoc[leveloffset=2] include::docker-desktop-alternative.asciidoc[leveloffset=3] +include::jmc.asciidoc[leveloffset=3] <<<< diff --git a/documentation/jmc.asciidoc b/documentation/jmc.asciidoc new file mode 100644 index 000000000..75f4918b6 --- /dev/null +++ b/documentation/jmc.asciidoc @@ -0,0 +1,14 @@ +:toc: +toc::[] + +# Java Mission Control + +The `jmc` commandlet allows to install and setup https://www.oracle.com/java/technologies/jdk-mission-control.html[Java Mission Control]. To learn more about Java Mission Control, please go https://docs.oracle.com/en/java/java-components/jdk-mission-control/index.html[here]. + +The arguments (`devon jmc «args»`) are explained by the following table: + +[options="header"] +|======================= +|*Command* |*Meaning* +|`install jmc` |install Java Mission Control (or update and verify) +|`jmc «args»` |run Java Mission Control with the given `«args»` \ No newline at end of file From 5176b961112dad19ce88a25a913bf1a76e16d9b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Hohwiller?= Date: Mon, 12 Feb 2024 18:26:59 +0100 Subject: [PATCH 3/8] Create coding-conventions.asciidoc --- documentation/coding-conventions.asciidoc | 363 ++++++++++++++++++++++ 1 file changed, 363 insertions(+) create mode 100644 documentation/coding-conventions.asciidoc diff --git a/documentation/coding-conventions.asciidoc b/documentation/coding-conventions.asciidoc new file mode 100644 index 000000000..4fe3bfec3 --- /dev/null +++ b/documentation/coding-conventions.asciidoc @@ -0,0 +1,363 @@ += Coding Conventions + +The code should follow general conventions for Java (see http://www.oracle.com/technetwork/java/namingconventions-139351.html[Oracle Naming Conventions], https://google.github.io/styleguide/javaguide.html[Google Java Style], etc.). +We consider this as common sense instead of repeating this here. +The following sections give us additional conventions that we consider additionally. + +== Naming +We follow these additional naming rules: + +* Always use short but speaking names (for types, methods, fields, parameters, variables, constants, etc.). +* Avoid using existing type names from JDK (from `java.lang.*`, `java.util.*`, etc.) - so e.g. never name your own Java type `List`, `Error`, etc. +* Strictly avoid special characters in technical names (for files, types, fields, methods, properties, variables, database tables, columns, constraints, etc.). In other words only use Latin alpahnumeric ASCII characters with the common allowed technical separators for the accordign context (e.g. underscore) for technical names (even excluding whitespaces). +* For package segments and type names prefer singular forms (`CustomerEntity` instead of [line-through]`CustomersEntity`). Only use plural forms when there is no singular or it is really semantically required (e.g. for a container that contains multiple of such objects). +* Avoid having duplicate type names. The name of a class, interface, enum or annotation should be unique within your project unless this is intentionally desired in a special and reasonable situation. +* Avoid artificial naming constructs such as prefixes (`I*`) or suffixes (`*IF`) for interfaces. +* Use CamelCase even for abbreviations (`XmlUtil` instead of [line-through]`XMLUtil`) +* Avoid property/field names where the second character is upper-case at all (e.g. 'aBc'). See https://github.com/devonfw/cobigen/issues/1095[#1095] for details. +* Names of Generics should be easy to understand. Where suitable follow the common rule `E=Element`, `T=Type`, `K=Key`, `V=Value` but feel free to use longer names for more specific cases such as `ID`, `DTO` or `ENTITY`. The capitalized naming helps to distinguish a generic type from a regular class. + +== Obsolete APIs +Please avoid using the following APIs: + +* `java.util.Date` - use according type from `java.time.*` such as `LocalDate`, `LocalDateTime`, or `Instant` +* `java.util.Calendar` - same as above +* `java.sql.Date` - use `LocalDate` +* `java.sql.Time` - use `LocalTime` +* `java.sql.Timestamp` - use `Instant` or `LocalDateTime` +* `java.io.File` - use `java.nio.file.Path` +* `java.nio.file.Paths.get(...)` - use `java.nio.file.Path.of(...)` +* `java.util.Vector` - use `List` and `ArrayList` or `LinkedList` +* `java.lang.StringBuffer` - use `java.lang.StringBuilder` +* `java.lang.Runtime.exec(...) - use `ProcessBuilder` (we use `ProcessContext` on top) +* `com.google.common.base.Objects` - use `java.util.Objects` + +== Code-Documentation +As a general goal, the code should be easy to read and understand. Besides, clear naming the documentation is important. We follow these rules: + +* APIs (especially component interfaces) are properly documented with JavaDoc. +* JavaDoc shall provide actual value - we do not write JavaDoc to satisfy tools such as checkstyle but to express information not already available in the signature. +* We make use of `{@link}` tags in JavaDoc to make it more expressive. +* JavaDoc of APIs describes how to use the type or method and not how the implementation internally works. +* To document implementation details, we use code comments (e.g. `// we have to flush explicitly to ensure version is up-to-date`). This is only needed for complex logic. +* Avoid the pointless `{@inheritDoc}` as since Java 1.5 there is the `@Override` annotation for overridden methods and your JavaDoc is inherited automatically even without any JavaDoc comment at all. + +== Catching and handling Exceptions +When catching exceptions always ensure the following: + +* Never call `printStackTrace()` method on an exception +* Either log or wrap and re-throw the entire catched exception. Be aware that the cause(s) of an exception is very valuable information. If you loose such information by improper exception-handling you may be unable to properly analyse production problems what can cause severe issues. +** If you wrap and re-throw an exception ensure that the catched exception is passed as cause to the newly created and thrown exception. +** If you log an exception ensure that the entire exception is passed as argument to the logger (and not only the result of `getMessage()` or `toString()` on the exception). + +[source,java] +---- +try { + doSomething(); +} catch (Exception e) { + // bad + throw new IllegalStateException("Something failed"); +} +---- + +This will result in a stacktrace like this: +[source,java] +---- +Exception in thread "main" java.lang.IllegalStateException: Something failed + at com.devonfw.tools.ide.ExceptionHandling.main(ExceptionHandling.java:14) +---- + +As you can see we have no information and clue what the catched `Exception` was and what really went wrong in `doSomething()`. + +Instead always rethrow with the original exception: +[source,java] +---- +try { + doSomething(); +} catch (Exception e) { + // good + throw new IllegalStateExeception("Something failed", e); +} +---- + +Now our stacktrace will look similar to this: +[source,java] +---- +Exception in thread "main" java.lang.IllegalStateException: Something failed + at com.devonfw.tools.ide.ExceptionHandling.main(ExceptionHandling.java:14) +Caused by: java.lang.IllegalArgumentException: Very important information + at com.devonfw.tools.ide.ExceptionHandling.doSomething(ExceptionHandling.java:23) + at com.devonfw.tools.ide.ExceptionHandling.main(ExceptionHandling.java:12) +---- + +Never do this severe mistake to lose this original exception cause! + +The same applies when logging the exception: +[source,java] +---- +try { + doSomething(); +} catch (Exception e) { + // bad + LOG.error("Something failed: " + e.getMessage()); +} +---- + +Instead include the full exception and use your logger properly: +[source,java] +---- +try { + doSomething(); +} catch (Exception e) { + // bad + LOG.error("Something failed: {}", e.getMessage(), e); +} +---- + +Also please add contextual information to the message for the logger or the new exception. +So instead of just saying "Something failed" a really good example could look like this: +[source,java] +---- +LOG.error("An unexpected error occurred whilst downloading the tool {} with edition {} and version {} from URL {}.", tool, edition, version, url, e); +---- + +=== Prefer general API +Avoid unnecessary strong bindings: + +* Do not bind your code to implementations such as `Vector` or `ArrayList` instead of `List` +* In APIs for input (=parameters) always consider to make little assumptions: +** prefer `Collection` over `List` or `Set` where the difference does not matter (e.g. only use `Set` when you require uniqueness or highly efficient `contains`) +** consider preferring `Collection` over `Collection` when `Foo` is an interface or super-class + +=== Prefer primitive types +In general prefer primitive types (`boolean`, `int`, `long`, ...) instead of corresponding boxed object types (`Boolean`, `Integer`, `Long`, ...). +Only use boxed object types, if you explicitly want to allow `null` as a value. +Typically you never want to use `Boolean` but instead use `boolean`. +[source,java] +---- +// bad +public Boolean isEmpty { + return size() == 0; +} +---- +Instead always use the primitive `boolean` type: +[source,java] +---- +// fine +public boolean isEmpty { + return size() == 0; +} +---- + +== BLOBs +Avoid using `byte[]` for BLOBs as this will load them entirely into your memory. +This will cause performance issues or out of memory errors. +Instead, use streams when dealing with BLOBs (`InputStream`, `OutputStream`, `Reader`, `Writer`). + +== Stateless Programming +When implementing logic as components or _beans_, we strongly encourage stateless programming. +This is not about data objects (e.g. JavaBeans) that are stateful by design. +Instead this applies to things like `IdeContext` and all its related child-objects. +Such classes shall never be modified after initialization. +Methods called at runtime (after initialization) do not assign fields (member variables of your class) or mutate the object stored in a field. +This allows your component or bean to be stateless and thread-safe. +Therefore it can be initialized as a singleton so only one instance is created and shared accross all threads of the application. +Ideally all fields are declared `final` otherwise be careful not to change them dynamically (except for lazy-initializations). +Here is an example: +[source,java] +---- +public class GitHelperImpl implements GitHelper { + + // bad + private boolean force; + + @Overide + public void gitPullOrClone(boolean force, Path target, String gitUrl) { + this.force = force; + if (Files.isDirectory(target.resolve(".git"))) { + gitPull(target); + } else { + gitClone(target, gitUrl); + } + } + + private void gitClone(Path target, String gitUrl) { ... } + + private void gitPull(Path target) { ... } +} +---- + +As you can see in the `bad` code fields of the class are assigned at runtime. +Since IDEasy is not implementing a concurremt multi-user application this is not really critical. +However, it is best-practice to avoid this pattern and generally follow thread-safe programming as best-practice: +[source,java] +---- +public class GitHelperImpl implements GitHelper { + + // fine + @Overide + public void gitPullOrClone(boolean force, Path target, String gitUrl) { + if (Files.isDirectory(target.resolve(".git"))) { + gitPull(force, target); + } else { + gitClone(force, target, gitUrl); + } + } + + private void gitClone(boolean force, Path target, String gitUrl) { ... } + + private void gitPull(boolean force, Path target) { ... } +} +---- + +== Closing Resources +Resources such as streams (`InputStream`, `OutputStream`, `Reader`, `Writer`) or generally speaking implementations of `AutoClosable` need to be handled properly. +Therefore, it is important to follow these rules: + +* Each resource has to be closed properly, otherwise you will get out of file handles, TX sessions, memory leaks or the like. +* Where possible avoid to deal with such resources manually. +* In case you have to deal with resources manually (e.g. binary streams) ensure to close them properly via `try-with-resource` pattern. See the example below for details. + +Closing streams and other such resources is error prone. Have a look at the following example: +[source,java] +---- +// bad +try { + InputStream in = new FileInputStream(file); + readData(in); + in.close(); +} catch (IOException e) { + throw new IllegalStateException("Failed to read data.", e); +} +---- + +The code above is wrong as in case of an `IOException` the `InputStream` is not properly closed. +In a server application such mistakes can cause severe errors that typically will only occur in production. +As such resources implement the `AutoCloseable` interface you can use the `try-with-resource` syntax to write correct code. +The following code shows a correct version of the example: +[source,java] +---- +// fine +try (InputStream in = new FileInputStream(file)) { + readData(in); +} catch (IOException e) { + throw new IllegalStateException("Failed to read data.", e); +} +---- + +== Lambdas and Streams +With Java8 you have cool new features like lambdas and monads like (`Stream`, `CompletableFuture`, `Optional`, etc.). +However, these new features can also be misused or led to code that is hard to read or debug. To avoid pain, we give you the following best practices: + +. Learn how to use the new features properly before using. Developers are often keen on using cool new features. When you do your first experiments in your project code you will cause deep pain and might be ashamed afterwards. Please study the features properly. Even Java8 experts still write for loops to iterate over collections, so only use these features where it really makes sense. +. Streams shall only be used in fluent API calls as a Stream can not be forked or reused. +. Each stream has to have exactly one terminal operation. +. Do not write multiple statements into lambda code: ++ +[source,java] +---- +// bad +collection.stream().map(x -> { +Foo foo = doSomething(x); +... +return foo; +}).collect(Collectors.toList()); +---- ++ +This style makes the code hard to read and debug. Never do that! Instead, extract the lambda body to a private method with a meaningful name: ++ +[source,java] +---- +// fine +collection.stream().map(this::convertToFoo).collect(Collectors.toList()); +---- +. Do not use `parallelStream()` in general code (that will run on server side) unless you know exactly what you are doing and what is going on under the hood. Some developers might think that using parallel streams is a good idea as it will make the code faster. However, if you want to do performance optimizations talk to your technical lead (architect). Many features such as security and transactions will rely on contextual information that is associated with the current thread. Hence, using parallel streams will most probably cause serious bugs. Only use them for standalone (CLI) applications or for code that is just processing large amounts of data. +. Do not perform operations on a sub-stream inside a lambda: ++ +[source,java] +---- +set.stream().flatMap(x -> x.getChildren().stream().filter(this::isSpecial)).collect(Collectors.toList()); // bad +set.stream().flatMap(x -> x.getChildren().stream()).filter(this::isSpecial).collect(Collectors.toList()); // fine +---- +. Only use `collect` at the end of the stream: ++ +[source,java] +---- +set.stream().collect(Collectors.toList()).forEach(...) // bad +set.stream().peek(...).collect(Collectors.toList()) // fine +---- +. Lambda parameters with Types inference ++ +[source,java] +---- +(String a, Float b, Byte[] c) -> a.toString() + Float.toString(b) + Arrays.toString(c) // bad +(a,b,c) -> a.toString() + Float.toString(b) + Arrays.toString(c) // fine + +Collections.sort(personList, (Person p1, Person p2) -> p1.getSurName().compareTo(p2.getSurName())); // bad +Collections.sort(personList, (p1, p2) -> p1.getSurName().compareTo(p2.getSurName())); // fine +---- +. Avoid Return Braces and Statement ++ +[source,java] +---- + a -> { return a.toString(); } // bad + a -> a.toString(); // fine +---- +. Avoid Parentheses with Single Parameter ++ +[source,java] +---- +(a) -> a.toString(); // bad + a -> a.toString(); // fine +---- +. Avoid if/else inside foreach method. Use Filter method & comprehension ++ +[source,java] +---- +// bad +static public Iterator TwitterHandles(Iterator authors, string company) { + final List result = new ArrayList (); + foreach (Author a : authors) { + if (a.Company.equals(company)) { + String handle = a.TwitterHandle; + if (handle != null) + result.Add(handle); + } + } + return result; + } +---- ++ +[source,java] +---- +// fine +public List twitterHandles(List authors, String company) { + return authors.stream() + .filter(a -> null != a && a.getCompany().equals(company)) + .map(a -> a.getTwitterHandle()) + .collect(toList()); + } +---- + +== Optionals +With `Optional` you can wrap values to avoid a `NullPointerException` (NPE). However, it is not a good code-style to use `Optional` for every parameter or result to express that it may be null. For such case use `@Nullable` or even better instead annotate `@NotNull` where `null` is not acceptable. + +However, `Optional` can be used to prevent NPEs in fluent calls (due to the lack of the elvis operator): +[source,java] +---- +Long id; +id = fooCto.getBar().getBar().getId(); // may cause NPE +id = Optional.ofNullable(fooCto).map(FooCto::getBar).map(BarCto::getBar).map(BarEto::getId).orElse(null); // null-safe +---- + +== Encoding +Encoding (esp. Unicode with combining characters and surrogates) is a complex topic. +Please study this topic if you have to deal with encodings and processing of special characters. +For the basics follow these recommendations: + +* Whenever possible prefer unicode (UTF-8 or better) as encoding. +* Do not cast from `byte` to `char` (unicode characters can be composed of multiple bytes, such cast may only work for ASCII characters) +* Never convert the case of a String using the default locale. E.g. if you do `"HI".toLowerCase()` and your system locale is Turkish, then the output will be "hı" instead of "hi", which can lead to wrong assumptions and serious problems. If you want to do a "universal" case conversion always explicitly use an according western locale (e.g. `toLowerCase(Locale.US)`). Consider using a helper class (see e.g. https://github.com/m-m-m/base/blob/master/core/src/main/java/io/github/mmm/base/text/CaseHelper.java[CaseHelper]) or create your own little static utility for that in your project. +* Write your code independent from the default encoding (system property `file.encoding`) - this will most likely differ in JUnit from production environment +** Always provide an encoding when you create a `String` from `byte[]`: `new String(bytes, encoding)` +** Always provide an encoding when you create a `Reader` or `Writer` : `new InputStreamReader(inStream, encoding)` From 9e09f68ada4e3c03b803a992e7a69db4e2c4f5b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Hohwiller?= Date: Mon, 12 Feb 2024 18:31:13 +0100 Subject: [PATCH 4/8] Update coding-conventions.asciidoc --- documentation/coding-conventions.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/coding-conventions.asciidoc b/documentation/coding-conventions.asciidoc index 4fe3bfec3..fc850598c 100644 --- a/documentation/coding-conventions.asciidoc +++ b/documentation/coding-conventions.asciidoc @@ -16,6 +16,7 @@ We follow these additional naming rules: * Use CamelCase even for abbreviations (`XmlUtil` instead of [line-through]`XMLUtil`) * Avoid property/field names where the second character is upper-case at all (e.g. 'aBc'). See https://github.com/devonfw/cobigen/issues/1095[#1095] for details. * Names of Generics should be easy to understand. Where suitable follow the common rule `E=Element`, `T=Type`, `K=Key`, `V=Value` but feel free to use longer names for more specific cases such as `ID`, `DTO` or `ENTITY`. The capitalized naming helps to distinguish a generic type from a regular class. +* For `boolean` getter methods use `is` prefix instead of `get` but for `boolean` variable names avoid the `is` prefix (`boolean force = ...` instead of `boolean isForce = ...`) unless the name is a reserved keyword (e.g. `boolean abstract = true` will result in a compile error so consider using `boolean isAbstract = true` instead). == Obsolete APIs Please avoid using the following APIs: From 410d834c59bf28b8c3a78cfaf11c887f8588b5b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Hohwiller?= Date: Mon, 12 Feb 2024 18:56:34 +0100 Subject: [PATCH 5/8] Update coding-conventions.asciidoc --- documentation/coding-conventions.asciidoc | 114 +++++++++++++++++----- 1 file changed, 90 insertions(+), 24 deletions(-) diff --git a/documentation/coding-conventions.asciidoc b/documentation/coding-conventions.asciidoc index fc850598c..fedf6029a 100644 --- a/documentation/coding-conventions.asciidoc +++ b/documentation/coding-conventions.asciidoc @@ -110,7 +110,7 @@ Instead include the full exception and use your logger properly: try { doSomething(); } catch (Exception e) { - // bad + // good LOG.error("Something failed: {}", e.getMessage(), e); } ---- @@ -144,12 +144,100 @@ public Boolean isEmpty { Instead always use the primitive `boolean` type: [source,java] ---- -// fine +// good public boolean isEmpty { return size() == 0; } ---- +== Optionals +With `Optional` you can wrap values to avoid a `NullPointerException` (NPE). +However, it is not a good code-style to use `Optional` for every parameter or result to express that it may be null. +For such case use JavaDoc (or consider `@Nullable` or even better instead annotate `@NotNull` where `null` is not acceptable). + +However, `Optional` can be used to prevent NPEs in fluent calls (due to the lack of the elvis operator): +[source,java] +---- +Long id; +id = fooCto.getBar().getBar().getId(); // may cause NPE +id = Optional.ofNullable(fooCto).map(FooCto::getBar).map(BarCto::getBar).map(BarEto::getId).orElse(null); // null-safe +---- + +== Avoid catching NPE + +Please avoid catching `NullPointerException`: +[source,java] +---- +// bad +try { + variable.getFoo().doSomething(); +} catch (NullPointerException e) { + LOG.warning("foo was null"); +} +---- + +Better explicitly check for `null`: +[source,java] +---- +// good +Foo foo = null; +if (variable != null) { + foo = variable.getFoo(); +} +if (foo == null) { + LOG.warning("foo was null"); +} else { + foo.doSomething(); +} +---- + +Please note that the term `Exception` is used for something exceptional. +Further creating an instance of an `Exception` or `Throable` in Java is expensive as the entire Strack has to be collected and copied into arrays, etc. causing significant overhead. +This should always be avoided in situations we can easily avoid with a simple `if` check. + +== Consider extractig local variable for multiple method calls + +Calling the same method (cascades) multiple times is redundant and reduces readability and performance: +[source,java] +---- +// bad +Candidate candidate; +if (variable.getFoo().getFirst().getSize() > variable.getFoo().getSecond().getSize()) { + candidate = variable.getFoo().getFirst(); +} else { + candidate = variable.getFoo().getSecond(); +} +---- + +The method `getFoo()` is used in 4 places and called 3 times. Maybe the method call is expensive? +[source,java] +---- +// good +Candidate candidate; +Foo foo = variable.getFoo(); +Candidate first = foo.getFirst(); +Candidate second = foo.getSecond(); +if (first.getSize() > second.getSize()) { + candidate = first; +} else { + candidate = second; +} +---- + +Please note that your IDE can automatically refactor your code extracting all occurrences of the same method call within the method body to a local variable. + +== Encoding +Encoding (esp. Unicode with combining characters and surrogates) is a complex topic. +Please study this topic if you have to deal with encodings and processing of special characters. +For the basics follow these recommendations: + +* Whenever possible prefer unicode (UTF-8 or better) as encoding. +* Do not cast from `byte` to `char` (unicode characters can be composed of multiple bytes, such cast may only work for ASCII characters) +* Never convert the case of a String using the default locale. E.g. if you do `"HI".toLowerCase()` and your system locale is Turkish, then the output will be "hı" instead of "hi", which can lead to wrong assumptions and serious problems. If you want to do a "universal" case conversion always explicitly use an according western locale (e.g. `toLowerCase(Locale.US)`). Consider using a helper class (see e.g. https://github.com/m-m-m/base/blob/master/core/src/main/java/io/github/mmm/base/text/CaseHelper.java[CaseHelper]) or create your own little static utility for that in your project. +* Write your code independent from the default encoding (system property `file.encoding`) - this will most likely differ in JUnit from production environment +** Always provide an encoding when you create a `String` from `byte[]`: `new String(bytes, encoding)` +** Always provide an encoding when you create a `Reader` or `Writer` : `new InputStreamReader(inStream, encoding)` + == BLOBs Avoid using `byte[]` for BLOBs as this will load them entirely into your memory. This will cause performance issues or out of memory errors. @@ -340,25 +428,3 @@ public List twitterHandles(List authors, String company) { } ---- -== Optionals -With `Optional` you can wrap values to avoid a `NullPointerException` (NPE). However, it is not a good code-style to use `Optional` for every parameter or result to express that it may be null. For such case use `@Nullable` or even better instead annotate `@NotNull` where `null` is not acceptable. - -However, `Optional` can be used to prevent NPEs in fluent calls (due to the lack of the elvis operator): -[source,java] ----- -Long id; -id = fooCto.getBar().getBar().getId(); // may cause NPE -id = Optional.ofNullable(fooCto).map(FooCto::getBar).map(BarCto::getBar).map(BarEto::getId).orElse(null); // null-safe ----- - -== Encoding -Encoding (esp. Unicode with combining characters and surrogates) is a complex topic. -Please study this topic if you have to deal with encodings and processing of special characters. -For the basics follow these recommendations: - -* Whenever possible prefer unicode (UTF-8 or better) as encoding. -* Do not cast from `byte` to `char` (unicode characters can be composed of multiple bytes, such cast may only work for ASCII characters) -* Never convert the case of a String using the default locale. E.g. if you do `"HI".toLowerCase()` and your system locale is Turkish, then the output will be "hı" instead of "hi", which can lead to wrong assumptions and serious problems. If you want to do a "universal" case conversion always explicitly use an according western locale (e.g. `toLowerCase(Locale.US)`). Consider using a helper class (see e.g. https://github.com/m-m-m/base/blob/master/core/src/main/java/io/github/mmm/base/text/CaseHelper.java[CaseHelper]) or create your own little static utility for that in your project. -* Write your code independent from the default encoding (system property `file.encoding`) - this will most likely differ in JUnit from production environment -** Always provide an encoding when you create a `String` from `byte[]`: `new String(bytes, encoding)` -** Always provide an encoding when you create a `Reader` or `Writer` : `new InputStreamReader(inStream, encoding)` From c815ca34b9eb342e3c51ebec4ac5cf8b1f7f796e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Hohwiller?= Date: Tue, 13 Feb 2024 11:33:29 +0100 Subject: [PATCH 6/8] improved code-style, added missing JavaDoc, code-cleanup (#198) --- .../tools/ide/commandlet/Commandlet.java | 1 - .../ide/commandlet/CompleteCommandlet.java | 1 - .../ide/commandlet/EditionGetCommandlet.java | 3 - .../tools/ide/commandlet/ShellCommandlet.java | 5 +- .../ide/commandlet/VersionListCommandlet.java | 2 +- .../ide/commandlet/VersionSetCommandlet.java | 5 - .../devonfw/tools/ide/common/SystemPath.java | 11 ++- .../ide/completion/CompletionCandidate.java | 4 +- .../tools/ide/completion/IdeCompleter.java | 24 +---- .../tools/ide/context/AbstractIdeContext.java | 19 ++-- .../tools/ide/context/IdeContextConsole.java | 5 +- .../ide/environment/EnvironmentVariables.java | 3 +- .../devonfw/tools/ide/io/FileAccessImpl.java | 9 +- .../devonfw/tools/ide/merge/JsonMerger.java | 24 ++--- .../devonfw/tools/ide/merge/XmlMerger.java | 3 +- .../tools/ide/os/WindowsPathSyntax.java | 28 ++---- .../tools/ide/process/ProcessContext.java | 24 ++++- .../tools/ide/process/ProcessContextImpl.java | 3 +- .../tools/ide/tool/PluginBasedCommandlet.java | 10 +- .../tools/ide/tool/ToolCommandlet.java | 57 +++++------ .../androidstudio/AndroidJsonDownload.java | 4 +- .../tool/androidstudio/AndroidJsonItem.java | 2 +- .../com/devonfw/tools/ide/tool/az/Azure.java | 4 +- .../tools/ide/tool/eclipse/Eclipse.java | 3 +- .../ide/tool/gcloud/GCloudUrlUpdater.java | 2 +- .../tools/ide/tool/gh/GhUrlUpdater.java | 2 +- .../ide/tool/gradle/GradleUrlUpdater.java | 8 +- .../com/devonfw/tools/ide/tool/jmc/Jmc.java | 35 ++++--- .../tool/lazydocker/LazyDockerUrlUpdater.java | 4 +- .../tools/ide/tool/npm/NpmUrlUpdater.java | 2 +- .../tool/terraform/TerraformUrlUpdater.java | 2 +- .../tools/ide/url/model/UrlMetadata.java | 22 ++--- .../ide/url/updater/AbstractUrlUpdater.java | 1 + .../tools/ide/url/updater/UpdateManager.java | 2 +- .../ide/variable/VariableDefinitionPath.java | 3 +- .../ide/version/VersionComparisonResult.java | 19 ++-- .../tools/ide/version/VersionLetters.java | 5 +- .../tools/ide/version/VersionSegment.java | 5 +- .../com/devonfw/tools/ide/Jmc/JmcTest.java | 3 +- .../cli/AutocompletionReaderTestSupport.java | 13 ++- .../ide/commandlet/ContextCommandletTest.java | 1 - .../commandlet/EditionGetCommandletTest.java | 10 +- .../ide/commandlet/InstallCommandletTest.java | 5 +- .../commandlet/VersionSetCommandletTest.java | 2 +- .../ide/completion/IdeCompleterTest.java | 99 +++++++++++++------ .../ide/context/AbstractIdeContextTest.java | 7 +- .../ide/context/AbstractIdeTestContext.java | 2 +- .../tools/ide/context/IdeTestContext.java | 3 +- .../tools/ide/context/IdeTestContextMock.java | 4 +- .../ide/environment/SortedPropertiesTest.java | 2 - .../tools/ide/io/IdeProgressBarTest.java | 2 +- .../tools/ide/io/IdeProgressBarTestImpl.java | 8 +- .../tools/ide/merge/DirectoryMergerTest.java | 3 +- .../devonfw/tools/ide/os/MacOsHelperTest.java | 3 +- .../ide/os/SystemInformationImplTest.java | 5 - .../tools/ide/os/SystemInformationMock.java | 5 - .../ide/property/LocalePropertyTest.java | 4 +- .../tool/ExamplePluginBasedCommandlet.java | 5 +- .../ide/tool/PluginBasedCommandletTest.java | 20 ++-- .../tools/ide/tool/UrlUpdaterMock.java | 9 +- .../tools/ide/tool/UrlUpdaterMockSingle.java | 4 - .../tools/ide/tool/UrlUpdaterTest.java | 10 +- .../AndroidStudioJsonUrlUpdaterTest.java | 7 +- .../intellij/IntellijJsonUrlUpdaterTest.java | 7 +- .../ide/tool/python/PythonUrlUpdaterTest.java | 3 +- .../ide/url/model/UrlStatusFileTest.java | 4 +- 66 files changed, 295 insertions(+), 321 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/Commandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/Commandlet.java index 06f921e87..9e18715d6 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/Commandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/Commandlet.java @@ -7,7 +7,6 @@ import java.util.Map; import java.util.Objects; -import com.devonfw.tools.ide.completion.CompletionCandidateCollector; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.property.KeywordProperty; import com.devonfw.tools.ide.property.Property; diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CompleteCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CompleteCommandlet.java index e3708b052..a9d856a98 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CompleteCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CompleteCommandlet.java @@ -1,6 +1,5 @@ package com.devonfw.tools.ide.commandlet; -import java.util.Collections; import java.util.List; import com.devonfw.tools.ide.cli.CliArguments; diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/EditionGetCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/EditionGetCommandlet.java index 5c2f11526..23c507377 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/EditionGetCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/EditionGetCommandlet.java @@ -1,13 +1,10 @@ package com.devonfw.tools.ide.commandlet; -import com.devonfw.tools.ide.cli.CliException; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.property.ToolProperty; import com.devonfw.tools.ide.tool.ToolCommandlet; import com.devonfw.tools.ide.version.VersionIdentifier; -import static com.devonfw.tools.ide.process.ProcessResult.TOOL_NOT_INSTALLED; - /** * An internal {@link Commandlet} to get the installed edition for a tool. * diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/ShellCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/ShellCommandlet.java index c2d9fd624..4457ac55d 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/ShellCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/ShellCommandlet.java @@ -17,12 +17,10 @@ import com.devonfw.tools.ide.cli.CliArgument; import com.devonfw.tools.ide.cli.CliArguments; -import com.devonfw.tools.ide.cli.CliException; import com.devonfw.tools.ide.completion.IdeCompleter; import com.devonfw.tools.ide.context.AbstractIdeContext; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.property.BooleanProperty; -import com.devonfw.tools.ide.property.FlagProperty; import com.devonfw.tools.ide.property.KeywordProperty; import com.devonfw.tools.ide.property.Property; @@ -166,8 +164,7 @@ private boolean apply(CliArgument argument, Commandlet commandlet) { } currentProperty = valueIterator.next(); this.context.trace("Next value candidate is {}", currentProperty); - if (currentProperty instanceof KeywordProperty) { - KeywordProperty keyword = (KeywordProperty) currentProperty; + if (currentProperty instanceof KeywordProperty keyword) { if (keyword.matches(arg)) { keyword.setValue(Boolean.TRUE); this.context.trace("Keyword matched"); diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/VersionListCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/VersionListCommandlet.java index be8cd08d4..1b4ab40ec 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/VersionListCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/VersionListCommandlet.java @@ -7,7 +7,7 @@ /** * An internal {@link Commandlet} to list versions for a tool. * - * @see ToolCommandlet#listVersions()) + * @see ToolCommandlet#listVersions() */ public class VersionListCommandlet extends Commandlet { diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/VersionSetCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/VersionSetCommandlet.java index 215156ce5..1bba9e31e 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/VersionSetCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/VersionSetCommandlet.java @@ -1,15 +1,10 @@ package com.devonfw.tools.ide.commandlet; -import java.util.List; -import java.util.stream.IntStream; - -import com.devonfw.tools.ide.completion.CompletionCandidateCollector; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.property.ToolProperty; import com.devonfw.tools.ide.property.VersionProperty; import com.devonfw.tools.ide.tool.ToolCommandlet; import com.devonfw.tools.ide.version.VersionIdentifier; -import com.devonfw.tools.ide.version.VersionSegment; /** * An internal {@link Commandlet} to set a tool version. diff --git a/cli/src/main/java/com/devonfw/tools/ide/common/SystemPath.java b/cli/src/main/java/com/devonfw/tools/ide/common/SystemPath.java index 3ad111467..a3b5415af 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/common/SystemPath.java +++ b/cli/src/main/java/com/devonfw/tools/ide/common/SystemPath.java @@ -4,7 +4,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -26,7 +25,7 @@ public class SystemPath { private final Map tool2pathMap; private final List paths; - + private final IdeContext context; private static final List EXTENSION_PRIORITY = List.of(".exe", ".cmd", ".bat", ".msi", ".ps1", ""); @@ -61,7 +60,7 @@ public SystemPath(String envPath, Path softwarePath, char pathSeparator, IdeCont this.paths = new ArrayList<>(); String[] envPaths = envPath.split(Character.toString(pathSeparator)); for (String segment : envPaths) { - Path path = Paths.get(segment); + Path path = Path.of(segment); String tool = getTool(path, softwarePath); if (tool == null) { this.paths.add(path); @@ -129,7 +128,13 @@ private Path findBinaryInOrder(Path path, String tool) { return null; } + /** + * @param toolPath the {@link Path} to the tool installation. + * @return the {@link Path} to the binary executable of the tool. E.g. is "software/mvn" is given + * "software/mvn/bin/mvn" could be returned. + */ public Path findBinary(Path toolPath) { + Path parent = toolPath.getParent(); String fileName = toolPath.getFileName().toString(); diff --git a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidate.java b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidate.java index cec685058..c62bd07d8 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidate.java +++ b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidate.java @@ -1,7 +1,5 @@ package com.devonfw.tools.ide.completion; -import com.devonfw.tools.ide.version.VersionSegment; - /** * Candidate for auto-completion. * @@ -13,6 +11,6 @@ public record CompletionCandidate(String text, String description) implements Co @Override public int compareTo(CompletionCandidate o) { - return text.compareTo(o.text); + return this.text.compareTo(o.text); } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/completion/IdeCompleter.java b/cli/src/main/java/com/devonfw/tools/ide/completion/IdeCompleter.java index 083378bcb..4f2edeb8f 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/completion/IdeCompleter.java +++ b/cli/src/main/java/com/devonfw/tools/ide/completion/IdeCompleter.java @@ -1,32 +1,14 @@ package com.devonfw.tools.ide.completion; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; import java.util.List; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import com.devonfw.tools.ide.cli.CliArguments; -import com.devonfw.tools.ide.completion.CompletionCandidate; -import com.devonfw.tools.ide.context.AbstractIdeContext; import org.jline.reader.Candidate; import org.jline.reader.Completer; import org.jline.reader.LineReader; import org.jline.reader.ParsedLine; -import org.jline.reader.impl.completer.ArgumentCompleter; -import org.jline.reader.impl.completer.NullCompleter; -import org.jline.utils.AttributedString; - -import com.devonfw.tools.ide.commandlet.Commandlet; -import com.devonfw.tools.ide.commandlet.HelpCommandlet; -import com.devonfw.tools.ide.context.IdeContext; -import com.devonfw.tools.ide.property.Property; -import com.devonfw.tools.ide.property.ToolProperty; -import com.devonfw.tools.ide.property.VersionProperty; -import com.devonfw.tools.ide.tool.ToolCommandlet; -import com.devonfw.tools.ide.version.VersionIdentifier; + +import com.devonfw.tools.ide.cli.CliArguments; +import com.devonfw.tools.ide.context.AbstractIdeContext; /** * Implements the {@link Completer} for jline3 autocompletion. Inspired by picocli 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 7dc3fc134..9cb1533a3 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 @@ -4,7 +4,6 @@ import java.net.InetAddress; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.nio.file.attribute.FileTime; import java.time.Duration; import java.util.HashMap; @@ -141,7 +140,7 @@ public AbstractIdeContext(IdeLogLevel minLogLevel, Function dir - source = (source.toString().isEmpty()) ? Paths.get(".") : source; + source = (source.toString().isEmpty()) ? Path.of(".") : source; } } else { // source is relative if (relative) { @@ -366,7 +365,7 @@ private Path adaptPath(Path source, Path targetLink, boolean relative) throws IO // this ../d1/../d2 to ../d2 source = targetLink.getParent() .relativize(targetLink.resolveSibling(source).toRealPath(LinkOption.NOFOLLOW_LINKS)); - source = (source.toString().isEmpty()) ? Paths.get(".") : source; + source = (source.toString().isEmpty()) ? Path.of(".") : source; } else { // !relative try { source = targetLink.resolveSibling(source).toRealPath(LinkOption.NOFOLLOW_LINKS); @@ -532,7 +531,7 @@ private void unpack(Path file, Path targetDir, Function mergeAndResolveObject((JsonObject) json, (JsonObject) mergeJson, variables, status, src); + case ARRAY -> mergeAndResolveArray((JsonArray) json, (JsonArray) mergeJson, variables, status, src); + case STRING -> mergeAndResolveString((JsonString) json, (JsonString) mergeJson, variables, status, src); + case NUMBER, FALSE, TRUE, NULL -> mergeAndResolveNativeType(json, mergeJson, variables, status); + default -> { this.context.error("Undefined JSON type {}", json.getClass()); - return null; - } + yield null; + } + }; } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/merge/XmlMerger.java b/cli/src/main/java/com/devonfw/tools/ide/merge/XmlMerger.java index 4be18367c..ae25cf5f9 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/merge/XmlMerger.java +++ b/cli/src/main/java/com/devonfw/tools/ide/merge/XmlMerger.java @@ -182,8 +182,7 @@ private void resolve(Element element, EnvironmentVariables variables, boolean in for (int i = 0; i < nodeList.getLength(); i++) { Node node = nodeList.item(i); - if (node instanceof Text) { - Text text = (Text) node; + if (node instanceof Text text) { String value = text.getNodeValue(); String resolvedValue; if (inverse) { diff --git a/cli/src/main/java/com/devonfw/tools/ide/os/WindowsPathSyntax.java b/cli/src/main/java/com/devonfw/tools/ide/os/WindowsPathSyntax.java index 91760184c..92f573e8b 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/os/WindowsPathSyntax.java +++ b/cli/src/main/java/com/devonfw/tools/ide/os/WindowsPathSyntax.java @@ -62,16 +62,11 @@ public String replaceDrive(String path, String drive) { throw new IllegalArgumentException(path); } String restPath = path.substring(3); - switch (this) { - case WINDOWS: - restPath = restPath.replace('/', '\\'); - break; - case MSYS: - restPath = restPath.replace('\\', '/'); - break; - default: - throw new IllegalStateException(toString()); - } + restPath = switch (this) { + case WINDOWS -> restPath.replace('/', '\\'); + case MSYS -> restPath.replace('\\', '/'); + default -> throw new IllegalStateException(toString()); + }; return getRootPath(drive) + restPath; } @@ -85,14 +80,11 @@ public String getRootPath(String drive) { if ((drive.length() != 1) || !isLowerLatinLetter(Character.toLowerCase(drive.charAt(0)))) { throw new IllegalArgumentException(drive); } - switch (this) { - case WINDOWS: - return drive.toUpperCase(Locale.ROOT) + ":\\"; - case MSYS: - return "/" + drive.toLowerCase(Locale.ROOT) + "/"; - default: - throw new IllegalStateException(toString()); - } + return switch (this) { + case WINDOWS -> drive.toUpperCase(Locale.ROOT) + ":\\"; + case MSYS -> "/" + drive.toLowerCase(Locale.ROOT) + "/"; + default -> throw new IllegalStateException(toString()); + }; } } 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 191ba706e..f2c6cc4a0 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 @@ -1,7 +1,6 @@ package com.devonfw.tools.ide.process; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.List; import java.util.Objects; @@ -41,7 +40,7 @@ public interface ProcessContext { */ default ProcessContext executable(String executable) { - return executable(Paths.get(executable)); + return executable(Path.of(executable)); } /** @@ -144,10 +143,25 @@ default int run() { * @param capture - {@code true} to capture standard {@link ProcessResult#getOut() out} and * {@link ProcessResult#getErr() err} in the {@link ProcessResult}, {@code false} otherwise (to redirect out * and err). - * @param isBackgroundProcess {@code true}, the process of the command will be run as background process, - * {@code false} otherwise it will be run as foreground process. * @return the {@link ProcessResult}. */ - ProcessResult run(boolean capture, boolean isBackgroundProcess); + default ProcessResult run(boolean capture) { + + return run(capture, false); + } + + /** + * 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 + * sub-sequent calls. + * + * @param capture - {@code true} to capture standard {@link ProcessResult#getOut() out} and + * {@link ProcessResult#getErr() err} in the {@link ProcessResult}, {@code false} otherwise (to redirect out + * and err). + * @param runInBackground {@code true}, the process of the command will be run as background process, {@code false} + * otherwise (it will be run as foreground process). + * @return the {@link ProcessResult}. + */ + ProcessResult run(boolean capture, boolean runInBackground); } 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 ba0db249b..a3a0ef379 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 @@ -7,7 +7,6 @@ import java.lang.ProcessBuilder.Redirect; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -225,7 +224,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"); + Path defaultPath = Path.of("C:\\Program Files\\Git\\bin\\bash.exe"); if (Files.exists(defaultPath)) { return defaultPath.toString(); } diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/PluginBasedCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/PluginBasedCommandlet.java index 2d92a6cec..898de06bf 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/PluginBasedCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/PluginBasedCommandlet.java @@ -3,7 +3,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -32,7 +31,7 @@ public abstract class PluginBasedCommandlet extends LocalToolCommandlet { * @param context the {@link IdeContext}. * @param tool the {@link #getName() tool name}. * @param tags the {@link #getTags() tags} classifying the tool. Should be created via {@link Set#of(Object) Set.of} - * method. + * method. */ public PluginBasedCommandlet(IdeContext context, String tool, Set tags) { @@ -59,6 +58,7 @@ protected Map getPluginsMap() { } private void loadPluginsFromDirectory(Map map, Path pluginsPath) { + if (Files.isDirectory(pluginsPath)) { try (Stream childStream = Files.list(pluginsPath)) { Iterator iterator = childStream.iterator(); @@ -86,6 +86,9 @@ protected boolean isPluginUrlNeeded() { return false; } + /** + * @return the {@link Path} to the folder with the plugin configuration files inside the settings. + */ protected Path getPluginsConfigPath() { return this.context.getSettingsPath().resolve(this.tool).resolve(IdeContext.FOLDER_PLUGINS); @@ -93,10 +96,9 @@ protected Path getPluginsConfigPath() { private Path getUserHomePluginsConfigPath() { - return context.getUserHome().resolve(Paths.get(".ide", "settings", this.tool, IdeContext.FOLDER_PLUGINS)); + return this.context.getUserHome().resolve(Path.of(".ide", "settings", this.tool, IdeContext.FOLDER_PLUGINS)); } - /** * @return the immutable {@link Collection} of {@link PluginDescriptor}s configured for this IDE tool. */ 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 6171cb459..be36fbc78 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 @@ -3,7 +3,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.List; import java.util.Set; import java.util.stream.Stream; @@ -88,34 +87,32 @@ public void run() { /** * Ensures the tool is installed and then runs this tool with the given arguments. * - * @param isBackgroundProcess {@code true}, the process of the command will be run as background process, - * {@code false} otherwise it will be run as foreground process. + * @param runInBackground {@code true}, the process of the command will be run as background process, {@code false} + * otherwise (it will be run as foreground process). * @param toolVersion the explicit version (pattern) to run. Typically {@code null} to ensure the configured version * is installed and use that one. Otherwise, the specified version will be installed in the software repository * without touching and IDE installation and used to run. - * @param args the commandline arguments to run the tool. + * @param args the command-line arguments to run the tool. */ - public void runTool(boolean isBackgroundProcess, VersionIdentifier toolVersion, String... args) { + public void runTool(boolean runInBackground, VersionIdentifier toolVersion, String... args) { Path binaryPath; - Path toolPath = Paths.get(getBinaryName()); + Path toolPath = Path.of(getBinaryName()); if (toolVersion == null) { install(true); binaryPath = toolPath; } else { throw new UnsupportedOperationException("Not yet implemented!"); } - ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.WARNING).executable(binaryPath) - .addArgs(args); + ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.WARNING).executable(binaryPath).addArgs(args); - pc.run(false, isBackgroundProcess); + pc.run(false, runInBackground); } /** - * See {@link ToolCommandlet#runTool(boolean, VersionIdentifier, String...)} method. - * - * @param toolVersion - * @param args + * @param toolVersion the explicit {@link VersionIdentifier} of the tool to run. + * @param args the command-line arguments to run the tool. + * @see ToolCommandlet#runTool(boolean, VersionIdentifier, String...) */ public void runTool(VersionIdentifier toolVersion, String... args) { @@ -213,12 +210,11 @@ private Path getProperInstallationSubDirOf(Path path) { try (Stream stream = Files.list(path)) { Path[] subFiles = stream.toArray(Path[]::new); if (subFiles.length == 0) { - throw new CliException("The downloaded package for the tool " + this.tool - + " seems to be empty as you can check in the extracted folder " + path); + throw new CliException("The downloaded package for the tool " + this.tool + " seems to be empty as you can check in the extracted folder " + path); } else if (subFiles.length == 1) { String filename = subFiles[0].getFileName().toString(); - if (!filename.equals(IdeContext.FOLDER_BIN) && !filename.equals(IdeContext.FOLDER_CONTENTS) - && !filename.endsWith(".app") && Files.isDirectory(subFiles[0])) { + if (!filename.equals(IdeContext.FOLDER_BIN) && !filename.equals(IdeContext.FOLDER_CONTENTS) && !filename.endsWith(".app") + && Files.isDirectory(subFiles[0])) { return getProperInstallationSubDirOf(subFiles[0]); } } @@ -260,10 +256,6 @@ protected void extract(Path file, Path targetDir) { fileAccess.copy(appPath, tmpDir); pc.addArgs("detach", "-force", mountPath); pc.run(); - // if [ -e "${target_dir}/Applications" ] - // then - // rm "${target_dir}/Applications" - // fi } else if ("msi".equals(extension)) { this.context.newProcess().executable("msiexec").addArgs("/a", file, "/qn", "TARGETDIR=" + tmpDir).run(); // msiexec also creates a copy of the MSI @@ -299,6 +291,13 @@ protected void extract(Path file, Path targetDir) { } } + /** + * Moves the extracted content to the final destination {@link Path}. May be overridden to customize the extraction + * process. + * + * @param from the source {@link Path} to move. + * @param to the target {@link Path} to move to. + */ protected void moveAndProcessExtraction(Path from, Path to) { this.context.getFileAccess().move(from, to); @@ -385,10 +384,8 @@ public String getInstalledEdition(Path toolPath) { } return edition; } catch (IOException e) { - throw new IllegalStateException("Couldn't determine the edition of " + getName() - + " from the directory structure of its software path " + toolPath - + ", assuming the name of the parent directory of the real path of the software path to be the edition " - + "of the tool.", e); + throw new IllegalStateException("Couldn't determine the edition of " + getName() + " from the directory structure of its software path " + toolPath + + ", assuming the name of the parent directory of the real path of the software path to be the edition " + "of the tool.", e); } } @@ -453,9 +450,8 @@ public void setVersion(VersionIdentifier version, boolean hint) { this.context.info("{}={} has been set in {}", name, version, settingsVariables.getSource()); EnvironmentVariables declaringVariables = variables.findVariable(name); if ((declaringVariables != null) && (declaringVariables != settingsVariables)) { - this.context.warning( - "The variable {} is overridden in {}. Please remove the overridden declaration in order to make the change affect.", - name, declaringVariables.getSource()); + this.context.warning("The variable {} is overridden in {}. Please remove the overridden declaration in order to make the change affect.", name, + declaringVariables.getSource()); } if (hint) { this.context.info("To install that version call the following command:"); @@ -498,9 +494,8 @@ public void setEdition(String edition, boolean hint) { this.context.info("{}={} has been set in {}", name, edition, settingsVariables.getSource()); EnvironmentVariables declaringVariables = variables.findVariable(name); if ((declaringVariables != null) && (declaringVariables != settingsVariables)) { - this.context.warning( - "The variable {} is overridden in {}. Please remove the overridden declaration in order to make the change affect.", - name, declaringVariables.getSource()); + this.context.warning("The variable {} is overridden in {}. Please remove the overridden declaration in order to make the change affect.", name, + declaringVariables.getSource()); } if (hint) { this.context.info("To install that edition call the following command:"); diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/androidstudio/AndroidJsonDownload.java b/cli/src/main/java/com/devonfw/tools/ide/tool/androidstudio/AndroidJsonDownload.java index 314cf06eb..950446bd7 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/androidstudio/AndroidJsonDownload.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/androidstudio/AndroidJsonDownload.java @@ -20,7 +20,7 @@ public class AndroidJsonDownload implements JsonObject { */ public String getLink() { - return link; + return this.link; } /** @@ -28,7 +28,7 @@ public String getLink() { */ public String getChecksum() { - return checksum; + return this.checksum; } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/androidstudio/AndroidJsonItem.java b/cli/src/main/java/com/devonfw/tools/ide/tool/androidstudio/AndroidJsonItem.java index a343825c8..0ae04ae69 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/androidstudio/AndroidJsonItem.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/androidstudio/AndroidJsonItem.java @@ -29,7 +29,7 @@ public String getVersion() { */ public List getDownload() { - return download; + return this.download; } } \ No newline at end of file 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 195ccf922..efe9681a9 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 @@ -1,6 +1,6 @@ package com.devonfw.tools.ide.tool.az; -import java.nio.file.Paths; +import java.nio.file.Path; import java.util.Set; import com.devonfw.tools.ide.common.Tag; @@ -35,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"), getToolPath().resolve("bin")); + this.context.getFileAccess().symlink(Path.of("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 c98794ff4..133c65d31 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 @@ -5,7 +5,6 @@ import java.nio.channels.FileLock; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.List; import java.util.Set; @@ -61,7 +60,7 @@ public boolean install(boolean silent) { */ protected ProcessResult runEclipse(boolean log, String... args) { - Path toolPath = Paths.get(getBinaryName()); + Path toolPath = Path.of(getBinaryName()); ProcessContext pc = this.context.newProcess(); if (log) { pc.errorHandling(ProcessErrorHandling.ERROR); diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/gcloud/GCloudUrlUpdater.java b/cli/src/main/java/com/devonfw/tools/ide/tool/gcloud/GCloudUrlUpdater.java index e046b0376..45f62d2b6 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/gcloud/GCloudUrlUpdater.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/gcloud/GCloudUrlUpdater.java @@ -12,7 +12,7 @@ public class GCloudUrlUpdater extends GithubUrlUpdater { private static final VersionIdentifier MIN_GCLOUD_VID = VersionIdentifier.of("299.0.0"); private static final VersionIdentifier MIN_ARM_GCLOUD_VID = VersionIdentifier.of("366.0.0"); private static final String BASE_URL = "https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-${version}-"; - + @Override protected String getTool() { diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/gh/GhUrlUpdater.java b/cli/src/main/java/com/devonfw/tools/ide/tool/gh/GhUrlUpdater.java index a0dd40801..bd8a30dbc 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/gh/GhUrlUpdater.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/gh/GhUrlUpdater.java @@ -8,7 +8,7 @@ * {@link GithubUrlUpdater} for "gh" (github CLI). */ public class GhUrlUpdater extends GithubUrlUpdater { - + private static final VersionIdentifier MIN_MAC_ARM_VID = VersionIdentifier.of("2.23.0"); @Override diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/gradle/GradleUrlUpdater.java b/cli/src/main/java/com/devonfw/tools/ide/tool/gradle/GradleUrlUpdater.java index 9355be394..b971c371f 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/gradle/GradleUrlUpdater.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/gradle/GradleUrlUpdater.java @@ -47,13 +47,13 @@ protected String getTool() { @Override protected void addVersion(UrlVersion urlVersion) { - if (responseBody == null) { - responseBody = doGetResponseBodyAsString(HASH_VERSION_URL); + if (this.responseBody == null) { + this.responseBody = doGetResponseBodyAsString(HASH_VERSION_URL); } String hashSum = ""; - if (responseBody != null && !responseBody.isEmpty()) { - hashSum = doGetHashSumForVersion(responseBody, urlVersion.getName()); + if (this.responseBody != null && !this.responseBody.isEmpty()) { + hashSum = doGetHashSumForVersion(this.responseBody, urlVersion.getName()); } if (hashSum.isEmpty()) { diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java b/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java index 227461b65..9522d0617 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java @@ -1,8 +1,11 @@ package com.devonfw.tools.ide.tool.jmc; -import java.io.File; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; +import java.util.Iterator; import java.util.Set; +import java.util.stream.Stream; import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.context.IdeContext; @@ -45,22 +48,32 @@ public void postInstall() { super.postInstall(); - if (context.getSystemInfo().isWindows() || context.getSystemInfo().isLinux()) { + if (this.context.getSystemInfo().isWindows() || this.context.getSystemInfo().isLinux()) { Path toolPath = getToolPath(); Path oldBinaryPath = toolPath.resolve("JDK Mission Control"); - FileAccess fileAccess = context.getFileAccess(); - moveFilesAndDirs(oldBinaryPath.toFile(), toolPath.toFile()); - fileAccess.delete(oldBinaryPath); + if (Files.isDirectory(oldBinaryPath)) { + FileAccess fileAccess = this.context.getFileAccess(); + moveFilesAndDirs(oldBinaryPath, toolPath); + fileAccess.delete(oldBinaryPath); + } else { + this.context.info("JMC binary folder not found at {} - ignoring as this legacy problem may be resolved in newer versions.", oldBinaryPath); + } } } - private void moveFilesAndDirs(File oldBinaryDir, File toolPathDir) { - - FileAccess fileAccess = context.getFileAccess(); - for (File fileOrDir : oldBinaryDir.listFiles()) { - fileAccess.move(fileOrDir.toPath(), new File(toolPathDir, fileOrDir.getName()).toPath()); + private void moveFilesAndDirs(Path sourceFolder, Path targetFolder) { + + FileAccess fileAccess = this.context.getFileAccess(); + try (Stream childStream = Files.list(sourceFolder)) { + Iterator iterator = childStream.iterator(); + while (iterator.hasNext()) { + Path child = iterator.next(); + fileAccess.move(child, targetFolder.resolve(child.getFileName())); + } + } catch (IOException e) { + throw new IllegalStateException("Failed to list files to move in " + sourceFolder, e); } } -} \ No newline at end of file +} diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/lazydocker/LazyDockerUrlUpdater.java b/cli/src/main/java/com/devonfw/tools/ide/tool/lazydocker/LazyDockerUrlUpdater.java index 94f3df860..168e35953 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/lazydocker/LazyDockerUrlUpdater.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/lazydocker/LazyDockerUrlUpdater.java @@ -8,9 +8,9 @@ * {@link GithubUrlUpdater} for lazydocker. */ public class LazyDockerUrlUpdater extends GithubUrlUpdater { - + private static final VersionIdentifier MIN_WIN_VID = VersionIdentifier.of("0.7.4"); - + private static final VersionIdentifier MIN_ARM_VID = VersionIdentifier.of("0.15.0"); @Override diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/npm/NpmUrlUpdater.java b/cli/src/main/java/com/devonfw/tools/ide/tool/npm/NpmUrlUpdater.java index e1273404a..adc2c5308 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/npm/NpmUrlUpdater.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/npm/NpmUrlUpdater.java @@ -17,7 +17,7 @@ protected String getTool() { @Override protected void addVersion(UrlVersion urlVersion) { - + doAddVersion(urlVersion, "https://registry.npmjs.org/npm/-/npm-${version}.tgz"); } diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/terraform/TerraformUrlUpdater.java b/cli/src/main/java/com/devonfw/tools/ide/tool/terraform/TerraformUrlUpdater.java index 206cb630f..241b73965 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/terraform/TerraformUrlUpdater.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/terraform/TerraformUrlUpdater.java @@ -8,7 +8,7 @@ * {@link GithubUrlUpdater} for terraform. */ public class TerraformUrlUpdater extends GithubUrlUpdater { - + private static final VersionIdentifier MIN_MAC_ARM_VID = VersionIdentifier.of("1.1.0"); @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 ccf5a6f0d..ab2f90369 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 @@ -1,15 +1,11 @@ package com.devonfw.tools.ide.url.model; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Stream; import com.devonfw.tools.ide.cli.CliException; import com.devonfw.tools.ide.context.IdeContext; @@ -62,13 +58,13 @@ public UrlEdition getEdition(String tool, String edition) { public List getSortedEditions(String tool) { List list = new ArrayList<>(); - try { - for (UrlEdition urlEdition : this.repository.getChild(tool).getChildren()) { + UrlTool urlTool = this.repository.getChild(tool); + if (urlTool == null) { + this.context.warning("Can't get sorted editions for tool {} because it does not exist in {}.", tool, this.repository.getPath()); + } else { + for (UrlEdition urlEdition : urlTool.getChildren()) { list.add(urlEdition.getName()); } - } catch (NullPointerException e) { - this.context.warning("Can't get sorted editions for tool {} because it does not exist in {}.", tool, - this.repository.getPath()); } Collections.sort(list); return Collections.unmodifiableList(list); @@ -121,9 +117,8 @@ public VersionIdentifier getVersion(String tool, String edition, VersionIdentifi return vi; } } - throw new CliException("Could not find any version matching '" + version + "' for tool '" + tool - + "' - potentially there are " + versions.size() + " version(s) available in " - + getEdition(tool, edition).getPath() + " but none matched!"); + throw new CliException("Could not find any version matching '" + version + "' for tool '" + tool + "' - potentially there are " + versions.size() + + " version(s) available in " + getEdition(tool, edition).getPath() + " but none matched!"); } /** @@ -138,8 +133,7 @@ public UrlVersion getVersionFolder(String tool, String edition, VersionIdentifie VersionIdentifier resolvedVersion = getVersion(tool, edition, version); UrlVersion urlVersion = getEdition(tool, edition).getChild(resolvedVersion.toString()); if (urlVersion == null) { - throw new IllegalArgumentException( - "Version " + version + " for tool " + tool + " does not exist in edition " + edition + "."); + 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/AbstractUrlUpdater.java b/cli/src/main/java/com/devonfw/tools/ide/url/updater/AbstractUrlUpdater.java index cb248d1cb..ba9248061 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/url/updater/AbstractUrlUpdater.java +++ b/cli/src/main/java/com/devonfw/tools/ide/url/updater/AbstractUrlUpdater.java @@ -90,6 +90,7 @@ protected String getEdition() { protected final String getToolWithEdition() { String tool = getTool(); + String edition = getEdition(); if (tool.equals(edition)) { return tool; 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 637feca26..194e2fbd1 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,8 +16,8 @@ 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.eclipse.EclipseJeeUrlUpdater; import com.devonfw.tools.ide.tool.gcloud.GCloudUrlUpdater; import com.devonfw.tools.ide.tool.gcviewer.GcViewerUrlUpdater; import com.devonfw.tools.ide.tool.gh.GhUrlUpdater; diff --git a/cli/src/main/java/com/devonfw/tools/ide/variable/VariableDefinitionPath.java b/cli/src/main/java/com/devonfw/tools/ide/variable/VariableDefinitionPath.java index c60329fc5..0840d13e9 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/variable/VariableDefinitionPath.java +++ b/cli/src/main/java/com/devonfw/tools/ide/variable/VariableDefinitionPath.java @@ -1,7 +1,6 @@ package com.devonfw.tools.ide.variable; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.function.Function; import com.devonfw.tools.ide.context.IdeContext; @@ -67,6 +66,6 @@ public Class getValueType() { @Override public Path fromString(String value, IdeContext context) { - return Paths.get(value); + return Path.of(value); } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/version/VersionComparisonResult.java b/cli/src/main/java/com/devonfw/tools/ide/version/VersionComparisonResult.java index ec69bcdbc..2452a5cb9 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/version/VersionComparisonResult.java +++ b/cli/src/main/java/com/devonfw/tools/ide/version/VersionComparisonResult.java @@ -65,19 +65,12 @@ public boolean isGreater() { */ public int asValue() { - switch (this) { - case LESS: - case LESS_UNSAFE: - return -1; - case EQUAL: - case EQUAL_UNSAFE: - return 0; - case GREATER: - case GREATER_UNSAFE: - return 1; - default: - throw new IllegalStateException(toString()); - } + return switch (this) { + case LESS, LESS_UNSAFE -> -1; + case EQUAL, EQUAL_UNSAFE -> 0; + case GREATER, GREATER_UNSAFE -> 1; + default -> throw new IllegalStateException(toString()); + }; } /** diff --git a/cli/src/main/java/com/devonfw/tools/ide/version/VersionLetters.java b/cli/src/main/java/com/devonfw/tools/ide/version/VersionLetters.java index d6537b611..80b6a3966 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/version/VersionLetters.java +++ b/cli/src/main/java/com/devonfw/tools/ide/version/VersionLetters.java @@ -171,10 +171,7 @@ public VersionMatchResult matches(VersionLetters other, boolean pattern) { } return VersionMatchResult.MATCH; } else { - if (this.phase != other.phase) { - return VersionMatchResult.MISMATCH; - } - if (!this.lettersLowerCase.equals(other.lettersLowerCase)) { + if ((this.phase != other.phase) || !this.lettersLowerCase.equals(other.lettersLowerCase)) { return VersionMatchResult.MISMATCH; } return VersionMatchResult.EQUAL; 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 0c84b68e2..fb47384f5 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 @@ -261,10 +261,7 @@ public VersionMatchResult matches(VersionSegment other) { } } } else { - if (this.number != other.number) { - return VersionMatchResult.MISMATCH; - } - if (!this.separator.equals(other.separator)) { + if ((this.number != other.number) || !this.separator.equals(other.separator)) { return VersionMatchResult.MISMATCH; } } diff --git a/cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java b/cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java index 2132b9c8a..8e371aa65 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java @@ -7,7 +7,6 @@ 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; @@ -26,7 +25,7 @@ public class JmcTest extends AbstractIdeContextTest { private static WireMockServer server; - private static Path resourcePath = Paths.get("src/test/resources"); + private static Path resourcePath = Path.of("src/test/resources"); @BeforeAll static void setUp() throws IOException { diff --git a/cli/src/test/java/com/devonfw/tools/ide/cli/AutocompletionReaderTestSupport.java b/cli/src/test/java/com/devonfw/tools/ide/cli/AutocompletionReaderTestSupport.java index ce0e3c73d..043e04790 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/cli/AutocompletionReaderTestSupport.java +++ b/cli/src/test/java/com/devonfw/tools/ide/cli/AutocompletionReaderTestSupport.java @@ -23,6 +23,7 @@ import org.jline.reader.Candidate; import org.jline.reader.EndOfFileException; +import org.jline.reader.LineReader; import org.jline.reader.impl.LineReaderImpl; import org.jline.terminal.Size; import org.jline.terminal.Terminal; @@ -63,20 +64,24 @@ public void setUp() throws Exception { this.terminal = new DumbTerminal("terminal", "ansi", this.in, this.out, StandardCharsets.UTF_8); this.terminal.setSize(new Size(160, 80)); this.reader = new TestLineReader(this.terminal, "JLine", null); - this.reader.setKeyMap(LineReaderImpl.EMACS); + this.reader.setKeyMap(LineReader.EMACS); this.mask = null; } - protected void assertBuffer(final String expected, final TestBuffer buffer) throws IOException { + protected void assertBuffer(final String expected, final TestBuffer buffer) { assertBuffer(expected, buffer, true); } - protected void assertBuffer(final String expected, final TestBuffer buffer, final boolean clear) throws IOException { + protected void assertBuffer(final String expected, final TestBuffer buffer, final boolean clear) { // clear current buffer, if any if (clear) { - this.reader.getHistory().purge(); + try { + this.reader.getHistory().purge(); + } catch (IOException e) { + throw new IllegalStateException("Failed to purge history.", e); + } } this.reader.list = false; this.reader.menu = false; diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/ContextCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/ContextCommandletTest.java index f1f02c21e..9a518ae60 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/commandlet/ContextCommandletTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/ContextCommandletTest.java @@ -4,7 +4,6 @@ import org.junit.jupiter.api.Test; -import com.devonfw.tools.ide.context.AbstractIdeContext; import com.devonfw.tools.ide.context.AbstractIdeContextTest; import com.devonfw.tools.ide.context.IdeContextConsole; diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/EditionGetCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/EditionGetCommandletTest.java index b00aec8b9..b4e239b3c 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/commandlet/EditionGetCommandletTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/EditionGetCommandletTest.java @@ -1,14 +1,14 @@ package com.devonfw.tools.ide.commandlet; -import com.devonfw.tools.ide.cli.CliException; +import java.nio.file.Path; +import java.util.List; + +import org.junit.jupiter.api.Test; + import com.devonfw.tools.ide.context.AbstractIdeContextTest; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.context.IdeTestContext; import com.devonfw.tools.ide.log.IdeLogLevel; -import org.junit.jupiter.api.Test; - -import java.nio.file.Path; -import java.util.List; /** Integration test of {@link EditionGetCommandlet}. */ 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 index 9b2a9681d..ac21f6f86 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/commandlet/InstallCommandletTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/InstallCommandletTest.java @@ -7,7 +7,6 @@ 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; @@ -26,7 +25,7 @@ public class InstallCommandletTest extends AbstractIdeContextTest { private static WireMockServer server; - private static Path resourcePath = Paths.get("src/test/resources"); + private static Path resourcePath = Path.of("src/test/resources"); @BeforeAll static void setUp() throws IOException { @@ -103,7 +102,7 @@ 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()){ + 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 61d817c83..fdfd493d6 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,7 +44,7 @@ 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 diff --git a/cli/src/test/java/com/devonfw/tools/ide/completion/IdeCompleterTest.java b/cli/src/test/java/com/devonfw/tools/ide/completion/IdeCompleterTest.java index d6ad0af95..e2c0a8e23 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/completion/IdeCompleterTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/completion/IdeCompleterTest.java @@ -1,12 +1,10 @@ package com.devonfw.tools.ide.completion; -import java.io.IOException; -import java.nio.file.Paths; +import java.nio.file.Path; -import com.devonfw.tools.ide.cli.AutocompletionReaderTestSupport; -import com.devonfw.tools.ide.completion.IdeCompleter; import org.junit.jupiter.api.Test; +import com.devonfw.tools.ide.cli.AutocompletionReaderTestSupport; import com.devonfw.tools.ide.context.IdeTestContext; /** @@ -14,49 +12,67 @@ */ public class IdeCompleterTest extends AutocompletionReaderTestSupport { + /** + * Test of 1st level auto-completion (commandlet name). As suggestions are sorted alphabetically "helm" will be the + * first match. + */ @Test - public void testIdeCompleterHelp() throws IOException { + public void testIdeCompleterHelp() { - IdeTestContext ideContext = new IdeTestContext(Paths.get(""), ""); - this.reader.setCompleter(new IdeCompleter(ideContext)); + this.reader.setCompleter(newCompleter()); assertBuffer("helm", new TestBuffer("he").tab().tab()); } + /** + * Test of 1st level auto-completion (commandlet name). Here we test the special case of the + * {@link com.devonfw.tools.ide.commandlet.VersionCommandlet} that has a long-option style. + */ @Test - public void testIdeCompleterVersion() throws IOException { + public void testIdeCompleterVersion() { - IdeTestContext ideContext = new IdeTestContext(Paths.get(""), ""); - this.reader.setCompleter(new IdeCompleter(ideContext)); + this.reader.setCompleter(newCompleter()); assertBuffer("--version ", new TestBuffer("--vers").tab()); } + /** + * Test of 2nd level auto-completion with tool property of {@link com.devonfw.tools.ide.commandlet.InstallCommandlet}. + */ @Test - public void testIdeCompleterInstall() throws IOException { + public void testIdeCompleterInstall() { - IdeTestContext ideContext = new IdeTestContext(Paths.get(""), ""); - this.reader.setCompleter(new IdeCompleter(ideContext)); + this.reader.setCompleter(newCompleter()); assertBuffer("install mvn ", new TestBuffer("install m").tab()); } + /** + * Test of 2nd level auto-completion with commandlet property of + * {@link com.devonfw.tools.ide.commandlet.HelpCommandlet}. + */ @Test - public void testIdeCompleterHelpWithToolCompletion() throws IOException { + public void testIdeCompleterHelpWithToolCompletion() { - IdeTestContext ideContext = new IdeTestContext(Paths.get(""), ""); - this.reader.setCompleter(new IdeCompleter(ideContext)); + this.reader.setCompleter(newCompleter()); assertBuffer("help mvn ", new TestBuffer("help m").tab().tab()); } + /** + * Test of second option completion that is already present as short-option. + */ @Test - public void testIdeCompleterOptionsRemovesUsedOption() throws IOException { + public void testIdeCompleterDuplicatedOptions() { - IdeTestContext ideContext = new IdeTestContext(Paths.get(""), ""); - this.reader.setCompleter(new IdeCompleter(ideContext)); + this.reader.setCompleter(newCompleter()); assertBuffer("-t --t", new TestBuffer("-t --t").tab()); } + /** + * Test of 3rd level completion using version property of {@link com.devonfw.tools.ide.commandlet.InstallCommandlet} + * contextual to the specified tool. The version "3.2.1" is the latest one from the mocked "basic" project configured + * for the tool "mvn". + */ @Test - public void testIdeCompleterThirdLayerVersions() throws IOException { + public void testIdeCompleterThirdLayerVersions() { String path = "workspaces/foo-test/my-git-repo"; IdeTestContext ideContext = newContext("basic", path, false); @@ -64,40 +80,59 @@ public void testIdeCompleterThirdLayerVersions() throws IOException { assertBuffer("install mvn 3.2.1", new TestBuffer("install mvn ").tab().tab()); } + /** + * Test that 2nd level completion of undefined commandlet has no effect. + */ @Test - public void testIdeCompleterNonExistentCommand() throws IOException { + public void testIdeCompleterNonExistentCommand() { - IdeTestContext ideContext = new IdeTestContext(Paths.get(""), ""); - this.reader.setCompleter(new IdeCompleter(ideContext)); + this.reader.setCompleter(newCompleter()); assertBuffer("cd ", new TestBuffer("cd ").tab().tab().tab()); } + /** + * Test that no options are completed on 2nd level for {@link com.devonfw.tools.ide.commandlet.VersionGetCommandlet} + * that has no options. + */ @Test - public void testIdeCompleterPreventsOptionsAfterCommandWithMinus() throws IOException { + public void testIdeCompleterPreventsOptionsAfterCommandWithMinus() { - IdeTestContext ideContext = new IdeTestContext(Paths.get(""), ""); - this.reader.setCompleter(new IdeCompleter(ideContext)); + this.reader.setCompleter(newCompleter()); assertBuffer("get-version -", new TestBuffer("get-version -").tab().tab()); assertBuffer("get-version - ", new TestBuffer("get-version - ").tab().tab()); } + /** + * Test that completion with invalid options does not trigger suggestions. + */ @Test - public void testIdeCompleterWithInvalidInputDoesNothing() throws IOException { + public void testIdeCompleterWithInvalidInputDoesNothing() { - IdeTestContext ideContext = new IdeTestContext(Paths.get(""), ""); - this.reader.setCompleter(new IdeCompleter(ideContext)); + this.reader.setCompleter(newCompleter()); assertBuffer("get-version -t ", new TestBuffer("get-version -t ").tab().tab()); assertBuffer("- get-version ", new TestBuffer("- get-version ").tab().tab()); assertBuffer(" - get-version", new TestBuffer(" - get-version").tab().tab()); } + /** + * Test of 2nd level completion of tool property for {@link com.devonfw.tools.ide.commandlet.VersionGetCommandlet}. + */ @Test - public void testIdeCompleterHandlesOptionsBeforeCommand() throws IOException { + public void testIdeCompleterHandlesOptionsBeforeCommand() { - IdeTestContext ideContext = new IdeTestContext(Paths.get(""), ""); - this.reader.setCompleter(new IdeCompleter(ideContext)); + this.reader.setCompleter(newCompleter()); assertBuffer("get-version mvn ", new TestBuffer("get-version mv").tab().tab()); } + + private IdeCompleter newCompleter() { + + return new IdeCompleter(newTestContext()); + } + + private IdeTestContext newTestContext() { + + return new IdeTestContext(Path.of(""), ""); + } } diff --git a/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeContextTest.java b/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeContextTest.java index 3cbcd9c53..8b79dcfb8 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeContextTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeContextTest.java @@ -1,7 +1,6 @@ package com.devonfw.tools.ide.context; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.List; import org.assertj.core.api.Assertions; @@ -24,10 +23,10 @@ public abstract class AbstractIdeContextTest extends Assertions { protected static final String PROJECT_BASIC = "basic"; /** The source {@link Path} to the test projects. */ - protected static final Path PATH_PROJECTS = Paths.get("src/test/resources/ide-projects"); + protected static final Path PATH_PROJECTS = Path.of("src/test/resources/ide-projects"); // will not use eclipse-target like done in maven via eclipse profile... - private static final Path PATH_PROJECTS_COPY = Paths.get("target/test-projects/"); + private static final Path PATH_PROJECTS_COPY = Path.of("target/test-projects/"); /** Chunk size to use for progress bars **/ private static final int CHUNK_SIZE = 1024; @@ -116,7 +115,7 @@ protected static void assertLogMessage(IdeTestContext context, IdeLogLevel level public boolean matches(String e) { return e.contains(message); - }; + } }; assertion.filteredOn(condition).isNotEmpty(); } else { diff --git a/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeTestContext.java b/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeTestContext.java index 97e63143a..9810990e9 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeTestContext.java +++ b/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeTestContext.java @@ -55,7 +55,7 @@ protected String readLine() { */ public Map getProgressBarMap() { - return progressBarMap; + return this.progressBarMap; } @Override diff --git a/cli/src/test/java/com/devonfw/tools/ide/context/IdeTestContext.java b/cli/src/test/java/com/devonfw/tools/ide/context/IdeTestContext.java index afd4c321a..1ad4ee8bd 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/context/IdeTestContext.java +++ b/cli/src/test/java/com/devonfw/tools/ide/context/IdeTestContext.java @@ -1,7 +1,6 @@ package com.devonfw.tools.ide.context; import java.nio.file.Path; -import java.nio.file.Paths; import com.devonfw.tools.ide.log.IdeLogLevel; import com.devonfw.tools.ide.log.IdeTestLogger; @@ -33,7 +32,7 @@ public IdeTestLogger level(IdeLogLevel level) { */ public static IdeTestContext of() { - return new IdeTestContext(Paths.get("/")); + return new IdeTestContext(Path.of("/")); } } diff --git a/cli/src/test/java/com/devonfw/tools/ide/context/IdeTestContextMock.java b/cli/src/test/java/com/devonfw/tools/ide/context/IdeTestContextMock.java index 2bb868220..ec97dd45d 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/context/IdeTestContextMock.java +++ b/cli/src/test/java/com/devonfw/tools/ide/context/IdeTestContextMock.java @@ -1,6 +1,6 @@ package com.devonfw.tools.ide.context; -import java.nio.file.Paths; +import java.nio.file.Path; /** * Mock instance of {@link com.devonfw.tools.ide.context.IdeContext}. @@ -13,7 +13,7 @@ public class IdeTestContextMock extends IdeSlf4jContext { private IdeTestContextMock() { - super(Paths.get("/")); + super(Path.of("/")); } @Override diff --git a/cli/src/test/java/com/devonfw/tools/ide/environment/SortedPropertiesTest.java b/cli/src/test/java/com/devonfw/tools/ide/environment/SortedPropertiesTest.java index 5a6fba48a..a821be3d8 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/environment/SortedPropertiesTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/environment/SortedPropertiesTest.java @@ -6,8 +6,6 @@ import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; -import com.devonfw.tools.ide.environment.SortedProperties; - /** * Test of {@link SortedProperties}. */ diff --git a/cli/src/test/java/com/devonfw/tools/ide/io/IdeProgressBarTest.java b/cli/src/test/java/com/devonfw/tools/ide/io/IdeProgressBarTest.java index 585664d5c..fa3ed0178 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/io/IdeProgressBarTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/io/IdeProgressBarTest.java @@ -22,7 +22,7 @@ public class IdeProgressBarTest extends AbstractIdeContextTest { /** * Tests if a download of a file with a valid content length was displaying an {@link IdeProgressBar} properly. - * + * * @param tempDir temporary directory to use. */ @Test diff --git a/cli/src/test/java/com/devonfw/tools/ide/io/IdeProgressBarTestImpl.java b/cli/src/test/java/com/devonfw/tools/ide/io/IdeProgressBarTestImpl.java index bc7ba82a7..5f6dbe037 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/io/IdeProgressBarTestImpl.java +++ b/cli/src/test/java/com/devonfw/tools/ide/io/IdeProgressBarTestImpl.java @@ -62,7 +62,7 @@ public void close() { */ public List getEventList() { - return eventList; + return this.eventList; } /** @@ -70,7 +70,7 @@ public List getEventList() { */ public long getMaxSize() { - return max; + return this.max; } /** @@ -100,7 +100,7 @@ public ProgressEvent(long stepSize) { */ public Instant getTimestamp() { - return timestamp; + return this.timestamp; } /** @@ -108,7 +108,7 @@ public Instant getTimestamp() { */ public long getStepSize() { - return stepSize; + return this.stepSize; } } diff --git a/cli/src/test/java/com/devonfw/tools/ide/merge/DirectoryMergerTest.java b/cli/src/test/java/com/devonfw/tools/ide/merge/DirectoryMergerTest.java index 55b0d37ba..4f3bb2342 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/merge/DirectoryMergerTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/merge/DirectoryMergerTest.java @@ -1,7 +1,6 @@ package com.devonfw.tools.ide.merge; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Map.Entry; import java.util.Properties; @@ -54,7 +53,7 @@ public void testConfigurator(@TempDir Path workspaceDir) throws Exception { // act IdeContext context = newContext(PROJECT_BASIC, null, false); DirectoryMerger merger = context.getWorkspaceMerger(); - Path templates = Paths.get("src/test/resources/templates"); + Path templates = Path.of("src/test/resources/templates"); Path setup = templates.resolve(IdeContext.FOLDER_SETUP); Path update = templates.resolve(IdeContext.FOLDER_UPDATE); merger.merge(setup, update, context.getVariables(), workspaceDir); diff --git a/cli/src/test/java/com/devonfw/tools/ide/os/MacOsHelperTest.java b/cli/src/test/java/com/devonfw/tools/ide/os/MacOsHelperTest.java index bf05ebadc..19ffb8237 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/os/MacOsHelperTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/os/MacOsHelperTest.java @@ -1,7 +1,6 @@ package com.devonfw.tools.ide.os; import java.nio.file.Path; -import java.nio.file.Paths; import org.junit.jupiter.api.Test; @@ -15,7 +14,7 @@ public class MacOsHelperTest extends AbstractIdeContextTest { private static final IdeContext CONTEXT = newContext("basic", "", false); - private static final Path APPS_DIR = Paths.get("src/test/resources/mac-apps"); + private static final Path APPS_DIR = Path.of("src/test/resources/mac-apps"); /** Test "java" structure. */ @Test diff --git a/cli/src/test/java/com/devonfw/tools/ide/os/SystemInformationImplTest.java b/cli/src/test/java/com/devonfw/tools/ide/os/SystemInformationImplTest.java index 4c21e6ee9..e94a414f1 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/os/SystemInformationImplTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/os/SystemInformationImplTest.java @@ -3,11 +3,6 @@ import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; -import com.devonfw.tools.ide.os.OperatingSystem; -import com.devonfw.tools.ide.os.SystemArchitecture; -import com.devonfw.tools.ide.os.SystemInfo; -import com.devonfw.tools.ide.os.SystemInfoImpl; - /** * Test of {@link SystemInfoImpl}. */ diff --git a/cli/src/test/java/com/devonfw/tools/ide/os/SystemInformationMock.java b/cli/src/test/java/com/devonfw/tools/ide/os/SystemInformationMock.java index c44d21b97..c7546a4ec 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/os/SystemInformationMock.java +++ b/cli/src/test/java/com/devonfw/tools/ide/os/SystemInformationMock.java @@ -1,10 +1,5 @@ package com.devonfw.tools.ide.os; -import com.devonfw.tools.ide.os.OperatingSystem; -import com.devonfw.tools.ide.os.SystemArchitecture; -import com.devonfw.tools.ide.os.SystemInfo; -import com.devonfw.tools.ide.os.SystemInfoImpl; - /** * Mock instances of {@link SystemInfo} to test OS specific behavior independent of the current OS running the test. */ diff --git a/cli/src/test/java/com/devonfw/tools/ide/property/LocalePropertyTest.java b/cli/src/test/java/com/devonfw/tools/ide/property/LocalePropertyTest.java index c11327a30..35500f50b 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/property/LocalePropertyTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/property/LocalePropertyTest.java @@ -2,13 +2,13 @@ import java.util.Locale; -import com.devonfw.tools.ide.completion.CompletionCandidate; -import com.devonfw.tools.ide.completion.CompletionCandidateCollectorDefault; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import com.devonfw.tools.ide.commandlet.ContextCommandlet; +import com.devonfw.tools.ide.completion.CompletionCandidate; import com.devonfw.tools.ide.completion.CompletionCandidateCollector; +import com.devonfw.tools.ide.completion.CompletionCandidateCollectorDefault; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.context.IdeTestContextMock; diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/ExamplePluginBasedCommandlet.java b/cli/src/test/java/com/devonfw/tools/ide/tool/ExamplePluginBasedCommandlet.java index 6570ce6a7..d15758a86 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/tool/ExamplePluginBasedCommandlet.java +++ b/cli/src/test/java/com/devonfw/tools/ide/tool/ExamplePluginBasedCommandlet.java @@ -6,6 +6,9 @@ import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.tool.ide.PluginDescriptor; +/** + * Example implementation of {@link PluginBasedCommandlet} for testing. + */ public class ExamplePluginBasedCommandlet extends PluginBasedCommandlet { /** * The constructor. @@ -13,7 +16,7 @@ public class ExamplePluginBasedCommandlet extends PluginBasedCommandlet { * @param context the {@link IdeContext}. * @param tool the {@link #getName() tool name}. * @param tags the {@link #getTags() tags} classifying the tool. Should be created via {@link Set#of(Object) Set.of} - * method. + * method. */ public ExamplePluginBasedCommandlet(IdeContext context, String tool, Set tags) { diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/PluginBasedCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/PluginBasedCommandletTest.java index f72ee5a35..19a48573f 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/tool/PluginBasedCommandletTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/tool/PluginBasedCommandletTest.java @@ -1,6 +1,6 @@ package com.devonfw.tools.ide.tool; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertNotNull; import java.util.Map; import java.util.Set; @@ -12,30 +12,34 @@ import com.devonfw.tools.ide.context.IdeTestContext; import com.devonfw.tools.ide.tool.ide.PluginDescriptor; +/** + * Test of {@link PluginBasedCommandlet}. + */ public class PluginBasedCommandletTest extends AbstractIdeContextTest { @Test - void testGetPluginsMap() { + void testGetPluginsMap() { + IdeTestContext context = newContext(PROJECT_BASIC, "", true); String tool = "eclipse"; Set tags = null; ExamplePluginBasedCommandlet pluginBasedCommandlet = new ExamplePluginBasedCommandlet(context, tool, tags); Map pluginsMap = pluginBasedCommandlet.getPluginsMap(); - assertNotNull(pluginsMap); + assertThat(pluginsMap).isNotNull(); - assertTrue(pluginsMap.containsKey("checkstyle")); - assertTrue(pluginsMap.containsKey("anyedit")); + assertThat(pluginsMap.containsKey("checkstyle")).isTrue(); + assertThat(pluginsMap.containsKey("anyedit")).isTrue(); PluginDescriptor plugin1 = pluginsMap.get("checkstyle"); assertNotNull(plugin1); - assertEquals("checkstyle", plugin1.getName()); + assertThat(plugin1.getName()).isEqualTo("checkstyle"); PluginDescriptor plugin2 = pluginsMap.get("anyedit"); assertNotNull(plugin2); - assertEquals("anyedit", plugin2.getName()); + assertThat(plugin2.getName()).isEqualTo("anyedit"); // Check if anyedit plugin has value "false" --> value from user directory - assertEquals(false, plugin2.isActive()); + assertThat(plugin2.isActive()).isFalse(); } } diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/UrlUpdaterMock.java b/cli/src/test/java/com/devonfw/tools/ide/tool/UrlUpdaterMock.java index f5917062f..ec1e02df6 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/tool/UrlUpdaterMock.java +++ b/cli/src/test/java/com/devonfw/tools/ide/tool/UrlUpdaterMock.java @@ -1,15 +1,14 @@ package com.devonfw.tools.ide.tool; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + import com.devonfw.tools.ide.url.model.folder.UrlRepository; import com.devonfw.tools.ide.url.model.folder.UrlVersion; import com.devonfw.tools.ide.url.updater.AbstractUrlUpdater; -import com.devonfw.tools.ide.url.updater.JsonUrlUpdater; import com.devonfw.tools.ide.url.updater.UrlUpdater; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - /** * Test mock for {@link UrlUpdater} preparing multiple tool versions and distributions. */ diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/UrlUpdaterMockSingle.java b/cli/src/test/java/com/devonfw/tools/ide/tool/UrlUpdaterMockSingle.java index dc4195f2b..c9e5cee73 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/tool/UrlUpdaterMockSingle.java +++ b/cli/src/test/java/com/devonfw/tools/ide/tool/UrlUpdaterMockSingle.java @@ -1,14 +1,10 @@ package com.devonfw.tools.ide.tool; -import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; -import com.devonfw.tools.ide.url.model.folder.UrlRepository; import com.devonfw.tools.ide.url.model.folder.UrlVersion; -import com.devonfw.tools.ide.url.updater.AbstractUrlUpdater; -import com.devonfw.tools.ide.url.updater.JsonUrlUpdater; import com.devonfw.tools.ide.url.updater.UrlUpdater; /** diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/UrlUpdaterTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/UrlUpdaterTest.java index a9af864ae..6bfdf43cb 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/tool/UrlUpdaterTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/tool/UrlUpdaterTest.java @@ -8,7 +8,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.time.Instant; import org.junit.jupiter.api.Test; @@ -84,7 +83,7 @@ public void testUrlUpdaterIsNotUpdatingWhenStatusManualIsTrue(@TempDir Path temp // act updater.update(urlRepository); - Path versionsPath = Paths.get(testdataRoot).resolve("mocked").resolve("mocked").resolve("1.0"); + Path versionsPath = Path.of(testdataRoot).resolve("mocked").resolve("mocked").resolve("1.0"); // assert assertThat(versionsPath.resolve("windows_x64.urls")).doesNotExist(); @@ -100,7 +99,6 @@ public void testUrlUpdaterIsNotUpdatingWhenStatusManualIsTrue(@TempDir Path temp * See: #1343 for reference. * * @param tempDir Temporary directory - * @throws IOException test fails */ @Test public void testUrlUpdaterStatusJsonRefreshBugStillExisting(@TempDir Path tempDir) { @@ -147,8 +145,7 @@ public void testUrlUpdaterStatusJsonRefreshBugStillExisting(@TempDir Path tempDi assertThat(errorCode).isEqualTo(404); assertThat(errorTimestamp).isAfter(successTimestamp); - stubFor( - any(urlMatching("/os/.*")).willReturn(aResponse().withStatus(200).withHeader("Content-Type", "text/plain"))); + stubFor(any(urlMatching("/os/.*")).willReturn(aResponse().withStatus(200).withHeader("Content-Type", "text/plain"))); // re-initialize UrlRepository for error timestamp UrlRepository urlRepositoryWithSuccess = UrlRepository.load(tempDir); @@ -181,8 +178,7 @@ public void testUrlUpdaterStatusJsonRefreshBugStillExisting(@TempDir Path tempDi public void testUrlUpdaterWithTextContentTypeWillNotCreateStatusJson(@TempDir Path tempDir) { // given - stubFor(any(urlMatching("/os/.*")) - .willReturn(aResponse().withStatus(200).withHeader("Content-Type", "text/plain").withBody("aBody"))); + stubFor(any(urlMatching("/os/.*")).willReturn(aResponse().withStatus(200).withHeader("Content-Type", "text/plain").withBody("aBody"))); UrlRepository urlRepository = UrlRepository.load(tempDir); UrlUpdaterMockSingle updater = new UrlUpdaterMockSingle(); diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/androidstudio/AndroidStudioJsonUrlUpdaterTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/androidstudio/AndroidStudioJsonUrlUpdaterTest.java index f7291ce2f..8817dfa28 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/tool/androidstudio/AndroidStudioJsonUrlUpdaterTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/tool/androidstudio/AndroidStudioJsonUrlUpdaterTest.java @@ -9,7 +9,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; @@ -44,7 +43,7 @@ public void testJsonUrlUpdaterCreatesDownloadUrlsAndChecksums(@TempDir Path temp // given stubFor(get(urlMatching("/android-studio-releases-list.*")).willReturn(aResponse().withStatus(200) - .withBody(Files.readAllBytes(Paths.get(testdataRoot).resolve("android-version.json"))))); + .withBody(Files.readAllBytes(Path.of(testdataRoot).resolve("android-version.json"))))); stubFor(any(urlMatching("/edgedl/android/studio/ide-zips.*")) .willReturn(aResponse().withStatus(200).withBody("aBody"))); @@ -83,7 +82,7 @@ public void testJsonUrlUpdaterWithMissingDownloadsDoesNotCreateVersionFolder(@Te // given stubFor(get(urlMatching("/android-studio-releases-list.*")).willReturn(aResponse().withStatus(200) - .withBody(Files.readAllBytes(Paths.get(testdataRoot).resolve("android-version.json"))))); + .withBody(Files.readAllBytes(Path.of(testdataRoot).resolve("android-version.json"))))); stubFor(get(urlMatching("/edgedl/android/studio/ide-zips.*")).willReturn(aResponse().withStatus(404))); @@ -112,7 +111,7 @@ public void testJsonUrlUpdaterWithMissingChecksumGeneratesChecksum(@TempDir Path // given stubFor(get(urlMatching("/android-studio-releases-list.*")).willReturn(aResponse().withStatus(200) - .withBody(Files.readAllBytes(Paths.get(testdataRoot).resolve("android-version-without-checksum.json"))))); + .withBody(Files.readAllBytes(Path.of(testdataRoot).resolve("android-version-without-checksum.json"))))); stubFor(any(urlMatching("/edgedl/android/studio/ide-zips.*")) .willReturn(aResponse().withStatus(200).withBody("aBody"))); diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/intellij/IntellijJsonUrlUpdaterTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/intellij/IntellijJsonUrlUpdaterTest.java index 2f106da27..4b5aa1ba9 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/tool/intellij/IntellijJsonUrlUpdaterTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/tool/intellij/IntellijJsonUrlUpdaterTest.java @@ -9,7 +9,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; @@ -44,7 +43,7 @@ public void testIntellijJsonUrlUpdaterCreatesDownloadUrlsAndChecksums(@TempDir P // given stubFor(get(urlMatching("/products.*")).willReturn(aResponse().withStatus(200) - .withBody(Files.readAllBytes(Paths.get(TEST_DATA_ROOT).resolve("intellij-version.json"))))); + .withBody(Files.readAllBytes(Path.of(TEST_DATA_ROOT).resolve("intellij-version.json"))))); stubFor(any(urlMatching("/idea/idea.*")).willReturn(aResponse().withStatus(200).withBody("aBody"))); @@ -76,7 +75,7 @@ public void testIntellijJsonUrlUpdaterWithMissingDownloadsDoesNotCreateVersionFo // given stubFor(get(urlMatching("/products.*")).willReturn(aResponse().withStatus(200) - .withBody(Files.readAllBytes(Paths.get(TEST_DATA_ROOT).resolve("intellij-version.json"))))); + .withBody(Files.readAllBytes(Path.of(TEST_DATA_ROOT).resolve("intellij-version.json"))))); stubFor(any(urlMatching("/idea/idea.*")).willReturn(aResponse().withStatus(404))); @@ -105,7 +104,7 @@ public void testIntellijJsonUrlUpdaterWithMissingChecksumGeneratesChecksum(@Temp // given stubFor(get(urlMatching("/products.*")).willReturn(aResponse().withStatus(200) - .withBody(Files.readAllBytes(Paths.get(TEST_DATA_ROOT).resolve("intellij-version-withoutchecksum.json"))))); + .withBody(Files.readAllBytes(Path.of(TEST_DATA_ROOT).resolve("intellij-version-withoutchecksum.json"))))); stubFor(any(urlMatching("/idea/idea.*")).willReturn(aResponse().withStatus(200).withBody("aBody"))); diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/python/PythonUrlUpdaterTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/python/PythonUrlUpdaterTest.java index f8615afbb..36c51c191 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/tool/python/PythonUrlUpdaterTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/tool/python/PythonUrlUpdaterTest.java @@ -9,7 +9,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; @@ -37,7 +36,7 @@ public void testPythonURl(@TempDir Path tempPath) throws IOException { // given stubFor(get(urlMatching("/actions/python-versions/main/.*")).willReturn(aResponse().withStatus(200) - .withBody(Files.readAllBytes(Paths.get(testdataRoot).resolve("python-version.json"))))); + .withBody(Files.readAllBytes(Path.of(testdataRoot).resolve("python-version.json"))))); stubFor(any(urlMatching("/actions/python-versions/releases/download.*")) .willReturn(aResponse().withStatus(200).withBody("aBody"))); diff --git a/cli/src/test/java/com/devonfw/tools/ide/url/model/UrlStatusFileTest.java b/cli/src/test/java/com/devonfw/tools/ide/url/model/UrlStatusFileTest.java index 5baaf497e..527cbd651 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/url/model/UrlStatusFileTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/url/model/UrlStatusFileTest.java @@ -1,6 +1,6 @@ package com.devonfw.tools.ide.url.model; -import java.nio.file.Paths; +import java.nio.file.Path; import java.time.Instant; import org.assertj.core.api.Assertions; @@ -27,7 +27,7 @@ public class UrlStatusFileTest extends Assertions { public void testReadJson() { // given - UrlRepository repo = UrlRepository.load(Paths.get("src/test/resources/urls")); + UrlRepository repo = UrlRepository.load(Path.of("src/test/resources/urls")); UrlTool tool = repo.getChild("docker"); UrlEdition edition = tool.getChild("rancher"); UrlVersion version = edition.getChild("1.6.2"); From bc5220fca522e513a4a6fc349df98d9b14710d54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Hohwiller?= Date: Tue, 13 Feb 2024 13:37:06 +0100 Subject: [PATCH 7/8] Update coding-conventions.asciidoc --- documentation/coding-conventions.asciidoc | 58 +++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/documentation/coding-conventions.asciidoc b/documentation/coding-conventions.asciidoc index fedf6029a..c56156fdf 100644 --- a/documentation/coding-conventions.asciidoc +++ b/documentation/coding-conventions.asciidoc @@ -1,3 +1,6 @@ +:toc: +toc::[] + = Coding Conventions The code should follow general conventions for Java (see http://www.oracle.com/technetwork/java/namingconventions-139351.html[Oracle Naming Conventions], https://google.github.io/styleguide/javaguide.html[Google Java Style], etc.). @@ -150,6 +153,61 @@ public boolean isEmpty { } ---- +== Constants +Literals and values used in multiple places that do not change, shall be defined as constants. +A constant in a Java class is a type variable declared with the modifiers `static final`. +In an interface, `public static final` can and should be omitted since it is there by default. +[source,java] +---- +public class MavenDownloader { + // bad + public String url = "https://repo1.maven.org/maven2/" + public void download(Dependency dependency) { + String downloadUrl = url + dependency.getGroupId() + "/" + dependency.getArtifactId() + "/" dependency.getVersion() + "/" + dependency.getArtifactId() + "-" + dependency.getVersion() + ".jar"; + download(downloadUrl); + } + public void download(String url) { ... } +} +---- +Here `url` is used as a constant however it is not declared as such. +Instead we should better do this: +[source,java] +---- +public class MavenDownloader { + // good + /** The base URL of the central maven repository. */ + public static final String REPOSITORY_URL = "https://repo1.maven.org/maven2/" + public void download(Dependency dependency) { + String artifactId = dependency.getArtifactId(); + String version = dependency.getVersion(); + String downloadUrl = REPOSITORY_URL + dependency.getGroupId().replace(".", "/") + "/" + artifactId + "/" + version + "/" + artifactId + "-" + version + ".jar"; + download(downloadUrl); + } + public void download(String url) { ... } +} +---- + +As stated above in case of an interface simply omit the modifiers: +[source,java] +---- +public interface MavenDownloader { + // good + /** The base URL of the central maven repository. */ + String REPOSITORY_URL = "https://repo1.maven.org/maven2/" + void download(Dependency dependency); + void download(String url); +} +---- + +So we conclude: + +* we want to use constants to define and reuse common immutable values. +* by giving the constant a reasonable name, we make our code reable +* following Java best-practices constants are named in `UPPER_CASE_WITH_UNDERSCORES` syntax +* by adding JavaDoc to the constant we give additional details what this value is about and good for. +* In classes we declare the constant with the visibility followed by the keywords `static final`. +* In interfaces, we omit all modifiers as they always default to `public static final` for type variables. + == Optionals With `Optional` you can wrap values to avoid a `NullPointerException` (NPE). However, it is not a good code-style to use `Optional` for every parameter or result to express that it may be null. From 3a77034fac9369c51918a0f32452495a617c7ff2 Mon Sep 17 00:00:00 2001 From: Marco Vomiero <102261609+mvomiero@users.noreply.github.com> Date: Tue, 13 Feb 2024 15:28:34 +0100 Subject: [PATCH 8/8] #26: add JasyptUrlUpdater to UpdateManager (#202) --- .../java/com/devonfw/tools/ide/url/updater/UpdateManager.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 194e2fbd1..0ab35e266 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 @@ -44,6 +44,7 @@ import com.devonfw.tools.ide.tool.tomcat.TomcatUrlUpdater; import com.devonfw.tools.ide.tool.vscode.VsCodeUrlUpdater; import com.devonfw.tools.ide.url.model.folder.UrlRepository; +import com.devonfw.tools.ide.tool.jasypt.JasyptUrlUpdater; /** * The {@code UpdateManager} class manages the update process for various tools by using a list of @@ -65,7 +66,8 @@ public class UpdateManager extends AbstractProcessorWithTimeout { 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 SonarUrlUpdater(), new TerraformUrlUpdater(), new TomcatUrlUpdater(), new VsCodeUrlUpdater(), + new JasyptUrlUpdater()); /** * The constructor.