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 / csv / virtualrows / RandomAccessFileReader.java @ 47506

History | View | Annotate | Download (36.9 KB)

1
package org.gvsig.fmap.dal.store.csv.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.gvsig.tools.ToolsLocator;
25
import org.gvsig.tools.i18n.I18nManager;
26
import org.gvsig.tools.library.impl.DefaultLibrariesInitializer;
27
import org.gvsig.tools.observer.Observable;
28
import org.gvsig.tools.task.SimpleTaskStatus;
29
import org.gvsig.tools.task.TaskStatus;
30
import org.gvsig.tools.task.TaskStatusManager;
31

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
236
    public RandomAccessFileIndex createOrOpenIndexOfLines(File index, Predicate<String> filter, SimpleTaskStatus status) throws IOException {
237
        return this.createOrOpenIndexOfLines(index, false, filter, status);
238
    }
239

    
240
    public RandomAccessFileIndex createOrOpenIndexOfLines(File index, boolean safe, Predicate<String> filter, SimpleTaskStatus status) throws IOException {
241
        return createOrOpenIndexOfLines(index, safe, filter, status, null);
242
    }
243

    
244
    public RandomAccessFileIndex createOrOpenIndexOfLines(File index, boolean safe, Predicate<String> filter, SimpleTaskStatus status, Function<BufferedReader,Integer> numberOfLines) throws IOException {
245
        if (this.isRecomemendedTheRecreationOfTheLinesIndex(index)) {
246
            return this.createIndexOfLines(index, safe, filter, status, numberOfLines);
247
        }
248
        return new RandomAccessFileIndex(index);
249
    }
250

    
251
    public RandomAccessFileIndex createIndexOfLines(File index, Predicate<String> filter, SimpleTaskStatus status) throws IOException {
252
        return this.createIndexOfLines(index, false, filter, status);
253
    }
254

    
255
    public RandomAccessFileIndex createIndexOfLines(File index, boolean safe, Predicate<String> filter, SimpleTaskStatus status) throws IOException {
256
        return createIndexOfLines(index, safe, filter, status, null);
257
    }
258
    
259
    public RandomAccessFileIndex createIndexOfLines(File index, boolean safe, Predicate<String> filter, SimpleTaskStatus status, Function<BufferedReader,Integer> numberOfLines) throws IOException {
260
        long countLines = this.countLines(filter, status);
261
        if (countLines < 1) {
262
            return null;
263
        }
264
        RandomAccessFileIndex line_idx = new RandomAccessFileIndex();
265
        line_idx.create(index, countLines);
266

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

    
355
//                    line_idx.set(lineno++, position);
356
                }
357
            }
358
            long t2 = System.currentTimeMillis();
359
            line_idx.setNumElements(lineno);
360
            line_idx.setHeader(INDEX_HEADER_FILESIZE, this.raf.length());
361
            line_idx.setHeader(INDEX_HEADER_INDEXCREATIONCOST, t2 - t1);
362
            if (status != null) {
363
                status.message("");
364
                status.pop();
365
            }
366
            return line_idx;
367
        } finally {
368
            this.seek(savedpos);
369
        }
370
    }
371

    
372
    public long getLastLinesIndexCreationCost(RandomAccessFileIndex index) {
373
        return index.getHeader(INDEX_HEADER_INDEXCREATIONCOST);
374
    }
