ThemisXAnalysisParser.java
/*
* Themis: Java Project Framework
* Copyright 2012-2026. Tony Washer
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package io.github.tonywasher.joceanus.themis.xanalysis.parser;
import com.github.javaparser.JavaParser;
import com.github.javaparser.ParseResult;
import com.github.javaparser.ParserConfiguration;
import com.github.javaparser.Position;
import com.github.javaparser.Problem;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.PackageDeclaration;
import com.github.javaparser.ast.body.BodyDeclaration;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.modules.ModuleDeclaration;
import com.github.javaparser.ast.stmt.Statement;
import com.github.javaparser.ast.type.Type;
import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
import io.github.tonywasher.joceanus.themis.exc.ThemisDataException;
import io.github.tonywasher.joceanus.themis.exc.ThemisIOException;
import io.github.tonywasher.joceanus.themis.xanalysis.parser.base.ThemisXAnalysisChar;
import io.github.tonywasher.joceanus.themis.xanalysis.parser.base.ThemisXAnalysisInstance;
import io.github.tonywasher.joceanus.themis.xanalysis.parser.base.ThemisXAnalysisInstance.ThemisXAnalysisClassInstance;
import io.github.tonywasher.joceanus.themis.xanalysis.parser.base.ThemisXAnalysisInstance.ThemisXAnalysisDeclarationInstance;
import io.github.tonywasher.joceanus.themis.xanalysis.parser.base.ThemisXAnalysisInstance.ThemisXAnalysisExpressionInstance;
import io.github.tonywasher.joceanus.themis.xanalysis.parser.base.ThemisXAnalysisInstance.ThemisXAnalysisModuleInstance;
import io.github.tonywasher.joceanus.themis.xanalysis.parser.base.ThemisXAnalysisInstance.ThemisXAnalysisNodeInstance;
import io.github.tonywasher.joceanus.themis.xanalysis.parser.base.ThemisXAnalysisInstance.ThemisXAnalysisStatementInstance;
import io.github.tonywasher.joceanus.themis.xanalysis.parser.base.ThemisXAnalysisInstance.ThemisXAnalysisTypeInstance;
import io.github.tonywasher.joceanus.themis.xanalysis.parser.base.ThemisXAnalysisModifierList;
import io.github.tonywasher.joceanus.themis.xanalysis.parser.base.ThemisXAnalysisParserDef;
import io.github.tonywasher.joceanus.themis.xanalysis.parser.decl.ThemisXAnalysisDeclParser;
import io.github.tonywasher.joceanus.themis.xanalysis.parser.expr.ThemisXAnalysisExprParser;
import io.github.tonywasher.joceanus.themis.xanalysis.parser.mod.ThemisXAnalysisModModule;
import io.github.tonywasher.joceanus.themis.xanalysis.parser.mod.ThemisXAnalysisModParser;
import io.github.tonywasher.joceanus.themis.xanalysis.parser.node.ThemisXAnalysisNodeCompilationUnit;
import io.github.tonywasher.joceanus.themis.xanalysis.parser.node.ThemisXAnalysisNodeParser;
import io.github.tonywasher.joceanus.themis.xanalysis.parser.proj.ThemisXAnalysisProject;
import io.github.tonywasher.joceanus.themis.xanalysis.parser.stmt.ThemisXAnalysisStmtParser;
import io.github.tonywasher.joceanus.themis.xanalysis.parser.type.ThemisXAnalysisTypeParser;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
/**
* Code Parser.
*/
public class ThemisXAnalysisParser
implements ThemisXAnalysisParserDef {
/**
* The underlying parser.
*/
private final JavaParser theParser;
/**
* The stack of the nodes that are being parsed.
*/
private final Deque<ThemisXAnalysisInstance> theNodes;
/**
* The current package being parsed.
*/
private String thePackage;
/**
* The File being parsed.
*/
private File theCurrentFile;
/**
* The List of classes in a file.
*/
private final List<ThemisXAnalysisClassInstance> theClasses;
/**
* The Class Stack.
*/
private final Deque<String> theClassStack;
/**
* The Current class index.
*/
private int theClassIndex;
/**
* The project.
*/
private ThemisXAnalysisProject theProject;
/**
* The Error.
*/
private OceanusException theError;
/**
* Constructor.
*
* @param pLocation the project location
*/
public ThemisXAnalysisParser(final File pLocation) {
/* Initialise the parser */
theParser = new JavaParser();
theNodes = new ArrayDeque<>();
theClassStack = new ArrayDeque<>();
theClasses = new ArrayList<>();
/* Protect against exceptions */
try {
/* Prepare the project */
theProject = new ThemisXAnalysisProject(pLocation);
/* Configure the parser */
configureParser();
/* Parse the javaCode */
theProject.parseJavaCode(this);
/* Store any exception */
} catch (OceanusException e) {
theError = e;
theProject = null;
}
}
/**
* Obtain the project.
*
* @return the project
*/
public ThemisXAnalysisProject getProject() {
return theProject;
}
/**
* Obtain the error.
*
* @return the error
*/
public OceanusException getError() {
return theError;
}
@Override
public List<ThemisXAnalysisClassInstance> getClasses() {
return theClasses;
}
/**
* Configure the parser.
*/
private void configureParser() {
/* Access the parser */
final ParserConfiguration myConfig = theParser.getParserConfiguration();
myConfig.setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_21);
}
@Override
public void setCurrentPackage(final String pPackage) {
thePackage = pPackage;
}
@Override
public void setCurrentFile(final File pFile) {
theCurrentFile = pFile;
theClasses.clear();
theClassStack.clear();
theClassIndex = 0;
}
@Override
public ThemisXAnalysisInstance registerInstance(final ThemisXAnalysisInstance pInstance) {
/* Register with parent unless top-level node */
final ThemisXAnalysisInstance myParent = theNodes.peekLast();
if (myParent != null) {
myParent.registerChild(pInstance);
}
/* Add to end of queue */
theNodes.addLast(pInstance);
return myParent;
}
/**
* Deregister instance.
*
* @param pInstance the instance to deRegister
*/
private void deRegisterInstance(final ThemisXAnalysisInstance pInstance) {
/* If the instance is non-null */
if (pInstance != null) {
/* If it matches the current node */
final ThemisXAnalysisInstance myCurrent = theNodes.peekLast();
if (pInstance.equals(myCurrent)) {
/* Remove from queue */
theNodes.removeLast();
}
if (pInstance instanceof ThemisXAnalysisClassInstance) {
theClassStack.removeLast();
}
}
}
@Override
public String registerClass(final ThemisXAnalysisClassInstance pClass) {
/* Add the class to the list */
theClasses.add(pClass);
/* Determine the name of the class */
String myFullName = pClass.getFullName();
if (myFullName == null) {
final String myCurrentName = theClassStack.peekLast();
myFullName = myCurrentName
+ ThemisXAnalysisChar.PERIOD
+ ThemisXAnalysisChar.DOLLAR
+ ++theClassIndex;
if (pClass.isLocalDeclaration()) {
myFullName += pClass.getName();
}
}
/* Add to the class stack */
theClassStack.addLast(myFullName);
/* Return the fullName */
return myFullName;
}
@Override
public ThemisXAnalysisNodeCompilationUnit parseJavaFile() throws OceanusException {
/* Protect against exceptions */
try (InputStream myStream = new FileInputStream(theCurrentFile)) {
/* Parse the contents */
final ParseResult<CompilationUnit> myUnit = theParser.parse(myStream);
if (!myUnit.isSuccessful()) {
final Problem myProblem = myUnit.getProblem(0);
throw new ThemisDataException(myProblem.getVerboseMessage());
}
return (ThemisXAnalysisNodeCompilationUnit) parseNode(myUnit.getResult().orElse(null));
/* Catch exceptions */
} catch (IOException e) {
/* Throw an exception */
throw new ThemisIOException("Failed to load file "
+ theCurrentFile.getAbsolutePath(), e);
}
}
@Override
public ThemisXAnalysisModModule parseModuleInfo(final File pInfoFile) throws OceanusException {
/* Protect against exceptions */
setCurrentFile(pInfoFile);
try (InputStream myStream = new FileInputStream(theCurrentFile)) {
/* Parse the contents */
final String myText = new String(myStream.readAllBytes(), StandardCharsets.UTF_8);
final ParseResult<ModuleDeclaration> myDecl = theParser.parseModuleDeclaration(myText);
if (!myDecl.isSuccessful()) {
final Problem myProblem = myDecl.getProblem(0);
throw new ThemisDataException(myProblem.getVerboseMessage());
}
return (ThemisXAnalysisModModule) parseModule(myDecl.getResult().orElse(null));
/* Catch exceptions */
} catch (IOException e) {
/* Throw an exception */
throw new ThemisIOException("Failed to load file "
+ theCurrentFile.getAbsolutePath(), e);
}
}
@Override
public OceanusException buildException(final String pMessage,
final Node pNode) {
/* Determine location of error */
final Position myPos = pNode.getBegin().orElse(null);
final String myLocation = pNode.getClass().getCanonicalName()
+ (myPos == null ? "" : ThemisXAnalysisChar.PARENTHESIS_OPEN
+ myPos.line
+ ThemisXAnalysisChar.COLON
+ myPos.column
+ ThemisXAnalysisChar.PARENTHESIS_CLOSE);
/* Build full error message */
final String myMsg = pMessage
+ ThemisXAnalysisChar.LF
+ myLocation
+ ThemisXAnalysisChar.LF
+ theCurrentFile.getAbsolutePath();
/* Create exception */
return new ThemisDataException(myMsg);
}
/**
* Check the package name.
*
* @param pPackage the package name
* @throws OceanusException on error
*/
public void checkPackage(final PackageDeclaration pPackage) throws OceanusException {
/* Check that package matches */
if (!thePackage.equals(pPackage.getNameAsString())) {
throw buildException("Mismatch on package", pPackage);
}
}
@Override
public ThemisXAnalysisDeclarationInstance parseDeclaration(final BodyDeclaration<?> pDecl) throws OceanusException {
final ThemisXAnalysisDeclarationInstance myInstance = ThemisXAnalysisDeclParser.parseDeclaration(this, pDecl);
deRegisterInstance(myInstance);
return myInstance;
}
@Override
public ThemisXAnalysisNodeInstance parseNode(final Node pNode) throws OceanusException {
final ThemisXAnalysisNodeInstance myInstance = ThemisXAnalysisNodeParser.parseNode(this, pNode);
deRegisterInstance(myInstance);
return myInstance;
}
@Override
public ThemisXAnalysisTypeInstance parseType(final Type pType) throws OceanusException {
final ThemisXAnalysisTypeInstance myInstance = ThemisXAnalysisTypeParser.parseType(this, pType);
deRegisterInstance(myInstance);
return myInstance;
}
@Override
public ThemisXAnalysisStatementInstance parseStatement(final Statement pStatement) throws OceanusException {
final ThemisXAnalysisStatementInstance myInstance = ThemisXAnalysisStmtParser.parseStatement(this, pStatement);
deRegisterInstance(myInstance);
return myInstance;
}
@Override
public ThemisXAnalysisExpressionInstance parseExpression(final Expression pExpr) throws OceanusException {
final ThemisXAnalysisExpressionInstance myInstance = ThemisXAnalysisExprParser.parseExpression(this, pExpr);
deRegisterInstance(myInstance);
return myInstance;
}
@Override
public ThemisXAnalysisModuleInstance parseModule(final Node pMod) throws OceanusException {
final ThemisXAnalysisModuleInstance myInstance = ThemisXAnalysisModParser.parseModule(this, pMod);
deRegisterInstance(myInstance);
return myInstance;
}
@Override
public ThemisXAnalysisModifierList parseModifierList(final NodeList<? extends Node> pNodeList) throws OceanusException {
return ThemisXAnalysisNodeParser.parseModifierList(this, pNodeList);
}
}