/*
* =============================================================================
*
* 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.engine;
import java.io.IOException;
import java.io.Writer;
import java.util.Arrays;
import org.thymeleaf.exceptions.TemplateOutputException;
/**
*
* @author Daniel Fernández
*
* @since 3.0.0
*
*/
final class ThrottledTemplateWriterWriterAdapter
extends Writer
implements ThrottledTemplateWriter.IThrottledTemplateWriterAdapter {
// Given we will be directly writing chars we will use a 256-char buffer as a sensible, approximate
// measure of the amount of overflow we will need, given the only influencing factor for us is
// the size of the structures being written to this writer (elements, texts, etc.)
private static int OVERFLOW_BUFFER_INCREMENT = 256;
private final String templateName;
private final TemplateFlowController flowController;
private Writer writer;
private char[] overflow;
private int overflowSize;
private int maxOverflowSize;
private int overflowGrowCount;
private boolean unlimited;
private int limit;
private int writtenCount;
ThrottledTemplateWriterWriterAdapter(final String templateName, final TemplateFlowController flowController) {
super();
this.templateName = templateName;
this.flowController = flowController;
this.overflow = null;
this.overflowSize = 0;
this.maxOverflowSize = 0;
this.overflowGrowCount = 0;
this.unlimited = false;
this.limit = 0;
this.writtenCount = 0;
this.flowController.stopProcessing = true;
}
void setWriter(final Writer writer) {
this.writer = writer;
this.writtenCount = 0;
}
public boolean isOverflown() {
return this.overflowSize > 0;
}
public boolean isStopped() {
return this.limit == 0;
}
public int getWrittenCount() {
return this.writtenCount;
}
public int getMaxOverflowSize() {
return this.maxOverflowSize;
}
public int getOverflowGrowCount() {
return this.overflowGrowCount;
}
public void allow(final int limit) {
if (limit == Integer.MAX_VALUE || limit < 0) {
this.unlimited = true;
this.limit = -1;
} else {
this.unlimited = false;
this.limit = limit;
}
this.flowController.stopProcessing = (this.limit == 0);
if (this.overflowSize == 0 || this.limit == 0) {
return;
}
try {
if (this.unlimited || this.limit > this.overflowSize) {
this.writer.write(this.overflow, 0, this.overflowSize);
if (!this.unlimited) {
this.limit -= this.overflowSize;
}
this.writtenCount += this.overflowSize;
this.overflowSize = 0;
return;
}
this.writer.write(this.overflow, 0, this.limit);
if (this.limit < this.overflowSize) {
System.arraycopy(this.overflow, this.limit, this.overflow, 0, this.overflowSize - this.limit);
}
this.overflowSize -= this.limit;
this.writtenCount += this.limit;
this.limit = 0;
this.flowController.stopProcessing = true;
} catch (final IOException e) {
throw new TemplateOutputException(
"Exception while trying to write overflowed buffer in throttled template", this.templateName, -1, -1, e);
}
}
@Override
public void write(final int c) throws IOException {
if (this.limit == 0) {
overflow(c);
return;
}
this.writer.write(c);
if (!this.unlimited) {
this.limit--;
}
this.writtenCount++;
if (this.limit == 0) {
this.flowController.stopProcessing = true;
}
}
@Override
public void write(final String str) throws IOException {
final int len = str.length();
if (this.limit == 0) {
overflow(str, 0, len);
return;
}
if (this.unlimited || this.limit > len) {
this.writer.write(str, 0, len);
if (!this.unlimited) {
this.limit -= len;
}
this.writtenCount += len;
return;
}
this.writer.write(str, 0, this.limit);
if (this.limit < len) {
overflow(str, this.limit, (len - this.limit));
}
this.writtenCount += this.limit;
this.limit = 0;
this.flowController.stopProcessing = true;
}
@Override
public void write(final String str, final int off, final int len) throws IOException {
if (this.limit == 0) {
overflow(str, off, len);
return;
}
if (this.unlimited || this.limit > len) {
this.writer.write(str, off, len);
if (!this.unlimited) {
this.limit -= len;
}
this.writtenCount += len;
return;
}
this.writer.write(str, off, this.limit);
if (this.limit < len) {
overflow(str, off + this.limit, (len - this.limit));
}
this.writtenCount += this.limit;
this.limit = 0;
this.flowController.stopProcessing = true;
}
@Override
public void write(final char[] cbuf) throws IOException {
final int len = cbuf.length;
if (this.limit == 0) {
overflow(cbuf, 0, len);
return;
}
if (this.unlimited || this.limit > len) {
this.writer.write(cbuf, 0, len);
if (!this.unlimited) {
this.limit -= len;
}
this.writtenCount += len;
return;
}
this.writer.write(cbuf, 0, this.limit);
if (this.limit < len) {
overflow(cbuf, this.limit, (len - this.limit));
}
this.writtenCount += this.limit;
this.limit = 0;
this.flowController.stopProcessing = true;
}
@Override
public void write(final char[] cbuf, final int off, final int len) throws IOException {
if (this.limit == 0) {
overflow(cbuf, off, len);
return;
}
if (this.unlimited || this.limit > len) {
this.writer.write(cbuf, off, len);
if (!this.unlimited) {
this.limit -= len;
}
this.writtenCount += len;
return;
}
this.writer.write(cbuf, off, this.limit);
if (this.limit < len) {
overflow(cbuf, off + this.limit, (len - this.limit));
}
this.writtenCount += this.limit;
this.limit = 0;
this.flowController.stopProcessing = true;
}
private void overflow(final int c) {
ensureOverflowCapacity(1);
this.overflow[this.overflowSize] = (char)c;
this.overflowSize++;
if (this.overflowSize > this.maxOverflowSize) {
this.maxOverflowSize = this.overflowSize;
}
}
private void overflow(final String str, final int off, final int len) {
ensureOverflowCapacity(len);
str.getChars(off, off + len, this.overflow, this.overflowSize);
this.overflowSize += len;
if (this.overflowSize > this.maxOverflowSize) {
this.maxOverflowSize = this.overflowSize;
}
}
private void overflow(final char[] cbuf, final int off, final int len) {
ensureOverflowCapacity(len);
System.arraycopy(cbuf, off, this.overflow, this.overflowSize, len);
this.overflowSize += len;
if (this.overflowSize > this.maxOverflowSize) {
this.maxOverflowSize = this.overflowSize;
}
}
private void ensureOverflowCapacity(final int len) {
if (this.overflow == null) {
this.overflow = new char[((len / OVERFLOW_BUFFER_INCREMENT) + 1) * OVERFLOW_BUFFER_INCREMENT];
return;
}
final int targetLen = this.overflowSize + len;
if (this.overflow.length < targetLen) {
this.overflow = Arrays.copyOf(this.overflow, ((targetLen / OVERFLOW_BUFFER_INCREMENT) + 1) * OVERFLOW_BUFFER_INCREMENT);
this.overflowGrowCount++;
}
}
@Override
public void flush() throws IOException {
// No need to control overflow here. The fact that this has overflow will be used as a flag to determine
// that further write operations are actually needed by means of the isOverflown() method.
this.writer.flush();
}
@Override
public void close() throws IOException {
// This will normally be NEVER called, as Thymeleaf will not call close() on its Writers/OutputStreams
// (only flush() is guaranteed to be called at the end).
this.writer.close();
}
}