// Copyright (c) Corporation for National Research Initiatives package org.python.core; import java.io.*; // To do: // - readinto(array) // - modes w, a should disallow reading // - what to do about buffer size? // - isatty() // - fileno() (defined, but always raises an exception, for urllib) // - name, mode, closed should be read-only /** * A python file wrapper around a java stream, reader/writer or file. */ public class PyFile extends PyObject { private static class FileWrapper { protected boolean reading; protected boolean writing; protected boolean binary; void setMode(String mode) { reading = mode.indexOf('r') >= 0; writing = mode.indexOf('w') >= 0 || mode.indexOf("+") >= 0 || mode.indexOf('a') >= 0; binary = mode.indexOf('b') >= 0; } public String read(int n) throws java.io.IOException { throw new java.io.IOException("file not open for reading"); } public int read() throws java.io.IOException { throw new java.io.IOException("file not open for reading"); } public int available() throws java.io.IOException { throw new java.io.IOException("file not open for reading"); } public void unread(int c) throws java.io.IOException { throw new java.io.IOException("file doesn't support unread"); } public void write(String s) throws java.io.IOException { throw new java.io.IOException("file not open for writing"); } public long tell() throws java.io.IOException { throw new java.io.IOException("file doesn't support tell/seek"); } public void seek(long pos, int how) throws java.io.IOException { throw new java.io.IOException("file doesn't support tell/seek"); } public void flush() throws java.io.IOException { } public void close() throws java.io.IOException { } public void truncate(long position) throws java.io.IOException { throw new java.io.IOException("file doesn't support truncate"); } public Object __tojava__(Class cls) throws IOException { return null; } protected byte[] getBytes(String s) { // Yes, I known the method is depricated, but it is the fastest // way of converting between between byte[] and String if (binary) { byte[] buf = new byte[s.length()]; s.getBytes(0, s.length(), buf, 0); return buf; } else return s.getBytes(); } protected String getString(byte[] buf, int offset, int len) { // Yes, I known the method is depricated, but it is the fastest // way of converting between between byte[] and String if (binary) { return new String(buf, 0, offset, len); } else return new String(buf, offset, len); } } private static class InputStreamWrapper extends FileWrapper { java.io.InputStream istream; public InputStreamWrapper(java.io.InputStream s) { istream = s; } public String read(int n) throws java.io.IOException { if (n == 0) // nothing to do return ""; if (n < 0) { // read until we hit EOF byte buf[] = new byte[1024]; StringBuffer sbuf = new StringBuffer(); for (int read=0; read >= 0; read=istream.read(buf)) sbuf.append(getString(buf, 0, read)); return sbuf.toString(); } // read the next chunk available, but make sure it's at least // one byte so as not to trip the `empty string' return value // test done by the caller int avail = istream.available(); //n = (n > avail) ? n : avail; byte buf[] = new byte[n]; int read = istream.read(buf); if (read < 0) // EOF encountered return ""; return new String(buf, 0, 0, read); } public int read() throws java.io.IOException { return istream.read(); } public int available() throws java.io.IOException { return istream.available(); } public void unread(int c) throws java.io.IOException { ((java.io.PushbackInputStream)istream).unread(c); } public void close() throws java.io.IOException { istream.close(); } public Object __tojava__(Class cls) throws IOException { if (InputStream.class.isAssignableFrom(cls)) return istream; return null; } } private static class OutputStreamWrapper extends FileWrapper { private java.io.OutputStream ostream; public OutputStreamWrapper(java.io.OutputStream s) { ostream = s; } private static final int MAX_WRITE = 30000; public void write(String s) throws java.io.IOException { byte[] bytes = getBytes(s); int n = bytes.length; int i = 0; while (i < n) { int sz = n-i; sz = sz > MAX_WRITE ? MAX_WRITE : sz; ostream.write(bytes, i, sz); i += sz; } } public void flush() throws java.io.IOException { ostream.flush(); } public void close() throws java.io.IOException { ostream.close(); } public Object __tojava__(Class cls) throws IOException { if (OutputStream.class.isAssignableFrom(cls)) return ostream; return null; } } private static class IOStreamWrapper extends InputStreamWrapper { private java.io.OutputStream ostream; public IOStreamWrapper(java.io.InputStream istream, java.io.OutputStream ostream) { super(istream); this.ostream = ostream; } public void write(String s) throws java.io.IOException { ostream.write(getBytes(s)); } public void flush() throws java.io.IOException { ostream.flush(); } public void close() throws java.io.IOException { ostream.close(); istream.close(); } public Object __tojava__(Class cls) throws IOException { if (OutputStream.class.isAssignableFrom(cls)) return ostream; return super.__tojava__(cls); } } private static class WriterWrapper extends FileWrapper { private java.io.Writer writer; public WriterWrapper(java.io.Writer s) { writer = s; } private static final int MAX_WRITE = 30000; public void write(String s) throws java.io.IOException { writer.write(s); } public void flush() throws java.io.IOException { writer.flush(); } public void close() throws java.io.IOException { writer.close(); } } private static class RFileWrapper extends FileWrapper { /** The default buffer size, in bytes. */ protected static final int defaultBufferSize = 4096; /** The underlying java.io.RandomAccessFile. */ protected java.io.RandomAccessFile file; /** The offset in bytes from the file start, of the next read or * write operation. */ protected long filePosition; /** The buffer used to load the data. */ protected byte buffer[]; /** The offset in bytes of the start of the buffer, from the start * of the file. */ protected long bufferStart; /** The offset in bytes of the end of the data in the buffer, from * the start of the file. This can be calculated from * bufferStart + dataSize, but it is cached to speed * up the read( ) method. */ protected long dataEnd; /** The size of the data stored in the buffer, in bytes. This may be * less than the size of the buffer.*/ protected int dataSize; /** True if we are at the end of the file. */ protected boolean endOfFile; /** True if the data in the buffer has been modified. */ boolean bufferModified = false; public RFileWrapper(java.io.RandomAccessFile file) { this(file, 8092); } public RFileWrapper(java.io.RandomAccessFile file, int bufferSize) { this.file = file; bufferStart = 0; dataEnd = 0; dataSize = 0; filePosition = 0; buffer = new byte[bufferSize]; endOfFile = false; } public String read(int n) throws java.io.IOException { if (n < 0) { n = (int)(file.length() - filePosition); if (n < 0) n = 0; } byte[] buf = new byte[n]; n = readBytes(buf, 0, n); if (n < 0) n = 0; return getString(buf, 0, n); } private int readBytes( byte b[], int off, int len ) throws IOException { // Check for end of file. if( endOfFile ) return -1; // See how many bytes are available in the buffer - if none, // seek to the file position to update the buffer and try again. int bytesAvailable = (int)(dataEnd - filePosition); if (bytesAvailable < 1) { seek(filePosition, 0); return readBytes( b, off, len ); } // Copy as much as we can. int copyLength = (bytesAvailable >= len) ? len : bytesAvailable; System.arraycopy(buffer, (int)(filePosition - bufferStart), b, off, copyLength); filePosition += copyLength; // If there is more to copy... if (copyLength < len) { int extraCopy = len - copyLength; // If the amount remaining is more than a buffer's // length, read it directly from the file. if (extraCopy > buffer.length) { file.seek(filePosition); extraCopy = file.read(b, off + copyLength, len - copyLength); } else { // ...or read a new buffer full, and copy as much // as possible... seek(filePosition, 0); if (!endOfFile) { extraCopy = (extraCopy > dataSize) ? dataSize : extraCopy; System.arraycopy(buffer, 0, b, off + copyLength, extraCopy); } else { extraCopy = -1; } } // If we did manage to copy any more, update the file // position and return the amount copied. if (extraCopy > 0) { filePosition += extraCopy; return copyLength + extraCopy; } } // Return the amount copied. return copyLength; } public int read() throws java.io.IOException { // If the file position is within the data, return the byte... if (filePosition < dataEnd) { return (int)(buffer[(int)(filePosition++ - bufferStart)] & 0xff); } else if (endOfFile) { // ...or should we indicate EOF... return -1; } else { // ...or seek to fill the buffer, and try again. seek(filePosition, 0); return read(); } } public int available() throws java.io.IOException { return 1; } public void unread(int c) throws java.io.IOException { filePosition--; } public void write(String s) throws java.io.IOException { byte[] b = getBytes(s); int len = b.length; // If the amount of data is small (less than a full buffer)... if (len < buffer.length) { // If any of the data fits within the buffer... int spaceInBuffer = 0; int copyLength = 0; if (filePosition >= bufferStart) spaceInBuffer = (int)((bufferStart + buffer.length) - filePosition); if (spaceInBuffer > 0) { // Copy as much as possible to the buffer. copyLength = (spaceInBuffer > len) ? len : spaceInBuffer; System.arraycopy(b, 0, buffer, (int)(filePosition - bufferStart), copyLength ); bufferModified = true; long myDataEnd = filePosition + copyLength; dataEnd = myDataEnd > dataEnd ? myDataEnd : dataEnd; dataSize = (int)(dataEnd - bufferStart); filePosition += copyLength; } // If there is any data remaining, move to the // new position and copy to the new buffer. if (copyLength < len) { seek(filePosition, 0); System.arraycopy(b, copyLength, buffer, (int)(filePosition - bufferStart), len - copyLength); bufferModified = true; long myDataEnd = filePosition + (len - copyLength); dataEnd = myDataEnd > dataEnd ? myDataEnd : dataEnd; dataSize = (int)(dataEnd - bufferStart); filePosition += (len - copyLength); } } else { // ...or write a lot of data... // Flush the current buffer, and write this data to the file. if (bufferModified) { flush( ); bufferStart = dataEnd = dataSize = 0; } file.write( b, 0, len ); filePosition += len; } } public long tell() throws java.io.IOException { return filePosition; } public void seek(long pos, int how) throws java.io.IOException { if (how == 1) pos += filePosition; else if (how == 2) pos += file.length(); if (pos < 0) pos = 0; // If the seek is into the buffer, just update the file pointer. if (pos >= bufferStart && pos < dataEnd) { filePosition = pos; endOfFile = false; return; } // If the current buffer is modified, write it to disk. if (bufferModified) flush(); // Move to the position on the disk. file.seek(pos); filePosition = file.getFilePointer(); bufferStart = filePosition; // Fill the buffer from the disk. dataSize = file.read(buffer); if (dataSize < 0) { dataSize = 0; endOfFile = true; } else { endOfFile = false; } // Cache the position of the buffer end. dataEnd = bufferStart + dataSize; } public void flush() throws java.io.IOException { file.seek(bufferStart); file.write(buffer, 0, dataSize); bufferModified = false; file.getFD().sync(); } public void close() throws java.io.IOException { if (writing && bufferModified) { file.seek(bufferStart); file.write(buffer, 0, (int)dataSize); } file.close(); } public void truncate(long position) throws java.io.IOException { flush(); try { // file.setLength(position); java.lang.reflect.Method m = file.getClass().getMethod( "setLength", new Class[] { Long.TYPE }); m.invoke(file, new Object[] { new Long(position) }); } catch (NoSuchMethodException exc) { super.truncate(position); } catch (SecurityException exc) { super.truncate(position); } catch (IllegalAccessException exc) { super.truncate(position); } catch (java.lang.reflect.InvocationTargetException exc) { if (exc.getTargetException() instanceof IOException) throw (IOException) exc.getTargetException(); super.truncate(position); } } public Object __tojava__(Class cls) throws IOException { if (OutputStream.class.isAssignableFrom(cls) && writing) return new FileOutputStream(file.getFD()); else if (InputStream.class.isAssignableFrom(cls) && reading) return new FileInputStream(file.getFD()); return super.__tojava__(cls); } } private static class TextWrapper extends FileWrapper { private FileWrapper file; private String sep; private boolean sep_is_nl; public TextWrapper(FileWrapper file) { this.file = file; sep = System.getProperty("line.separator"); sep_is_nl = (sep == "\n"); } public String read(int n) throws java.io.IOException { String s = this.file.read(n); int index = s.indexOf('\r'); if (index < 0) return s; StringBuffer buf = new StringBuffer(); int start = 0; int end = s.length(); do { buf.append(s.substring(start, index)); buf.append('\n'); start = index + 1; if (start < end && s.charAt(start) == '\n') start++; index = s.indexOf('\r', start); } while (index >= 0); buf.append(s.substring(start)); if (s.endsWith("\r") && file.available() > 0) { int c = file.read(); if (c != -1 && c != '\n') file.unread(c); } return buf.toString(); } public int read() throws java.io.IOException { int c = file.read(); if (c != '\r') return c; if (file.available() > 0) { c = file.read(); if (c != -1 && c != '\n') file.unread(c); } return '\n'; } public void write(String s) throws java.io.IOException { if (!sep_is_nl) { int index = s.indexOf('\n'); if (index >= 0) { StringBuffer buf = new StringBuffer(); int start = 0; do { buf.append(s.substring(start, index)); buf.append(sep); start = index + 1; index = s.indexOf('\n', start); } while (index >= 0); buf.append(s.substring(start)); s = buf.toString(); } } this.file.write(s); } public long tell() throws java.io.IOException { return file.tell(); } public void seek(long pos, int how) throws java.io.IOException { file.seek(pos, how); } public void flush() throws java.io.IOException { file.flush(); } public void close() throws java.io.IOException { file.close(); } public void truncate(long position) throws java.io.IOException { file.truncate(position); } public Object __tojava__(Class cls) throws IOException { return file.__tojava__(cls); } } public String name; public String mode; public boolean softspace; public boolean closed; private FileWrapper file; private static java.io.InputStream _pb(java.io.InputStream s, String mode) { if (mode.indexOf('b') < 0) { try { s = (java.io.PushbackInputStream)s; } catch (ClassCastException e) { s = new java.io.PushbackInputStream(s); } } return s; } public PyFile(FileWrapper file, String name, String mode) { file.setMode(mode); this.name = name; this.mode = mode; this.softspace = false; this.closed = false; if (mode.indexOf('b') < 0) this.file = new TextWrapper(file); else this.file = file; } public PyFile(java.io.InputStream istream, java.io.OutputStream ostream, String name, String mode) { this(new IOStreamWrapper(_pb(istream, mode), ostream), name, mode); } public PyFile(java.io.InputStream istream, java.io.OutputStream ostream, String name) { this(istream, ostream, name, "r+"); } public PyFile(java.io.InputStream istream, java.io.OutputStream ostream) { this(istream, ostream, "", "r+"); } public PyFile(java.io.InputStream istream, String name, String mode) { this(new InputStreamWrapper(_pb(istream, mode)), name, mode); } public PyFile(java.io.InputStream istream, String name) { this(istream, name, "r"); } public PyFile(java.io.InputStream istream) { this(istream, "", "r"); } public PyFile(java.io.OutputStream ostream, String name, String mode) { this(new OutputStreamWrapper(ostream), name, mode); } public PyFile(java.io.OutputStream ostream, String name) { this(ostream, name, "w"); } public PyFile(java.io.OutputStream ostream) { this(ostream, "", "w"); } public PyFile(java.io.Writer ostream, String name, String mode) { this(new WriterWrapper(ostream), name, mode); } public PyFile(java.io.Writer ostream, String name) { this(ostream, name, "w"); } public PyFile(java.io.Writer ostream) { this(ostream, "", "w"); } public PyFile(java.io.RandomAccessFile file, String name, String mode) { this(new RFileWrapper(file), name, mode); } public PyFile(java.io.RandomAccessFile file, String name) { this(file, name, "r+"); } public PyFile(java.io.RandomAccessFile file) { this(file, "", "r+"); } public PyFile(String name, String mode, int bufsize) { this(_setup(name, mode, bufsize), name, mode); } public void __setattr__(String name, PyObject value) { // softspace is the only writeable file object attribute if (name == "softspace") softspace = value.__nonzero__(); else if (name == "mode" || name == "closed" || name == "name") throw Py.TypeError("readonly attribute: " + name); else throw Py.AttributeError(name); } public Object __tojava__(Class cls) { Object o = null; try { o = file.__tojava__(cls); } catch (java.io.IOException exc) { } if (o == null) o = super.__tojava__(cls); return o; } private static FileWrapper _setup(String name, String mode, int bufsize) { char c1 = ' '; char c2 = ' '; char c3 = ' '; int n = mode.length(); for (int i = 0; i < n; i++) { if ("awrtb+".indexOf(mode.charAt(i)) < 0) throw Py.IOError("Unknown open mode:" + mode); } if (n > 0) { c1 = mode.charAt(0); if (n > 1) { c2 = mode.charAt(1); if (n > 2) c3 = mode.charAt(2); } } String jmode = "r"; if (c1 == 'r') { if (c2 == '+' || c3 == '+') jmode = "rw"; else jmode = "r"; } else if (c1 == 'w' || c1 == 'a') jmode = "rw"; try { java.io.File f = new java.io.File(name); if (c1 == 'w') { // Hack to truncate the file without deleting it: // create a FileOutputStream for it and close it again. java.io.FileOutputStream fo = new java.io.FileOutputStream(f); fo.close(); fo = null; } // What about bufsize? java.io.RandomAccessFile rfile = new java.io.RandomAccessFile(f, jmode); RFileWrapper iofile = new RFileWrapper(rfile); if (c1 == 'a') iofile.seek(0, 2); return iofile; } catch (java.io.IOException e) { throw Py.IOError(e); } } public PyString read(int n) { if (closed) err_closed(); StringBuffer data = new StringBuffer(); try { while (n != 0) { String s = file.read(n); int len = s.length(); if (len == 0) break; data.append(s); if (n > 0) { n -= len; if (n <= 0) break; } } } catch (java.io.IOException e) { throw Py.IOError(e); } return new PyString(data.toString()); } public PyString read() { return read(-1); } public PyString readline(int max) { if (closed) err_closed(); StringBuffer s = new StringBuffer(); while (max < 0 || s.length() < max) { int c; try { c = file.read(); } catch (java.io.IOException e) { throw Py.IOError(e); } if (c < 0) break; s.append((char)c); if ((char)c == '\n') break; } return new PyString(s.toString()); } public PyString readline() { return readline(-1); } public PyObject readlines(int sizehint) { if (closed) err_closed(); PyList list = new PyList(); int bytesread = 0; for (;;) { PyString s = readline(); int len = s.__len__(); if (len == 0) // EOF break; bytesread += len; list.append(s); if (sizehint > 0 && bytesread > sizehint) break; } return list; } public PyObject readlines() { return readlines(0); } public PyObject __iter__() { return this; } public PyObject __iternext__() { PyString s = readline(); if (s.__len__() == 0) return null; return s; } public PyObject next() { PyObject ret = __iternext__(); if (ret == null) throw Py.StopIteration(""); return ret; } public PyObject xreadlines() { return this; } public void write(String s) { if (closed) err_closed(); try { file.write(s); softspace = false; } catch (java.io.IOException e) { throw Py.IOError(e); } } public void writelines(PyObject a) { PyObject item = null; for (int i = 0; (item = a.__finditem__(i)) != null; i++) { if (!(item instanceof PyString)) throw Py.TypeError("writelines() argument must be a " + "sequence of strings"); write(item.toString()); } } public long tell() { if (closed) err_closed(); try { return file.tell(); } catch (java.io.IOException e) { throw Py.IOError(e); } } public void seek(long pos, int how) { if (closed) err_closed(); try { file.seek(pos, how); } catch (java.io.IOException e) { throw Py.IOError(e); } } public void seek(long pos) { seek(pos, 0); } public void flush() { if (closed) err_closed(); try { file.flush(); } catch (java.io.IOException e) { throw Py.IOError(e); } } public void close() { try { file.close(); } catch (java.io.IOException e) { throw Py.IOError(e); } closed = true; file = new FileWrapper(); } public void truncate() { try { file.truncate(file.tell()); } catch (java.io.IOException e) { throw Py.IOError(e); } } public void truncate(long position) { try { file.truncate(position); } catch (java.io.IOException e) { throw Py.IOError(e); } } // TBD: should this be removed? I think it's better to raise an // AttributeError than an IOError here. public PyObject fileno() { throw Py.IOError("fileno() is not supported in jpython"); } public String toString() { return ""; } private void err_closed() { throw Py.ValueError("I/O operation on closed file"); } }