/*
* 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 com.google.common.base.Charsets;
import com.google.common.collect.Sets;
import org.grouplens.lenskit.data.event.MutableRating;
import org.grouplens.lenskit.data.event.Rating;
import org.grouplens.lenskit.data.event.RatingBuilder;
import org.grouplens.lenskit.data.pref.Preference;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;
/**
* Utilities for making the binary format.
*
* @since 2.1
* @author <a href="http://www.grouplens.org">GroupLens Research</a>
*/
final class BinaryFormat {
public static final byte[] HEADER_MAGIC = "LK".getBytes(Charsets.US_ASCII);
static final int INT_SIZE = 4;
static final int LONG_SIZE = 8;
static final int DOUBLE_SIZE = 8;
private final EnumSet<PackHeaderFlag> formatFlags;
private final boolean includeTimestamps;
private final boolean compactItems, compactUsers;
private final int ratingSize;
private BinaryFormat(Set<PackHeaderFlag> flags) {
formatFlags = EnumSet.copyOf(flags);
includeTimestamps = flags.contains(PackHeaderFlag.TIMESTAMPS);
compactItems = flags.contains(PackHeaderFlag.COMPACT_ITEMS);
compactUsers = flags.contains(PackHeaderFlag.COMPACT_USERS);
int rsz = DOUBLE_SIZE;
rsz += compactItems ? INT_SIZE : LONG_SIZE;
rsz += compactUsers ? INT_SIZE : LONG_SIZE;
if (hasTimestamps()) {
rsz += LONG_SIZE;
}
ratingSize = rsz;
}
/**
* Create a new binary format with some flags.
* @param flags The flags.
* @return The new binary format.
*/
public static BinaryFormat create(PackHeaderFlag... flags) {
return new BinaryFormat(Sets.newEnumSet(Arrays.asList(flags), PackHeaderFlag.class));
}
/**
* Create a new binary format with some flags.
* @param flags The flags.
* @return The new binary format.
*/
public static BinaryFormat createWithFlags(Set<PackHeaderFlag> flags) {
return new BinaryFormat(Sets.newEnumSet(flags, PackHeaderFlag.class));
}
/**
* Create a new binary format with some externally-facing flags and the default header
* settings.
* @param flags The format flags.
* @return A new format.
*/
@Deprecated
public static BinaryFormat create(Set<BinaryFormatFlag> flags) {
Set<PackHeaderFlag> hflags = PackHeaderFlag.fromFormatFlags(flags);
return new BinaryFormat(hflags);
}
public static BinaryFormat fromFlags(short flagWord) {
EnumSet<PackHeaderFlag> flags = PackHeaderFlag.unpackWord(flagWord);
return new BinaryFormat(flags);
}
public boolean hasTimestamps() {
return includeTimestamps;
}
public boolean hasCompactItems() {
return compactItems;
}
public boolean hasCompactUsers() {
return compactUsers;
}
public boolean isCompact() {
return compactUsers || compactItems;
}
public Set<PackHeaderFlag> getFlags() {
return Collections.unmodifiableSet(formatFlags);
}
public short getFlagWord() {
return PackHeaderFlag.packWord(formatFlags);
}
public int getRatingSize() {
return ratingSize;
}
public int getHeaderSize() {
return BinaryHeader.HEADER_SIZE;
}
static long readId(ByteBuffer buf, boolean compact) {
if (compact) {
return buf.getInt();
} else {
return buf.getLong();
}
}
static void writeId(ByteBuffer buf, long id, boolean compact) {
if (compact) {
assert id >= Integer.MIN_VALUE && id <= Integer.MAX_VALUE;
buf.putInt((int) id);
} else {
buf.putLong(id);
}
}
public int getUserIdSize() {
return compactUsers ? INT_SIZE : LONG_SIZE;
}
public int getItemIdSize() {
return compactUsers ? INT_SIZE : LONG_SIZE;
}
public boolean userIdIsValid(long id) {
if (compactUsers) {
return id >= Integer.MIN_VALUE && id <= Integer.MAX_VALUE;
} else {
return true;
}
}
public boolean itemIdIsValid(long id) {
if (compactItems) {
return id >= Integer.MIN_VALUE && id <= Integer.MAX_VALUE;
} else {
return true;
}
}
public long readUserId(ByteBuffer buf) {
return readId(buf, compactUsers);
}
public long readItemId(ByteBuffer buf) {
return readId(buf, compactItems);
}
public void writeUserId(ByteBuffer buf, long id) {
writeId(buf, id, compactUsers);
}
public void writeItemId(ByteBuffer buf, long id) {
writeId(buf, id, compactItems);
}
/**
* Render a rating to a byte buffer.
* @param rating The rating.
* @param buf The buffer.
*/
public void renderRating(Rating rating, ByteBuffer buf) {
writeUserId(buf, rating.getUserId());
writeItemId(buf, rating.getItemId());
Preference pref = rating.getPreference();
if (pref == null) {
buf.putDouble(Double.NaN);
} else {
buf.putDouble(pref.getValue());
}
if (hasTimestamps()) {
buf.putLong(rating.getTimestamp());
}
}
/**
* Read a rating from a buffer.
* @param buf The buffer to read.
* @return The rating.
*/
public Rating readRating(ByteBuffer buf) {
RatingBuilder rb = new RatingBuilder();
rb.setUserId(readUserId(buf));
rb.setItemId(readItemId(buf));
double rating = buf.getDouble();
if (!Double.isNaN(rating)) {
rb.setRating(rating);
}
if (hasTimestamps()) {
rb.setTimestamp(buf.getLong());
}
return rb.build();
}
/**
* Read a rating from a buffer into a mutable rating.
* @param buf The buffer to read.
* @param rating The rating to populate.
*/
public void readRating(ByteBuffer buf, MutableRating rating) {
rating.setUserId(readUserId(buf));
rating.setItemId(readItemId(buf));
rating.setRating(buf.getDouble());
if (hasTimestamps()) {
rating.setTimestamp(buf.getLong());
} else {
rating.setTimestamp(-1);
}
}
public int indexTableEntrySize() {
return BinaryIndexTable.TABLE_ENTRY_SIZE;
}
@Override
public String toString() {
return "BinFormat" + formatFlags.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BinaryFormat that = (BinaryFormat) o;
if (formatFlags != null ? !formatFlags.equals(that.formatFlags) : that.formatFlags != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
return formatFlags != null ? formatFlags.hashCode() : 0;
}
}