/* * Copyright (c) 1998-2010 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 Emil Ong */ package com.caucho.quercus.lib.file; import com.caucho.quercus.QuercusModuleException; import com.caucho.quercus.env.*; import com.caucho.vfs.TempBuffer; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; /** * A stream that has its operations mediated by a Quercus object. */ public class WrappedStream implements BinaryInput, BinaryOutput { private static final ConstStringValue STREAM_CLOSE = new ConstStringValue("stream_close"); private static final ConstStringValue STREAM_EOF = new ConstStringValue("stream_eof"); private static final ConstStringValue STREAM_FLUSH = new ConstStringValue("stream_flush"); private static final ConstStringValue STREAM_OPEN = new ConstStringValue("stream_open"); private static final ConstStringValue STREAM_READ = new ConstStringValue("stream_read"); private static final ConstStringValue STREAM_SEEK = new ConstStringValue("stream_seek"); private static final ConstStringValue STREAM_TELL = new ConstStringValue("stream_tell"); private static final ConstStringValue STREAM_WRITE = new ConstStringValue("stream_write"); private static final UnicodeBuilderValue STREAM_CLOSE_U = new UnicodeBuilderValue("stream_close"); private static final UnicodeBuilderValue STREAM_EOF_U = new UnicodeBuilderValue("stream_eof"); private static final UnicodeBuilderValue STREAM_FLUSH_U = new UnicodeBuilderValue("stream_flush"); private static final UnicodeBuilderValue STREAM_OPEN_U = new UnicodeBuilderValue("stream_open"); private static final UnicodeBuilderValue STREAM_READ_U = new UnicodeBuilderValue("stream_read"); private static final UnicodeBuilderValue STREAM_SEEK_U = new UnicodeBuilderValue("stream_seek"); private static final UnicodeBuilderValue STREAM_TELL_U = new UnicodeBuilderValue("stream_tell"); private static final UnicodeBuilderValue STREAM_WRITE_U = new UnicodeBuilderValue("stream_write"); private byte[] printBuffer = new byte[1]; private Env _env; private Value _wrapper; private LineReader _lineReader; private InputStream _is; private OutputStream _os; private int _buffer; private boolean _doUnread = false; private int _writeLength; private WrappedStream(Env env, Value wrapper) { _env = env; _wrapper = wrapper; _lineReader = new LineReader(env); } public WrappedStream(Env env, QuercusClass qClass, StringValue path, StringValue mode, LongValue options) { _env = env; _lineReader = new LineReader(env); _wrapper = qClass.callNew(_env, Value.NULL_ARGS); if (env.isUnicodeSemantics()) { _wrapper.callMethod(_env, STREAM_OPEN_U, path, mode, options, NullValue.NULL); } else { _wrapper.callMethod(_env, STREAM_OPEN, path, mode, options, NullValue.NULL); } } @Override public InputStream getInputStream() { if (_is == null) { _is = new WrappedInputStream(); } return _is; } @Override public OutputStream getOutputStream() { if (_os == null) { _os = new WrappedOutputStream(); } return _os; } /** * Opens a new copy. */ @Override public BinaryInput openCopy() throws IOException { return new WrappedStream(_env, _wrapper); } /** * Sets the current read encoding. The encoding can either be a * Java encoding name or a mime encoding. * * @param encoding name of the read encoding */ public void setEncoding(String encoding) throws UnsupportedEncodingException { } @Override public void closeRead() { close(); } @Override public void closeWrite() { close(); } @Override public void close() { if (_env.isUnicodeSemantics()) { _wrapper.callMethod(_env, STREAM_CLOSE_U); } else { _wrapper.callMethod(_env, STREAM_CLOSE); } } /** * Reads a character from a file, returning -1 on EOF. */ @Override public int read() throws IOException { if (_doUnread) { _doUnread = false; return _buffer; } else { Value output; if (_env.isUnicodeSemantics()) { output = _wrapper.callMethod(_env, STREAM_READ_U, LongValue.ONE); } else { output = _wrapper.callMethod(_env, STREAM_READ, LongValue.ONE); } _buffer = (int) output.toLong(); return _buffer; } } /** * Unread a character. */ @Override public void unread() throws IOException { _doUnread = true; } @Override public int read(byte[] buffer, int offset, int length) { // TODO: shgould be reimplemented Value output; if (_env.isUnicodeSemantics()) { output = _wrapper.callMethod(_env, STREAM_READ_U, LongValue.create(length)); } else { output = _wrapper.callMethod(_env, STREAM_READ, LongValue.create(length)); } // XXX "0"? if (!output.toBoolean()) { return -1; } byte[] outputBytes = output.toString().getBytes(); if (outputBytes.length < length) { length = outputBytes.length; } System.arraycopy(outputBytes, 0, buffer, offset, length); return length; } public int read(char[] buffer, int offset, int length) { // TODO: should be reimplemented Value output; if (_env.isUnicodeSemantics()) { output = _wrapper.callMethod(_env, STREAM_READ_U, LongValue.create(length)); } else { output = _wrapper.callMethod(_env, STREAM_READ, LongValue.create(length)); } // XXX "0"? if (!output.toBoolean()) { return -1; } byte[] outputBytes = output.toString().getBytes(); if (outputBytes.length < length) { length = outputBytes.length; } System.arraycopy(outputBytes, 0, buffer, offset, length); return length; } /** * Appends to a string builder. */ @Override public StringValue appendTo(StringValue builder) { try { int ch; while ((ch = read()) >= 0) { builder.append((char) ch); } return builder; } catch (IOException e) { throw new QuercusModuleException(e); } } /** * Reads a Binary string. */ @Override public StringValue read(int length) throws IOException { Value output; if (_env.isUnicodeSemantics()) { output = _wrapper.callMethod(_env, STREAM_READ_U, LongValue.create(length)); } else { output = _wrapper.callMethod(_env, STREAM_READ, LongValue.create(length)); } return output.toBinaryValue(_env); } /** * Reads the optional linefeed character from a \r\n */ @Override public boolean readOptionalLinefeed() throws IOException { int ch = read(); if (ch == '\n') { return true; } else { unread(); return false; } } /** * Reads a line from a file, returning null on EOF. */ @Override public StringValue readLine(long length) throws IOException { return _lineReader.readLine(_env, this, length); } @Override public void write(byte[] buffer, int offset, int length) throws IOException { StringValue bb = _env.createBinaryBuilder(buffer, offset, length); Value output; if (_env.isUnicodeSemantics()) { output = _wrapper.callMethod(_env, STREAM_WRITE_U, bb); } else { output = _wrapper.callMethod(_env, STREAM_WRITE, bb); } _writeLength = (int) output.toLong(); } /** * Writes to a stream. */ @Override public int write(InputStream is, int length) { int writeLength = 0; TempBuffer tb = TempBuffer.allocate(); byte[] buffer = tb.getBuffer(); try { while (length > 0) { int sublen; if (length < buffer.length) { sublen = length; } else { sublen = buffer.length; } sublen = is.read(buffer, 0, sublen); if (sublen < 0) { break; } for (int offset = 0; offset < sublen;) { write(buffer, offset, sublen); if (_writeLength > 0) { offset += _writeLength; } else { return writeLength; } } writeLength += sublen; length -= sublen; } return writeLength; } catch (IOException e) { throw new QuercusModuleException(e); } finally { TempBuffer.free(tb); } } /** * Prints a string to a file. */ @Override public void print(char v) throws IOException { printBuffer[0] = (byte) v; write(printBuffer, 0, 1); } /** * Prints a string to a file. */ @Override public void print(String v) throws IOException { for (int i = 0; i < v.length(); i++) { print(v.charAt(i)); } } /** * Returns true if end-of-file has been reached */ @Override public boolean isEOF() { if (_env.isUnicodeSemantics()) { return _wrapper.callMethod(_env, STREAM_EOF_U).toBoolean(); } else { return _wrapper.callMethod(_env, STREAM_EOF).toBoolean(); } } /** * Tells the position in the stream */ @Override public long getPosition() { if (_env.isUnicodeSemantics()) { return _wrapper.callMethod(_env, STREAM_TELL_U).toLong(); } else { return _wrapper.callMethod(_env, STREAM_TELL).toLong(); } } /** * Sets the position. */ @Override public boolean setPosition(long offset) { LongValue offsetValue = LongValue.create(offset); LongValue whenceValue = LongValue.create(SEEK_SET); if (_env.isUnicodeSemantics()) { return _wrapper.callMethod(_env, STREAM_SEEK_U, offsetValue, whenceValue).toBoolean(); } else { return _wrapper.callMethod(_env, STREAM_SEEK, offsetValue, whenceValue).toBoolean(); } } @Override public long seek(long offset, int whence) { LongValue offsetValue = LongValue.create(offset); LongValue whenceValue = LongValue.create(whence); if (_env.isUnicodeSemantics()) { return _wrapper.callMethod(_env, STREAM_SEEK_U, offsetValue, whenceValue).toLong(); } else { return _wrapper.callMethod(_env, STREAM_SEEK, offsetValue, whenceValue).toLong(); } } @Override public void flush() throws IOException { boolean result; if (_env.isUnicodeSemantics()) { result = _wrapper.callMethod(_env, STREAM_FLUSH_U).toBoolean(); } else { result = _wrapper.callMethod(_env, STREAM_FLUSH).toBoolean(); } if (!result) { throw new IOException(); // Get around java.io.Flushable } } @Override public Value stat() { if (_env.isUnicodeSemantics()) { return _wrapper.callMethod(_env, STREAM_FLUSH_U); } else { return _wrapper.callMethod(_env, STREAM_FLUSH); } } private class WrappedInputStream extends InputStream { @Override public int read() throws IOException { return WrappedStream.this.read(); } } private class WrappedOutputStream extends OutputStream { @Override public void write(int b) throws IOException { if (_env.isUnicodeSemantics()) { _wrapper.callMethod(_env, STREAM_WRITE_U, LongValue.create(b)); } else { _wrapper.callMethod(_env, STREAM_WRITE, LongValue.create(b)); } } } }