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

com.intershop.beehive.isml.internal.parser.CompactingWriter Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2021 Intershop Communications AG.
 *
 * 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 com.intershop.beehive.isml.internal.parser;

import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.Writer;


/**
 * This class is an output stream that can be used to compact HTML templates.
 * It is able to remove unnecessary whitespaces and thus to reduce the size of
 * the template. Whitespaces may only be removed from content, but not from
 * ISML tag expressions. Therefore, two kinds of print-methods exist, one kind
 * to write unchangeable data and another kind to write compactable data.
 *
 * Note: This writer is applied when the template is compiled. Its performance
 * is not critical to the runtime performance of templates.
 */
public class CompactingWriter extends Writer
{
    /**
     * A marker for invalid characters during optimization.
     */

    private static final char MARKER = 0;

    /**
     * The chained output writer.
     */

    private Writer out;

    /**
     * The buffer for content that must be compacted.
     */

    private CharArrayWriter buffer;

    /**
     * The flag whether compacting is enabled or not.
     */

    private boolean enabled;

    /**
     *   The name of the character encoding being used by this stream.
     */
    private String encoding;

    /**
     * The constructor. Creates a compacting writer that is not enabled.
     * Compacting must be switched on explicitely.
     *
     * @param  out    the chained output stream
     * @param  enc    the character encoding, the jsp file is writen in
     * @throws UnsupportedEncodingException
     *
     * @see     #enable
     */

    public CompactingWriter(Writer out, String enc)
        throws UnsupportedEncodingException
    {
        this.out = out;
        buffer = new CharArrayWriter();
        enabled = false;

        byte[] testChar = {(byte)80};

        // test the decoding char set
        new String(testChar, enc);

        // no exception raised => take the encoding
        encoding = enc;
    }


    /**
     * Switches on content compacting mode. All unnecessary whitespaces are
     * removed from content that is written using the printCompact-methods.
     */

    public void enable()
    {
        enabled = true;
    }


    /**
     * Writes a string unchanged to the output stream.
     *
     * @param       s       the string
     * @throws   IOException if something went wrong
     */

    public void print(String s) throws IOException
    {
        write(s);
    }


    /**
     * Stringifies an object and writes it unchanged to the output stream.
     *
     * @param       o       the object
     * @throws   IOException if something went wrong
     */

    public void print(Object o) throws IOException
    {
        write(String.valueOf(o));
    }


    /**
     * Writes a character unchanged to the output stream.
     *
     * @param       ch      the character
     * @throws   IOException if something went wrong
     */

    public void print(char ch) throws IOException
    {
        write(ch);
    }


    /**
     * Writes a string to the output stream. Any unnecessary whitespaces are
     * removed.
     *
     * @param       s       the string
     * @throws   IOException if something went wrong
     */

    public void printCompact(String s) throws IOException
    {
        if (enabled)
        {
            // if enabled, buffer the content for later optimization
            // the buffer is flushed either when uncompacted content is
            // written or if the stream is explicitely flushed
            buffer.write(s, 0, s.length());
        }
        else
        {
            // if not enable, simply forward it to the output stream without buffering
            write(s);
        }
    }


    /**
     * Stringifies an object and writes it compacted to the output stream.
     *
     * @param       o       the object
     * @throws   IOException if something went wrong
     */

    public void printCompact(Object o) throws IOException
    {
        printCompact(String.valueOf(o));
    }

    /**
     * Return the name of the character encoding being used by this stream.
     * If the encoding has an historical name then that name is returned;
     * otherwise the encoding's canonical name is returned.
     *
     * @return The historical name of this encoding
     */

    public String getEncoding()
    {
        return encoding;
    }

    /*---------------------------------------------------------------------
                            Methods from Writer
    ---------------------------------------------------------------------*/


    /**
     * Implementation of the Writer abstract method. Writes data unchanged.
     *
     * @param       ch      the characters to be written
     * @param       off     the offset
     * @param       len     the number of characters
     * @throws   IOException if something went wrong
     */

