All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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