/* * 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.Comparator; /** * Just like {@link BytesRefArray} except all values have the same length. * * <b>Note: This class is not Thread-Safe!</b> * * @lucene.internal * @lucene.experimental */ final class FixedLengthBytesRefArray implements SortableBytesRefArray { private final int valueLength; private final int valuesPerBlock; /** How many values have been appended */ private int size; /** How many blocks are used */ private int currentBlock = -1; private int nextEntry; private byte[][] blocks; /** * Creates a new {@link BytesRefArray} with a counter to track allocated bytes */ public FixedLengthBytesRefArray(int valueLength) { this.valueLength = valueLength; // ~32K per page, unless each value is > 32K: valuesPerBlock = Math.max(1, 32768/valueLength); nextEntry = valuesPerBlock; blocks = new byte[0][]; } /** * Clears this {@link BytesRefArray} */ @Override public void clear() { size = 0; blocks = new byte[0][]; currentBlock = -1; nextEntry = valuesPerBlock; } /** * Appends a copy of the given {@link BytesRef} to this {@link BytesRefArray}. * @param bytes the bytes to append * @return the index of the appended bytes */ @Override public int append(BytesRef bytes) { if (bytes.length != valueLength) { throw new IllegalArgumentException("value length is " + bytes.length + " but is supposed to always be " + valueLength); } if (nextEntry == valuesPerBlock) { currentBlock++; if (currentBlock == blocks.length) { int size = ArrayUtil.oversize(currentBlock+1, RamUsageEstimator.NUM_BYTES_OBJECT_REF); byte[][] next = new byte[size][]; System.arraycopy(blocks, 0, next, 0, blocks.length); blocks = next; } blocks[currentBlock] = new byte[valuesPerBlock * valueLength]; nextEntry = 0; } System.arraycopy(bytes.bytes, bytes.offset, blocks[currentBlock], nextEntry*valueLength, valueLength); nextEntry++; return size++; } /** * Returns the current size of this {@link FixedLengthBytesRefArray} * @return the current size of this {@link FixedLengthBytesRefArray} */ @Override public int size() { return size; } private int[] sort(final Comparator<BytesRef> comp) { final int[] orderedEntries = new int[size()]; for (int i = 0; i < orderedEntries.length; i++) { orderedEntries[i] = i; } if (comp instanceof BytesRefComparator) { BytesRefComparator bComp = (BytesRefComparator) comp; new MSBRadixSorter(bComp.comparedBytesCount) { BytesRef scratch; { scratch = new BytesRef(); scratch.length = valueLength; } @Override protected void swap(int i, int j) { int o = orderedEntries[i]; orderedEntries[i] = orderedEntries[j]; orderedEntries[j] = o; } @Override protected int byteAt(int i, int k) { int index1 = orderedEntries[i]; scratch.bytes = blocks[index1 / valuesPerBlock]; scratch.offset = (index1 % valuesPerBlock) * valueLength; return bComp.byteAt(scratch, k); } }.sort(0, size()); return orderedEntries; } final BytesRef pivot = new BytesRef(); final BytesRef scratch1 = new BytesRef(); final BytesRef scratch2 = new BytesRef(); pivot.length = valueLength; scratch1.length = valueLength; scratch2.length = valueLength; new IntroSorter() { @Override protected void swap(int i, int j) { int o = orderedEntries[i]; orderedEntries[i] = orderedEntries[j]; orderedEntries[j] = o; } @Override protected int compare(int i, int j) { int index1 = orderedEntries[i]; scratch1.bytes = blocks[index1 / valuesPerBlock]; scratch1.offset = (index1 % valuesPerBlock) * valueLength; int index2 = orderedEntries[j]; scratch2.bytes = blocks[index2 / valuesPerBlock]; scratch2.offset = (index2 % valuesPerBlock) * valueLength; return comp.compare(scratch1, scratch2); } @Override protected void setPivot(int i) { int index = orderedEntries[i]; pivot.bytes = blocks[index / valuesPerBlock]; pivot.offset = (index % valuesPerBlock) * valueLength; } @Override protected int comparePivot(int j) { final int index = orderedEntries[j]; scratch2.bytes = blocks[index / valuesPerBlock]; scratch2.offset = (index % valuesPerBlock) * valueLength; return comp.compare(pivot, scratch2); } }.sort(0, size()); return orderedEntries; } /** * <p> * Returns a {@link BytesRefIterator} with point in time semantics. The * iterator provides access to all so far appended {@link BytesRef} instances. * </p> * <p> * The iterator will iterate the byte values in the order specified by the comparator. * </p> * <p> * This is a non-destructive operation. * </p> */ @Override public BytesRefIterator iterator(final Comparator<BytesRef> comp) { final BytesRef result = new BytesRef(); result.length = valueLength; final int size = size(); final int[] indices = sort(comp); return new BytesRefIterator() { int pos = 0; @Override public BytesRef next() { if (pos < size) { int index = indices[pos]; pos++; result.bytes = blocks[index / valuesPerBlock]; result.offset = (index % valuesPerBlock) * valueLength; return result; } return null; } }; } }