View Javadoc
1   /*
2    * Themis: Java Project Framework
3    * Copyright 2012-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  package io.github.tonywasher.joceanus.themis.lethe.analysis;
18  
19  import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
20  import io.github.tonywasher.joceanus.oceanus.logger.OceanusLogManager;
21  import io.github.tonywasher.joceanus.oceanus.logger.OceanusLogger;
22  import io.github.tonywasher.joceanus.themis.exc.ThemisDataException;
23  import io.github.tonywasher.joceanus.themis.lethe.analysis.ThemisAnalysisFile.ThemisAnalysisObject;
24  import io.github.tonywasher.joceanus.themis.lethe.analysis.ThemisAnalysisGeneric.ThemisAnalysisGenericVar;
25  import io.github.tonywasher.joceanus.themis.lethe.analysis.ThemisAnalysisImports.ThemisAnalysisImport;
26  
27  import java.util.ArrayList;
28  import java.util.HashMap;
29  import java.util.LinkedHashMap;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Map.Entry;
33  
34  /**
35   * The data map manager.
36   */
37  public class ThemisAnalysisDataMap {
38      /**
39       * Marker interface for dataType.
40       */
41      interface ThemisAnalysisDataType {
42      }
43  
44      /**
45       * Marker interface for intermediate.
46       */
47      interface ThemisAnalysisIntermediate extends ThemisAnalysisDataType {
48      }
49  
50      /**
51       * The logger.
52       */
53      private static final OceanusLogger LOGGER = OceanusLogManager.getLogger(ThemisAnalysisDataMap.class);
54  
55      /**
56       * The base dataTypes.
57       */
58      private static final Map<String, ThemisAnalysisDataType> BASETYPES = createDataTypeMap();
59  
60      /**
61       * The hidden dataTypes.
62       */
63      private static final Map<String, String> HIDDENTYPES = createHiddenTypeMap();
64  
65      /**
66       * The local dataTypes.
67       */
68      private final Map<String, ThemisAnalysisDataType> theLocalTypes;
69  
70      /**
71       * The map of all classes.
72       */
73      private final Map<String, ThemisAnalysisObject> theClassMap;
74  
75      /**
76       * The map of all classes.
77       */
78      private final Map<String, ThemisAnalysisObject> theShortClassMap;
79  
80      /**
81       * The map of local/anonymous classes.
82       */
83      private final Map<String, Integer> theLocalIdMap;
84  
85      /**
86       * The file dataTypes.
87       */
88      private Map<String, ThemisAnalysisDataType> theFileTypes;
89  
90      /**
91       * The list of file classes.
92       */
93      private List<ThemisAnalysisObject> theFileClasses;
94  
95      /**
96       * The list of all references.
97       */
98      private List<ThemisAnalysisReference> theReferences;
99  
100     /**
101      * The parent.
102      */
103     private ThemisAnalysisDataMap theParent;
104 
105     /**
106      * Base constructor.
107      */
108     ThemisAnalysisDataMap() {
109         theParent = null;
110         theClassMap = new LinkedHashMap<>();
111         theShortClassMap = new LinkedHashMap<>();
112         theLocalTypes = new HashMap<>();
113         theLocalIdMap = new HashMap<>();
114     }
115 
116     /**
117      * Nested constructor.
118      *
119      * @param pParent the parent dataMap
120      */
121     ThemisAnalysisDataMap(final ThemisAnalysisDataMap pParent) {
122         theParent = pParent;
123         theClassMap = pParent.theClassMap;
124         theShortClassMap = pParent.theShortClassMap;
125         theLocalTypes = new HashMap<>();
126         theFileTypes = pParent.theFileTypes;
127         theFileClasses = pParent.theFileClasses;
128         theReferences = pParent.theReferences;
129         theLocalIdMap = new HashMap<>();
130     }
131 
132     /**
133      * lookUp thedataType.
134      *
135      * @param pToken  the token.
136      * @param pMethod is this a method call/def?
137      * @return the dataType (or null)
138      */
139     ThemisAnalysisDataType lookUpDataType(final String pToken,
140                                           final boolean pMethod) {
141         /* Look up standard data type */
142         ThemisAnalysisDataType myDataType = lookUpTheDataType(pToken);
143 
144         /* If it is not found and this is not a method call */
145         if (myDataType == null && !pMethod) {
146             /* Look for a root dataType */
147             final int myIndex = pToken.indexOf(ThemisAnalysisChar.PERIOD);
148             if (myIndex != -1) {
149                 /* Check for child of imported dataType */
150                 final String myStart = pToken.substring(0, myIndex);
151                 final String myEnd = pToken.substring(myIndex + 1);
152                 final ThemisAnalysisDataType myParent = lookUpTheDataType(myStart);
153 
154                 /* If we have found it */
155                 if (myParent != null) {
156                     myDataType = new ThemisAnalysisDataTypeChild(myParent, myEnd);
157                     theFileTypes.put(pToken, myDataType);
158                 }
159             }
160         }
161 
162         /* Return the dataType */
163         return myDataType;
164     }
165 
166     /**
167      * lookUp dataType.
168      *
169      * @param pToken the token.
170      * @return the dataType (or null)
171      */
172     ThemisAnalysisDataType lookUpTheDataType(final String pToken) {
173         /* Look up in local types */
174         final ThemisAnalysisDataType myDataType = theLocalTypes.get(pToken);
175         if (myDataType != null) {
176             return myDataType;
177         }
178 
179         /* Pass call on */
180         return theParent == null
181                 ? BASETYPES.get(pToken)
182                 : theParent.lookUpTheDataType(pToken);
183     }
184 
185     /**
186      * Record Object.
187      *
188      * @param pObject the object.
189      * @throws OceanusException on error
190      */
191     void declareObject(final ThemisAnalysisObject pObject) throws OceanusException {
192         /* Access properties */
193         final ThemisAnalysisProperties myProps = pObject.getProperties();
194         final String myShortName = pObject.getShortName();
195 
196         /* Only register class globally if it is non-private */
197         if (!myProps.hasModifier(ThemisAnalysisModifier.PRIVATE)) {
198             /* Check that the shortName is unique */
199             if (theShortClassMap.get(myShortName) != null) {
200                 throw new ThemisDataException("Duplicate class shortName: " + myShortName);
201             }
202 
203             /* Register the object */
204             theShortClassMap.put(myShortName, pObject);
205             theClassMap.put(pObject.getFullName(), pObject);
206         }
207 
208         /* Store locally */
209         theFileTypes.put(myShortName, pObject);
210         theFileClasses.add(pObject);
211     }
212 
213     /**
214      * Set up file resources.
215      */
216     void setUpFileResources() {
217         /* Local types are file wide */
218         theFileTypes = theLocalTypes;
219 
220         /* Allocate the fileClasses */
221         theFileClasses = new ArrayList<>();
222 
223         /* Allocate the references list */
224         theReferences = new ArrayList<>();
225     }
226 
227     /**
228      * Set parent.
229      *
230      * @param pParent the parent dataMap.
231      */
232     void setParent(final ThemisAnalysisDataMap pParent) {
233         theParent = pParent;
234     }
235 
236     /**
237      * declare file.
238      *
239      * @param pFile the file
240      */
241     void declareFile(final ThemisAnalysisFile pFile) {
242         theLocalTypes.put(pFile.getName(), pFile);
243     }
244 
245     /**
246      * declare import.
247      *
248      * @param pImport the import
249      */
250     void declareImport(final ThemisAnalysisImport pImport) {
251         theLocalTypes.put(pImport.getSimpleName(), pImport);
252     }
253 
254     /**
255      * declare generic variable.
256      *
257      * @param pVar the generic variable
258      */
259     void declareGenericVar(final ThemisAnalysisGenericVar pVar) {
260         theLocalTypes.put(pVar.getName(), pVar);
261     }
262 
263     /**
264      * declare unknown type.
265      *
266      * @param pName the name
267      * @return the new type
268      */
269     ThemisAnalysisDataType declareUnknown(final String pName) {
270         final ThemisAnalysisDataType myType = new ThemisAnalysisDataTypeUnknown(pName);
271         theFileTypes.put(pName, myType);
272         return myType;
273     }
274 
275     /**
276      * declare reference.
277      *
278      * @param pRef the reference
279      */
280     void declareReference(final ThemisAnalysisReference pRef) {
281         theReferences.add(pRef);
282     }
283 
284     /**
285      * Consolidate the class map.
286      *
287      * @throws OceanusException on error
288      */
289     void consolidateMap() throws OceanusException {
290         /* Update intermediate references */
291         updateIntermediates();
292 
293         /* resolve the immediate ancestors */
294         resolveImmediateAncestors();
295     }
296 
297     /**
298      * Update intermediate references from classMap.
299      */
300     private void updateIntermediates() {
301         /* Loop through the localDataTypes */
302         for (Entry<String, ThemisAnalysisDataType> myEntry : theLocalTypes.entrySet()) {
303             /* Access the value */
304             final ThemisAnalysisDataType myType = myEntry.getValue();
305 
306             /* If this is an intermediate */
307             if (myType instanceof ThemisAnalysisIntermediate myIntermediate) {
308                 /* Look up actual value */
309                 final ThemisAnalysisDataType myActual = lookUpActualDataType(myIntermediate);
310                 if (myActual != null) {
311                     myEntry.setValue(myActual);
312                 }
313             }
314         }
315 
316         /* Loop through the References */
317         for (ThemisAnalysisReference myRef : theReferences) {
318             /* Access the dataType */
319             final ThemisAnalysisDataType myType = myRef.getDataType();
320 
321             /* If this is an intermediate */
322             if (myType instanceof ThemisAnalysisIntermediate myIntermediate) {
323                 /* Look up actual value */
324                 final ThemisAnalysisDataType myActual = lookUpActualDataType(myIntermediate);
325                 if (myActual != null) {
326                     myRef.updateDataType(myActual);
327                 }
328             }
329         }
330     }
331 
332     /**
333      * Look up actual dataType.
334      *
335      * @param pIntermediate the intermediate dataType.
336      * @return the actual dataType (or null)
337      */
338     ThemisAnalysisObject lookUpActualDataType(final ThemisAnalysisIntermediate pIntermediate) {
339         /* If this is an import */
340         if (pIntermediate instanceof ThemisAnalysisImport myImport) {
341             /* Replace it with actual object if known */
342             return theClassMap.get(myImport.getFullName());
343         }
344 
345         /* If this is a file */
346         if (pIntermediate instanceof ThemisAnalysisFile myFile) {
347             /* Replace it with actual object if known */
348             final String myFullName = myFile.getPackageName() + ThemisAnalysisChar.PERIOD + myFile.getName();
349             return theClassMap.get(myFullName);
350         }
351 
352         /* No change */
353         return null;
354     }
355 
356     /**
357      * Resolve immediate ancestors.
358      *
359      * @throws OceanusException on error
360      */
361     private void resolveImmediateAncestors() throws OceanusException {
362         /* Loop through the fileClasses */
363         for (ThemisAnalysisObject myClass : theFileClasses) {
364             /* Loop through the ancestors of each class */
365             for (ThemisAnalysisReference myRef : myClass.getAncestors()) {
366                 /* Resolve the ancestor */
367                 resolveAncestor(myRef);
368             }
369         }
370     }
371 
372     /**
373      * Resolve ancestors.
374      *
375      * @param pAncestor the ancestor
376      * @throws OceanusException on error
377      */
378     private void resolveAncestor(final ThemisAnalysisReference pAncestor) throws OceanusException {
379         /* Only worry about unknown ancestors */
380         final ThemisAnalysisDataType myDataType = pAncestor.getDataType();
381         if (!(myDataType instanceof ThemisAnalysisDataTypeUnknown)) {
382             return;
383         }
384 
385         /* Look up the shortName */
386         final String myName = ((ThemisAnalysisDataTypeUnknown) myDataType).theName;
387         final ThemisAnalysisObject myActual = theShortClassMap.get(myName);
388         if (myActual == null) {
389             throw new ThemisDataException("Unknown ancestor: " + myName);
390         }
391 
392         /* Update the reference */
393         pAncestor.updateDataType(myActual);
394     }
395 
396     /**
397      * Resolve references.
398      */
399     void resolveReferences() {
400         /* Process implicit imports */
401         processImplicit();
402 
403         /* Loop through the References */
404         for (ThemisAnalysisReference myRef : theReferences) {
405             /* Access the dataType */
406             final ThemisAnalysisDataType myType = myRef.getDataType();
407 
408             /* If this is an unknown */
409             if (myType instanceof ThemisAnalysisDataTypeUnknown) {
410                 /* Check for implicit import */
411                 final ThemisAnalysisDataType myActual = theLocalTypes.get(myType.toString());
412                 if (!(myActual instanceof ThemisAnalysisDataTypeUnknown)) {
413                     myRef.updateDataType(myActual);
414                 }
415             }
416         }
417     }
418 
419     /**
420      * Process implicit imports.
421      */
422     private void processImplicit() {
423         /* Loop through the fileClasses */
424         for (ThemisAnalysisObject myClass : theFileClasses) {
425             /* Process the ancestors */
426             processAncestors(myClass);
427         }
428     }
429 
430     /**
431      * Process implicit imports.
432      *
433      * @param pClass the class
434      */
435     private void processAncestors(final ThemisAnalysisObject pClass) {
436         /* Loop through the ancestors */
437         for (ThemisAnalysisReference myRef : pClass.getAncestors()) {
438             /* Process the ancestor */
439             final ThemisAnalysisDataType myDataType = myRef.getDataType();
440             if (myDataType instanceof ThemisAnalysisObject myObject) {
441                 processAncestor(myObject);
442                 processAncestors(myObject);
443             }
444         }
445     }
446 
447     /**
448      * Process ancestor.
449      *
450      * @param pAncestor the ancestor
451      */
452     private void processAncestor(final ThemisAnalysisObject pAncestor) {
453         /* Loop through the fileClasses */
454         for (ThemisAnalysisObject myClass : theClassMap.values()) {
455             /* If this is a direct child of the ancestor */
456             final String myName = pAncestor.getFullName() + ThemisAnalysisChar.PERIOD + myClass.getShortName();
457             if (myName.equals(myClass.getFullName())) {
458                 /* Add it to the local types */
459                 theLocalTypes.put(myClass.getShortName(), myClass);
460             }
461         }
462     }
463 
464     /**
465      * Report unknown references.
466      */
467     void reportUnknown() {
468         /* Loop through the localDataTypes */
469         for (Entry<String, ThemisAnalysisDataType> myEntry : theLocalTypes.entrySet()) {
470             /* Access the value */
471             final ThemisAnalysisDataType myType = myEntry.getValue();
472 
473             /* If this is an unknown value */
474             if (myType instanceof ThemisAnalysisDataTypeUnknown myUnknown) {
475                 /* Process the unknown reference */
476                 processUnknown(myEntry, myUnknown);
477             }
478         }
479     }
480 
481     /**
482      * Process unknown reference.
483      *
484      * @param pEntry   the entry
485      * @param pUnknown the unknown reference
486      */
487     private void processUnknown(final Entry<String, ThemisAnalysisDataType> pEntry,
488                                 final ThemisAnalysisDataTypeUnknown pUnknown) {
489         /* If this is a hidden child */
490         final String myName = pEntry.getKey();
491         if (HIDDENTYPES.containsKey(myName)) {
492             /* Look up the parent */
493             final ThemisAnalysisDataType myParent = theLocalTypes.get(HIDDENTYPES.get(myName));
494             if (myParent instanceof ThemisAnalysisImport myImport) {
495                 /* Register a fake import and return */
496                 final String myFullName = myImport.getFullName() + ThemisAnalysisChar.PERIOD + myName;
497                 final ThemisAnalysisImport myChild = new ThemisAnalysisImport(myFullName);
498                 pEntry.setValue(myChild);
499                 return;
500             }
501         }
502 
503         /* Report it if we have not rectified the problem */
504         LOGGER.info("Unknown: " + pUnknown.toString());
505     }
506 
507     /**
508      * Create the dataTypeMap.
509      *
510      * @return the new map
511      */
512     private static Map<String, ThemisAnalysisDataType> createDataTypeMap() {
513         /* create the map */
514         final Map<String, ThemisAnalysisDataType> myMap = new HashMap<>();
515 
516         /* Add the primitives */
517         for (ThemisAnalysisPrimitive myPrimitive : ThemisAnalysisPrimitive.values()) {
518             myMap.put(myPrimitive.toString(), myPrimitive);
519             if (myPrimitive.getBoxed() != null) {
520                 myMap.put(myPrimitive.getBoxed(), myPrimitive);
521             }
522         }
523 
524         /* Add the java.lang classes */
525         for (ThemisAnalysisJavaLang myClass : ThemisAnalysisJavaLang.values()) {
526             myMap.put(myClass.toString(), myClass);
527         }
528 
529         /* return the map */
530         return myMap;
531     }
532 
533     /**
534      * Create hidden dataType map.
535      *
536      * @return the map
537      */
538     private static Map<String, String> createHiddenTypeMap() {
539         final Map<String, String> myMap = new HashMap<>();
540         myMap.put("StateChangeNotification", "Preloader");
541         myMap.put("SortKey", "RowSorter");
542         myMap.put("Entry", "RowFilter");
543         return myMap;
544     }
545 
546     /**
547      * Obtain local id.
548      *
549      * @param pName the name
550      * @return the localId
551      */
552     public int getLocalId(final String pName) {
553         final int myId = 1 + theLocalIdMap.computeIfAbsent(pName, s -> 0);
554         theLocalIdMap.put(pName, myId);
555         return myId;
556     }
557 
558     /**
559      * DataType Unknown.
560      */
561     public static class ThemisAnalysisDataTypeUnknown
562             implements ThemisAnalysisDataType {
563         /**
564          * The Name.
565          */
566         private final String theName;
567 
568         /**
569          * Constructor.
570          *
571          * @param pName the name
572          */
573         ThemisAnalysisDataTypeUnknown(final String pName) {
574             theName = pName;
575         }
576 
577         @Override
578         public String toString() {
579             return theName;
580         }
581     }
582 
583     /**
584      * DataType Child.
585      */
586     public static class ThemisAnalysisDataTypeChild
587             implements ThemisAnalysisDataType {
588         /**
589          * The Name.
590          */
591         private final ThemisAnalysisDataType theParent;
592 
593         /**
594          * The Child.
595          */
596         private final String theChild;
597 
598         /**
599          * Constructor.
600          *
601          * @param pParent the parent
602          * @param pChild  the child
603          */
604         ThemisAnalysisDataTypeChild(final ThemisAnalysisDataType pParent,
605                                     final String pChild) {
606             theParent = pParent;
607             theChild = pChild;
608         }
609 
610         @Override
611         public String toString() {
612             return theParent.toString() + ThemisAnalysisChar.PERIOD + theChild;
613         }
614     }
615 }