11package com .devonfw .cobigen .impl .config .reader ;
22
33import java .io .IOException ;
4+ import java .io .InputStream ;
45import java .nio .file .Files ;
56import java .nio .file .Path ;
67import java .util .ArrayList ;
78import java .util .HashMap ;
89import java .util .List ;
910import java .util .Map ;
11+ import java .util .stream .Collectors ;
1012import 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+
1220import com .devonfw .cobigen .api .constants .ConfigurationConstants ;
1321import com .devonfw .cobigen .api .exception .InvalidConfigurationException ;
22+ import com .devonfw .cobigen .api .util .MavenCoordinate ;
1423import com .devonfw .cobigen .api .util .TemplatesJarUtil ;
1524import com .devonfw .cobigen .impl .util .FileSystemUtil ;
1625
2130 */
2231public 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}
0 commit comments