Skip to content

Commit 1890d30

Browse files
areinickelaert-llhohwille
authored
#1800: IntelliJ will automatically switch to standard edition when downloading newer ultimate editions (#1824)
Co-authored-by: Laert Llaveshi <llaveshilaert@gmail.com> Co-authored-by: Jörg Hohwiller <hohwille@users.noreply.github.com>
1 parent 84fe4e9 commit 1890d30

7 files changed

Lines changed: 213 additions & 0 deletions

File tree

CHANGELOG.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ This file documents all notable changes to https://github.com/devonfw/IDEasy[IDE
66

77
Release with new features and bugfixes:
88

9+
* https://github.com/devonfw/IDEasy/issues/1800[#1800]: IDEasy will automatically switch to IntelliJ standard edition when installing newer ultimate edition versions
910
* https://github.com/devonfw/IDEasy/issues/1552[#1552]: User-defined MAVEN_ARGS appends and no longer overwrites IDEasy's defaults
1011
* https://github.com/devonfw/IDEasy/issues/1833[#1833]: No settings repo update with missing `.commit.id`
1112
* https://github.com/devonfw/IDEasy/issues/1693[#1693]: Fix behavior when there's no settings repo (.../settings/.git)

cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,13 +368,20 @@ private void completeRequestRequested(ToolInstallRequest request) {
368368
edition = new ToolEdition(this.tool, getConfiguredEdition());
369369
requested = new ToolEditionAndVersion(edition);
370370
request.setRequested(requested);
371+
371372
} else {
372373
edition = requested.getEdition();
373374
if (edition == null) {
375+
// If no edition was specified, set it to the configured one
374376
edition = new ToolEdition(this.tool, getConfiguredEdition());
375377
requested.setEdition(edition);
376378
}
377379
}
380+
381+
// Adjust edition if necessary based on requested version. This is needed for tools like IntelliJ where we may need to automatically switch editions
382+
requested = adjustRequestedEdition(requested);
383+
edition = requested.getEdition();
384+
378385
GenericVersionRange version = requested.getVersion();
379386
if (version == null) {
380387
version = getConfiguredVersion();
@@ -398,6 +405,19 @@ private void completeRequestRequested(ToolInstallRequest request) {
398405
}
399406
}
400407

