/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.tajo.plan.function.stream; import io.netty.buffer.ByteBuf; import io.netty.util.CharsetUtil; import java.io.Closeable; import java.io.IOException; import java.util.concurrent.atomic.AtomicInteger; public class ByteBufLineReader implements Closeable { private static int DEFAULT_BUFFER = 64 * 1024; private int bufferSize; private long readBytes; private int startIndex; private boolean eof = false; private ByteBuf buffer; private final ByteBufInputChannel channel; private final AtomicInteger lineReadBytes = new AtomicInteger(); private final LineSplitProcessor processor = new LineSplitProcessor(); public ByteBufLineReader(ByteBufInputChannel channel) { this(channel, BufferPool.directBuffer(DEFAULT_BUFFER)); } public ByteBufLineReader(ByteBufInputChannel channel, ByteBuf buf) { this.readBytes = 0; this.channel = channel; this.buffer = buf; this.bufferSize = buf.capacity(); } public long readBytes() { return readBytes - buffer.readableBytes(); } @Override public void close() throws IOException { if (this.buffer.refCnt() > 0) { this.buffer.release(); } this.channel.close(); } public String readLine() throws IOException { ByteBuf buf = readLineBuf(lineReadBytes); if (buf != null) { return buf.toString(CharsetUtil.UTF_8); } return null; } private void fillBuffer() throws IOException { int tailBytes = 0; if (this.readBytes > 0) { //startIndex = 0, readIndex = tailBytes length, writable = (buffer capacity - tailBytes) this.buffer.markReaderIndex(); this.buffer.discardReadBytes(); // compact the buffer tailBytes = this.buffer.writerIndex(); if (!this.buffer.isWritable()) { // a line bytes is large than the buffer BufferPool.ensureWritable(buffer, bufferSize * 2); this.bufferSize = buffer.capacity(); } this.startIndex = 0; } boolean release = true; try { int readBytes = tailBytes; // read only once int localReadBytes = buffer.writeBytes(channel, this.bufferSize - readBytes); if (localReadBytes < 0) { if (buffer.isWritable()) { //if read bytes is less than the buffer capacity, there is no more bytes in the channel eof = true; } } readBytes += localReadBytes; this.readBytes += (readBytes - tailBytes); release = false; this.buffer.readerIndex(this.buffer.readerIndex() + tailBytes); //skip past buffer (tail) } finally { if (release) { buffer.release(); } } } /** * Read a line terminated by one of CR, LF, or CRLF. */ public ByteBuf readLineBuf(AtomicInteger reads) throws IOException { int readBytes = 0; // newline + text line bytes int newlineLength = 0; //length of terminating newline int readable; this.startIndex = buffer.readerIndex(); loop: while (true) { readable = buffer.readableBytes(); if (readable <= 0) { buffer.readerIndex(this.startIndex); fillBuffer(); //compact and fill buffer //if buffer.writerIndex() is zero, there is no bytes in buffer if (!buffer.isReadable() && buffer.writerIndex() == 0) { reads.set(0); return null; } else { //skip first newLine if (processor.isPrevCharCR() && buffer.getByte(buffer.readerIndex()) == LineSplitProcessor.LF) { buffer.skipBytes(1); if(eof && !buffer.isReadable()) { reads.set(1); return null; } newlineLength++; readBytes++; startIndex = buffer.readerIndex(); } } readable = buffer.readableBytes(); } int endIndex = buffer.forEachByte(buffer.readerIndex(), readable, processor); if (endIndex < 0) { //does not appeared terminating newline buffer.readerIndex(buffer.writerIndex()); // set to end buffer if(eof){ readBytes += (buffer.readerIndex() - startIndex); break loop; } } else { buffer.readerIndex(endIndex + 1); readBytes += (buffer.readerIndex() - startIndex); //past newline + text line //appeared terminating CRLF if (processor.isPrevCharCR() && buffer.isReadable() && buffer.getByte(buffer.readerIndex()) == LineSplitProcessor.LF) { buffer.skipBytes(1); readBytes++; newlineLength += 2; } else { newlineLength += 1; } break loop; } } reads.set(readBytes); return buffer.slice(startIndex, readBytes - newlineLength); } }