/* Copyright 2010 Ben Gunter
*
* 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 net.sourceforge.stripes.tag.layout;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.LinkedList;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
import net.sourceforge.stripes.exception.StripesRuntimeException;
import net.sourceforge.stripes.util.Log;
/**
* A writer that wraps around the normal JSP writer with the ability to silence the output
* temporarily. This is required to prevent the non-layout contents of a {@link LayoutDefinitionTag}
* from rendering more than once when {@link LayoutRenderTag}s and {@link LayoutComponentTag}s are
* nested within it. The definition tag silences output during a component render phase, and the
* component that wishes to render turns output back on during its body evaluation.
*
* @author Ben Gunter
* @since Stripes 1.5.4
*/
public class LayoutWriter extends Writer {
private static final Log log = Log.getInstance(LayoutWriter.class);
/** The control character that, when encountered in the output stream, toggles the silent state. */
private static final char TOGGLE = 0;
private LinkedList<Writer> writers = new LinkedList<Writer>();
private boolean silent, silentState;
/**
* Create a new layout writer that wraps the given JSP writer.
*
* @param out The JSP writer to which output will be written.
*/
public LayoutWriter(JspWriter out) {
log.debug("Create layout writer wrapped around ", out);
this.writers.addFirst(out);
}
/** Get the writer to which output is currently being written. */
protected Writer getOut() {
return writers.peek();
}
/** If true, then discard all output. If false, then resume sending output to the JSP writer. */
public boolean isSilent() {
return silent;
}
/**
* Enable or disable silent mode. The output buffer for the given page context will be flushed
* before silent mode is enabled to ensure all buffered data are written.
*
* @param silent True to silence output, false to enable output.
* @param pageContext The page context in use at the time output is to be silenced.
* @throws IOException If an error occurs writing to output.
*/
public void setSilent(boolean silent, PageContext pageContext) throws IOException {
if (silent != this.silent) {
pageContext.getOut().write(TOGGLE);
this.silent = silent;
log.trace("Output is ", (silent ? "DISABLED" : "ENABLED"));
}
}
/**
* Flush the page context's output buffer and redirect output into a buffer. The buffer can be
* closed and its contents retrieved by calling {@link #closeBuffer(PageContext)}.
*/
public void openBuffer(PageContext pageContext) {
log.trace("Open buffer");
tryFlush(pageContext);
writers.addFirst(new StringWriter(1024));
}
/**
* Flush the page context's output buffer and resume sending output to the writer that was
* receiving output prior to calling {@link #openBuffer(PageContext)}.
*
* @return The buffer's contents.
*/
public String closeBuffer(PageContext pageContext) {
if (getOut() instanceof StringWriter) {
tryFlush(pageContext);
String contents = ((StringWriter) writers.poll()).toString();
log.trace("Closed buffer: \"", contents, "\"");
return contents;
}
else {
throw new StripesRuntimeException(
"Attempt to close a buffer without having first called openBuffer(..)!");
}
}
/** Try to flush the page context's output buffer. If an exception is thrown, just log it. */
protected void tryFlush(PageContext pageContext) {
try {
if (pageContext != null)
pageContext.getOut().flush();
}
catch (IOException e) {
// This seems to happen once at the beginning and once at the end. Don't know why.
log.debug("Failed to flush buffer: ", e.getMessage());
}
}
@Override
public void close() throws IOException {
getOut().close();
}
@Override
public void flush() throws IOException {
getOut().flush();
}
/**
* Calls {@link JspWriter#clear()} on the wrapped JSP writer.
*
* @throws IOException
*/
public void clear() throws IOException {
Writer out = getOut();
if (out instanceof JspWriter) {
((JspWriter) out).clear();
}
else if (out instanceof StringWriter) {
((StringWriter) out).getBuffer().setLength(0);
}
else {
throw new StripesRuntimeException("How did I get a writer of type "
+ out.getClass().getName() + "??");
}
}
@Override
public void write(char[] cbuf, int off, int len) throws IOException {
for (int i = off, mark = i, n = i + len; i < n; ++i) {
switch (cbuf[i]) {
case TOGGLE:
if (this.silentState)
mark = i + 1;
else if (i > mark)
getOut().write(cbuf, mark, i - mark);
this.silentState = !this.silentState;
break;
default:
if (this.silentState)
++mark;
else if (i >= mark && i == n - 1)
getOut().write(cbuf, mark, i - mark + 1);
}
}
}
}