001package org.unix4j.io;
002
003import java.io.IOException;
004import java.io.Reader;
005
006import org.unix4j.line.Line;
007import org.unix4j.line.SingleCharSequenceLine;
008
009/**
010 * Input device based on a {@link Reader} forming the base for most input
011 * devices; handles parsing and recognition of {@link Line lines}.
012 */
013public class ReaderInput extends AbstractInput {
014
015        private final Reader reader;
016        private final char[] buffer = new char[1024];
017        private int length;
018        private int offset;
019
020        /**
021         * Constructor with reader.
022         * 
023         * @param reader
024         *            the reader forming the basis of this input device.
025         */
026        public ReaderInput(Reader reader) {
027                this.reader = reader;
028                readBuffer();
029        }
030
031        @Override
032        public boolean hasMoreLines() {
033                return length > offset;
034        }
035
036        @Override
037        public Line readLine() {
038                if (length == 0) {
039                        readBuffer();
040                }
041                if (length > offset) {
042                        return makeLine(null);
043                }
044                // no more lines
045                return null;
046        }
047
048        private Line makeLine(StringBuilder lineBuilder) {
049                int len = length;
050                int index = offset;
051                do {
052                        while (index < len) {
053                                final char ch0 = buffer[index];
054                                if (ch0 == '\n' || ch0 == '\r') {
055                                        int contentEnd = index;
056                                        index++;
057                                        if (index < len) {
058                                                final char ch1 = buffer[index];
059                                                if ((ch1 == '\n' || ch1 == '\r') && ch0 != ch1) {
060                                                        index++;
061                                                }
062                                                if (lineBuilder == null) {
063                                                        lineBuilder = new StringBuilder(index - offset);
064                                                }
065                                                lineBuilder.append(buffer, offset, index - offset);
066                                                if (index < len) {
067                                                        offset = index;
068                                                } else {
069                                                        readBuffer();
070                                                }
071                                                return new SingleCharSequenceLine(lineBuilder, index - contentEnd);
072                                        } else {
073                                                if (lineBuilder == null) {
074                                                        lineBuilder = new StringBuilder(len - offset + 1);
075                                                }
076                                                lineBuilder.append(buffer, offset, len - offset);
077                                                return makeLineMaybeWithOneMoreLineEndingChar(lineBuilder);
078                                        }
079                                }
080                                index++;
081                        }
082                        if (lineBuilder == null) {
083                                lineBuilder = new StringBuilder(len - offset + 16);
084                        }
085                        lineBuilder.append(buffer, offset, len - offset);
086                        readBuffer();
087                        index = offset;
088                        len = length;
089                } while (index < len);
090
091                // eof, no newline, return rest as a line if there is something to
092                // return
093                return lineBuilder.length() > 0 ? new SingleCharSequenceLine(lineBuilder, 0) : null;
094        }
095
096        private Line makeLineMaybeWithOneMoreLineEndingChar(StringBuilder lineBuilder) {
097                int lineEndingLength = 1;
098                readBuffer();
099                if (offset < length) {
100                        final char ch = buffer[offset];
101                        if (ch == '\n' || ch == '\r') {
102                                if (lineBuilder.charAt(lineBuilder.length() - 1) != ch) {
103                                        lineBuilder.append(ch);
104                                        lineEndingLength++;
105                                        offset++;
106                                }
107                        }
108                }
109                return new SingleCharSequenceLine(lineBuilder, lineEndingLength);
110        }
111
112        private void readBuffer() {
113                try {
114                        this.length = reader.read(buffer);
115                        this.offset = 0;
116                } catch (IOException e) {
117                        throw new RuntimeException(e);
118                }
119        }
120
121}