Changeset 11403
- Timestamp:
- 11/29/08 21:40:18 (14 years ago)
- 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 3 3 * 4 4 * Copyright (C) 2004-2006 Peter Graves 5 * Copyright (C) 2008 Hideo at Yokohama 5 6 * $Id$ 6 7 * … … 35 36 36 37 import java.io.File; 38 import java.io.InputStream; 39 import java.io.OutputStream; 40 import java.io.Reader; 41 import java.io.Writer; 37 42 import java.io.FileNotFoundException; 38 43 import java.io.IOException; … … 43 48 { 44 49 private final RandomAccessCharacterFile racf; 45 private final RandomAccessCharacterFile in;46 private final RandomAccessCharacterFile out;47 50 private final Pathname pathname; 48 51 private final int bytesPerUnit; 52 private InputStream inst; 53 private OutputStream outst; 54 private Reader reader; 55 private Writer writer; 49 56 50 57 public enum EolStyle { … … 95 102 Debug.assertTrue(mode != null); 96 103 RandomAccessFile raf = new RandomAccessFile(file, mode); 97 racf = new RandomAccessCharacterFile(raf, encoding); 98 in = isInputStream ? racf : null; 99 out = isOutputStream ? racf : null; 104 100 105 // ifExists is ignored unless we have an output stream. 101 106 if (isOutputStream) { … … 110 115 } 111 116 } 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 112 121 this.pathname = pathname; 113 122 this.elementType = elementType; … … 115 124 isCharacterStream = true; 116 125 bytesPerUnit = 1; 126 if (isInputStream) { 127 reader = racf.getReader(); 128 } 129 if (isOutputStream) { 130 writer = racf.getWriter(); 131 } 117 132 } else { 118 133 isBinaryStream = true; … … 125 140 } 126 141 bytesPerUnit = width / 8; 142 if (isInputStream) { 143 inst = racf.getInputStream(); 144 } 145 if (isOutputStream) { 146 outst = racf.getOutputStream(); 147 } 127 148 } 128 149 eolChar = (eol == EolStyle.CR) ? '\r' : '\n'; … … 160 181 { 161 182 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) { 168 190 error(new StreamError(this, e)); 169 191 } … … 205 227 { 206 228 try { 207 int c = in.getReader().read();229 int c = reader.read(); 208 230 if (eolStyle == EolStyle.CRLF) { 209 231 if (c == '\r') { 210 long mark = in.position(); 211 int c2 = in.getReader().read(); 232 int c2 = reader.read(); 212 233 if (c2 == '\n') { 213 234 ++lineNumber; 214 235 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 } 220 243 } 221 244 return c; 222 } 223 if (c == eolChar) { 245 } else if (c == eolChar) { 224 246 ++lineNumber; 225 247 return c; 226 } 227 return c; 248 } else { 249 return c; 250 } 228 251 } 229 252 catch (NullPointerException e) { … … 241 264 { 242 265 try { 243 in.unreadChar((char)n);266 racf.unreadChar((char)n); 244 267 } 245 268 catch (IOException e) { … … 260 283 if (c == '\n') { 261 284 if (eolStyle == EolStyle.CRLF) 262 out.getWriter().write((byte)'\r');263 out.getWriter().write((byte)eolChar);285 writer.write('\r'); 286 writer.write(eolChar); 264 287 charPos = 0; 265 288 } else { 266 out.getWriter().write((byte)c);289 writer.write(c); 267 290 ++charPos; 268 291 } … … 273 296 } 274 297 275 @Override 298 276 299 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) 277 305 throws ConditionThrowable 278 306 { 279 307 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) { 281 325 for (int i = start; i < end; i++) { 282 326 char c = chars[i]; 283 327 if (c == '\n') { 284 out.getWriter().write((byte)'\r');285 out.getWriter().write((byte)'\n');328 writer.write('\r'); 329 writer.write('\n'); 286 330 charPos = 0; 287 331 } else { 288 out.getWriter().write((byte)c);332 writer.write(c); 289 333 ++charPos; 290 334 } … … 294 338 char c = chars[i]; 295 339 if (c == '\n') { 296 out.getWriter().write((byte)eolChar);340 writer.write(eolChar); 297 341 charPos = 0; 298 342 } else { 299 out.getWriter().write((byte)c);343 writer.write(c); 300 344 ++charPos; 301 345 } … … 311 355 public void _writeString(String s) throws ConditionThrowable 312 356 { 313 _writeChars(s.toCharArray(), 0, s.length() );357 _writeChars(s.toCharArray(), 0, s.length(), true); 314 358 } 315 359 … … 317 361 public void _writeLine(String s) throws ConditionThrowable 318 362 { 319 _writeString(s);363 _writeChars(s.toCharArray(), 0, s.length(), false); 320 364 if (eolStyle == EolStyle.CRLF) 321 365 _writeChar('\r'); … … 329 373 { 330 374 try { 331 return in .getInputStream().read(); // Reads an 8-bit byte.375 return inst.read(); // Reads an 8-bit byte. 332 376 } 333 377 catch (NullPointerException e) { … … 346 390 { 347 391 try { 348 out .getOutputStream().write(n); // Writes an 8-bit byte.392 outst.write(n); // Writes an 8-bit byte. 349 393 } 350 394 catch (NullPointerException e) { … … 360 404 { 361 405 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 } 366 411 } 367 412 catch (IOException e) { … … 423 468 } 424 469 425 // ### make-file-stream pathname namestring element-type direction if-exists => stream470 // ### make-file-stream pathname namestring element-type direction if-exists external-format => stream 426 471 private static final Primitive MAKE_FILE_STREAM = 427 472 new Primitive("make-file-stream", PACKAGE_SYS, true, … … 455 500 String encoding = "ISO-8859-1"; 456 501 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 } 467 516 468 517 -
branches/open-external-format/src/org/armedbear/lisp/open.lisp
r11395 r11403 107 107 (if-does-not-exist nil if-does-not-exist-given) 108 108 (external-format :default)) 109 (declare (ignore external-format)) ; FIXME109 ; (declare (ignore external-format)) ; FIXME 110 110 (setf element-type (case element-type 111 111 ((character base-char) … … 144 144 :format-control "The file ~S does not exist." 145 145 :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)) 147 147 (:probe 148 148 (case if-does-not-exist … … 159 159 (create-new-file namestring))) 160 160 (let ((stream (make-file-stream pathname namestring element-type 161 :input nil nil)))161 :input nil external-format))) 162 162 (when stream 163 163 (close stream)) … … 207 207 :format-arguments (list if-exists)))) 208 208 (let ((stream (make-file-stream pathname namestring element-type 209 direction if-exists nil)))209 direction if-exists external-format))) 210 210 (unless stream 211 211 (error 'file-error -
branches/open-external-format/src/org/armedbear/lisp/util/RandomAccessCharacterFile.java
r11400 r11403 41 41 import java.io.Reader; 42 42 import java.io.Writer; 43 import java.io.PrintWriter; 44 import java.io.FileWriter; 43 45 import java.nio.ByteBuffer; 44 46 import java.nio.CharBuffer; … … 51 53 public class RandomAccessCharacterFile { 52 54 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 } 72 72 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(); 171 175 172 173 174 176 cset = Charset.forName(encoding); 177 cdec = cset.newDecoder(); 178 cenc = cset.newEncoder(); 175 179 176 180 bbuf = ByteBuffer.allocate(BUFSIZ); 177 181 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); 179 241 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(); 242 296 } 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); 247 363 } 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(); 269 444 } 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 } 446 450 }
Note: See TracChangeset
for help on using the changeset viewer.