Package org.thymeleaf.templateparser

Source Code of org.thymeleaf.templateparser.TemplatePreprocessingReader

/*
* =============================================================================
*
*   Copyright (c) 2011-2014, The THYMELEAF team (http://www.thymeleaf.org)
*
*   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 org.thymeleaf.templateparser;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.nio.CharBuffer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.exceptions.TemplateInputException;


/**
*
* @author Daniel Fernández
*
* @since 2.0.7
*
*/
public final class TemplatePreprocessingReader extends Reader {

   
    private static final Logger readerLogger = LoggerFactory.getLogger(TemplatePreprocessingReader.class);

    private static final int BUFFER_BLOCK_SIZE = 1024;
    private static final int OVERFLOW_BLOCK_SIZE = 2048;

    public static final char CHAR_ENTITY_START_SUBSTITUTE = '\uFFF8';

   
    private static final char CHAR_OPTIONAL_WHITESPACE_WILDCARD = '\u0358';
    private static final char CHAR_WHITESPACE_WILDCARD = '\u0359';
    private static final char CHAR_ALPHANUMERIC_WILDCARD = '\u0360';
    private static final char CHAR_ANY_WILDCARD = '\u0361';

   
    private static final char[] LOWER_CHARS =
            ("[]<>!?=-_.,:;+*()&/%$\"'@#~^ \t\n\rabcdefghijklmnopqrstuvwxyz" +
            String.valueOf(CHAR_OPTIONAL_WHITESPACE_WILDCARD) + String.valueOf(CHAR_WHITESPACE_WILDCARD) +
            String.valueOf(CHAR_ALPHANUMERIC_WILDCARD) + String.valueOf(CHAR_ANY_WILDCARD)).toCharArray();
    private static final char[] UPPER_CHARS =
            ("[]<>!?=-_.,:;+*()&/%$\"'@#~^ \t\n\rABCDEFGHIJKLMNOPQRSTUVWXYZ" +
            String.valueOf(CHAR_OPTIONAL_WHITESPACE_WILDCARD) + String.valueOf(CHAR_WHITESPACE_WILDCARD) +
            String.valueOf(CHAR_ALPHANUMERIC_WILDCARD) + String.valueOf(CHAR_ANY_WILDCARD)).toCharArray();

   
   