    @Override
    public void write(char[] ch, int off, int len) throws IOException
    {
        // make sure any cached content is flushed before
        flushBuffer();
        out.write(ch, off, len);
    }


    /**
     * Flushes the stream. Any buffered content is written to the output stream.
     *
     * @throws   IOException if something went wrong
     */

    @Override
    public void flush() throws IOException
    {
        flushBuffer();
        out.flush();
    }


    /**
     * Closes the stream.
     *
     * @throws   IOException if something went wrong
     */

    @Override
    public void close() throws IOException
    {
        out.close();
    }


    /*---------------------------------------------------------------------
                            Private helpers
    ---------------------------------------------------------------------*/


    /**
     * Flushes the buffer. The buffered content is optimized before writing
     * to the output stream.
     *
     * @throws   IOException if something went wrong
     */

    private void flushBuffer() throws IOException
    {
        if (buffer.size() == 0)
        {
            // nothing to do
            return;
        }

        char[] content = buffer.toCharArray();

        // all operations operate on the whole content from 0 to end
        // do not change the order!
        int length = content.length;

        length = removeAll('\r', content, length);
        length = replaceAll('\t', ' ', content, length);
        length = replaceAll(' ', ' ', ' ', content, length);
        //length = replaceAll(' ', '\n', '\n', content, length); // leave trailing blanks, see ENFINITY-2297
        length = replaceAll('\n', ' ', '\n', content, length);
        length = replaceAll('\n', '\n', '\n', content, length);

        if (!(length == 1 && content[0] == '\n'))
        {
            // remove leading and trailing new lines if they occur immediately
            // before a tag opens or after a tag was closed
            int start = 0;
            if (length > 1 && content[length - 1] == '\n' && content[length - 2] == '>')
            {
                length--;
            }
            if (length > 1 && content[0] == '\n' && content[1] == '<')
            {
                length--;
                start++;
            }
            
            // now write the compacted content
            out.write(content, start, length);
        }
        // else don't write a single '\n'

        // empty the buffer
        buffer.reset();
    }


    /**
     * Removes all occurrences of the passed character in the buffer.
     *
     * @param       ch      the character to be removed
     * @param       buf     the buffer
     * @param       length  the length of the buffer
     * @return      the new length of the buffer
     */

    private int removeAll(char ch, char[] buf, int length)
    {
        int index = 0;

        for (int i = 0; i < length; i++)
        {
            if (buf[i] != ch)
            {
                buf[index] = buf[i];
                index++;
            }
        }

        return index;
    }


    /**
     * Replaces all occurrences of the passed character in the buffer
     * by another character.
     *
     * @param       ch1     the character to be replaced
     * @param       ch2     the new character
     * @param       buf     the buffer
     * @param       length  the length of the buffer
     * @return      the length of the buffer (doesn't change)
     */

    private int replaceAll(char ch1, char ch2, char[] buf, int length)
    {
        for (int i = 0; i < length; i++)
        {
            if (buf[i] == ch1)
            {
                buf[i] = ch2;
            }
        }

        return length;
    }


    /**
     * Replaces all occurrences of the two passed characters in the buffer
     * by another character.
     *
     * @param       ch1     the first character
     * @param       ch2     the second character
     * @param       ch3     the new character
     * @param       buf     the buffer
     * @param       length  the length of the buffer
     * @return      the new length of the buffer
     */

    private int replaceAll(char ch1, char ch2, char ch3, char[] buf, int length)
    {
        // if buf is too small, there is nothing to do
        if (length < 2)
        {
            return length;
        }

        int i1 = 0;

        for (int i = 0; i < length - 1; i = i1)
        {
            i1 = i + 1;

            if ((buf[i] == ch1) && (buf[i1] == ch2))
            {
                // mark all invalid characters
                buf[i] = MARKER;
                buf[i1] = ch3;
            }
        }

        // now remove the marked places
        return removeAll(MARKER, buf, length);
    }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy