Skip to content

Commit 6fbcd9e

Browse files
Copilotmaybeec
andcommitted
Implement version conflict resolution for template sets
Co-authored-by: maybeec <[email protected]>
1 parent d695e57 commit 6fbcd9e

File tree

19 files changed

+400
-7
lines changed

19 files changed

+400
-7
lines changed

cobigen/cobigen-core-systemtest/src/test/java/com/devonfw/cobigen/systemtest/ClassLoadingIT.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,6 @@ public void callClassLoadingTemplateSetTest() throws Exception {
121121
* @throws Exception test fails
122122
*/
123123
@Test
124-
@Ignore // TODO: re-enable when versions can be detected and version handling was implemented, see:
125-
// https://github.com/devonfw/cobigen/issues/1665
126124
public void callClassLoadingTemplateSetTestWithVersionConflict() throws Exception {
127125

128126
// Mocking
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
public class JarredClass {
2+
3+
public String methodOne(String str) {
4+
return str;
5+
}
6+
7+
public int methodTwo(int a, int b) {
8+
9+
return a+b;
10+
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
public class JarredClass {
2+
3+
public String methodOne(String str) {
4+
return str;
5+
}
6+
7+
public int methodTwo(int a, int b) {
8+
9+
return a-b;
10+
11+
}
12+
}

cobigen/cobigen-core/src/main/java/com/devonfw/cobigen/impl/config/reader/TemplateSetConfigurationManager.java

Lines changed: 164 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
package com.devonfw.cobigen.impl.config.reader;
22

33
import java.io.IOException;
4+
import java.io.InputStream;
45
import java.nio.file.Files;
56
import java.nio.file.Path;
67
import java.util.ArrayList;
78
import java.util.HashMap;
89
import java.util.List;
910
import java.util.Map;
11+
import java.util.stream.Collectors;
1012
import java.util.stream.Stream;
1113

14+
import org.apache.maven.model.Model;
15+
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
16+
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
17+
import org.slf4j.Logger;
18+
import org.slf4j.LoggerFactory;
19+
1220
import com.devonfw.cobigen.api.constants.ConfigurationConstants;
1321
import com.devonfw.cobigen.api.exception.InvalidConfigurationException;
22+
import com.devonfw.cobigen.api.util.MavenCoordinate;
1423
import com.devonfw.cobigen.api.util.TemplatesJarUtil;
1524
import com.devonfw.cobigen.impl.util.FileSystemUtil;
1625

@@ -21,6 +30,9 @@
2130
*/
2231
public class TemplateSetConfigurationManager {
2332

33+
/** Logger instance */
34+
private static final Logger LOG = LoggerFactory.getLogger(TemplateSetConfigurationManager.class);
35+
2436
/** List with the paths of the configuration locations for the template-set.xml files */
2537
private Map<Path, Path> configLocations;
2638

@@ -47,23 +59,60 @@ public Map<Path, Path> getConfigLocations() {
4759
* @param configRoot root directory of the configuration template-sets/adapted
4860
* @return List of Paths to the adapted templateSetFiles
4961
*/
50-
protected List<Path> loadTemplateSetFilesAdapted(Path configRoot) {
62+
public List<Path> loadTemplateSetFilesAdapted(Path configRoot) {
5163

5264
List<Path> templateSetDirectories = retrieveTemplateSetDirectories(configRoot);
5365

66+
// Create a map to hold template set info and their paths
67+
Map<String, TemplateSetInfo> templateSetInfoMap = new HashMap<>();
68+
5469
List<Path> adaptedTemplateSets = new ArrayList<>();
5570
for (Path templateDirectory : templateSetDirectories) {
5671
Path templateSetFilePath = templateDirectory.resolve(ConfigurationConstants.MAVEN_CONFIGURATION_RESOURCE_FOLDER)
5772
.resolve(ConfigurationConstants.TEMPLATE_SET_CONFIG_FILENAME);
5873

5974
// makes sure that only valid template set folders get added
6075
if (Files.exists(templateSetFilePath)) {
61-
adaptedTemplateSets.add(templateSetFilePath);
62-
63-
this.configLocations.put(templateSetFilePath, templateDirectory);
76+
77+
// Parse POM to get Maven coordinates
78+
MavenCoordinate mavenCoordinate = parsePomFromTemplateSet(templateDirectory);
79+
if (mavenCoordinate != null) {
80+
String key = mavenCoordinate.getGroupId() + ":" + mavenCoordinate.getArtifactId();
81+
TemplateSetInfo newInfo = new TemplateSetInfo(templateSetFilePath, templateDirectory, mavenCoordinate);
82+
83+
// Check if we already have a template set with the same groupId:artifactId
84+
TemplateSetInfo existingInfo = templateSetInfoMap.get(key);
85+
if (existingInfo == null || compareVersions(mavenCoordinate.getVersion(), existingInfo.coordinate.getVersion()) > 0) {
86+
// This version is newer or it's the first one we've seen
87+
templateSetInfoMap.put(key, newInfo);
88+
LOG.debug("Found template set {}:{}:{} at {}",
89+
mavenCoordinate.getGroupId(),
90+
mavenCoordinate.getArtifactId(),
91+
mavenCoordinate.getVersion(),
92+
templateDirectory);
93+
} else {
94+
LOG.debug("Skipping older template set {}:{}:{} at {} in favor of version {}",
95+
mavenCoordinate.getGroupId(),
96+
mavenCoordinate.getArtifactId(),
97+
mavenCoordinate.getVersion(),
98+
templateDirectory,
99+
existingInfo.coordinate.getVersion());
100+
}
101+
} else {
102+
// Fallback: if POM parsing fails, include it anyway
103+
LOG.warn("Could not parse Maven coordinates from template set at {}, including anyway", templateDirectory);
104+
adaptedTemplateSets.add(templateSetFilePath);
105+
this.configLocations.put(templateSetFilePath, templateDirectory);
106+
}
64107
}
65108
}
66109

110+
// Add the final selected template sets
111+
for (TemplateSetInfo info : templateSetInfoMap.values()) {
112+
adaptedTemplateSets.add(info.templateSetFilePath);
113+
this.configLocations.put(info.templateSetFilePath, info.templateDirectory);
114+
}
115+
67116
return adaptedTemplateSets;
68117
}
69118

@@ -94,7 +143,7 @@ private List<Path> retrieveTemplateSetDirectories(Path configRoot) {
94143
* @param configRoot root directory of the configuration template-sets/downloaded
95144
* @return List of Paths to the downloaded templateSetFiles
96145
*/
97-
protected List<Path> loadTemplateSetFilesDownloaded(Path configRoot) {
146+
public List<Path> loadTemplateSetFilesDownloaded(Path configRoot) {
98147

99148
// TODO: add check for valid templatesetjar util
100149
List<Path> templateJars = TemplatesJarUtil.getJarFiles(configRoot);
@@ -116,4 +165,114 @@ protected List<Path> loadTemplateSetFilesDownloaded(Path configRoot) {
116165
return downloadedTemplateSets;
117166
}
118167

168+
/**
169+
* Parses the pom.xml file from a template set directory to extract Maven coordinates
170+
*
171+
* @param templateDirectory the template set directory
172+
* @return MavenCoordinate or null if parsing fails
173+
*/
174+
private MavenCoordinate parsePomFromTemplateSet(Path templateDirectory) {
175+
176+
Path pomPath = templateDirectory.resolve("pom.xml");
177+
if (!Files.exists(pomPath)) {
178+
return null;
179+
}
180+
181+
try (InputStream is = Files.newInputStream(pomPath)) {
182+
MavenXpp3Reader reader = new MavenXpp3Reader();
183+
Model model = reader.read(is);
184+
185+
String groupId = model.getGroupId();
186+
String artifactId = model.getArtifactId();
187+
String version = model.getVersion();
188+
189+
// Handle parent POM inheritance
190+
if (groupId == null && model.getParent() != null) {
191+
groupId = model.getParent().getGroupId();
192+
}
193+
if (version == null && model.getParent() != null) {
194+
version = model.getParent().getVersion();
195+
}
196+
197+
if (groupId != null && artifactId != null && version != null) {
198+
return new MavenCoordinate(groupId, artifactId, version);
199+
}
200+
201+
} catch (IOException | XmlPullParserException e) {
202+
LOG.warn("Failed to parse POM file at {}: {}", pomPath, e.getMessage());
203+
}
204+
205+
return null;
206+
}
207+
208+
/**
209+
* Compares two version strings. Returns positive if version1 > version2, negative if version1 < version2, 0 if equal.
210+
*
211+
* This is a simple version comparison that handles semantic versioning and snapshot versions.
212+
*
213+
* @param version1 first version to compare
214+
* @param version2 second version to compare
215+
* @return comparison result
216+
*/
217+
private int compareVersions(String version1, String version2) {
218+
219+
if (version1.equals(version2)) {
220+
return 0;
221+
}
222+
223+
// Remove snapshot suffix for comparison
224+
String v1 = version1.replace("-SNAPSHOT", "");
225+
String v2 = version2.replace("-SNAPSHOT", "");
226+
227+
String[] parts1 = v1.split("\\.");
228+
String[] parts2 = v2.split("\\.");
229+
230+
int maxLength = Math.max(parts1.length, parts2.length);
231+
232+
for (int i = 0; i < maxLength; i++) {
233+
String part1 = i < parts1.length ? parts1[i] : "0";
234+
String part2 = i < parts2.length ? parts2[i] : "0";
235+
236+
try {
237+
int num1 = Integer.parseInt(part1);
238+
int num2 = Integer.parseInt(part2);
239+
int result = Integer.compare(num1, num2);
240+
if (result != 0) {
241+
return result;
242+
}
243+
} catch (NumberFormatException e) {
244+
// Fall back to string comparison if parts are not numeric
245+
int result = part1.compareTo(part2);
246+
if (result != 0) {
247+
return result;
248+
}
249+
}
250+
}
251+
252+
// If all numeric parts are equal, prefer non-snapshot over snapshot
253+
if (version1.contains("-SNAPSHOT") && !version2.contains("-SNAPSHOT")) {
254+
return -1;
255+
} else if (!version1.contains("-SNAPSHOT") && version2.contains("-SNAPSHOT")) {
256+
return 1;
257+
}
258+
259+
return 0;
260+
}
261+
262+
/**
263+
* Helper class to hold template set information
264+
*/
265+
private static class TemplateSetInfo {
266+
267+
final Path templateSetFilePath;
268+
final Path templateDirectory;
269+
final MavenCoordinate coordinate;
270+
271+
TemplateSetInfo(Path templateSetFilePath, Path templateDirectory, MavenCoordinate coordinate) {
272+
this.templateSetFilePath = templateSetFilePath;
273+
this.templateDirectory = templateDirectory;
274+
this.coordinate = coordinate;
275+
}
276+
}
277+
119278
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.devonfw.cobigen.unittest.config.reader;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import java.io.File;
6+
import java.nio.file.Path;
7+
import java.nio.file.Paths;
8+
import java.util.List;
9+
10+
import org.junit.Test;
11+
12+
import com.devonfw.cobigen.impl.config.reader.TemplateSetConfigurationManager;
13+
14+
public class TemplateSetConfigurationManagerTest {
15+
16+
@Test
17+
public void testVersionConflictResolution() throws Exception {
18+
19+
Path configRoot = Paths.get("src/test/resources/testdata/systemtest/ClassLoadTemplateSetTest/conflicted/template-sets/adapted");
20+
21+
TemplateSetConfigurationManager manager = new TemplateSetConfigurationManager();
22+
List<Path> templateSetFiles = manager.loadTemplateSetFilesAdapted(configRoot);
23+
24+
// Should only load one template set (the newest version)
25+
assertThat(templateSetFiles).hasSize(1);
26+
27+
// Should be template-set2 (version 1.1) which is newer than template-set1 (version 1)
28+
assertThat(templateSetFiles.get(0).toString()).contains("template-set2");
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
<groupId>com.devonfw.test</groupId>
5+
<artifactId>classpath-template-set-load-test</artifactId>
6+
<packaging>jar</packaging>
7+
<version>dev-SNAPSHOT-1</version>
8+
<properties>
9+
<maven.compiler.source>1.8</maven.compiler.source>
10+
<maven.compiler.target>1.8</maven.compiler.target>
11+
</properties>
12+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
public class JarredClass {
2+
3+
public String methodOne(String str) {
4+
return str;
5+
}
6+
7+
public int methodTwo(int a, int b) {
8+
9+
return a+b;
10+
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<tns:templateSetConfiguration version="6.0"
3+
xmlns:tns="http://capgemini.com/devonfw/cobigen/TemplateSetConfiguration"
4+
xmlns:cc="http://capgemini.com/devonfw/cobigen/ContextConfiguration"
5+
xmlns:tc="http://capgemini.com/devonfw/cobigen/TemplatesConfiguration"
6+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
7+
xsi:schemaLocation="http://capgemini.com/devonfw/cobigen/TemplateSetConfiguration templateSetConfiguration.xsd ">
8+
<cc:contextConfiguration version="3.0">
9+
<cc:trigger id="entities" type="java">
10+
<!-- <cc:matcher type="fqn" value=".+">-->
11+
<cc:matcher type="fqn" value="((.+\.)?([^\.]+))\.([^\.]+)\.logic\.api\.to\.([^\.]+)Eto">
12+
<cc:variableAssignment type="constant" key="etoName" value="5"/>
13+
<cc:variableAssignment type="regex" key="etoName2" value="4" />
14+
</cc:matcher>
15+
</cc:trigger>
16+
<cc:tags>
17+
<cc:tag name="bla"/>
18+
</cc:tags>
19+
</cc:contextConfiguration>
20+
<tc:templatesConfiguration version="5.0">
21+
<tc:templates>
22+
<tc:template name="t1" destinationPath="generated.txt" templateFile="templates/generated.txt.ftl"
23+
mergeStrategy="override"/>
24+
</tc:templates>
25+
26+
<tc:increments>
27+
<tc:increment name="i1" description="the only increment">
28+
<tc:templateRef ref="t1"/>
29+
</tc:increment>
30+
</tc:increments>
31+
</tc:templatesConfiguration>
32+
</tns:templateSetConfiguration>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Testing JarredClass:
2+
String is ${JarredClass.methodTwo(3,1)}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
<groupId>com.devonfw.test</groupId>
5+
<artifactId>classpath-template-set-load-test</artifactId>
6+
<packaging>jar</packaging>
7+
<version>dev-SNAPSHOT-1.1</version>
8+
<properties>
9+
<maven.compiler.source>1.8</maven.compiler.source>
10+
<maven.compiler.target>1.8</maven.compiler.target>
11+
</properties>
12+
</project>

0 commit comments

Comments
 (0)