375

    
376
    public static void main(String[] args) throws Exception {
377
        new DefaultLibrariesInitializer().fullInitialize();
378

    
379
        String fname;
380
        fname = "/home/jjdelcerro/Descargas/test/origen_coordenadas.csv";
381
//        fname = "/home/jjdelcerro/Descargas/test/esp_poblaciones.csv";
382
//        fname = "/home/jjdelcerro/Descargas/test/esp_provincias.csv";
383
//        fname = "/home/jjdelcerro/Descargas/test/sigpac.csv";
384

    
385
        File data_file = new File(fname);
386
        File idx_file = new File(FilenameUtils.removeExtension(data_file.getAbsolutePath()) + ".idx");
387

    
388
        final TaskStatusManager taskStatusManager = ToolsLocator.getTaskStatusManager();
389
        taskStatusManager.addObserver((Observable observable, Object notification) -> {
390
            TaskStatus status = taskStatusManager.getRunningTaskStatusMostRecent();
391
//            System.out.print("\033[?25l\r");
392
//            if( status!=null && status.isRunning() ) {
393
//                System.out.print("\033[?25l\r");
394
//                System.out.print(status.getTitle()+ " - " + status.getLabel());
395
//                System.out.print("\033[K\033[?12l\033[?25h");
396
//            }
397
//            System.out.flush();
398
        });
399
        SimpleTaskStatus status = taskStatusManager.createDefaultSimpleTaskStatus(data_file.getName());
400
        status.add();
401

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

    
406
        for (int linenumber = 0; linenumber < lines_idx.size(); linenumber++) {
407
            long lineoffset = lines_idx.get(linenumber);
408
            reader.seek(lineoffset);
409
            MyBufferedReader breader = new MyBufferedReader(reader, MAX_BUFFER_FOR_LINE);
410
            String line = breader.readLine();
411
            if (linenumber < 100) {
412
                System.out.println(String.format("%6d/%d: %s", lineoffset, linenumber, line));
413
            } else if (linenumber == 100) {
414
                System.out.println("More records...");
415
            }
416
        }
417

    
418
        System.out.println("------------------------------------");
419

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

    
432
    }
433

    
434
    /*
435
        Copy of java's BufferedReader adding clean and isSkipLf methods
436
    */
437
    public static class MyBufferedReader extends BufferedReader {
438

    
439
        private Reader in;
440

    
441
        private char cb[];
442
        private int nChars, nextChar;
443

    
444
        private static final int INVALIDATED = -2;
445
        private static final int UNMARKED = -1;
446
        private int markedChar = UNMARKED;
447
        private int readAheadLimit = 0;
448
        /* Valid only when markedChar > 0 */
449

    
450
        /**
451
         * If the next character is a line feed, skip it
452
         */
453
        private boolean skipLF = false;
454

    
455
        /**
456
         * The skipLF flag when the mark was set
457
         */
458
        private boolean markedSkipLF = false;
459

    
460
        private static int defaultCharBufferSize = 8192;
461
        private static int defaultExpectedLineLength = 80;
462

    
463
        /**
464
         * Creates a buffering character-input stream that uses an input buffer
465
         * of the specified size.
466
         *
467
         * @param in A Reader
468
         * @param sz Input-buffer size
469
         *
470
         * @exception IllegalArgumentException If {@code sz <= 0}
471
         */
472
        public MyBufferedReader(Reader in, int sz) {
473
            super(in);
474
            if (sz <= 0) {
475
                throw new IllegalArgumentException("Buffer size <= 0");
476
            }
477
            this.in = in;
478
            cb = new char[sz];
479
            nextChar = nChars = 0;
480
        }
481

    
482
        /**
483
         * Creates a buffering character-input stream that uses a default-sized
484
         * input buffer.
485
         *
486
         * @param in A Reader
487
         */
488
        public MyBufferedReader(Reader in) {
489
            this(in, defaultCharBufferSize);
490
        }
491
        
492
        /**
493
         * Checks to make sure that the stream has not been closed
494
         */
495
        private void ensureOpen() throws IOException {
496
            if (in == null) {
497
                throw new IOException("Stream closed");
498
            }
499
        }
500

    
501
        /**
502
         * Fills the input buffer, taking the mark into account if it is valid.
503
         */
504
        private void fill() throws IOException {
505
            int dst;
506
            if (markedChar <= UNMARKED) {
507
                /* No mark */
508
                dst = 0;
509
            } else {
510
                /* Marked */
511
                int delta = nextChar - markedChar;
512
                if (delta >= readAheadLimit) {
513
                    /* Gone past read-ahead limit: Invalidate mark */
514
                    markedChar = INVALIDATED;
515
                    readAheadLimit = 0;
516
                    dst = 0;
517
                } else {
518
                    if (readAheadLimit <= cb.length) {
519
                        /* Shuffle in the current buffer */
520
                        System.arraycopy(cb, markedChar, cb, 0, delta);
521
                        markedChar = 0;
522
                        dst = delta;
523
                    } else {
524
                        /* Reallocate buffer to accommodate read-ahead limit */
525
                        char ncb[] = new char[readAheadLimit];
526
                        System.arraycopy(cb, markedChar, ncb, 0, delta);
527
                        cb = ncb;
528
                        markedChar = 0;
529
                        dst = delta;
530
                    }
531
                    nextChar = nChars = delta;
532
                }
533
            }
534

    
535
            int n;
536
            do {
537
                n = in.read(cb, dst, cb.length - dst);
538
            } while (n == 0);
539
            if (n > 0) {
540
                nChars = dst + n;
541
                nextChar = dst;
542
            }
543
        }
544

    
545
        /**
546
         * Reads a single character.
547
         *
548
         * @return The character read, as an integer in the range 0 to 65535
549
         * (<tt>0x00-0xffff</tt>), or -1 if the end of the stream has been
550
         * reached
551
         * @exception IOException If an I/O error occurs
552
         */
553
        @Override
554
        public int read() throws IOException {
555
            synchronized (lock) {
556
                ensureOpen();
557
                for (;;) {
558
                    if (nextChar >= nChars) {
559
                        fill();
560
                        if (nextChar >= nChars) {
561
                            return -1;
562
                        }
563
                    }
564
                    if (skipLF) {
565
                        skipLF = false;
566
                        if (cb[nextChar] == '\n') {
567
                            nextChar++;
568
                            continue;
569
                        }
570
                    }
571
                    return cb[nextChar++];
572
                }
573
            }
574
        }
575

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

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

    
672
                int n = read1(cbuf, off, len);
673
                if (n <= 0) {
674
                    return n;
675
                }
676
                while ((n < len) && in.ready()) {
677
                    int n1 = read1(cbuf, off + n, len - n);
678
                    if (n1 <= 0) {
679
                        break;
680
                    }
681
                    n += n1;
682
                }
683
                return n;
684
            }
