Changeset 11403


Ignore:
Timestamp:
11/29/08 21:40:18 (13 years ago)
Author:
ehuelsmann
Message:

Adjust integration between RandomAccessCharacterFile? and FileStream?.

Patch by: Hideo at Yokohama

Location:
branches/open-external-format/src/org/armedbear/lisp
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • branches/open-external-format/src/org/armedbear/lisp/FileStream.java

    r11402 r11403  
    33 *
    44 * Copyright (C) 2004-2006 Peter Graves
     5 * Copyright (C) 2008 Hideo at Yokohama
    56 * $Id$
    67 *
     
    3536
    3637import java.io.File;
     38import java.io.InputStream;
     39import java.io.OutputStream;
     40import java.io.Reader;
     41import java.io.Writer;
    3742import java.io.FileNotFoundException;
    3843import java.io.IOException;
     
    4348{
    4449    private final RandomAccessCharacterFile racf;
    45     private final RandomAccessCharacterFile in;
    46     private final RandomAccessCharacterFile out;
    4750    private final Pathname pathname;
    4851    private final int bytesPerUnit;
     52    private InputStream inst;
     53    private OutputStream outst;
     54    private Reader reader;
     55    private Writer writer;
    4956
    5057    public enum EolStyle {
     
    95102        Debug.assertTrue(mode != null);
    96103        RandomAccessFile raf = new RandomAccessFile(file, mode);
    97         racf = new RandomAccessCharacterFile(raf, encoding);
    98         in = isInputStream ? racf : null;
    99         out = isOutputStream ? racf : null;
     104 
    100105        // ifExists is ignored unless we have an output stream.
    101106        if (isOutputStream) {
     
    110115            }
    111116        }
     117  // don't touch raf directly after passing it to racf.
     118  // the state will become inconsistent if you do that.
     119        racf = new RandomAccessCharacterFile(raf, encoding);
     120
    112121        this.pathname = pathname;
    113122        this.elementType = elementType;
     
    115124            isCharacterStream = true;
    116125            bytesPerUnit = 1;
     126      if (isInputStream) {
     127    reader = racf.getReader();
     128      }
     129      if (isOutputStream) {
     130    writer = racf.getWriter();
     131      }
    117132        } else {
    118133            isBinaryStream = true;
     
    125140            }
    126141            bytesPerUnit = width / 8;
     142      if (isInputStream) {
     143    inst = racf.getInputStream();
     144      }
     145      if (isOutputStream) {
     146    outst = racf.getOutputStream();
     147      }
    127148        }
    128149        eolChar = (eol == EolStyle.CR) ? '\r' : '\n';
     
    160181    {
    161182        try {
    162             return in.dataIsAvailableForRead() ? T : NIL;
    163         }
    164         catch (NullPointerException e) {
    165             streamNotInputStream();
    166         }
    167         catch (IOException e) {
     183      if (isInputStream) {
     184    return (racf.position() < racf.length()) ? T : NIL;
     185      } else {
     186    streamNotInputStream();
     187      }
     188        }
     189  catch (IOException e) {
    168190            error(new StreamError(this, e));
    169191        }
     
    205227    {
    206228        try {
    207             int c = in.getReader().read();
     229            int c = reader.read();
    208230            if (eolStyle == EolStyle.CRLF) {
    209231                if (c == '\r') {
    210                     long mark = in.position();
    211                     int c2 = in.getReader().read();
     232                    int c2 = reader.read();
    212233                    if (c2 == '\n') {
    213234                        ++lineNumber;
    214235                        return c2;
    215                     }
    216                     // '\r' was not followed by '\n'
    217                     // we cannot depend on characters to contain 1 byte only
    218                     // so we need to revert to the last known position.
    219                     in.position(mark);
     236                    } else {
     237      // '\r' was not followed by '\n'
     238      // we cannot depend on characters to contain 1 byte only
     239      // so we need to revert to the last known position.
     240      // The classical use case for unreadChar
     241      racf.unreadChar((char)c2);
     242        }
    220243                }
    221244                return c;
    222             }
    223             if (c == eolChar) {
     245            } else if (c == eolChar) {
    224246                ++lineNumber;
    225247                return c;
    226             }
    227             return c;
     248            } else {
     249    return c;
     250      }
    228251        }
    229252        catch (NullPointerException e) {
     
    241264    {
    242265        try {
    243             in.unreadChar((char)n);
     266            racf.unreadChar((char)n);
    244267        }
    245268        catch (IOException e) {
     
    260283            if (c == '\n') {
    261284                if (eolStyle == EolStyle.CRLF)
    262                     out.getWriter().write((byte)'\r');
    263                 out.getWriter().write((byte)eolChar);
     285                    writer.write('\r');
     286                writer.write(eolChar);
    264287                charPos = 0;
    265288            } else {
    266                 out.getWriter().write((byte)c);
     289                writer.write(c);
    267290                ++charPos;
    268291            }
     
    273296    }
    274297
    275     @Override
     298
    276299    public void _writeChars(char[] chars, int start, int end)
     300        throws ConditionThrowable {
     301  _writeChars(chars, start, end, true);
     302    }
     303
     304    public void _writeChars(char[] chars, int start, int end, boolean maintainCharPos)
    277305        throws ConditionThrowable
    278306    {
    279307        try {
    280             if (eolStyle == EolStyle.CRLF) {
     308      if (eolStyle == EolStyle.LF) {
     309    /* we can do a little bit better in this special case */
     310    writer.write(chars, start, end);
     311    if (maintainCharPos) {
     312        int lastlfpos = -1;
     313        for (int i = start; i < end; i++) {
     314      if (chars[i] == '\n') {
     315          lastlfpos = i;
     316      }
     317        }
     318        if (lastlfpos == -1) {
     319      charPos += end - start;
     320        } else {
     321      charPos = end - lastlfpos;
     322        }
     323    }
     324      } else if (eolStyle == EolStyle.CRLF) {
    281325                for (int i = start; i < end; i++) {
    282326                    char c = chars[i];
    283327                    if (c == '\n') {
    284                         out.getWriter().write((byte)'\r');
    285                         out.getWriter().write((byte)'\n');
     328                        writer.write('\r');
     329                        writer.write('\n');
    286330                        charPos = 0;
    287331                    } else {
    288                         out.getWriter().write((byte)c);
     332                        writer.write(c);
    289333                        ++charPos;
    290334                    }
     
    294338                    char c = chars[i];
    295339                    if (c == '\n') {
    296                         out.getWriter().write((byte)eolChar);
     340                        writer.write(eolChar);
    297341                        charPos = 0;
    298342                    } else {
    299                         out.getWriter().write((byte)c);
     343                        writer.write(c);
    300344                        ++charPos;
    301345                    }
     
    311355    public void _writeString(String s) throws ConditionThrowable
    312356    {
    313         _writeChars(s.toCharArray(), 0, s.length());
     357        _writeChars(s.toCharArray(), 0, s.length(), true);
    314358    }
    315359
     
    317361    public void _writeLine(String s) throws ConditionThrowable
    318362    {
    319         _writeString(s);
     363  _writeChars(s.toCharArray(), 0, s.length(), false);
    320364        if (eolStyle == EolStyle.CRLF)
    321365            _writeChar('\r');
     
    329373    {
    330374        try {
    331             return in.getInputStream().read(); // Reads an 8-bit byte.
     375            return inst.read(); // Reads an 8-bit byte.
    332376        }
    333377        catch (NullPointerException e) {
     
    346390    {
    347391        try {
    348             out.getOutputStream().write(n); // Writes an 8-bit byte.
     392            outst.write(n); // Writes an 8-bit byte.
    349393        }
    350394        catch (NullPointerException e) {
     
    360404    {
    361405        try {
    362             in.position(in.length());
    363         }
    364         catch (NullPointerException e) {
    365             streamNotInputStream();
     406      if (isInputStream) {
     407    racf.position(racf.length());
     408      } else {
     409    streamNotInputStream();
     410      }
    366411        }
    367412        catch (IOException e) {
     
    423468    }
    424469
    425     // ### make-file-stream pathname namestring element-type direction if-exists => stream
     470    // ### make-file-stream pathname namestring element-type direction if-exists external-format => stream
    426471    private static final Primitive MAKE_FILE_STREAM =
    427472        new Primitive("make-file-stream", PACKAGE_SYS, true,
     
    455500            String encoding = "ISO-8859-1";
    456501            if (externalFormat != NIL) {
    457                 Symbol enc = (Symbol)externalFormat.car(); //FIXME: class cast exception to be caught
    458                 if (enc != NIL) {
    459                     if (enc != keywordCodePage) {
    460                         encoding = enc.getName();
    461                     }
    462                     //FIXME: the else for the keywordCodePage to be filled in
    463                 }
    464                 //FIXME: the else for the == NIL to be filled in: raise an error...
    465             }
    466        
     502    if (externalFormat instanceof Symbol) {
     503        Symbol enc = (Symbol)externalFormat; //FIXME: class cast exception to be caught
     504        if (enc != NIL) {
     505      if (enc != keywordCodePage) {
     506          encoding = enc.getName();
     507      }
     508      //FIXME: the else for the keywordCodePage to be filled in
     509        }
     510        //FIXME: the else for the == NIL to be filled in: raise an error...
     511    } else if (externalFormat instanceof AbstractString) {
     512        AbstractString encName = (AbstractString) externalFormat;
     513        encoding = encName.getStringValue();
     514    }
     515            }
    467516           
    468517           
  • branches/open-external-format/src/org/armedbear/lisp/open.lisp

    r11395 r11403  
    107107       (if-does-not-exist nil if-does-not-exist-given)
    108108       (external-format :default))
    109   (declare (ignore external-format)) ; FIXME
     109;  (declare (ignore external-format)) ; FIXME
    110110  (setf element-type (case element-type
    111111                       ((character base-char)
     
    144144                   :format-control "The file ~S does not exist."
    145145                   :format-arguments (list namestring)))))
    146        (make-file-stream pathname namestring element-type :input nil nil))
     146       (make-file-stream pathname namestring element-type :input nil external-format))
    147147      (:probe
    148148       (case if-does-not-exist
     
    159159          (create-new-file namestring)))
    160160       (let ((stream (make-file-stream pathname namestring element-type
    161                                        :input nil nil)))
     161                                       :input nil external-format)))
    162162         (when stream
    163163           (close stream))
     
    207207                 :format-arguments (list if-exists))))
    208208       (let ((stream (make-file-stream pathname namestring element-type
    209                                        direction if-exists nil)))
     209                                       direction if-exists external-format)))
    210210         (unless stream
    211211           (error 'file-error
  • branches/open-external-format/src/org/armedbear/lisp/util/RandomAccessCharacterFile.java

    r11400 r11403  
    4141import java.io.Reader;
    4242import java.io.Writer;
     43import java.io.PrintWriter;
     44import java.io.FileWriter;
    4345import java.nio.ByteBuffer;
    4446import java.nio.CharBuffer;
     
    5153public class RandomAccessCharacterFile {
    5254
    53         public class RandomAccessInputStream extends InputStream {
    54 
    55                 private RandomAccessCharacterFile racf;
    56 
    57                 public RandomAccessInputStream(RandomAccessCharacterFile racf) {
    58                         this.racf = racf;
    59                 }
    60                 private byte[] buf = new byte[1];
    61 
    62                 public int read() throws IOException {
    63                         int len = read(buf);
    64                         if (len == 1) {
    65                                 // byte is signed, char is unsigned, int is signed.
    66                                 // buf can hold 0xff, we want it as 0xff in int, not -1.
    67                                 return 0xff & (int) buf[0];
    68                         } else {
    69                                 return -1;
    70                         }
    71                 }
     55    private class RandomAccessInputStream extends InputStream {
     56
     57  private RandomAccessInputStream() {
     58  }
     59 
     60  private byte[] buf = new byte[1];
     61
     62  public int read() throws IOException {
     63      int len = read(buf);
     64      if (len == 1) {
     65    // byte is signed, char is unsigned, int is signed.
     66    // buf can hold 0xff, we want it as 0xff in int, not -1.
     67    return 0xff & (int) buf[0];
     68      } else {
     69    return -1;
     70      }
     71  }
    7272               
    73                 @Override
    74                 public int read(byte[] b, int off, int len) throws IOException {
    75                         return racf.read(b, off, len);
    76                 }
    77         }
    78 
    79         public class RandomAccessOutputStream extends OutputStream {
    80 
    81                 private RandomAccessCharacterFile racf;
    82 
    83                 public RandomAccessOutputStream(RandomAccessCharacterFile racf) {
    84                         this.racf = racf;
    85                 }
    86 
    87                 private byte[] buf = new byte[1];
    88                 public void write(int b) throws IOException {
    89                         buf[0] = (byte)b;
    90                         write(buf);
    91                 }
    92 
    93                 @Override
    94                 public void write(byte[] b, int off, int len) throws IOException {
    95                         racf.write(b, off, len);
    96                 }
    97         }
    98 
    99         public class RandomAccessReader extends Reader {
    100 
    101                 private RandomAccessCharacterFile racf;
    102 
    103                 public RandomAccessReader(
    104                                 RandomAccessCharacterFile racf) {
    105                         this.racf = racf;
    106                 }
    107 
    108                 public void close() throws IOException {
    109                         racf.close();
    110                 }
    111 
    112                 public int read(char[] cb, int off, int len) throws IOException {
    113                         return racf.read(cb, off, len);
    114                 }
    115         }
    116 
    117         public class RandomAccessWriter extends Writer {
    118 
    119                 private RandomAccessCharacterFile racf;
    120 
    121                 public RandomAccessWriter(
    122                                 RandomAccessCharacterFile racf) {
    123                         this.racf = racf;
    124                 }
    125 
    126                 public void close() throws IOException {
    127                         racf.close();
    128                 }
    129 
    130                 public void flush() throws IOException {
    131                         racf.flush();
    132                 }
    133 
    134                 public void write(char[] cb, int off, int len) throws IOException {
    135                         racf.write(cb, off, len);
    136                 }
    137 
    138         }
    139 
    140 
    141   final static int BUFSIZ = 4*1024; // setting this to a small value like 8 is helpful for testing.
    142  
    143   private RandomAccessWriter writer;
    144   private RandomAccessReader reader;
    145   private RandomAccessInputStream inputStream;
    146   private RandomAccessOutputStream outputStream;
    147   private FileChannel fcn;
    148   private long fcnpos; /* where fcn is pointing now. */
    149   private long fcnsize; /* the file size */
    150  
    151   private Charset cset;
    152   private CharsetEncoder cenc;
    153   private CharsetDecoder cdec;
    154  
    155   /**
    156    * bbuf is treated as a cache of the file content.
    157    * If it points to somewhere in the middle of the file, it holds the copy of the file content,
    158    * even when you are writing a large chunk of data.  If you write in the middle of a file,
    159    * bbuf first gets filled with contents of the data, and only after that any new data is
    160    * written on bbuf.
    161    * The exception is when you are appending data at the end of the file.
    162    */
    163   private ByteBuffer bbuf;
    164   private boolean bbufIsDirty; /* whether bbuf holds data that must be written. */
    165   private long bbufpos; /* where the beginning of bbuf is pointing in the file now. */
    166 
    167   public RandomAccessCharacterFile(RandomAccessFile raf, String encoding) throws IOException {
    168     fcn = raf.getChannel();
    169     fcnpos = 0; // fcn points at BOF.
    170     fcnsize = fcn.size();
     73  @Override
     74      public int read(byte[] b, int off, int len) throws IOException {
     75      return RandomAccessCharacterFile.this.read(b, off, len);
     76  }
     77
     78  public void close() throws IOException {
     79      RandomAccessCharacterFile.this.close();
     80  }
     81    }
     82
     83    private class RandomAccessOutputStream extends OutputStream {
     84
     85  private RandomAccessOutputStream() {
     86  }
     87
     88  private byte[] buf = new byte[1];
     89  public void write(int b) throws IOException {
     90      buf[0] = (byte)b;
     91      write(buf);
     92  }
     93
     94  @Override
     95      public void write(byte[] b, int off, int len) throws IOException {
     96      RandomAccessCharacterFile.this.write(b, off, len);
     97  }
     98
     99  public void flush() throws IOException {
     100      RandomAccessCharacterFile.this.flush();
     101  }
     102
     103  public void close() throws IOException {
     104      RandomAccessCharacterFile.this.close();
     105  }
     106    }
     107
     108    private class RandomAccessReader extends Reader {
     109
     110  private RandomAccessReader() {
     111  }
     112
     113  public void close() throws IOException {
     114      RandomAccessCharacterFile.this.close();
     115  }
     116
     117  @Override
     118      public int read(char[] cb, int off, int len) throws IOException {
     119      return RandomAccessCharacterFile.this.read(cb, off, len);
     120  }
     121    }
     122
     123    private class RandomAccessWriter extends Writer {
     124
     125  private RandomAccessWriter() {
     126  }
     127
     128  public void close() throws IOException {
     129      RandomAccessCharacterFile.this.close();
     130  }
     131
     132  public void flush() throws IOException {
     133      RandomAccessCharacterFile.this.flush();
     134  }
     135
     136  @Override
     137      public void write(char[] cb, int off, int len) throws IOException {
     138      RandomAccessCharacterFile.this.write(cb, off, len);
     139  }
     140
     141    }
     142
     143
     144    final static int BUFSIZ = 4*1024; // setting this to a small value like 8 is helpful for testing.
     145 
     146    private RandomAccessWriter writer;
     147    private RandomAccessReader reader;
     148    private RandomAccessInputStream inputStream;
     149    private RandomAccessOutputStream outputStream;
     150    private FileChannel fcn;
     151    private long fcnpos; /* where fcn is pointing now. */
     152    private long fcnsize; /* the file size */
     153 
     154    private Charset cset;
     155    private CharsetEncoder cenc;
     156    private CharsetDecoder cdec;
     157 
     158    /**
     159     * bbuf is treated as a cache of the file content.
     160     * If it points to somewhere in the middle of the file, it holds the copy of the file content,
     161     * even when you are writing a large chunk of data.  If you write in the middle of a file,
     162     * bbuf first gets filled with contents of the data, and only after that any new data is
     163     * written on bbuf.
     164     * The exception is when you are appending data at the end of the file.
     165     */
     166    private ByteBuffer bbuf;
     167    private boolean bbufIsDirty; /* whether bbuf holds data that must be written. */
     168    private long bbufpos; /* where the beginning of bbuf is pointing in the file now. */
     169
     170    public RandomAccessCharacterFile(RandomAccessFile raf, String encoding) throws IOException {
     171
     172  fcn = raf.getChannel();
     173  fcnpos = fcn.position();
     174  fcnsize = fcn.size();
    171175   
    172     cset = Charset.forName(encoding);
    173     cdec = cset.newDecoder();
    174     cenc = cset.newEncoder();
     176  cset = Charset.forName(encoding);
     177  cdec = cset.newDecoder();
     178  cenc = cset.newEncoder();
    175179   
    176     bbuf = ByteBuffer.allocate(BUFSIZ);
     180  bbuf = ByteBuffer.allocate(BUFSIZ);
    177181   
    178     // there is no readable data available in the buffers.
     182  // there is no readable data available in the buffers.
     183  bbuf.flip();
     184   
     185  // there is no write pending data in the buffers.
     186  bbufIsDirty = false;
     187   
     188  bbufpos = fcn.position();
     189
     190  reader = new RandomAccessReader();
     191  writer = new RandomAccessWriter();
     192  inputStream = new RandomAccessInputStream();
     193  outputStream = new RandomAccessOutputStream();
     194    }
     195 
     196    public Writer getWriter() {
     197  return writer;
     198    }
     199 
     200    public Reader getReader() {
     201  return reader;
     202    }
     203 
     204    public InputStream getInputStream() {
     205  return inputStream;
     206    }
     207 
     208    public OutputStream getOutputStream() {
     209  return outputStream;
     210    }
     211 
     212    public void close() throws IOException {
     213  internalFlush(true);
     214  fcn.close();
     215    }
     216 
     217    public void flush() throws IOException {
     218  internalFlush(false);
     219    }
     220
     221    private int read(char[] cb, int off, int len) throws IOException {
     222  CharBuffer cbuf = CharBuffer.wrap(cb, off, len);
     223  boolean decodeWasUnderflow = false;
     224  boolean atEof = false;
     225  while ((cbuf.remaining() > 0) && dataIsAvailableForRead()
     226         && ! atEof) {
     227      if ((bbuf.remaining() == 0) || decodeWasUnderflow) {
     228    // need to read from the file.
     229    flushBbuf(); // in case bbuf is dirty.
     230    // update bbufpos.
     231    bbufpos += bbuf.position();
     232    int partialBytes = bbuf.remaining(); // partialBytes > 0 happens when decodeWasUnderflow
     233    // if reads and writes are mixed, we may need to seek first.
     234    if (bbufpos + partialBytes != fcnpos) {
     235        fcn.position(bbufpos + partialBytes);
     236    }
     237    // need to read data from file.
     238    bbuf.compact();
     239    //###FIXME: we're ignoring end-of-stream here!!!
     240    atEof = (fcn.read(bbuf) == -1);
    179241    bbuf.flip();
    180    
    181     // there is no write pending data in the buffers.
    182     bbufIsDirty = false;
    183    
    184     bbufpos = fcn.position(); // so as the byte buffer.
    185 
    186     reader = new RandomAccessReader(this);
    187     writer = new RandomAccessWriter(this);
    188     inputStream = new RandomAccessInputStream(this);
    189     outputStream = new RandomAccessOutputStream(this);
    190   }
    191  
    192   public Writer getWriter() {
    193     return writer;
    194   }
    195  
    196   public Reader getReader() {
    197     return reader;
    198   }
    199  
    200   public InputStream getInputStream() {
    201     return inputStream;
    202   }
    203  
    204   public OutputStream getOutputStream() {
    205     return outputStream;
    206   }
    207  
    208   public void close() throws IOException {
    209     internalFlush(true);
    210     fcn.close();
    211   }
    212  
    213   public void flush() throws IOException {
    214     internalFlush(false);
    215   }
    216 
    217   public int read(char[] cb, int off, int len) throws IOException {
    218     CharBuffer cbuf = CharBuffer.wrap(cb, off, len);
    219     boolean decodeWasUnderflow = false;
    220                 boolean atEof = false;
    221     while ((cbuf.remaining() > 0) && dataIsAvailableForRead()
    222                         && ! atEof) {
    223       if ((bbuf.remaining() == 0) || decodeWasUnderflow) {
    224         // need to read from the file.
    225         flushBbuf(); // in case bbuf is dirty.
    226         // update bbufpos.
    227         bbufpos += bbuf.position();
    228         int partialBytes = bbuf.remaining(); // partialBytes > 0 happens when decodeWasUnderflow
    229         // if reads and writes are mixed, we may need to seek first.
    230         if (bbufpos + partialBytes != fcnpos) {
    231           fcn.position(bbufpos + partialBytes);
    232         }
    233         // need to read data from file.
    234         bbuf.compact();
    235                                 //###FIXME: we're ignoring end-of-stream here!!!
    236         atEof = (fcn.read(bbuf) == -1);
    237         bbuf.flip();
    238         fcnpos = bbufpos + bbuf.remaining();
    239       }
    240       CoderResult r = cdec.decode(bbuf, cbuf, pointingAtEOF() );
    241       decodeWasUnderflow = (CoderResult.UNDERFLOW == r);
     242    fcnpos = bbufpos + bbuf.remaining();
     243      }
     244      CoderResult r = cdec.decode(bbuf, cbuf, pointingAtEOF() );
     245      decodeWasUnderflow = (CoderResult.UNDERFLOW == r);
     246  }
     247  if (cbuf.remaining() == len) {
     248      return -1;
     249  } else {
     250      return len - cbuf.remaining();
     251  }
     252    }
     253
     254    private boolean dataIsAvailableForRead() throws IOException {
     255  return ((bbuf.remaining() > 0) || (fcn.position() < fcn.size()));
     256    }
     257 
     258    private boolean pointingAtEOF() {
     259  return (bbuf.remaining() == 0) && (fcnpos == fcnsize);
     260    }
     261
     262    private void write(char[] cb, int off, int len) throws IOException {
     263  CharBuffer cbuf = CharBuffer.wrap(cb, off, len);
     264  encodeAndWrite(cbuf, false, false);
     265    }
     266
     267    private void internalFlush(boolean endOfFile) throws IOException {
     268  if (endOfFile) {
     269      CharBuffer cbuf = CharBuffer.allocate(0);
     270      encodeAndWrite(cbuf, true, endOfFile);
     271  } else {
     272      flushBbuf();
     273  }
     274    }
     275
     276    private void encodeAndWrite(CharBuffer cbuf, boolean flush, boolean endOfFile) throws IOException {
     277  if (bbufpos == fcnsize) {
     278      bbuf.clear();
     279  }
     280  while (cbuf.remaining() > 0) {
     281      CoderResult r = cenc.encode(cbuf, bbuf, endOfFile);
     282      bbufIsDirty = true;
     283      long curpos = bbufpos + bbuf.position();
     284      if (curpos > fcnsize) {
     285    // the file is extended.
     286    fcnsize = curpos;
     287      }
     288      if (CoderResult.OVERFLOW == r || bbuf.remaining() == 0) {
     289    flushBbuf();
     290    bbufpos += bbuf.limit();
     291    bbuf.clear();
     292    if (fcnpos < fcnsize) {
     293        fcn.read(bbuf);
     294        bbuf.flip();
     295        fcnpos += bbuf.remaining();
    242296    }
    243     if (cbuf.remaining() == len) {
    244       return -1;
    245     } else {
    246       return len - cbuf.remaining();
     297    // if we are at the end of file, bbuf is simply cleared.
     298    // in that case, bbufpos + bbuf.position points to the EOF, not fcnpos.
     299      }
     300  }
     301  if (bbuf.position() > 0 && bbufIsDirty && flush) {
     302      flushBbuf();
     303  }
     304    }
     305
     306    public void position(long newPosition) throws IOException {
     307  flushBbuf();
     308  long bbufend = bbufpos + bbuf.limit();
     309  if (newPosition >= bbufpos && newPosition < bbufend) {
     310      // near seek. within existing data of bbuf.
     311      bbuf.position((int)(newPosition - bbufpos));
     312  } else {
     313      // far seek. discard the buffer.
     314      flushBbuf();
     315      fcn.position(newPosition);
     316      fcnpos = newPosition;
     317      bbuf.clear();
     318      bbuf.flip(); // "there is no useful data on this buffer yet."
     319      bbufpos = fcnpos;
     320  }
     321    }
     322 
     323    public long position() throws IOException {
     324  flushBbuf();
     325  return bbufpos + bbuf.position(); // the logical position within the file.
     326    }
     327
     328    public long length() throws IOException {
     329  flushBbuf();
     330  return fcn.size();
     331    }
     332       
     333    private void flushBbuf() throws IOException {
     334  if (bbufIsDirty) {
     335      if (fcnpos != bbufpos) {
     336    fcn.position(bbufpos);
     337      }
     338      bbuf.position(0);
     339      if (bbufpos + bbuf.limit() > fcnsize) {
     340    // the buffer is at the end of the file.
     341    // area beyond fcnsize does not have data.
     342    bbuf.limit((int)(fcnsize - bbufpos));
     343      }
     344      fcn.write(bbuf);
     345      fcnpos = bbufpos + bbuf.limit();
     346      bbufIsDirty = false;
     347  }
     348    }
     349
     350    public int read(byte[] b, int off, int len) throws IOException {
     351  int pos = off;
     352  boolean atEof = false;
     353  while (pos - off < len && dataIsAvailableForRead()
     354         && ! atEof) {
     355      if (bbuf.remaining() == 0) {
     356    // need to read from the file.
     357    flushBbuf(); // in case bbuf is dirty.
     358    // update bbufpos.
     359    bbufpos += bbuf.limit();
     360    // if reads and writes are mixed, we may need to seek first.
     361    if (bbufpos != fcnpos) {
     362        fcn.position(bbufpos);
    247363    }
    248   }
    249 
    250   public boolean dataIsAvailableForRead() throws IOException {
    251     return ((bbuf.remaining() > 0) || (fcn.position() < fcn.size()));
    252   }
    253  
    254   private boolean pointingAtEOF() {
    255     return (bbuf.remaining() == 0) && (fcnpos == fcnsize);
    256   }
    257 
    258   public void write(char[] cb, int off, int len) throws IOException {
    259     CharBuffer cbuf = CharBuffer.wrap(cb, off, len);
    260     encodeAndWrite(cbuf, false, false);
    261   }
    262 
    263   private void internalFlush(boolean endOfFile) throws IOException {
    264     if (endOfFile) {
    265       CharBuffer cbuf = CharBuffer.allocate(0);
    266       encodeAndWrite(cbuf, true, endOfFile);
    267     } else {
    268       flushBbuf();
     364    // need to read data from file.
     365    bbuf.clear();
     366    atEof = (fcn.read(bbuf) == -1);
     367    bbuf.flip();
     368    fcnpos = bbufpos + bbuf.remaining();
     369      }
     370      int want = len - pos;
     371      if (want > bbuf.remaining()) {
     372    want = bbuf.remaining();
     373      }
     374      bbuf.get(b, pos, want);
     375      pos += want;
     376  }
     377  return pos - off;
     378    }
     379       
     380    // a method corresponding to the good ol' ungetc in C.
     381    // This function may fail when using (combined) character codes that use
     382    // escape sequences to switch between sub-codes.
     383    // ASCII, ISO-8859 series, any 8bit code are OK, all unicode variations are OK,
     384    // but applications of the ISO-2022 encoding framework can have trouble.
     385    // Example of such code is ISO-2022-JP which is used in Japanese e-mail.
     386    private CharBuffer singleCharBuf;
     387    private ByteBuffer shortByteBuf;
     388    public void unreadChar(char c) throws IOException {
     389  // algorithm :
     390  //  1. encode c into bytes, to find out how many bytes it corresponds to
     391  //  2. move the position backwards that many bytes.
     392  //  ** we stop here.  Don't bother to write the bytes to the buffer,
     393  //     assuming that it is the same as the original data.
     394  //     If we allow to write back different characters, the buffer must get 'dirty'
     395  //     but that would require read/write permissions on files you use unreadChar,
     396  //     even if you are just reading for some tokenizer.
     397  //
     398  //  So we don't do the following.
     399  //  3. write the bytes.
     400  //  4. move the position back again.
     401  if (singleCharBuf == null) {
     402      singleCharBuf = CharBuffer.allocate(1);
     403      shortByteBuf = ByteBuffer.allocate((int)cenc.maxBytesPerChar());
     404  }
     405  singleCharBuf.clear();
     406  singleCharBuf.append(c);
     407  singleCharBuf.flip();
     408  shortByteBuf.clear();
     409  cenc.encode(singleCharBuf, shortByteBuf, false);
     410  int n = shortByteBuf.position();
     411  long pos = position() - n;
     412  position(pos);
     413    }
     414 
     415    public void unreadByte(byte b) throws IOException {
     416  long pos = position() - 1;
     417  position(pos);
     418    }
     419
     420    private void write(byte[] b, int off, int len) throws IOException {
     421  int pos = off;
     422  while (pos < off + len) {
     423      int want = len;
     424      if (want > bbuf.remaining()) {
     425    want = bbuf.remaining();
     426      }
     427      bbuf.put(b, pos, want);
     428      pos += want;
     429      bbufIsDirty = true;
     430      long curpos = bbufpos + bbuf.position();
     431      if (curpos > fcn.size()) {
     432    // the file is extended.
     433    fcnsize = curpos;
     434      }
     435      if (bbuf.remaining() == 0) {
     436    flushBbuf();
     437    bbufpos += bbuf.limit();
     438    bbuf.clear();
     439    if (fcn.position() < fcn.size()) {
     440        bbufpos = fcn.position();
     441        fcn.read(bbuf);
     442        bbuf.flip();
     443        fcnpos += bbuf.remaining();
    269444    }
    270   }
    271 
    272   private void encodeAndWrite(CharBuffer cbuf, boolean flush, boolean endOfFile) throws IOException {
    273     if (bbufpos == fcnsize) {
    274       bbuf.clear();
    275     }
    276     while (cbuf.remaining() > 0) {
    277       CoderResult r = cenc.encode(cbuf, bbuf, endOfFile);
    278       bbufIsDirty = true;
    279       long curpos = bbufpos + bbuf.position();
    280       if (curpos > fcnsize) {
    281         // the file is extended.
    282         fcnsize = curpos;
    283       }
    284       if (CoderResult.OVERFLOW == r || bbuf.remaining() == 0) {
    285         flushBbuf();
    286         bbufpos += bbuf.limit();
    287         bbuf.clear();
    288         if (fcnpos < fcnsize) {
    289           fcn.read(bbuf);
    290           bbuf.flip();
    291           fcnpos += bbuf.remaining();
    292         }
    293         // if we are at the end of file, bbuf is simply cleared.
    294         // in that case, bbufpos + bbuf.position points to the EOF, not fcnpos.
    295       }
    296     }
    297     if (bbuf.position() > 0 && bbufIsDirty && flush) {
    298       flushBbuf();
    299     }
    300   }
    301 
    302   public void position(long newPosition) throws IOException {
    303                 flushBbuf();
    304     long bbufend = bbufpos + bbuf.limit();
    305     if (newPosition >= bbufpos && newPosition < bbufend) {
    306       // near seek. within existing data of bbuf.
    307       bbuf.position((int)(newPosition - bbufpos));
    308     } else {
    309       // far seek. discard the buffer.
    310       flushBbuf();
    311       fcn.position(newPosition);
    312       fcnpos = newPosition;
    313       bbuf.clear();
    314       bbuf.flip(); // "there is no useful data on this buffer yet."
    315       bbufpos = fcnpos;
    316     }
    317   }
    318  
    319   public long position() throws IOException {
    320                 flushBbuf();
    321     return bbufpos + bbuf.position(); // the logical position within the file.
    322   }
    323 
    324         public long length() throws IOException {
    325             flushBbuf();
    326             return fcn.size();
    327         }
    328        
    329   private void flushBbuf() throws IOException {
    330     if (bbufIsDirty) {
    331       if (fcnpos != bbufpos) {
    332         fcn.position(bbufpos);
    333       }
    334       bbuf.position(0);
    335       if (bbufpos + bbuf.limit() > fcnsize) {
    336         // the buffer is at the end of the file.
    337         // area beyond fcnsize does not have data.
    338         bbuf.limit((int)(fcnsize - bbufpos));
    339       }
    340       fcn.write(bbuf);
    341       fcnpos = bbufpos + bbuf.limit();
    342       bbufIsDirty = false;
    343     }
    344   }
    345 
    346   public int read(byte[] b, int off, int len) throws IOException {
    347     int pos = off;
    348                 boolean atEof = false;
    349     while (pos - off < len && dataIsAvailableForRead()
    350                         && ! atEof) {
    351       if (bbuf.remaining() == 0) {
    352         // need to read from the file.
    353         flushBbuf(); // in case bbuf is dirty.
    354         // update bbufpos.
    355         bbufpos += bbuf.limit();
    356         // if reads and writes are mixed, we may need to seek first.
    357         if (bbufpos != fcnpos) {
    358           fcn.position(bbufpos);
    359         }
    360         // need to read data from file.
    361         bbuf.clear();
    362         atEof = (fcn.read(bbuf) == -1);
    363         bbuf.flip();
    364         fcnpos = bbufpos + bbuf.remaining();
    365       }
    366       int want = len - pos;
    367       if (want > bbuf.remaining()) {
    368         want = bbuf.remaining();
    369       }
    370       bbuf.get(b, pos, want);
    371       pos += want;
    372     }
    373     return pos - off;
    374   }
    375        
    376   // a method corresponding to the good ol' ungetc in C.
    377   // This function may fail when using (combined) character codes that use
    378   // escape sequences to switch between sub-codes.
    379   // ASCII, ISO-8859 series, any 8bit code are OK, all unicode variations are OK,
    380   // but applications of the ISO-2022 encoding framework can have trouble.
    381   // Example of such code is ISO-2022-JP which is used in Japanese e-mail.
    382   private CharBuffer singleCharBuf;
    383   private ByteBuffer shortByteBuf;
    384   public void unreadChar(char c) throws IOException {
    385     // algorithm :
    386     //  1. encode c into bytes, to find out how many bytes it corresponds to
    387     //  2. move the position backwards that many bytes.
    388     //  ** we stop here.  Don't bother to write the bytes to the buffer,
    389     //     assuming that it is the same as the original data.
    390     //     If we allow to write back different characters, the buffer must get 'dirty'
    391     //     but that would require read/write permissions on files you use unreadChar,
    392     //     even if you are just reading for some tokenizer.
    393     //
    394     //  So we don't do the following.
    395     //  3. write the bytes.
    396     //  4. move the position back again.
    397     if (singleCharBuf == null) {
    398       singleCharBuf = CharBuffer.allocate(1);
    399       shortByteBuf = ByteBuffer.allocate((int)cenc.maxBytesPerChar());
    400     }
    401     singleCharBuf.clear();
    402     singleCharBuf.append(c);
    403     singleCharBuf.flip();
    404     shortByteBuf.clear();
    405     cenc.encode(singleCharBuf, shortByteBuf, false);
    406     int n = shortByteBuf.position();
    407     long pos = position() - n;
    408     position(pos);
    409   }
    410  
    411   public void unreadByte(byte b) throws IOException {
    412     long pos = position() - 1;
    413     position(pos);
    414   }
    415 
    416   public void write(byte[] b, int off, int len) throws IOException {
    417     int pos = off;
    418     while (pos < off + len) {
    419       int want = len;
    420       if (want > bbuf.remaining()) {
    421         want = bbuf.remaining();
    422       }
    423       bbuf.put(b, pos, want);
    424       pos += want;
    425       bbufIsDirty = true;
    426       long curpos = bbufpos + bbuf.position();
    427       if (curpos > fcn.size()) {
    428         // the file is extended.
    429         fcnsize = curpos;
    430       }
    431       if (bbuf.remaining() == 0) {
    432         flushBbuf();
    433         bbufpos += bbuf.limit();
    434         bbuf.clear();
    435         if (fcn.position() < fcn.size()) {
    436                                         bbufpos = fcn.position();
    437           fcn.read(bbuf);
    438           bbuf.flip();
    439           fcnpos += bbuf.remaining();
    440         }
    441         // if we are at the end of file, bbuf is simply cleared.
    442         // in that case, bbufpos + bbuf.position points to the EOF, not fcnpos.
    443       }
    444     }
    445   }
     445    // if we are at the end of file, bbuf is simply cleared.
     446    // in that case, bbufpos + bbuf.position points to the EOF, not fcnpos.
     447      }
     448  }
     449    }
    446450}
Note: See TracChangeset for help on using the changeset viewer.