Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,8 @@ protected ToolInstallation doInstall(ToolInstallRequest request) {
}

@Override
public VersionIdentifier getInstalledVersion() {
return getDelegate().getInstalledVersion();
}

@Override
public String getInstalledEdition() {
return getDelegate().getInstalledEdition();
public EditionAndVersion getInstalledEditionAndVersion() {
return getDelegate().getInstalledEditionAndVersion();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.devonfw.tools.ide.tool;

import com.devonfw.tools.ide.version.VersionIdentifier;

/**
* Simple record holding an installed tool's edition and version together.
*/
public record EditionAndVersion(String edition, VersionIdentifier version) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
Expand Down Expand Up @@ -197,20 +198,31 @@ public String getWindowsRegistryAppName() {
return this.tool;
}

/**
* @return a {@link Map} that maps edition names to the app name to look for in the Windows registry. Default
* returns a single entry with {@code tool -> tool}. Override for tools with multiple editions on Windows.
*/
public Map<String, String> getWindowsRegistryAppNames() {

return Map.of(this.tool, getWindowsRegistryAppName());
}

@Override
public VersionIdentifier getInstalledVersion() {
protected EditionAndVersion computeInstalledEditionAndVersion() {

if (this.context.getSystemInfo().isWindows()) {
WindowsAppInstallation installation = WindowsHelper.get(this.context).getAppInstallationFromRegistry(getWindowsRegistryAppName());
if (installation != null) {
return VersionIdentifier.of(installation.version());
for (Map.Entry<String, String> entry : getWindowsRegistryAppNames().entrySet()) {
WindowsAppInstallation installation = WindowsHelper.get(this.context).getAppInstallationFromRegistry(entry.getValue());
if (installation != null) {
return new EditionAndVersion(entry.getKey(), VersionIdentifier.of(installation.version()));
}
}
}
return null;
}

@Override
public String getInstalledEdition() {
protected String getInstalledEditionDeprecated() {
//TODO: handle "get-edition <globaltool>"
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,15 +118,9 @@ public IdeasyCommandlet(IdeContext context, UpgradeMode mode) {
}

@Override
public VersionIdentifier getInstalledVersion() {
protected EditionAndVersion computeInstalledEditionAndVersion() {

return IdeVersion.getVersionIdentifier();
}

@Override
public String getInstalledEdition() {

return this.tool;
return new EditionAndVersion(this.tool, IdeVersion.getVersionIdentifier());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,23 @@ protected void postExtract(Path extractedDir) {
}

@Override
public VersionIdentifier getInstalledVersion() {
protected EditionAndVersion computeInstalledEditionAndVersion() {

Path toolPath = getToolPath();
if (isToolNotInstalled(toolPath)) {
return null;
}
// Resolve edition and version from a single tool-path lookup (one pass) instead of two separate lookups.
String edition = getInstalledEdition(toolPath);
VersionIdentifier version = getInstalledVersion(toolPath);
if ((edition == null) && (version == null)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for the record: having an edition here but no version seems like an inconsistent state to me.
Hopefully never happens but wouldn't this be more consistent?

Suggested change
if ((edition == null) && (version == null)) {
if (version == null) {

return null;
}
return new EditionAndVersion(edition, version);
}

@Override
protected VersionIdentifier getInstalledVersionDeprecated() {
Comment thread
krystynaShatkovska marked this conversation as resolved.

return getInstalledVersion(getToolPath());
}
Expand Down Expand Up @@ -399,7 +415,7 @@ protected VersionIdentifier getInstalledVersion(Path toolPath) {
}

@Override
public String getInstalledEdition() {
protected String getInstalledEditionDeprecated() {

return getInstalledEdition(getToolPath());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.devonfw.tools.ide.cache.CachedValue;
import com.devonfw.tools.ide.common.Tag;
import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.process.ProcessContext;
Expand All @@ -25,8 +24,6 @@ public abstract class PackageManagerBasedLocalToolCommandlet<P extends ToolComma

private static final Logger LOG = LoggerFactory.getLogger(PackageManagerBasedLocalToolCommandlet.class);

private final CachedValue<VersionIdentifier> installedVersion;

/**
* The constructor.
*
Expand All @@ -37,7 +34,6 @@ public abstract class PackageManagerBasedLocalToolCommandlet<P extends ToolComma
public PackageManagerBasedLocalToolCommandlet(IdeContext context, String tool, Set<Tag> tags) {

super(context, tool, tags);
this.installedVersion = new CachedValue<>(this::determineInstalledVersion);
}

@Override
Expand Down Expand Up @@ -156,30 +152,29 @@ protected boolean isIgnoreMissingSoftwareVersionFile() {
return true;
}

private VersionIdentifier determineInstalledVersion() {
@Override
protected EditionAndVersion computeInstalledEditionAndVersion() {

try {
return computeInstalledVersion();
VersionIdentifier version = computeInstalledVersion();
if (version == null) {
return null;
}
return new EditionAndVersion(this.tool, version);
} catch (Exception e) {
LOG.debug("Failed to compute installed version of {}", this.tool, e);
return null;
}
}

/**
* @return the computed value of the {@link #getInstalledVersion() installed version}.
* @return the computed value of the installed version.
* @implNote Implementations of this method should NOT trigger any tool installation or download. If you need to call
* {@link #runPackageManager(PackageManagerRequest)}, make sure to use {@link #runPackageManager(PackageManagerRequest, boolean)} with
* {@code skipInstallation=true} to avoid inadvertently triggering installations when only checking the version.
*/
protected abstract VersionIdentifier computeInstalledVersion();

@Override
public VersionIdentifier getInstalledVersion() {

return this.installedVersion.get();
}

/**
* Override to ignore the {@code toolPath} parameter and use the package-manager based detection of the actually installed version.
*
Expand All @@ -197,7 +192,7 @@ protected final void performToolInstallation(ToolInstallRequest request, Path in
PackageManagerRequest packageManagerRequest = new PackageManagerRequest(PackageManagerRequest.TYPE_INSTALL, getPackageName())
.setProcessContext(request.getProcessContext()).setVersion(request.getRequested().getResolvedVersion());
runPackageManager(packageManagerRequest, isSkipInstallation()).failOnError();
this.installedVersion.invalidate();
invalidateInstalledEditionAndVersion();
}

/**
Expand All @@ -220,7 +215,7 @@ protected final void performUninstall(Path toolPath) {
if (canBeUninstalled()) {
PackageManagerRequest request = new PackageManagerRequest(PackageManagerRequest.TYPE_UNINSTALL, getPackageName());
runPackageManager(request).failOnError();
this.installedVersion.invalidate();
invalidateInstalledEditionAndVersion();
} else {
LOG.info("IDEasy does not support uninstalling the tool {} since this will break your installation.\n"
+ "If you really want to uninstall it, please uninstall its parent tool via:\n"
Expand Down
93 changes: 91 additions & 2 deletions cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.slf4j.LoggerFactory;
import org.slf4j.event.Level;

import com.devonfw.tools.ide.cache.CachedValue;
import com.devonfw.tools.ide.commandlet.Commandlet;
import com.devonfw.tools.ide.common.Tag;
import com.devonfw.tools.ide.common.Tags;
Expand Down Expand Up @@ -63,6 +64,9 @@ public abstract class ToolCommandlet extends Commandlet implements Tags {

private MacOsHelper macOsHelper;

/** Cached result for {@link #getInstalledEditionAndVersion()}. */
private CachedValue<EditionAndVersion> installedEditionAndVersion;

/**
* Registry for tool-specific auto-completion candidates.
*/
Expand Down Expand Up @@ -608,6 +612,8 @@ protected ToolInstallation createToolInstallation(Path rootDir, VersionIdentifie
protected ToolInstallation createToolInstallation(Path rootDir, Path linkDir, Path binDir, VersionIdentifier version, boolean newInstallation,
EnvironmentContext environmentContext, boolean additionalInstallation) {

// Invalidate cached edition/version so that subsequent calls reflect the new installation
Comment thread
krystynaShatkovska marked this conversation as resolved.
invalidateInstalledEditionAndVersion();
// do not copy the version file into macOS .app bundles: changing the bundle after codesigning breaks the seal.
ToolInstallation toolInstallation = new ToolInstallation(rootDir, linkDir, binDir, version, newInstallation);
setEnvironment(environmentContext, toolInstallation, additionalInstallation);
Expand Down Expand Up @@ -811,10 +817,43 @@ protected MacOsHelper getMacOsHelper() {
return this.macOsHelper;
}

/**
* Gets the installed edition and version together, resolving both in a single operation.
*
* @return the {@link EditionAndVersion} or {@code null} if not installed.
*/
public EditionAndVersion getInstalledEditionAndVersion() {

if (this.installedEditionAndVersion == null) {
this.installedEditionAndVersion = new CachedValue<>(this::computeInstalledEditionAndVersion);
}
return this.installedEditionAndVersion.get();
}

/**
* Hook to compute the installed edition and version together. Override this method in subclasses to resolve both
* edition and version in a single operation, avoiding redundant expensive lookups.
*
* @return the {@link EditionAndVersion} or {@code null} if not installed.
*/
protected EditionAndVersion computeInstalledEditionAndVersion() {

String edition = computeInstalledEdition();
VersionIdentifier version = computeInstalledVersion();
if ((edition == null) && (version == null)) {
return null;
}
return new EditionAndVersion(edition, version);
}

/**
* @return the currently installed {@link VersionIdentifier version} of this tool or {@code null} if not installed.
*/
public abstract VersionIdentifier getInstalledVersion();
public final VersionIdentifier getInstalledVersion() {

EditionAndVersion ev = getInstalledEditionAndVersion();
return (ev != null) ? ev.version() : null;
}

/**
* @return {@code true} if this tool is installed, {@code false} otherwise.
Expand All @@ -827,7 +866,57 @@ public boolean isInstalled() {
/**
* @return the installed edition of this tool or {@code null} if not installed.
*/
public abstract String getInstalledEdition();
public final String getInstalledEdition() {

EditionAndVersion ev = getInstalledEditionAndVersion();
return (ev != null) ? ev.edition() : null;
}

/**
* @deprecated Override {@link #computeInstalledEditionAndVersion()} instead.
* @return the currently installed version.
*/
@Deprecated
protected VersionIdentifier computeInstalledVersion() {

return getInstalledVersionDeprecated();
}

/**
* @deprecated Override {@link #computeInstalledEditionAndVersion()} instead.
* @return the installed edition.
*/
@Deprecated
protected String computeInstalledEdition() {

return getInstalledEditionDeprecated();
}

/**
* @deprecated Override {@link #computeInstalledEditionAndVersion()} instead.
* @return the installed version.
*/
@Deprecated
protected VersionIdentifier getInstalledVersionDeprecated() {

return null;
}

/**
* @deprecated Override {@link #computeInstalledEditionAndVersion()} instead.
* @return the installed edition.
*/
@Deprecated
protected String getInstalledEditionDeprecated() {

return null;
}
Comment on lines +875 to +913

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we still need this or was that only and intermediate state during refactoring?


/** Invalidates the cached installed edition and version so the next call to {@link #getInstalledEditionAndVersion()} recomputes the result. */
protected void invalidateInstalledEditionAndVersion() {

this.installedEditionAndVersion = null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
this.installedEditionAndVersion = null;
if (this.installedEditionAndVersion != null) {
this.installedEditionAndVersion.invalidate();
}

BTW: I would even suggest to make installedEditionAndVersion final and initialize it in the constructor since the creation is cheap and does not compute the value. Then you can skip such if statements and also the lazy init in getInstalledEditionAndVersion() is not needed any more. BTW: In the GUI we will have more concurrency and initialization in constructor is always thread-safe while lazy init in getter could cause race condition.

}

/**
* Uninstalls the {@link #getName() tool}.
Expand Down
Loading