ThemisAnalysisLine.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.ThemisAnalysisGeneric.ThemisAnalysisGenericBase;
import java.nio.CharBuffer;
import java.util.Deque;
/**
* Line buffer.
*/
public class ThemisAnalysisLine
implements ThemisAnalysisElement {
/**
* The token terminators.
*/
private static final char[] TERMINATORS = {
ThemisAnalysisChar.PARENTHESIS_OPEN,
ThemisAnalysisChar.PARENTHESIS_CLOSE,
ThemisAnalysisChar.GENERIC_OPEN,
ThemisAnalysisChar.COMMA,
ThemisAnalysisChar.SEMICOLON,
ThemisAnalysisChar.COLON,
ThemisAnalysisChar.ARRAY_OPEN
};
/**
* The line buffer.
*/
private final CharBuffer theBuffer;
/**
* The properties.
*/
private ThemisAnalysisProperties theProperties;
/**
* Constructor.
*
* @param pBuffer the buffer
* @param pOffset the offset to copy from
* @param pLen the length
*/
ThemisAnalysisLine(final char[] pBuffer,
final int pOffset,
final int pLen) {
/* create the buffer */
final char[] myBuffer = new char[pLen];
System.arraycopy(pBuffer, pOffset, myBuffer, 0, pLen);
theBuffer = CharBuffer.wrap(myBuffer);
/* Strip any leading/trailing whiteSpace */
stripLeadingWhiteSpace();
stripTrailingWhiteSpace();
/* Set null properties */
theProperties = ThemisAnalysisProperties.NULL;
}
/**
* Constructor.
*
* @param pBuffer the buffer
*/
ThemisAnalysisLine(final CharBuffer pBuffer) {
/* Store values */
theBuffer = pBuffer;
/* Set null properties */
theProperties = ThemisAnalysisProperties.NULL;
}
/**
* Constructor.
*
* @param pLine the line
*/
ThemisAnalysisLine(final ThemisAnalysisLine pLine) {
/* Store values */
final CharBuffer myBuffer = pLine.theBuffer;
theBuffer = myBuffer.subSequence(0, myBuffer.length());
/* Set null properties */
theProperties = ThemisAnalysisProperties.NULL;
}
/**
* Constructor.
*
* @param pLines the lines
*/
ThemisAnalysisLine(final Deque<ThemisAnalysisElement> pLines) {
/* Create a stringBuilder */
final StringBuilder myBuilder = new StringBuilder();
/* Loop through the lines */
for (ThemisAnalysisElement myLine : pLines) {
myBuilder.append(((ThemisAnalysisLine) myLine).theBuffer);
myBuilder.append(ThemisAnalysisChar.BLANK);
}
/* Create the buffer */
theBuffer = CharBuffer.wrap(myBuilder.toString());
stripLeadingWhiteSpace();
stripTrailingWhiteSpace();
/* Set null properties */
theProperties = ThemisAnalysisProperties.NULL;
}
/**
* Obtain the length.
*
* @return the length
*/
public int getLength() {
return theBuffer.remaining();
}
/**
* Set the new length.
*
* @param pLen the length
*/
private void setLength(final int pLen) {
theBuffer.limit(theBuffer.position() + pLen);
}
/**
* Adjust the position.
*
* @param pAdjust the adjustment
*/
private void adjustPosition(final int pAdjust) {
theBuffer.position(theBuffer.position() + pAdjust);
}
/**
* Obtain the character at the given position.
*
* @param pIndex the position of the character
* @return the character
*/
char charAt(final int pIndex) {
return theBuffer.charAt(pIndex);
}
/**
* Obtain the properties.
*
* @return the properties
*/
public ThemisAnalysisProperties getProperties() {
return theProperties;
}
/**
* Strip trailing comments.
*
* @throws OceanusException on error
*/
void stripTrailingComments() throws OceanusException {
/* Loop through the characters */
final int myLength = getLength();
int mySkipped = 0;
for (int i = 0; i < myLength - mySkipped - 1; i++) {
/* Access position and current character */
final int myPos = i + mySkipped;
final char myChar = theBuffer.charAt(myPos);
/* If this is a single/double quote */
if (myChar == ThemisAnalysisChar.SINGLEQUOTE
|| myChar == ThemisAnalysisChar.DOUBLEQUOTE) {
final int myEnd = findEndOfQuotedSequence(myPos);
mySkipped += myEnd - myPos;
/* If we have a line comment */
} else if (myChar == ThemisAnalysisChar.COMMENT
&& theBuffer.charAt(myPos + 1) == ThemisAnalysisChar.COMMENT) {
/* Reset the length */
setLength(myPos);
stripTrailingWhiteSpace();
break;
}
}
}
/**
* Trim leading whiteSpace.
*/
void stripLeadingWhiteSpace() {
/* Loop through the characters */
int myWhiteSpace = 0;
final int myLength = getLength();
for (int i = 0; i < myLength; i++) {
/* Break loop if not whiteSpace */
if (!Character.isWhitespace(theBuffer.charAt(i))) {
break;
}
/* Increment count */
myWhiteSpace++;
}
/* Adjust position */
adjustPosition(myWhiteSpace);
}
/**
* Trim trailing whiteSpace.
*/
private void stripTrailingWhiteSpace() {
/* Loop through the characters */
int myWhiteSpace = 0;
final int myLength = getLength();
for (int i = myLength - 1; i >= 0; i--) {
/* Break loop if not whiteSpace */
if (!Character.isWhitespace(theBuffer.charAt(i))) {
break;
}
/* Increment count */
myWhiteSpace++;
}
/* Adjust length */
setLength(myLength - myWhiteSpace);
}
/**
* Mark the line.
*/
void mark() {
theBuffer.mark();
}
/**
* Reset the line.
*/
void reset() {
theBuffer.reset();
}
/**
* Strip Modifiers.
*
* @throws OceanusException on error
*/
void stripModifiers() throws OceanusException {
/* Loop while we find a modifier */
boolean bContinue = true;
while (bContinue) {
/* If we have a generic variable list */
if (ThemisAnalysisGeneric.isGeneric(this)) {
/* Declare them to the properties */
theProperties = theProperties.setGenericVariables(new ThemisAnalysisGenericBase(this));
}
/* Access the next token */
final String nextToken = peekNextToken();
bContinue = false;
/* If this is a modifier */
final ThemisAnalysisModifier myModifier = ThemisAnalysisModifier.findModifier(nextToken);
if (myModifier != null) {
/* Set modifier */
theProperties = theProperties.setModifier(myModifier);
stripStartSequence(nextToken);
bContinue = true;
}
}
}
/**
* Does line start with identifier?
*
* @param pIdentifier the identifier
* @return true/false
* @throws OceanusException on error
*/
boolean isStartedBy(final String pIdentifier) throws OceanusException {
/* If the line is too short, just return */
final int myIdLen = pIdentifier.length();
final int myLength = getLength();
if (myIdLen > myLength) {
return false;
}
/* Loop through the identifier */
for (int i = 0; i < myIdLen; i++) {
if (theBuffer.charAt(i) != pIdentifier.charAt(i)) {
return false;
}
}
/* Catch any solo modifiers */
if (myIdLen == myLength) {
throw new ThemisDataException("Modifier found without object");
}
/* The next character must be whitespace */
if (Character.isWhitespace(theBuffer.charAt(myIdLen))) {
adjustPosition(myIdLen + 1);
stripLeadingWhiteSpace();
return true;
}
/* False alarm */
return false;
}
/**
* Strip NextToken.
*
* @return the next token
*/
String stripNextToken() {
/* Access the next token */
final String myToken = peekNextToken();
stripStartSequence(myToken);
return myToken;
}
/**
* Peek NextToken.
*
* @return the next token
*/
String peekNextToken() {
/* Loop through the buffer */
final int myLength = getLength();
for (int i = 0; i < myLength; i++) {
/* if we have hit whiteSpace or a terminator */
final char myChar = theBuffer.charAt(i);
if (isTerminator(myChar)) {
/* Strip out the characters */
final CharBuffer myToken = theBuffer.subSequence(0, i);
return myToken.toString();
}
}
/* Whole buffer is the token */
return toString();
}
/**
* Strip NextToken.
*
* @return the next token
*/
String stripLastToken() {
/* Access the next token */
final String myToken = peekLastToken();
stripEndSequence(myToken);
return myToken;
}
/**
* Peek lastToken.
*
* @return the last token
*/
String peekLastToken() {
/* Loop through the buffer */
final int myLength = getLength();
for (int i = myLength - 1; i >= 0; i--) {
/* if we have hit whiteSpace or a terminator */
final char myChar = theBuffer.charAt(i);
if (isTerminator(myChar)) {
/* Strip out the characters */
final CharBuffer myToken = theBuffer.subSequence(i + 1, myLength);
return myToken.toString();
}
}
/* Whole buffer is the token */
return toString();
}
/**
* Is the character a token terminator?
*
* @param pChar the character
* @return true/false
*/
private static boolean isTerminator(final char pChar) {
/* WhiteSpace is a terminator */
if (Character.isWhitespace(pChar)) {
return true;
}
/* Check whether char is any of the terminators */
return isInList(pChar, TERMINATORS);
}
/**
* Is the character in the list?
*
* @param pChar the character
* @param pList the list of characters
* @return true/false
*/
private static boolean isInList(final char pChar,
final char[] pList) {
/* Loop through the list */
for (char myChar : pList) {
/* if we have matched */
if (pChar == myChar) {
return true;
}
}
/* Not a terminator */
return false;
}
/**
* Strip data up to position.
*
* @param pPosition the position to strip to (inclusive)
* @return the stripped line
*/
ThemisAnalysisLine stripUpToPosition(final int pPosition) {
/* Obtain the new buffer */
final CharBuffer myChars = theBuffer.subSequence(0, pPosition + 1);
adjustPosition(pPosition + 1);
stripLeadingWhiteSpace();
return new ThemisAnalysisLine(myChars);
}
/**
* Does line start with the sequence?
*
* @param pSequence the sequence
* @return true/false
*/
boolean startsWithSequence(final CharSequence pSequence) {
/* If the line is too short, just return */
final int mySeqLen = pSequence.length();
final int myLength = getLength();
if (mySeqLen > myLength) {
return false;
}
/* Loop through the sequence */
for (int i = 0; i < mySeqLen; i++) {
if (theBuffer.charAt(i) != pSequence.charAt(i)) {
return false;
}
}
/* True */
return true;
}
/**
* Does line start with the character?
*
* @param pChar the character
* @return true/false
*/
boolean startsWithChar(final char pChar) {
/* If the line is too short, just return false */
final int myLength = getLength();
if (myLength == 0) {
return false;
}
/* Test the character */
return theBuffer.charAt(0) == pChar;
}
/**
* Strip the starting sequence.
*
* @param pSequence the sequence
*/
void stripStartSequence(final CharSequence pSequence) {
/* If the line starts with the sequence */
if (startsWithSequence(pSequence)) {
/* adjust the length */
adjustPosition(pSequence.length());
stripLeadingWhiteSpace();
}
}
/**
* Strip the starting character.
*
* @param pChar the character
*/
void stripStartChar(final char pChar) {
/* If the line starts with the character */
if (startsWithChar(pChar)) {
/* adjust the length */
adjustPosition(1);
stripLeadingWhiteSpace();
}
}
/**
* Does line end with the sequence?
*
* @param pSequence the sequence
* @return true/false
*/
boolean endsWithSequence(final CharSequence pSequence) {
/* If the line is too short, just return */
final int mySeqLen = pSequence.length();
final int myLength = getLength();
if (mySeqLen > myLength) {
return false;
}
/* Loop through the buffer */
final int myBase = myLength - mySeqLen;
for (int i = 0; i < mySeqLen; i++) {
/* Loop through the sequence */
if (theBuffer.charAt(i + myBase) != pSequence.charAt(i)) {
/* Not found */
return false;
}
}
/* found */
return true;
}
/**
* Does line end with the character?
*
* @param pChar the character
* @return true/false
*/
boolean endsWithChar(final char pChar) {
/* If the line is too short, just return false */
final int myLength = getLength();
if (myLength == 0) {
return false;
}
/* Test the character */
return theBuffer.charAt(myLength - 1) == pChar;
}
/**
* Strip the starting sequence.
*
* @param pSequence the sequence
*/
void stripEndSequence(final CharSequence pSequence) {
/* If the line ends with the sequence */
if (endsWithSequence(pSequence)) {
/* adjust the length */
setLength(getLength() - pSequence.length());
stripTrailingWhiteSpace();
}
}
/**
* Strip the ending character.
*
* @param pChar the character
*/
void stripEndChar(final char pChar) {
/* If the line ends with the sequence */
if (endsWithChar(pChar)) {
/* adjust the length */
setLength(getLength() - 1);
stripTrailingWhiteSpace();
}
}
/**
* Find end of nested sequence, allowing for escaped quotes.
* <p>
* To enable distinction between finding the end of the sequence from still being nested, the nestLevel
* is negative. Hence a result that is negative indicates that the sequence is continuing.
* </p>
*
* @param pStart the start position
* @param pLevel the current nestLevel (negative value)
* @param pTerm the end nest character
* @param pNest the start nest character
* @return the position of the end of the nest if (non-negative), or nestLevel (negative) if not terminated.
* @throws OceanusException on error
*/
int findEndOfNestedSequence(final int pStart,
final int pLevel,
final char pTerm,
final char pNest) throws OceanusException {
/* Access details of quote */
final int myLength = getLength();
/* Loop through the line */
boolean maybeComment = false;
int myNested = pLevel;
int mySkipped = 0;
for (int i = pStart; i < myLength - mySkipped; i++) {
/* Access position and current character */
final int myPos = i + mySkipped;
final char myChar = theBuffer.charAt(myPos);
/* If this is the comment character */
if (myChar == ThemisAnalysisChar.COMMENT) {
/* Flip flag */
maybeComment = !maybeComment;
/* If we have double comment, break loop */
if (!maybeComment) {
break;
}
} else {
maybeComment = false;
}
/* If this is a single/double quote */
if (myChar == ThemisAnalysisChar.SINGLEQUOTE
|| myChar == ThemisAnalysisChar.DOUBLEQUOTE) {
/* Find the end of the sequence and skip the quotes */
final int myEnd = findEndOfQuotedSequence(myPos);
mySkipped += myEnd - myPos;
/* If we should be increasing the nest level */
} else if (myChar == pNest) {
myNested--;
/* If we should be decreasing the nest level */
} else if (myChar == pTerm) {
myNested++;
/* Return current position if we have finished */
if (myNested == 0) {
return myPos;
}
}
}
/* Return the new nest level */
return myNested;
}
/**
* Find end of single/double quoted sequence, allowing for escaped quote.
* <p>
* Note that a quoted sequence cannot span lines.
* </p>
*
* @param pStart the start position of the quote
* @return the end position of the sequence.
* @throws OceanusException on error
*/
int findEndOfQuotedSequence(final int pStart) throws OceanusException {
/* Access details of single/double quote */
final int myLength = getLength();
final char myQuote = theBuffer.charAt(pStart);
/* Loop through the characters */
int mySkipped = 0;
for (int i = pStart + 1; i < myLength - mySkipped; i++) {
/* Access position and current character */
final int myPos = i + mySkipped;
final char myChar = theBuffer.charAt(myPos);
/* Skip escaped character */
if (myChar == ThemisAnalysisChar.ESCAPE) {
mySkipped++;
/* Return current position if we have finished */
} else if (myChar == myQuote) {
return myPos;
}
}
/* We should always be terminated */
throw new ThemisDataException("Unable to find end of quote in line");
}
@Override
public String toString() {
return theBuffer.toString();
}
}