/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.quercus.env; import com.caucho.quercus.lib.OutputModule; import com.caucho.vfs.*; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.logging.Logger; import java.util.logging.Level; /** * Represents a PHP output buffer */ public class OutputBuffer { private static final Logger log = Logger.getLogger(OutputBuffer.class.getName()); private int _state; private boolean _haveFlushed; private Callable _callback; private final boolean _erase; private final int _chunkSize; private final int _level; private final OutputBuffer _next; private TempStream _tempStream; private WriteStream _out; private final Env _env; OutputBuffer(OutputBuffer next, Env env, Callable callback, int chunkSize, boolean erase) { _next = next; if (_next != null) _level = _next._level + 1; else _level = 1; _erase = erase; _chunkSize = chunkSize; _env = env; _callback = callback; _tempStream = new TempStream(); _out = new WriteStream(_tempStream); _out.setNewlineString("\n"); String encoding = env.getOutputEncoding(); if (encoding != null) { try { _out.setEncoding(encoding); } catch (UnsupportedEncodingException e) { if (log.isLoggable(Level.WARNING)) log.log(Level.WARNING, e.toString(), e); try { _out.setEncoding("UTF-8"); } catch (UnsupportedEncodingException e2) { if (log.isLoggable(Level.WARNING)) log.log(Level.WARNING, e.toString(), e2); } } } _state = OutputModule.PHP_OUTPUT_HANDLER_START; _haveFlushed = false; } /** * Returns the next output buffer; */ public OutputBuffer getNext() { return _next; } /** * Returns the writer. */ public WriteStream getOut() { return _out; } /** * Returns the buffer contents. */ public Value getContents() { try { _out.flush(); StringValue bb = _env.createBinaryBuilder(_tempStream.getLength()); for (TempBuffer ptr = _tempStream.getHead(); ptr != null; ptr = ptr.getNext()) { bb.append(ptr.getBuffer(), 0, ptr.getLength()); } return bb; } catch (IOException e) { _env.error(e.toString(), e); return BooleanValue.FALSE; } } /** * Returns the buffer length. */ public long getLength() { try { _out.flush(); return (long)_tempStream.getLength(); } catch (IOException e) { _env.error(e.toString(), e); return -1L; } } /** * Returns the nesting level. */ public int getLevel() { return _level; } /** * Returns true if this buffer has ever been flushed. */ public boolean haveFlushed() { return _haveFlushed; } /** * Returns the erase flag. */ public boolean getEraseFlag() { // XXX: Why would anyone need this? If the erase flag is false, // that supposedly means that the buffer will not be destroyed // until the script finishes, but you can't access the buffer // after it has been popped anyway, so who cares if you delete // it or not? It is also confusingly named. More research may // be necessary... return _erase; } /** * Returns the chunk size. */ public int getChunkSize() { return _chunkSize; } /** * Cleans (clears) the buffer. */ public void clean() { try { _state |= OutputModule.PHP_OUTPUT_HANDLER_CONT; _out.flush(); _tempStream.clearWrite(); _state &= ~(OutputModule.PHP_OUTPUT_HANDLER_START); _state &= ~(OutputModule.PHP_OUTPUT_HANDLER_CONT); } catch (IOException e) { _env.error(e.toString(), e); } } /** * Flushs the data in the stream, calling the callback with appropriate * flags if necessary. */ public void flush() { _state |= OutputModule.PHP_OUTPUT_HANDLER_CONT; if (! callCallback()) { // clear the start and cont flags doFlush(); } _state &= ~(OutputModule.PHP_OUTPUT_HANDLER_START); _state &= ~(OutputModule.PHP_OUTPUT_HANDLER_CONT); _haveFlushed = true; } /** * Closes the output buffer. */ public void close() { _state |= OutputModule.PHP_OUTPUT_HANDLER_END; if (! callCallback()) { // all data that has and ever will be written has now been processed _state = 0; doFlush(); } WriteStream out = _out; _out = null; TempStream tempStream = _tempStream; _tempStream = null; try { if (out != null) out.close(); } catch (IOException e) { log.log(Level.FINER, e.toString(), e); } if (tempStream != null) tempStream.destroy(); } /** * Invokes the callback using the data in the current buffer. */ private boolean callCallback() { if (_callback == null || ! _callback.isValid(_env)) return false; Value result = _callback.call(_env, getContents(), LongValue.create(_state)); // special code to do nothing to the buffer if (result.toValue() != BooleanValue.FALSE) { // php/1l11, php/1l13 clean(); result.print(_env, getNextOut()); return true; } else return false; } /** * Flushes the data without calling the callback. */ private void doFlush() { try { _out.flush(); WriteStream out = getNextOut(); _tempStream.writeToStream(out); _tempStream.clearWrite(); } catch (IOException e) { _env.error(e.toString(), e); } } private WriteStream getNextOut() { if (_next != null) return _next.getOut(); else return _env.getOriginalOut(); } /** * Returns the callback for this output buffer. */ public Callable getCallback() { return _callback; } /** * Sets the callback for this output buffer. */ public void setCallback(Callback callback) { _callback = callback; } }