/*
* LensKit, an open source recommender systems toolkit.
* Copyright 2010-2014 LensKit Contributors. See CONTRIBUTORS.md.
* Work on LensKit has been funded by the National Science Foundation under
* grants IIS 05-34939, 08-08692, 08-12148, and 10-17697.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.grouplens.lenskit.data.dao.packed;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.longs.LongSet;
import org.apache.commons.lang3.tuple.Pair;
import org.grouplens.lenskit.collections.LongKeyDomain;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.ThreadSafe;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.AbstractCollection;
import java.util.Collection;
import java.util.Iterator;
/**
* An index table from a byte buffer.
*
* @author <a href="http://www.grouplens.org">GroupLens Research</a>
*/
@ThreadSafe
class BinaryIndexTable implements Serializable {
static final int TABLE_ENTRY_SIZE = BinaryFormat.LONG_SIZE + 2 * BinaryFormat.INT_SIZE;
private static final long serialVersionUID = -1L;
private static final Logger logger = LoggerFactory.getLogger(BinaryIndexTable.class);
private final LongKeyDomain keys;
private final int[] offsets;
private final int[] sizes;
private final IntBuffer buffer;
private BinaryIndexTable(LongKeyDomain keytbl, int[] offtbl, int[] sztbl, IntBuffer buf) {
assert offtbl.length == keytbl.domainSize();
assert sztbl.length == keytbl.domainSize();
keys = keytbl;
offsets = offtbl;
sizes = sztbl;
buffer = buf;
}
/**
* Create a binary index table.
* @param nentries The number of entries in the table.
* @param buffer The table buffer. Its position will be advanced to the end of the table.
* @return The index table.
*/
public static BinaryIndexTable fromBuffer(int nentries, ByteBuffer buffer) {
logger.debug("reading table of {} entries", nentries);
long[] keys = new long[nentries];
int[] offsets = new int[nentries];
int[] sizes = new int[nentries];
int nextExpectedOffset = 0;
for (int i = 0; i < nentries; i++) {
keys[i] = buffer.getLong();
if (i > 0 && keys[i-1] >= keys[i]) {
logger.error("key {} is not greater than previous key {}", keys[i], keys[i-1]);
throw new IllegalArgumentException("corrupted index table");
}
offsets[i] = buffer.getInt();
sizes[i] = buffer.getInt();
if (offsets[i] != nextExpectedOffset) {
logger.error("expected offset {}, got {}", nextExpectedOffset, offsets[i]);
throw new IllegalArgumentException("corrupted index table");
}
nextExpectedOffset += sizes[i];
}
if (buffer.remaining() < nextExpectedOffset) {
throw new IllegalArgumentException("buffer not large enough");
}
int end = buffer.position() + nextExpectedOffset * 4;
ByteBuffer dup = buffer.duplicate();
dup.limit(end);
buffer.position(end);
LongKeyDomain dom = LongKeyDomain.wrap(keys, keys.length, true);
return new BinaryIndexTable(dom, offsets, sizes, dup.asIntBuffer());
}
public LongSet getKeys() {
return keys.activeSetView();
}
/**
* Get the position list for a key.
* @param key The key.
* @return The position list.
*/
public IntList getEntry(long key) {
int idx = keys.getIndex(key);
if (idx < 0) {
return null;
}
return getEntryInternal(idx);
}
private IntList getEntryInternal(int idx) {
int offset = offsets[idx];
int size = sizes[idx];
IntBuffer buf = buffer.duplicate();
buf.position(offset).limit(offset + size);
return BufferBackedIntList.create(buf);
}
public Collection<Pair<Long,IntList>> entries() {
return new EntryCollection();
}
private Object writeReplace() throws ObjectStreamException {
return new SerialProxy(keys, offsets, sizes, buffer);
}
private Object readObject(ObjectInputStream in) throws IOException {
throw new InvalidObjectException("index table must use serial proxy");
}
@SuppressWarnings("deprecation")
private class EntryCollection extends AbstractCollection<Pair<Long, IntList>> {
@Override
public int size() {
return keys.domainSize();
}
@Override
@Nonnull
public Iterator<Pair<Long, IntList>> iterator() {
return new IterImpl();
}
}
private class IterImpl implements Iterator<Pair<Long,IntList>> {
int pos = 0;
@Override
public boolean hasNext() {
return pos < keys.domainSize();
}
@Override
public Pair<Long, IntList> next() {
int i = pos;
pos += 1;
return Pair.of(keys.getKey(i), getEntryInternal(i));
}
@Override
public void remove() {
throw new UnsupportedOperationException("remove");
}
}
private static class SerialProxy implements Serializable {
private static final long serialVersionUID = 1L;
private final long[] keys;
private final int[] offsets;
private final int[] sizes;
private transient IntBuffer buffer;
private SerialProxy(LongKeyDomain keys, int [] offsets, int[] sizes, IntBuffer buffer) {
this.keys = keys.activeSetView().toLongArray();
this.offsets = offsets;
this.sizes = sizes;
this.buffer = buffer.duplicate();
this.buffer.clear();
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeInt(buffer.limit());
while (buffer.hasRemaining()) {
out.writeInt(buffer.get());
}
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
int size = in.readInt();
ByteBuffer store = ByteBuffer.allocateDirect(size * 4);
buffer = store.asIntBuffer();
assert buffer.remaining() == size;
while (buffer.hasRemaining()) {
buffer.put(in.readInt());
}
buffer.clear();
}
private Object readResolve() throws ObjectStreamException {
if (keys.length != offsets.length || keys.length != sizes.length) {
throw new InvalidObjectException("arrays not the same length");
}
return new BinaryIndexTable(LongKeyDomain.wrap(keys, keys.length, true),
offsets, sizes, buffer.duplicate());
}
}
}