View Javadoc
1   /*
2    * Themis: Java Project Framework
3    * Copyright 2026. Tony Washer
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6    * use this file except in compliance with the License.  You may obtain a copy
7    * of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
14   * License for the specific language governing permissions and limitations under
15   * the License.
16   */
17  
18  package io.github.tonywasher.joceanus.themis.parser.maven;
19  
20  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem;
21  import io.github.tonywasher.joceanus.metis.field.MetisFieldSet;
22  import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
23  import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
24  import io.github.tonywasher.joceanus.themis.exc.ThemisDataException;
25  import io.github.tonywasher.joceanus.themis.parser.base.ThemisDataResource;
26  
27  import java.io.File;
28  import java.util.ArrayList;
29  import java.util.List;
30  
31  /**
32   * Maven Pom.
33   */
34  public class ThemisMavenPom
35          implements MetisFieldItem {
36      /**
37       * The controller.
38       */
39      interface ThemisXMavenControl {
40          /**
41           * Load a pom via its id.
42           *
43           * @param pId the id of the pom
44           * @return the loaded pom
45           * @throws OceanusException on error
46           */
47          ThemisMavenPom loadPomViaId(ThemisMavenId pId) throws OceanusException;
48  
49          /**
50           * Load a pom at the location.
51           *
52           * @param pLocation the location of the pom
53           * @return the loaded pom
54           */
55          ThemisMavenPom loadPomAtLocation(File pLocation) throws OceanusException;
56      }
57  
58      /**
59       * Report fields.
60       */
61      private static final MetisFieldSet<ThemisMavenPom> FIELD_DEFS = MetisFieldSet.newFieldSet(ThemisMavenPom.class);
62  
63      /*
64       * Declare Fields.
65       */
66      static {
67          FIELD_DEFS.declareLocalField(ThemisDataResource.DATA_ID, ThemisMavenPom::getId);
68          FIELD_DEFS.declareLocalField(ThemisDataResource.DATA_PARENT, ThemisMavenPom::getParent);
69          FIELD_DEFS.declareLocalField(ThemisDataResource.DATA_PROPERTIES, ThemisMavenPom::getProperties);
70          FIELD_DEFS.declareLocalField(ThemisDataResource.DATA_XTRADIRS, ThemisMavenPom::getXtraDirs);
71          FIELD_DEFS.declareLocalField(ThemisDataResource.DATA_MODULES, ThemisMavenPom::getModules);
72          FIELD_DEFS.declareLocalField(ThemisDataResource.DATA_VERSIONS, ThemisMavenPom::getTheVersions);
73          FIELD_DEFS.declareLocalField(ThemisDataResource.DATA_DIRECTDEPENDENCIES, ThemisMavenPom::getTheDirectDependencies);
74          FIELD_DEFS.declareLocalField(ThemisDataResource.DATA_DEPENDENCIES, ThemisMavenPom::getTheDependencies);
75      }
76  
77      /**
78       * The controller.
79       */
80      private final ThemisXMavenControl theControl;
81  
82      /**
83       * The location.
84       */
85      private final File theLocation;
86  
87      /**
88       * The parser.
89       */
90      private final ThemisMavenPomParser theParser;
91  
92      /**
93       * The Maven Id.
94       */
95      private final ThemisMavenId theId;
96  
97      /**
98       * The parent.
99       */
100     private final ThemisMavenPom theParent;
101 
102     /**
103      * The property cache.
104      */
105     private final ThemisMavenPropertyCache theProperties;
106 
107     /**
108      * The list of extra directories.
109      */
110     private final List<String> theXtraDirs;
111 
112     /**
113      * The list of modules.
114      */
115     private final List<ThemisMavenPom> theModules;
116 
117     /**
118      * Is this a jar package.
119      */
120     private final boolean isJarPackage;
121 
122     /**
123      * The version cache.
124      */
125     private ThemisMavenVersionCache theVersions;
126 
127     /**
128      * The list of direct dependencies.
129      */
130     private List<ThemisMavenPom> theDirectDependencies;
131 
132     /**
133      * The full list of dependencies.
134      */
135     private List<ThemisMavenId> theDependencies;
136 
137     /**
138      * Constructor.
139      *
140      * @param pController the controller
141      * @param pLocation   the location
142      * @throws OceanusException on error
143      */
144     ThemisMavenPom(final ThemisXMavenControl pController,
145                    final File pLocation) throws OceanusException {
146         /* Store the controller and location */
147         theControl = pController;
148         theLocation = new File(pLocation.getParent());
149 
150         /* Create the caches */
151         theProperties = new ThemisMavenPropertyCache();
152 
153         /* Create the parser */
154         theParser = new ThemisMavenPomParser(pLocation, theProperties);
155 
156         /* Create the various lists */
157         theModules = new ArrayList<>();
158         theXtraDirs = new ArrayList<>();
159 
160         /* Access the parent Pom */
161         final ThemisMavenId myParentId = theParser.getParent();
162 
163         /* If we have a parent */
164         if (myParentId != null) {
165             /* Access pom details */
166             theParent = theControl.loadPomViaId(myParentId);
167 
168             /* Inherit the parent properties and read own properties */
169             theProperties.setParent(theParent.getProperties());
170 
171         } else {
172             theParent = null;
173         }
174 
175         /* Read the properties */
176         theParser.readProperties();
177 
178         /* Load the id */
179         theId = theParser.getId(theParent == null ? null : theParent.getId());
180 
181         /* Determine jar packaging */
182         isJarPackage = theParser.isJarPackaging();
183     }
184 
185     /**
186      * Constructor.
187      *
188      * @param pId the id
189      * @throws OceanusException on error
190      */
191     ThemisMavenPom(final ThemisMavenId pId) throws OceanusException {
192         /* Store the id */
193         theId = pId;
194 
195         /* Store the controller and location */
196         theControl = null;
197         theLocation = null;
198         theParent = null;
199 
200         /* Create the cache */
201         theProperties = new ThemisMavenPropertyCache();
202         theVersions = new ThemisMavenVersionCache();
203 
204         /* Create the parser */
205         theParser = null;
206 
207         /* Set Pom packaging */
208         isJarPackage = false;
209 
210         /* Create the various lists */
211         theModules = new ArrayList<>();
212         theXtraDirs = new ArrayList<>();
213         theDirectDependencies = new ArrayList<>();
214         theDependencies = new ArrayList<>();
215     }
216 
217     @Override
218     public MetisFieldSet<ThemisMavenPom> getDataFieldSet() {
219         return FIELD_DEFS;
220     }
221 
222     @Override
223     public String formatObject(final OceanusDataFormatter pFormatter) {
224         return getId().formatObject(pFormatter);
225     }
226 
227     /**
228      * Obtain the id.
229      *
230      * @return the id
231      */
232     public ThemisMavenId getId() {
233         return theId;
234     }
235 
236     /**
237      * Obtain the artifactId.
238      *
239      * @return the id
240      */
241     public String getArtifactId() {
242         return theId.getArtifactId();
243     }
244 
245     /**
246      * Obtain the location.
247      *
248      * @return the parent
249      */
250     public File getLocation() {
251         return theLocation;
252     }
253 
254     /**
255      * Obtain the parent.
256      *
257      * @return the parent
258      */
259     private ThemisMavenPom getParent() {
260         return theParent;
261     }
262 
263     /**
264      * Is this jar packaging?
265      *
266      * @return true/false
267      * @throws OceanusException on error
268      */
269     public boolean isJarPackaging() throws OceanusException {
270         return isJarPackage;
271     }
272 
273     /**
274      * Obtain the modules.
275      *
276      * @return the modules
277      */
278     public List<ThemisMavenPom> getModules() {
279         return theModules;
280     }
281 
282     /**
283      * Obtain the dependencies.
284      *
285      * @return the dependencies
286      * @throws OceanusException on error
287      */
288     public List<ThemisMavenPom> getDirectDependencies() throws OceanusException {
289         /* If we have not processed dependencies */
290         if (theDirectDependencies == null) {
291             /* Process dependencies */
292             theDirectDependencies = processDependencies();
293         }
294 
295         /* Return the dependencies */
296         return theDirectDependencies;
297     }
298 
299     /**
300      * Obtain the direct dependencies.
301      *
302      * @return the dependencies
303      */
304     private List<ThemisMavenPom> getTheDirectDependencies() {
305         /* Return the dependencies */
306         return theDirectDependencies;
307     }
308 
309     /**
310      * Obtain the dependencies.
311      *
312      * @return the dependencies
313      * @throws OceanusException on error
314      */
315     public List<ThemisMavenId> getDependencies() throws OceanusException {
316         /* If we have not combined dependencies */
317         if (theDependencies == null) {
318             /* Combine dependencies */
319             theDependencies = combineDependencies();
320         }
321 
322         /* Return the dependencies */
323         return theDependencies;
324     }
325 
326     /**
327      * Obtain the dependencies.
328      *
329      * @return the dependencies
330      */
331     public List<ThemisMavenId> getTheDependencies() {
332         /* Return the dependencies */
333         return theDependencies;
334     }
335 
336     /**
337      * Obtain the extraDirectories.
338      *
339      * @return the extra Directories
340      */
341     public List<String> getXtraDirs() {
342         return theXtraDirs;
343     }
344 
345     /**
346      * Obtain the versions.
347      *
348      * @return the versions
349      * @throws OceanusException on error
350      */
351     public ThemisMavenVersionCache getVersions() throws OceanusException {
352         /* If we have not processed versions */
353         if (theVersions == null) {
354             /* Process dependencies */
355             theVersions = processDependencyManagement();
356         }
357 
358         /* Return the versions */
359         return theVersions;
360     }
361 
362     /**
363      * Obtain the versions.
364      *
365      * @return the versions
366      */
367     private ThemisMavenVersionCache getTheVersions() {
368         /* Return the versions */
369         return theVersions;
370     }
371 
372     /**
373      * Obtain the properties.
374      *
375      * @return the properties
376      */
377     ThemisMavenPropertyCache getProperties() {
378         return theProperties;
379     }
380 
381     /**
382      * Process local details.
383      *
384      * @throws OceanusException on error
385      */
386     void processLocalDetails() throws OceanusException {
387         /* Look up any modules */
388         final List<String> myModules = theParser.getModules();
389         for (String myModule : myModules) {
390             /* Load the file at the location */
391             final ThemisMavenPom myLoaded = theControl.loadPomAtLocation(new File(theLocation, myModule));
392             theModules.add(myLoaded);
393         }
394 
395         /* Look up any extra directories */
396         final List<String> myXtraDirs = theParser.getXtraDirs();
397         theXtraDirs.addAll(myXtraDirs);
398     }
399 
400     /**
401      * Process dependencies.
402      *
403      * @return the dependencies
404      * @throws OceanusException on error
405      */
406     private ThemisMavenVersionCache processDependencyManagement() throws OceanusException {
407         /* Allocate the cache */
408         final ThemisMavenVersionCache myCache = new ThemisMavenVersionCache();
409 
410         /* Attach to parent if present */
411         if (theParent != null) {
412             myCache.setParent(theParent.getVersions());
413         }
414 
415         /* Look up any dependencyManagement */
416         final List<ThemisMavenId> myDependencyMgmt = theParser.getDependencyManagement();
417         for (ThemisMavenId myDependency : myDependencyMgmt) {
418             /* If this is a BOM */
419             if ("import".equals(myDependency.getScope())) {
420                 final ThemisMavenPom myLoaded = theControl.loadPomViaId(myDependency);
421                 myCache.importDependencies(myLoaded.getVersions());
422             } else {
423                 myCache.addToCache(myDependency);
424             }
425         }
426 
427         /* Return the cache */
428         return myCache;
429     }
430 
431     /**
432      * Process dependencies.
433      *
434      * @return the dependencies
435      * @throws OceanusException on error
436      */
437     private List<ThemisMavenPom> processDependencies() throws OceanusException {
438         /* Allocate the dependencies */
439         final List<ThemisMavenPom> myPoms = new ArrayList<>();
440 
441         /* Look up any dependencies */
442         final List<ThemisMavenId> myDependencies = theParser.getDependencies();
443         for (ThemisMavenId myDependency : myDependencies) {
444             /* Load the dependency via its id */
445             final ThemisMavenId myId = theVersions.lookUpVersion(myDependency);
446             final ThemisMavenPom myLoaded = theControl.loadPomViaId(myId);
447             myPoms.add(myLoaded);
448         }
449 
450         /* Return the poms */
451         return myPoms;
452     }
453 
454     /**
455      * Combine direct dependencies.
456      *
457      * @return the dependencies
458      * @throws OceanusException on error
459      */
460     private List<ThemisMavenId> combineDependencies() throws OceanusException {
461         /* Allocate the dependencies */
462         final List<ThemisMavenId> myIds = new ArrayList<>();
463 
464         /* Combine dependencies for this pom */
465         combineDependencies(myIds, this);
466 
467         /* Return the id */
468         return myIds;
469     }
470 
471     /**
472      * Combine direct dependencies.
473      *
474      * @param pIds the list to populate
475      * @param pPom the pom to process
476      * @throws OceanusException on error
477      */
478     private static void combineDependencies(final List<ThemisMavenId> pIds,
479                                             final ThemisMavenPom pPom) throws OceanusException {
480         /* Loop through direct dependencies */
481         for (ThemisMavenPom myDependency : pPom.getDirectDependencies()) {
482             /* If we have not processed this dependency before */
483             final ThemisMavenId myId = myDependency.getId();
484             if (checkDependencyId(pIds, myId)) {
485                 /* Combine underlying dependencies */
486                 combineDependencies(pIds, myDependency);
487             }
488         }
489     }
490 
491     /**
492      * Check id.
493      *
494      * @param pDependencies the list of existing dependencies
495      * @param pId           to id to check
496      * @return was the id added true/false
497      * @throws OceanusException on error
498      */
499     static boolean checkDependencyId(final List<ThemisMavenId> pDependencies,
500                                      final ThemisMavenId pId) throws OceanusException {
501         /* Loop through the existing dependencies */
502         for (ThemisMavenId myDependency : pDependencies) {
503             /* If we have a match */
504             if (myDependency.equalsPrefix(pId)) {
505                 /* OK as long as version matches */
506                 if (myDependency.getVersion().equals(pId.getVersion())) {
507                     return false;
508                 }
509 
510                 /* Reject mismatch of versions */
511                 throw new ThemisDataException("Mismatch of dependency versions " + myDependency + " vs " + pId);
512             }
513         }
514 
515         /* No match, so add to list */
516         pDependencies.add(pId);
517         return true;
518     }
519 }