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.xanalysis.solver.reflect;
18  
19  import com.github.javaparser.ast.body.BodyDeclaration;
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.xanalysis.parser.ThemisXAnalysisParser;
23  import io.github.tonywasher.joceanus.themis.xanalysis.parser.base.ThemisXAnalysisChar;
24  import io.github.tonywasher.joceanus.themis.xanalysis.parser.base.ThemisXAnalysisInstance.ThemisXAnalysisClassInstance;
25  import io.github.tonywasher.joceanus.themis.xanalysis.parser.base.ThemisXAnalysisInstance.ThemisXAnalysisTypeInstance;
26  import io.github.tonywasher.joceanus.themis.xanalysis.parser.proj.ThemisXAnalysisMaven.ThemisXAnalysisMavenId;
27  import io.github.tonywasher.joceanus.themis.xanalysis.parser.proj.ThemisXAnalysisProject;
28  import io.github.tonywasher.joceanus.themis.xanalysis.parser.type.ThemisXAnalysisTypeClassInterface;
29  
30  import java.io.File;
31  import java.io.IOException;
32  import java.net.MalformedURLException;
33  import java.net.URI;
34  import java.net.URISyntaxException;
35  import java.net.URL;
36  import java.net.URLClassLoader;
37  import java.util.ArrayList;
38  import java.util.List;
39  import java.util.Map;
40  
41  /**
42   * Solve external class references via Jars and reflection.
43   */
44  public class ThemisXAnalysisReflectJar
45          implements AutoCloseable {
46      /**
47       * The Project parser.
48       */
49      private final ThemisXAnalysisParser theProjectParser;
50  
51      /**
52       * The JarClass Loader.
53       */
54      private final URLClassLoader theClassLoader;
55  
56      /**
57       * The External Classes map.
58       */
59      private Map<String, ThemisXAnalysisReflectExternal> theExternalClasses;
60  
61      /**
62       * Constructor.
63       *
64       * @param pParser the project parser.
65       * @throws OceanusException on error
66       */
67      public ThemisXAnalysisReflectJar(final ThemisXAnalysisParser pParser) throws OceanusException {
68          /* Create URL list and create URL Loader */
69          theProjectParser = pParser;
70          final URL[] myUrls = determineURLList(pParser.getProject());
71          theClassLoader = URLClassLoader.newInstance(myUrls);
72      }
73  
74      /**
75       * Process external class list.
76       *
77       * @param pExternalClasses the external classes.
78       * @throws OceanusException on error
79       */
80      public void processExternalClasses(final Map<String, ThemisXAnalysisReflectExternal> pExternalClasses) throws OceanusException {
81          /* Extract the values as a separate list */
82          theExternalClasses = pExternalClasses;
83          final List<ThemisXAnalysisReflectExternal> myExternals = new ArrayList<>(pExternalClasses.values());
84  
85          /* Loop through the list */
86          for (ThemisXAnalysisReflectExternal myClass : myExternals) {
87              /* Load the external class */
88              final Class<?> myLoaded = loadClass(myClass.getFullName());
89  
90              /* Create a resolved class based on the loaded class */
91              final BodyDeclaration<?> myResolved = buildClass(myLoaded);
92              final ThemisXAnalysisClassInstance myInstance = (ThemisXAnalysisClassInstance) theProjectParser.parseDeclaration(myResolved);
93              myClass.setClassInstance(myInstance);
94  
95              /* Process ancestors */
96              processAncestors(myInstance);
97          }
98      }
99  
100     /**
101      * Process external class list.
102      *
103      * @param pExternal the external classes.
104      * @throws OceanusException on error
105      */
106     private void processAncestors(final ThemisXAnalysisClassInstance pExternal) throws OceanusException {
107         /* Process all the extended classes */
108         for (ThemisXAnalysisTypeInstance myAncestor : pExternal.getExtends()) {
109             /* Process the ancestor */
110             processAncestor((ThemisXAnalysisTypeClassInterface) myAncestor);
111         }
112 
113         /* Process all the implemented classes */
114         for (ThemisXAnalysisTypeInstance myAncestor : pExternal.getImplements()) {
115             /* Process the ancestor */
116             processAncestor((ThemisXAnalysisTypeClassInterface) myAncestor);
117         }
118     }
119 
120     /**
121      * Process an ancestor.
122      *
123      * @param pAncestor the ancestor.
124      * @throws OceanusException on error
125      */
126     private void processAncestor(final ThemisXAnalysisTypeClassInterface pAncestor) throws OceanusException {
127         /* Access the name of the class and convert to period format */
128         final String myFullName = pAncestor.getFullName().replace(ThemisXAnalysisChar.DOLLAR, ThemisXAnalysisChar.PERIOD);
129 
130         /* See whether we have seen this class before */
131         ThemisXAnalysisReflectExternal myExternal = theExternalClasses.get(myFullName);
132         if (myExternal == null) {
133             /* Load the external class */
134             final Class<?> myLoaded = loadClass(myFullName);
135 
136             /* Create a resolved class based on the loaded class */
137             final BodyDeclaration<?> myResolved = buildClass(myLoaded);
138             final ThemisXAnalysisClassInstance myInstance = (ThemisXAnalysisClassInstance) theProjectParser.parseDeclaration(myResolved);
139             myExternal = new ThemisXAnalysisReflectExternal(myInstance);
140             theExternalClasses.put(myFullName, myExternal);
141 
142             /* Process ancestors */
143             processAncestors(myInstance);
144 
145             /* else known class */
146         } else {
147             /* Add link */
148             pAncestor.setClassInstance(myExternal);
149         }
150     }
151 
152     /**
153      * determine the URL List.
154      *
155      * @param pProject the project
156      * @return the URL List
157      * @throws OceanusException on error
158      */
159     private URL[] determineURLList(final ThemisXAnalysisProject pProject) throws OceanusException {
160         /* Create list of URLs for the dependencies */
161         final List<URL> myList = new ArrayList<>();
162         for (ThemisXAnalysisMavenId myId : pProject.getDependencies()) {
163             /* Protect against exceptions */
164             try {
165                 final File myJar = myId.getMavenJarPath();
166                 final URL myUrl = (new URI("jar:file:/" + myJar + "!/")).toURL();
167                 myList.add(myUrl);
168 
169                 /* Handle exceptions */
170             } catch (URISyntaxException
171                      | MalformedURLException e) {
172                 throw new ThemisDataException("Failed to build URL", e);
173             }
174         }
175 
176         /* Convert list to array */
177         return myList.toArray(new URL[0]);
178     }
179 
180     /**
181      * Load a class.
182      *
183      * @param pClassName the class name.
184      * @return the loaded class
185      * @throws OceanusException on error
186      */
187     private Class<?> loadClass(final String pClassName) throws OceanusException {
188         /* Protect against exceptions */
189         try {
190             return theClassLoader.loadClass(pClassName);
191 
192             /* If we failed to find the class */
193         } catch (ClassNotFoundException e) {
194             /* Try again with the canonical name converted to a subClass */
195             final String mySubClass = trySubClass(pClassName);
196             if (mySubClass != null) {
197                 return loadClass(mySubClass);
198             }
199 
200             /* Failed to find the class */
201             throw new ThemisDataException("Failed to find class " + pClassName, e);
202         }
203     }
204 
205     /**
206      * Change class name to make last class subClass.
207      *
208      * @param pClassName the class name
209      * @return the subClass name or null
210      */
211     private static String trySubClass(final String pClassName) {
212         /* Swap last period for dollar */
213         final int myLastIndex = pClassName.lastIndexOf(ThemisXAnalysisChar.PERIOD);
214         return myLastIndex != -1
215                 ? pClassName.substring(0, myLastIndex) + ThemisXAnalysisChar.DOLLAR + pClassName.substring(myLastIndex + 1)
216                 : null;
217     }
218 
219     /**
220      * build class.
221      *
222      * @param pSource the source class
223      * @return the parsed class
224      * @throws OceanusException on error
225      */
226     private BodyDeclaration<?> buildClass(final Class<?> pSource) throws OceanusException {
227         /* Build the relevant class type */
228         if (pSource.isAnnotation()) {
229             return new ThemisXAnalysisReflectAnnotation(pSource);
230         } else if (pSource.isEnum()) {
231             return new ThemisXAnalysisReflectEnum(pSource);
232         } else if (pSource.isRecord()) {
233             return new ThemisXAnalysisReflectRecord(pSource);
234         } else {
235             return new ThemisXAnalysisReflectClass(pSource);
236         }
237     }
238 
239     @Override
240     public void close() {
241         try {
242             if (theClassLoader != null) {
243                 theClassLoader.close();
244             }
245         } catch (IOException e) {
246             /* Do nothing */
247         }
248     }
249 }