ThemisAnalysisFile.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.exc.ThemisIOException;
import io.github.tonywasher.joceanus.themis.lethe.analysis.ThemisAnalysisDataMap.ThemisAnalysisDataType;
import io.github.tonywasher.joceanus.themis.lethe.analysis.ThemisAnalysisDataMap.ThemisAnalysisIntermediate;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
/**
* Analysis representation of a java file.
*/
public class ThemisAnalysisFile
implements ThemisAnalysisContainer, ThemisAnalysisIntermediate {
/**
* Object class.
*/
public interface ThemisAnalysisObject
extends ThemisAnalysisDataType, ThemisAnalysisContainer {
/**
* Obtain the short name.
*
* @return the name
*/
String getShortName();
/**
* Obtain the full name of the object.
*
* @return the fullName
*/
String getFullName();
/**
* Obtain ancestors.
*
* @return the list of ancestors
*/
List<ThemisAnalysisReference> getAncestors();
/**
* Obtain properties.
*
* @return the properties
*/
ThemisAnalysisProperties getProperties();
}
/**
* The buffer length (must be longer than longest line).
*/
private static final int BUFLEN = 1024;
/**
* The location of the file.
*/
private final File theLocation;
/**
* The name of the file.
*/
private final String theName;
/**
* The package file.
*/
private final ThemisAnalysisPackage thePackage;
/**
* The dataMap.
*/
private final ThemisAnalysisDataMap theDataMap;
/**
* The contents.
*/
private final Deque<ThemisAnalysisElement> theContents;
/**
* The number of lines in the file.
*/
private int theNumLines;
/**
* Constructor.
*
* @param pPackage the package
* @param pFile the file to analyse
*/
ThemisAnalysisFile(final ThemisAnalysisPackage pPackage,
final File pFile) {
/* Store the parameters */
thePackage = pPackage;
theLocation = pFile;
theName = pFile.getName().replace(ThemisAnalysisPackage.SFX_JAVA, "");
theDataMap = new ThemisAnalysisDataMap(thePackage.getDataMap());
/* Create the list */
theContents = new ArrayDeque<>();
}
/**
* Obtain the name of the fileClass.
*
* @return the name
*/
public String getName() {
return theName;
}
/**
* Obtain the location of the fileClass.
*
* @return the location
*/
public String getLocation() {
return theLocation.getAbsolutePath();
}
/**
* Process the file.
*
* @throws OceanusException on error
*/
void processFile() throws OceanusException {
/* Create the queue */
final Deque<ThemisAnalysisElement> myLines = new ArrayDeque<>();
/* create a read buffer */
final char[] myBuffer = new char[BUFLEN];
int myOffset = 0;
/* Protect against exceptions */
try (InputStream myStream = new FileInputStream(theLocation);
InputStreamReader myInputReader = new InputStreamReader(myStream, StandardCharsets.UTF_8);
BufferedReader myReader = new BufferedReader(myInputReader)) {
/* Read the header entry */
while (true) {
/* Read some characters into the buffer */
final int myChars = myReader.read(myBuffer, myOffset, BUFLEN - myOffset);
if (myChars == -1 && myOffset == 0) {
break;
}
/* Process lines in the buffer */
myOffset = processLines(myLines, myBuffer, myChars + myOffset);
}
/* Record the number of lines */
theNumLines = myLines.size();
/* Perform initial processing pass */
initialProcessingPass(myLines);
/* Catch exceptions */
} catch (IOException e) {
/* Throw an exception */
throw new ThemisIOException("Failed to load file "
+ theLocation.getAbsolutePath(), e);
}
}
@Override
public ThemisAnalysisDataMap getDataMap() {
return theDataMap;
}
@Override
public Deque<ThemisAnalysisElement> getContents() {
return theContents;
}
@Override
public ThemisAnalysisContainer getParent() {
return this;
}
/**
* Obtain the package.
*
* @return the package
*/
String getPackageName() {
return thePackage.getPackage();
}
@Override
public int getNumLines() {
return theNumLines;
}
/**
* Process lines.
*
* @param pLines the list of lines to build
* @param pBuffer the character buffer
* @param pNumChars the number of characters in the buffer
* @return the remaining characters in the buffer
* @throws OceanusException on error
*/
private static int processLines(final Deque<ThemisAnalysisElement> pLines,
final char[] pBuffer,
final int pNumChars) throws OceanusException {
/* The start of the current line */
int myOffset = 0;
/* Look for line feed in the buffer */
int myLF = findLineFeedInBuffer(pBuffer, 0, pNumChars);
while (myLF != -1) {
/* Build the line */
final ThemisAnalysisLine myLine = buildLine(pBuffer, myOffset, myLF);
pLines.add(myLine);
/* Look for next lineFeed */
myOffset = myLF + 1;
myLF = findLineFeedInBuffer(pBuffer, myOffset, pNumChars);
}
/* Copy remaining characters down */
final int myRemaining = pNumChars - myOffset;
if (myRemaining > 0) {
System.arraycopy(pBuffer, myOffset, pBuffer, 0, myRemaining);
}
/* Return the number remaining */
return myRemaining;
}
/**
* Find lineFeed in buffer.
*
* @param pBuffer the character buffer
* @param pOffset the starting offset
* @param pNumChars the number of characters in the buffer
* @return the remaining characters in the buffer
* @throws OceanusException on error
*/
private static int findLineFeedInBuffer(final char[] pBuffer,
final int pOffset,
final int pNumChars) throws OceanusException {
/* Loop through the buffer */
for (int i = pOffset; i < pNumChars; i++) {
/* Check for LF and NULL */
switch (pBuffer[i]) {
case ThemisAnalysisChar.LF:
return i;
case ThemisAnalysisChar.NULL:
throw new ThemisDataException("Null character in file");
default:
break;
}
}
/* Look for line feed in the buffer */
return -1;
}
/**
* Build line from buffer.
*
* @param pBuffer the character buffer
* @param pOffset the starting offset
* @param pLineFeed the location of the lineFeed
* @return the new line
*/
private static ThemisAnalysisLine buildLine(final char[] pBuffer,
final int pOffset,
final int pLineFeed) {
/* Strip any trailing cr */
int myLen = pLineFeed - pOffset;
if (myLen > 0 && pBuffer[pOffset + myLen - 1] == ThemisAnalysisChar.CR) {
myLen--;
}
/* Build the line */
return new ThemisAnalysisLine(pBuffer, pOffset, myLen);
}
/**
* Post-process the lines as first Pass.
*
* @param pLines the lines to process
* @throws OceanusException on error
*/
private void initialProcessingPass(final Deque<ThemisAnalysisElement> pLines) throws OceanusException {
/* Create the parser */
final ThemisAnalysisParser myParser = new ThemisAnalysisParser(pLines, theContents, this);
/* Loop through the lines */
while (myParser.hasLines()) {
/* Access next line */
ThemisAnalysisLine myLine = (ThemisAnalysisLine) myParser.popNextLine();
/* Process comments/blanks/package/imports */
boolean processed = myParser.processCommentsAndBlanks(myLine)
|| processPackage(myLine)
|| myParser.processImports(myLine);
/* If we haven't processed yet */
if (!processed) {
/* Process the class */
processed = myParser.processClass(myLine);
if (!processed) {
throw new ThemisDataException("Unexpected construct in file");
}
/* Process any trailing blanks/comments */
while (myParser.hasLines()) {
myLine = (ThemisAnalysisLine) myParser.popNextLine();
if (!myParser.processCommentsAndBlanks(myLine)) {
throw new ThemisDataException("Trailing data in file");
}
}
}
}
}
/**
* perform consolidation processing pass.
*
* @throws OceanusException on error
*/
void consolidationProcessingPass() throws OceanusException {
/* Consolidate classMap */
theDataMap.consolidateMap();
}
/**
* Perform final processing pass.
*
* @throws OceanusException on error
*/
void finalProcessingPass() throws OceanusException {
/* Resolve references */
theDataMap.resolveReferences();
/* Loop through the lines */
for (ThemisAnalysisElement myElement : theContents) {
/* If the element is a container */
if (myElement instanceof ThemisAnalysisContainer myContainer) {
/* Access and process the container */
myContainer.postProcessLines();
}
}
/* Report unknown items */
theDataMap.reportUnknown();
}
/**
* Process a potential import line.
*
* @param pLine the line
* @return have we processed the line?
* @throws OceanusException on error
*/
private boolean processPackage(final ThemisAnalysisLine pLine) throws OceanusException {
/* If this is a package line */
if (ThemisAnalysisPackage.isPackage(pLine)) {
/* Check that the package is correct named */
if (!thePackage.getPackage().equals(pLine.toString())) {
throw new ThemisDataException("Bad package");
}
/* Setup the file resources */
theDataMap.setUpFileResources();
/* Declare all the files in this package to the dataMap */
for (ThemisAnalysisFile myFile : thePackage.getFiles()) {
theDataMap.declareFile(myFile);
}
/* Process the package line */
theContents.add(thePackage);
/* Processed */
return true;
}
/* return false */
return false;
}
@Override
public String toString() {
return getName();
}
}