685
        }
686

    
687
        /**
688
         * Reads a line of text. A line is considered to be terminated by any
689
         * one of a line feed ('\n'), a carriage return ('\r'), or a carriage
690
         * return followed immediately by a linefeed.
691
         *
692
         * @param ignoreLF If true, the next '\n' will be skipped
693
         *
694
         * @return A String containing the contents of the line, not including
695
         * any line-termination characters, or null if the end of the stream has
696
         * been reached
697
         *
698
         * @see java.io.LineNumberReader#readLine()
699
         *
700
         * @exception IOException If an I/O error occurs
701
         */
702
        String readLine(boolean ignoreLF) throws IOException {
703
            StringBuilder s = null;
704
            int startChar;
705

    
706
            synchronized (lock) {
707
                ensureOpen();
708
                boolean omitLF = ignoreLF || skipLF;
709

    
710
                bufferLoop:
711
                for (;;) {
712

    
713
                    if (nextChar >= nChars) {
714
                        fill();
715
                    }
716
                    if (nextChar >= nChars) {
717
                        /* EOF */
718
                        if (s != null && s.length() > 0) {
719
                            return s.toString();
720
                        } else {
721
                            return null;
722
                        }
723
                    }
724
                    boolean eol = false;
725
                    char c = 0;
726
                    int i;
727

    
728
                    /* Skip a leftover '\n', if necessary */
729
                    if (omitLF && (cb[nextChar] == '\n')) {
730
                        nextChar++;
731
                    }
732
                    skipLF = false;
733
                    omitLF = false;
734

    
735
                    charLoop:
736
                    for (i = nextChar; i < nChars; i++) {
737
                        c = cb[i];
738
                        if ((c == '\n') || (c == '\r')) {
739
                            eol = true;
740
                            break charLoop;
741
                        }
742
                    }
743

    
744
                    startChar = nextChar;
745
                    nextChar = i;
746

    
747
                    if (eol) {
748
                        String str;
749
                        if (s == null) {
750
                            str = new String(cb, startChar, i - startChar);
751
                        } else {
752
                            s.append(cb, startChar, i - startChar);
753
                            str = s.toString();
754
                        }
755
                        nextChar++;
756
                        if (c == '\r') {
757
                            skipLF = true;
758
                        }
759
                        return str;
760
                    }
761

    
762
                    if (s == null) {
763
                        s = new StringBuilder(defaultExpectedLineLength);
764
                    }
765
                    s.append(cb, startChar, i - startChar);
766
                }
767
            }
768
        }
