ThemisAnalysisParser.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.lethe.analysis;
import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
import io.github.tonywasher.joceanus.themis.exc.ThemisDataException;
import io.github.tonywasher.joceanus.themis.lethe.analysis.ThemisAnalysisDataMap.ThemisAnalysisDataType;
import io.github.tonywasher.joceanus.themis.lethe.analysis.ThemisAnalysisEmbedded.ThemisAnalysisEmbedType;
import io.github.tonywasher.joceanus.themis.lethe.analysis.ThemisAnalysisGeneric.ThemisAnalysisGenericBase;
import io.github.tonywasher.joceanus.themis.lethe.analysis.ThemisAnalysisScanner.ThemisAnalysisSource;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Parser.
*/
public class ThemisAnalysisParser
implements ThemisAnalysisSource {
/**
* The keyWordMap.
*/
static final Map<String, Object> KEYWORDS = createKeyWordMap();
/**
* The parent container.
*/
private final ThemisAnalysisContainer theParent;
/**
* The list of source lines.
*/
private final Deque<ThemisAnalysisElement> theLines;
/**
* The list of output contents lines.
*/
private final Deque<ThemisAnalysisElement> theContents;
/**
* The dataMap.
*/
private final ThemisAnalysisDataMap theDataMap;
/**
* Temporary parser?
*/
private final boolean isTemporary;
/**
* Constructor.
*
* @param pLines the source lines.
* @param pContents the processed contents
* @param pContainer the container
*/
ThemisAnalysisParser(final Deque<ThemisAnalysisElement> pLines,
final Deque<ThemisAnalysisElement> pContents,
final ThemisAnalysisContainer pContainer) {
/* Store parameters */
theLines = pLines;
theContents = pContents;
theParent = pContainer;
/* Create the dataTypeMap */
theDataMap = pContainer.getDataMap();
isTemporary = false;
}
/**
* Constructor.
*
* @param pParser the source parser.
* @param pProcessed the processed output
*/
ThemisAnalysisParser(final ThemisAnalysisParser pParser,
final Deque<ThemisAnalysisElement> pProcessed) {
this(pParser.theLines, pProcessed, pParser.getParent());
}
/**
* Temporary parser constructor.
*
* @param pParser the base parser.
*/
ThemisAnalysisParser(final ThemisAnalysisParser pParser) {
/* Store parameters */
theLines = pParser.theLines;
theContents = pParser.theContents;
theParent = pParser.theParent;
/* Create the dataTypeMap */
theDataMap = new ThemisAnalysisDataMap(pParser.getDataMap());
isTemporary = true;
}
@Override
public boolean hasLines() {
return !theLines.isEmpty();
}
/**
* Is this a temporary parser?
*
* @return true/false
*/
boolean isTemporary() {
return isTemporary;
}
/**
* Obtain the parent container.
*
* @return the parent
*/
ThemisAnalysisContainer getParent() {
return theParent;
}
/**
* Obtain the dataTypes map.
*
* @return the dataTypesMap
*/
ThemisAnalysisDataMap getDataMap() {
return theDataMap;
}
@Override
public ThemisAnalysisElement popNextLine() throws OceanusException {
/* Check that there is a line to pop */
if (theLines.isEmpty()) {
throw new ThemisDataException("No more lines");
}
/* Access the first line and remove from the list */
return theLines.removeFirst();
}
/**
* Peek next line from list.
*
* @return the next line
* @throws OceanusException on error
*/
ThemisAnalysisElement peekNextLine() throws OceanusException {
/* Check that there is a line to pop */
if (theLines.isEmpty()) {
throw new ThemisDataException("No more lines");
}
/* Return the first line in the list */
return theLines.getFirst();
}
@Override
public void pushLine(final ThemisAnalysisElement pLine) {
/* Insert the line at the front of the stack */
theLines.offerFirst(pLine);
}
/**
* Process a potential comment/blank line.
*
* @param pLine the line
* @return have we processed the line?
* @throws OceanusException on error
*/
boolean processCommentsAndBlanks(final ThemisAnalysisLine pLine) throws OceanusException {
/* If this is a starting comment */
if (ThemisAnalysisComment.isStartComment(pLine)) {
/* Process the comment lines */
final ThemisAnalysisComment myComment = new ThemisAnalysisComment(this, pLine);
theContents.add(myComment);
return true;
}
/* Strip Trailing comments and modifiers */
pLine.stripTrailingComments();
pLine.stripModifiers();
/* If this is a blank line */
if (ThemisAnalysisBlank.isBlank(pLine)) {
/* Process the blank lines */
final ThemisAnalysisBlank myBlank = new ThemisAnalysisBlank(this, pLine);
theContents.add(myBlank);
return true;
}
/* If this is an annotation line */
if (ThemisAnalysisAnnotation.isAnnotation(pLine)) {
/* Process the annotation lines */
final ThemisAnalysisAnnotation myAnnotation = new ThemisAnalysisAnnotation(this, pLine);
theContents.add(myAnnotation);
return true;
}
/* Not processed */
return false;
}
/**
* Process a potential import line.
*
* @param pLine the line
* @return have we processed the line?
* @throws OceanusException on error
*/
boolean processImports(final ThemisAnalysisLine pLine) throws OceanusException {
/* If this is an import line */
if (ThemisAnalysisImports.isImport(pLine)) {
/* Process the import lines */
final ThemisAnalysisImports myImports = new ThemisAnalysisImports(this, pLine);
theContents.add(myImports);
return true;
}
/* Not processed */
return false;
}
/**
* Process a class/enum/interface line.
*
* @param pLine the line
* @return have we processed the line?
* @throws OceanusException on error
*/
boolean processClass(final ThemisAnalysisLine pLine) throws OceanusException {
/* Access class type */
final String myToken = pLine.peekNextToken();
final Object myType = KEYWORDS.get(myToken);
/* If we have a keyWord */
if (myType instanceof ThemisAnalysisKeyWord myKeyWord) {
/* If this is a class */
switch (myKeyWord) {
case CLASS:
/* Create the class */
pLine.stripStartSequence(myToken);
theContents.add(new ThemisAnalysisClass(this, pLine));
return true;
/* If this is an interface/annotation */
case INTERFACE:
case ANNOTATION:
/* Create the interface */
pLine.stripStartSequence(myToken);
theContents.add(new ThemisAnalysisInterface(this, myType.equals(ThemisAnalysisKeyWord.ANNOTATION), pLine));
return true;
/* If this is an enum */
case ENUM:
/* Create the enum */
pLine.stripStartSequence(myToken);
theContents.add(new ThemisAnalysisEnum(this, pLine));
return true;
default:
break;
}
}
/* Not processed */
return false;
}
/**
* Process language constructs.
*
* @param pLine the line
* @return have we processed the line?
* @throws OceanusException on error
*/
boolean processLanguage(final ThemisAnalysisLine pLine) throws OceanusException {
/* Access class type */
final String myToken = pLine.peekNextToken();
final Object myType = KEYWORDS.get(myToken);
/* If we have a keyWord */
if (myType instanceof ThemisAnalysisKeyWord myKeyWord) {
/* Switch on the type */
switch (myKeyWord) {
/* If this is a while */
case WHILE:
/* Create the while */
pLine.stripStartSequence(myToken);
theContents.add(new ThemisAnalysisWhile(this, pLine));
return true;
/* If this is a doWhile */
case DO:
/* Create the while */
pLine.stripStartSequence(myToken);
theContents.add(new ThemisAnalysisDoWhile(this));
return true;
/* If this is a switch */
case SWITCH:
/* Create the switch */
pLine.stripStartSequence(myToken);
theContents.add(new ThemisAnalysisSwitch(this, pLine));
return true;
/* If this is a for */
case FOR:
/* Create the for */
pLine.stripStartSequence(myToken);
theContents.add(new ThemisAnalysisFor(this, pLine));
return true;
/* If this is an if */
case IF:
/* Create the if */
pLine.stripStartSequence(myToken);
theContents.add(new ThemisAnalysisIf(this, pLine));
return true;
/* If this is a try */
case TRY:
/* Create the try */
pLine.stripStartSequence(myToken);
theContents.add(new ThemisAnalysisTry(this, pLine));
return true;
default:
break;
}
}
/* Not processed */
return false;
}
/**
* Process blocks.
*
* @param pLine the line
* @return have we processed the line?
* @throws OceanusException on error
*/
boolean processBlocks(final ThemisAnalysisLine pLine) throws OceanusException {
/* handle a standard block */
if (ThemisAnalysisBlock.checkBlock(pLine)) {
theContents.add(new ThemisAnalysisBlock(this, pLine));
return true;
}
/* check for an embedded lambda/Anonymous class/ArrayInit */
final ThemisAnalysisEmbedType myEmbed = ThemisAnalysisEmbedded.checkForEmbedded(pLine);
switch (myEmbed) {
case LAMBDA:
case ANON:
case ARRAY:
/* Process an embedded lambda/anon/init */
theContents.add(new ThemisAnalysisEmbedded(this, myEmbed, pLine));
return true;
case METHOD:
/* Process an embedded methodBody */
theContents.add(new ThemisAnalysisMethodBody(this, pLine));
return true;
default:
break;
}
/* Not processed */
return false;
}
/**
* Process a case/default line.
*
* @param pOwner the owning switch
* @param pLine the line
* @return have we processed the line?
* @throws OceanusException on error
*/
boolean processCase(final ThemisAnalysisContainer pOwner,
final ThemisAnalysisLine pLine) throws OceanusException {
/* Access case type */
final Object myCase = parseCase(pLine);
/* If we have a case */
if (myCase != null) {
theContents.add(new ThemisAnalysisCase(this, pOwner, myCase));
return true;
}
/* Not processed */
return false;
}
/**
* Process a case/default line.
*
* @param pLine the line
* @return have we processed the line?
*/
static Object parseCase(final ThemisAnalysisLine pLine) {
/* Handle default clause */
if (pLine.getProperties().hasModifier(ThemisAnalysisModifier.DEFAULT)) {
return ThemisAnalysisKeyWord.DEFAULT;
}
/* Access case type */
final String myToken = pLine.peekNextToken();
final Object myType = KEYWORDS.get(myToken);
/* If we have a keyWord */
if (myType instanceof ThemisAnalysisKeyWord myKeyWord) {
/* If this is a case/default */
switch (myKeyWord) {
case CASE:
pLine.stripStartSequence(myToken);
return pLine.stripNextToken();
case DEFAULT:
pLine.stripStartSequence(myToken);
return myKeyWord;
default:
return null;
}
}
/* Not processed */
return null;
}
/**
* Process extra constructs.
*
* @param pOwner the owning construct
* @param pKeyWord the keyWord
* @return have we processed the line?
* @throws OceanusException on error
*/
ThemisAnalysisElement processExtra(final ThemisAnalysisContainer pOwner,
final ThemisAnalysisKeyWord pKeyWord) throws OceanusException {
/* Just return if there are no more lines */
if (!hasLines()) {
return null;
}
/* Access keyWord */
final ThemisAnalysisLine myLine = (ThemisAnalysisLine) popNextLine();
final String myToken = myLine.peekNextToken();
final Object myType = KEYWORDS.get(myToken);
/* If we have a keyWord */
if (pKeyWord.equals(myType)) {
/* Switch on the type */
switch ((ThemisAnalysisKeyWord) myType) {
/* If this is an else */
case ELSE:
/* Create the else */
myLine.stripStartSequence(myToken);
return new ThemisAnalysisElse(this, pOwner, myLine);
/* If this is a catch */
case CATCH:
/* Create the switch */
myLine.stripStartSequence(myToken);
return new ThemisAnalysisCatch(this, pOwner, myLine);
/* If this is a finally */
case FINALLY:
/* Create the finally */
myLine.stripStartSequence(myToken);
return new ThemisAnalysisFinally(this, pOwner, myLine);
default:
break;
}
}
/* Not processed */
pushLine(myLine);
return null;
}
/**
* Process embedded block construct.
*
* @param pEmbedded the embedded block
* @return the field/statement
* @throws OceanusException on error
*/
ThemisAnalysisElement processEmbedded(final ThemisAnalysisEmbedded pEmbedded) throws OceanusException {
/* Look for a reference */
final ThemisAnalysisLine myLine = pEmbedded.getHeader();
final ThemisAnalysisReference myReference = parsePotentialDataType(myLine);
/* If we have a reference */
if (myReference != null) {
/* Create as field */
final String myName = myLine.stripNextToken();
return new ThemisAnalysisField(this, myName, myReference, pEmbedded);
}
/* Just convert to statement */
final ThemisAnalysisKeyWord myKeyWord = determineStatementKeyWord(myLine);
return new ThemisAnalysisStatement(myKeyWord, pEmbedded);
}
/**
* Process methodBody construct.
*
* @param pMethod the methodBody
* @return the method
* @throws OceanusException on error
*/
ThemisAnalysisElement processMethodBody(final ThemisAnalysisMethodBody pMethod) throws OceanusException {
/* Look for a reference */
final ThemisAnalysisLine myLine = pMethod.getHeader();
final ThemisAnalysisReference myReference = parsePotentialDataType(myLine);
/* Access the name of the method */
final String myName = myLine.stripNextToken();
final boolean isMethod = myLine.startsWithChar(ThemisAnalysisChar.PARENTHESIS_OPEN);
/* We must have a reference and be a method */
if (myReference == null || !isMethod) {
throw new ThemisDataException("Invalid Method Body");
}
/* Create as field */
return new ThemisAnalysisMethod(this, myName, myReference, pMethod);
}
/**
* Process field and method constructs.
*
* @param pLine the line
* @return the field/method or null
* @throws OceanusException on error
*/
ThemisAnalysisElement processFieldsAndMethods(final ThemisAnalysisLine pLine) throws OceanusException {
/* Look for a reference */
final ThemisAnalysisReference myReference = parsePotentialDataType(pLine);
if (myReference != null) {
/* Access the name of the field or method */
final String myName = pLine.stripNextToken();
final boolean isMethod = pLine.startsWithChar(ThemisAnalysisChar.PARENTHESIS_OPEN);
if (!isMethod) {
myReference.resolveGeneric(this);
return new ThemisAnalysisField(this, myName, myReference, pLine);
} else {
return new ThemisAnalysisMethod(this, myName, myReference, pLine);
}
}
/* Not processed */
return null;
}
/**
* Process a statement.
*
* @param pLine the line
* @return the statement
* @throws OceanusException on error
*/
ThemisAnalysisElement processStatement(final ThemisAnalysisLine pLine) throws OceanusException {
/* Determine keyWord (if any) */
final ThemisAnalysisKeyWord myKeyWord = determineStatementKeyWord(pLine);
return new ThemisAnalysisStatement(this, myKeyWord, pLine);
}
/**
* Process a statement.
*
* @param pLine the line
* @return the statement
*/
private static ThemisAnalysisKeyWord determineStatementKeyWord(final ThemisAnalysisLine pLine) {
/* Look for a control keyWord */
final String myToken = pLine.peekNextToken();
final Object myType = KEYWORDS.get(myToken);
/* If we have a keyWord */
if (myType instanceof ThemisAnalysisKeyWord myKeyWord) {
/* Switch on the type */
switch (myKeyWord) {
case RETURN:
case THROW:
case BREAK:
case CONTINUE:
case YIELD:
pLine.stripNextToken();
return myKeyWord;
default:
break;
}
}
/* No keyWord */
return null;
}
/**
* Parse a possible dataType.
*
* @param pLine the line
* @return the dataType or null
* @throws OceanusException on error
*/
private ThemisAnalysisReference parsePotentialDataType(final ThemisAnalysisLine pLine) throws OceanusException {
/* Not a dataType if we start with a keyWord */
final String myToken = pLine.peekNextToken();
if (KEYWORDS.get(myToken) != null) {
return null;
}
/* Determine whether this is a method call or declaration */
final boolean isMethod = pLine.startsWithSequence(myToken + ThemisAnalysisChar.PARENTHESIS_OPEN);
/* Look for a valid, existing dataType */
ThemisAnalysisDataType myType = theDataMap.lookUpDataType(myToken, isMethod);
if (myType == null) {
/* If the line has generic definitions */
final ThemisAnalysisProperties myProps = pLine.getProperties();
if (myProps.hasGeneric()) {
/* Create a temporary parser and resolve against the generics */
final ThemisAnalysisParser myTempParser = new ThemisAnalysisParser(this);
myProps.resolveGeneric(myTempParser);
myType = myTempParser.getDataMap().lookUpTheDataType(myToken);
}
/* Return null if we haven't resolved it */
if (myType == null) {
return null;
}
}
pLine.stripStartSequence(myToken);
/* Return the reference */
return buildReference(theDataMap, pLine, myType);
}
/**
* Parse a dataType.
*
* @param pDataMap the dataMap
* @param pLine the line
* @return the dataType or null
* @throws OceanusException on error
*/
static ThemisAnalysisReference parseDataType(final ThemisAnalysisDataMap pDataMap,
final ThemisAnalysisLine pLine) throws OceanusException {
/* Cannot be started by a keyWord */
String myToken = pLine.peekNextToken();
if (KEYWORDS.get(myToken) != null) {
throw new ThemisDataException("DataType required but keyWord found");
}
/* If the token ends with the VARARGS indication */
if (myToken.endsWith(ThemisAnalysisArray.VARARGS)) {
/* Strip the varArgs indication */
myToken = myToken.substring(0, myToken.length() - ThemisAnalysisArray.VARARGS.length());
}
/* Look for a valid dataType */
ThemisAnalysisDataType myType = pDataMap.lookUpDataType(myToken, false);
if (myType == null) {
/* Declare the unrecognised dataType */
myType = pDataMap.declareUnknown(myToken);
}
pLine.stripStartSequence(myToken);
/* Return the reference */
return buildReference(pDataMap, pLine, myType);
}
/**
* Create the reference.
*
* @param pDataMap the dataMap
* @param pLine the line
* @param pType the dataType
* @return the dataType or null
* @throws OceanusException on error
*/
private static ThemisAnalysisReference buildReference(final ThemisAnalysisDataMap pDataMap,
final ThemisAnalysisLine pLine,
final ThemisAnalysisDataType pType) throws OceanusException {
/* Access any generic/array detail */
final ThemisAnalysisGeneric myGeneric = ThemisAnalysisGeneric.isGeneric(pLine)
? new ThemisAnalysisGenericBase(pLine)
: null;
final ThemisAnalysisArray myArray = ThemisAnalysisArray.isArray(pLine)
? new ThemisAnalysisArray(pLine)
: null;
/* Return the reference */
final ThemisAnalysisReference myRef = new ThemisAnalysisReference(pType, myGeneric, myArray);
pDataMap.declareReference(myRef);
return myRef;
}
/**
* process the lines.
*
* @throws OceanusException on error
*/
void processLines() throws OceanusException {
/* Loop through the lines */
while (hasLines()) {
/* Access next line */
final ThemisAnalysisLine myLine = (ThemisAnalysisLine) popNextLine();
/* Process comments/blanks/languageConstructs */
final boolean processed = processCommentsAndBlanks(myLine)
|| processClass(myLine)
|| processLanguage(myLine)
|| processBlocks(myLine);
/* If we haven't processed yet */
if (!processed) {
/* Just add the line to contents at present */
theContents.add(myLine);
}
}
}
/**
* Create the keyWordMap.
*
* @return the new map
*/
private static Map<String, Object> createKeyWordMap() {
/* create the map */
final Map<String, Object> myMap = new HashMap<>();
/* Add the modifiers */
for (ThemisAnalysisModifier myModifier : ThemisAnalysisModifier.values()) {
myMap.put(myModifier.toString(), myModifier);
}
/* Add the keyWords */
for (ThemisAnalysisKeyWord myKeyWord : ThemisAnalysisKeyWord.values()) {
myMap.put(myKeyWord.toString(), myKeyWord);
}
/* return the map */
return myMap;
}
/**
* Parse ancestors.
*
* @param pHeaders the headers
* @return the list of ancestors
* @throws OceanusException on error
*/
List<ThemisAnalysisReference> parseAncestors(final Deque<ThemisAnalysisElement> pHeaders) throws OceanusException {
/* Create the list */
final List<ThemisAnalysisReference> myAncestors = new ArrayList<>();
final ThemisAnalysisLine myHeader = new ThemisAnalysisLine(pHeaders);
/* Loop through the line */
while (true) {
/* Strip leading comma */
if (myHeader.startsWithChar(ThemisAnalysisChar.COMMA)) {
myHeader.stripStartChar(ThemisAnalysisChar.COMMA);
}
/* Access first token */
final String myToken = myHeader.peekNextToken();
if (myToken.length() == 0) {
return myAncestors;
}
/* Ignore keywords */
if (KEYWORDS.get(myToken) != null) {
/* Strip the token from the line */
myHeader.stripNextToken();
} else {
/* Process the ancestor */
final ThemisAnalysisReference myReference = parseDataType(theDataMap, myHeader);
myReference.resolveGeneric(this);
myAncestors.add(myReference);
}
}
}
/**
* Parse parameters.
*
* @param pParams the parameters
* @return the parameter map
* @throws OceanusException on error
*/
Map<String, ThemisAnalysisReference> parseParameters(final ThemisAnalysisLine pParams) throws OceanusException {
/* Create the list */
final Map<String, ThemisAnalysisReference> myParams = new LinkedHashMap<>();
/* Loop through the lines */
while (true) {
/* Strip leading comma */
if (pParams.startsWithChar(ThemisAnalysisChar.COMMA)) {
pParams.stripStartChar(ThemisAnalysisChar.COMMA);
}
/* Access first token */
final String myToken = pParams.peekNextToken();
if (myToken.length() == 0) {
return myParams;
}
/* Ignore keywords */
if (KEYWORDS.get(myToken) != null) {
/* Strip the token from the line */
pParams.stripNextToken();
} else {
/* Process the parameter */
final ThemisAnalysisReference myReference = parseDataType(theDataMap, pParams);
myReference.resolveGeneric(this);
final String myVar = pParams.stripNextToken();
myParams.put(myVar, myReference);
}
}
}
}