/*
* =============================================================================
*
* Copyright (c) 2011-2016, 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.reader;
import java.io.IOException;
import java.io.Reader;
/**
*
* @author Daniel Fernández
* @since 3.0.0
*
*/
abstract class BlockAwareReader extends Reader {
public enum BlockAction { DISCARD_ALL, DISCARD_CONTAINER }
private final Reader reader;
private final BlockAction action;
private final char[] prefix, suffix;
private final char p0, s0;
private char[] overflowBuffer = null;
private int overflowBufferLen = 0;
private boolean insideComment = false;
private int index = 0;
private int discardFrom = -1;
protected BlockAwareReader(final Reader reader, final BlockAction action, final char[] prefix, final char[] suffix) {
super();
this.reader = reader;
this.action = action;
this.prefix = prefix;
this.suffix = suffix;
this.p0 = this.prefix[0];
this.s0 = this.suffix[0];
}
@Override
public int read(final char[] cbuf, final int off, final int len) throws IOException {
int read = readBytes(cbuf, off, len);
if (read <= 0) {
return read;
}
// At this point, discardFrom should only be -1 or 0, so we might have to adapt it to the specified offset
this.discardFrom = (this.discardFrom < 0? this.discardFrom : Math.max(off, this.discardFrom));
int maxi = off + read;
char c;
int i = off;
while (i < maxi) {
c = cbuf[i++];
if (this.index == 0 && c != this.p0 && c != this.s0) {
// Shortcut for most characters in a template: no further tests to be done
continue;
}
if (!this.insideComment) {
if (c == this.prefix[this.index]) {
this.index++;
if (this.index == this.prefix.length) {
// Remove the prefix, as if it was never there...
if (i < maxi) {
System.arraycopy(cbuf, i, cbuf, i - this.prefix.length, (maxi - i));
}
this.insideComment = true;
this.index = 0;
read -= this.prefix.length;
maxi -= this.prefix.length;
i -= this.prefix.length;
this.discardFrom = (this.action == BlockAction.DISCARD_ALL ? i : -1);
}
} else {
if (this.index > 0) {
i -= this.index;
}
this.index = 0;
}
} else {
if (c == this.suffix[this.index]) {
this.index++;
if (this.index == this.suffix.length) {
// Remove the suffix, as if it was never there...
if (i < maxi) {
System.arraycopy(cbuf, i, cbuf, i - this.suffix.length, (maxi - i));
}
this.insideComment = false;
this.index = 0;
read -= this.suffix.length;
maxi -= this.suffix.length;
i -= this.suffix.length;
// Once the suffix has been removed, we check whether we should actually also removed the
// block contents...
if (this.discardFrom >= 0) {
if (i < maxi) {
System.arraycopy(cbuf, i, cbuf, this.discardFrom, (maxi - i));
}
read -= (i - this.discardFrom);
maxi -= (i - this.discardFrom);
i = this.discardFrom;
this.discardFrom = -1;
}
}
} else {
if (this.index > 0) {
i -= this.index;
}
this.index = 0;
}
}
}
if (this.index > 0) {
// Oops, the buffer ended in something that could be a structure to be removed -- will need some more processing
// First step is to copy the contents we doubt about to the overflow buffer and subtract them from cbuf
overflowLastBytes(cbuf, maxi, this.index);
read -= this.index;
maxi -= this.index;
// Second step is trying to complete the overflow buffer in order to make a decision on whether we are
// really looking at a removable structure here or not...
final char[] structure = (this.insideComment? this.suffix : this.prefix);
// Check if we actually found a complete structure
if (matchOverflow(structure)) {
this.insideComment = !this.insideComment;
// We don't modify the discardFrom flag here, as it will be needed for discarding (or not) part of the buffer afterwards
this.overflowBufferLen -= structure.length;
this.index = 0;
} else {
// We didn't find the structure we were looking for, and now we have an overflow that contains
// several characters. Including the possibility that it includes the beginning of a structure...
// At this stage, we know we can copy back JUST ONE of those "this.index" bytes into the cbuf array, so
// that we don't try to match it against prefix/suffix again (but we allow any matches starting with
// the next char)
System.arraycopy(this.overflowBuffer, 0, cbuf, maxi, 1);
read++;
maxi++;
System.arraycopy(this.overflowBuffer, 1, this.overflowBuffer, 0, (this.overflowBufferLen - 1));
this.overflowBufferLen--;
this.index = 0;
}
}
if (this.discardFrom >= 0) {
read -= (maxi - this.discardFrom);
this.discardFrom = 0;
}
this.discardFrom = (this.insideComment && this.action == BlockAction.DISCARD_ALL? 0 : -1);
return read;
}
private int readBytes(final char[] buffer, final int off, final int len) throws IOException {
if (len == 0) {
return 0;
}
if (this.overflowBufferLen == 0) {
return this.reader.read(buffer, off, len);
}
if (this.overflowBufferLen <= len) {
// Our overflow fits in the cbuf len, so we copy and ask the delegate reader to write from there
System.arraycopy(this.overflowBuffer, 0, buffer, off, this.overflowBufferLen);
int read = this.overflowBufferLen;
this.overflowBufferLen = 0;
if (read < len) {
final int delegateRead = this.reader.read(buffer, (off + read), (len - read));
if (delegateRead > 0) {
read += delegateRead;
}
}
return read;
}
// we are asking for less characters than we currently have in overflow
System.arraycopy(this.overflowBuffer, 0, buffer, off, len);
if (len < this.overflowBufferLen) {
System.arraycopy(this.overflowBuffer, len, this.overflowBuffer, 0, (this.overflowBufferLen - len));
}
this.overflowBufferLen -= len;
return len;
}
private void overflowLastBytes(final char[] buffer, final int maxi, final int overflowCount) {
if (this.overflowBuffer == null) {
this.overflowBuffer = new char[Math.max(this.prefix.length, this.suffix.length)];
}
if (this.overflowBufferLen > 0) {
System.arraycopy(this.overflowBuffer, 0, this.overflowBuffer, overflowCount, this.overflowBufferLen);
}
System.arraycopy(buffer, maxi - overflowCount, this.overflowBuffer, 0, overflowCount);
this.overflowBufferLen += overflowCount;
}
private boolean matchOverflow(final char[] structure) throws IOException {
if (this.overflowBufferLen > 0) {
for (int i = 0; i < this.overflowBufferLen; i++) {
if (this.overflowBuffer[i] != structure[i]) {
return false;
}
}
}
int overflowRead = 0;
while (overflowRead >= 0 && this.overflowBufferLen < structure.length) {
overflowRead = this.reader.read(this.overflowBuffer, this.overflowBufferLen, 1); // can only return 0 or 1
if (overflowRead > 0) {
this.overflowBufferLen++;
if (this.overflowBuffer[this.overflowBufferLen - 1] != structure[this.overflowBufferLen - 1]) {
return false;
}
}
}
return (this.overflowBufferLen == structure.length);
}
@Override
public void close() throws IOException {
this.reader.close();
}
}