769

    
770
        /**
771
         * Reads a line of text. A line is considered to be terminated by any
772
         * one of a line feed ('\n'), a carriage return ('\r'), or a carriage
773
         * return followed immediately by a linefeed.
774
         *
775
         * @return A String containing the contents of the line, not including
776
         * any line-termination characters, or null if the end of the stream has
777
         * been reached
778
         *
779
         * @exception IOException If an I/O error occurs
780
         *
781
         * @see java.nio.file.Files#readAllLines
782
         */
783
        @Override
784
        public String readLine() throws IOException {
785
            return readLine(false);
786
        }
787

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

    
833
        /**
834
         * Tells whether this stream is ready to be read. A buffered character
835
         * stream is ready if the buffer is not empty, or if the underlying
836
         * character stream is ready.
837
         *
838
         * @exception IOException If an I/O error occurs
839
         */
840
        @Override
841
        public boolean ready() throws IOException {
842
            synchronized (lock) {
843
                ensureOpen();
844

    
845
                /*
846
             * If newline needs to be skipped and the next char to be read
847
             * is a newline character, then just skip it right away.
848
                 */
849
                if (skipLF) {
850
                    /* Note that in.ready() will return true if and only if the next
851
                 * read on the stream will not block.
852
                     */
853
                    if (nextChar >= nChars && in.ready()) {
854
                        fill();
855
                    }
856
                    if (nextChar < nChars) {
857
                        if (cb[nextChar] == '\n') {
858
                            nextChar++;
859
                        }
860
                        skipLF = false;
861
                    }
862
                }
863
                return (nextChar < nChars) || in.ready();
864
            }
865
        }
866

    
867
        /**
868
         * Tells whether this stream supports the mark() operation, which it
869
         * does.
870
         */
871
        @Override
872
        public boolean markSupported() {
873
            return true;
874
        }
875

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

    
903
        /**
904
         * Resets the stream to the most recent mark.
905
         *
906
         * @exception IOException If the stream has never been marked, or if the
907
         * mark has been invalidated
908
         */
909
        @Override
910
        public void reset() throws IOException {
911
            synchronized (lock) {
912
                ensureOpen();
913
                if (markedChar < 0) {
914
                    throw new IOException((markedChar == INVALIDATED)
915
                            ? "Mark invalid"
916
                            : "Stream not marked");
917
                }
918
                nextChar = markedChar;
919
                skipLF = markedSkipLF;
920
            }
921
        }
922

    
923
        @Override
924
        public void close() throws IOException {
925
            synchronized (lock) {
926
                if (in == null) {
927
                    return;
928
                }
929
                try {
930
                    in.close();
931
                } finally {
932
                    in = null;
933
                    cb = null;
934
                }
935
            }
936
        }
937

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

    
974
                @Override
975
                public boolean hasNext() {
976
                    if (nextLine != null) {
977
                        return true;
978
                    } else {
979
                        try {
980
                            nextLine = readLine();
981
                            return (nextLine != null);
982
                        } catch (IOException e) {
983
                            throw new UncheckedIOException(e);
984
                        }
985
                    }
986
                }
987

    
988
                @Override
989
                public String next() {
990
                    if (nextLine != null || hasNext()) {
991
                        String line = nextLine;
992
                        nextLine = null;
993
                        return line;
994
                    } else {
995
                        throw new NoSuchElementException();
996
                    }
997
                }
998
            };
999
            return StreamSupport.stream(Spliterators.spliteratorUnknownSize(
1000
                    iter, Spliterator.ORDERED | Spliterator.NONNULL), false);
1001
        }
1002
        
1003
        public boolean isSkipLf() {
1004
            return this.skipLF;
1005
        }
1006
        
1007
        public void clean() {
1008
            nextChar = nChars = 0;
1009
            markedChar = UNMARKED;
1010
            readAheadLimit = 0;
1011
            skipLF = false;
1012
            markedSkipLF = false;
1013
            
1014
        }
1015
    }
1016

    
1017
}