package org.apache.lucene.index;
import java.io.IOException;
import java.util.Objects;
import org.apache.lucene.store.IndexInput;
import org.apache.lucene.store.RAMFile;
import org.apache.lucene.store.RAMInputStream;
import org.apache.lucene.store.RAMOutputStream;
import org.apache.lucene.util.Accountable;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.BytesRefBuilder;
import org.apache.lucene.util.StringHelper;
public class PrefixCodedTerms implements Accountable {
final RAMFile buffer;
private final long size;
private long delGen;
private PrefixCodedTerms(RAMFile buffer, long size) {
this.buffer = Objects.requireNonNull(buffer);
this.size = size;
}
@Override
public long ramBytesUsed() {
return buffer.ramBytesUsed() + 2 * Long.BYTES;
}
public void setDelGen(long delGen) {
this.delGen = delGen;
}
public static class Builder {
private RAMFile buffer = new RAMFile();
private RAMOutputStream output = new RAMOutputStream(buffer, false);
private Term lastTerm = new Term("");
private BytesRefBuilder lastTermBytes = new BytesRefBuilder();
private long size;
public Builder() {}
public void add(Term term) {
add(term.field(), term.bytes());
}
public void add(String field, BytesRef bytes) {
assert lastTerm.equals(new Term("")) || new Term(field, bytes).compareTo(lastTerm) > 0;
try {
final int prefix;
if (size > 0 && field.equals(lastTerm.field)) {
prefix = StringHelper.bytesDifference(lastTerm.bytes, bytes);
output.writeVInt(prefix << 1);
} else {
prefix = 0;
output.writeVInt(1);
output.writeString(field);
}
int suffix = bytes.length - prefix;
output.writeVInt(suffix);
output.writeBytes(bytes.bytes, bytes.offset + prefix, suffix);
lastTermBytes.copyBytes(bytes);
lastTerm.bytes = lastTermBytes.get();
lastTerm.field = field;
size += 1;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public PrefixCodedTerms finish() {
try {
output.close();
return new PrefixCodedTerms(buffer, size);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public static class TermIterator extends FieldTermIterator {
final IndexInput input;
final BytesRefBuilder builder = new BytesRefBuilder();
final BytesRef bytes = builder.get();
final long end;
final long delGen;
String field = "";
private TermIterator(long delGen, RAMFile buffer) {
try {
input = new RAMInputStream("PrefixCodedTermsIterator", buffer);
} catch (IOException e) {
throw new RuntimeException(e);
}
end = input.length();
this.delGen = delGen;
}
@Override
public BytesRef next() {
if (input.getFilePointer() < end) {
try {
int code = input.readVInt();
boolean newField = (code & 1) != 0;
if (newField) {
field = input.readString();
}
int prefix = code >>> 1;
int suffix = input.readVInt();
readTermBytes(prefix, suffix);
return bytes;
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
field = null;
return null;
}
}
private void readTermBytes(int prefix, int suffix) throws IOException {
builder.grow(prefix + suffix);
input.readBytes(builder.bytes(), prefix, suffix);
builder.setLength(prefix + suffix);
}
@Override
public String field() {
return field;
}
@Override
public long delGen() {
return delGen;
}
}
public TermIterator iterator() {
return new TermIterator(delGen, buffer);
}
public long size() {
return size;
}
@Override
public int hashCode() {
int h = buffer.hashCode();
h = 31 * h + (int) (delGen ^ (delGen >>> 32));
return h;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
PrefixCodedTerms other = (PrefixCodedTerms) obj;
return buffer.equals(other.buffer) && delGen == other.delGen;
}
}