From e1809129a30f7a15ea60ecc692866257391d5d80 Mon Sep 17 00:00:00 2001 From: Mattes Mrzik Date: Fri, 26 Jan 2024 12:19:48 +0100 Subject: [PATCH 01/15] #132: untar preserves unix file permissios (#185) --- .../com/devonfw/tools/ide/io/FileAccess.java | 4 +- .../devonfw/tools/ide/io/FileAccessImpl.java | 41 ++++++- .../tools/ide/io/FileAccessImplTest.java | 101 +++++++++++++++++- .../ide/io/executable_and_non_executable.tar | Bin 0 -> 10240 bytes .../io/executable_and_non_executable.tar.bz2 | Bin 0 -> 196 bytes .../io/executable_and_non_executable.tar.gz | Bin 0 -> 188 bytes .../ide/io/executable_and_non_executable.zip | Bin 0 -> 421 bytes 7 files changed, 140 insertions(+), 6 deletions(-) create mode 100644 cli/src/test/resources/com/devonfw/tools/ide/io/executable_and_non_executable.tar create mode 100644 cli/src/test/resources/com/devonfw/tools/ide/io/executable_and_non_executable.tar.bz2 create mode 100644 cli/src/test/resources/com/devonfw/tools/ide/io/executable_and_non_executable.tar.gz create mode 100644 cli/src/test/resources/com/devonfw/tools/ide/io/executable_and_non_executable.zip diff --git a/cli/src/main/java/com/devonfw/tools/ide/io/FileAccess.java b/cli/src/main/java/com/devonfw/tools/ide/io/FileAccess.java index d20361128..5d105d8e0 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/io/FileAccess.java +++ b/cli/src/main/java/com/devonfw/tools/ide/io/FileAccess.java @@ -62,7 +62,7 @@ public interface FileAccess { * Creates a symbolic link. If the given {@code targetLink} already exists and is a symbolic link or a Windows * junction, it will be replaced. In case of missing privileges, Windows Junctions may be used as fallback, which must * point to absolute paths. Therefore, the created link will be absolute instead of relative. - * + * * @param source the source {@link Path} to link to, may be relative or absolute. * @param targetLink the {@link Path} where the symbolic link shall be created pointing to {@code source}. * @param relative - {@code true} if the symbolic link shall be relative, {@code false} if it shall be absolute. @@ -73,7 +73,7 @@ public interface FileAccess { * Creates a relative symbolic link. If the given {@code targetLink} already exists and is a symbolic link or a * Windows junction, it will be replaced. In case of missing privileges, Windows Junctions may be used as fallback, * which must point to absolute paths. Therefore, the created link will be absolute instead of relative. - * + * * @param source the source {@link Path} to link to, may be relative or absolute. * @param targetLink the {@link Path} where the symbolic link shall be created pointing to {@code source}. */ diff --git a/cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java b/cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java index 08985d6d5..f11248e05 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java @@ -18,6 +18,8 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -25,12 +27,14 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Stream; import org.apache.commons.compress.archivers.ArchiveEntry; import org.apache.commons.compress.archivers.ArchiveInputStream; +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream; @@ -315,8 +319,7 @@ private void deleteLinkIfExists(Path path) throws IOException { return; } } - exists = exists || Files.exists(path); // "||" since broken junctions are not detected by - // Files.exists(brokenJunction) + exists = exists || Files.exists(path); boolean isSymlink = exists && Files.isSymbolicLink(path); assert !(isSymlink && isJunction); @@ -378,7 +381,7 @@ private Path adaptPath(Path source, Path targetLink, boolean relative) throws IO /** * Creates a Windows junction at {@code targetLink} pointing to {@code source}. - * + * * @param source must be another Windows junction or a directory. * @param targetLink the location of the Windows junction. */ @@ -495,12 +498,40 @@ public void untar(Path file, Path targetDir, TarCompression compression) { unpack(file, targetDir, in -> new TarArchiveInputStream(compression.unpack(in))); } + /** + * @param permissions The integer as returned by {@link TarArchiveEntry#getMode()} that represents the file + * permissions of a file on a Unix file system. + * @return A String representing the file permissions. E.g. "rwxrwxr-x" or "rw-rw-r--" + */ + public static String generatePermissionString(int permissions) { + + // Ensure that only the last 9 bits are considered + permissions &= 0b111111111; + + StringBuilder permissionStringBuilder = new StringBuilder("rwxrwxrwx"); + + for (int i = 0; i < 9; i++) { + int mask = 1 << i; + char currentChar = ((permissions & mask) != 0) ? permissionStringBuilder.charAt(8 - i) : '-'; + permissionStringBuilder.setCharAt(8 - i, currentChar); + } + + return permissionStringBuilder.toString(); + } + private void unpack(Path file, Path targetDir, Function unpacker) { this.context.trace("Unpacking archive {} to {}", file, targetDir); try (InputStream is = Files.newInputStream(file); ArchiveInputStream ais = unpacker.apply(is)) { ArchiveEntry entry = ais.getNextEntry(); + boolean isTar = ais instanceof TarArchiveInputStream; while (entry != null) { + String permissionStr = null; + if (isTar) { + int tarMode = ((TarArchiveEntry) entry).getMode(); + permissionStr = generatePermissionString(tarMode); + } + Path entryName = Paths.get(entry.getName()); Path entryPath = targetDir.resolve(entryName).toAbsolutePath(); if (!entryPath.startsWith(targetDir)) { @@ -513,6 +544,10 @@ private void unpack(Path file, Path targetDir, Function permissions = PosixFilePermissions.fromString(permissionStr); + Files.setPosixFilePermissions(entryPath, permissions); + } entry = ais.getNextEntry(); } } catch (IOException e) { diff --git a/cli/src/test/java/com/devonfw/tools/ide/io/FileAccessImplTest.java b/cli/src/test/java/com/devonfw/tools/ide/io/FileAccessImplTest.java index c2f0bae8c..33f2b7183 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/io/FileAccessImplTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/io/FileAccessImplTest.java @@ -1,5 +1,6 @@ package com.devonfw.tools.ide.io; +import static com.devonfw.tools.ide.io.FileAccessImpl.generatePermissionString; import static org.junit.jupiter.api.Assertions.assertThrows; import java.io.IOException; @@ -7,6 +8,9 @@ import java.nio.file.LinkOption; import java.nio.file.NoSuchFileException; import java.nio.file.Path; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; +import java.util.Set; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -325,7 +329,7 @@ private void createSymlinks(FileAccess fa, Path dir, boolean relative) { /** * Checks if the symlinks exist. This is used by the tests of {@link FileAccessImpl#symlink(Path, Path, boolean)}. - * + * * @param dir the {@link Path} to the directory where the symlinks are expected. */ private void assertSymlinksExist(Path dir) { @@ -469,4 +473,99 @@ private void assertSymlinkRead(Path link, Path trueTarget) { + " and readPath " + readPath, e); } } + + /** + * Test of {@link FileAccessImpl#untar(Path, Path, TarCompression)} with {@link TarCompression#NONE} and checks if + * file permissions are preserved on Unix. + */ + @Test + public void testUntarWithNoneCompressionWithFilePermissions(@TempDir Path tempDir) { + + // arrange + IdeContext context = IdeTestContextMock.get(); + if (context.getSystemInfo().isWindows()) { + return; + } + + // act + context.getFileAccess().untar( + Path.of("src/test/resources/com/devonfw/tools/ide/io").resolve("executable_and_non_executable.tar"), tempDir, + TarCompression.NONE); + + // assert + assertPosixFilePermissions(tempDir.resolve("executableFile.txt"), "rwxrwxr-x"); + assertPosixFilePermissions(tempDir.resolve("nonExecutableFile.txt"), "rw-rw-r--"); + } + + /** + * Test of {@link FileAccessImpl#untar(Path, Path, TarCompression)} with {@link TarCompression#GZ} and checks if file + * permissions are preserved on Unix. + */ + @Test + public void testUntarWithGzCompressionWithFilePermissions(@TempDir Path tempDir) { + + // arrange + IdeContext context = IdeTestContextMock.get(); + if (context.getSystemInfo().isWindows()) { + return; + } + + // act + context.getFileAccess().untar( + Path.of("src/test/resources/com/devonfw/tools/ide/io").resolve("executable_and_non_executable.tar.gz"), tempDir, + TarCompression.GZ); + + // assert + assertPosixFilePermissions(tempDir.resolve("executableFile.txt"), "rwxrwxr-x"); + assertPosixFilePermissions(tempDir.resolve("nonExecutableFile.txt"), "rw-rw-r--"); + } + + /** + * Test of {@link FileAccessImpl#untar(Path, Path, TarCompression)} with {@link TarCompression#BZIP2} and checks if + * file permissions are preserved on Unix. + */ + @Test + public void testUntarWithBzip2CompressionWithFilePermissions(@TempDir Path tempDir) { + + // arrange + IdeContext context = IdeTestContextMock.get(); + if (context.getSystemInfo().isWindows()) { + return; + } + + // act + context.getFileAccess().untar( + Path.of("src/test/resources/com/devonfw/tools/ide/io").resolve("executable_and_non_executable.tar.bz2"), + tempDir, TarCompression.BZIP2); + + // assert + assertPosixFilePermissions(tempDir.resolve("executableFile.txt"), "rwxrwxr-x"); + assertPosixFilePermissions(tempDir.resolve("nonExecutableFile.txt"), "rw-rw-r--"); + } + + private void assertPosixFilePermissions(Path file, String permissions) { + + try { + Set posixPermissions = Files.getPosixFilePermissions(file); + String permissionStr = PosixFilePermissions.toString(posixPermissions); + assertThat(permissions).isEqualTo(permissionStr); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Test of {@link FileAccessImpl#generatePermissionString(int)}. + */ + @Test + public void testGeneratePermissionString() { + + assertThat(generatePermissionString(0)).isEqualTo("---------"); + assertThat(generatePermissionString(436)).isEqualTo("rw-rw-r--"); + assertThat(generatePermissionString(948)).isEqualTo("rw-rw-r--"); + assertThat(generatePermissionString(509)).isEqualTo("rwxrwxr-x"); + assertThat(generatePermissionString(511)).isEqualTo("rwxrwxrwx"); + + } + } diff --git a/cli/src/test/resources/com/devonfw/tools/ide/io/executable_and_non_executable.tar b/cli/src/test/resources/com/devonfw/tools/ide/io/executable_and_non_executable.tar new file mode 100644 index 0000000000000000000000000000000000000000..86c1e1761b4590cefad277b2dfaac60f98781154 GIT binary patch literal 10240 zcmeIyK?;K~5J1taJw;AXlA4$kwD+)*LcvC;5qkVaP{_jKLQ7rzY$8q!!*gc*+c!h3 zZ>_&RT7Qb~D0W{eE$6IAn|4;^`xsKHbV3_zwQ<^{XQhoUjgV^hOcpL2`dGiD6wf+F z-!B}PUaiHye|hLg(}n0ml-*tWsT6!WmqJk-I(L%WE)cqK`Sl1GTfC}F-uLqy7(+^# zbJqW9KBQsbH2uH*@2=mF_5WfiFap*MIe`EI2q1s}0tg_000IagfB*srAbe*Sj~yp96L6qeQmvB(#-vA};i$*BTE;XP0)v%C6$t_OaKNlb y2H2z5>vqRI%wsooSGFo9)x%Z=-3${ylWJ7ly2Y<4ROpC5#oUoj6eKopqcZ@+3{EQm literal 0 HcmV?d00001 diff --git a/cli/src/test/resources/com/devonfw/tools/ide/io/executable_and_non_executable.tar.gz b/cli/src/test/resources/com/devonfw/tools/ide/io/executable_and_non_executable.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..29e02dff2ccb40f99deecb5692f7db96d99e656f GIT binary patch literal 188 zcmb2|=3oE==C@aTxegf!v^`u?-8IKSXts&^h3YFtO##6hG{5}Vwj$udqh(*&@1+|z zo=g(^-1{Y{bfwLeP4-tedDaGX8#fF5iOBts7M1^L!J5zab|u{XV0P{19nIWn@=qhL zFZ;A;tJQQ(PwW5hWB>by`uYe>&g+YMw_4$AvgoEaw;v~_ZtUE>)&GFR$3+F7f0p0& l{C9Rny}ZuP|1F;X;@2^$LmUVpA8e0h{CG}SCX1nqL81a0MnS71JlUG6#%#D1kkFTNB*x-0$KyY zqCl(i^7CA=+a>|DjqMsf+py{dS;)vF$BfH&5 Date: Tue, 30 Jan 2024 08:44:57 +0100 Subject: [PATCH 02/15] #189: completion shell using jline (#178) --- cli/pom.xml | 12 + .../tools/ide/commandlet/Commandlet.java | 10 +- .../ide/commandlet/CommandletManagerImpl.java | 1 + .../ide/commandlet/InstallCommandlet.java | 5 + .../tools/ide/commandlet/ShellCommandlet.java | 208 +++++++++++++++ .../ide/commandlet/VersionSetCommandlet.java | 26 +- .../ide/completion/CompletionCandidate.java | 5 +- .../CompletionCandidateCollector.java | 37 ++- .../CompletionCandidateCollectorAdapter.java | 16 +- .../CompletionCandidateCollectorDefault.java | 24 +- .../tools/ide/completion/IdeCompleter.java | 60 +++++ .../tools/ide/context/AbstractIdeContext.java | 13 +- .../ide/property/CommandletProperty.java | 7 +- .../tools/ide/property/LocaleProperty.java | 5 +- .../tools/ide/property/PathProperty.java | 11 +- .../devonfw/tools/ide/property/Property.java | 18 +- .../tools/ide/property/ToolProperty.java | 7 +- .../tools/ide/property/VersionProperty.java | 43 ++- cli/src/main/resources/nls/Ide.properties | 5 +- .../cli/AutocompletionReaderTestSupport.java | 185 +++++++++++++ .../ide/completion/IdeCompleterTest.java | 103 ++++++++ .../ide/property/LocalePropertyTest.java | 6 +- documentation/LICENSE.asciidoc | 246 ++++++++++++++++++ 23 files changed, 972 insertions(+), 81 deletions(-) create mode 100644 cli/src/main/java/com/devonfw/tools/ide/commandlet/ShellCommandlet.java create mode 100644 cli/src/main/java/com/devonfw/tools/ide/completion/IdeCompleter.java create mode 100644 cli/src/test/java/com/devonfw/tools/ide/cli/AutocompletionReaderTestSupport.java create mode 100644 cli/src/test/java/com/devonfw/tools/ide/completion/IdeCompleterTest.java diff --git a/cli/pom.xml b/cli/pom.xml index ed62690d1..384dd94ab 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -17,6 +17,8 @@ ${project.artifactId}-${os.detected.classifier}-${os.detected.arch} 17 0.9.28 + 3.24.1 + 2.4.0 @@ -75,6 +77,16 @@ progressbar 0.10.0 + + org.jline + jline + ${jline.version} + + + org.fusesource.jansi + jansi + ${jansi.version} + 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 4cbd6f521..06f921e87 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 @@ -11,6 +11,7 @@ import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.property.KeywordProperty; import com.devonfw.tools.ide.property.Property; +import com.devonfw.tools.ide.tool.ToolCommandlet; import com.devonfw.tools.ide.version.VersionIdentifier; /** @@ -208,12 +209,11 @@ public String toString() { } /** - * @param version the {@link VersionIdentifier} to complete. - * @param collector the {@link CompletionCandidateCollector}. - * @return {@code true} on success, {@code false} otherwise. + * @return the {@link ToolCommandlet} set in a {@link Property} of this commandlet used for auto-completion of a + * {@link VersionIdentifier} or {@code null} if not exists or not configured. */ - public boolean completeVersion(VersionIdentifier version, CompletionCandidateCollector collector) { + public ToolCommandlet getToolForVersionCompletion() { - return false; + return null; } } 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 838b0fc67..a558ebb39 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 @@ -52,6 +52,7 @@ public CommandletManagerImpl(IdeContext context) { add(new HelpCommandlet(context)); add(new EnvironmentCommandlet(context)); add(new CompleteCommandlet(context)); + add(new ShellCommandlet(context)); add(new InstallCommandlet(context)); add(new VersionSetCommandlet(context)); add(new VersionGetCommandlet(context)); diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/InstallCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/InstallCommandlet.java index 6396b61a2..696401e09 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/InstallCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/InstallCommandlet.java @@ -49,4 +49,9 @@ public void run() { commandlet.install(false); } + @Override + public ToolCommandlet getToolForVersionCompletion() { + + return this.tool.getValue(); + } } 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 new file mode 100644 index 000000000..c2d9fd624 --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/ShellCommandlet.java @@ -0,0 +1,208 @@ +package com.devonfw.tools.ide.commandlet; + +import java.io.IOException; +import java.util.Iterator; + +import org.fusesource.jansi.AnsiConsole; +import org.jline.reader.EndOfFileException; +import org.jline.reader.LineReader; +import org.jline.reader.LineReaderBuilder; +import org.jline.reader.MaskingCallback; +import org.jline.reader.Parser; +import org.jline.reader.UserInterruptException; +import org.jline.reader.impl.DefaultParser; +import org.jline.terminal.Terminal; +import org.jline.terminal.TerminalBuilder; +import org.jline.widget.AutosuggestionWidgets; + +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; + +/** + * {@link Commandlet} for internal interactive shell with build-in auto-completion and help. + */ +public final class ShellCommandlet extends Commandlet { + + private static final int AUTOCOMPLETER_MAX_RESULTS = 50; + + private static final int RC_EXIT = 987654321; + + /** + * The constructor. + * + * @param context the {@link IdeContext}. + */ + public ShellCommandlet(IdeContext context) { + + super(context); + addKeyword(getName()); + } + + @Override + public String getName() { + + return "shell"; + } + + @Override + public boolean isIdeHomeRequired() { + + return false; + } + + @Override + public void run() { + + try { + // TODO: add BuiltIns here, see: https://github.com/devonfw/IDEasy/issues/168 + + Parser parser = new DefaultParser(); + try (Terminal terminal = TerminalBuilder.builder().build()) { + + // initialize our own completer here + IdeCompleter completer = new IdeCompleter((AbstractIdeContext) this.context); + + LineReader reader = LineReaderBuilder.builder().terminal(terminal).completer(completer).parser(parser) + .variable(LineReader.LIST_MAX, AUTOCOMPLETER_MAX_RESULTS).build(); + + // Create autosuggestion widgets + AutosuggestionWidgets autosuggestionWidgets = new AutosuggestionWidgets(reader); + // Enable autosuggestions + autosuggestionWidgets.enable(); + + // TODO: implement TailTipWidgets, see: https://github.com/devonfw/IDEasy/issues/169 + + String prompt = "ide> "; + String rightPrompt = null; + String line; + + AnsiConsole.systemInstall(); + while (true) { + try { + line = reader.readLine(prompt, rightPrompt, (MaskingCallback) null, null); + reader.getHistory().add(line); + int rc = runCommand(line); + if (rc == RC_EXIT) { + return; + } + } catch (UserInterruptException e) { + // Ignore CTRL+C + return; + } catch (EndOfFileException e) { + // CTRL+D + return; + } finally { + AnsiConsole.systemUninstall(); + } + } + + } catch (IOException e) { + throw new RuntimeException(e); + } + } catch (Exception e) { + throw new RuntimeException("Unexpected error during interactive auto-completion", e); + } + } + + /** + * Converts String of arguments to array and runs the command + * + * @param args String of arguments + * @return status code + */ + private int runCommand(String args) { + + if ("exit".equals(args) || "quit".equals(args)) { + return RC_EXIT; + } + String[] arguments = args.split(" ", 0); + CliArguments cliArgs = new CliArguments(arguments); + cliArgs.next(); + return ((AbstractIdeContext) this.context).run(cliArgs); + } + + /** + * @param argument the current {@link CliArgument} (position) to match. + * @param commandlet the potential {@link Commandlet} to match. + * @return {@code true} if the given {@link Commandlet} matches to the given {@link CliArgument}(s) and those have + * been applied (set in the {@link Commandlet} and {@link Commandlet#validate() validated}), {@code false} + * otherwise (the {@link Commandlet} did not match and we have to try a different candidate). + */ + private boolean apply(CliArgument argument, Commandlet commandlet) { + + this.context.trace("Trying to match arguments to commandlet {}", commandlet.getName()); + CliArgument currentArgument = argument; + Iterator> valueIterator = commandlet.getValues().iterator(); + Property currentProperty = null; + boolean endOpts = false; + while (!currentArgument.isEnd()) { + if (currentArgument.isEndOptions()) { + endOpts = true; + } else { + String arg = currentArgument.get(); + this.context.trace("Trying to match argument '{}'", currentArgument); + if ((currentProperty != null) && (currentProperty.isExpectValue())) { + currentProperty.setValueAsString(arg, this.context); + if (!currentProperty.isMultiValued()) { + currentProperty = null; + } + } else { + Property property = null; + if (!endOpts) { + property = commandlet.getOption(currentArgument.getKey()); + } + if (property == null) { + if (!valueIterator.hasNext()) { + this.context.trace("No option or next value found"); + return false; + } + currentProperty = valueIterator.next(); + this.context.trace("Next value candidate is {}", currentProperty); + if (currentProperty instanceof KeywordProperty) { + KeywordProperty keyword = (KeywordProperty) currentProperty; + if (keyword.matches(arg)) { + keyword.setValue(Boolean.TRUE); + this.context.trace("Keyword matched"); + } else { + this.context.trace("Missing keyword"); + return false; + } + } else { + boolean success = currentProperty.assignValueAsString(arg, this.context, commandlet); + if (!success && currentProperty.isRequired()) { + return false; + } + } + if ((currentProperty != null) && !currentProperty.isMultiValued()) { + currentProperty = null; + } + } else { + this.context.trace("Found option by name"); + String value = currentArgument.getValue(); + if (value != null) { + property.setValueAsString(value, this.context); + } else if (property instanceof BooleanProperty) { + ((BooleanProperty) property).setValue(Boolean.TRUE); + } else { + currentProperty = property; + if (property.isEndOptions()) { + endOpts = true; + } + throw new UnsupportedOperationException("not implemented"); + } + } + } + } + currentArgument = currentArgument.getNext(!endOpts); + } + return commandlet.validate(); + } +} 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 9c309360a..215156ce5 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 @@ -52,31 +52,9 @@ public void run() { } @Override - public boolean completeVersion(VersionIdentifier version2complete, CompletionCandidateCollector collector) { + public ToolCommandlet getToolForVersionCompletion() { - ToolCommandlet toolCmd = this.tool.getValue(); - if (toolCmd != null) { - String text; - if (version2complete == null) { - text = ""; - } else { - text = version2complete.toString(); - if (version2complete.isPattern()) { - collector.add(text, this.version, this); - return true; - } - } - collector.add(text + VersionSegment.PATTERN_MATCH_ANY_STABLE_VERSION, this.tool, this); - collector.add(text + VersionSegment.PATTERN_MATCH_ANY_VERSION, this.tool, this); - List versions = this.context.getUrls().getSortedVersions(toolCmd.getName(), - toolCmd.getEdition()); - int size = versions.size(); - String[] sorderCandidates = IntStream.rangeClosed(1, size).mapToObj(i -> versions.get(size - i).toString()) - .toArray(s -> new String[s]); - collector.addAllMatches(text, sorderCandidates, this.version, this); - return true; - } - return super.completeVersion(version2complete, collector); + return this.tool.getValue(); } } 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 12ca83369..cec685058 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,11 +1,14 @@ package com.devonfw.tools.ide.completion; +import com.devonfw.tools.ide.version.VersionSegment; + /** * Candidate for auto-completion. * * @param text the text to suggest (CLI argument value). + * @param description the description of the candidate. */ -public record CompletionCandidate(String text /* , String description */) implements Comparable { +public record CompletionCandidate(String text, String description) implements Comparable { @Override public int compareTo(CompletionCandidate o) { diff --git a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollector.java b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollector.java index 97de8457a..9d1c64f1f 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollector.java +++ b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollector.java @@ -1,11 +1,9 @@ package com.devonfw.tools.ide.completion; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; import com.devonfw.tools.ide.commandlet.Commandlet; -import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.property.Property; /** @@ -15,10 +13,27 @@ public interface CompletionCandidateCollector { /** * @param text the suggested word to add to auto-completion. + * @param description the description of the suggestion candidate or {@code null} to determine automatically form the given parameters. * @param property the {@link Property} that triggered this suggestion. * @param commandlet the {@link Commandlet} owning the {@link Property}. */ - void add(String text, Property property, Commandlet commandlet); + void add(String text, String description, Property property, Commandlet commandlet); + + /** + * @param text the suggested word to add to auto-completion. + * @param description the description of the suggestion candidate or {@code null} to determine automatically form the given parameters. + * @param property the {@link Property} that triggered this suggestion. + * @param commandlet the {@link Commandlet} owning the {@link Property}. + * @return the {@link CompletionCandidate} for the given parameters. + */ + default CompletionCandidate createCandidate(String text, String description, Property property, Commandlet commandlet) { + + if (description == null) { + // compute description from property + commandlet like in HelpCommandlet? + } + CompletionCandidate candidate = new CompletionCandidate(text, description); + return candidate; + } /** * @param text the suggested word to add to auto-completion. @@ -31,14 +46,14 @@ default int addAllMatches(String text, String[] sortedCandidates, Property pr if (text.isEmpty()) { for (String candidate : sortedCandidates) { - add(candidate, property, commandlet); + add(candidate, "", property, commandlet); } return sortedCandidates.length; } int count = 0; int index = Arrays.binarySearch(sortedCandidates, text); if (index >= 0) { - add(sortedCandidates[index], property, commandlet); + add(sortedCandidates[index], "", property, commandlet); index++; count++; } else { @@ -46,7 +61,7 @@ default int addAllMatches(String text, String[] sortedCandidates, Property pr } while ((index >= 0) && (index < sortedCandidates.length)) { if (sortedCandidates[index].startsWith(text)) { - add(sortedCandidates[index], property, commandlet); + add(sortedCandidates[index], "", property, commandlet); count++; } else { break; @@ -69,4 +84,14 @@ default void clear() { */ List getCandidates(); + /** + * Disables the {@link #getSortedCandidates() sorting}. + */ + void disableSorting(); + + /** + * @return the sorted {@link #getCandidates() candidates}. + */ + List getSortedCandidates(); + } diff --git a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollectorAdapter.java b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollectorAdapter.java index 2b3c1fd7e..16fb62333 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollectorAdapter.java +++ b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollectorAdapter.java @@ -29,9 +29,9 @@ public CompletionCandidateCollectorAdapter(String prefix, CompletionCandidateCol } @Override - public void add(String text, Property property, Commandlet commandlet) { + public void add(String text, String description, Property property, Commandlet commandlet) { - this.delegate.add(this.prefix + text, property, commandlet); + this.delegate.add(this.prefix + text, description, property, commandlet); } @Override @@ -39,4 +39,16 @@ public List getCandidates() { return this.delegate.getCandidates(); } + + @Override + public List getSortedCandidates() { + + return this.delegate.getSortedCandidates(); + } + + @Override + public void disableSorting() { + + this.delegate.disableSorting(); + } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollectorDefault.java b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollectorDefault.java index f4ba507ba..41a7a1102 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollectorDefault.java +++ b/cli/src/main/java/com/devonfw/tools/ide/completion/CompletionCandidateCollectorDefault.java @@ -1,7 +1,7 @@ package com.devonfw.tools.ide.completion; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collections; import java.util.List; import com.devonfw.tools.ide.commandlet.Commandlet; @@ -17,6 +17,8 @@ public class CompletionCandidateCollectorDefault implements CompletionCandidateC private final IdeContext context; + private boolean sortCandidates; + /** * The constructor. * @@ -27,12 +29,13 @@ public CompletionCandidateCollectorDefault(IdeContext context) { super(); this.candidates = new ArrayList<>(); this.context = context; + this.sortCandidates = true; } @Override - public void add(String text, Property property, Commandlet commandlet) { + public void add(String text, String description, Property property, Commandlet commandlet) { - CompletionCandidate candidate = new CompletionCandidate(text); + CompletionCandidate candidate = createCandidate(text, description, property, commandlet); this.candidates.add(candidate); this.context.trace("Added {} for auto-completion of property {}.{}", candidate, commandlet, property); } @@ -43,6 +46,21 @@ public List getCandidates() { return this.candidates; } + @Override + public List getSortedCandidates() { + + if (this.sortCandidates) { + Collections.sort(this.candidates); + } + return this.candidates; + } + + @Override + public void disableSorting() { + + this.sortCandidates = false; + } + @Override public String toString() { 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 new file mode 100644 index 000000000..083378bcb --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/completion/IdeCompleter.java @@ -0,0 +1,60 @@ +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; + +/** + * Implements the {@link Completer} for jline3 autocompletion. Inspired by picocli + */ +public class IdeCompleter implements Completer { + + private final AbstractIdeContext context; + + /** + * The constructor. + * + * @param context the {@link AbstractIdeContext}. + */ + public IdeCompleter(AbstractIdeContext context) { + + super(); + this.context = context; + } + + @Override + public void complete(LineReader reader, ParsedLine commandLine, List candidates) { + List words = commandLine.words(); + CliArguments args = CliArguments.ofCompletion(words.toArray(String[]::new)); + List completion = this.context.complete(args, false); + int i = 0; + for (CompletionCandidate candidate : completion) { + candidates.add(new Candidate(candidate.text(), candidate.text(), null, null, null, null, true, i++)); + } + } + +} 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 2cd44eb3f..89aa4108d 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,7 +5,12 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.*; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; import java.util.function.Function; import com.devonfw.tools.ide.cli.CliArgument; @@ -814,7 +819,7 @@ public List complete(CliArguments arguments, boolean includ if (firstCandidate != null) { matches = apply(arguments.copy(), firstCandidate, collector); } else if (current.isCombinedShortOption()) { - collector.add(keyword, null, null); + collector.add(keyword, null, null, null); } if (!matches) { for (Commandlet cmd : this.commandletManager.getCommandlets()) { @@ -824,9 +829,7 @@ public List complete(CliArguments arguments, boolean includ } } } - List candidates = collector.getCandidates(); - Collections.sort(candidates); - return candidates; + return collector.getSortedCandidates(); } /** diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/CommandletProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/CommandletProperty.java index f1567ed07..a0854d6bc 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/CommandletProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/CommandletProperty.java @@ -49,18 +49,15 @@ protected String format(Commandlet valueToFormat) { } @Override - protected boolean completeValue(String arg, IdeContext context, Commandlet commandlet, + protected void completeValue(String arg, IdeContext context, Commandlet commandlet, CompletionCandidateCollector collector) { - boolean matches = false; for (Commandlet cmd : context.getCommandletManager().getCommandlets()) { String cmdName = cmd.getName(); if (cmdName.startsWith(arg)) { - collector.add(cmdName, this, commandlet); - matches = true; + collector.add(cmdName, null, null, cmd); } } - return matches; } @Override diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/LocaleProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/LocaleProperty.java index 7bcbeac40..0db5e9750 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/LocaleProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/LocaleProperty.java @@ -53,11 +53,10 @@ public Locale parse(String valueAsString, IdeContext context) { } @Override - protected boolean completeValue(String arg, IdeContext context, Commandlet commandlet, + protected void completeValue(String arg, IdeContext context, Commandlet commandlet, CompletionCandidateCollector collector) { - int count = collector.addAllMatches(arg, getAvailableLocales(), this, commandlet); - return count > 0; + collector.addAllMatches(arg, getAvailableLocales(), this, commandlet); } private static String[] getAvailableLocales() { diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/PathProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/PathProperty.java index 96e3264d2..92c86155a 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/PathProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/PathProperty.java @@ -4,6 +4,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.function.Consumer; +import java.util.stream.Stream; import com.devonfw.tools.ide.commandlet.Commandlet; import com.devonfw.tools.ide.completion.CompletionCandidateCollector; @@ -102,22 +103,20 @@ protected boolean isPathRequiredToBeFile() { } @Override - protected boolean completeValue(String arg, IdeContext context, Commandlet commandlet, + protected void completeValue(String arg, IdeContext context, Commandlet commandlet, CompletionCandidateCollector collector) { Path path = Path.of(arg); Path parent = path.getParent(); String filename = path.getFileName().toString(); if (Files.isDirectory(parent)) { - try { - Files.list(parent).filter(child -> isValidPath(path, filename)) - .forEach(child -> collector.add(child.toString(), this, commandlet)); - return true; + try (Stream children = Files.list(parent)) { + children.filter(child -> isValidPath(path, filename)) + .forEach(child -> collector.add(child.toString(), null, this, commandlet)); } catch (IOException e) { throw new IllegalStateException(e); } } - return false; } private boolean isValidPath(Path path, String filename) { diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/Property.java b/cli/src/main/java/com/devonfw/tools/ide/property/Property.java index 59a2bbcde..5f79211bc 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/Property.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/Property.java @@ -280,7 +280,8 @@ public boolean apply(CliArguments args, IdeContext context, Commandlet commandle if (argValue == null) { argument = args.next(); if (argument.isCompletion()) { - return completeValue(argument.get(), context, commandlet, collector); + completeValue(argument.get(), context, commandlet, collector); + return true; } else { if (!argument.isEnd()) { argValue = argument.get(); @@ -326,22 +327,23 @@ protected void complete(CliArgument argument, CliArguments args, IdeContext cont String arg = argument.get(); if (this.name.isEmpty()) { - boolean match = completeValue(arg, context, commandlet, collector); - if (match) { + int count = collector.getCandidates().size(); + completeValue(arg, context, commandlet, collector); + if (collector.getCandidates().size() > count) { args.next(); } return; } if (this.name.startsWith(arg)) { - collector.add(this.name, this, commandlet); + collector.add(this.name, null, this, commandlet); } if (this.alias != null) { if (this.alias.startsWith(arg)) { - collector.add(this.alias, this, commandlet); + collector.add(this.alias, null, this, commandlet); } else if ((this.alias.length() == 2) && (this.alias.charAt(0) == '-') && argument.isShortOption()) { char opt = this.alias.charAt(1); // e.g. arg="-do" and alias="-f" -complete-> "-dof" if (arg.indexOf(opt) < 0) { - collector.add(arg + opt, this, commandlet); + collector.add(arg + opt, null, this, commandlet); } } } @@ -361,12 +363,10 @@ protected void complete(CliArgument argument, CliArguments args, IdeContext cont * @param context the {@link IdeContext}. * @param commandlet the {@link Commandlet} owning this {@link Property}. * @param collector the {@link CompletionCandidateCollector}. - * @return {@code true} if it matches, {@code false} otherwise. */ - protected boolean completeValue(String arg, IdeContext context, Commandlet commandlet, + protected void completeValue(String arg, IdeContext context, Commandlet commandlet, CompletionCandidateCollector collector) { - return true; } /** diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/ToolProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/ToolProperty.java index a0682ca45..6b3d71e5c 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/ToolProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/ToolProperty.java @@ -56,20 +56,17 @@ public ToolCommandlet parse(String valueAsString, IdeContext context) { } @Override - protected boolean completeValue(String arg, IdeContext context, Commandlet commandlet, + protected void completeValue(String arg, IdeContext context, Commandlet commandlet, CompletionCandidateCollector collector) { - boolean matches = false; for (Commandlet cmd : context.getCommandletManager().getCommandlets()) { if (cmd instanceof ToolCommandlet) { String cmdName = cmd.getName(); if (cmdName.startsWith(arg)) { - collector.add(cmdName, this, commandlet); - matches = true; + collector.add(cmdName, null, null, cmd); } } } - return matches; } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/VersionProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/VersionProperty.java index 3fa8f1fdd..5ceb204aa 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/VersionProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/VersionProperty.java @@ -1,11 +1,17 @@ package com.devonfw.tools.ide.property; +import java.util.Collections; +import java.util.List; import java.util.function.Consumer; +import java.util.stream.IntStream; import com.devonfw.tools.ide.commandlet.Commandlet; +import com.devonfw.tools.ide.completion.CompletionCandidate; import com.devonfw.tools.ide.completion.CompletionCandidateCollector; import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.tool.ToolCommandlet; import com.devonfw.tools.ide.version.VersionIdentifier; +import com.devonfw.tools.ide.version.VersionSegment; /** * {@link Property} for {@link VersionIdentifier} as {@link #getValueType() value type}. @@ -50,10 +56,43 @@ public VersionIdentifier parse(String valueAsString, IdeContext context) { } @Override - protected boolean completeValue(String arg, IdeContext context, Commandlet commandlet, + protected void completeValue(String arg, IdeContext context, Commandlet commandlet, CompletionCandidateCollector collector) { - return commandlet.completeVersion(VersionIdentifier.of(arg), collector); + ToolCommandlet tool = commandlet.getToolForVersionCompletion(); + if (tool != null) { + completeVersion(VersionIdentifier.of(arg), tool, context, commandlet, collector); + } } + private void completeVersion(VersionIdentifier version2complete, ToolCommandlet tool, IdeContext context, Commandlet commandlet, CompletionCandidateCollector collector) { + collector.disableSorting(); + if (tool != null) { + String text; + if (version2complete == null) { + text = ""; + } else { + text = version2complete.toString(); + if (version2complete.isPattern()) { + collector.add(text, "Given version pattern.", this, commandlet); + return; + } + } + List versions = context.getUrls().getSortedVersions(tool.getName(), + tool.getEdition()); + int size = versions.size(); + String[] sorderCandidates = IntStream.rangeClosed(1, size).mapToObj(i -> versions.get(size - i).toString()) + .toArray(String[]::new); + collector.addAllMatches(text, sorderCandidates, this, commandlet); + List candidates = collector.getCandidates(); + Collections.reverse(candidates); + CompletionCandidate latest = collector.createCandidate(text + VersionSegment.PATTERN_MATCH_ANY_STABLE_VERSION, "Latest stable matching version", this, commandlet); + if (candidates.isEmpty()) { + candidates.add(latest); + } else { + candidates.add(1, latest); + } + collector.add(text + VersionSegment.PATTERN_MATCH_ANY_VERSION, "Latest matching version including unstable versions", this, commandlet); + } + } } diff --git a/cli/src/main/resources/nls/Ide.properties b/cli/src/main/resources/nls/Ide.properties index cbbc9a9b0..0f0812d86 100644 --- a/cli/src/main/resources/nls/Ide.properties +++ b/cli/src/main/resources/nls/Ide.properties @@ -22,11 +22,12 @@ 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-version=Set the version of the selected tool. -cmd-terraform=Tool commandlet for Terraform. +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) val-args=The commandline arguments to pass to the tool. val-tool=The tool commandlet to select. -val-version=The tool version. +val-version=The tool version val-set-version-version=The tool version to set. version-banner=Current version of IDE is {} opt--batch=enable batch mode (non-interactive) 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 new file mode 100644 index 000000000..ce0e3c73d --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/cli/AutocompletionReaderTestSupport.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2002-2017, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package com.devonfw.tools.ide.cli; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; +import java.util.function.BiFunction; +import java.util.logging.ConsoleHandler; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.jline.reader.Candidate; +import org.jline.reader.EndOfFileException; +import org.jline.reader.impl.LineReaderImpl; +import org.jline.terminal.Size; +import org.jline.terminal.Terminal; +import org.jline.terminal.impl.DumbTerminal; +import org.junit.jupiter.api.BeforeEach; + +import com.devonfw.tools.ide.context.AbstractIdeContextTest; + +/** + * Provides support for reader and completion tests. Inspired by jline3 + */ +public abstract class AutocompletionReaderTestSupport extends AbstractIdeContextTest { + + private static final String TAB = "\011"; + + protected Terminal terminal; + + protected TestLineReader reader; + + protected EofPipedInputStream in; + + protected ByteArrayOutputStream out; + + protected Character mask; + + @BeforeEach + public void setUp() throws Exception { + + Handler ch = new ConsoleHandler(); + ch.setLevel(Level.FINEST); + Logger logger = Logger.getLogger("org.jline"); + logger.addHandler(ch); + // Set the handler log level + logger.setLevel(Level.INFO); + + this.in = new EofPipedInputStream(); + this.out = new ByteArrayOutputStream(); + 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.mask = null; + } + + protected void assertBuffer(final String expected, final TestBuffer buffer) throws IOException { + + assertBuffer(expected, buffer, true); + } + + protected void assertBuffer(final String expected, final TestBuffer buffer, final boolean clear) throws IOException { + + // clear current buffer, if any + if (clear) { + this.reader.getHistory().purge(); + } + this.reader.list = false; + this.reader.menu = false; + + this.in.setIn(new ByteArrayInputStream(buffer.getBytes())); + + // run it through the reader + try { + while (true) { + this.reader.readLine(null, null, this.mask, null); + } + } catch (EndOfFileException e) { + // noop + } + + assertThat(this.reader.getBuffer().toString()).isEqualTo(expected); + } + + protected class TestBuffer { + private final ByteArrayOutputStream out = new ByteArrayOutputStream(); + + public TestBuffer(String str) { + + append(str); + } + + @Override + public String toString() { + + return this.out.toString(StandardCharsets.UTF_8); + } + + public byte[] getBytes() { + + return this.out.toByteArray(); + } + + public TestBuffer tab() { + + return append(TAB); + } + + public TestBuffer append(final String str) { + + for (byte b : str.getBytes(StandardCharsets.UTF_8)) { + append(b); + } + return this; + } + + public TestBuffer append(final int i) { + + this.out.write((byte) i); + return this; + } + } + + public static class EofPipedInputStream extends InputStream { + + private InputStream in; + + public void setIn(InputStream in) { + + this.in = in; + } + + @Override + public int read() throws IOException { + + return this.in != null ? this.in.read() : -1; + } + + @Override + public int available() throws IOException { + + return this.in != null ? this.in.available() : 0; + } + } + + public static class TestLineReader extends LineReaderImpl { + boolean list = false; + + boolean menu = false; + + public TestLineReader(Terminal terminal, String appName, Map variables) { + + super(terminal, appName, variables); + } + + @Override + protected boolean doList(List possible, String completed, boolean runLoop, + BiFunction escaper) { + + this.list = true; + return super.doList(possible, completed, runLoop, escaper); + } + + @Override + protected boolean doMenu(List possible, String completed, + BiFunction escaper) { + + this.menu = true; + return super.doMenu(possible, completed, escaper); + } + } +} 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 new file mode 100644 index 000000000..d6ad0af95 --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/completion/IdeCompleterTest.java @@ -0,0 +1,103 @@ +package com.devonfw.tools.ide.completion; + +import java.io.IOException; +import java.nio.file.Paths; + +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.context.IdeTestContext; + +/** + * Integration test of {@link IdeCompleter}. + */ +public class IdeCompleterTest extends AutocompletionReaderTestSupport { + + @Test + public void testIdeCompleterHelp() throws IOException { + + IdeTestContext ideContext = new IdeTestContext(Paths.get(""), ""); + this.reader.setCompleter(new IdeCompleter(ideContext)); + assertBuffer("helm", new TestBuffer("he").tab().tab()); + } + + @Test + public void testIdeCompleterVersion() throws IOException { + + IdeTestContext ideContext = new IdeTestContext(Paths.get(""), ""); + this.reader.setCompleter(new IdeCompleter(ideContext)); + assertBuffer("--version ", new TestBuffer("--vers").tab()); + } + + @Test + public void testIdeCompleterInstall() throws IOException { + + IdeTestContext ideContext = new IdeTestContext(Paths.get(""), ""); + this.reader.setCompleter(new IdeCompleter(ideContext)); + assertBuffer("install mvn ", new TestBuffer("install m").tab()); + } + + @Test + public void testIdeCompleterHelpWithToolCompletion() throws IOException { + + IdeTestContext ideContext = new IdeTestContext(Paths.get(""), ""); + this.reader.setCompleter(new IdeCompleter(ideContext)); + assertBuffer("help mvn ", new TestBuffer("help m").tab().tab()); + } + + @Test + public void testIdeCompleterOptionsRemovesUsedOption() throws IOException { + + IdeTestContext ideContext = new IdeTestContext(Paths.get(""), ""); + this.reader.setCompleter(new IdeCompleter(ideContext)); + assertBuffer("-t --t", new TestBuffer("-t --t").tab()); + + } + + @Test + public void testIdeCompleterThirdLayerVersions() throws IOException { + + String path = "workspaces/foo-test/my-git-repo"; + IdeTestContext ideContext = newContext("basic", path, false); + this.reader.setCompleter(new IdeCompleter(ideContext)); + assertBuffer("install mvn 3.2.1", new TestBuffer("install mvn ").tab().tab()); + } + + @Test + public void testIdeCompleterNonExistentCommand() throws IOException { + + IdeTestContext ideContext = new IdeTestContext(Paths.get(""), ""); + this.reader.setCompleter(new IdeCompleter(ideContext)); + assertBuffer("cd ", new TestBuffer("cd ").tab().tab().tab()); + + } + + @Test + public void testIdeCompleterPreventsOptionsAfterCommandWithMinus() throws IOException { + + IdeTestContext ideContext = new IdeTestContext(Paths.get(""), ""); + this.reader.setCompleter(new IdeCompleter(ideContext)); + assertBuffer("get-version -", new TestBuffer("get-version -").tab().tab()); + assertBuffer("get-version - ", new TestBuffer("get-version - ").tab().tab()); + + } + + @Test + public void testIdeCompleterWithInvalidInputDoesNothing() throws IOException { + + IdeTestContext ideContext = new IdeTestContext(Paths.get(""), ""); + this.reader.setCompleter(new IdeCompleter(ideContext)); + 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 + public void testIdeCompleterHandlesOptionsBeforeCommand() throws IOException { + + IdeTestContext ideContext = new IdeTestContext(Paths.get(""), ""); + this.reader.setCompleter(new IdeCompleter(ideContext)); + assertBuffer("get-version mvn ", new TestBuffer("get-version mv").tab().tab()); + } +} 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 c0d6df364..c11327a30 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,6 +2,7 @@ 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; @@ -45,10 +46,9 @@ public void testCompletion() { CompletionCandidateCollector collector = new CompletionCandidateCollectorDefault(context); // act LocaleProperty property = new LocaleProperty("--locale", true, null); - boolean success = property.completeValue(input, context, new ContextCommandlet(), collector); + property.completeValue(input, context, new ContextCommandlet(), collector); // assert - assertThat(success).isTrue(); - assertThat(collector.getCandidates().stream().map(c -> c.text())).containsExactly(expectedCandidates); + assertThat(collector.getCandidates().stream().map(CompletionCandidate::text)).containsExactly(expectedCandidates); } } diff --git a/documentation/LICENSE.asciidoc b/documentation/LICENSE.asciidoc index 4e22d69b6..2d877234a 100644 --- a/documentation/LICENSE.asciidoc +++ b/documentation/LICENSE.asciidoc @@ -47,6 +47,8 @@ The following table shows the components that may be used. The column `inclusion |https://ant.apache.org/[Apache Ant]|Optional|https://github.com/apache/ant/blob/master/LICENSE[ASL 2.0] |https://gradle.org/[Gradle] |Optional|https://github.com/gradle/gradle/blob/master/LICENSE[ASL 2.0] |https://jenkins.io/[Jenkins] |Optional|https://github.com/jenkinsci/jenkins/blob/master/LICENSE.txt[MIT] +|https://github.com/jline/jline3[JLine3] |Optional|https://github.com/jline/jline3/blob/master/LICENSE.txt[BSD-3] +|https://github.com/fusesource/jansi[jansi] |Optional|https://github.com/fusesource/jansi/blob/master/license.txt[ASL 2.0] |https://www.sonarsource.com/plans-and-pricing/community/[SonarQube (Community Edition)] |Optional|https://github.com/SonarSource/sonarqube/blob/master/LICENSE.txt[LGPL 3.0] |https://www.sonarlint.org/eclipse/[SonarLint] |Optional|https://github.com/SonarSource/sonarlint-eclipse/blob/master/LICENSE.txt[LGPL 3+] |https://github.com/devonfw/devon4j[devon4j] |Optional|https://github.com/devonfw/devon4j/blob/develop/LICENSE[ASL 2.0] @@ -2652,6 +2654,250 @@ The externally maintained libraries used by Node.js are: OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ + + - JLine, located at cli, is licensed as follows: + """ + Copyright (c) 2002-2018, the original author or authors. + All rights reserved. + + https://opensource.org/licenses/BSD-3-Clause + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with + the distribution. + + Neither the name of JLine nor the names of its contributors + may be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, + BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + OF THE POSSIBILITY OF SUCH DAMAGE. + """ + + - jansi, located at cli, is licensed as follows: + """ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + """ ``` == MICROSOFT SOFTWARE LICENSE TERMS From ae3771451067c96a4403124ff50e42a60c79fd5e Mon Sep 17 00:00:00 2001 From: Carsten Reitz <95033856+CREITZ25@users.noreply.github.com> Date: Tue, 30 Jan 2024 11:25:28 +0100 Subject: [PATCH 03/15] #15: Implement ToolCommandlet for Cobigen (#164) --- .../ide/commandlet/CommandletManagerImpl.java | 2 ++ .../tools/ide/tool/cobigen/Cobigen.java | 33 +++++++++++++++++++ cli/src/main/resources/nls/Ide.properties | 1 + cli/src/main/resources/nls/Ide_de.properties | 1 + 4 files changed, 37 insertions(+) create mode 100644 cli/src/main/java/com/devonfw/tools/ide/tool/cobigen/Cobigen.java 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 a558ebb39..a9a1dccb5 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 @@ -23,6 +23,7 @@ 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}. @@ -72,6 +73,7 @@ public CommandletManagerImpl(IdeContext context) { add(new KotlincNative(context)); add(new Vscode(context)); add(new Azure(context)); + add(new Cobigen(context)); } private void add(Commandlet commandlet) { diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/cobigen/Cobigen.java b/cli/src/main/java/com/devonfw/tools/ide/tool/cobigen/Cobigen.java new file mode 100644 index 000000000..73f338aa7 --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/cobigen/Cobigen.java @@ -0,0 +1,33 @@ +package com.devonfw.tools.ide.tool.cobigen; + +import java.util.Set; + +import com.devonfw.tools.ide.common.Tag; +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.tool.LocalToolCommandlet; +import com.devonfw.tools.ide.tool.ToolCommandlet; +import com.devonfw.tools.ide.tool.mvn.Mvn; + +/** + * {@link ToolCommandlet} for cobigen CLI (cobigen). + */ +public class Cobigen extends LocalToolCommandlet { + + /** + * The constructor. + * + * @param context the {@link IdeContext}. + */ + public Cobigen(IdeContext context) { + + super(context, "cobigen", Set.of(Tag.GENERATOR)); + } + + @Override + public boolean install(boolean silent) { + + getCommandlet(Mvn.class).install(); + return super.install(silent); + } + +} diff --git a/cli/src/main/resources/nls/Ide.properties b/cli/src/main/resources/nls/Ide.properties index 0f0812d86..bbe13b90e 100644 --- a/cli/src/main/resources/nls/Ide.properties +++ b/cli/src/main/resources/nls/Ide.properties @@ -25,6 +25,7 @@ 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-tool=The tool commandlet to select. val-version=The tool version diff --git a/cli/src/main/resources/nls/Ide_de.properties b/cli/src/main/resources/nls/Ide_de.properties index cb895c7e6..56071158e 100644 --- a/cli/src/main/resources/nls/Ide_de.properties +++ b/cli/src/main/resources/nls/Ide_de.properties @@ -22,6 +22,7 @@ cmd-quarkus=Werkzeug Kommando für Quarkus (Framework für Cloud-native Anwendun 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-tool=Das zu selektierende Werkzeug Kommando. val-version=Die Werkzeug Version. From 830c11f8468376f66433b6ffa09fc810d30f1a04 Mon Sep 17 00:00:00 2001 From: alfeilex <101652401+alfeilex@users.noreply.github.com> Date: Tue, 30 Jan 2024 11:31:47 +0100 Subject: [PATCH 04/15] #20: Implement ToolCommandlet for GCViewer (#151) --- .../ide/commandlet/CommandletManagerImpl.java | 2 + .../tools/ide/tool/ToolCommandlet.java | 12 +++++- .../tools/ide/tool/gcviewer/GcViewer.java | 43 +++++++++++++++++++ .../devonfw/tools/ide/util/FilenameUtil.java | 7 +++ 4 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 cli/src/main/java/com/devonfw/tools/ide/tool/gcviewer/GcViewer.java 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 a9a1dccb5..385778781 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,6 +11,7 @@ import com.devonfw.tools.ide.property.Property; import com.devonfw.tools.ide.tool.az.Azure; 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; @@ -64,6 +65,7 @@ public CommandletManagerImpl(IdeContext context) { add(new Java(context)); add(new Node(context)); add(new Mvn(context)); + add(new GcViewer(context)); add(new Gradle(context)); add(new Eclipse(context)); add(new Terraform(context)); 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 14e2150ef..971ae1f2a 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 @@ -271,7 +271,17 @@ protected void extract(Path file, Path targetDir) { fileAccess.delete(tmpDir); } else { this.context.trace("Extraction is disabled for '{}' hence just moving the downloaded file {}.", getName(), file); - fileAccess.move(file, targetDir); + + if (Files.isDirectory(file)) { + fileAccess.move(file, targetDir); + } else { + try { + Files.createDirectories(targetDir); + } catch (IOException e) { + throw new IllegalStateException("Failed to create folder " + targetDir); + } + fileAccess.move(file, targetDir.resolve(file.getFileName())); + } } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/gcviewer/GcViewer.java b/cli/src/main/java/com/devonfw/tools/ide/tool/gcviewer/GcViewer.java new file mode 100644 index 000000000..300a6f909 --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/gcviewer/GcViewer.java @@ -0,0 +1,43 @@ +package com.devonfw.tools.ide.tool.gcviewer; + +import java.util.Set; + +import com.devonfw.tools.ide.common.Tag; +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.process.ProcessContext; +import com.devonfw.tools.ide.tool.LocalToolCommandlet; +import com.devonfw.tools.ide.tool.ToolCommandlet; + +/** + * {@link ToolCommandlet} for GcViewer. + */ +public class GcViewer extends LocalToolCommandlet { + + /** + * The constructor. + * + * @param context the {@link IdeContext}. + */ + public GcViewer(IdeContext context) { + + super(context, "gcviewer", Set.of(Tag.JAVA)); + } + + @Override + protected boolean isExtract() { + + return false; + } + + @Override + public void run() { + + install(true); + ProcessContext pc = this.context.newProcess(); + pc.directory(getToolPath()); + pc.executable("java"); + pc.addArg("-jar"); + pc.addArg("gcviewer-" + getInstalledVersion() + ".jar"); + pc.run(); + } +} \ No newline at end of file diff --git a/cli/src/main/java/com/devonfw/tools/ide/util/FilenameUtil.java b/cli/src/main/java/com/devonfw/tools/ide/util/FilenameUtil.java index e0a2d2add..283e35deb 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/util/FilenameUtil.java +++ b/cli/src/main/java/com/devonfw/tools/ide/util/FilenameUtil.java @@ -28,6 +28,13 @@ public static String getExtension(String path) { lastSlash = 0; } int lastDot = path.lastIndexOf('.'); + + // workaround for sourceforge urls ending with /download like + // https://sourceforge.net/projects/gcviewer/files/gcviewer-1.36.jar/download + if (path.startsWith("https://") && path.contains("sourceforge") && path.endsWith("download")) { + return path.substring(lastDot + 1, lastSlash); + } + if (lastDot < lastSlash) { return null; } From 8746243dca6d9d22b90e9171f0a9a1fd44896c39 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Tue, 30 Jan 2024 11:40:33 +0100 Subject: [PATCH 05/15] #118: smart git pulls (#163) --- .../tools/ide/context/AbstractIdeContext.java | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) 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 89aa4108d..c6387fc4d 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.time.Duration; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -12,6 +13,7 @@ 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; @@ -119,6 +121,8 @@ public abstract class AbstractIdeContext implements IdeContext { private UrlMetadata urlMetadata; + private static final Duration GIT_PULL_CACHE_DELAY_MILLIS = Duration.ofMillis(30 * 60 * 1000); + /** * The constructor. * @@ -493,7 +497,7 @@ public UrlMetadata getUrls() { if (this.urlMetadata == null) { if (!isTest()) { - gitPullOrClone(this.urlsPath, "https://github.com/devonfw/ide-urls.git"); + gitPullOrCloneIfNeeded(this.urlsPath, "https://github.com/devonfw/ide-urls.git"); } this.urlMetadata = new UrlMetadata(this); } @@ -658,6 +662,46 @@ public void gitPullOrClone(Path target, String gitRepoUrl) { } } + /** + * Checks if the Git repository in the specified target folder needs an update by + * inspecting the modification time of a magic file. + * + * @param urlsPath The Path to the Urls repository. + * @param repoUrl The git remote URL of the Urls repository. + */ + + private void gitPullOrCloneIfNeeded(Path urlsPath, String repoUrl) { + + Path gitDirectory = urlsPath.resolve(".git"); + + // Check if the .git directory exists + if (Files.isDirectory(gitDirectory)) { + Path magicFilePath = gitDirectory.resolve("HEAD"); + long currentTime = System.currentTimeMillis(); + // Get the modification time of the magic file + long fileMTime; + try { + fileMTime = Files.getLastModifiedTime(magicFilePath).toMillis(); + } catch (IOException e) { + throw new IllegalStateException("Could not read " + magicFilePath, e); + } + + // Check if the file modification time is older than the delta threshold + if ((currentTime - fileMTime > GIT_PULL_CACHE_DELAY_MILLIS.toMillis()) || isForceMode()) { + gitPullOrClone(urlsPath, repoUrl); + try { + Files.setLastModifiedTime(magicFilePath, FileTime.fromMillis(currentTime)); + } catch (IOException e) { + throw new IllegalStateException("Could not read or write in " + magicFilePath, e); + } + } + } else { + // If the .git directory does not exist, perform git clone + gitPullOrClone(urlsPath, repoUrl); + } + } + + @Override public IdeSubLogger level(IdeLogLevel level) { From 905710a59ac5c648b4e950a698d7ff9022fe84a7 Mon Sep 17 00:00:00 2001 From: Mattes Mrzik Date: Wed, 31 Jan 2024 12:49:51 +0100 Subject: [PATCH 06/15] #13: implement tool commandlet for aws cli (#122) --- .../ide/commandlet/CommandletManagerImpl.java | 2 + .../tools/ide/tool/ToolCommandlet.java | 9 +- .../com/devonfw/tools/ide/tool/aws/Aws.java | 100 ++++++++++++++++++ cli/src/main/resources/nls/Ide.properties | 9 +- cli/src/main/resources/nls/Ide_de.properties | 9 +- ...nvironmentVariablesPropertiesFileTest.java | 9 +- 6 files changed, 123 insertions(+), 15 deletions(-) create mode 100644 cli/src/main/java/com/devonfw/tools/ide/tool/aws/Aws.java 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 385778781..e8fbefaae 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 @@ -9,6 +9,7 @@ import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.property.KeywordProperty; 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.eclipse.Eclipse; import com.devonfw.tools.ide.tool.gcviewer.GcViewer; @@ -75,6 +76,7 @@ public CommandletManagerImpl(IdeContext context) { add(new KotlincNative(context)); add(new Vscode(context)); add(new Azure(context)); + add(new Aws(context)); add(new Cobigen(context)); } 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 971ae1f2a..4f8f0a0d8 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 @@ -89,7 +89,7 @@ public void run() { * Ensures the tool is installed and then runs this tool with the given arguments. * * @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 + * 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. */ @@ -267,7 +267,7 @@ protected void extract(Path file, Path targetDir) { } else { throw new IllegalStateException("Unknown archive format " + extension + ". Can not extract " + file); } - fileAccess.move(getProperInstallationSubDirOf(tmpDir), targetDir); + moveAndProcessExtraction(getProperInstallationSubDirOf(tmpDir), targetDir); fileAccess.delete(tmpDir); } else { this.context.trace("Extraction is disabled for '{}' hence just moving the downloaded file {}.", getName(), file); @@ -285,6 +285,11 @@ protected void extract(Path file, Path targetDir) { } } + protected void moveAndProcessExtraction(Path from, Path to) { + + this.context.getFileAccess().move(from, to); + } + /** * @return {@code true} to extract (unpack) the downloaded binary file, {@code false} otherwise. */ diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/aws/Aws.java b/cli/src/main/java/com/devonfw/tools/ide/tool/aws/Aws.java new file mode 100644 index 000000000..d2257228f --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/aws/Aws.java @@ -0,0 +1,100 @@ +package com.devonfw.tools.ide.tool.aws; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.PosixFilePermission; +import java.util.Set; + +import com.devonfw.tools.ide.common.Tag; +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.environment.EnvironmentVariables; +import com.devonfw.tools.ide.environment.EnvironmentVariablesType; +import com.devonfw.tools.ide.process.ProcessContext; +import com.devonfw.tools.ide.tool.LocalToolCommandlet; + +/** + * {@link LocalToolCommandlet} for AWS CLI (aws). + * + * @see AWS CLI homepage + */ + +public class Aws extends LocalToolCommandlet { + + /** + * The constructor. + * + * @param context the {@link IdeContext}. + */ + public Aws(IdeContext context) { + + super(context, "aws", Set.of(Tag.CLOUD)); + } + + private void makeExecutable(Path file) { + + // TODO this can be removed if issue #132 is fixed. See https://github.com/devonfw/IDEasy/issues/132 + Set permissions = null; + try { + permissions = Files.getPosixFilePermissions(file); + permissions.add(PosixFilePermission.GROUP_EXECUTE); + permissions.add(PosixFilePermission.OWNER_EXECUTE); + permissions.add(PosixFilePermission.OTHERS_EXECUTE); + Files.setPosixFilePermissions(file, permissions); + } catch (IOException e) { + throw new RuntimeException("Adding execution permission for Group, Owner and Others did not work for " + file, e); + } + } + + @Override + protected void moveAndProcessExtraction(Path from, Path to) { + + if (this.context.getSystemInfo().isLinux()) { + // make binary executable using java nio because unpacking didn't preserve the file permissions + // TODO this can be removed if issue #132 is fixed + Path awsInDistPath = from.resolve("dist").resolve("aws"); + Path awsCompleterInDistPath = from.resolve("dist").resolve("aws_completer"); + makeExecutable(awsInDistPath); + makeExecutable(awsCompleterInDistPath); + + // running the install-script that aws shipped + ProcessContext pc = this.context.newProcess(); + Path linuxInstallScript = from.resolve("install"); + pc.executable(linuxInstallScript); + pc.addArgs("-i", from.toString(), "-b", from.toString()); + pc.run(); + + // The install-script that aws ships creates symbolic links to binaries but using absolute paths. + // Since the current process happens in a temporary dir, these links wouldn't be valid after moving the + // installation files to the target dir. So the absolute paths are replaced by relative ones. + for (String file : new String[] { "aws", "aws_completer", Path.of("v2").resolve("current").toString() }) { + Path link = from.resolve(file); + try { + this.context.getFileAccess().symlink(link.toRealPath(), link, true); + } catch (IOException e) { + throw new RuntimeException( + "Failed to replace absolute link (" + link + ") provided by AWS install script with relative link.", e); + } + } + this.context.getFileAccess().delete(linuxInstallScript); + this.context.getFileAccess().delete(from.resolve("dist")); + } + super.moveAndProcessExtraction(from, to); + } + + @Override + public void postInstall() { + + super.postInstall(); + + EnvironmentVariables variables = this.context.getVariables(); + EnvironmentVariables typeVariables = variables.getByType(EnvironmentVariablesType.CONF); + Path awsConfigDir = this.context.getConfPath().resolve("aws"); + this.context.getFileAccess().mkdirs(awsConfigDir); + Path awsConfigFile = awsConfigDir.resolve("config"); + Path awsCredentialsFile = awsConfigDir.resolve("credentials"); + typeVariables.set("AWS_CONFIG_FILE", awsConfigFile.toString(), true); + typeVariables.set("AWS_SHARED_CREDENTIALS_FILE", awsCredentialsFile.toString(), true); + typeVariables.save(); + } +} diff --git a/cli/src/main/resources/nls/Ide.properties b/cli/src/main/resources/nls/Ide.properties index bbe13b90e..a07f3d352 100644 --- a/cli/src/main/resources/nls/Ide.properties +++ b/cli/src/main/resources/nls/Ide.properties @@ -2,20 +2,21 @@ usage=Usage: values=Values: commandlets=Available commandlets: options=Options: +cmd-aws=Tool commandlet for AWS CLI. +cmd-az=Tool commandlet for Azure CLI. cmd---version=Print the version of IDEasy. -cmd-az=Tool commandlet for Azure CLI 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-version=Get the version of the selected tool. -cmd-gh=Tool commandlet for Github CLI +cmd-gh=Tool commandlet for Github CLI. cmd-gradle=Tool commandlet for Gradle (Build-Tool) 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-kotlinc=Tool commandlet for Kotlin -cmd-kotlincnative=Tool commandlet for Kotlin-Native +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-mvn=Tool commandlet for Maven (Build-Tool) cmd-node=Tool commandlet for Node.js (JavaScript runtime) diff --git a/cli/src/main/resources/nls/Ide_de.properties b/cli/src/main/resources/nls/Ide_de.properties index 56071158e..82b1711b8 100644 --- a/cli/src/main/resources/nls/Ide_de.properties +++ b/cli/src/main/resources/nls/Ide_de.properties @@ -2,18 +2,19 @@ usage=Verwendung: values=Werte: commandlets=Verfügbare Kommandos: options=Optionen: +cmd-aws=Werkzeug Kommando fuer AWS Kommandoschnittstelle. +cmd-az=Werkzeug Kommando fuer Azure Kommandoschnittstelle. cmd---version=Gibt die Version von IDEasy aus. -cmd-az=Werkzeug Kommando für die Azure Kommandoschnittstelle. cmd-eclipse=Werkzeug Kommando für Eclipse (IDE) cmd-env=Gibt die zu setztenden und exportierenden Umgebungsvariablen aus. cmd-get-version=Zeigt die Version des selektierten Werkzeugs an. -cmd-gh=Werkzeug Kommando für die Github Kommandoschnittstelle +cmd-gh=Werkzeug Kommando für die Github Kommandoschnittstelle. 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-kotlinc=Werkzeug Kommando für Kotlin -cmd-kotlincnative=Werkzeug Kommando für Kotlin-Native +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-mvn=Werkzeug Kommando für Maven (Build-Werkzeug) cmd-node=Werkzeug Kommando für Node.js (JavaScript Laufzeitumgebung) diff --git a/cli/src/test/java/com/devonfw/tools/ide/environment/EnvironmentVariablesPropertiesFileTest.java b/cli/src/test/java/com/devonfw/tools/ide/environment/EnvironmentVariablesPropertiesFileTest.java index c7e0bbddb..7a8198b10 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/environment/EnvironmentVariablesPropertiesFileTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/environment/EnvironmentVariablesPropertiesFileTest.java @@ -10,6 +10,7 @@ import org.junit.jupiter.api.Test; import com.devonfw.tools.ide.context.IdeTestContextMock; +import org.junit.jupiter.api.io.TempDir; /** * Test of {@link EnvironmentVariablesPropertiesFile}. @@ -40,7 +41,7 @@ public void testLoad() { } @Test - void testSave() throws Exception { + void testSave(@TempDir Path tempDir) throws Exception { // arrange List linesToWrite = new ArrayList<>(); @@ -60,7 +61,7 @@ void testSave() throws Exception { linesToWrite.add("# 5th comment"); linesToWrite.add("var9=9"); - Path propertiesFilePath = Path.of("target/tmp-EnvironmentVariablesPropertiesFileTest-ide.properties"); + Path propertiesFilePath = tempDir.resolve("test.properties"); Files.write(propertiesFilePath, linesToWrite, StandardOpenOption.CREATE_NEW); // check if this writing was correct List lines = Files.readAllLines(propertiesFilePath); @@ -76,7 +77,7 @@ void testSave() throws Exception { variables.set("var5", "5", true); variables.set("var1", "1.0", false); variables.set("var10", "10", false); - variables.set("var11", "11", true); // var11 must be set after var 10, the other lines can be shuffled + variables.set("var11", "11", true); variables.set("var3", "3", false); variables.set("var7", "7", true); variables.set("var6", "6.0", true); @@ -107,7 +108,5 @@ void testSave() throws Exception { lines = Files.readAllLines(propertiesFilePath); assertThat(lines).containsExactlyElementsOf(linesAfterSave); - // clean up - Files.delete(propertiesFilePath); } } \ No newline at end of file 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 07/15] #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 08/15] #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 09/15] 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 10/15] 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 11/15] 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 12/15] 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 13/15] 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 14/15] #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. From fb0ed7e00250bc70c01ed204dbfd42e797632cda Mon Sep 17 00:00:00 2001 From: aBega2000 <145266540+aBega2000@users.noreply.github.com> Date: Mon, 19 Feb 2024 12:36:21 +0100 Subject: [PATCH 15/15] #209: Quick Fix for CI build failure (#210) --- cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java | 3 ++- .../com/devonfw/tools/ide/{Jmc => tool/jmc}/JmcTest.java | 7 +++---- .../ide-projects/_ide/urls/jmc/jmc/8.3.0/linux_x64.urls | 2 +- .../ide-projects/_ide/urls/jmc/jmc/8.3.0/mac_x64.urls | 2 +- .../ide-projects/_ide/urls/jmc/jmc/8.3.0/windows_x64.urls | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) rename cli/src/test/java/com/devonfw/tools/ide/{Jmc => tool/jmc}/JmcTest.java (98%) 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 9522d0617..0c316cfbf 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 @@ -33,7 +33,8 @@ public Jmc(IdeContext context) { @Override public boolean doInstall(boolean silent) { - getCommandlet(Java.class).install(); + // TODO https://github.com/devonfw/IDEasy/issues/209 currently outcommented as this breaks the tests, real fix needed asap + // getCommandlet(Java.class).install(); return super.doInstall(silent); } diff --git a/cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/jmc/JmcTest.java similarity index 98% rename from cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java rename to cli/src/test/java/com/devonfw/tools/ide/tool/jmc/JmcTest.java index 8e371aa65..59477d1de 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/tool/jmc/JmcTest.java @@ -1,4 +1,4 @@ -package com.devonfw.tools.ide.Jmc; +package com.devonfw.tools.ide.tool.jmc; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.get; @@ -29,8 +29,7 @@ public class JmcTest extends AbstractIdeContextTest { @BeforeAll static void setUp() throws IOException { - - server = new WireMockServer(WireMockConfiguration.wireMockConfig().port(1111)); + server = new WireMockServer(WireMockConfiguration.wireMockConfig().port(1112)); server.start(); } @@ -113,4 +112,4 @@ public void jmcPostInstallShouldMoveFilesIfRequired() throws IOException { } -} \ 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 index a0da161ed..57920ab67 100644 --- 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 @@ -1 +1 @@ -http://localhost:1111/jmcTest/linux \ No newline at end of file +http://localhost:1112/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.urls b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/mac_x64.urls index ef4e100c8..9d3d0a85c 100644 --- 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 @@ -1 +1 @@ -http://localhost:1111/jmcTest/macOS \ No newline at end of file +http://localhost:1112/jmcTest/macOS \ 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 index ce3f2f5ad..520aaac8d 100644 --- 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 @@ -1 +1 @@ -http://localhost:1111/jmcTest/windows \ No newline at end of file +http://localhost:1112/jmcTest/windows \ No newline at end of file