    private static final int[] COMMENT_START = convertToIndexes("<!--".toCharArray());
    private static final int[] COMMENT_END = convertToIndexes("-->".toCharArray());
    private static final int[] ENTITY = convertToIndexes(('&' + String.valueOf(CHAR_ALPHANUMERIC_WILDCARD) + ';').toCharArray());
    private static final int[] DOCTYPE =
            convertToIndexes(("<!DOCTYPE" + String.valueOf(CHAR_WHITESPACE_WILDCARD+ String.valueOf(CHAR_ANY_WILDCARD) + ">").toCharArray());
    private static final int[] XML_PROLOG =
            convertToIndexes(("<?xml" + String.valueOf(CHAR_WHITESPACE_WILDCARD+ String.valueOf(CHAR_ANY_WILDCARD) + "?>").toCharArray());

    private static final int[] PROTOTYPE_ONLY_COMMENT_START = convertToIndexes("<!--/*/".toCharArray());
    private static final int[] PROTOTYPE_ONLY_COMMENT_END = convertToIndexes("/*/-->".toCharArray());

    private static final int[] PARSER_LEVEL_COMMENT_START = convertToIndexes("<!--/*".toCharArray());
    private static final int[] PARSER_LEVEL_COMMENT_END = convertToIndexes("*/-->".toCharArray());


    private static final char[] NORMALIZED_DOCTYPE_PREFIX = "<!DOCTYPE ".toCharArray();
    private static final char[] NORMALIZED_DOCTYPE_PUBLIC = "PUBLIC ".toCharArray();
    private static final char[] NORMALIZED_DOCTYPE_SYSTEM = "SYSTEM ".toCharArray();
   
   
    private static final char[] ENTITY_START_SUBSTITUTE_CHAR_ARRAY = new char[] { CHAR_ENTITY_START_SUBSTITUTE };

   
    public static final String SYNTHETIC_ROOT_ELEMENT_NAME = "THYMELEAF_ROOT";
    private static final char[] SYNTHETIC_ROOT_ELEMENT_START_CHARS = ("<" + SYNTHETIC_ROOT_ELEMENT_NAME + ">").toCharArray();
    private static final char[] SYNTHETIC_ROOT_ELEMENT_END_CHARS = ("</" + SYNTHETIC_ROOT_ELEMENT_NAME + ">").toCharArray();
   

    private final Reader innerReader;
    private final BufferedReader bufferedReader;
    private final boolean addSyntheticRootElement;
   
    private char[] buffer;
    private char[] overflow;
    private int overflowIndex;
   
    private boolean inComment = false;
    private boolean inParserLevelComment = false;
    private boolean docTypeClauseRead = false;
    private boolean xmlPrologRead = false;
    private int xmlPrologRemaining = -1;
    private boolean syntheticRootElementOpeningProcessed = false;
    private boolean syntheticRootElementClosingSent = false;
    private int rootElementClosingOffset = 0;
   
    private boolean noMoreToRead = false;
   
    private String docTypeClause = null;
   


   
   
    private static int[] convertToIndexes(final char[] chars) {
       
        final int charsLen = chars.length;
        final int[] result = new int[charsLen];
        for (int i = 0; i < charsLen; i++) {
            final char c = chars[i];
            boolean found = false;
            for (int j = 0; !found && j < UPPER_CHARS.length; j++) {
                if (UPPER_CHARS[j] == c) {
                    result[i] = j;
                    found = true;
                }
            }
            for (int j = 0; !found && j < LOWER_CHARS.length; j++) {
                if (LOWER_CHARS[j] == c) {
                    result[i] = j;
                    found = true;
                }
            }
            if (!found) {
                throw new RuntimeException(
                        "Cannot convert to index character: '" + c + "' (value: " + ((int)c) + ")");
            }
        }
        return result;
       
    }
   
   
   
    /*
     *
     * TODO Add exceptions for not substituting anything inside [[...]]
     *
     */


   
    public TemplatePreprocessingReader(final Reader in, final int bufferSize) {
        this(in, bufferSize, true);
    }


    /**
     *
     * @since 2.0.11
     */
    public TemplatePreprocessingReader(final Reader in, final int bufferSize, final boolean addSyntheticRootElement) {
        super();
        this.innerReader = in;
        this.bufferedReader = new BufferedReader(this.innerReader, bufferSize);
        this.buffer = new char[bufferSize + BUFFER_BLOCK_SIZE];
        this.overflow = new char[bufferSize + OVERFLOW_BLOCK_SIZE];
        this.overflowIndex = 0;
        this.addSyntheticRootElement = addSyntheticRootElement;
    }

   
   

    @Override
    public int read(final char[] cbuf, final int off, final int len) throws IOException {

        if (readerLogger.isTraceEnabled()) {
            readerLogger.trace("[THYMELEAF][TEMPLATEPREPROCESSINGREADER][{}] CALLING read(char[], {}, {})",
                    new Object[] {TemplateEngine.threadIndex(),Integer.valueOf(off), Integer.valueOf(len)});
        }
       
        if ((len * 2) > this.overflow.length) {
            // Resize buffer and overflow
           
            this.buffer = new char[len + BUFFER_BLOCK_SIZE];
            final char[] newOverflow = new char[len + OVERFLOW_BLOCK_SIZE];
            System.arraycopy(this.overflow, 0, newOverflow, 0, this.overflowIndex);
            this.overflow = newOverflow;
        }
       
       
        int bufferSize = 0;
        if (this.overflowIndex > 0) {
           
            final int copied =
                copyToResult(
                        this.overflow, 0, this.overflowIndex,
                        this.buffer, 0, this.buffer.length);
           
            bufferSize += copied;
           
            if (readerLogger.isTraceEnabled()) {
                readerLogger.trace("[THYMELEAF][TEMPLATEPREPROCESSINGREADER][{}] READ FROM OVERFLOW BUFFER {} Some content from the overflow buffer has been copied into results.",
                        new Object[] {TemplateEngine.threadIndex(), Integer.valueOf(copied)});
            }
           
        }
       
       
       
        char[] overflowOverflow = null;
        if (this.overflowIndex > 0) {
            // Overflow did not entirely fit into buffer, and some overflow
            // had to be relocated. This overflow will have to be placed at the end of the
            // overflow generated in the current call (if any).
           
            overflowOverflow = new char[this.overflowIndex];
            System.arraycopy(this.overflow, 0, overflowOverflow, 0, this.overflowIndex);
           
            if (readerLogger.isTraceEnabled()) {
                readerLogger.trace("[THYMELEAF][TEMPLATEPREPROCESSINGREADER][{}] RELLOCATED SOME OVERFLOW CONTENTS, WAITING TO BE ADDED TO RESULT/NEW OVERFLOW {} Some content was remaining at the overflow buffer and will have to be rellocated.",
                        new Object[] {TemplateEngine.threadIndex(), Integer.valueOf(this.overflowIndex)});
            }
           
            this.overflowIndex = 0;
           
        }

       
       
        if (!this.noMoreToRead && bufferSize < this.buffer.length) {
            // Buffer was not filled up with content from overflow, so ask for more content
           
            final int toBeRead = this.buffer.length - bufferSize;
            int reallyRead = this.bufferedReader.read(this.buffer, bufferSize, toBeRead);

            if (this.addSyntheticRootElement && !this.syntheticRootElementClosingSent && (reallyRead < 0)) {
                // If there is no more content to be read from the source reader, close the synthetic
                // root element and make it look like it was read from source.

                final int closingSizeToInsert =
                    Math.min(
                            (SYNTHETIC_ROOT_ELEMENT_END_CHARS.length - this.rootElementClosingOffset),
                            (this.buffer.length - bufferSize));
                reallyRead =
                    copyToResult(
                            SYNTHETIC_ROOT_ELEMENT_END_CHARS, this.rootElementClosingOffset, closingSizeToInsert,
                            this.buffer, bufferSize, this.buffer.length);
               
                this.rootElementClosingOffset += reallyRead;

                if (this.rootElementClosingOffset >= SYNTHETIC_ROOT_ELEMENT_END_CHARS.length) {
                    this.syntheticRootElementClosingSent = true;
                }
               
            }
           
            if (readerLogger.isTraceEnabled()) {
                readerLogger.trace("[THYMELEAF][TEMPLATEPREPROCESSINGREADER][{}] READ FROM SOURCE {} A read operation was executed on the source reader (max chars requested: {}).",
                        new Object[] {TemplateEngine.threadIndex(), Integer.valueOf(reallyRead), Integer.valueOf(toBeRead)});
            }
           
            if (reallyRead < 0) {

               
                if (bufferSize == 0) {

                   
                    if (readerLogger.isTraceEnabled()) {
                        readerLogger.trace("[THYMELEAF][TEMPLATEPREPROCESSINGREADER][{}] RETURN {} After trying to read from input: No input left, no buffer left.",
                                new Object[] {TemplateEngine.threadIndex(), Integer.valueOf(reallyRead)});
                    }
                   
                    return reallyRead;
                   
                }
               
                this.noMoreToRead = true;
               
            } else {
               
                bufferSize += reallyRead;
               
            }
           
        }

       
       
        if (this.noMoreToRead && bufferSize == 0) {
            // Nothing left to do. Just return -1

            if (readerLogger.isTraceEnabled()) {
                readerLogger.trace("[THYMELEAF][TEMPLATEPREPROCESSINGREADER][{}] RETURN -1 Reader was already marked to be finished. No more input, no more buffer.",
                        new Object[] {TemplateEngine.threadIndex()});
            }
           
            return -1;
        }
       
       

        int totalRead = 0;
        int cbufi = off;
        final int last = off + len;
       
        int buffi = 0;
        while (cbufi < last && buffi < bufferSize) {


            if (this.buffer[buffi] == '\uFEFF') {
                // This is a Byte Order Mark (BOM) symbol. We are working at a character level already (as opposed to
                // a byte level), so we should be fine just ignoring it.
                buffi++;
                continue;
            }

           
            /*
             * Process XML_PROLOG (if needed)
             *
             * Processing it specifically before any other thing ensures the synthetic
             * root element will never be inserted before it.
             */
            if (!this.docTypeClauseRead && !this.xmlPrologRead && !this.inParserLevelComment) {

                if (this.xmlPrologRemaining >= 0) {
                    // We have still some bytes remaining from the XML PROLOG, and we
                    // don't want the reader to mistake them for text and input the synthetic
                    // root element right now
                   
                    if (readerLogger.isTraceEnabled()) {
                        readerLogger.trace("[THYMELEAF][TEMPLATEPREPROCESSINGREADER][{}] Writing remaining byte from incompletely output XML PROLOG: {}",
                                new Object[] {TemplateEngine.threadIndex(), String.valueOf(this.buffer[buffi])});
                    }
                   
                    cbuf[cbufi++] = this.buffer[buffi++];
                    totalRead++;
                    this.xmlPrologRemaining--;
                    continue;
       
                }
               
                final int matchedXmlProlog =
                        match(XML_PROLOG, 0, XML_PROLOG.length, this.buffer, buffi, bufferSize);
               
                if (matchedXmlProlog > 0) {

                    this.xmlPrologRemaining = matchedXmlProlog;
                   
                    final int copied =
                        copyToResult(
                                this.buffer, buffi, matchedXmlProlog,
                                cbuf, cbufi, last);

                    this.xmlPrologRemaining -= copied;
                    if (this.xmlPrologRemaining <= 0) {
                        this.xmlPrologRead = true;
                    }

                    cbufi += copied;
                    totalRead += copied;
                    buffi += matchedXmlProlog;
                    continue;
                   
                }
               
            }

           
            /*
             * Process DOCTYPE (if needed)
             */
            if (!this.docTypeClauseRead && !this.inParserLevelComment) {
               
                final int matchedDocType =
                        match(DOCTYPE, 0, DOCTYPE.length, this.buffer, buffi, bufferSize);
               
                if (matchedDocType > 0) {
                   
                    this.docTypeClause = new String(this.buffer, buffi, matchedDocType);
                    this.docTypeClauseRead = true;
                   
                    final char[] normalizedDocType =
                            normalizeDocTypeClause(this.buffer, buffi, matchedDocType);
                   
                    if (readerLogger.isTraceEnabled()) {
                        readerLogger.trace("[THYMELEAF][TEMPLATEPREPROCESSINGREADER][{}] Normalized DOCTYPE clause: {}",
                                new Object[] {TemplateEngine.threadIndex(), new String(normalizedDocType)});
                    }
                   
                    int copied = -1;
                    if (this.addSyntheticRootElement && !this.syntheticRootElementOpeningProcessed) {
                        // If DOCTYPE is processed, we will inject the synthetic root
                        // element just after the DOCTYPE so that we avoid problems with
                        // DOCTYPE clause being bigger than 'len' argument in the first 'read()' call.
                       
                        final char[] normalizedDocTypePlusSyntheticRootElement =
                                new char[normalizedDocType.length + SYNTHETIC_ROOT_ELEMENT_START_CHARS.length];
                        System.arraycopy(normalizedDocType, 0, normalizedDocTypePlusSyntheticRootElement, 0, normalizedDocType.length);
                        System.arraycopy(SYNTHETIC_ROOT_ELEMENT_START_CHARS, 0, normalizedDocTypePlusSyntheticRootElement, normalizedDocType.length, SYNTHETIC_ROOT_ELEMENT_START_CHARS.length);
                       
                        if (readerLogger.isTraceEnabled()) {
                            readerLogger.trace("[THYMELEAF][TEMPLATEPREPROCESSINGREADER][{}] Synthetic root element will be output along with DOCTYPE clause: '{}'",
                                    new Object[] {TemplateEngine.threadIndex(), new String(normalizedDocTypePlusSyntheticRootElement)});
                        }
                       
                        copied =
                                copyToResult(
                                        normalizedDocTypePlusSyntheticRootElement, 0, normalizedDocTypePlusSyntheticRootElement.length,
                                        cbuf, cbufi, last);
                       
                        this.syntheticRootElementOpeningProcessed = true;
                       
                    } else {
                       
                       
                        if (readerLogger.isTraceEnabled()) {
                            readerLogger.trace("[THYMELEAF][TEMPLATEPREPROCESSINGREADER][{}] DOCTYPE clause will be output, without synthetic root element: '{}'",
                                    new Object[] {TemplateEngine.threadIndex(), new String(normalizedDocType)});
                        }

                        copied =
                                copyToResult(
                                        normalizedDocType, 0, normalizedDocType.length,
                                        cbuf, cbufi, last);
                       
                    }
                   
                   
                    cbufi += copied;
                    totalRead += copied;
                    buffi += matchedDocType;
                    continue;
                   
                }
               
            }

            /*
             * ------------------
             * CHECK FOR PROTOTYPE-ONLY COMMENT BLOCKS
             * ------------------
             */

            // Completely ignored if inside comment
            final int matchedStartOfPrototypeOnlyComment =
                    (this.inComment || this.inParserLevelComment?
                            -2 : match(PROTOTYPE_ONLY_COMMENT_START, 0, PROTOTYPE_ONLY_COMMENT_START.length, this.buffer, buffi, bufferSize));

            if (matchedStartOfPrototypeOnlyComment > 0) {

                // no changes to "cbufi", as nothing is copied into result
                // no changes to "totalRead", as nothing is copied into result
                buffi += matchedStartOfPrototypeOnlyComment; // We just skip all characters in comment start
                continue;

            }


            // Completely ignored if inside comment, in contrast with "normal" comment end
            final int matchedEndOfPrototypeOnlyComment =
                    (this.inComment || this.inParserLevelComment?
                            -2 : match(PROTOTYPE_ONLY_COMMENT_END, 0, PROTOTYPE_ONLY_COMMENT_END.length, this.buffer, buffi, bufferSize));

            if (matchedEndOfPrototypeOnlyComment > 0) {

                // no changes to "cbufi", as nothing is copied into result
                // no changes to "totalRead", as nothing is copied into result
                buffi += matchedEndOfPrototypeOnlyComment; // We just skip all characters in comment end
                continue;

            }



            /*
             * ------------------
             * CHECK FOR PARSER-LEVEL COMMENT BLOCKS
             * ------------------
             */

            // Completely ignored if inside comment
            final int matchedStartOfParserLevelComment =
                    (this.inComment || this.inParserLevelComment?
                            -2 : match(PARSER_LEVEL_COMMENT_START, 0, PARSER_LEVEL_COMMENT_START.length, this.buffer, buffi, bufferSize));

            if (matchedStartOfParserLevelComment > 0) {

                // no changes to "cbufi", as nothing is copied into result
                // no changes to "totalRead", as nothing is copied into result
                buffi += matchedStartOfParserLevelComment; // We skip all characters in comment start
                this.inParserLevelComment = true;
                continue;

            }


            // Completely ignored if inside comment, in contrast with "normal" comment end
            final int matchedEndOfParserLevelComment =
                    (this.inParserLevelComment ?
                            match(PARSER_LEVEL_COMMENT_END, 0, PARSER_LEVEL_COMMENT_END.length, this.buffer, buffi, bufferSize) : -2);

            if (matchedEndOfParserLevelComment > 0) {

                // no changes to "cbufi", as nothing is copied into result
                // no changes to "totalRead", as nothing is copied into result
                buffi += matchedEndOfParserLevelComment; // We skip all characters in comment end
                this.inParserLevelComment = false;
                continue;

            }



            /*
             * ------------------
             * CHECK FOR NORMAL COMMENT BLOCKS (which should change the "inComment" flag)
             * ------------------
             */

            final int matchedStartOfComment =
                    (this.inComment || this.inParserLevelComment?
                            -2 : match(COMMENT_START, 0, COMMENT_START.length, this.buffer, buffi, bufferSize));

            if (matchedStartOfComment > 0) {

                this.inComment = true;
                final int copied =
                        copyToResult(
                                this.buffer, buffi, matchedStartOfComment,
                                cbuf, cbufi, last);
                cbufi += copied;
                totalRead += copied;
                buffi += matchedStartOfComment;
                continue;

            }

           
            final int matchedEndOfComment =
                    (this.inComment?
                            match(COMMENT_END, 0, COMMENT_END.length, this.buffer, buffi, bufferSize) : -2);
           
            if (matchedEndOfComment > 0) {
               
                this.inComment = false;
                final int copied =
                    copyToResult(
                            this.buffer, buffi, matchedEndOfComment,
                            cbuf, cbufi, last);
                cbufi += copied;
                totalRead += copied;
                buffi += matchedEndOfComment;
                continue;
               
            }



            /*
             * ------------------
             * CHECK FOR ENTITIES(& sign will be replaced in order to avoid parser behaviours)
             * ------------------
             */

            final int matchedEntity =
                (this.inComment || this.inParserLevelComment?
                        -2 : match(ENTITY, 0, ENTITY.length, this.buffer, buffi, bufferSize));
           
            if (matchedEntity > 0) {
               
                final int copied =
                    copyToResult(
                            ENTITY_START_SUBSTITUTE_CHAR_ARRAY, 0, ENTITY_START_SUBSTITUTE_CHAR_ARRAY.length,
                            cbuf, cbufi, last);
                cbufi += copied;
                totalRead += copied;
                buffi += 1; // Only one character is substituted (&)
                continue;
               
            }



            /*
             * ------------------
             * ADD A SYNTHETIC ROOT ELEMENT IF NEEDED (e.g. in order to allow several-root-node documents)
             * ------------------
             */

            if (!Character.isWhitespace(this.buffer[buffi]) && this.addSyntheticRootElement && !this.syntheticRootElementOpeningProcessed && !this.inComment && !this.inParserLevelComment) {
                // This block will be reached if we did not have to process a
                // DOCTYPE clause (because the DOCTYPE would have
                // matched the previous block). And will not be affected by any whitespaces
                // or comments before DOCTYPE because of the !Character.isWhitespace condition.
                   
                final int copied =
                    copyToResult(
                            SYNTHETIC_ROOT_ELEMENT_START_CHARS, 0, SYNTHETIC_ROOT_ELEMENT_START_CHARS.length,
                            cbuf, cbufi, last);
               
                cbufi += copied;
                totalRead += copied;
               
                this.syntheticRootElementOpeningProcessed = true;
               
                continue;
               
            }


            /*
             * ------------------
             * NOTHING ELSE TO CHECK, JUST COPY OUTPUT (if not in parser-level comment)
             * ------------------
             */

            if (!this.inParserLevelComment) {
                cbuf[cbufi++] = this.buffer[buffi++];
                totalRead++;
            } else {
                buffi++;
            }


        }

       
        if (buffi < bufferSize) {
            // Copy remaining buffer to overflow
            final int toBeOverFlowed = bufferSize - buffi;
            final int copied =
                copyToResult(
                        this.buffer, buffi, toBeOverFlowed,
                        cbuf, cbufi, last);
            // copied must be zero
            if (copied != 0) {
                throw new TemplateInputException("Overflow was not correctly computed!");
            }
        }
       
       
        if (overflowOverflow != null) {
           
            final int copied =
                copyToResult(
                        overflowOverflow, 0, overflowOverflow.length,
                        cbuf, cbufi, last);
           
            // copied must be zero
            if (copied != 0) {
                throw new TemplateInputException("Overflow-overflow was not correctly computed!");
            }
           
        }


        if (readerLogger.isTraceEnabled()) {
            final char[] result = new char[totalRead];
            System.arraycopy(cbuf, off, result, 0, totalRead);
            readerLogger.trace("[THYMELEAF][TEMPLATEPREPROCESSINGREADER][{}] RETURN {} Input was read and processed. Returning content: [[{}]]",
                    new Object[] {TemplateEngine.threadIndex(), Integer.valueOf(totalRead), new String(result)});
        }
       

        return totalRead;
       
    }


   
   
    @Override
    public int read() throws IOException {

        if (readerLogger.isTraceEnabled()) {
            readerLogger.trace("[THYMELEAF][TEMPLATEPREPROCESSINGREADER][{}] CALLING read(). Will be delegated to read(char[], 0, 1).",
                    new Object[] {TemplateEngine.threadIndex()});
        }
       
        final char[] cbuf = new char[1];
        final int res = read(cbuf, 0, 1);
        if (res <= 0) {
            return res;
        }
       
        return cbuf[0];
       
    }

   

   
   
    @Override
    public int read(final CharBuffer target) throws IOException {

        if (readerLogger.isTraceEnabled()) {
            readerLogger.trace("[THYMELEAF][TEMPLATEPREPROCESSINGREADER][{}] CALLING read(CharBuffer). Will be delegated as several calls to read(char[], 0, 1024).",
                    new Object[] {TemplateEngine.threadIndex()});
        }
       
        final char[] cbuf = new char[BUFFER_BLOCK_SIZE];
        int totalRead = -1;
        int read;
        while ((read = read(cbuf, 0, cbuf.length)) != -1) {
            target.put(cbuf, 0, read);
            if (totalRead == -1) {
                totalRead = 0;
            }
            totalRead += read;
        }
        return totalRead;
       
    }




   
    @Override
    public int read(final char[] cbuf) throws IOException {

        if (readerLogger.isTraceEnabled()) {
            readerLogger.trace("[THYMELEAF][TEMPLATEPREPROCESSINGREADER][{}] CALLING read(char[] cbuf). Will be delegated to read(cbuf, 0, cbuf.length).",
                    new Object[] {TemplateEngine.threadIndex()});
        }
       
        return read(cbuf, 0, cbuf.length);
       
    }

   


    @Override
    public long skip(final long n) throws IOException {
        throw new IOException("Skip not supported in reader");
    }




    @Override
    public boolean ready() throws IOException {
        return this.bufferedReader.ready() || this.overflowIndex > 0;
    }




    @Override
    public boolean markSupported() {
        return false;
    }




    @Override
    public void mark(final int readAheadLimit) throws IOException {
        throw new IOException("Mark not supported in reader");
    }




    @Override
    public void reset() throws IOException {
        throw new IOException("Reset not supported in reader");
    }




    @Override
    public void close() throws IOException {
        this.bufferedReader.close();
    }
   
   
   
    /*
     * fragment: the char[] containing the fragment to be copied
     * fragmentOff: the offset for the fragment char[]
     * fragmentLen: the length of the fragment to be copied
     * cbuf: the result buffer
     * cbufi: the current position in the result buffer (offset)
     * last: the last position (non-inclusive) that can be filled in cbuf
     *
     * RETURNS: the amount of chars that have been actually copied to the cbuf result array
     */
    private int copyToResult(
            final char[] fragment, final int fragmentOff, final int fragmentLen,
            final char[] cbuf, final int cbufi, final int last) {
       
        if (cbufi + fragmentLen < last) {
           
            System.arraycopy(fragment, fragmentOff, cbuf, cbufi, fragmentLen);

            //noinspection ArrayEquality
            if (fragment == this.overflow) {
                // Overflow has been cleaned
                this.overflowIndex = 0;
            }

            return fragmentLen;
           
        }
       
        // There is no space at the result buffer: we must overflow
       
        final int toBeCopied = last - cbufi;
        final int toBeOverflowed = fragmentLen - toBeCopied;
       
        if (toBeCopied > 0) {
            System.arraycopy(fragment, fragmentOff, cbuf, cbufi, toBeCopied);
        }
        //noinspection ArrayEquality
        if (fragment != this.overflow) {
            System.arraycopy(fragment, (fragmentOff + toBeCopied), this.overflow, this.overflowIndex, toBeOverflowed);
            this.overflowIndex += toBeOverflowed;
        } else {
            // Both source and target are the overflow array
            System.arraycopy(fragment, (fragmentOff + toBeCopied), this.overflow, 0, toBeOverflowed);
            this.overflowIndex = toBeOverflowed;
        }
        return toBeCopied;
       
    }
   
   

   
    /*
     * RETURNS:
     *     <0 = does not start here
     *     0  = maybe (not enough buffer to know)
     *     >0 = does start here
     */
    private static int match(
            final int[] fragment, final int fragmentOff, final int fragmentLen,
            final char[] buffer, final int buffi, final int bufferLast) {
       
        /*
         * Trying to fail fast
         */
        final char f0Lower = LOWER_CHARS[fragment[fragmentOff]];
        final char f0Upper = UPPER_CHARS[fragment[fragmentOff]];
        if (f0Lower != CHAR_WHITESPACE_WILDCARD && f0Lower != CHAR_ALPHANUMERIC_WILDCARD &&
            f0Lower != buffer[buffi] && f0Upper != buffer[buffi]) {
            return -1;
        }
       
        final int fragmentLast = fragmentOff + fragmentLen; 
       
        int buffj = buffi;
        int fragmenti = fragmentOff;
        while (buffj < bufferLast && fragmenti < fragmentLast) {

            final int fragmentIndex = fragment[fragmenti];
            // lower will be enough for most checks
            final char fLower = LOWER_CHARS[fragmentIndex];
           
            // For wildcards, checking against lowercase will be enough (wildcards are at the same
            // position in lower and upper case).
            if (fLower == CHAR_WHITESPACE_WILDCARD) {
               
                if (buffer[buffj] != ' ' && buffer[buffj] != '\t') {
                    if (buffj > buffi && (buffer[buffj - 1] == ' ' || buffer[buffj - 1] == '\t')) {
                        fragmenti++;
                    } else {
                        // We did not find at least one whitespace
                        return -1;
                    }
                } else {
                    buffj++;
                }
               
            } else if (fLower == CHAR_OPTIONAL_WHITESPACE_WILDCARD) {
                   
                if (buffer[buffj] != ' ' && buffer[buffj] != '\t') {
                    fragmenti++;
                } else {
                    buffj++;
                }
                   
            } else if (fLower == CHAR_ALPHANUMERIC_WILDCARD) {
               
                final char c = buffer[buffj];
                final boolean isUpper = (c >= 'A' && c <= 'Z');
                final boolean isLower = (c >= 'a' && c <= 'z');
                final boolean isDigit = (c >= '0' && c <= '9');
                final boolean isHash = (c == '#');
                if ((!isUpper && !isLower && !isDigit && !isHash) ||
                        (fragmenti + 1 < fragmentLast &&
                                (UPPER_CHARS[fragment[fragmenti + 1]] == buffer[buffj]) ||
                                (LOWER_CHARS[fragment[fragmenti + 1]] == buffer[buffj]))) {
                    // Either we found a non-alphanumeric, or we simply found
                    // a character that matches next one in fragment
                    fragmenti++;
                } else {
                    buffj++;
                }
               
            } else if (fLower == CHAR_ANY_WILDCARD) {
               
                if ((fragmenti + 1 < fragmentLast &&
                        (UPPER_CHARS[fragment[fragmenti + 1]] == buffer[buffj]) ||
                        (LOWER_CHARS[fragment[fragmenti + 1]] == buffer[buffj]))) {
                    // We found a character that matches next one in fragment!
                    fragmenti++;
                } else {
                    buffj++;
                }
               
            } else {
               
                final char bufferChar = buffer[buffj];
                       
                if (bufferChar != UPPER_CHARS[fragmentIndex] &&
                    bufferChar != fLower) {
                    return -1;
                }

                buffj++;
                fragmenti++;
               
            }
           
        }
        if (fragmenti == fragmentLast) {
            // Matches! and we return the number of chars that matched
            return buffj - buffi;
        }
        // Was matching OK, but then we hit the end of the buffer...
        return 0;
       
    }


   
   
    private static char[] normalizeDocTypeClause(final char[] buffer, final int offset, final int len) {
       
        try {
           
            boolean afterQuote = false;
           
            final char[] result = new char[len];
            System.arraycopy(NORMALIZED_DOCTYPE_PREFIX, 0, result, 0, NORMALIZED_DOCTYPE_PREFIX.length);
           
            for (int i = (offset + NORMALIZED_DOCTYPE_PREFIX.length); i < (offset + len); i++) {
                final char c = buffer[i];
                if (c == '\"') {
                    // Once we find a quote symbol, we stop worrying about normalizing and just copy verbatim
                    afterQuote = true;
                    result[i - offset] = '\"';
                } else if (!afterQuote && (c == 'P' || c == 'p')) {
                    final char c2 = buffer[i + 1];
                    if (c2 == 'U' || c2 == 'u') {
                        final char c3 = buffer[i + 2];
                        final char c4 = buffer[i + 3];
                        final char c5 = buffer[i + 4];
                        final char c6 = buffer[i + 5];
                        final char c7 = buffer[i + 6];
                        if ((c3 == 'B' || c3 == 'b') &&
                            (c4 == 'L' || c4 == 'l') &&
                            (c5 == 'I' || c5 == 'i') &&
                            (c6 == 'C' || c6 == 'c') &&
                            (c7 == ' ' || c7 == '\t')) {
                            System.arraycopy(NORMALIZED_DOCTYPE_PUBLIC, 0, result, (i - offset), NORMALIZED_DOCTYPE_PUBLIC.length);
                            i += NORMALIZED_DOCTYPE_PUBLIC.length - 1;
                            continue;
                        }
                    }
                    result[i - offset] = c;
                } else if (!afterQuote && (c == 'S' || c == 's')) {
                    final char c2 = buffer[i + 1];
                    if (c2 == 'Y' || c2 == 'y') {
                        final char c3 = buffer[i + 2];
                        final char c4 = buffer[i + 3];
                        final char c5 = buffer[i + 4];
                        final char c6 = buffer[i + 5];
                        final char c7 = buffer[i + 6];
                        if ((c3 == 'S' || c3 == 's') &&
                            (c4 == 'T' || c4 == 't') &&
                            (c5 == 'E' || c5 == 'e') &&
                            (c6 == 'M' || c6 == 'm') &&
                            (c7 == ' ' || c7 == '\t')) {
                            System.arraycopy(NORMALIZED_DOCTYPE_SYSTEM, 0, result, (i - offset), NORMALIZED_DOCTYPE_SYSTEM.length);
                            i += NORMALIZED_DOCTYPE_SYSTEM.length - 1;
                            continue;
                        }
                    }
                    result[i - offset] = c;
                } else {
                    result[i - offset] = c;
                }
            }
   
           
            return result;

        } catch (final Exception e) {
            throw new TemplateInputException("DOCTYPE clause has bad format: \"" + (new String(buffer, offset, len)) + "\"", e);
        }
       
    }

   
   
    public String getDocTypeClause() {
        return this.docTypeClause;
    }

   
   
   
    public static String removeEntitySubstitutions(final String text) {

        if (text == null) {
            return null;
        }
        final int textLen = text.length();
        for (int i = 0; i < textLen; i++) {
            if (text.charAt(i) == TemplatePreprocessingReader.CHAR_ENTITY_START_SUBSTITUTE) {
                final char[] textCharArray = text.toCharArray();
                for (int j = 0; j < textLen; j++) {
                    if (textCharArray[j] == TemplatePreprocessingReader.CHAR_ENTITY_START_SUBSTITUTE) {
                        textCharArray[j] = '&';
                    }
                }
                return new String(textCharArray);
            }
        }
        return text;
       
    }
       

   
   
    public static void removeEntitySubstitutions(final char[] text, final int off, final int len) {

        if (text == null) {
            return;
        }
        final int finalPos = off + len;
        for (int i = off; i < finalPos; i++) {
            if (text[i] == TemplatePreprocessingReader.CHAR_ENTITY_START_SUBSTITUTE) {
                text[i] = '&';
            }
        }
       
    }
   
   
   
    /**
     * @since 2.0.11
     */
    public Reader getInnerReader() {
        return this.innerReader;
    }
   
   
}
TOP

Related Classes of org.thymeleaf.templateparser.TemplatePreprocessingReader

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.