| /* |
| * 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.lucene.analysis.util; |
| |
| import java.io.IOException; |
| import java.io.Reader; |
| import org.apache.lucene.util.ArrayUtil; |
| |
| /** |
| * Acts like a forever growing char[] as you read characters into it from the provided reader, but |
| * internally it uses a circular buffer to only hold the characters that haven't been freed yet. |
| * This is like a PushbackReader, except you don't have to specify up-front the max size of the |
| * buffer, but you do have to periodically call {@link #freeBefore}. |
| */ |
| public final class RollingCharBuffer { |
| |
| private Reader reader; |
| |
| private char[] buffer = new char[512]; |
| |
| // Next array index to write to in buffer: |
| private int nextWrite; |
| |
| // Next absolute position to read from reader: |
| private int nextPos; |
| |
| // How many valid chars (wrapped) are in the buffer: |
| private int count; |
| |
| // True if we hit EOF |
| private boolean end; |
| |
| /** Clear array and switch to new reader. */ |
| public void reset(Reader reader) { |
| this.reader = reader; |
| nextPos = 0; |
| nextWrite = 0; |
| count = 0; |
| end = false; |
| } |
| |
| /* Absolute position read. NOTE: pos must not jump |
| * ahead by more than 1! Ie, it's OK to read arbitarily |
| * far back (just not prior to the last {@link |
| * #freeBefore}), but NOT ok to read arbitrarily far |
| * ahead. Returns -1 if you hit EOF. */ |
| public int get(int pos) throws IOException { |
| // System.out.println(" get pos=" + pos + " nextPos=" + nextPos + " count=" + count); |
| if (pos == nextPos) { |
| if (end) { |
| return -1; |
| } |
| if (count == buffer.length) { |
| // Grow |
| final char[] newBuffer = new char[ArrayUtil.oversize(1 + count, Character.BYTES)]; |
| // System.out.println(Thread.currentThread().getName() + ": cb grow " + newBuffer.length); |
| System.arraycopy(buffer, nextWrite, newBuffer, 0, buffer.length - nextWrite); |
| System.arraycopy(buffer, 0, newBuffer, buffer.length - nextWrite, nextWrite); |
| nextWrite = buffer.length; |
| buffer = newBuffer; |
| } |
| if (nextWrite == buffer.length) { |
| nextWrite = 0; |
| } |
| |
| final int toRead = buffer.length - Math.max(count, nextWrite); |
| final int readCount = reader.read(buffer, nextWrite, toRead); |
| if (readCount == -1) { |
| end = true; |
| return -1; |
| } |
| final int ch = buffer[nextWrite]; |
| nextWrite += readCount; |
| count += readCount; |
| nextPos += readCount; |
| return ch; |
| } else { |
| // Cannot read from future (except by 1): |
| assert pos < nextPos; |
| |
| // Cannot read from already freed past: |
| assert nextPos - pos <= count : "nextPos=" + nextPos + " pos=" + pos + " count=" + count; |
| |
| return buffer[getIndex(pos)]; |
| } |
| } |
| |
| // For assert: |
| private boolean inBounds(int pos) { |
| return pos >= 0 && pos < nextPos && pos >= nextPos - count; |
| } |
| |
| private int getIndex(int pos) { |
| int index = nextWrite - (nextPos - pos); |
| if (index < 0) { |
| // Wrap: |
| index += buffer.length; |
| assert index >= 0; |
| } |
| return index; |
| } |
| |
| public char[] get(int posStart, int length) { |
| assert length > 0; |
| assert inBounds(posStart) : "posStart=" + posStart + " length=" + length; |
| // System.out.println(" buffer.get posStart=" + posStart + " len=" + length); |
| |
| final int startIndex = getIndex(posStart); |
| final int endIndex = getIndex(posStart + length); |
| // System.out.println(" startIndex=" + startIndex + " endIndex=" + endIndex); |
| |
| final char[] result = new char[length]; |
| if (endIndex >= startIndex && length < buffer.length) { |
| System.arraycopy(buffer, startIndex, result, 0, endIndex - startIndex); |
| } else { |
| // Wrapped: |
| final int part1 = buffer.length - startIndex; |
| System.arraycopy(buffer, startIndex, result, 0, part1); |
| System.arraycopy(buffer, 0, result, buffer.length - startIndex, length - part1); |
| } |
| return result; |
| } |
| |
| /** Call this to notify us that no chars before this absolute position are needed anymore. */ |
| public void freeBefore(int pos) { |
| assert pos >= 0; |
| assert pos <= nextPos; |
| final int newCount = nextPos - pos; |
| assert newCount <= count : "newCount=" + newCount + " count=" + count; |
| assert newCount <= buffer.length : "newCount=" + newCount + " buf.length=" + buffer.length; |
| count = newCount; |
| } |
| } |