[armedbear-cvs] r11403 - in branches/open-external-format/src/org/armedbear/lisp: . util
Erik Huelsmann
ehuelsmann at common-lisp.net
Sat Nov 29 21:40:19 UTC 2008
Author: ehuelsmann
Date: Sat Nov 29 21:40:18 2008
New Revision: 11403
Log:
Adjust integration between RandomAccessCharacterFile and FileStream.
Patch by: Hideo at Yokohama
Modified:
branches/open-external-format/src/org/armedbear/lisp/FileStream.java
branches/open-external-format/src/org/armedbear/lisp/open.lisp
branches/open-external-format/src/org/armedbear/lisp/util/RandomAccessCharacterFile.java
Modified: branches/open-external-format/src/org/armedbear/lisp/FileStream.java
==============================================================================
--- branches/open-external-format/src/org/armedbear/lisp/FileStream.java (original)
+++ branches/open-external-format/src/org/armedbear/lisp/FileStream.java Sat Nov 29 21:40:18 2008
@@ -2,6 +2,7 @@
* FileStream.java
*
* Copyright (C) 2004-2006 Peter Graves
+ * Copyright (C) 2008 Hideo at Yokohama
* $Id$
*
* This program is free software; you can redistribute it and/or
@@ -34,6 +35,10 @@
package org.armedbear.lisp;
import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
@@ -42,10 +47,12 @@
public final class FileStream extends Stream
{
private final RandomAccessCharacterFile racf;
- private final RandomAccessCharacterFile in;
- private final RandomAccessCharacterFile out;
private final Pathname pathname;
private final int bytesPerUnit;
+ private InputStream inst;
+ private OutputStream outst;
+ private Reader reader;
+ private Writer writer;
public enum EolStyle {
CR,
@@ -94,9 +101,7 @@
Debug.assertTrue(mode != null);
RandomAccessFile raf = new RandomAccessFile(file, mode);
- racf = new RandomAccessCharacterFile(raf, encoding);
- in = isInputStream ? racf : null;
- out = isOutputStream ? racf : null;
+
// ifExists is ignored unless we have an output stream.
if (isOutputStream) {
final long length = file.isFile() ? file.length() : 0;
@@ -109,11 +114,21 @@
raf.setLength(0);
}
}
+ // don't touch raf directly after passing it to racf.
+ // the state will become inconsistent if you do that.
+ racf = new RandomAccessCharacterFile(raf, encoding);
+
this.pathname = pathname;
this.elementType = elementType;
if (elementType == Symbol.CHARACTER || elementType == Symbol.BASE_CHAR) {
isCharacterStream = true;
bytesPerUnit = 1;
+ if (isInputStream) {
+ reader = racf.getReader();
+ }
+ if (isOutputStream) {
+ writer = racf.getWriter();
+ }
} else {
isBinaryStream = true;
int width;
@@ -124,6 +139,12 @@
width = 8;
}
bytesPerUnit = width / 8;
+ if (isInputStream) {
+ inst = racf.getInputStream();
+ }
+ if (isOutputStream) {
+ outst = racf.getOutputStream();
+ }
}
eolChar = (eol == EolStyle.CR) ? '\r' : '\n';
}
@@ -159,12 +180,13 @@
public LispObject listen() throws ConditionThrowable
{
try {
- return in.dataIsAvailableForRead() ? T : NIL;
- }
- catch (NullPointerException e) {
- streamNotInputStream();
+ if (isInputStream) {
+ return (racf.position() < racf.length()) ? T : NIL;
+ } else {
+ streamNotInputStream();
+ }
}
- catch (IOException e) {
+ catch (IOException e) {
error(new StreamError(this, e));
}
// Not reached.
@@ -204,27 +226,28 @@
protected int _readChar() throws ConditionThrowable
{
try {
- int c = in.getReader().read();
+ int c = reader.read();
if (eolStyle == EolStyle.CRLF) {
if (c == '\r') {
- long mark = in.position();
- int c2 = in.getReader().read();
+ int c2 = reader.read();
if (c2 == '\n') {
++lineNumber;
return c2;
- }
- // '\r' was not followed by '\n'
- // we cannot depend on characters to contain 1 byte only
- // so we need to revert to the last known position.
- in.position(mark);
+ } else {
+ // '\r' was not followed by '\n'
+ // we cannot depend on characters to contain 1 byte only
+ // so we need to revert to the last known position.
+ // The classical use case for unreadChar
+ racf.unreadChar((char)c2);
+ }
}
return c;
- }
- if (c == eolChar) {
+ } else if (c == eolChar) {
++lineNumber;
return c;
- }
- return c;
+ } else {
+ return c;
+ }
}
catch (NullPointerException e) {
streamNotInputStream();
@@ -240,7 +263,7 @@
protected void _unreadChar(int n) throws ConditionThrowable
{
try {
- in.unreadChar((char)n);
+ racf.unreadChar((char)n);
}
catch (IOException e) {
error(new StreamError(this, e));
@@ -259,11 +282,11 @@
try {
if (c == '\n') {
if (eolStyle == EolStyle.CRLF)
- out.getWriter().write((byte)'\r');
- out.getWriter().write((byte)eolChar);
+ writer.write('\r');
+ writer.write(eolChar);
charPos = 0;
} else {
- out.getWriter().write((byte)c);
+ writer.write(c);
++charPos;
}
}
@@ -272,20 +295,41 @@
}
}
- @Override
+
public void _writeChars(char[] chars, int start, int end)
+ throws ConditionThrowable {
+ _writeChars(chars, start, end, true);
+ }
+
+ public void _writeChars(char[] chars, int start, int end, boolean maintainCharPos)
throws ConditionThrowable
{
try {
- if (eolStyle == EolStyle.CRLF) {
+ if (eolStyle == EolStyle.LF) {
+ /* we can do a little bit better in this special case */
+ writer.write(chars, start, end);
+ if (maintainCharPos) {
+ int lastlfpos = -1;
+ for (int i = start; i < end; i++) {
+ if (chars[i] == '\n') {
+ lastlfpos = i;
+ }
+ }
+ if (lastlfpos == -1) {
+ charPos += end - start;
+ } else {
+ charPos = end - lastlfpos;
+ }
+ }
+ } else if (eolStyle == EolStyle.CRLF) {
for (int i = start; i < end; i++) {
char c = chars[i];
if (c == '\n') {
- out.getWriter().write((byte)'\r');
- out.getWriter().write((byte)'\n');
+ writer.write('\r');
+ writer.write('\n');
charPos = 0;
} else {
- out.getWriter().write((byte)c);
+ writer.write(c);
++charPos;
}
}
@@ -293,10 +337,10 @@
for (int i = start; i < end; i++) {
char c = chars[i];
if (c == '\n') {
- out.getWriter().write((byte)eolChar);
+ writer.write(eolChar);
charPos = 0;
} else {
- out.getWriter().write((byte)c);
+ writer.write(c);
++charPos;
}
}
@@ -310,13 +354,13 @@
@Override
public void _writeString(String s) throws ConditionThrowable
{
- _writeChars(s.toCharArray(), 0, s.length());
+ _writeChars(s.toCharArray(), 0, s.length(), true);
}
@Override
public void _writeLine(String s) throws ConditionThrowable
{
- _writeString(s);
+ _writeChars(s.toCharArray(), 0, s.length(), false);
if (eolStyle == EolStyle.CRLF)
_writeChar('\r');
_writeChar(eolChar);
@@ -328,7 +372,7 @@
public int _readByte() throws ConditionThrowable
{
try {
- return in.getInputStream().read(); // Reads an 8-bit byte.
+ return inst.read(); // Reads an 8-bit byte.
}
catch (NullPointerException e) {
streamNotInputStream();
@@ -345,7 +389,7 @@
public void _writeByte(int n) throws ConditionThrowable
{
try {
- out.getOutputStream().write(n); // Writes an 8-bit byte.
+ outst.write(n); // Writes an 8-bit byte.
}
catch (NullPointerException e) {
streamNotOutputStream();
@@ -359,10 +403,11 @@
public void _clearInput() throws ConditionThrowable
{
try {
- in.position(in.length());
- }
- catch (NullPointerException e) {
- streamNotInputStream();
+ if (isInputStream) {
+ racf.position(racf.length());
+ } else {
+ streamNotInputStream();
+ }
}
catch (IOException e) {
error(new StreamError(this, e));
@@ -422,7 +467,7 @@
return unreadableString(Symbol.FILE_STREAM);
}
- // ### make-file-stream pathname namestring element-type direction if-exists => stream
+ // ### make-file-stream pathname namestring element-type direction if-exists external-format => stream
private static final Primitive MAKE_FILE_STREAM =
new Primitive("make-file-stream", PACKAGE_SYS, true,
"pathname namestring element-type direction if-exists external-format")
@@ -454,16 +499,20 @@
String encoding = "ISO-8859-1";
if (externalFormat != NIL) {
- Symbol enc = (Symbol)externalFormat.car(); //FIXME: class cast exception to be caught
- if (enc != NIL) {
- if (enc != keywordCodePage) {
- encoding = enc.getName();
- }
- //FIXME: the else for the keywordCodePage to be filled in
- }
- //FIXME: the else for the == NIL to be filled in: raise an error...
+ if (externalFormat instanceof Symbol) {
+ Symbol enc = (Symbol)externalFormat; //FIXME: class cast exception to be caught
+ if (enc != NIL) {
+ if (enc != keywordCodePage) {
+ encoding = enc.getName();
+ }
+ //FIXME: the else for the keywordCodePage to be filled in
+ }
+ //FIXME: the else for the == NIL to be filled in: raise an error...
+ } else if (externalFormat instanceof AbstractString) {
+ AbstractString encName = (AbstractString) externalFormat;
+ encoding = encName.getStringValue();
+ }
}
-
if (direction != Keyword.INPUT && direction != Keyword.OUTPUT &&
Modified: branches/open-external-format/src/org/armedbear/lisp/open.lisp
==============================================================================
--- branches/open-external-format/src/org/armedbear/lisp/open.lisp (original)
+++ branches/open-external-format/src/org/armedbear/lisp/open.lisp Sat Nov 29 21:40:18 2008
@@ -106,7 +106,7 @@
(if-exists nil if-exists-given)
(if-does-not-exist nil if-does-not-exist-given)
(external-format :default))
- (declare (ignore external-format)) ; FIXME
+; (declare (ignore external-format)) ; FIXME
(setf element-type (case element-type
((character base-char)
'character)
@@ -143,7 +143,7 @@
:pathname pathname
:format-control "The file ~S does not exist."
:format-arguments (list namestring)))))
- (make-file-stream pathname namestring element-type :input nil nil))
+ (make-file-stream pathname namestring element-type :input nil external-format))
(:probe
(case if-does-not-exist
(:error
@@ -158,7 +158,7 @@
;; not yet exist." See java.io.File.createNewFile().
(create-new-file namestring)))
(let ((stream (make-file-stream pathname namestring element-type
- :input nil nil)))
+ :input nil external-format)))
(when stream
(close stream))
stream))
@@ -206,7 +206,7 @@
:format-control "Option not supported: ~S."
:format-arguments (list if-exists))))
(let ((stream (make-file-stream pathname namestring element-type
- direction if-exists nil)))
+ direction if-exists external-format)))
(unless stream
(error 'file-error
:pathname pathname
Modified: branches/open-external-format/src/org/armedbear/lisp/util/RandomAccessCharacterFile.java
==============================================================================
--- branches/open-external-format/src/org/armedbear/lisp/util/RandomAccessCharacterFile.java (original)
+++ branches/open-external-format/src/org/armedbear/lisp/util/RandomAccessCharacterFile.java Sat Nov 29 21:40:18 2008
@@ -40,6 +40,8 @@
import java.io.RandomAccessFile;
import java.io.Reader;
import java.io.Writer;
+import java.io.PrintWriter;
+import java.io.FileWriter;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
@@ -50,397 +52,399 @@
public class RandomAccessCharacterFile {
- public class RandomAccessInputStream extends InputStream {
+ private class RandomAccessInputStream extends InputStream {
- private RandomAccessCharacterFile racf;
+ private RandomAccessInputStream() {
+ }
+
+ private byte[] buf = new byte[1];
- public RandomAccessInputStream(RandomAccessCharacterFile racf) {
- this.racf = racf;
- }
- private byte[] buf = new byte[1];
-
- public int read() throws IOException {
- int len = read(buf);
- if (len == 1) {
- // byte is signed, char is unsigned, int is signed.
- // buf can hold 0xff, we want it as 0xff in int, not -1.
- return 0xff & (int) buf[0];
- } else {
- return -1;
- }
- }
+ public int read() throws IOException {
+ int len = read(buf);
+ if (len == 1) {
+ // byte is signed, char is unsigned, int is signed.
+ // buf can hold 0xff, we want it as 0xff in int, not -1.
+ return 0xff & (int) buf[0];
+ } else {
+ return -1;
+ }
+ }
- @Override
- public int read(byte[] b, int off, int len) throws IOException {
- return racf.read(b, off, len);
- }
- }
-
- public class RandomAccessOutputStream extends OutputStream {
-
- private RandomAccessCharacterFile racf;
-
- public RandomAccessOutputStream(RandomAccessCharacterFile racf) {
- this.racf = racf;
- }
-
- private byte[] buf = new byte[1];
- public void write(int b) throws IOException {
- buf[0] = (byte)b;
- write(buf);
- }
-
- @Override
- public void write(byte[] b, int off, int len) throws IOException {
- racf.write(b, off, len);
- }
- }
-
- public class RandomAccessReader extends Reader {
-
- private RandomAccessCharacterFile racf;
-
- public RandomAccessReader(
- RandomAccessCharacterFile racf) {
- this.racf = racf;
- }
-
- public void close() throws IOException {
- racf.close();
- }
-
- public int read(char[] cb, int off, int len) throws IOException {
- return racf.read(cb, off, len);
- }
- }
-
- public class RandomAccessWriter extends Writer {
-
- private RandomAccessCharacterFile racf;
-
- public RandomAccessWriter(
- RandomAccessCharacterFile racf) {
- this.racf = racf;
- }
-
- public void close() throws IOException {
- racf.close();
- }
-
- public void flush() throws IOException {
- racf.flush();
- }
-
- public void write(char[] cb, int off, int len) throws IOException {
- racf.write(cb, off, len);
- }
-
- }
-
-
- final static int BUFSIZ = 4*1024; // setting this to a small value like 8 is helpful for testing.
-
- private RandomAccessWriter writer;
- private RandomAccessReader reader;
- private RandomAccessInputStream inputStream;
- private RandomAccessOutputStream outputStream;
- private FileChannel fcn;
- private long fcnpos; /* where fcn is pointing now. */
- private long fcnsize; /* the file size */
-
- private Charset cset;
- private CharsetEncoder cenc;
- private CharsetDecoder cdec;
-
- /**
- * bbuf is treated as a cache of the file content.
- * If it points to somewhere in the middle of the file, it holds the copy of the file content,
- * even when you are writing a large chunk of data. If you write in the middle of a file,
- * bbuf first gets filled with contents of the data, and only after that any new data is
- * written on bbuf.
- * The exception is when you are appending data at the end of the file.
- */
- private ByteBuffer bbuf;
- private boolean bbufIsDirty; /* whether bbuf holds data that must be written. */
- private long bbufpos; /* where the beginning of bbuf is pointing in the file now. */
-
- public RandomAccessCharacterFile(RandomAccessFile raf, String encoding) throws IOException {
- fcn = raf.getChannel();
- fcnpos = 0; // fcn points at BOF.
- fcnsize = fcn.size();
-
- cset = Charset.forName(encoding);
- cdec = cset.newDecoder();
- cenc = cset.newEncoder();
-
- bbuf = ByteBuffer.allocate(BUFSIZ);
-
- // there is no readable data available in the buffers.
- bbuf.flip();
-
- // there is no write pending data in the buffers.
- bbufIsDirty = false;
-
- bbufpos = fcn.position(); // so as the byte buffer.
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ return RandomAccessCharacterFile.this.read(b, off, len);
+ }
- reader = new RandomAccessReader(this);
- writer = new RandomAccessWriter(this);
- inputStream = new RandomAccessInputStream(this);
- outputStream = new RandomAccessOutputStream(this);
+ public void close() throws IOException {
+ RandomAccessCharacterFile.this.close();
}
-
- public Writer getWriter() {
- return writer;
+ }
+
+ private class RandomAccessOutputStream extends OutputStream {
+
+ private RandomAccessOutputStream() {
}
-
- public Reader getReader() {
- return reader;
+
+ private byte[] buf = new byte[1];
+ public void write(int b) throws IOException {
+ buf[0] = (byte)b;
+ write(buf);
}
-
- public InputStream getInputStream() {
- return inputStream;
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ RandomAccessCharacterFile.this.write(b, off, len);
}
-
- public OutputStream getOutputStream() {
- return outputStream;
+
+ public void flush() throws IOException {
+ RandomAccessCharacterFile.this.flush();
}
-
+
public void close() throws IOException {
- internalFlush(true);
- fcn.close();
- }
-
- public void flush() throws IOException {
- internalFlush(false);
+ RandomAccessCharacterFile.this.close();
}
+ }
- public int read(char[] cb, int off, int len) throws IOException {
- CharBuffer cbuf = CharBuffer.wrap(cb, off, len);
- boolean decodeWasUnderflow = false;
- boolean atEof = false;
- while ((cbuf.remaining() > 0) && dataIsAvailableForRead()
- && ! atEof) {
- if ((bbuf.remaining() == 0) || decodeWasUnderflow) {
- // need to read from the file.
- flushBbuf(); // in case bbuf is dirty.
- // update bbufpos.
- bbufpos += bbuf.position();
- int partialBytes = bbuf.remaining(); // partialBytes > 0 happens when decodeWasUnderflow
- // if reads and writes are mixed, we may need to seek first.
- if (bbufpos + partialBytes != fcnpos) {
- fcn.position(bbufpos + partialBytes);
- }
- // need to read data from file.
- bbuf.compact();
- //###FIXME: we're ignoring end-of-stream here!!!
- atEof = (fcn.read(bbuf) == -1);
- bbuf.flip();
- fcnpos = bbufpos + bbuf.remaining();
- }
- CoderResult r = cdec.decode(bbuf, cbuf, pointingAtEOF() );
- decodeWasUnderflow = (CoderResult.UNDERFLOW == r);
- }
- if (cbuf.remaining() == len) {
- return -1;
- } else {
- return len - cbuf.remaining();
- }
+ private class RandomAccessReader extends Reader {
+
+ private RandomAccessReader() {
}
- public boolean dataIsAvailableForRead() throws IOException {
- return ((bbuf.remaining() > 0) || (fcn.position() < fcn.size()));
+ public void close() throws IOException {
+ RandomAccessCharacterFile.this.close();
}
-
- private boolean pointingAtEOF() {
- return (bbuf.remaining() == 0) && (fcnpos == fcnsize);
+
+ @Override
+ public int read(char[] cb, int off, int len) throws IOException {
+ return RandomAccessCharacterFile.this.read(cb, off, len);
}
+ }
+
+ private class RandomAccessWriter extends Writer {
- public void write(char[] cb, int off, int len) throws IOException {
- CharBuffer cbuf = CharBuffer.wrap(cb, off, len);
- encodeAndWrite(cbuf, false, false);
+ private RandomAccessWriter() {
}
- private void internalFlush(boolean endOfFile) throws IOException {
- if (endOfFile) {
- CharBuffer cbuf = CharBuffer.allocate(0);
- encodeAndWrite(cbuf, true, endOfFile);
- } else {
- flushBbuf();
- }
+ public void close() throws IOException {
+ RandomAccessCharacterFile.this.close();
}
- private void encodeAndWrite(CharBuffer cbuf, boolean flush, boolean endOfFile) throws IOException {
- if (bbufpos == fcnsize) {
- bbuf.clear();
- }
- while (cbuf.remaining() > 0) {
- CoderResult r = cenc.encode(cbuf, bbuf, endOfFile);
- bbufIsDirty = true;
- long curpos = bbufpos + bbuf.position();
- if (curpos > fcnsize) {
- // the file is extended.
- fcnsize = curpos;
- }
- if (CoderResult.OVERFLOW == r || bbuf.remaining() == 0) {
- flushBbuf();
- bbufpos += bbuf.limit();
- bbuf.clear();
- if (fcnpos < fcnsize) {
- fcn.read(bbuf);
- bbuf.flip();
- fcnpos += bbuf.remaining();
- }
- // if we are at the end of file, bbuf is simply cleared.
- // in that case, bbufpos + bbuf.position points to the EOF, not fcnpos.
- }
- }
- if (bbuf.position() > 0 && bbufIsDirty && flush) {
- flushBbuf();
- }
+ public void flush() throws IOException {
+ RandomAccessCharacterFile.this.flush();
}
- public void position(long newPosition) throws IOException {
- flushBbuf();
- long bbufend = bbufpos + bbuf.limit();
- if (newPosition >= bbufpos && newPosition < bbufend) {
- // near seek. within existing data of bbuf.
- bbuf.position((int)(newPosition - bbufpos));
- } else {
- // far seek. discard the buffer.
- flushBbuf();
- fcn.position(newPosition);
- fcnpos = newPosition;
- bbuf.clear();
- bbuf.flip(); // "there is no useful data on this buffer yet."
- bbufpos = fcnpos;
- }
+ @Override
+ public void write(char[] cb, int off, int len) throws IOException {
+ RandomAccessCharacterFile.this.write(cb, off, len);
}
+
+ }
+
+
+ final static int BUFSIZ = 4*1024; // setting this to a small value like 8 is helpful for testing.
- public long position() throws IOException {
- flushBbuf();
- return bbufpos + bbuf.position(); // the logical position within the file.
- }
+ private RandomAccessWriter writer;
+ private RandomAccessReader reader;
+ private RandomAccessInputStream inputStream;
+ private RandomAccessOutputStream outputStream;
+ private FileChannel fcn;
+ private long fcnpos; /* where fcn is pointing now. */
+ private long fcnsize; /* the file size */
+
+ private Charset cset;
+ private CharsetEncoder cenc;
+ private CharsetDecoder cdec;
+
+ /**
+ * bbuf is treated as a cache of the file content.
+ * If it points to somewhere in the middle of the file, it holds the copy of the file content,
+ * even when you are writing a large chunk of data. If you write in the middle of a file,
+ * bbuf first gets filled with contents of the data, and only after that any new data is
+ * written on bbuf.
+ * The exception is when you are appending data at the end of the file.
+ */
+ private ByteBuffer bbuf;
+ private boolean bbufIsDirty; /* whether bbuf holds data that must be written. */
+ private long bbufpos; /* where the beginning of bbuf is pointing in the file now. */
- public long length() throws IOException {
- flushBbuf();
- return fcn.size();
- }
-
- private void flushBbuf() throws IOException {
- if (bbufIsDirty) {
- if (fcnpos != bbufpos) {
- fcn.position(bbufpos);
- }
- bbuf.position(0);
- if (bbufpos + bbuf.limit() > fcnsize) {
- // the buffer is at the end of the file.
- // area beyond fcnsize does not have data.
- bbuf.limit((int)(fcnsize - bbufpos));
- }
- fcn.write(bbuf);
- fcnpos = bbufpos + bbuf.limit();
- bbufIsDirty = false;
- }
- }
+ public RandomAccessCharacterFile(RandomAccessFile raf, String encoding) throws IOException {
- public int read(byte[] b, int off, int len) throws IOException {
- int pos = off;
- boolean atEof = false;
- while (pos - off < len && dataIsAvailableForRead()
- && ! atEof) {
- if (bbuf.remaining() == 0) {
- // need to read from the file.
- flushBbuf(); // in case bbuf is dirty.
- // update bbufpos.
- bbufpos += bbuf.limit();
- // if reads and writes are mixed, we may need to seek first.
- if (bbufpos != fcnpos) {
- fcn.position(bbufpos);
- }
- // need to read data from file.
- bbuf.clear();
- atEof = (fcn.read(bbuf) == -1);
- bbuf.flip();
- fcnpos = bbufpos + bbuf.remaining();
- }
- int want = len - pos;
- if (want > bbuf.remaining()) {
- want = bbuf.remaining();
- }
- bbuf.get(b, pos, want);
- pos += want;
- }
- return pos - off;
+ fcn = raf.getChannel();
+ fcnpos = fcn.position();
+ fcnsize = fcn.size();
+
+ cset = Charset.forName(encoding);
+ cdec = cset.newDecoder();
+ cenc = cset.newEncoder();
+
+ bbuf = ByteBuffer.allocate(BUFSIZ);
+
+ // there is no readable data available in the buffers.
+ bbuf.flip();
+
+ // there is no write pending data in the buffers.
+ bbufIsDirty = false;
+
+ bbufpos = fcn.position();
+
+ reader = new RandomAccessReader();
+ writer = new RandomAccessWriter();
+ inputStream = new RandomAccessInputStream();
+ outputStream = new RandomAccessOutputStream();
+ }
+
+ public Writer getWriter() {
+ return writer;
+ }
+
+ public Reader getReader() {
+ return reader;
+ }
+
+ public InputStream getInputStream() {
+ return inputStream;
+ }
+
+ public OutputStream getOutputStream() {
+ return outputStream;
+ }
+
+ public void close() throws IOException {
+ internalFlush(true);
+ fcn.close();
+ }
+
+ public void flush() throws IOException {
+ internalFlush(false);
+ }
+
+ private int read(char[] cb, int off, int len) throws IOException {
+ CharBuffer cbuf = CharBuffer.wrap(cb, off, len);
+ boolean decodeWasUnderflow = false;
+ boolean atEof = false;
+ while ((cbuf.remaining() > 0) && dataIsAvailableForRead()
+ && ! atEof) {
+ if ((bbuf.remaining() == 0) || decodeWasUnderflow) {
+ // need to read from the file.
+ flushBbuf(); // in case bbuf is dirty.
+ // update bbufpos.
+ bbufpos += bbuf.position();
+ int partialBytes = bbuf.remaining(); // partialBytes > 0 happens when decodeWasUnderflow
+ // if reads and writes are mixed, we may need to seek first.
+ if (bbufpos + partialBytes != fcnpos) {
+ fcn.position(bbufpos + partialBytes);
+ }
+ // need to read data from file.
+ bbuf.compact();
+ //###FIXME: we're ignoring end-of-stream here!!!
+ atEof = (fcn.read(bbuf) == -1);
+ bbuf.flip();
+ fcnpos = bbufpos + bbuf.remaining();
+ }
+ CoderResult r = cdec.decode(bbuf, cbuf, pointingAtEOF() );
+ decodeWasUnderflow = (CoderResult.UNDERFLOW == r);
+ }
+ if (cbuf.remaining() == len) {
+ return -1;
+ } else {
+ return len - cbuf.remaining();
+ }
+ }
+
+ private boolean dataIsAvailableForRead() throws IOException {
+ return ((bbuf.remaining() > 0) || (fcn.position() < fcn.size()));
+ }
+
+ private boolean pointingAtEOF() {
+ return (bbuf.remaining() == 0) && (fcnpos == fcnsize);
+ }
+
+ private void write(char[] cb, int off, int len) throws IOException {
+ CharBuffer cbuf = CharBuffer.wrap(cb, off, len);
+ encodeAndWrite(cbuf, false, false);
+ }
+
+ private void internalFlush(boolean endOfFile) throws IOException {
+ if (endOfFile) {
+ CharBuffer cbuf = CharBuffer.allocate(0);
+ encodeAndWrite(cbuf, true, endOfFile);
+ } else {
+ flushBbuf();
+ }
+ }
+
+ private void encodeAndWrite(CharBuffer cbuf, boolean flush, boolean endOfFile) throws IOException {
+ if (bbufpos == fcnsize) {
+ bbuf.clear();
+ }
+ while (cbuf.remaining() > 0) {
+ CoderResult r = cenc.encode(cbuf, bbuf, endOfFile);
+ bbufIsDirty = true;
+ long curpos = bbufpos + bbuf.position();
+ if (curpos > fcnsize) {
+ // the file is extended.
+ fcnsize = curpos;
+ }
+ if (CoderResult.OVERFLOW == r || bbuf.remaining() == 0) {
+ flushBbuf();
+ bbufpos += bbuf.limit();
+ bbuf.clear();
+ if (fcnpos < fcnsize) {
+ fcn.read(bbuf);
+ bbuf.flip();
+ fcnpos += bbuf.remaining();
+ }
+ // if we are at the end of file, bbuf is simply cleared.
+ // in that case, bbufpos + bbuf.position points to the EOF, not fcnpos.
+ }
+ }
+ if (bbuf.position() > 0 && bbufIsDirty && flush) {
+ flushBbuf();
+ }
+ }
+
+ public void position(long newPosition) throws IOException {
+ flushBbuf();
+ long bbufend = bbufpos + bbuf.limit();
+ if (newPosition >= bbufpos && newPosition < bbufend) {
+ // near seek. within existing data of bbuf.
+ bbuf.position((int)(newPosition - bbufpos));
+ } else {
+ // far seek. discard the buffer.
+ flushBbuf();
+ fcn.position(newPosition);
+ fcnpos = newPosition;
+ bbuf.clear();
+ bbuf.flip(); // "there is no useful data on this buffer yet."
+ bbufpos = fcnpos;
+ }
+ }
+
+ public long position() throws IOException {
+ flushBbuf();
+ return bbufpos + bbuf.position(); // the logical position within the file.
+ }
+
+ public long length() throws IOException {
+ flushBbuf();
+ return fcn.size();
+ }
+
+ private void flushBbuf() throws IOException {
+ if (bbufIsDirty) {
+ if (fcnpos != bbufpos) {
+ fcn.position(bbufpos);
+ }
+ bbuf.position(0);
+ if (bbufpos + bbuf.limit() > fcnsize) {
+ // the buffer is at the end of the file.
+ // area beyond fcnsize does not have data.
+ bbuf.limit((int)(fcnsize - bbufpos));
+ }
+ fcn.write(bbuf);
+ fcnpos = bbufpos + bbuf.limit();
+ bbufIsDirty = false;
+ }
+ }
+
+ public int read(byte[] b, int off, int len) throws IOException {
+ int pos = off;
+ boolean atEof = false;
+ while (pos - off < len && dataIsAvailableForRead()
+ && ! atEof) {
+ if (bbuf.remaining() == 0) {
+ // need to read from the file.
+ flushBbuf(); // in case bbuf is dirty.
+ // update bbufpos.
+ bbufpos += bbuf.limit();
+ // if reads and writes are mixed, we may need to seek first.
+ if (bbufpos != fcnpos) {
+ fcn.position(bbufpos);
+ }
+ // need to read data from file.
+ bbuf.clear();
+ atEof = (fcn.read(bbuf) == -1);
+ bbuf.flip();
+ fcnpos = bbufpos + bbuf.remaining();
+ }
+ int want = len - pos;
+ if (want > bbuf.remaining()) {
+ want = bbuf.remaining();
+ }
+ bbuf.get(b, pos, want);
+ pos += want;
}
+ return pos - off;
+ }
- // a method corresponding to the good ol' ungetc in C.
- // This function may fail when using (combined) character codes that use
- // escape sequences to switch between sub-codes.
- // ASCII, ISO-8859 series, any 8bit code are OK, all unicode variations are OK,
- // but applications of the ISO-2022 encoding framework can have trouble.
- // Example of such code is ISO-2022-JP which is used in Japanese e-mail.
- private CharBuffer singleCharBuf;
- private ByteBuffer shortByteBuf;
- public void unreadChar(char c) throws IOException {
- // algorithm :
- // 1. encode c into bytes, to find out how many bytes it corresponds to
- // 2. move the position backwards that many bytes.
- // ** we stop here. Don't bother to write the bytes to the buffer,
- // assuming that it is the same as the original data.
- // If we allow to write back different characters, the buffer must get 'dirty'
- // but that would require read/write permissions on files you use unreadChar,
- // even if you are just reading for some tokenizer.
- //
- // So we don't do the following.
- // 3. write the bytes.
- // 4. move the position back again.
- if (singleCharBuf == null) {
- singleCharBuf = CharBuffer.allocate(1);
- shortByteBuf = ByteBuffer.allocate((int)cenc.maxBytesPerChar());
- }
- singleCharBuf.clear();
- singleCharBuf.append(c);
- singleCharBuf.flip();
- shortByteBuf.clear();
- cenc.encode(singleCharBuf, shortByteBuf, false);
- int n = shortByteBuf.position();
- long pos = position() - n;
- position(pos);
- }
-
- public void unreadByte(byte b) throws IOException {
- long pos = position() - 1;
- position(pos);
- }
-
- public void write(byte[] b, int off, int len) throws IOException {
- int pos = off;
- while (pos < off + len) {
- int want = len;
- if (want > bbuf.remaining()) {
- want = bbuf.remaining();
- }
- bbuf.put(b, pos, want);
- pos += want;
- bbufIsDirty = true;
- long curpos = bbufpos + bbuf.position();
- if (curpos > fcn.size()) {
- // the file is extended.
- fcnsize = curpos;
- }
- if (bbuf.remaining() == 0) {
- flushBbuf();
- bbufpos += bbuf.limit();
- bbuf.clear();
- if (fcn.position() < fcn.size()) {
- bbufpos = fcn.position();
- fcn.read(bbuf);
- bbuf.flip();
- fcnpos += bbuf.remaining();
- }
- // if we are at the end of file, bbuf is simply cleared.
- // in that case, bbufpos + bbuf.position points to the EOF, not fcnpos.
- }
- }
+ // a method corresponding to the good ol' ungetc in C.
+ // This function may fail when using (combined) character codes that use
+ // escape sequences to switch between sub-codes.
+ // ASCII, ISO-8859 series, any 8bit code are OK, all unicode variations are OK,
+ // but applications of the ISO-2022 encoding framework can have trouble.
+ // Example of such code is ISO-2022-JP which is used in Japanese e-mail.
+ private CharBuffer singleCharBuf;
+ private ByteBuffer shortByteBuf;
+ public void unreadChar(char c) throws IOException {
+ // algorithm :
+ // 1. encode c into bytes, to find out how many bytes it corresponds to
+ // 2. move the position backwards that many bytes.
+ // ** we stop here. Don't bother to write the bytes to the buffer,
+ // assuming that it is the same as the original data.
+ // If we allow to write back different characters, the buffer must get 'dirty'
+ // but that would require read/write permissions on files you use unreadChar,
+ // even if you are just reading for some tokenizer.
+ //
+ // So we don't do the following.
+ // 3. write the bytes.
+ // 4. move the position back again.
+ if (singleCharBuf == null) {
+ singleCharBuf = CharBuffer.allocate(1);
+ shortByteBuf = ByteBuffer.allocate((int)cenc.maxBytesPerChar());
+ }
+ singleCharBuf.clear();
+ singleCharBuf.append(c);
+ singleCharBuf.flip();
+ shortByteBuf.clear();
+ cenc.encode(singleCharBuf, shortByteBuf, false);
+ int n = shortByteBuf.position();
+ long pos = position() - n;
+ position(pos);
+ }
+
+ public void unreadByte(byte b) throws IOException {
+ long pos = position() - 1;
+ position(pos);
+ }
+
+ private void write(byte[] b, int off, int len) throws IOException {
+ int pos = off;
+ while (pos < off + len) {
+ int want = len;
+ if (want > bbuf.remaining()) {
+ want = bbuf.remaining();
+ }
+ bbuf.put(b, pos, want);
+ pos += want;
+ bbufIsDirty = true;
+ long curpos = bbufpos + bbuf.position();
+ if (curpos > fcn.size()) {
+ // the file is extended.
+ fcnsize = curpos;
+ }
+ if (bbuf.remaining() == 0) {
+ flushBbuf();
+ bbufpos += bbuf.limit();
+ bbuf.clear();
+ if (fcn.position() < fcn.size()) {
+ bbufpos = fcn.position();
+ fcn.read(bbuf);
+ bbuf.flip();
+ fcnpos += bbuf.remaining();
+ }
+ // if we are at the end of file, bbuf is simply cleared.
+ // in that case, bbufpos + bbuf.position points to the EOF, not fcnpos.
+ }
}
+ }
}
More information about the armedbear-cvs
mailing list