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