com.github.czyzby.lml.parser.impl.DefaultLmlTemplateReader Maven / Gradle / Ivy
package com.github.czyzby.lml.parser.impl;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.utils.Array;
import com.github.czyzby.kiwi.util.common.Strings;
import com.github.czyzby.kiwi.util.gdx.collection.GdxArrays;
import com.github.czyzby.kiwi.util.gdx.collection.pooled.PooledList;
import com.github.czyzby.lml.parser.LmlTemplateReader;
/** Standard template reader, working on plain char sequences (which mostly comes down to simple strings). Does not do
* buffered reading of template files which will be fine for most templates. However, if you do have enormous files to
* read or if this implementation causes significant performance penalties, consider using a custom template reader.
*
* @author MJ */
public class DefaultLmlTemplateReader implements LmlTemplateReader {
protected final PooledList sequencesQueue = new PooledList();
/** First sequence appended to the reader since the time it was empty. Basically, any time the reader is empty and
* gets a sequence, it will be set as the original one to determine current line number. */
protected CharSequenceEntry originalSequence;
protected CharSequenceEntry currentSequence;
@Override
public void append(final char[] template) {
if (template != null && template.length > 0) {
appendSequence(new String(template), null);
}
}
@Override
public void append(final String template) {
appendSequence(template, null);
}
@Override
public void append(final String template, final String templateName) {
appendSequence(template, templateName);
}
@Override
public void append(final FileHandle templateFile) {
appendSequence(templateFile.readString("UTF-8"), templateFile.name());
}
@Override
public void append(final CharSequence template) {
appendSequence(template, null);
}
@Override
public void append(final CharSequence template, final String templateName) {
appendSequence(template, templateName);
}
/** Actual appending method, referenced by all others.
*
* @param sequence should be appended to the reader and set as currently parsed sequence.
* @param name name of the template for debugging purposes. */
protected void appendSequence(final CharSequence sequence, final String name) {
if (Strings.isNotEmpty(sequence)) {
queueCurrentSequence();
setCurrentSequence(new CharSequenceEntry(sequence, name));
}
}
/** Adds current sequence with its character index to the queue. */
protected void queueCurrentSequence() {
if (currentSequence != null) {
sequencesQueue.addFirst(currentSequence);
}
}
/** @param sequence becomes currently parsed sequence. */
protected void setCurrentSequence(final CharSequenceEntry sequence) {
currentSequence = sequence;
if (sequence == null) {
originalSequence = null; // There are no more parsed sequences, so original is irrelevant.
} else if (originalSequence == null) {
// This is the first sequence during this parsing.
originalSequence = sequence;
}
}
@Override
public boolean hasNextCharacter() {
return currentSequence != null && currentSequence.hasNext() || isAnyQueuedSequenceNonEmpty();
}
/** @return true if any sequence on the queue is not empty. */
protected boolean isAnyQueuedSequenceNonEmpty() {
if (sequencesQueue.isEmpty()) {
return false;
}
for (final CharSequenceEntry sequence : sequencesQueue) {
if (sequence.hasNext()) {
return true;
}
}
return false;
}
@Override
public char nextCharacter() {
while (currentSequence.isEmpty()) {
getSequenceFromQueue();
}
final char character = currentSequence.nextChar();
if (character == '\n') {
currentSequence.incrementLine();
}
return character;
}
@Override
public char peekCharacter() {
return currentSequence.currentChar();
}
/** Dequeues last sequence, if any. */
protected void getSequenceFromQueue() {
setCurrentSequence(sequencesQueue.removeFirst());
}
@Override
public boolean hasNextCharacter(final int additionalIndexes) {
if (currentSequence == null) {
return false;
}
if (currentSequence.charsLeft() > additionalIndexes) {
// There are enough characters in the current sequence alone.
return true;
}
if (sequencesQueue.isEmpty()) {
// There aren't enough characters in the current sequence and there are no sequences left.
return false;
}
int indexesLeft = additionalIndexes - currentSequence.charsLeft();
for (final CharSequenceEntry sequence : sequencesQueue) {
indexesLeft -= sequence.charsLeft();
if (indexesLeft < 0) {
// Stored sequences had enough characters.
return true;
}
}
return false;
}
@Override
public char peekCharacter(final int additionalIndexes) {
if (currentSequence.charsLeft() > additionalIndexes) {
return currentSequence.peekChar(additionalIndexes);
}
int indexesLeft = additionalIndexes - currentSequence.charsLeft();
for (final CharSequenceEntry sequence : sequencesQueue) {
final int charactersLeft = sequence.charsLeft();
if (charactersLeft > indexesLeft) {
return sequence.peekChar(indexesLeft);
}
indexesLeft -= charactersLeft;
}
throw new IllegalStateException(
"Not enough characters left to peek value with: " + additionalIndexes + " additional indexes.");
}
@Override
public boolean startsWith(final CharSequence value) {
for (int index = 0, length = value.length(); index < length; index++) {
if (peekCharacter(index) != value.charAt(index)) {
return false;
}
}
return true;
}
@Override
public int getCurrentLine() {
return originalSequence == null ? 0 : originalSequence.getLine();
}
@Override
public int getCurrentSequenceLine() {
return currentSequence == null ? 0 : currentSequence.getLine();
}
@Override
public String getCurrentTemplateName() {
return originalSequence == null ? null : originalSequence.getName();
}
@Override
public String getCurrentSequenceName() {
return currentSequence == null ? null : currentSequence.getName();
}
@Override
public String getCurrentSequence() {
return currentSequence == null ? null : currentSequence.sequence.toString();
}
@Override
public String getOriginalSequence() {
return originalSequence == null ? null : originalSequence.sequence.toString();
}
@Override
public boolean isParsingOriginalTemplate() {
return currentSequence == originalSequence && originalSequence != null;
}
@Override
public void clear() {
currentSequence = null;
originalSequence = null;
sequencesQueue.clear();
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("Currently parsing: ");
appendDebugMessage(builder, originalSequence);
// Slow, but this is for debug only.
final Array sequences = GdxArrays.newArray(sequencesQueue);
for (int index = sequencesQueue.size() - 2; index >= 0; index--) {
builder.append("\nWhich spawned:");
appendDebugMessage(builder, sequences.get(index));
}
if (currentSequence != originalSequence) {
builder.append("\nWhich spawned:");
appendDebugMessage(builder, currentSequence);
}
return builder.toString();
}
private static void appendDebugMessage(final StringBuilder builder, final CharSequenceEntry sequence) {
builder.append('\n').append(sequence.name).append(": line ").append(sequence.getLine())
.append(" of template part: \n").append(sequence.sequence);
}
/** Data container for a single template part.
*
* @author MJ */
// This is preferred to keeping two separate queues for char sequences and indexes, since A) linked list creates
// nodes for its values anyway, so it's not like we're adding extra object allocation, B) there's no integer boxing,
// C) working with objects is much easier than managing a bunch of indexes, lengths and sequences "by hand" in the
// reader itself.
protected static class CharSequenceEntry {
private final CharSequence sequence;
private final String name;
private final int length;
private int index;
private int line = 1; // Counting lines from 1 is more natural to humans.
public CharSequenceEntry(final CharSequence sequence, final String name) {
this.sequence = sequence;
this.name = name;
length = sequence.length();
}
/** @return next character stored in sequence. Modifies iteration index. */
public char nextChar() {
return sequence.charAt(index++);
}
/** @return character currently pointed by the iteration index. */
public char currentChar() {
return sequence.charAt(index);
}
/** @param additionalIndexes temporarily modifies iteration index.
* @return character at the selected position. */
public char peekChar(final int additionalIndexes) {
return sequence.charAt(index + additionalIndexes);
}
/** @return amount of unparsed characters. */
public int charsLeft() {
return length - index;
}
/** @return true if the sequence has any characters left. */
public boolean hasNext() {
return index < length;
}
/** @return true if has no characters left. */
public boolean isEmpty() {
return index == length;
}
/** Increments lines count. */
public void incrementLine() {
line++;
}
/** @return currently parsed line of the sequence. */
public int getLine() {
return line;
}
/** @return name of the template. */
public String getName() {
return name;
}
@Override
public String toString() {
return sequence.toString();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy