Statistics
| Revision:

svn-gvsig-desktop / trunk / org.gvsig.desktop / org.gvsig.desktop.compat.cdc / org.gvsig.fmap.dal / org.gvsig.fmap.dal.file / org.gvsig.fmap.dal.file.csv / src / main / java / org / gvsig / fmap / dal / store / simplereader / virtualrows / RandomAccessFileReader.java @ 47652

History | View | Annotate | Download (37.5 KB)

1
package org.gvsig.fmap.dal.store.simplereader.virtualrows;
2

    
3
import java.io.BufferedReader;
4
import java.io.File;
5
import java.io.IOException;
6
import java.io.RandomAccessFile;
7
import java.io.Reader;
8
import java.io.UncheckedIOException;
9
import java.nio.ByteBuffer;
10
import java.nio.CharBuffer;
11
import java.nio.channels.Channels;
12
import java.nio.charset.Charset;
13
import java.util.Iterator;
14
import java.util.NoSuchElementException;
15
import java.util.Spliterator;
16
import java.util.Spliterators;
17
import java.util.function.Function;
18
import java.util.function.Predicate;
19
import java.util.stream.Stream;
20
import java.util.stream.StreamSupport;
21
import org.apache.commons.io.FilenameUtils;
22
import org.apache.commons.io.IOUtils;
23
import org.apache.commons.lang3.StringUtils;
24
import org.apache.commons.lang3.mutable.MutableInt;
25
import org.gvsig.tools.ToolsLocator;
26
import org.gvsig.tools.i18n.I18nManager;
27
import org.gvsig.tools.library.impl.DefaultLibrariesInitializer;
28
import org.gvsig.tools.observer.Observable;
29
import org.gvsig.tools.task.SimpleTaskStatus;
30
import org.gvsig.tools.task.TaskStatus;
31
import org.gvsig.tools.task.TaskStatusManager;
32

    
33
/**
34
 *
35
 * @author gvSIG Team
36
 */
37
public class RandomAccessFileReader extends Reader {
38

    
39
    public static final Predicate<String> FILTER_NONE = (String t) -> false;
40

    
41
    protected static final int INDEX_HEADER_FILESIZE = 0;
42
    protected static final int INDEX_HEADER_INDEXCREATIONCOST = 1;
43
    
44
    protected static final int MAX_BUFFER_FOR_LINE = 50*1024; //50K
45

    
46
    protected RandomAccessFile raf;
47
    protected Reader reader;
48
    protected long currentPosition;
49
    protected final Charset charset;
50
    protected long lastModified;
51

    
52
    public RandomAccessFileReader(File f, String charsetName) throws IOException {
53
        this(new RandomAccessFile(f, "r"), Charset.forName(charsetName));
54
        this.lastModified = f.lastModified();
55
    }
56

    
57
    public RandomAccessFileReader(File f, Charset charset) throws IOException {
58
        this(new RandomAccessFile(f, "r"), charset);
59
        this.lastModified = f.lastModified();
60
    }
61

    
62
    public RandomAccessFileReader(RandomAccessFile raf, String charsetName) throws IOException {
63
        this(raf, Charset.forName(charsetName));
64
        this.lastModified = -1;
65
    }
66

    
67
    public RandomAccessFileReader(RandomAccessFile raf, Charset charset) throws IOException {
68
        this.charset = charset;
69
        this.raf = raf;
70
        this.reader = null;
71
        this.currentPosition = 0;
72
        this.lastModified = -1;
73
    }
74

    
75
    public Charset getCharset() {
76
        return this.charset;
77
    }
78
    
79
    @Override
80
    public int read(char[] cbuf, int off, int len) throws IOException {
81
        if (this.reader == null) {
82
            this.createReader();
83
        }
84
        int n = this.reader.read(cbuf, off, len);
85
        if (n > 0) {
86
            // Update current position (bytes) adding the read characters.
87
            CharBuffer charBuffer = CharBuffer.wrap(cbuf, off, len);
88
            ByteBuffer byteBuffer = this.charset.encode(charBuffer);
89
            this.currentPosition += byteBuffer.limit();
90
        }
91
        return n;
92
    }
93

    
94
    protected void createReader() {
95
        this.reader = Channels.newReader(this.raf.getChannel(), this.charset.name());
96
    }
97

    
98
//    protected InputStream is;
99
//    protected void createReader() {
100
//        if( this.is==null ) {
101
//            this.is = new InputStream() {
102
//                @Override
103
//                public int read() throws IOException {
104
//                    return raf.read();
105
//                }
106
//            };
107
//        }
108
//        this.reader = new InputStreamReader(this.is, charset);
109
//    }
110
    @Override
111
    public void close() throws IOException {
112
//        IOUtils.closeQuietly(this.is);
113
        IOUtils.closeQuietly(this.reader);
114
        IOUtils.closeQuietly(this.raf);
115
    }
116

    
117
    public long getFilePointer() throws IOException {
118
        return this.raf.getFilePointer();
119
    }
120

    
121
    public long getCurrentPosition() {
122
        return this.currentPosition;
123
    }
124

    
125
    public void rewind() throws IOException {
126
        this.raf.seek(0);
127
        this.reader = null;
128
        this.currentPosition = 0;
129
    }
130

    
131
    public void seek(long position) throws IOException {
132
        this.raf.seek(position);
133
        this.reader = null;
134
        this.currentPosition = position;
135
    }
136

    
137
    public String readLine() throws IOException {
138
        StringBuilder buffer = new StringBuilder();
139
        int c = -1;
140
        boolean eol = false;
141

    
142
        while (!eol) {
143
            switch (c = this.read()) {
144
                case -1:
145
                case '\n':
146
                    eol = true;
147
                    break;
148
                case '\r':
149
                    eol = true;
150
                    long cur = raf.getFilePointer();
151
                    if ((raf.read()) != '\n') {
152
                        raf.seek(cur);
153
                    }
154
                    break;
155
                default:
156
                    buffer.append((char) c);
157
                    break;
158
            }
159
        }
160
        if ((c == -1) && (buffer.length() == 0)) {
161
            return null;
162
        }
163
        return buffer.toString();
164
    }
165

    
166
    public long countLines(Predicate<String> filter, SimpleTaskStatus status) throws IOException {
167
        return countLines(filter, new MutableInt(), status);
168
    }
169
    
170
    public long countLines(Predicate<String> filter, MutableInt maxLineLen, SimpleTaskStatus status) throws IOException {
171
        if (raf.length() == 0) {
172
            return 0;
173
        }
174
        long savedpos = this.getCurrentPosition();
175
        long count = -1;
176
        if (status != null) {
177
            I18nManager i18n = ToolsLocator.getI18nManager();
178
            status.message(i18n.getTranslation("_Calculating_number_of_lines"));
179
            status.setIndeterminate();
180
        }
181
        maxLineLen.setValue(0);
182
        BufferedReader breader = new BufferedReader(this, MAX_BUFFER_FOR_LINE);
183
        try {
184
            String line;
185
            count = 0;
186
            while ((line = breader.readLine()) != null) {
187
                if (status != null) {
188
                    if (status.isCancellationRequested()) {
189
                        return -1;
190
                    }
191
//                    status.incrementCurrentValue();
192
                    if((count % 1000) == 0){
193
                        status.setCurValue(count);
194
                    }
195
                }
196
                if (filter.test(line)) {
197
                    continue;
198
                }
199
                count++;
200
                int l = line.getBytes(this.getCharset()).length;
201
                if(l>maxLineLen.getValue()){
202
                    maxLineLen.setValue(l);
203
                }
204
            }
205
            if (status != null) {
206
                status.setCurValue(count);
207
                status.message("");
208
                status.setIndeterminate();
209
            }
210
        } finally {
211
            this.seek(savedpos);
212
        }
213
        return count;
214
    }
215

    
216
    public boolean isRecomemendedTheRecreationOfTheLinesIndex(File index) {
217
        RandomAccessFileIndex line_idx = null;
218
        try {
219
            if (this.lastModified > 0 && this.lastModified > index.lastModified()) {
220
                return true;
221
            }
222
            line_idx = new RandomAccessFileIndex();
223
            line_idx.open(index);
224
            if (this.raf.length() != line_idx.getHeader(INDEX_HEADER_FILESIZE)) {
225
                return true;
226
            }
227
            long creationCost = line_idx.getHeader(INDEX_HEADER_FILESIZE);
228
            if (creationCost < 2000) { // < 2 sec.
229
                return true;
230
            }
231
            if (line_idx.get(-1) == 0) {
232
                // if last index == 0, index is corrupt
233
                return true;
234
            }
235
//            FIXME: isValidIndexOfLines not full implemented 
236
//            Podria comprobarse que  una muestra de 4 o 5 bloques de datos
237
//            repartidas por el fichero tengan el checksum correcto
238
            return false;
239
        } catch (IOException ex) {
240
            return true;
241
        } finally {
242
            IOUtils.closeQuietly(line_idx);
243
        }
244
    }
245

    
246
    public RandomAccessFileIndex createOrOpenIndexOfLines(File index, Predicate<String> filter, SimpleTaskStatus status) throws IOException {
247
        return this.createOrOpenIndexOfLines(index, false, filter, status);
248
    }
249

    
250
    public RandomAccessFileIndex createOrOpenIndexOfLines(File index, boolean safe, Predicate<String> filter, SimpleTaskStatus status) throws IOException {
251
        return createOrOpenIndexOfLines(index, safe, filter, status, null);
252
    }
253

    
254
    public RandomAccessFileIndex createOrOpenIndexOfLines(File index, boolean safe, Predicate<String> filter, SimpleTaskStatus status, Function<BufferedReader,Integer> numberOfLines) throws IOException {
255
        if (this.isRecomemendedTheRecreationOfTheLinesIndex(index)) {
256
            return this.createIndexOfLines(index, safe, filter, status, numberOfLines);
257
        }
258
        return new RandomAccessFileIndex(index);
259
    }
260

    
261
    public RandomAccessFileIndex createIndexOfLines(File index, Predicate<String> filter, SimpleTaskStatus status) throws IOException {
262
        return this.createIndexOfLines(index, false, filter, status);
263
    }
264

    
265
    public RandomAccessFileIndex createIndexOfLines(File index, boolean safe, Predicate<String> filter, SimpleTaskStatus status) throws IOException {
266
        return createIndexOfLines(index, safe, filter, status, null);
267
    }
268
    
269
    public RandomAccessFileIndex createIndexOfLines(File index, boolean safe, Predicate<String> filter, SimpleTaskStatus status, Function<BufferedReader,Integer> numberOfLines) throws IOException {
270
        MutableInt maxLineLen = new MutableInt();
271
        long countLines = this.countLines(filter, maxLineLen, status);
272
        if (countLines < 1) {
273
            return null;
274
        }
275
        int maxBufferForLine = maxLineLen.getValue()+1024;
276
        RandomAccessFileIndex line_idx = new RandomAccessFileIndex();
277
        line_idx.create(index, countLines);
278

    
279
        long savedpos = this.getCurrentPosition();
280
        try {
281
            if (status != null) {
282
                I18nManager i18n = ToolsLocator.getI18nManager();
283
                status.push();
284
                status.message(i18n.getTranslation("_Creating_the_index_of_the_lines"));
285
                status.setRangeOfValues(0, line_idx.size64());
286
                status.setCurValue(0);
287
            }
288
            long t1 = System.currentTimeMillis();
289
            String line = null;
290
            int lineno = 0;
291
            long position = 0;
292
//            line_idx.set(lineno++, position);
293
            if (safe) {
294
                // Don't use buffered reader, slow and safe calculate position
295
                int x = (int) (countLines / 100);
296
                while (lineno < countLines) { //true ) {
297
                    line = this.readLine();
298
                    if (line == null) {
299
                        break;
300
                    }
301
                    if (filter.test(line)) {
302
                        continue;
303
                    }
304
                    line_idx.set(lineno++, position);
305
                    if (status != null) {
306
                        if (status.isCancellationRequested()) {
307
                            status.cancel();
308
                            return null;
309
                        }
310
//                        status.incrementCurrentValue();
311
                        if((lineno % x) == 0){
312
                            status.setCurValue(lineno);
313
                        }
314
                    }
315
                    position = this.getCurrentPosition();
316
//                    line_idx.set(lineno++, position);
317
                }
318
                status.setCurValue(lineno);
319
            } else {
320
                // Use buffered reader, fast and unsafe calculate position.
321
                StringBuilder builder = new StringBuilder();
322
                MyBufferedReader breader = new MyBufferedReader(this, maxBufferForLine);
323
                while (lineno < countLines) {
324
                    this.seek(position);
325
                    breader.clean();
326
                    if(numberOfLines == null){
327
                        line = breader.readLine();
328
                    } else {
329
                        breader.mark(maxBufferForLine);
330
                        Integer nextLine = numberOfLines.apply(breader);
331
                        breader.reset();
332
                        builder.setLength(0);
333
                        for (int i = 0; i < nextLine; i++) {
334
                            String l = breader.readLine();
335
                            if(l != null){
336
                                builder.append(l);
337
                            } else {
338
                                break;
339
                            }
340
                        }
341
                        line = StringUtils.defaultIfBlank(builder.toString(), null);
342
                    }
343
                    if (line == null) {
344
                        break;
345
                    }
346
                    if (filter.test(line)) {
347
                        continue;
348
                    }
349
                    line_idx.set(lineno++, position);
350
                    if (status != null) {
351
                        if (status.isCancellationRequested()) {
352
                            status.cancel();
353
                            return null;
354
                        }
355
                        status.incrementCurrentValue();
356
                    }
357
                    CharBuffer charBuffer = null;
358
                    // ? Y si hay un \r\n ?
359
                    if(breader.isSkipLf()){
360
                        charBuffer = CharBuffer.wrap(line + "\r\n");
361
                    } else {
362
                        charBuffer = CharBuffer.wrap(line + "\n");
363
                    }
364
                    ByteBuffer byteBuffer = this.charset.encode(charBuffer);
365
                    position += byteBuffer.limit();
366

    
367
//                    line_idx.set(lineno++, position);
368
                }
369
            }
370
            long t2 = System.currentTimeMillis();
371
            line_idx.setNumElements(lineno);
372
            line_idx.setHeader(INDEX_HEADER_FILESIZE, this.raf.length());
373
            line_idx.setHeader(INDEX_HEADER_INDEXCREATIONCOST, t2 - t1);
374
            if (status != null) {
375
                status.message("");
376
                status.pop();
377
            }
378
            return line_idx;
379
        } finally {
380
            this.seek(savedpos);
381
        }
382
    }
383

    
384
    public long getLastLinesIndexCreationCost(RandomAccessFileIndex index) {
385
        return index.getHeader(INDEX_HEADER_INDEXCREATIONCOST);
386
    }
387

    
388
    public static void main(String[] args) throws Exception {
389
        new DefaultLibrariesInitializer().fullInitialize();
390

    
391
        String fname;
392
        fname = "/home/jjdelcerro/Descargas/test/origen_coordenadas.csv";
393
//        fname = "/home/jjdelcerro/Descargas/test/esp_poblaciones.csv";
394
//        fname = "/home/jjdelcerro/Descargas/test/esp_provincias.csv";
395
//        fname = "/home/jjdelcerro/Descargas/test/sigpac.csv";
396

    
397
        File data_file = new File(fname);
398
        File idx_file = new File(FilenameUtils.removeExtension(data_file.getAbsolutePath()) + ".idx");
399

    
400
        final TaskStatusManager taskStatusManager = ToolsLocator.getTaskStatusManager();
401
        taskStatusManager.addObserver((Observable observable, Object notification) -> {
402
            TaskStatus status = taskStatusManager.getRunningTaskStatusMostRecent();
403
//            System.out.print("\033[?25l\r");
404
//            if( status!=null && status.isRunning() ) {
405
//                System.out.print("\033[?25l\r");
406
//                System.out.print(status.getTitle()+ " - " + status.getLabel());
407
//                System.out.print("\033[K\033[?12l\033[?25h");
408
//            }
409
//            System.out.flush();
410
        });
411
        SimpleTaskStatus status = taskStatusManager.createDefaultSimpleTaskStatus(data_file.getName());
412
        status.add();
413

    
414
        RandomAccessFileReader reader = new RandomAccessFileReader(data_file, "UTF-8");
415
        System.out.println("Index '" + idx_file.getName() + "', is creation recomended: " + reader.isRecomemendedTheRecreationOfTheLinesIndex(idx_file));
416
        RandomAccessFileIndex lines_idx = reader.createOrOpenIndexOfLines(idx_file, FILTER_NONE, status);
417

    
418
        for (int linenumber = 0; linenumber < lines_idx.size(); linenumber++) {
419
            long lineoffset = lines_idx.get(linenumber);
420
            reader.seek(lineoffset);
421
            MyBufferedReader breader = new MyBufferedReader(reader, MAX_BUFFER_FOR_LINE);
422
            String line = breader.readLine();
423
            if (linenumber < 100) {
424
                System.out.println(String.format("%6d/%d: %s", lineoffset, linenumber, line));
425
            } else if (linenumber == 100) {
426
                System.out.println("More records...");
427
            }
428
        }
429

    
430
        System.out.println("------------------------------------");
431

    
432
        for (int linenumber = lines_idx.size() - 1; linenumber >= 0; linenumber--) {
433
            long lineoffset = lines_idx.get(linenumber);
434
            reader.seek(lineoffset);
435
            MyBufferedReader breader = new MyBufferedReader(reader, MAX_BUFFER_FOR_LINE);
436
            String line = breader.readLine();
437
            if (linenumber < 100) {
438
                System.out.println(String.format("%6d/%d: %s", lineoffset, linenumber, line));
439
            } else if (linenumber == 100) {
440
                System.out.println("More records...");
441
            }
442
        }
443

    
444
    }
445

    
446
    /*
447
        Copy of java's BufferedReader adding clean and isSkipLf methods
448
    */
449
    public static class MyBufferedReader extends BufferedReader {
450

    
451
        private Reader in;
452

    
453
        private char cb[];
454
        private int nChars, nextChar;
455

    
456
        private static final int INVALIDATED = -2;
457
        private static final int UNMARKED = -1;
458
        private int markedChar = UNMARKED;
459
        private int readAheadLimit = 0;
460
        /* Valid only when markedChar > 0 */
461

    
462
        /**
463
         * If the next character is a line feed, skip it
464
         */
465
        private boolean skipLF = false;
466

    
467
        /**
468
         * The skipLF flag when the mark was set
469
         */
470
        private boolean markedSkipLF = false;
471

    
472
        private static int defaultCharBufferSize = 8192;
473
        private static int defaultExpectedLineLength = 80;
474

    
475
        /**
476
         * Creates a buffering character-input stream that uses an input buffer
477
         * of the specified size.
478
         *
479
         * @param in A Reader
480
         * @param sz Input-buffer size
481
         *
482
         * @exception IllegalArgumentException If {@code sz <= 0}
483
         */
484
        public MyBufferedReader(Reader in, int sz) {
485
            super(in);
486
            if (sz <= 0) {
487
                throw new IllegalArgumentException("Buffer size <= 0");
488
            }
489
            this.in = in;
490
            cb = new char[sz];
491
            nextChar = nChars = 0;
492
        }
493

    
494
        /**
495
         * Creates a buffering character-input stream that uses a default-sized
496
         * input buffer.
497
         *
498
         * @param in A Reader
499
         */
500
        public MyBufferedReader(Reader in) {
501
            this(in, defaultCharBufferSize);
502
        }
503
        
504
        /**
505
         * Checks to make sure that the stream has not been closed
506
         */
507
        private void ensureOpen() throws IOException {
508
            if (in == null) {
509
                throw new IOException("Stream closed");
510
            }
511
        }
512

    
513
        /**
514
         * Fills the input buffer, taking the mark into account if it is valid.
515
         */
516
        private void fill() throws IOException {
517
            int dst;
518
            if (markedChar <= UNMARKED) {
519
                /* No mark */
520
                dst = 0;
521
            } else {
522
                /* Marked */
523
                int delta = nextChar - markedChar;
524
                if (delta >= readAheadLimit) {
525
                    /* Gone past read-ahead limit: Invalidate mark */
526
                    markedChar = INVALIDATED;
527
                    readAheadLimit = 0;
528
                    dst = 0;
529
                } else {
530
                    if (readAheadLimit <= cb.length) {
531
                        /* Shuffle in the current buffer */
532
                        System.arraycopy(cb, markedChar, cb, 0, delta);
533
                        markedChar = 0;
534
                        dst = delta;
535
                    } else {
536
                        /* Reallocate buffer to accommodate read-ahead limit */
537
                        char ncb[] = new char[readAheadLimit];
538
                        System.arraycopy(cb, markedChar, ncb, 0, delta);
539
                        cb = ncb;
540
                        markedChar = 0;
541
                        dst = delta;
542
                    }
543
                    nextChar = nChars = delta;
544
                }
545
            }
546

    
547
            int n;
548
            do {
549
                n = in.read(cb, dst, cb.length - dst);
550
            } while (n == 0);
551
            if (n > 0) {
552
                nChars = dst + n;
553
                nextChar = dst;
554
            }
555
        }
556

    
557
        /**
558
         * Reads a single character.
559
         *
560
         * @return The character read, as an integer in the range 0 to 65535
561
         * (<tt>0x00-0xffff</tt>), or -1 if the end of the stream has been
562
         * reached
563
         * @exception IOException If an I/O error occurs
564
         */
565
        @Override
566
        public int read() throws IOException {
567
            synchronized (lock) {
568
                ensureOpen();
569
                for (;;) {
570
                    if (nextChar >= nChars) {
571
                        fill();
572
                        if (nextChar >= nChars) {
573
                            return -1;
574
                        }
575
                    }
576
                    if (skipLF) {
577
                        skipLF = false;
578
                        if (cb[nextChar] == '\n') {
579
                            nextChar++;
580
                            continue;
581
                        }
582
                    }
583
                    return cb[nextChar++];
584
                }
585
            }
586
        }
587

    
588
        /**
589
         * Reads characters into a portion of an array, reading from the
590
         * underlying stream if necessary.
591
         */
592
        private int read1(char[] cbuf, int off, int len) throws IOException {
593
            if (nextChar >= nChars) {
594
                /* If the requested length is at least as large as the buffer, and
595
               if there is no mark/reset activity, and if line feeds are not
596
               being skipped, do not bother to copy the characters into the
597
               local buffer.  In this way buffered streams will cascade
598
               harmlessly. */
599
                if (len >= cb.length && markedChar <= UNMARKED && !skipLF) {
600
                    return in.read(cbuf, off, len);
601
                }
602
                fill();
603
            }
604
            if (nextChar >= nChars) {
605
                return -1;
606
            }
607
            if (skipLF) {
608
                skipLF = false;
609
                if (cb[nextChar] == '\n') {
610
                    nextChar++;
611
                    if (nextChar >= nChars) {
612
                        fill();
613
                    }
614
                    if (nextChar >= nChars) {
615
                        return -1;
616
                    }
617
                }
618
            }
619
            int n = Math.min(len, nChars - nextChar);
620
            System.arraycopy(cb, nextChar, cbuf, off, n);
621
            nextChar += n;
622
            return n;
623
        }
624

    
625
        /**
626
         * Reads characters into a portion of an array.
627
         *
628
         * <p>
629
         * This method implements the general contract of the corresponding
630
         * <code>{@link Reader#read(char[], int, int) read}</code> method of the
631
         * <code>{@link Reader}</code> class. As an additional convenience, it
632
         * attempts to read as many characters as possible by repeatedly
633
         * invoking the <code>read</code> method of the underlying stream. This
634
         * iterated <code>read</code> continues until one of the following
635
         * conditions becomes true: <ul>
636
         *
637
         * <li> The specified number of characters have been read,
638
         *
639
         * <li> The <code>read</code> method of the underlying stream returns
640
         * <code>-1</code>, indicating end-of-file, or
641
         *
642
         * <li> The <code>ready</code> method of the underlying stream returns
643
         * <code>false</code>, indicating that further input requests would
644
         * block.
645
         *
646
         * </ul> If the first <code>read</code> on the underlying stream returns
647
         * <code>-1</code> to indicate end-of-file then this method returns
648
         * <code>-1</code>. Otherwise this method returns the number of
649
         * characters actually read.
650
         *
651
         * <p>
652
         * Subclasses of this class are encouraged, but not required, to attempt
653
         * to read as many characters as possible in the same fashion.
654
         *
655
         * <p>
656
         * Ordinarily this method takes characters from this stream's character
657
         * buffer, filling it from the underlying stream as necessary. If,
658
         * however, the buffer is empty, the mark is not valid, and the
659
         * requested length is at least as large as the buffer, then this method
660
         * will read characters directly from the underlying stream into the
661
         * given array. Thus redundant <code>BufferedReader</code>s will not
662
         * copy data unnecessarily.
663
         *
664
         * @param cbuf Destination buffer
665
         * @param off Offset at which to start storing characters
666
         * @param len Maximum number of characters to read
667
         *
668
         * @return The number of characters read, or -1 if the end of the stream
669
         * has been reached
670
         *
671
         * @exception IOException If an I/O error occurs
672
         */
673
        @Override
674
        public int read(char cbuf[], int off, int len) throws IOException {
675
            synchronized (lock) {
676
                ensureOpen();
677
                if ((off < 0) || (off > cbuf.length) || (len < 0)
678
                        || ((off + len) > cbuf.length) || ((off + len) < 0)) {
679
                    throw new IndexOutOfBoundsException();
680
                } else if (len == 0) {
681
                    return 0;
682
                }
683

    
684
                int n = read1(cbuf, off, len);
685
                if (n <= 0) {
686
                    return n;
687
                }
688
                while ((n < len) && in.ready()) {
689
                    int n1 = read1(cbuf, off + n, len - n);
690
                    if (n1 <= 0) {
691
                        break;
692
                    }
693
                    n += n1;
694
                }
695
                return n;
696
            }
697
        }
698

    
699
        /**
700
         * Reads a line of text. A line is considered to be terminated by any
701
         * one of a line feed ('\n'), a carriage return ('\r'), or a carriage
702
         * return followed immediately by a linefeed.
703
         *
704
         * @param ignoreLF If true, the next '\n' will be skipped
705
         *
706
         * @return A String containing the contents of the line, not including
707
         * any line-termination characters, or null if the end of the stream has
708
         * been reached
709
         *
710
         * @see java.io.LineNumberReader#readLine()
711
         *
712
         * @exception IOException If an I/O error occurs
713
         */
714
        String readLine(boolean ignoreLF) throws IOException {
715
            StringBuilder s = null;
716
            int startChar;
717

    
718
            synchronized (lock) {
719
                ensureOpen();
720
                boolean omitLF = ignoreLF || skipLF;
721

    
722
                bufferLoop:
723
                for (;;) {
724

    
725
                    if (nextChar >= nChars) {
726
                        fill();
727
                    }
728
                    if (nextChar >= nChars) {
729
                        /* EOF */
730
                        if (s != null && s.length() > 0) {
731
                            return s.toString();
732
                        } else {
733
                            return null;
734
                        }
735
                    }
736
                    boolean eol = false;
737
                    char c = 0;
738
                    int i;
739

    
740
                    /* Skip a leftover '\n', if necessary */
741
                    if (omitLF && (cb[nextChar] == '\n')) {
742
                        nextChar++;
743
                    }
744
                    skipLF = false;
745
                    omitLF = false;
746

    
747
                    charLoop:
748
                    for (i = nextChar; i < nChars; i++) {
749
                        c = cb[i];
750
                        if ((c == '\n') || (c == '\r')) {
751
                            eol = true;
752
                            break charLoop;
753
                        }
754
                    }
755

    
756
                    startChar = nextChar;
757
                    nextChar = i;
758

    
759
                    if (eol) {
760
                        String str;
761
                        if (s == null) {
762
                            str = new String(cb, startChar, i - startChar);
763
                        } else {
764
                            s.append(cb, startChar, i - startChar);
765
                            str = s.toString();
766
                        }
767
                        nextChar++;
768
                        if (c == '\r') {
769
                            skipLF = true;
770
                        }
771
                        return str;
772
                    }
773

    
774
                    if (s == null) {
775
                        s = new StringBuilder(defaultExpectedLineLength);
776
                    }
777
                    s.append(cb, startChar, i - startChar);
778
                }
779
            }
780
        }
781

    
782
        /**
783
         * Reads a line of text. A line is considered to be terminated by any
784
         * one of a line feed ('\n'), a carriage return ('\r'), or a carriage
785
         * return followed immediately by a linefeed.
786
         *
787
         * @return A String containing the contents of the line, not including
788
         * any line-termination characters, or null if the end of the stream has
789
         * been reached
790
         *
791
         * @exception IOException If an I/O error occurs
792
         *
793
         * @see java.nio.file.Files#readAllLines
794
         */
795
        @Override
796
        public String readLine() throws IOException {
797
            return readLine(false);
798
        }
799

    
800
        /**
801
         * Skips characters.
802
         *
803
         * @param n The number of characters to skip
804
         *
805
         * @return The number of characters actually skipped
806
         *
807
         * @exception IllegalArgumentException If <code>n</code> is negative.
808
         * @exception IOException If an I/O error occurs
809
         */
810
        @Override
811
        public long skip(long n) throws IOException {
812
            if (n < 0L) {
813
                throw new IllegalArgumentException("skip value is negative");
814
            }
815
            synchronized (lock) {
816
                ensureOpen();
817
                long r = n;
818
                while (r > 0) {
819
                    if (nextChar >= nChars) {
820
                        fill();
821
                    }
822
                    if (nextChar >= nChars) /* EOF */ {
823
                        break;
824
                    }
825
                    if (skipLF) {
826
                        skipLF = false;
827
                        if (cb[nextChar] == '\n') {
828
                            nextChar++;
829
                        }
830
                    }
831
                    long d = nChars - nextChar;
832
                    if (r <= d) {
833
                        nextChar += r;
834
                        r = 0;
835
                        break;
836
                    } else {
837
                        r -= d;
838
                        nextChar = nChars;
839
                    }
840
                }
841
                return n - r;
842
            }
843
        }
844

    
845
        /**
846
         * Tells whether this stream is ready to be read. A buffered character
847
         * stream is ready if the buffer is not empty, or if the underlying
848
         * character stream is ready.
849
         *
850
         * @exception IOException If an I/O error occurs
851
         */
852
        @Override
853
        public boolean ready() throws IOException {
854
            synchronized (lock) {
855
                ensureOpen();
856

    
857
                /*
858
             * If newline needs to be skipped and the next char to be read
859
             * is a newline character, then just skip it right away.
860
                 */
861
                if (skipLF) {
862
                    /* Note that in.ready() will return true if and only if the next
863
                 * read on the stream will not block.
864
                     */
865
                    if (nextChar >= nChars && in.ready()) {
866
                        fill();
867
                    }
868
                    if (nextChar < nChars) {
869
                        if (cb[nextChar] == '\n') {
870
                            nextChar++;
871
                        }
872
                        skipLF = false;
873
                    }
874
                }
875
                return (nextChar < nChars) || in.ready();
876
            }
877
        }
878

    
879
        /**
880
         * Tells whether this stream supports the mark() operation, which it
881
         * does.
882
         */
883
        @Override
884
        public boolean markSupported() {
885
            return true;
886
        }
887

    
888
        /**
889
         * Marks the present position in the stream. Subsequent calls to reset()
890
         * will attempt to reposition the stream to this point.
891
         *
892
         * @param readAheadLimit Limit on the number of characters that may be
893
         * read while still preserving the mark. An attempt to reset the stream
894
         * after reading characters up to this limit or beyond may fail. A limit
895
         * value larger than the size of the input buffer will cause a new
896
         * buffer to be allocated whose size is no smaller than limit. Therefore
897
         * large values should be used with care.
898
         *
899
         * @exception IllegalArgumentException If {@code readAheadLimit < 0}
900
         * @exception IOException If an I/O error occurs
901
         */
902
        @Override
903
        public void mark(int readAheadLimit) throws IOException {
904
            if (readAheadLimit < 0) {
905
                throw new IllegalArgumentException("Read-ahead limit < 0");
906
            }
907
            synchronized (lock) {
908
                ensureOpen();
909
                this.readAheadLimit = readAheadLimit;
910
                markedChar = nextChar;
911
                markedSkipLF = skipLF;
912
            }
913
        }
914

    
915
        /**
916
         * Resets the stream to the most recent mark.
917
         *
918
         * @exception IOException If the stream has never been marked, or if the
919
         * mark has been invalidated
920
         */
921
        @Override
922
        public void reset() throws IOException {
923
            synchronized (lock) {
924
                ensureOpen();
925
                if (markedChar < 0) {
926
                    throw new IOException((markedChar == INVALIDATED)
927
                            ? "Mark invalid"
928
                            : "Stream not marked");
929
                }
930
                nextChar = markedChar;
931
                skipLF = markedSkipLF;
932
            }
933
        }
934

    
935
        @Override
936
        public void close() throws IOException {
937
            synchronized (lock) {
938
                if (in == null) {
939
                    return;
940
                }
941
                try {
942
                    in.close();
943
                } finally {
944
                    in = null;
945
                    cb = null;
946
                }
947
            }
948
        }
949

    
950
        /**
951
         * Returns a {@code Stream}, the elements of which are lines read from
952
         * this {@code BufferedReader}. The {@link Stream} is lazily populated,
953
         * i.e., read only occurs during the
954
         * <a href="../util/stream/package-summary.html#StreamOps">terminal
955
         * stream operation</a>.
956
         *
957
         * <p>
958
         * The reader must not be operated on during the execution of the
959
         * terminal stream operation. Otherwise, the result of the terminal
960
         * stream operation is undefined.
961
         *
962
         * <p>
963
         * After execution of the terminal stream operation there are no
964
         * guarantees that the reader will be at a specific position from which
965
         * to read the next character or line.
966
         *
967
         * <p>
968
         * If an {@link IOException} is thrown when accessing the underlying
969
         * {@code BufferedReader}, it is wrapped in an {@link
970
         * UncheckedIOException} which will be thrown from the {@code Stream}
971
         * method that caused the read to take place. This method will return a
972
         * Stream if invoked on a BufferedReader that is closed. Any operation
973
         * on that stream that requires reading from the BufferedReader after it
974
         * is closed, will cause an UncheckedIOException to be thrown.
975
         *
976
         * @return a {@code Stream<String>} providing the lines of text
977
         * described by this {@code BufferedReader}
978
         *
979
         * @since 1.8
980
         */
981
        @Override
982
        public Stream<String> lines() {
983
            Iterator<String> iter = new Iterator<String>() {
984
                String nextLine = null;
985

    
986
                @Override
987
                public boolean hasNext() {
988
                    if (nextLine != null) {
989
                        return true;
990
                    } else {
991
                        try {
992
                            nextLine = readLine();
993
                            return (nextLine != null);
994
                        } catch (IOException e) {
995
                            throw new UncheckedIOException(e);
996
                        }
997
                    }
998
                }
999

    
1000
                @Override
1001
                public String next() {
1002
                    if (nextLine != null || hasNext()) {
1003
                        String line = nextLine;
1004
                        nextLine = null;
1005
                        return line;
1006
                    } else {
1007
                        throw new NoSuchElementException();
1008
                    }
1009
                }
1010
            };
1011
            return StreamSupport.stream(Spliterators.spliteratorUnknownSize(
1012
                    iter, Spliterator.ORDERED | Spliterator.NONNULL), false);
1013
        }
1014
        
1015
        public boolean isSkipLf() {
1016
            return this.skipLF;
1017
        }
1018
        
1019
        public void clean() {
1020
            nextChar = nChars = 0;
1021
            markedChar = UNMARKED;
1022
            readAheadLimit = 0;
1023
            skipLF = false;
1024
            markedSkipLF = false;
1025
            
1026
        }
1027
    }
1028

    
1029
}