/* * Licensed 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 com.addthis.hydra.store.db; import javax.annotation.Nonnull; import java.util.Arrays; import com.addthis.basis.util.LessBytes; import com.addthis.basis.util.Varint; import com.addthis.hydra.store.util.Raw; import static com.google.common.base.Preconditions.checkArgument; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; public final class DBKey implements IPageDB.Key, Comparable<DBKey> { private static final Raw EMPTY = Raw.get(new byte[0]); private final long id; private final Raw key; public DBKey(long id) { this(id, (Raw) null); } public DBKey(long id, String key) { this(id, Raw.get(key)); } public DBKey(long id, Raw key) { checkArgument(id >= 0, "Argument was %s but expected non-negative integer", id); if (key == null) { key = EMPTY; } this.id = id; this.key = key; } /** * Create a DBKey from its serialization byte array representation. * If the most significant bit of the first byte is set then * interpret the id as a 63-bit unsigned long. Otherwise interpret the * id as a 31-bit unsigned int. * * @param raw * @return */ public static DBKey fromBytes(byte[] raw) { byte head = raw[0]; int numBytes; long id; if ((head >> 7) == 0) { id = (long) LessBytes.toInt(raw); numBytes = 4; } else { id = LessBytes.toLong(raw) & ~(Long.MIN_VALUE); numBytes = 8; } Raw key = Raw.get(LessBytes.cut(raw, numBytes, raw.length - numBytes)); return new DBKey(id, key); } public static DBKey deltaDecode(byte[] encoding, @Nonnull IPageDB.Key baseKey) { ByteBuf buffer = Unpooled.copiedBuffer(encoding); long offset = Varint.readSignedVarLong(buffer); long id = offset + baseKey.id(); Raw key; if (buffer.readableBytes() == 0) { key = null; } else { byte[] data = new byte[buffer.readableBytes()]; buffer.readBytes(data); key = Raw.get(data); } return new DBKey(id, key); } @Override public long id() { return id; } @Override public byte[] key() { return key.toBytes(); } @Override public Raw rawKey() { return key; } public String toString() { return id + ":" + key; } @Override public int hashCode() { return key.hashCode() + Long.hashCode(id); } @Override public boolean equals(Object o) { if (o == this) { return true; } if (o instanceof DBKey) { DBKey k = (DBKey) o; return (k.id == id && k.key.equals(key)); } else { return false; } } @Override public int compareTo(DBKey dk) { long dkId = dk.id(); if (dkId == id) { return key.compareTo(dk.key); } return id > dkId ? 1 : -1; } /** * Generate the serialized byte array representation. * If the id can be represented as a 31-bit unsigned integer * then use a 4-byte representation. If is cannot be represented * in 4 bytes then use an 8-byte representation and set the * most significant bit of the first byte as a flag. * * @return serialized representation */ @Override public byte[] toBytes() { byte[] idBytes; if (id <= Integer.MAX_VALUE) { idBytes = LessBytes.toBytes((int) id); } else { idBytes = LessBytes.toBytes(id | Long.MIN_VALUE); } if (key == null) { return idBytes; } return Raw.get(idBytes).cat(key).toBytes(); } @Override public byte[] deltaEncode(@Nonnull IPageDB.Key baseKey) { long offset = id - baseKey.id(); ByteBuf buffer = Unpooled.buffer(); Varint.writeSignedVarLong(offset, buffer); if (key != null) { buffer.writeBytes(key.toBytes()); } return Arrays.copyOf(buffer.array(), buffer.readableBytes()); } }