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.solver.mapper;
19  
20  import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
21  import io.github.tonywasher.joceanus.themis.parser.base.ThemisChar;
22  import io.github.tonywasher.joceanus.themis.parser.base.ThemisInstance.ThemisClassInstance;
23  import io.github.tonywasher.joceanus.themis.parser.base.ThemisInstance.ThemisNodeInstance;
24  import io.github.tonywasher.joceanus.themis.parser.node.ThemisNodeImport;
25  import io.github.tonywasher.joceanus.themis.solver.proj.ThemisSolverClass;
26  import io.github.tonywasher.joceanus.themis.solver.proj.ThemisSolverFile;
27  import io.github.tonywasher.joceanus.themis.solver.proj.ThemisSolverModule;
28  import io.github.tonywasher.joceanus.themis.solver.proj.ThemisSolverPackage;
29  import io.github.tonywasher.joceanus.themis.solver.proj.ThemisSolverProject;
30  import io.github.tonywasher.joceanus.themis.solver.reflect.ThemisReflectExternal;
31  import io.github.tonywasher.joceanus.themis.solver.reflect.ThemisReflectJar;
32  
33  import java.util.ArrayList;
34  import java.util.LinkedHashMap;
35  import java.util.List;
36  import java.util.Map;
37  
38  /**
39   * Project State.
40   */
41  public class ThemisMapperProjectState
42          implements AutoCloseable {
43      /**
44       * Map of all classes defined in the project.
45       */
46      private final Map<String, ThemisSolverClass> theProjectClasses;
47  
48      /**
49       * Map of all external classes referenced in the project.
50       */
51      private final Map<String, ThemisReflectExternal> theExternalClasses;
52  
53      /**
54       * The Jar parser.
55       */
56      private final ThemisReflectJar theJar;
57  
58      /**
59       * Constructor.
60       *
61       * @param pProject the project
62       * @throws OceanusException on error
63       */
64      ThemisMapperProjectState(final ThemisSolverProject pProject) throws OceanusException {
65          /* build the project classMap */
66          theProjectClasses = new LinkedHashMap<>();
67          buildProjectClassMap(pProject);
68  
69          /* build the external classMap */
70          theExternalClasses = new LinkedHashMap<>();
71          buildExternalClassMap(pProject);
72  
73          /* Process external classes */
74          theJar = new ThemisReflectJar(pProject.getProjectParser(), theExternalClasses);
75          theJar.processExternalClasses();
76      }
77  
78      /**
79       * Obtain the project classes.
80       *
81       * @return the project classes.
82       */
83      Map<String, ThemisSolverClass> getProjectClassMap() {
84          return theProjectClasses;
85      }
86  
87      /**
88       * Obtain the external classes.
89       *
90       * @return the external classes.
91       */
92      Map<String, ThemisReflectExternal> getExternalClassMap() {
93          return theExternalClasses;
94      }
95  
96      /**
97       * Build project classMap.
98       *
99       * @param pProject the project
100      */
101     private void buildProjectClassMap(final ThemisSolverProject pProject) {
102         /* Loop through all modules */
103         for (ThemisSolverModule myModule : pProject.getModules()) {
104             /* Loop through all packages */
105             for (ThemisSolverPackage myPackage : myModule.getPackages().values()) {
106                 buildProjectClassMap(myPackage);
107             }
108         }
109     }
110 
111     /**
112      * Build project classMap.
113      *
114      * @param pPackage the package
115      */
116     private void buildProjectClassMap(final ThemisSolverPackage pPackage) {
117         /* Loop through all files */
118         for (ThemisSolverFile myFile : pPackage.getFiles()) {
119             /* Loop through all classes */
120             for (ThemisSolverClass myClass : myFile.getClasses()) {
121                 theProjectClasses.put(myClass.getFullName(), myClass);
122             }
123         }
124     }
125 
126     /**
127      * Build external classMap.
128      *
129      * @param pProject the project
130      */
131     private void buildExternalClassMap(final ThemisSolverProject pProject) {
132         /* Loop through all modules */
133         for (ThemisSolverModule myModule : pProject.getModules()) {
134             /* Loop through all packages */
135             for (ThemisSolverPackage myPackage : myModule.getPackages().values()) {
136                 buildExternalClassMap(myPackage);
137             }
138         }
139     }
140 
141     /**
142      * Build external classMap.
143      *
144      * @param pPackage the package to process.
145      */
146     private void buildExternalClassMap(final ThemisSolverPackage pPackage) {
147         /* Loop through all files */
148         for (ThemisSolverFile myFile : pPackage.getFiles()) {
149             /* Process the imports */
150             for (ThemisNodeInstance myInstance : myFile.getUnderlyingFile().getContents().getImports()) {
151                 /* Determine full name */
152                 final ThemisNodeImport myImport = (ThemisNodeImport) myInstance;
153                 final String myFullName = myImport.getFullName();
154 
155                 /* If this is a previously unseen class */
156                 if (!theProjectClasses.containsKey(myFullName)) {
157                     /* Ensure the external class map */
158                     theExternalClasses.computeIfAbsent(myFullName, n -> new ThemisReflectExternal(myImport));
159                 }
160             }
161         }
162     }
163 
164     /**
165      * Obtain a list of all children of a project class.
166      *
167      * @param pClass the class
168      * @return the children
169      */
170     public List<ThemisClassInstance> listAllInherited(final String pClass) {
171         /* Create list of all ancestors */
172         final List<String> myAncestors = listAllAncestors(pClass);
173 
174         /* Build list of all children of the ancestors */
175         final List<ThemisClassInstance> myResult = new ArrayList<>();
176         for (String myAncestor : myAncestors) {
177             listAllInherited(myResult, myAncestor);
178         }
179         return myResult;
180     }
181 
182     /**
183      * Obtain a list of all inherited children of a class.
184      *
185      * @param pResult the list to populate
186      * @param pClass  the class
187      */
188     private void listAllInherited(final List<ThemisClassInstance> pResult,
189                                   final String pClass) {
190         final ThemisSolverClass myProjectClass = theProjectClasses.get(pClass);
191         if (myProjectClass != null) {
192             listAllProjectInherited(pResult, myProjectClass);
193         } else {
194             listAllExternalInherited(pResult, theExternalClasses.get(pClass));
195         }
196     }
197 
198     /**
199      * Obtain a list of all inherited children of a project class.
200      *
201      * @param pResult the list to populate
202      * @param pClass  the class
203      */
204     private void listAllProjectInherited(final List<ThemisClassInstance> pResult,
205                                          final ThemisSolverClass pClass) {
206         final String myParent = pClass.getFullName();
207         for (ThemisSolverClass myChild : theProjectClasses.values()) {
208             final String myCheckName = myParent + ThemisChar.PERIOD + myChild.getName();
209             if (myCheckName.equals(myChild.getFullName())) {
210                 pResult.add(myChild.getUnderlyingClass());
211             }
212         }
213     }
214 
215     /**
216      * Obtain a list of all inherited children of an external class.
217      *
218      * @param pResult the list to populate
219      * @param pClass  the class
220      */
221     private void listAllExternalInherited(final List<ThemisClassInstance> pResult,
222                                           final ThemisReflectExternal pClass) {
223         final String myParent = pClass.getFullName();
224         for (ThemisReflectExternal myChild : theExternalClasses.values()) {
225             final String myCheckName = myParent + ThemisChar.PERIOD + myChild.getName();
226             if (myCheckName.equals(myChild.getFullName())) {
227                 pResult.add(myChild.getClassInstance());
228             }
229         }
230     }
231 
232     /**
233      * Obtain a list of all ancestors of a class.
234      *
235      * @param pClass the class
236      * @return the ancestors
237      */
238     private List<String> listAllAncestors(final String pClass) {
239         final List<String> myResult = new ArrayList<>();
240         final ThemisSolverClass myClass = theProjectClasses.get(pClass);
241         if (myClass != null) {
242             listAllProjectAncestors(myResult, myClass);
243         } else {
244             listAllExternalAncestors(myResult, theExternalClasses.get(pClass));
245         }
246         return myResult;
247     }
248 
249     /**
250      * Populate a list of all ancestors of a project class.
251      *
252      * @param pExisting the list of existing ancestors
253      * @param pClass    the class
254      */
255     private void listAllProjectAncestors(final List<String> pExisting,
256                                          final ThemisSolverClass pClass) {
257         for (String myAncestor : pClass.getAncestors()) {
258             /* If the ancestor is unknown */
259             if (!pExisting.contains(myAncestor)) {
260                 /* Add the ancestor */
261                 pExisting.add(myAncestor);
262 
263                 /* If the ancestor is a project file */
264                 final ThemisSolverClass myClass = theProjectClasses.get(myAncestor);
265                 if (myClass != null) {
266                     /* Process all ancestors */
267                     listAllProjectAncestors(pExisting, myClass);
268 
269                     /* else must be external so add all ancestors */
270                 } else {
271                     listAllExternalAncestors(pExisting, theExternalClasses.get(myAncestor));
272                 }
273             }
274         }
275     }
276 
277     /**
278      * Populate a list of all ancestors of an external class.
279      *
280      * @param pExisting the list of existing ancestors
281      * @param pClass    the class
282      */
283     private void listAllExternalAncestors(final List<String> pExisting,
284                                           final ThemisReflectExternal pClass) {
285         for (String myAncestor : pClass.getAncestors()) {
286             /* If the ancestor is unknown */
287             if (!pExisting.contains(myAncestor)) {
288                 /* Add the ancestor and all of its ancestors */
289                 pExisting.add(myAncestor);
290                 listAllExternalAncestors(pExisting, theExternalClasses.get(myAncestor));
291             }
292         }
293     }
294 
295     /**
296      * Try a class as a java.lang class.
297      *
298      * @param pName the class name
299      * @return the loaded class or null if it did not exist
300      */
301     public ThemisReflectExternal tryJavaLang(final String pName) {
302         return theJar.tryJavaLang(pName);
303     }
304 
305     /**
306      * Try a class as a java.lang class.
307      *
308      * @param pName the class name
309      * @return the loaded class or null if it did not exist
310      */
311     public ThemisReflectExternal tryNamedClass(final String pName) {
312         return theJar.tryNamedClass(pName);
313     }
314 
315     @Override
316     public void close() {
317         theJar.close();
318     }
319 }