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.data.MetisDataItem.MetisDataMap;
21  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem;
22  import io.github.tonywasher.joceanus.metis.field.MetisFieldSet;
23  import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
24  import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
25  import io.github.tonywasher.joceanus.themis.exc.ThemisDataException;
26  import io.github.tonywasher.joceanus.themis.parser.base.ThemisChar;
27  import io.github.tonywasher.joceanus.themis.parser.base.ThemisDataResource;
28  
29  import java.util.HashMap;
30  import java.util.Map;
31  
32  /**
33   * Maven dependency cache.
34   */
35  public class ThemisMavenVersionCache
36          implements MetisDataMap<String, String>, MetisFieldItem {
37      /**
38       * Report fields.
39       */
40      private static final MetisFieldSet<ThemisMavenVersionCache> FIELD_DEFS = MetisFieldSet.newFieldSet(ThemisMavenVersionCache.class);
41  
42      /*
43       * Declare Fields.
44       */
45      static {
46          FIELD_DEFS.declareLocalField(ThemisDataResource.DATA_PARENT, ThemisMavenVersionCache::getParent);
47      }
48  
49      /**
50       * The map of prefix to version.
51       */
52      private final Map<String, String> theMap;
53  
54      /**
55       * The parent cache.
56       */
57      private ThemisMavenVersionCache theParent;
58  
59      /**
60       * Constructor.
61       */
62      ThemisMavenVersionCache() {
63          theMap = new HashMap<>();
64      }
65  
66      @Override
67      public MetisFieldSet<ThemisMavenVersionCache> getDataFieldSet() {
68          return FIELD_DEFS;
69      }
70  
71      @Override
72      public String formatObject(final OceanusDataFormatter pFormatter) {
73          return getClass().getSimpleName();
74      }
75  
76      @Override
77      public Map<String, String> getUnderlyingMap() {
78          return theMap;
79      }
80  
81      /**
82       * Obtain the Parent.
83       *
84       * @return the parent
85       */
86      private ThemisMavenVersionCache getParent() {
87          return theParent;
88      }
89  
90      /**
91       * Set the parent cache.
92       *
93       * @param pParent the parent cache
94       */
95      void setParent(final ThemisMavenVersionCache pParent) {
96          theParent = pParent;
97      }
98  
99      /**
100      * Add dependency to cache.
101      *
102      * @param pId the id to add to the cache
103      * @throws OceanusException on error
104      */
105     public void addToCache(final ThemisMavenId pId) throws OceanusException {
106         /* Determine the prefix and the proposed version */
107         final String myPrefix = buildPrefix(pId);
108         final String myProposed = pId.getVersion();
109 
110         /* Reject null version */
111         if (myProposed == null) {
112             throw new ThemisDataException("Proposed version for " + myPrefix + " is null");
113         }
114 
115         /* Update the dependency */
116         theMap.put(myPrefix, myProposed);
117     }
118 
119     /**
120      * Validate version against cache.
121      *
122      * @param pId the version to check against cache
123      * @return the autoCorrected id.
124      * @throws OceanusException on error
125      */
126     public ThemisMavenId lookUpVersion(final ThemisMavenId pId) throws OceanusException {
127         /* Determine the prefix and the proposed version */
128         final String myPrefix = buildPrefix(pId);
129         final String myProposed = pId.getVersion();
130 
131         /* Look for existing entry */
132         final String myVersion = lookUpVersionInCache(myPrefix);
133 
134         /* If the prefix is in the cache */
135         if (myVersion != null) {
136             /* Handle no proposed version */
137             if (myProposed == null) {
138                 return new ThemisMavenId(pId, myVersion);
139 
140                 /* Check proposed version is valid */
141             } else if (myProposed.equals(myVersion)) {
142                 return pId;
143 
144                 /* Reject conflict */
145             } else {
146                 throw new ThemisDataException("Conflicting explicit dependency version for " + myPrefix
147                         + " Required=" + myVersion
148                         + " Proposed=" + myProposed);
149             }
150 
151             /* else prefix is not in cache */
152         } else {
153             /* Reject if version is unknown */
154             if (myProposed == null) {
155                 throw new ThemisDataException("Proposed version for " + myPrefix + " is null");
156             }
157 
158             /* Add new dependency and return id */
159             theMap.put(myPrefix, pId.getVersion());
160             return pId;
161         }
162     }
163 
164     /**
165      * Import dependencies.
166      *
167      * @param pChild the child dependency cache
168      * @throws OceanusException on error
169      */
170     void importDependencies(final ThemisMavenVersionCache pChild) throws OceanusException {
171         /* Loop through the child entries */
172         for (Map.Entry<String, String> myEntry : pChild.getUnderlyingMap().entrySet()) {
173             /* Access details */
174             final String myPrefix = myEntry.getKey();
175             final String myProposed = myEntry.getValue();
176 
177             /* Look for existing entry */
178             final String myVersion = lookUpVersionInCache(myPrefix);
179 
180             /* Check for mismatch */
181             if (myVersion != null && !myVersion.equals(myProposed)) {
182                 throw new ThemisDataException("Conflicting imported version for " + myPrefix
183                         + " Required=" + myVersion
184                         + " Proposed=" + myProposed);
185             }
186 
187             /* Store the entry */
188             theMap.put(myPrefix, myProposed);
189         }
190     }
191 
192     /**
193      * Look up prefix in chain of caches.
194      *
195      * @param pPrefix the prefix
196      * @return the version (or null)
197      */
198     private String lookUpVersionInCache(final String pPrefix) {
199         /* Look for existing entry */
200         final String myVersion = theMap.get(pPrefix);
201         return (theParent == null || myVersion != null) ? myVersion : theParent.lookUpVersionInCache(pPrefix);
202     }
203 
204     /**
205      * Build prefix.
206      *
207      * @param pId the id
208      * @return the prefix
209      */
210     private String buildPrefix(final ThemisMavenId pId) {
211         return pId.getGroupId() + ThemisChar.COLON + pId.getArtifactId();
212     }
213 }