[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