001package org.unix4j.io;
002
003import org.unix4j.line.Line;
004import org.unix4j.line.SingleCharSequenceLine;
005
006import java.io.IOException;
007import java.io.Reader;
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 boolean closeReaderOnClose;
017        private final char[] buffer = new char[1024];
018        private int length;
019        private int offset;
020
021        /**
022         * Constructor with reader.
023         *
024         * @param reader
025         *            the reader forming the basis of this input device.
026         */
027        public ReaderInput(Reader reader) {
028                this(reader, false);
029        }
030        
031        /**
032         * Constructor with reader.
033         * 
034         * @param reader
035         *            the reader forming the basis of this input device.
036         * @param closeReaderOnClose
037         *                        if true the reader will be closed when {@link #close()} is invoked
038         */
039        protected ReaderInput(Reader reader, boolean closeReaderOnClose) {
040                this.reader = reader;
041                this.closeReaderOnClose = closeReaderOnClose;
042                readBuffer();
043        }
044
045        @Override
046        public boolean hasMoreLines() {
047                return length > offset;
048        }
049
050        @Override
051        public Line readLine() {
052                if (length == 0) {
053                        readBuffer();
054                }
055                if (length > offset) {
056                        return makeLine(null);
057                }
058                // no more lines
059                return null;
060        }
061
062        private Line makeLine(StringBuilder lineBuilder) {
063                int len = length;
064                int index = offset;
065                do {
066                        while (index < len) {
067                                final char ch0 = buffer[index];
068                                if (ch0 == '\n' || ch0 == '\r') {
069                                        int contentEnd = index;
070                                        index++;
071                                        if (index < len) {
072                                                final char ch1 = buffer[index];
073                                                if ((ch1 == '\n' || ch1 == '\r') && ch0 != ch1) {
074                                                        index++;
075                                                }
076                                                if (lineBuilder == null) {
077                                                        lineBuilder = new StringBuilder(index - offset);
078                                                }
079                                                lineBuilder.append(buffer, offset, index - offset);
080                                                if (index < len) {
081                                                        offset = index;
082                                                } else {
083                                                        readBuffer();
084                                                }
085                                                return new SingleCharSequenceLine(lineBuilder, index - contentEnd);
086                                        } else {
087                                                if (lineBuilder == null) {
088                                                        lineBuilder = new StringBuilder(len - offset + 1);
089                                                }
090                                                lineBuilder.append(buffer, offset, len - offset);
091                                                return makeLineMaybeWithOneMoreLineEndingChar(lineBuilder);
092                                        }
093                                }
094                                index++;
095                        }
096                        if (lineBuilder == null) {
097                                lineBuilder = new StringBuilder(len - offset + 16);
098                        }
099                        lineBuilder.append(buffer, offset, len - offset);
100                        readBuffer();
101                        index = offset;
102                        len = length;
103                } while (index < len);
104
105                // eof, no newline, return rest as a line if there is something to
106                // return
107                return lineBuilder.length() > 0 ? new SingleCharSequenceLine(lineBuilder, 0) : null;
108        }
109
110        private Line makeLineMaybeWithOneMoreLineEndingChar(StringBuilder lineBuilder) {
111                int lineEndingLength = 1;
112                readBuffer();
113                if (offset < length) {
114                        final char ch = buffer[offset];
115                        if (ch == '\n' || ch == '\r') {
116                                if (lineBuilder.charAt(lineBuilder.length() - 1) != ch) {
117                                        lineBuilder.append(ch);
118                                        lineEndingLength++;
119                                        offset++;
120                                }
121                        }
122                }
123                return new SingleCharSequenceLine(lineBuilder, lineEndingLength);
124        }
125
126        private void readBuffer() {
127                try {
128                        this.length = reader.read(buffer);
129                        this.offset = 0;
130                } catch (IOException e) {
131                        throw new RuntimeException(e);
132                }
133        }
134
135        @Override
136        public void close() {
137                if (closeReaderOnClose) {
138                        try {
139                                reader.close();
140                        } catch (final IOException e) {
141                                throw new RuntimeException("Could not close underlying reader, e=" + e, e);
142                        }
143                }
144        }
145}