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.parser;
18  
19  import com.github.javaparser.JavaParser;
20  import com.github.javaparser.ParseResult;
21  import com.github.javaparser.ParserConfiguration;
22  import com.github.javaparser.Position;
23  import com.github.javaparser.Problem;
24  import com.github.javaparser.ast.CompilationUnit;
25  import com.github.javaparser.ast.Node;
26  import com.github.javaparser.ast.NodeList;
27  import com.github.javaparser.ast.PackageDeclaration;
28  import com.github.javaparser.ast.body.BodyDeclaration;
29  import com.github.javaparser.ast.expr.Expression;
30  import com.github.javaparser.ast.modules.ModuleDeclaration;
31  import com.github.javaparser.ast.stmt.Statement;
32  import com.github.javaparser.ast.type.Type;
33  import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
34  import io.github.tonywasher.joceanus.themis.exc.ThemisDataException;
35  import io.github.tonywasher.joceanus.themis.exc.ThemisIOException;
36  import io.github.tonywasher.joceanus.themis.xanalysis.parser.base.ThemisXAnalysisChar;
37  import io.github.tonywasher.joceanus.themis.xanalysis.parser.base.ThemisXAnalysisInstance;
38  import io.github.tonywasher.joceanus.themis.xanalysis.parser.base.ThemisXAnalysisInstance.ThemisXAnalysisClassInstance;
39  import io.github.tonywasher.joceanus.themis.xanalysis.parser.base.ThemisXAnalysisInstance.ThemisXAnalysisDeclarationInstance;
40  import io.github.tonywasher.joceanus.themis.xanalysis.parser.base.ThemisXAnalysisInstance.ThemisXAnalysisExpressionInstance;
41  import io.github.tonywasher.joceanus.themis.xanalysis.parser.base.ThemisXAnalysisInstance.ThemisXAnalysisModuleInstance;
42  import io.github.tonywasher.joceanus.themis.xanalysis.parser.base.ThemisXAnalysisInstance.ThemisXAnalysisNodeInstance;
43  import io.github.tonywasher.joceanus.themis.xanalysis.parser.base.ThemisXAnalysisInstance.ThemisXAnalysisStatementInstance;
44  import io.github.tonywasher.joceanus.themis.xanalysis.parser.base.ThemisXAnalysisInstance.ThemisXAnalysisTypeInstance;
45  import io.github.tonywasher.joceanus.themis.xanalysis.parser.base.ThemisXAnalysisModifierList;
46  import io.github.tonywasher.joceanus.themis.xanalysis.parser.base.ThemisXAnalysisParserDef;
47  import io.github.tonywasher.joceanus.themis.xanalysis.parser.decl.ThemisXAnalysisDeclParser;
48  import io.github.tonywasher.joceanus.themis.xanalysis.parser.expr.ThemisXAnalysisExprParser;
49  import io.github.tonywasher.joceanus.themis.xanalysis.parser.mod.ThemisXAnalysisModModule;
50  import io.github.tonywasher.joceanus.themis.xanalysis.parser.mod.ThemisXAnalysisModParser;
51  import io.github.tonywasher.joceanus.themis.xanalysis.parser.node.ThemisXAnalysisNodeCompilationUnit;
52  import io.github.tonywasher.joceanus.themis.xanalysis.parser.node.ThemisXAnalysisNodeParser;
53  import io.github.tonywasher.joceanus.themis.xanalysis.parser.proj.ThemisXAnalysisProject;
54  import io.github.tonywasher.joceanus.themis.xanalysis.parser.stmt.ThemisXAnalysisStmtParser;
55  import io.github.tonywasher.joceanus.themis.xanalysis.parser.type.ThemisXAnalysisTypeParser;
56  
57  import java.io.File;
58  import java.io.FileInputStream;
59  import java.io.IOException;
60  import java.io.InputStream;
61  import java.nio.charset.StandardCharsets;
62  import java.util.ArrayDeque;
63  import java.util.ArrayList;
64  import java.util.Deque;
65  import java.util.List;
66  
67  /**
68   * Code Parser.
69   */
70  public class ThemisXAnalysisParser
71          implements ThemisXAnalysisParserDef {
72      /**
73       * The underlying parser.
74       */
75      private final JavaParser theParser;
76  
77      /**
78       * The stack of the nodes that are being parsed.
79       */
80      private final Deque<ThemisXAnalysisInstance> theNodes;
81  
82      /**
83       * The current package being parsed.
84       */
85      private String thePackage;
86  
87      /**
88       * The File being parsed.
89       */
90      private File theCurrentFile;
91  
92      /**
93       * The List of classes in a file.
94       */
95      private final List<ThemisXAnalysisClassInstance> theClasses;
96  
97      /**
98       * The Class Stack.
99       */
100     private final Deque<String> theClassStack;
101 
102     /**
103      * The Current class index.
104      */
105     private int theClassIndex;
106 
107     /**
108      * The project.
109      */
110     private ThemisXAnalysisProject theProject;
111 
112     /**
113      * The Error.
114      */
115     private OceanusException theError;
116 
117     /**
118      * Constructor.
119      *
120      * @param pLocation the project location
121      */
122     public ThemisXAnalysisParser(final File pLocation) {
123         /* Initialise the parser */
124         theParser = new JavaParser();
125         theNodes = new ArrayDeque<>();
126         theClassStack = new ArrayDeque<>();
127         theClasses = new ArrayList<>();
128 
129         /* Protect against exceptions */
130         try {
131             /* Prepare the project */
132             theProject = new ThemisXAnalysisProject(pLocation);
133 
134             /* Configure the parser */
135             configureParser();
136 
137             /* Parse the javaCode */
138             theProject.parseJavaCode(this);
139 
140             /* Store any exception */
141         } catch (OceanusException e) {
142             theError = e;
143             theProject = null;
144         }
145     }
146 
147     /**
148      * Obtain the project.
149      *
150      * @return the project
151      */
152     public ThemisXAnalysisProject getProject() {
153         return theProject;
154     }
155 
156     /**
157      * Obtain the error.
158      *
159      * @return the error
160      */
161     public OceanusException getError() {
162         return theError;
163     }
164 
165     @Override
166     public List<ThemisXAnalysisClassInstance> getClasses() {
167         return theClasses;
168     }
169 
170     /**
171      * Configure the parser.
172      */
173     private void configureParser() {
174         /* Access the parser */
175         final ParserConfiguration myConfig = theParser.getParserConfiguration();
176         myConfig.setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_21);
177     }
178 
179     @Override
180     public void setCurrentPackage(final String pPackage) {
181         thePackage = pPackage;
182     }
183 
184     @Override
185     public void setCurrentFile(final File pFile) {
186         theCurrentFile = pFile;
187         theClasses.clear();
188         theClassStack.clear();
189         theClassIndex = 0;
190     }
191 
192     @Override
193     public ThemisXAnalysisInstance registerInstance(final ThemisXAnalysisInstance pInstance) {
194         /* Register with parent unless top-level node */
195         final ThemisXAnalysisInstance myParent = theNodes.peekLast();
196         if (myParent != null) {
197             myParent.registerChild(pInstance);
198         }
199 
200         /* Add to end of queue */
201         theNodes.addLast(pInstance);
202         return myParent;
203     }
204 
205     /**
206      * Deregister instance.
207      *
208      * @param pInstance the instance to deRegister
209      */
210     private void deRegisterInstance(final ThemisXAnalysisInstance pInstance) {
211         /* If the instance is non-null */
212         if (pInstance != null) {
213             /* If it matches the current node */
214             final ThemisXAnalysisInstance myCurrent = theNodes.peekLast();
215             if (pInstance.equals(myCurrent)) {
216                 /* Remove from queue */
217                 theNodes.removeLast();
218             }
219             if (pInstance instanceof ThemisXAnalysisClassInstance) {
220                 theClassStack.removeLast();
221             }
222         }
223     }
224 
225     @Override
226     public String registerClass(final ThemisXAnalysisClassInstance pClass) {
227         /* Add the class to the list */
228         theClasses.add(pClass);
229 
230         /* Determine the name of the class */
231         String myFullName = pClass.getFullName();
232         if (myFullName == null) {
233             final String myCurrentName = theClassStack.peekLast();
234             myFullName = myCurrentName
235                     + ThemisXAnalysisChar.PERIOD
236                     + ThemisXAnalysisChar.DOLLAR
237                     + ++theClassIndex;
238             if (pClass.isLocalDeclaration()) {
239                 myFullName += pClass.getName();
240             }
241         }
242 
243         /* Add to the class stack */
244         theClassStack.addLast(myFullName);
245 
246         /* Return the fullName */
247         return myFullName;
248     }
249 
250     @Override
251     public ThemisXAnalysisNodeCompilationUnit parseJavaFile() throws OceanusException {
252         /* Protect against exceptions */
253         try (InputStream myStream = new FileInputStream(theCurrentFile)) {
254             /* Parse the contents */
255             final ParseResult<CompilationUnit> myUnit = theParser.parse(myStream);
256             if (!myUnit.isSuccessful()) {
257                 final Problem myProblem = myUnit.getProblem(0);
258                 throw new ThemisDataException(myProblem.getVerboseMessage());
259             }
260             return (ThemisXAnalysisNodeCompilationUnit) parseNode(myUnit.getResult().orElse(null));
261 
262             /* Catch exceptions */
263         } catch (IOException e) {
264             /* Throw an exception */
265             throw new ThemisIOException("Failed to load file "
266                     + theCurrentFile.getAbsolutePath(), e);
267         }
268     }
269 
270     @Override
271     public ThemisXAnalysisModModule parseModuleInfo(final File pInfoFile) throws OceanusException {
272         /* Protect against exceptions */
273         setCurrentFile(pInfoFile);
274         try (InputStream myStream = new FileInputStream(theCurrentFile)) {
275             /* Parse the contents */
276             final String myText = new String(myStream.readAllBytes(), StandardCharsets.UTF_8);
277             final ParseResult<ModuleDeclaration> myDecl = theParser.parseModuleDeclaration(myText);
278             if (!myDecl.isSuccessful()) {
279                 final Problem myProblem = myDecl.getProblem(0);
280                 throw new ThemisDataException(myProblem.getVerboseMessage());
281             }
282             return (ThemisXAnalysisModModule) parseModule(myDecl.getResult().orElse(null));
283 
284             /* Catch exceptions */
285         } catch (IOException e) {
286             /* Throw an exception */
287             throw new ThemisIOException("Failed to load file "
288                     + theCurrentFile.getAbsolutePath(), e);
289         }
290     }
291 
292     @Override
293     public OceanusException buildException(final String pMessage,
294                                            final Node pNode) {
295         /* Determine location of error */
296         final Position myPos = pNode.getBegin().orElse(null);
297         final String myLocation = pNode.getClass().getCanonicalName()
298                 + (myPos == null ? "" : ThemisXAnalysisChar.PARENTHESIS_OPEN
299                 + myPos.line
300                 + ThemisXAnalysisChar.COLON
301                 + myPos.column
302                 + ThemisXAnalysisChar.PARENTHESIS_CLOSE);
303 
304         /* Build full error message */
305         final String myMsg = pMessage
306                 + ThemisXAnalysisChar.LF
307                 + myLocation
308                 + ThemisXAnalysisChar.LF
309                 + theCurrentFile.getAbsolutePath();
310 
311         /* Create exception */
312         return new ThemisDataException(myMsg);
313     }
314 
315     /**
316      * Check the package name.
317      *
318      * @param pPackage the package name
319      * @throws OceanusException on error
320      */
321     public void checkPackage(final PackageDeclaration pPackage) throws OceanusException {
322         /* Check that package matches */
323         if (!thePackage.equals(pPackage.getNameAsString())) {
324             throw buildException("Mismatch on package", pPackage);
325         }
326     }
327 
328     @Override
329     public ThemisXAnalysisDeclarationInstance parseDeclaration(final BodyDeclaration<?> pDecl) throws OceanusException {
330         final ThemisXAnalysisDeclarationInstance myInstance = ThemisXAnalysisDeclParser.parseDeclaration(this, pDecl);
331         deRegisterInstance(myInstance);
332         return myInstance;
333     }
334 
335     @Override
336     public ThemisXAnalysisNodeInstance parseNode(final Node pNode) throws OceanusException {
337         final ThemisXAnalysisNodeInstance myInstance = ThemisXAnalysisNodeParser.parseNode(this, pNode);
338         deRegisterInstance(myInstance);
339         return myInstance;
340     }
341 
342     @Override
343     public ThemisXAnalysisTypeInstance parseType(final Type pType) throws OceanusException {
344         final ThemisXAnalysisTypeInstance myInstance = ThemisXAnalysisTypeParser.parseType(this, pType);
345         deRegisterInstance(myInstance);
346         return myInstance;
347     }
348 
349     @Override
350     public ThemisXAnalysisStatementInstance parseStatement(final Statement pStatement) throws OceanusException {
351         final ThemisXAnalysisStatementInstance myInstance = ThemisXAnalysisStmtParser.parseStatement(this, pStatement);
352         deRegisterInstance(myInstance);
353         return myInstance;
354     }
355 
356     @Override
357     public ThemisXAnalysisExpressionInstance parseExpression(final Expression pExpr) throws OceanusException {
358         final ThemisXAnalysisExpressionInstance myInstance = ThemisXAnalysisExprParser.parseExpression(this, pExpr);
359         deRegisterInstance(myInstance);
360         return myInstance;
361     }
362 
363     @Override
364     public ThemisXAnalysisModuleInstance parseModule(final Node pMod) throws OceanusException {
365         final ThemisXAnalysisModuleInstance myInstance = ThemisXAnalysisModParser.parseModule(this, pMod);
366         deRegisterInstance(myInstance);
367         return myInstance;
368     }
369 
370     @Override
371     public ThemisXAnalysisModifierList parseModifierList(final NodeList<? extends Node> pNodeList) throws OceanusException {
372         return ThemisXAnalysisNodeParser.parseModifierList(this, pNodeList);
373     }
374 }