/* * 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.beam.sdk.io.range; import static com.google.common.base.Preconditions.checkNotNull; import com.google.protobuf.ByteString; import com.google.protobuf.ByteString.ByteIterator; import java.io.Serializable; import java.nio.ByteBuffer; import javax.annotation.Nonnull; /** * A class representing a key consisting of an array of bytes. Arbitrary-length * {@code byte[]} keys are typical in key-value stores such as Google Cloud Bigtable. * * <p>Instances of {@link ByteKey} are immutable. * * <p>{@link ByteKey} implements {@link Comparable Comparable<ByteKey>} by comparing the * arrays in lexicographic order. The smallest {@link ByteKey} is a zero-length array; the successor * to a key is the same key with an additional 0 byte appended; and keys have unbounded size. * * <p>Note that the empty {@link ByteKey} compares smaller than all other keys, but some systems * have the semantic that when an empty {@link ByteKey} is used as an upper bound, it represents * the largest possible key. In these cases, implementors should use {@link #isEmpty} to test * whether an upper bound key is empty. */ public final class ByteKey implements Comparable<ByteKey>, Serializable { /** An empty key. */ public static final ByteKey EMPTY = ByteKey.of(); /** * Creates a new {@link ByteKey} backed by a copy of the data remaining in the specified * {@link ByteBuffer}. */ public static ByteKey copyFrom(ByteBuffer value) { return new ByteKey(ByteString.copyFrom(value)); } /** * Creates a new {@link ByteKey} backed by a copy of the specified {@code byte[]}. * * <p>Makes a copy of the underlying array. */ public static ByteKey copyFrom(byte[] bytes) { return new ByteKey(ByteString.copyFrom(bytes)); } /** * Creates a new {@link ByteKey} backed by a copy of the specified {@code int[]}. This method is * primarily used as a convenience to create a {@link ByteKey} in code without casting down to * signed Java {@link Byte bytes}: * * <pre>{@code * ByteKey key = ByteKey.of(0xde, 0xad, 0xbe, 0xef); * }</pre> * * <p>Makes a copy of the input. */ public static ByteKey of(int... bytes) { byte[] ret = new byte[bytes.length]; for (int i = 0; i < bytes.length; ++i) { ret[i] = (byte) (bytes[i] & 0xff); } return ByteKey.copyFrom(ret); } /** * Returns a read-only {@link ByteBuffer} representing this {@link ByteKey}. */ public ByteBuffer getValue() { return value.asReadOnlyByteBuffer(); } /** * Returns a newly-allocated {@code byte[]} representing this {@link ByteKey}. * * <p>Copies the underlying {@code byte[]}. */ public byte[] getBytes() { return value.toByteArray(); } /** * Returns {@code true} if the {@code byte[]} backing this {@link ByteKey} is of length 0. */ public boolean isEmpty() { return value.isEmpty(); } /** * {@link ByteKey} implements {@link Comparable Comparable<ByteKey>} by comparing the * arrays in lexicographic order. The smallest {@link ByteKey} is a zero-length array; the * successor to a key is the same key with an additional 0 byte appended; and keys have unbounded * size. */ @Override public int compareTo(@Nonnull ByteKey other) { checkNotNull(other, "other"); ByteIterator thisIt = value.iterator(); ByteIterator otherIt = other.value.iterator(); while (thisIt.hasNext() && otherIt.hasNext()) { // (byte & 0xff) converts [-128,127] bytes to [0,255] ints. int cmp = (thisIt.nextByte() & 0xff) - (otherIt.nextByte() & 0xff); if (cmp != 0) { return cmp; } } // If we get here, the prefix of both arrays is equal up to the shorter array. The array with // more bytes is larger. return value.size() - other.value.size(); } //////////////////////////////////////////////////////////////////////////////////// private final ByteString value; private ByteKey(ByteString value) { this.value = value; } /** Array used as a helper in {@link #toString}. */ private static final char[] HEX = new char[] {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; // Prints the key as a string "[deadbeef]". @Override public String toString() { char[] encoded = new char[2 * value.size() + 2]; encoded[0] = '['; int cnt = 1; ByteIterator iterator = value.iterator(); while (iterator.hasNext()) { byte b = iterator.nextByte(); encoded[cnt] = HEX[(b & 0xF0) >>> 4]; ++cnt; encoded[cnt] = HEX[b & 0xF]; ++cnt; } encoded[cnt] = ']'; return new String(encoded); } @Override public boolean equals(Object o) { if (o == this) { return true; } if (!(o instanceof ByteKey)) { return false; } ByteKey other = (ByteKey) o; return (other.value.size() == value.size()) && this.compareTo(other) == 0; } @Override public int hashCode() { return value.hashCode(); } }