package php.runtime.output;
import php.runtime.Memory;
import php.runtime.env.Environment;
import php.runtime.env.TraceInfo;
import php.runtime.invoke.Invoker;
import php.runtime.memory.BinaryMemory;
import php.runtime.memory.LongMemory;
import php.runtime.memory.StringMemory;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
public class OutputBuffer {
public final static int HANDLER_START = 1;
public final static int HANDLER_WRITE = 0;
public final static int HANDLER_FLUSH = 4;
public final static int HANDLER_CLEAN = 2;
public final static int HANDLER_FINAL = 8;
public enum Type { INTERNAL, USER }
private OutputStream output;
private ByteArrayOutputStream buffer;
private boolean binaryInBuffer;
private Memory callback;
private Invoker invoker;
private int chunkSize;
private boolean erase;
private final Environment environment;
private final OutputBuffer parentOutput;
private int level;
private Type type;
private String name = "default output handler";
private boolean implicitFlush;
private int lock = 0;
private boolean isFlushed = false;
private TraceInfo trace;
private int status;
public OutputBuffer(Environment environment, OutputBuffer parent, Memory callback, int chunkSize, boolean erase) {
this.environment = environment;
this.buffer = new ByteArrayOutputStream(4098);
this.callback = callback;
if (callback != null)
this.invoker = Invoker.valueOf(environment, null, callback);
this.chunkSize = chunkSize;
this.erase = erase;
this.parentOutput = parent;
this.type = Type.INTERNAL;
this.implicitFlush = false;
this.status = HANDLER_START;
}
public OutputBuffer(Environment environment, OutputBuffer parent, Memory callback, int chunkSize) {
this(environment, parent, callback, chunkSize, true);
}
public OutputBuffer(Environment environment, OutputBuffer parent, Memory callback) {
this(environment, parent, callback, 4096);
}
public OutputBuffer(Environment environment, OutputBuffer parent) {
this(environment, parent, null);
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
public String getName() {
return invoker != null ? invoker.getName() : name;
}
public void setName(String name) {
this.name = name;
}
public Type getType() {
return type;
}
public void setType(Type type) {
this.type = type;
}
public void setImplicitFlush(boolean implicitFlush) {
this.implicitFlush = implicitFlush;
}
public Memory getCallback() {
return callback;
}
public void setOutput(OutputStream output) {
this.output = output;
}
public OutputStream getOutput() {
return output;
}
public void setCallback(Memory callback, Invoker invoker) {
this.callback = callback;
this.invoker = invoker;
}
public void setCallback(Memory callback) {
this.callback = callback;
if (callback != null)
this.invoker = Invoker.valueOf(environment, null, callback);
else
this.invoker = null;
}
public int getChunkSize() {
return chunkSize;
}
public void setChunkSize(int chunkSize) {
this.chunkSize = chunkSize;
}
public boolean isErase() {
return erase;
}
public void setErase(boolean erase) {
this.erase = erase;
}
public Memory getContents() throws UnsupportedEncodingException {
if (!binaryInBuffer){
return new StringMemory(buffer.toString(environment.getDefaultCharset().name()));
} else
return new BinaryMemory(buffer.toByteArray());
}
protected void reset(){
buffer.reset();
binaryInBuffer = false;
}
public void clean() throws Throwable {
if (invoker != null){
doFlush(false);
}
status = HANDLER_CLEAN;
reset();
}
protected void doFlush(boolean flush) throws Throwable {
/*if (buffer.size() == 0)
return;*/
if (flush){
isFlushed = true;
if (status != HANDLER_START && status != HANDLER_FLUSH)
status = HANDLER_WRITE;
} else {
if (status == HANDLER_FINAL)
status |= HANDLER_CLEAN;
else
status = HANDLER_CLEAN;
}
if (invoker != null){
invoker.setTrace(trace == null ? TraceInfo.UNKNOWN : trace);
Memory[] args;
if (!binaryInBuffer){
args = new Memory[]{ new StringMemory(buffer.toString(environment.getDefaultCharset().name())), null };
} else
args = new Memory[]{ new BinaryMemory(buffer.toByteArray()), null };
args[1] = LongMemory.valueOf(status);
invoker.setTrace(trace == null ? TraceInfo.UNKNOWN : trace);
Memory result;
incLock();
try {
result = invoker.call(args).toValue();
} finally {
decLock();
}
if (result != Memory.FALSE){
byte[] data = result instanceof BinaryMemory
? result.getBinaryBytes(environment.getDefaultCharset()) : result.toString().getBytes(environment.getDefaultCharset());
if (flush){
if (output == null)
parentOutput.write(data);
else
output.write(data);
reset();
status = HANDLER_FLUSH;
}
return;
}
}
if (flush){
status |= HANDLER_FLUSH;
if (output == null)
parentOutput.write(buffer.toByteArray());
else
buffer.writeTo(output);
reset();
}
}
public void write(String content) throws Throwable {
if (!isLock())
_write(content.getBytes(environment.getDefaultCharset()));
}
public void write(Memory content) throws Throwable {
if (!isLock()){
content = content.toValue();
if (content instanceof BinaryMemory)
write(content.getBinaryBytes(environment.getDefaultCharset()));
else
write(content.toString());
}
}
protected void _write(byte[] bytes) throws Throwable {
_write(bytes, bytes.length);
}
protected void _write(byte[] bytes, int length) throws Throwable {
if (isLock()) return;
boolean needFlush = implicitFlush || (chunkSize > 0 && length + buffer.size() >= chunkSize);
if (needFlush){
if (chunkSize > 0){
int cutLength = (length + buffer.size()) - chunkSize;
if (cutLength > 0){
buffer.write(bytes, 0, length);
doFlush(true);
/*byte[] tmp = new byte[cutLength];
System.arraycopy(bytes, bytes.length - cutLength, tmp, 0, cutLength);
_write(tmp);*/
} else {
buffer.write(bytes, 0, length);
doFlush(true);
}
} else {
buffer.write(bytes, 0, length);
doFlush(true);
}
} else {
buffer.write(bytes, 0, length);
}
}
public void write(byte[] bytes, int length) throws Throwable {
if (!isLock()){
binaryInBuffer = true;
_write(bytes, length);
}
}
public void write(byte[] bytes) throws Throwable {
write(bytes, bytes.length);
}
public void flush() throws Throwable {
if (!isLock()){
status = HANDLER_FLUSH;
doFlush(true);
if (output != null)
output.flush();
}
}
public void close() throws Throwable {
if (!isBufferEmpty())
doFlush(true);
}
public int getBufferSize(){
return buffer.size();
}
public boolean isRoot(){
return level == 0;
}
public void decLock(){
if (isRoot())
lock--;
else
environment.getDefaultBuffer().decLock();
}
public void incLock(){
if (isRoot())
lock++;
else
environment.getDefaultBuffer().incLock();
}
public boolean isLock(){
return isRoot() ? lock > 0 : environment.getDefaultBuffer().isLock();
}
public boolean isFlushed() {
return isFlushed;
}
public Invoker getInvoker() {
return invoker;
}
public OutputBuffer getParentOutput() {
return parentOutput;
}
public void setTrace(TraceInfo trace) {
this.trace = trace;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public boolean isBufferEmpty(){
return buffer.size() == 0;
}
}