408+
/**
409+
* Hook for subclasses to adjust the requested tool edition before the version is finalized.
410+
*
411+
* @param requested the requested {@link ToolEditionAndVersion}
412+
* @return the given or trgansformed {@link ToolEditionAndVersion}
413+
*/
414+
protected ToolEditionAndVersion adjustRequestedEdition(ToolEditionAndVersion requested) {
415+
416+
// default no-op
417+
return requested;
418+
}
419+
420+
401421
private void completeRequestToolPath(ToolInstallRequest request) {
402422

403423
Path toolPath = request.getToolPath();

cli/src/main/java/com/devonfw/tools/ide/tool/ToolEditionAndVersion.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,14 @@ public void setEdition(ToolEdition edition) {
6868
this.edition = edition;
6969
}
7070

71+
/**
72+
* @param edition new value of {@link #getEdition()}.
73+
*/
74+
public void replaceEdition(ToolEdition edition) {
75+
76+
this.edition = edition;
77+
}
78+
7179
/**
7280
* @return the {@link GenericVersionRange} that is installed or configured.
7381
*/

cli/src/main/java/com/devonfw/tools/ide/tool/intellij/Intellij.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,14 @@
2121
import com.devonfw.tools.ide.merge.xml.XmlMerger;
2222
import com.devonfw.tools.ide.process.EnvironmentContext;
2323
import com.devonfw.tools.ide.tool.LocalToolCommandlet;
24+
import com.devonfw.tools.ide.tool.ToolEdition;
25+
import com.devonfw.tools.ide.tool.ToolEditionAndVersion;
2426
import com.devonfw.tools.ide.tool.ToolInstallation;
2527
import com.devonfw.tools.ide.tool.gradle.Gradle;
2628
import com.devonfw.tools.ide.tool.ide.IdeToolCommandlet;
2729
import com.devonfw.tools.ide.tool.ide.IdeaBasedIdeToolCommandlet;
2830
import com.devonfw.tools.ide.tool.mvn.Mvn;
31+
import com.devonfw.tools.ide.version.VersionIdentifier;
2932

3033
/**
3134
* {@link IdeToolCommandlet} for <a href="https://www.jetbrains.com/idea/">IntelliJ</a>.
@@ -34,6 +37,8 @@ public class Intellij extends IdeaBasedIdeToolCommandlet {
3437

3538
private static final Logger LOG = LoggerFactory.getLogger(Intellij.class);
3639

40+
private static final VersionIdentifier INTELLIJ_LAST_SEPARATE_VERSION = VersionIdentifier.of("2025.2.6.1");
41+
3742
private static final String IDEA = "idea";
3843

3944
private static final String IDEA64_EXE = IDEA + "64.exe";
@@ -86,6 +91,36 @@ public void setEnvironment(EnvironmentContext environmentContext, ToolInstallati
8691
environmentContext.withEnvVar("IDEA_PROPERTIES", this.context.getWorkspacePath().resolve(IDEA_PROPERTIES).toString());
8792
}
8893

94+
@Override
95+
protected ToolEditionAndVersion adjustRequestedEdition(ToolEditionAndVersion requested) {
96+
97+
ToolEdition edition = requested.getEdition();
98+
// Check if edition is set as "ultimate"
99+
if ("ultimate".equals(edition.edition())) {
100+
101+
VersionIdentifier version;
102+
if (requested.getVersion() != null) {
103+
version = VersionIdentifier.of(requested.getVersion().toString());
104+
} else {
105+
version = getConfiguredVersion();
106+
}
107+
// Check whether set version warrants switching editions
108+
if ((version.isGreater(INTELLIJ_LAST_SEPARATE_VERSION)) || // Specified version is > 2025.2.6.1 **OR** no specified version but configured version is > 2025.2.6.1
109+
(VersionIdentifier.LATEST.equals(version)) || // No version specified and no configured version
110+
(VersionIdentifier.LATEST_UNSTABLE.equals(version))) { // No version specified and no configured version
111+
// Switching to IntelliJ Standard edition
112+
LOG.warn("""
113+
Notice: You have configured IDEasy to use the IntelliJ Ultimate Edition. Since version 2025.3, the Ultimate and Community editions of IntelliJ have been unified into a single edition.
114+
Since you are attempting to install a version of IntelliJ that is 2025.3 or newer, we are automatically switching your edition to the unified edition to ensure compatibility.
115+
To specifically install the last true ultimate version of IntelliJ, please run "ide install intellij 2025.2.6.1".
116+
Otherwise, we recommend permanently switching to the unified edition by running "ide set-edition intellij intellij".""");
117+
edition = new ToolEdition(this.tool, "intellij");
118+
requested.replaceEdition(edition);
119+
}
120+
}
121+
return requested;
122+
}
123+
89124
private EnvironmentVariables getIntellijEnvironmentVariables(Path projectPath) {
90125
ExtensibleEnvironmentVariables environmentVariables = new ExtensibleEnvironmentVariables(
91126
(AbstractEnvironmentVariables) this.context.getVariables().getParent(), this.context);

cli/src/test/java/com/devonfw/tools/ide/tool/intellij/IntellijTest.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
import com.devonfw.tools.ide.log.IdeLogLevel;
1313
import com.devonfw.tools.ide.os.SystemInfo;
1414
import com.devonfw.tools.ide.os.SystemInfoMock;
15+
import com.devonfw.tools.ide.tool.ToolEdition;
16+
import com.devonfw.tools.ide.tool.ToolEditionAndVersion;
17+
import com.devonfw.tools.ide.version.VersionIdentifier;
1518
import com.github.tomakehurst.wiremock.junit5.WireMockTest;
1619

1720
/**
@@ -195,6 +198,63 @@ void testIntellijMvnAndGradleRepositoryImport() {
195198
""");
196199
}
197200

201+
/**
202+
* Tests whether IDEasy correctly switches editions when no version is specified or configured
203+
*/
204+
@Test
205+
void testAdjustRequestedEditionSwitchesForUltimateWithoutConfiguredVersion() {
206+
207+
// arrange
208+
IdeTestContext context = newContext("intellij");
209+
Intellij commandlet = context.getCommandletManager().getCommandlet(Intellij.class);
210+
ToolEditionAndVersion requested = new ToolEditionAndVersion(new ToolEdition("intellij", "ultimate"));
211+
requested.setVersion(VersionIdentifier.LATEST);
212+
213+
// act
214+
ToolEditionAndVersion adjusted = commandlet.adjustRequestedEdition(requested);
215+
216+
// assert
217+
assertThat(adjusted.getEdition().edition()).isEqualTo("intellij");
218+
}
219+
220+
/**
221+
* Tests whether IDEasy correctly switches editions when the specified version is after 2025.2.6.1
222+
*/
223+
@Test
224+
void testAdjustRequestedEditionSwitchesForUltimateWithVersionAboveCutoff() {
225+
226+
// arrange
227+
IdeTestContext context = newContext("intellij");
228+
Intellij commandlet = context.getCommandletManager().getCommandlet(Intellij.class);
229+
ToolEditionAndVersion requested = new ToolEditionAndVersion(new ToolEdition("intellij", "ultimate"));
230+
requested.setVersion(VersionIdentifier.of("2025.3"));
231+
232+
// act
233+
ToolEditionAndVersion adjusted = commandlet.adjustRequestedEdition(requested);
234+
235+
// assert
236+
assertThat(adjusted.getEdition().edition()).isEqualTo("intellij");
237+
}
238+
239+
/**
240+
* Tests whether IDEasy correctly remains on the ultimate edition when the specified version is 2025.2.6.1
241+
*/
242+
@Test
243+
void testAdjustRequestedEditionDoesNotSwitchForUltimateAtCutoffVersion() {
244+
245+
// arrange
246+
IdeTestContext context = newContext("intellij");
247+
Intellij commandlet = context.getCommandletManager().getCommandlet(Intellij.class);
248+
ToolEditionAndVersion requested = new ToolEditionAndVersion(new ToolEdition("intellij", "ultimate"));
249+
requested.setVersion(VersionIdentifier.of("2025.2.6.1"));
250+
251+
// act
252+
ToolEditionAndVersion adjusted = commandlet.adjustRequestedEdition(requested);
253+
254+
// assert
255+
assertThat(adjusted.getEdition().edition()).isEqualTo("ultimate");
256+
}
257+
198258
/**
199259
* Tests if the custom jvm options of the ide variable INTELLI_VM_ARGS have been set.
200260
*/

url-updater/src/main/java/com/devonfw/tools/ide/url/tool/intellij/IntellijUrlUpdater.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
package com.devonfw.tools.ide.url.tool.intellij;
22

3+
import java.util.ArrayList;
4+
import java.util.Iterator;
35
import java.util.List;
46

57
import com.devonfw.tools.ide.json.JsonMapping;
68
import com.devonfw.tools.ide.url.updater.IdeaBasedUrlUpdater;
79
import com.fasterxml.jackson.core.JsonProcessingException;
810
import com.fasterxml.jackson.databind.ObjectMapper;
11+
import com.devonfw.tools.ide.version.VersionIdentifier;
912

1013
/**
1114
* {@link IdeaBasedUrlUpdater} base class for IntelliJ.
1215
*/
1316
public class IntellijUrlUpdater extends IdeaBasedUrlUpdater {
1417

1518
private static final String JSON_URL = "products?code=IIU%2CIIC&release.type=release";
19+
private static final VersionIdentifier LAST_SEPARATE_VERSION = VersionIdentifier.of("2025.2.6.1");
1620
protected static final List<String> EDITIONS = List.of("ultimate", "intellij");
1721
protected static final ObjectMapper MAPPER = JsonMapping.createWithReflectionSupportForUrlUpdaters();
1822

@@ -36,9 +40,43 @@ protected String doGetVersionUrl() {
3640
@Override
3741
protected IntellijJsonObject getJsonObjectFromResponse(String response, String edition) throws JsonProcessingException {
3842
IntellijJsonObject[] jsonObjects = MAPPER.readValue(response, IntellijJsonObject[].class);
43+
moveUnifiedReleases(jsonObjects);
3944
return jsonObjects[EDITIONS.indexOf(edition)];
4045
}
4146

47+
/**
48+
* This function moves releases later than 2025.2.6.1, which are unified releases of IntelliJ, but are still distributed internall as ultimate editions to the community edition releases in the JSON objects,
49+
* so that they are correctly recognized as community edition releases by the rest of the code.
50+
* @param jsonObjects the array of JSON objects parsed from the response, which contains one object for the ultimate edition and one for the community edition.
51+
* The function modifies this array in-place, so that after execution, all unified releases are moved to the community edition JSON object.
52+
*/
53+
private void moveUnifiedReleases(IntellijJsonObject[] jsonObjects) {
54+
55+
IntellijJsonObject ultimate = jsonObjects[EDITIONS.indexOf("ultimate")];
56+
IntellijJsonObject community = jsonObjects[EDITIONS.indexOf("intellij")];
57+
List<IntellijJsonRelease> movedReleases = new ArrayList<>();
58+
Iterator<IntellijJsonRelease> iterator = ultimate.releases().iterator();
59+
while (iterator.hasNext()) {
60+
IntellijJsonRelease release = iterator.next();
61+
if (isUnifiedRelease(release)) {
62+
movedReleases.add(release);
63+
iterator.remove();
64+
}
65+
}
66+
community.releases().addAll(0, movedReleases);
67+
}
68+
69+
/**
70+
* This function determines, whether a given IntelliJ release is a unified release, meaning that there is no separate community edition available.
71+
* This is the case for all releases greater than 2025.2.6.1.
72+
* @param release the IntelliJ JSON release to check.
73+
* @return {@code true} if the release is a unified release, {@code false} otherwise.
74+
*/
75+
private boolean isUnifiedRelease(IntellijJsonRelease release) {
76+
77+
return VersionIdentifier.of(release.version()).isGreater(LAST_SEPARATE_VERSION);
78+
}
79+
4280

4381
@Override
4482
public String getCpeVendor() {

url-updater/src/test/java/com/devonfw/tools/ide/url/tool/intellij/IntellijUrlUpdaterTest.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,55 @@ void testIntellijJsonUrlUpdaterWithMissingChecksumGeneratesChecksum(@TempDir Pat
130130
Path intellijVersionsPath = tempDir.resolve("intellij").resolve("intellij").resolve("2023.1.2");
131131
assertUrlVersion(intellijVersionsPath, List.of("linux_x64"));
132132
}
133+
134+
/**
135+
* Test if the {@link IntellijUrlUpdater} correctly moves unified releases to the community edition JSON object, so that they are correctly recognized as community edition releases by the rest of the code.
136+
* This is done by providing a mocked JSON response with a unified release and checking whether this release is moved to the community edition JSON object by the {@link IntellijUrlUpdater}.
137+
*/
138+
@Test
139+
void testIntellijJsonResponseMovesUnifiedReleasesToCommunity() throws Exception {
140+
141+
String response = """
142+
[
143+
{
144+
"releases": [
145+
{
146+
"version": "2025.2.6.2",
147+
"downloads": {}
148+
},
149+
{
150+
"version": "2025.2.6.1",
151+
"downloads": {}
152+
},
153+
{
154+
"version": "2024.3.0",
155+
"downloads": {}
156+
}
157+
]
158+
},
159+
{
160+
"releases": [
161+
{
162+
"version": "2025.2.6.1",
163+
"downloads": {}
164+
},
165+
{
166+
"version": "2024.3.0",
167+
"downloads": {}
168+
}
169+
]
170+
}
171+
]
172+
""";
173+
174+
IntellijUrlUpdater updater = new IntellijUrlUpdater();
175+
176+
IntellijJsonObject communityEdition = updater.getJsonObjectFromResponse(response, "intellij");
177+
IntellijJsonObject ultimateEdition = updater.getJsonObjectFromResponse(response, "ultimate");
178+
179+
assertThat(communityEdition.releases()).extracting(IntellijJsonRelease::version)
180+
.containsExactly("2025.2.6.2", "2025.2.6.1", "2024.3.0");
181+
assertThat(ultimateEdition.releases()).extracting(IntellijJsonRelease::version)
182+
.containsExactly("2025.2.6.1", "2024.3.0");
183+
}
133184
}

0 commit comments

Comments
 (0)