ThemisMapper.java
/*
* Themis: Java Project Framework
* Copyright 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.solver.mapper;
import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
import io.github.tonywasher.joceanus.themis.exc.ThemisDataException;
import io.github.tonywasher.joceanus.themis.parser.base.ThemisInstance;
import io.github.tonywasher.joceanus.themis.parser.base.ThemisInstance.ThemisClassInstance;
import io.github.tonywasher.joceanus.themis.parser.base.ThemisInstance.ThemisExpressionInstance;
import io.github.tonywasher.joceanus.themis.parser.base.ThemisInstance.ThemisNodeInstance;
import io.github.tonywasher.joceanus.themis.parser.base.ThemisInstance.ThemisTypeInstance;
import io.github.tonywasher.joceanus.themis.parser.expr.ThemisExprFieldAccess;
import io.github.tonywasher.joceanus.themis.parser.expr.ThemisExprMethodCall;
import io.github.tonywasher.joceanus.themis.parser.expr.ThemisExprMethodRef;
import io.github.tonywasher.joceanus.themis.parser.expr.ThemisExprName;
import io.github.tonywasher.joceanus.themis.parser.node.ThemisNodeImport;
import io.github.tonywasher.joceanus.themis.parser.type.ThemisTypeClassInterface;
import io.github.tonywasher.joceanus.themis.solver.proj.ThemisSolverClass;
import io.github.tonywasher.joceanus.themis.solver.proj.ThemisSolverFile;
import io.github.tonywasher.joceanus.themis.solver.proj.ThemisSolverPackage;
import io.github.tonywasher.joceanus.themis.solver.proj.ThemisSolverProject;
import java.util.ArrayList;
import java.util.List;
/**
* Analysis Mapper.
*/
public class ThemisMapper
implements AutoCloseable {
/**
* Project State.
*/
private final ThemisMapperProjectState theProject;
/**
* Project State.
*/
private final ThemisMapperFileState theFile;
/**
* Project State.
*/
private final ThemisMapperTypeState theType;
/**
* Project State.
*/
private final ThemisMapperNameState theName;
/**
* Constructor.
*
* @param pProject the project.
* @throws OceanusException on error
*/
public ThemisMapper(final ThemisSolverProject pProject) throws OceanusException {
/* Create the project state */
theProject = new ThemisMapperProjectState(pProject);
/* Create the file state */
theFile = new ThemisMapperFileState(theProject);
theType = new ThemisMapperTypeState();
theName = new ThemisMapperNameState();
}
/**
* PreProcess package.
*
* @param pPackage the package
*/
public void preProcessPackage(final ThemisSolverPackage pPackage) {
/* Loop through the files in the package */
for (ThemisSolverFile myFile : pPackage.getFiles()) {
/* preProcess the file if it has not been done yet */
if (myFile.needsPreProcess()) {
preProcessFile(myFile);
}
}
}
/**
* Process package.
*
* @param pPackage the package
* @throws OceanusException on error
*/
public void processPackage(final ThemisSolverPackage pPackage) throws OceanusException {
/* Loop through the files in the package */
for (ThemisSolverFile myFile : pPackage.getFiles()) {
/* Process the file */
processFile(myFile);
}
/* Loop through the files in the package */
for (ThemisSolverFile myFile : pPackage.getFiles()) {
/* Process the local references */
myFile.processLocalReferences();
}
}
/**
* Reset fileState.
*
* @param pFile the file
*/
private void resetFileState(final ThemisSolverFile pFile) {
/* Reset the various states */
theFile.initForFile(pFile);
theType.reset();
theName.reset();
}
/**
* preProcess file.
*
* @param pFile the file
*/
private void preProcessFile(final ThemisSolverFile pFile) {
/* Note that we have pre-processed this file */
pFile.markPreProcessed();
/* preProcess the imports */
preProcessImports(pFile);
/* Reset the fileState */
resetFileState(pFile);
/* Loop through the classes in the file */
for (ThemisSolverClass myClass : pFile.getClasses()) {
final ThemisClassInstance myInstance = myClass.getUnderlyingClass();
/* Loop through the extends */
for (ThemisTypeInstance myExtends : myInstance.getExtends()) {
processAncestor(myClass, myExtends);
}
/* Loop through the implements */
for (ThemisTypeInstance myImplements : myInstance.getImplements()) {
processAncestor(myClass, myImplements);
}
/* Process inherited children */
theFile.processInherited(myClass);
}
}
/**
* preProcess file.
*
* @param pFile the file
*/
private void preProcessImports(final ThemisSolverFile pFile) {
/* Loop through all the imports */
for (ThemisNodeInstance myNode : pFile.getUnderlyingFile().getContents().getImports()) {
/* If the import is of a project file */
final ThemisNodeImport myImport = (ThemisNodeImport) myNode;
final ThemisSolverClass myClass = theProject.getProjectClassMap().get(myImport.getFullName());
if (myClass != null) {
/* Make sure that the file has been pre-processed */
final ThemisSolverFile myFile = (ThemisSolverFile) myClass.getOwningFile();
if (myFile.needsPreProcess()) {
/* PreProcess the file */
preProcessFile(myFile);
}
}
}
}
/**
* process ancestor.
*
* @param pClass the class
* @param pAncestor the ancestor
*/
private void processAncestor(final ThemisSolverClass pClass,
final ThemisTypeInstance pAncestor) {
/* Handle ClassInterface Reference */
if (pAncestor instanceof ThemisTypeClassInterface myRef) {
final ThemisClassInstance myResolved = theFile.processPossibleReference(myRef.getFullName());
if (myResolved != null) {
pClass.addAncestor(myResolved.getFullName());
myRef.setClassInstance(myResolved);
}
}
}
/**
* Process file.
*
* @param pFile the file
* @throws OceanusException on error
*/
private void processFile(final ThemisSolverFile pFile) throws OceanusException {
/* Reset the fileState */
resetFileState(pFile);
/* Obtain the top-level class element */
final ThemisSolverClass myBase = pFile.getTopLevel();
final ThemisInstance myInstance = (ThemisInstance) myBase.getUnderlyingClass();
processInstance(myInstance);
/* Propagate the referenced classes */
pFile.setReferenced(theFile.getReferenced());
}
/**
* Process instance.
*
* @param pInstance the instance
* @throws OceanusException on error
*/
private void processInstance(final ThemisInstance pInstance) throws OceanusException {
/* Process stacks */
final boolean bumpType = theType.processInstance(pInstance);
final boolean bumpName = theName.processInstance(pInstance);
/* Process element */
final boolean doChildren = processElement(pInstance);
/* Process children */
if (doChildren) {
for (ThemisInstance myChild : sortedChildren(pInstance)) {
processInstance(myChild);
}
}
/* CleanUp stacks */
if (bumpName) {
theName.cleanUpAfterInstance();
}
if (bumpType) {
theType.cleanUpAfterInstance();
}
}
/**
* Process element.
*
* @param pElement the element
* @return process children? true/false
* @throws OceanusException on error
*/
private boolean processElement(final ThemisInstance pElement) throws OceanusException {
/* Handle ClassInstance */
if (pElement instanceof ThemisClassInstance myInstance) {
final ThemisSolverClass myClass = theProject.getProjectClassMap().get(myInstance.getFullName());
theFile.processInherited(myClass);
}
/* Handle ClassInterface Reference */
if (pElement instanceof ThemisTypeClassInterface myRef) {
processClassReference(myRef);
return false;
}
/* Handle FieldAccess */
if (pElement instanceof ThemisExprFieldAccess myAccess) {
processFieldAccess(myAccess);
return false;
}
/* Handle MethodCall */
if (pElement instanceof ThemisExprMethodCall myCall) {
processMethodCall(myCall);
return false;
}
/* Handle MethodReference */
if (pElement instanceof ThemisExprMethodRef myRef) {
processMethodReference(myRef);
return false;
}
return true;
}
/**
* Process class reference.
*
* @param pReference the reference
* @throws OceanusException on error
*/
private void processClassReference(final ThemisTypeClassInterface pReference) throws OceanusException {
/* Process as a possible reference */
final ThemisClassInstance myResolved = theFile.processPossibleReference(pReference.getFullName());
/* If we failed to resolve */
if (myResolved == null) {
/* Check for type parameters and variable names */
final ThemisInstance myType = theType.lookUpType(pReference.getName());
final ThemisInstance myName = theName.lookUpName(pReference.getName());
/* Report failure */
final boolean bFound = (myType != null || myName != null);
if (!bFound) {
throw new ThemisDataException("Unresolved link: " + pReference.getFullName());
}
}
}
/**
* Process field Access.
*
* @param pAccess the fieldAccess
* @throws OceanusException on error
*/
private void processFieldAccess(final ThemisExprFieldAccess pAccess) throws OceanusException {
final ThemisExpressionInstance myExpr = pAccess.getScope();
if (myExpr instanceof ThemisExprName myNameExpr) {
/* Check to see if the field access is to an explicit class */
final String myFullName = pAccess.toString();
final String myNameRef = myNameExpr.toString();
ThemisClassInstance myResolved = theFile.processPossibleReference(myFullName);
/* If not a full path look to see whether it is a field of a class */
if (myResolved == null) {
myResolved = theFile.processPossibleReference(myNameRef);
}
/* If we still failed to resolve */
if (myResolved == null) {
/* Check for variable names */
final ThemisInstance myName = theName.lookUpName(myNameRef);
/* Report failure */
if (myName == null) {
throw new ThemisDataException("Unresolved fieldAccess: " + myNameRef);
}
}
}
}
/**
* Process method call.
*
* @param pCall the methodCall
* @throws OceanusException on error
*/
private void processMethodCall(final ThemisExprMethodCall pCall) throws OceanusException {
final ThemisExpressionInstance myExpr = pCall.getScope();
if (myExpr instanceof ThemisExprName myNameExpr) {
/* Check to see if the field access is to an explicit class */
final String myNameRef = myNameExpr.toString();
final ThemisClassInstance myResolved = theFile.processPossibleReference(myNameRef);
/* If we still failed to resolve */
if (myResolved == null) {
/* Check for variable names */
final ThemisInstance myName = theName.lookUpName(myNameRef);
/* Report failure */
if (myName == null) {
throw new ThemisDataException("Unresolved method call using name: " + myNameRef);
}
}
} else if (myExpr instanceof ThemisExprFieldAccess myFieldExpr) {
processFieldAccess(myFieldExpr);
} else if (myExpr instanceof ThemisExprMethodCall myMethodCall) {
processMethodCall(myMethodCall);
}
}
/**
* Process method Reference.
*
* @param pReference the reference
* @throws OceanusException on error
*/
private void processMethodReference(final ThemisExprMethodRef pReference) throws OceanusException {
final ThemisExpressionInstance myExpr = pReference.getScope();
if (myExpr instanceof ThemisExprName myNameExpr) {
/* Check to see if the method reference is to an explicit class */
final String myNameRef = myNameExpr.toString();
final ThemisClassInstance myResolved = theFile.processPossibleReference(myNameRef);
/* If we still failed to resolve */
if (myResolved == null) {
/* Check for variable names */
final ThemisInstance myName = theName.lookUpName(myNameRef);
/* Report failure */
if (myName == null) {
throw new ThemisDataException("Unresolved methodRef: " + myNameRef);
}
}
}
}
/**
* Obtain sorted list of children.
*
* @param pInstance the instanceNode
* @return the sorted list
*/
private List<ThemisInstance> sortedChildren(final ThemisInstance pInstance) {
final List<ThemisInstance> myChildren = new ArrayList<>(pInstance.getChildren());
myChildren.sort(this::compareTo);
return myChildren;
}
/**
* Comparator that pushes ClassInstances to bottom of list.
*
* @param pFirst the first node
* @param pSecond the second node
* @return -1, 0, 1 according to order
*/
private int compareTo(final ThemisInstance pFirst,
final ThemisInstance pSecond) {
if (pFirst instanceof ThemisClassInstance) {
return pSecond instanceof ThemisClassInstance ? 0 : 1;
}
return pSecond instanceof ThemisClassInstance ? -1 : 0;
}
@Override
public void close() {
theProject.close();
}
}