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.exc.ThemisDataException;
22  import io.github.tonywasher.joceanus.themis.parser.base.ThemisInstance;
23  import io.github.tonywasher.joceanus.themis.parser.base.ThemisInstance.ThemisClassInstance;
24  import io.github.tonywasher.joceanus.themis.parser.base.ThemisInstance.ThemisExpressionInstance;
25  import io.github.tonywasher.joceanus.themis.parser.base.ThemisInstance.ThemisNodeInstance;
26  import io.github.tonywasher.joceanus.themis.parser.base.ThemisInstance.ThemisTypeInstance;
27  import io.github.tonywasher.joceanus.themis.parser.expr.ThemisExprFieldAccess;
28  import io.github.tonywasher.joceanus.themis.parser.expr.ThemisExprMethodCall;
29  import io.github.tonywasher.joceanus.themis.parser.expr.ThemisExprMethodRef;
30  import io.github.tonywasher.joceanus.themis.parser.expr.ThemisExprName;
31  import io.github.tonywasher.joceanus.themis.parser.node.ThemisNodeImport;
32  import io.github.tonywasher.joceanus.themis.parser.type.ThemisTypeClassInterface;
33  import io.github.tonywasher.joceanus.themis.solver.proj.ThemisSolverClass;
34  import io.github.tonywasher.joceanus.themis.solver.proj.ThemisSolverFile;
35  import io.github.tonywasher.joceanus.themis.solver.proj.ThemisSolverPackage;
36  import io.github.tonywasher.joceanus.themis.solver.proj.ThemisSolverProject;
37  
38  import java.util.ArrayList;
39  import java.util.List;
40  
41  /**
42   * Analysis Mapper.
43   */
44  public class ThemisMapper
45          implements AutoCloseable {
46      /**
47       * Project State.
48       */
49      private final ThemisMapperProjectState theProject;
50  
51      /**
52       * Project State.
53       */
54      private final ThemisMapperFileState theFile;
55  
56      /**
57       * Project State.
58       */
59      private final ThemisMapperTypeState theType;
60  
61      /**
62       * Project State.
63       */
64      private final ThemisMapperNameState theName;
65  
66      /**
67       * Constructor.
68       *
69       * @param pProject the project.
70       * @throws OceanusException on error
71       */
72      public ThemisMapper(final ThemisSolverProject pProject) throws OceanusException {
73          /* Create the project state */
74          theProject = new ThemisMapperProjectState(pProject);
75  
76          /* Create the file state */
77          theFile = new ThemisMapperFileState(theProject);
78          theType = new ThemisMapperTypeState();
79          theName = new ThemisMapperNameState();
80      }
81  
82      /**
83       * PreProcess package.
84       *
85       * @param pPackage the package
86       */
87      public void preProcessPackage(final ThemisSolverPackage pPackage) {
88          /* Loop through the files in the package */
89          for (ThemisSolverFile myFile : pPackage.getFiles()) {
90              /* preProcess the file if it has not been done yet */
91              if (myFile.needsPreProcess()) {
92                  preProcessFile(myFile);
93              }
94          }
95      }
96  
97      /**
98       * Process package.
99       *
100      * @param pPackage the package
101      * @throws OceanusException on error
102      */
103     public void processPackage(final ThemisSolverPackage pPackage) throws OceanusException {
104         /* Loop through the files in the package */
105         for (ThemisSolverFile myFile : pPackage.getFiles()) {
106             /* Process the file */
107             processFile(myFile);
108         }
109 
110         /* Loop through the files in the package */
111         for (ThemisSolverFile myFile : pPackage.getFiles()) {
112             /* Process the local references */
113             myFile.processLocalReferences();
114         }
115     }
116 
117     /**
118      * Reset fileState.
119      *
120      * @param pFile the file
121      */
122     private void resetFileState(final ThemisSolverFile pFile) {
123         /* Reset the various states */
124         theFile.initForFile(pFile);
125         theType.reset();
126         theName.reset();
127     }
128 
129     /**
130      * preProcess file.
131      *
132      * @param pFile the file
133      */
134     private void preProcessFile(final ThemisSolverFile pFile) {
135         /* Note that we have pre-processed this file */
136         pFile.markPreProcessed();
137 
138         /* preProcess the imports */
139         preProcessImports(pFile);
140 
141         /* Reset the fileState */
142         resetFileState(pFile);
143 
144         /* Loop through the classes in the file */
145         for (ThemisSolverClass myClass : pFile.getClasses()) {
146             final ThemisClassInstance myInstance = myClass.getUnderlyingClass();
147 
148             /* Loop through the extends */
149             for (ThemisTypeInstance myExtends : myInstance.getExtends()) {
150                 processAncestor(myClass, myExtends);
151             }
152 
153             /* Loop through the implements */
154             for (ThemisTypeInstance myImplements : myInstance.getImplements()) {
155                 processAncestor(myClass, myImplements);
156             }
157 
158             /* Process inherited children */
159             theFile.processInherited(myClass);
160         }
161     }
162 
163 
164     /**
165      * preProcess file.
166      *
167      * @param pFile the file
168      */
169     private void preProcessImports(final ThemisSolverFile pFile) {
170         /* Loop through all the imports */
171         for (ThemisNodeInstance myNode : pFile.getUnderlyingFile().getContents().getImports()) {
172             /* If the import is of a project file */
173             final ThemisNodeImport myImport = (ThemisNodeImport) myNode;
174             final ThemisSolverClass myClass = theProject.getProjectClassMap().get(myImport.getFullName());
175             if (myClass != null) {
176                 /* Make sure that the file has been pre-processed */
177                 final ThemisSolverFile myFile = (ThemisSolverFile) myClass.getOwningFile();
178                 if (myFile.needsPreProcess()) {
179                     /* PreProcess the file */
180                     preProcessFile(myFile);
181                 }
182             }
183         }
184     }
185 
186     /**
187      * process ancestor.
188      *
189      * @param pClass    the class
190      * @param pAncestor the ancestor
191      */
192     private void processAncestor(final ThemisSolverClass pClass,
193                                  final ThemisTypeInstance pAncestor) {
194         /* Handle ClassInterface Reference */
195         if (pAncestor instanceof ThemisTypeClassInterface myRef) {
196             final ThemisClassInstance myResolved = theFile.processPossibleReference(myRef.getFullName());
197             if (myResolved != null) {
198                 pClass.addAncestor(myResolved.getFullName());
199                 myRef.setClassInstance(myResolved);
200             }
201         }
202     }
203 
204     /**
205      * Process file.
206      *
207      * @param pFile the file
208      * @throws OceanusException on error
209      */
210     private void processFile(final ThemisSolverFile pFile) throws OceanusException {
211         /* Reset the fileState */
212         resetFileState(pFile);
213 
214         /* Obtain the top-level class element */
215         final ThemisSolverClass myBase = pFile.getTopLevel();
216         final ThemisInstance myInstance = (ThemisInstance) myBase.getUnderlyingClass();
217         processInstance(myInstance);
218 
219         /* Propagate the referenced classes */
220         pFile.setReferenced(theFile.getReferenced());
221     }
222 
223     /**
224      * Process instance.
225      *
226      * @param pInstance the instance
227      * @throws OceanusException on error
228      */
229     private void processInstance(final ThemisInstance pInstance) throws OceanusException {
230         /* Process stacks */
231         final boolean bumpType = theType.processInstance(pInstance);
232         final boolean bumpName = theName.processInstance(pInstance);
233 
234         /* Process element */
235         final boolean doChildren = processElement(pInstance);
236 
237         /* Process children */
238         if (doChildren) {
239             for (ThemisInstance myChild : sortedChildren(pInstance)) {
240                 processInstance(myChild);
241             }
242         }
243 
244         /* CleanUp stacks */
245         if (bumpName) {
246             theName.cleanUpAfterInstance();
247         }
248         if (bumpType) {
249             theType.cleanUpAfterInstance();
250         }
251     }
252 
253     /**
254      * Process element.
255      *
256      * @param pElement the element
257      * @return process children? true/false
258      * @throws OceanusException on error
259      */
260     private boolean processElement(final ThemisInstance pElement) throws OceanusException {
261         /* Handle ClassInstance */
262         return switch (pElement) {
263             case ThemisClassInstance myInstance -> processClass(myInstance);
264             case ThemisTypeClassInterface myRef -> processClassReference(myRef);
265             case ThemisExprFieldAccess myAccess -> processFieldAccess(myAccess);
266             case ThemisExprMethodCall myCall -> processMethodCall(myCall);
267             case ThemisExprMethodRef myRef -> processMethodReference(myRef);
268             default -> true;
269         };
270     }
271 
272     /**
273      * Process class.
274      *
275      * @param pInstance the class instance
276      * @return true
277      */
278     private boolean processClass(final ThemisClassInstance pInstance) {
279         final ThemisSolverClass myClass = theProject.getProjectClassMap().get(pInstance.getFullName());
280         theFile.processInherited(myClass);
281         return true;
282     }
283 
284     /**
285      * Process class reference.
286      *
287      * @param pReference the reference
288      * @return false
289      * @throws OceanusException on error
290      */
291     private boolean processClassReference(final ThemisTypeClassInterface pReference) throws OceanusException {
292         /* Process as a possible reference */
293         final ThemisClassInstance myResolved = theFile.processPossibleReference(pReference.getFullName());
294 
295         /* If we failed to resolve */
296         if (myResolved == null) {
297             /* Check for type parameters and variable names */
298             final ThemisInstance myType = theType.lookUpType(pReference.getName());
299             final ThemisInstance myName = theName.lookUpName(pReference.getName());
300 
301             /* Report failure */
302             final boolean bFound = (myType != null || myName != null);
303             if (!bFound) {
304                 throw new ThemisDataException("Unresolved link: " + pReference.getFullName());
305             }
306         }
307 
308         /* Process any type parameters */
309         for (ThemisTypeInstance myParam : pReference.getTypeParams()) {
310             processInstance(myParam);
311         }
312         return false;
313     }
314 
315     /**
316      * Process field Access.
317      *
318      * @param pAccess the fieldAccess
319      * @return false
320      * @throws OceanusException on error
321      */
322     private boolean processFieldAccess(final ThemisExprFieldAccess pAccess) throws OceanusException {
323         final ThemisExpressionInstance myExpr = pAccess.getScope();
324         if (myExpr instanceof ThemisExprName myNameExpr) {
325             /* Check to see if the field access is to an explicit class */
326             final String myFullName = pAccess.toString();
327             final String myNameRef = myNameExpr.toString();
328             ThemisClassInstance myResolved = theFile.processPossibleReference(myFullName);
329 
330             /* If not a full path look to see whether it is a field of a class */
331             if (myResolved == null) {
332                 myResolved = theFile.processPossibleReference(myNameRef);
333             }
334 
335             /* If we still failed to resolve */
336             if (myResolved == null) {
337                 /* Check for variable names */
338                 final ThemisInstance myName = theName.lookUpName(myNameRef);
339 
340                 /* Report failure */
341                 if (myName == null) {
342                     throw new ThemisDataException("Unresolved fieldAccess: " + myNameRef);
343                 }
344             }
345         }
346         return false;
347     }
348 
349     /**
350      * Process method call.
351      *
352      * @param pCall the methodCall
353      * @return true
354      * @throws OceanusException on error
355      */
356     private boolean processMethodCall(final ThemisExprMethodCall pCall) throws OceanusException {
357         final ThemisExpressionInstance myExpr = pCall.getScope();
358         if (myExpr instanceof ThemisExprName myNameExpr) {
359             /* Check to see if the field access is to an explicit class */
360             final String myNameRef = myNameExpr.toString();
361             final ThemisClassInstance myResolved = theFile.processPossibleReference(myNameRef);
362 
363             /* If we still failed to resolve */
364             if (myResolved == null) {
365                 /* Check for variable names */
366                 final ThemisInstance myName = theName.lookUpName(myNameRef);
367 
368                 /* Report failure */
369                 if (myName == null) {
370                     throw new ThemisDataException("Unresolved method call using name: " + myNameRef);
371                 }
372             }
373         } else if (myExpr instanceof ThemisExprFieldAccess myFieldExpr) {
374             processFieldAccess(myFieldExpr);
375         } else if (myExpr instanceof ThemisExprMethodCall myMethodCall) {
376             processMethodCall(myMethodCall);
377         }
378         return true;
379     }
380 
381     /**
382      * Process method Reference.
383      *
384      * @param pReference the reference
385      * @return false
386      * @throws OceanusException on error
387      */
388     private boolean processMethodReference(final ThemisExprMethodRef pReference) throws OceanusException {
389         final ThemisExpressionInstance myExpr = pReference.getScope();
390         if (myExpr instanceof ThemisExprName myNameExpr) {
391             /* Check to see if the method reference is to an explicit class */
392             final String myNameRef = myNameExpr.toString();
393             final ThemisClassInstance myResolved = theFile.processPossibleReference(myNameRef);
394 
395             /* If we still failed to resolve */
396             if (myResolved == null) {
397                 /* Check for variable names */
398                 final ThemisInstance myName = theName.lookUpName(myNameRef);
399 
400                 /* Report failure */
401                 if (myName == null) {
402                     throw new ThemisDataException("Unresolved methodRef: " + myNameRef);
403                 }
404             }
405         }
406         return false;
407     }
408 
409     /**
410      * Obtain sorted list of children.
411      *
412      * @param pInstance the instanceNode
413      * @return the sorted list
414      */
415     private List<ThemisInstance> sortedChildren(final ThemisInstance pInstance) {
416         final List<ThemisInstance> myChildren = new ArrayList<>(pInstance.getChildren());
417         myChildren.sort(this::compareTo);
418         return myChildren;
419     }
420 
421     /**
422      * Comparator that pushes ClassInstances to bottom of list.
423      *
424      * @param pFirst  the first node
425      * @param pSecond the second node
426      * @return -1, 0, 1 according to order
427      */
428     private int compareTo(final ThemisInstance pFirst,
429                           final ThemisInstance pSecond) {
430         if (pFirst instanceof ThemisClassInstance) {
431             return pSecond instanceof ThemisClassInstance ? 0 : 1;
432         }
433         return pSecond instanceof ThemisClassInstance ? -1 : 0;
434     }
435 
436     @Override
437     public void close() {
438         theProject.close();
439     }
440 }