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}