blob: 8bfac85d266a26782a5b1ef074937a7da801e233 [file] [log] [blame]
/*
* 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.util;
import java.util.Arrays;
/**
* A pool for int blocks similar to {@link ByteBlockPool}
* @lucene.internal
*/
public final class IntBlockPool {
public final static int INT_BLOCK_SHIFT = 13;
public final static int INT_BLOCK_SIZE = 1 << INT_BLOCK_SHIFT;
public final static int INT_BLOCK_MASK = INT_BLOCK_SIZE - 1;
/** Abstract class for allocating and freeing int
* blocks. */
public abstract static class Allocator {
protected final int blockSize;
public Allocator(int blockSize) {
this.blockSize = blockSize;
}
public abstract void recycleIntBlocks(int[][] blocks, int start, int end);
public int[] getIntBlock() {
return new int[blockSize];
}
}
/** A simple {@link Allocator} that never recycles. */
public static final class DirectAllocator extends Allocator {
/**
* Creates a new {@link DirectAllocator} with a default block size
*/
public DirectAllocator() {
super(INT_BLOCK_SIZE);
}
@Override
public void recycleIntBlocks(int[][] blocks, int start, int end) {
}
}
/** array of buffers currently used in the pool. Buffers are allocated if needed don't modify this outside of this class */
public int[][] buffers = new int[10][];
/** index into the buffers array pointing to the current buffer used as the head */
private int bufferUpto = -1;
/** Pointer to the current position in head buffer */
public int intUpto = INT_BLOCK_SIZE;
/** Current head buffer */
public int[] buffer;
/** Current head offset */
public int intOffset = -INT_BLOCK_SIZE;
private final Allocator allocator;
/**
* Creates a new {@link IntBlockPool} with a default {@link Allocator}.
* @see IntBlockPool#nextBuffer()
*/
public IntBlockPool() {
this(new DirectAllocator());
}
/**
* Creates a new {@link IntBlockPool} with the given {@link Allocator}.
* @see IntBlockPool#nextBuffer()
*/
public IntBlockPool(Allocator allocator) {
this.allocator = allocator;
}
/**
* Resets the pool to its initial state reusing the first buffer. Calling
* {@link IntBlockPool#nextBuffer()} is not needed after reset.
*/
public void reset() {
this.reset(true, true);
}
/**
* Expert: Resets the pool to its initial state reusing the first buffer.
* @param zeroFillBuffers if <code>true</code> the buffers are filled with <tt>0</tt>.
* This should be set to <code>true</code> if this pool is used with
* {@link SliceWriter}.
* @param reuseFirst if <code>true</code> the first buffer will be reused and calling
* {@link IntBlockPool#nextBuffer()} is not needed after reset iff the
* block pool was used before ie. {@link IntBlockPool#nextBuffer()} was called before.
*/
public void reset(boolean zeroFillBuffers, boolean reuseFirst) {
if (bufferUpto != -1) {
// We allocated at least one buffer
if (zeroFillBuffers) {
for(int i=0;i<bufferUpto;i++) {
// Fully zero fill buffers that we fully used
Arrays.fill(buffers[i], 0);
}
// Partial zero fill the final buffer
Arrays.fill(buffers[bufferUpto], 0, intUpto, 0);
}
if (bufferUpto > 0 || !reuseFirst) {
final int offset = reuseFirst ? 1 : 0;
// Recycle all but the first buffer
allocator.recycleIntBlocks(buffers, offset, 1+bufferUpto);
Arrays.fill(buffers, offset, bufferUpto+1, null);
}
if (reuseFirst) {
// Re-use the first buffer
bufferUpto = 0;
intUpto = 0;
intOffset = 0;
buffer = buffers[0];
} else {
bufferUpto = -1;
intUpto = INT_BLOCK_SIZE;
intOffset = -INT_BLOCK_SIZE;
buffer = null;
}
}
}
/**
* Advances the pool to its next buffer. This method should be called once
* after the constructor to initialize the pool. In contrast to the
* constructor a {@link IntBlockPool#reset()} call will advance the pool to
* its first buffer immediately.
*/
public void nextBuffer() {
if (1+bufferUpto == buffers.length) {
int[][] newBuffers = new int[(int) (buffers.length*1.5)][];
System.arraycopy(buffers, 0, newBuffers, 0, buffers.length);
buffers = newBuffers;
}
buffer = buffers[1+bufferUpto] = allocator.getIntBlock();
bufferUpto++;
intUpto = 0;
intOffset += INT_BLOCK_SIZE;
}
/**
* Creates a new int slice with the given starting size and returns the slices offset in the pool.
* @see SliceReader
*/
private int newSlice(final int size) {
if (intUpto > INT_BLOCK_SIZE-size) {
nextBuffer();
assert assertSliceBuffer(buffer);
}
final int upto = intUpto;
intUpto += size;
buffer[intUpto - 1] = 16;
return upto;
}
private static boolean assertSliceBuffer(int[] buffer) {
int count = 0;
for (int i = 0; i < buffer.length; i++) {
count += buffer[i]; // for slices the buffer must only have 0 values
}
return count == 0;
}
// no need to make this public unless we support different sizes
/**
* An array holding the offset into the {@link IntBlockPool#LEVEL_SIZE_ARRAY}
* to quickly navigate to the next slice level.
*/
private static final int[] NEXT_LEVEL_ARRAY = {1, 2, 3, 4, 5, 6, 7, 8, 9, 9};
/** An array holding the level sizes for int slices. */
private static final int[] LEVEL_SIZE_ARRAY = {2, 4, 8, 16, 16, 32, 32, 64, 64, 128};
/** The first level size for new slices */
private static final int FIRST_LEVEL_SIZE = LEVEL_SIZE_ARRAY[0];
/**
* Allocates a new slice from the given offset
*/
private int allocSlice(final int[] slice, final int sliceOffset) {
final int level = slice[sliceOffset] & 15;
final int newLevel = NEXT_LEVEL_ARRAY[level];
final int newSize = LEVEL_SIZE_ARRAY[newLevel];
// Maybe allocate another block
if (intUpto > INT_BLOCK_SIZE-newSize) {
nextBuffer();
assert assertSliceBuffer(buffer);
}
final int newUpto = intUpto;
final int offset = newUpto + intOffset;
intUpto += newSize;
// Write forwarding address at end of last slice:
slice[sliceOffset] = offset;
// Write new level:
buffer[intUpto - 1] = 16 | newLevel;
return newUpto;
}
/**
* A {@link SliceWriter} that allows to write multiple integer slices into a given {@link IntBlockPool}.
*
* @see SliceReader
* @lucene.internal
*/
public static class SliceWriter {
private int offset;
private final IntBlockPool pool;
public SliceWriter(IntBlockPool pool) {
this.pool = pool;
}
/**
*
*/
public void reset(int sliceOffset) {
this.offset = sliceOffset;
}
/**
* Writes the given value into the slice and resizes the slice if needed
*/
public void writeInt(int value) {
int[] ints = pool.buffers[offset >> INT_BLOCK_SHIFT];
assert ints != null;
int relativeOffset = offset & INT_BLOCK_MASK;
if (ints[relativeOffset] != 0) {
// End of slice; allocate a new one
relativeOffset = pool.allocSlice(ints, relativeOffset);
ints = pool.buffer;
offset = relativeOffset + pool.intOffset;
}
ints[relativeOffset] = value;
offset++;
}
/**
* starts a new slice and returns the start offset. The returned value
* should be used as the start offset to initialize a {@link SliceReader}.
*/
public int startNewSlice() {
return offset = pool.newSlice(FIRST_LEVEL_SIZE) + pool.intOffset;
}
/**
* Returns the offset of the currently written slice. The returned value
* should be used as the end offset to initialize a {@link SliceReader} once
* this slice is fully written or to reset the this writer if another slice
* needs to be written.
*/
public int getCurrentOffset() {
return offset;
}
}
/**
* A {@link SliceReader} that can read int slices written by a {@link SliceWriter}
* @lucene.internal
*/
public static final class SliceReader {
private final IntBlockPool pool;
private int upto;
private int bufferUpto;
private int bufferOffset;
private int[] buffer;
private int limit;
private int level;
private int end;
/**
* Creates a new {@link SliceReader} on the given pool
*/
public SliceReader(IntBlockPool pool) {
this.pool = pool;
}
/**
* Resets the reader to a slice give the slices absolute start and end offset in the pool
*/
public void reset(int startOffset, int endOffset) {
bufferUpto = startOffset / INT_BLOCK_SIZE;
bufferOffset = bufferUpto * INT_BLOCK_SIZE;
this.end = endOffset;
level = 0;
buffer = pool.buffers[bufferUpto];
upto = startOffset & INT_BLOCK_MASK;
final int firstSize = IntBlockPool.LEVEL_SIZE_ARRAY[0];
if (startOffset+firstSize >= endOffset) {
// There is only this one slice to read
limit = endOffset & INT_BLOCK_MASK;
} else {
limit = upto+firstSize-1;
}
}
/**
* Returns <code>true</code> iff the current slice is fully read. If this
* method returns <code>true</code> {@link SliceReader#readInt()} should not
* be called again on this slice.
*/
public boolean endOfSlice() {
assert upto + bufferOffset <= end;
return upto + bufferOffset == end;
}
/**
* Reads the next int from the current slice and returns it.
* @see SliceReader#endOfSlice()
*/
public int readInt() {
assert !endOfSlice();
assert upto <= limit;
if (upto == limit)
nextSlice();
return buffer[upto++];
}
private void nextSlice() {
// Skip to our next slice
final int nextIndex = buffer[limit];
level = NEXT_LEVEL_ARRAY[level];
final int newSize = LEVEL_SIZE_ARRAY[level];
bufferUpto = nextIndex / INT_BLOCK_SIZE;
bufferOffset = bufferUpto * INT_BLOCK_SIZE;
buffer = pool.buffers[bufferUpto];
upto = nextIndex & INT_BLOCK_MASK;
if (nextIndex + newSize >= end) {
// We are advancing to the final slice
assert end - nextIndex > 0;
limit = end - bufferOffset;
} else {
// This is not the final slice (subtract 4 for the
// forwarding address at the end of this new slice)
limit = upto+newSize-1;
}
}
}
}