source: branches/0.17.x/abcl/src/org/armedbear/lisp/util/RandomAccessCharacterFile.java

Last change on this file was 11988, checked in by ehuelsmann, 16 years ago

Remove the 'large block write' special case code:
it interacts badly with flushBbuf() and we seem to
have an issue with *small* writes/reads, not large ones.

  • Property svn:eol-style set to LF
File size: 17.1 KB
Line 
1/*
2 * RandomAccessCharacterFile.java
3 *
4 * Copyright (C) 2008 Hideo at Yokohama
5 * Copyright (C) 2008 Erik Huelsmann
6 * $Id$
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
21 *
22 * As a special exception, the copyright holders of this library give you
23 * permission to link this library with independent modules to produce an
24 * executable, regardless of the license terms of these independent
25 * modules, and to copy and distribute the resulting executable under
26 * terms of your choice, provided that you also meet, for each linked
27 * independent module, the terms and conditions of the license of that
28 * module.  An independent module is a module which is not derived from
29 * or based on this library.  If you modify this library, you may extend
30 * this exception to your version of the library, but you are not
31 * obligated to do so.  If you do not wish to do so, delete this
32 * exception statement from your version.
33 */
34
35package org.armedbear.lisp.util;
36
37import java.io.IOException;
38import java.io.PushbackInputStream;
39import java.io.OutputStream;
40import java.io.RandomAccessFile;
41import java.io.PushbackReader;
42import java.io.Reader;
43import java.io.StringReader;
44import java.io.Writer;
45import java.nio.ByteBuffer;
46import java.nio.CharBuffer;
47import java.nio.channels.FileChannel;
48import java.nio.charset.Charset;
49import java.nio.charset.CharsetDecoder;
50import java.nio.charset.CharsetEncoder;
51import java.nio.charset.CoderResult;
52import java.nio.charset.CodingErrorAction;
53
54public class RandomAccessCharacterFile {
55
56    private class RandomAccessInputStream extends PushbackInputStream {
57
58        public RandomAccessInputStream() {
59            super(null);
60        }
61       
62        private byte[] read_buf = new byte[1];
63
64        @Override
65        public final int read() throws IOException {
66            int len = read(read_buf);
67            if (len == 1) {
68                // byte is signed, char is unsigned, int is signed.
69                // buf can hold 0xff, we want it as 0xff in int, not -1.
70                return 0xff & (int) read_buf[0];
71            } else {
72                return -1;
73            }
74        }
75               
76        @Override
77        public final int read(byte[] b, int off, int len) throws IOException {
78            return RandomAccessCharacterFile.this.read(b, off, len);
79        }
80
81        @Override
82        public final void unread(int b) throws IOException {
83            RandomAccessCharacterFile.this.unreadByte((byte)b);
84        }
85
86        @Override
87        public final void unread(byte[] b, int off, int len) throws IOException {
88            for (int i = 0; i < len; i++)
89                this.unread(b[off+i]);
90        }
91
92        @Override
93        public final void unread(byte[] b) throws IOException {
94            this.unread(b, 0, b.length);
95        }
96
97        @Override
98        public final int available() throws IOException {
99            return (int)(RandomAccessCharacterFile.this.length()
100                            - RandomAccessCharacterFile.this.position());
101        }
102
103        @Override
104        public final synchronized void mark(int readlimit) {
105        }
106
107        @Override
108        public final boolean markSupported() {
109            return false;
110        }
111
112        @Override
113        public final synchronized void reset() throws IOException {
114            throw new IOException("Operation not supported");
115        }
116
117        @Override
118        public final long skip(long n) throws IOException {
119            RandomAccessCharacterFile.this.position(RandomAccessCharacterFile.this.position()+n);
120            return n;
121        }
122
123        @Override
124        public final int read(byte[] b) throws IOException {
125            return this.read(b, 0, b.length);
126        }
127
128        @Override
129        public final void close() throws IOException {
130            RandomAccessCharacterFile.this.close();
131        }
132    }
133
134    private class RandomAccessOutputStream extends OutputStream {
135
136        private RandomAccessOutputStream() {
137        }
138
139        private byte[] buf = new byte[1];
140        public final void write(int b) throws IOException {
141            buf[0] = (byte)b;
142            RandomAccessCharacterFile.this.write(buf, 0, 1);
143        }
144
145        @Override
146        public final void write(byte[] b) throws IOException {
147            RandomAccessCharacterFile.this.write(b, 0, b.length);
148        }
149
150        @Override
151        public final void write(byte[] b, int off, int len) throws IOException {
152            RandomAccessCharacterFile.this.write(b, off, len);
153        }
154
155        @Override
156        public final void flush() throws IOException {
157            RandomAccessCharacterFile.this.flush();
158        }
159
160        @Override
161        public final void close() throws IOException {
162            RandomAccessCharacterFile.this.close();
163        }
164    }
165   
166    // dummy reader which we need to call the Pushback constructor
167    // because a null value won't work
168    private static Reader staticReader = new StringReader("");
169   
170    private class RandomAccessReader extends PushbackReader {
171
172        private RandomAccessReader() {
173                // because we override all methods of Pushbackreader,
174                // staticReader will never be referenced
175                super(staticReader);
176        }
177
178        @Override
179        public final void close() throws IOException {
180            RandomAccessCharacterFile.this.close();
181        }
182       
183        private char[] read_buf = new char[1];
184
185        @Override
186        public final int read() throws IOException {
187            int n = this.read(read_buf);
188           
189            if (n == 1)
190                return read_buf[0];
191            else
192                return -1;
193        }
194
195        @Override
196        public final void unread(int c) throws IOException {
197            RandomAccessCharacterFile.this.unreadChar((char)c);
198        }
199
200        @Override
201        public final void unread(char[] cbuf, int off, int len) throws IOException {
202            for (int i = 0; i < len; i++)
203                this.unread(cbuf[off+i]);
204        }
205
206        @Override
207        public final void unread(char[] cbuf) throws IOException {
208            this.unread(cbuf, 0, cbuf.length);
209        }
210
211        @Override
212        public final int read(CharBuffer target) throws IOException {
213            //FIXME: to be implemented
214            throw new IOException("Not implemented");
215        }
216
217        @Override
218        public final int read(char[] cbuf) throws IOException {
219            return RandomAccessCharacterFile.this.read(cbuf, 0, cbuf.length);
220        }
221       
222        @Override
223        public final int read(char[] cb, int off, int len) throws IOException {
224            return RandomAccessCharacterFile.this.read(cb, off, len);
225        }
226
227        @Override
228        public final boolean ready() throws IOException {
229            return true;
230        }
231    }
232
233    private class RandomAccessWriter extends Writer {
234
235        private RandomAccessWriter() {
236        }
237
238        public final void close() throws IOException {
239            RandomAccessCharacterFile.this.close();
240        }
241
242        public final void flush() throws IOException {
243            RandomAccessCharacterFile.this.flush();
244        }
245
246        @Override
247        public final void write(char[] cb, int off, int len) throws IOException {
248            RandomAccessCharacterFile.this.write(cb, off, len);
249        }
250
251    }
252
253
254    final static int BUFSIZ = 4*1024; // setting this to a small value like 8 is helpful for testing.
255 
256    private RandomAccessWriter writer;
257    private RandomAccessReader reader;
258    private RandomAccessInputStream inputStream;
259    private RandomAccessOutputStream outputStream;
260    private FileChannel fcn;
261 
262    private Charset cset;
263    private CharsetEncoder cenc;
264    private CharsetDecoder cdec;
265 
266    /**
267     * bbuf is treated as a cache of the file content.
268     * If it points to somewhere in the middle of the file, it holds the copy of the file content,
269     * even when you are writing a large chunk of data.  If you write in the middle of a file,
270     * bbuf first gets filled with contents of the data, and only after that any new data is
271     * written on bbuf.
272     * The exception is when you are appending data at the end of the file.
273     */
274    private ByteBuffer bbuf;
275    private boolean bbufIsDirty; /* whether bbuf holds data that must be written. */
276    private boolean bbufIsReadable; /* whether bbuf.remaining() contains readable content. */
277    private long bbufpos; /* where the beginning of bbuf is pointing in the file now. */
278
279    public RandomAccessCharacterFile(RandomAccessFile raf, String encoding) throws IOException {
280
281        fcn = raf.getChannel();
282
283        cset = (encoding == null) ? Charset.defaultCharset() : Charset.forName(encoding);
284        cdec = cset.newDecoder();
285        cdec.onMalformedInput(CodingErrorAction.REPLACE);
286        cdec.onUnmappableCharacter(CodingErrorAction.REPLACE);
287        cenc = cset.newEncoder();
288
289        bbuf = ByteBuffer.allocate(BUFSIZ);
290
291        // there is no readable data available in the buffers.
292        bbuf.flip();
293
294        // there is no write pending data in the buffers.
295        bbufIsDirty = false;
296
297        bbufIsReadable = false;
298
299        bbufpos = fcn.position();
300
301        reader = new RandomAccessReader();
302        writer = new RandomAccessWriter();
303        inputStream = new RandomAccessInputStream();
304        outputStream = new RandomAccessOutputStream();
305    }
306 
307    public Writer getWriter() {
308        return writer;
309    }
310 
311    public PushbackReader getReader() {
312        return reader;
313    }
314 
315    public PushbackInputStream getInputStream() {
316        return inputStream;
317    }
318 
319    public OutputStream getOutputStream() {
320        return outputStream;
321    }
322 
323    public final void close() throws IOException {
324        internalFlush(true);
325        fcn.close();
326    }
327 
328    public final void flush() throws IOException {
329        internalFlush(false);
330    }
331
332    private final boolean ensureReadBbuf(boolean force) throws IOException {
333        boolean bufReady = true;
334
335        if ((bbuf.remaining() == 0) || force || ! bbufIsReadable) {
336            // need to read from the file.
337
338            if (bbufIsDirty) {
339                bbuf.flip();
340                fcn.position(bbufpos);
341                fcn.write(bbuf);
342                bbufpos += bbuf.position();
343                bbuf.clear();
344            } else {
345                int bbufEnd = bbufIsReadable ? bbuf.limit() : bbuf.position();
346                fcn.position(bbufpos + bbufEnd);
347                bbufpos += bbuf.position();
348                bbuf.compact();
349            }
350
351            bufReady = (fcn.read(bbuf) != -1);
352            bbuf.flip();
353            bbufIsReadable = true;
354        }
355
356        return bufReady;
357    }
358
359    private final int read(char[] cb, int off, int len) throws IOException {
360        CharBuffer cbuf = CharBuffer.wrap(cb, off, len);
361        boolean decodeWasUnderflow = false;
362        boolean atEof = false;
363        while ((cbuf.remaining() > 0) && ! atEof) {
364
365            atEof = ! ensureReadBbuf(decodeWasUnderflow);
366            CoderResult r = cdec.decode(bbuf, cbuf, atEof );
367            decodeWasUnderflow = (CoderResult.UNDERFLOW == r);
368        }
369        if (cbuf.remaining() == len) {
370            return -1;
371        } else {
372            return len - cbuf.remaining();
373        }
374    }
375
376    private final void write(char[] cb, int off, int len) throws IOException {
377        CharBuffer cbuf = CharBuffer.wrap(cb, off, len);
378        encodeAndWrite(cbuf, false, false);
379    }
380
381    private final void internalFlush(boolean endOfFile) throws IOException {
382        if (endOfFile) {
383            CharBuffer cbuf = CharBuffer.allocate(0);
384            encodeAndWrite(cbuf, true, endOfFile);
385        } else {
386            flushBbuf(false);
387        }
388    }
389
390    private final void encodeAndWrite(CharBuffer cbuf, boolean flush, boolean endOfFile) throws IOException {
391        while (cbuf.remaining() > 0) {
392            CoderResult r = cenc.encode(cbuf, bbuf, endOfFile);
393            bbufIsDirty = true;
394            if (CoderResult.OVERFLOW == r || bbuf.remaining() == 0) {
395                flushBbuf(false);
396                bbuf.clear();
397            }
398        }
399        if (bbuf.position() > 0 && bbufIsDirty && flush) {
400            flushBbuf(false);
401        }
402    }
403
404    public final void position(long newPosition) throws IOException {
405        flushBbuf(true);
406        long bbufend = bbufpos // in case bbuf is readable, its contents is valid
407            + (bbufIsReadable ? bbuf.limit() : bbuf.position()); // beyond position()
408        if (newPosition >= bbufpos && newPosition < bbufend) {
409            // near seek. within existing data of bbuf.
410            bbuf.position((int)(newPosition - bbufpos));
411        } else {
412            fcn.position(newPosition);
413            // far seek; discard the buffer (it's already cleared)
414            bbuf.clear();
415            bbuf.flip(); // "there is no useful data on this buffer yet."
416            bbufpos = newPosition;
417        }
418    }
419 
420    public final long position() throws IOException {
421        return bbufpos + bbuf.position(); // the logical position within the file.
422    }
423
424    public final long length() throws IOException {
425        flushBbuf(false);
426        return fcn.size();
427    }
428
429    private final void flushBbuf(boolean commitOnly) throws IOException {
430        if (! bbufIsDirty)
431            return;
432
433        fcn.position(bbufpos);
434
435        // if the buffer is dirty, the modifications have to be
436        // before position(): before re-positioning, this.position()
437        // calls this function.
438        if (commitOnly || bbufIsReadable) {
439            ByteBuffer dup = bbuf.duplicate();
440            dup.flip();
441            fcn.write(dup);
442            return;
443        }
444        bbuf.flip();
445        fcn.write(bbuf);
446
447        bbufpos += bbuf.position();
448        bbuf.clear();
449        bbuf.flip(); // there's no useable data in this buffer
450        bbufIsDirty = false;
451        bbufIsReadable = false;
452    }
453
454    public final int read(byte[] b, int off, int len) throws IOException {
455        int pos = off;
456        boolean atEof = false;
457        while (pos - off < len && ! atEof) {
458
459            atEof = ! ensureReadBbuf(false);
460            int want = len - pos;
461            if (want > bbuf.remaining()) {
462                want = bbuf.remaining();
463            }
464            bbuf.get(b, pos, want);
465            pos += want;
466        }
467        return pos - off;
468    }
469       
470    // a method corresponding to the good ol' ungetc in C.
471    // This function may fail when using (combined) character codes that use
472    // escape sequences to switch between sub-codes.
473    // ASCII, ISO-8859 series, any 8bit code are OK, all unicode variations are OK,
474    // but applications of the ISO-2022 encoding framework can have trouble.
475    // Example of such code is ISO-2022-JP which is used in Japanese e-mail.
476    private CharBuffer singleCharBuf;
477    private ByteBuffer shortByteBuf;
478    public final void unreadChar(char c) throws IOException {
479        // algorithm :
480        //  1. encode c into bytes, to find out how many bytes it corresponds to
481        //  2. move the position backwards that many bytes.
482        //  ** we stop here.  Don't bother to write the bytes to the buffer,
483        //     assuming that it is the same as the original data.
484        //     If we allow to write back different characters, the buffer must get 'dirty'
485        //     but that would require read/write permissions on files you use unreadChar,
486        //     even if you are just reading for some tokenizer.
487        //
488        //  So we don't do the following.
489        //  3. write the bytes.
490        //  4. move the position back again.
491        if (singleCharBuf == null) {
492            singleCharBuf = CharBuffer.allocate(1);
493            shortByteBuf = ByteBuffer.allocate((int)cenc.maxBytesPerChar());
494        }
495        singleCharBuf.clear();
496        singleCharBuf.append(c);
497        singleCharBuf.flip();
498        shortByteBuf.clear();
499        cenc.encode(singleCharBuf, shortByteBuf, false);
500        int n = shortByteBuf.position();
501        long pos = position() - n;
502        position(pos);
503    }
504 
505    public final void unreadByte(byte b) throws IOException {
506        long pos = position() - 1;
507        position(pos);
508    }
509
510    private final void write(byte[] b, int off, int len) throws IOException {
511        int pos = off;
512        while (pos < off + len) {
513            int want = len;
514            if (want > bbuf.remaining()) {
515                want = bbuf.remaining();
516            }
517            bbuf.put(b, pos, want);
518            pos += want;
519            bbufIsDirty = true;
520            if (bbuf.remaining() == 0) {
521                flushBbuf(false);
522                bbuf.clear();
523            }
524        }
525    }
526}
Note: See TracBrowser for help on using the repository browser.