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         if (pElement instanceof ThemisClassInstance myInstance) {
263             final ThemisSolverClass myClass = theProject.getProjectClassMap().get(myInstance.getFullName());
264             theFile.processInherited(myClass);
265         }
266 
267         /* Handle ClassInterface Reference */
268         if (pElement instanceof ThemisTypeClassInterface myRef) {
269             processClassReference(myRef);
270             return false;
271         }
272 
273         /* Handle FieldAccess */
274         if (pElement instanceof ThemisExprFieldAccess myAccess) {
275             processFieldAccess(myAccess);
276             return false;
277         }
278 
279         /* Handle MethodCall */
280         if (pElement instanceof ThemisExprMethodCall myCall) {
281             processMethodCall(myCall);
282             return false;
283         }
284 
285         /* Handle MethodReference */
286         if (pElement instanceof ThemisExprMethodRef myRef) {
287             processMethodReference(myRef);
288             return false;
289         }
290         return true;
291     }
292 
293     /**
294      * Process class reference.
295      *
296      * @param pReference the reference
297      * @throws OceanusException on error
298      */
299     private void processClassReference(final ThemisTypeClassInterface pReference) throws OceanusException {
300         /* Process as a possible reference */
301         final ThemisClassInstance myResolved = theFile.processPossibleReference(pReference.getFullName());
302 
303         /* If we failed to resolve */
304         if (myResolved == null) {
305             /* Check for type parameters and variable names */
306             final ThemisInstance myType = theType.lookUpType(pReference.getName());
307             final ThemisInstance myName = theName.lookUpName(pReference.getName());
308 
309             /* Report failure */
310             final boolean bFound = (myType != null || myName != null);
311             if (!bFound) {
312                 throw new ThemisDataException("Unresolved link: " + pReference.getFullName());
313             }
314         }
315     }
316 
317     /**
318      * Process field Access.
319      *
320      * @param pAccess the fieldAccess
321      * @throws OceanusException on error
322      */
323     private void processFieldAccess(final ThemisExprFieldAccess pAccess) throws OceanusException {
324         final ThemisExpressionInstance myExpr = pAccess.getScope();
325         if (myExpr instanceof ThemisExprName myNameExpr) {
326             /* Check to see if the field access is to an explicit class */
327             final String myFullName = pAccess.toString();
328             final String myNameRef = myNameExpr.toString();
329             ThemisClassInstance myResolved = theFile.processPossibleReference(myFullName);
330 
331             /* If not a full path look to see whether it is a field of a class */
332             if (myResolved == null) {
333                 myResolved = theFile.processPossibleReference(myNameRef);
334             }
335 
336             /* If we still failed to resolve */
337             if (myResolved == null) {
338                 /* Check for variable names */
339                 final ThemisInstance myName = theName.lookUpName(myNameRef);
340 
341                 /* Report failure */
342                 if (myName == null) {
343                     throw new ThemisDataException("Unresolved fieldAccess: " + myNameRef);
344                 }
345             }
346         }
347     }
348 
349     /**
350      * Process method call.
351      *
352      * @param pCall the methodCall
353      * @throws OceanusException on error
354      */
355     private void processMethodCall(final ThemisExprMethodCall pCall) throws OceanusException {
356         final ThemisExpressionInstance myExpr = pCall.getScope();
357         if (myExpr instanceof ThemisExprName myNameExpr) {
358             /* Check to see if the field access is to an explicit class */
359             final String myNameRef = myNameExpr.toString();
360             final ThemisClassInstance myResolved = theFile.processPossibleReference(myNameRef);
361 
362             /* If we still failed to resolve */
363             if (myResolved == null) {
364                 /* Check for variable names */
365                 final ThemisInstance myName = theName.lookUpName(myNameRef);
366 
367                 /* Report failure */
368                 if (myName == null) {
369                     throw new ThemisDataException("Unresolved method call using name: " + myNameRef);
370                 }
371             }
372         } else if (myExpr instanceof ThemisExprFieldAccess myFieldExpr) {
373             processFieldAccess(myFieldExpr);
374         } else if (myExpr instanceof ThemisExprMethodCall myMethodCall) {
375             processMethodCall(myMethodCall);
376         }
377     }
378 
379     /**
380      * Process method Reference.
381      *
382      * @param pReference the reference
383      * @throws OceanusException on error
384      */
385     private void processMethodReference(final ThemisExprMethodRef pReference) throws OceanusException {
386         final ThemisExpressionInstance myExpr = pReference.getScope();
387         if (myExpr instanceof ThemisExprName myNameExpr) {
388             /* Check to see if the method reference is to an explicit class */
389             final String myNameRef = myNameExpr.toString();
390             final ThemisClassInstance myResolved = theFile.processPossibleReference(myNameRef);
391 
392             /* If we still failed to resolve */
393             if (myResolved == null) {
394                 /* Check for variable names */
395                 final ThemisInstance myName = theName.lookUpName(myNameRef);
396 
397                 /* Report failure */
398                 if (myName == null) {
399                     throw new ThemisDataException("Unresolved methodRef: " + myNameRef);
400                 }
401             }
402         }
403     }
404 
405     /**
406      * Obtain sorted list of children.
407      *
408      * @param pInstance the instanceNode
409      * @return the sorted list
410      */
411     private List<ThemisInstance> sortedChildren(final ThemisInstance pInstance) {
412         final List<ThemisInstance> myChildren = new ArrayList<>(pInstance.getChildren());
413         myChildren.sort(this::compareTo);
414         return myChildren;
415     }
416 
417     /**
418      * Comparator that pushes ClassInstances to bottom of list.
419      *
420      * @param pFirst  the first node
421      * @param pSecond the second node
422      * @return -1, 0, 1 according to order
423      */
424     private int compareTo(final ThemisInstance pFirst,
425                           final ThemisInstance pSecond) {
426         if (pFirst instanceof ThemisClassInstance) {
427             return pSecond instanceof ThemisClassInstance ? 0 : 1;
428         }
429         return pSecond instanceof ThemisClassInstance ? -1 : 0;
430     }
431 
432     @Override
433     public void close() {
434         theProject.close();
435     }
436 }