diff --git a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/OrderedCode.java b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/OrderedCode.java deleted file mode 100644 index 89217c72ad1..00000000000 --- a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/OrderedCode.java +++ /dev/null @@ -1,646 +0,0 @@ -/* - * 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.runners.dataflow.worker; - -import java.math.RoundingMode; -import java.util.ArrayList; -import java.util.Arrays; -import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.math.LongMath; -import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.primitives.Longs; - -/** - * This module provides routines for encoding a sequence of typed entities into a byte array. The - * resulting byte arrays can be lexicographically compared to yield the same comparison value that - * would have been generated if the encoded items had been compared one by one according to their - * type. - * - *

More precisely, suppose: - * - *

    - *
  1. byte array A is generated by encoding the sequence of items [A_1..A_n] - *
  2. byte array B is generated by encoding the sequence of items [B_1..B_n] - *
  3. The types match; i.e., for all i: A_i was encoded using the same routine as B_i - *
- * - * Then: Comparing A vs. B lexicographically is the same as comparing the vectors [A_1..A_n] and - * [B_1..B_n] lexicographically. - * - *

This class is NOT thread safe. - */ -public class OrderedCode { - // We want to encode a few extra symbols in strings: - // Separator between items - // Infinite string - // - // Therefore we need an alphabet with at least 258 characters. We - // achieve this by using two-letter sequences starting with '\0' and '\xff' - // as extra symbols: - // encoded as => \0\1 - // \0 encoded as => \0\xff - // \xff encoded as => \xff\x00 - // encoded as => \xff\xff - // - // The remaining two letter sequences starting with '\0' and '\xff' - // are currently unused. - - public static final byte ESCAPE1 = 0x00; - public static final byte NULL_CHARACTER = (byte) 0xff; // Combined with ESCAPE1 - public static final byte SEPARATOR = 0x01; // Combined with ESCAPE1 - - public static final byte ESCAPE2 = (byte) 0xff; - public static final byte INFINITY = (byte) 0xff; // Combined with ESCAPE2 - public static final byte FF_CHARACTER = 0x00; // Combined with ESCAPE2 - - public static final byte[] ESCAPE1_SEPARATOR = {ESCAPE1, SEPARATOR}; - - static final byte[] INFINITY_ENCODED = {ESCAPE2, INFINITY}; - - /** - * This array maps encoding length to header bits in the first two bytes for SignedNumIncreasing - * encoding. - */ - private static final byte[][] LENGTH_TO_HEADER_BITS = { - {0, 0}, - {(byte) 0x80, 0}, - {(byte) 0xc0, 0}, - {(byte) 0xe0, 0}, - {(byte) 0xf0, 0}, - {(byte) 0xf8, 0}, - {(byte) 0xfc, 0}, - {(byte) 0xfe, 0}, - {(byte) 0xff, 0}, - {(byte) 0xff, (byte) 0x80}, - {(byte) 0xff, (byte) 0xc0} - }; - - /** - * This array maps encoding lengths to the header bits that overlap with the payload and need - * fixing during readSignedNumIncreasing. - */ - private static final long[] LENGTH_TO_MASK = { - 0L, - 0x80L, - 0xc000L, - 0xe00000L, - 0xf0000000L, - 0xf800000000L, - 0xfc0000000000L, - 0xfe000000000000L, - 0xff00000000000000L, - 0x8000000000000000L, - 0L - }; - - /** - * This array maps the number of bits in a number to the encoding length produced by - * WriteSignedNumIncreasing. For positive numbers, the number of bits is 1 plus the most - * significant bit position (the highest bit position in a positive long is 63). For a negative - * number n, we count the bits in ~n. That is, length = BITS_TO_LENGTH[log2Floor(n < 0 ? ~n : n) + - * 1]. - */ - private static final short[] BITS_TO_LENGTH = { - 1, 1, 1, 1, 1, 1, 1, - 2, 2, 2, 2, 2, 2, 2, - 3, 3, 3, 3, 3, 3, 3, - 4, 4, 4, 4, 4, 4, 4, - 5, 5, 5, 5, 5, 5, 5, - 6, 6, 6, 6, 6, 6, 6, - 7, 7, 7, 7, 7, 7, 7, - 8, 8, 8, 8, 8, 8, 8, - 9, 9, 9, 9, 9, 9, 9, - 10 - }; - - /** - * stores the current encoded value as a list of byte arrays. Note that this is manipulated as we - * read/write items. Note that every item will fit on at most one array. One array may have more - * than one item (eg when used for decoding). While encoding, one array will have exactly one - * item. While returning the encoded array we will merge all the arrays in this list. - */ - private final ArrayList encodedArrays = new ArrayList<>(); - - /** - * This is the current position on the first array. Will be non-zero only if the ordered code was - * created using encoded byte array. - */ - private int firstArrayPosition = 0; - - /** Creates OrderedCode from scractch. Typically used at encoding time. */ - public OrderedCode() {} - - /** - * Creates OrderedCode from a given encoded byte array. Typically used at decoding time. - * - *

For better performance, it uses the input array provided (not a copy). Therefore the - * input array should not be modified. - */ - public OrderedCode(byte[] encodedByteArray) { - encodedArrays.add(encodedByteArray); - } - - /** - * Adds the given byte array item to the OrderedCode. It encodes the input byte array, followed by - * a separator and appends the result to its internal encoded byte array store. - * - *

It works with the input array, so the input array 'value' should not be modified till the - * method returns. - * - * @param value bytes to be written. - * @see #readBytes() - */ - public void writeBytes(byte[] value) { - // Determine the length of the encoded array - int encodedLength = 2; // for separator - for (byte b : value) { - if ((b == ESCAPE1) || (b == ESCAPE2)) { - encodedLength += 2; - } else { - encodedLength++; - } - } - - byte[] encodedArray = new byte[encodedLength]; - int copyStart = 0; - int outIndex = 0; - for (int i = 0; i < value.length; i++) { - byte b = value[i]; - if (b == ESCAPE1) { - System.arraycopy(value, copyStart, encodedArray, outIndex, i - copyStart); - outIndex += i - copyStart; - encodedArray[outIndex++] = ESCAPE1; - encodedArray[outIndex++] = NULL_CHARACTER; - copyStart = i + 1; - } else if (b == ESCAPE2) { - System.arraycopy(value, copyStart, encodedArray, outIndex, i - copyStart); - outIndex += i - copyStart; - encodedArray[outIndex++] = ESCAPE2; - encodedArray[outIndex++] = FF_CHARACTER; - copyStart = i + 1; - } - } - if (copyStart < value.length) { - System.arraycopy(value, copyStart, encodedArray, outIndex, value.length - copyStart); - outIndex += value.length - copyStart; - } - encodedArray[outIndex++] = ESCAPE1; - encodedArray[outIndex] = SEPARATOR; - - encodedArrays.add(encodedArray); - } - - /** - * Encodes the long item, in big-endian format, and appends the result to its internal encoded - * byte array store. - * - *

Note that the specified long is treated like a uint64, e.g. {@code new - * OrderedCode().writeNumIncreasing(-1L).getEncodedBytes()} is greater than {@code new - * OrderedCode().writeNumIncreasing(Long.MAX_VALUE).getEncodedBytes()}. - * - * @see #readNumIncreasing() - */ - public void writeNumIncreasing(long value) { - // Values are encoded with a single byte length prefix, followed - // by the actual value in big-endian format with leading 0 bytes - // dropped. - byte[] bufer = new byte[9]; // 8 bytes for value plus one byte for length - int len = 0; - while (value != 0) { - len++; - bufer[9 - len] = (byte) (value & 0xff); - value >>>= 8; - } - bufer[9 - len - 1] = (byte) len; - len++; - byte[] encodedArray = new byte[len]; - System.arraycopy(bufer, 9 - len, encodedArray, 0, len); - encodedArrays.add(encodedArray); - } - - /** Return floor(log2(n)) for positive integer n. Returns -1 iff n == 0. */ - int log2Floor(long n) { - if (n < 0) { - throw new IllegalArgumentException("must be non-negative"); - } - return n == 0 ? -1 : LongMath.log2(n, RoundingMode.FLOOR); - } - - /** Calculates the encoding length in bytes of the signed number n. */ - int getSignedEncodingLength(long n) { - return BITS_TO_LENGTH[log2Floor(n < 0 ? ~n : n) + 1]; - } - - /** - * Encodes the long item, in big-endian format, and appends the result to its internal encoded - * byte array store. - * - *

Note that the specified long is treated like an int64, i.e. {@code new - * OrderedCode().writeNumIncreasing(-1L).getEncodedBytes()} is less than {@code new - * OrderedCode().writeNumIncreasing(0L).getEncodedBytes()}. - * - * @see #readSignedNumIncreasing() - */ - public void writeSignedNumIncreasing(long val) { - long x = val < 0 ? ~val : val; - if (x < 64) { // Fast path for encoding length == 1. - byte[] encodedArray = new byte[] {(byte) (LENGTH_TO_HEADER_BITS[1][0] ^ val)}; - encodedArrays.add(encodedArray); - return; - } - // buf = val in network byte order, sign extended to 10 bytes. - byte signByte = val < 0 ? (byte) 0xff : 0; - byte[] buf = new byte[2 + Longs.BYTES]; - buf[0] = buf[1] = signByte; - System.arraycopy(Longs.toByteArray(val), 0, buf, 2, Longs.BYTES); - int len = getSignedEncodingLength(x); - if (len < 2) { - throw new IllegalStateException( - "Invalid length (" + len + ")" + " returned by getSignedEncodingLength(" + x + ")"); - } - int beginIndex = buf.length - len; - buf[beginIndex] ^= LENGTH_TO_HEADER_BITS[len][0]; - buf[beginIndex + 1] ^= LENGTH_TO_HEADER_BITS[len][1]; - - byte[] encodedArray = new byte[len]; - System.arraycopy(buf, beginIndex, encodedArray, 0, len); - encodedArrays.add(encodedArray); - } - - /** - * Encodes and appends INFINITY item to its internal encoded byte array store. - * - * @see #readInfinity() - */ - public void writeInfinity() { - writeTrailingBytes(INFINITY_ENCODED); - } - - /** - * Appends the byte array item to its internal encoded byte array store. This is used for the last - * item and is not encoded. It also can be used to write a fixed number of bytes that will be read - * back using {@link #readBytes(int)}. - * - *

It stores the input array in the store, so the input array 'value' should not be modified. - * - * @param value bytes to be written. - * @see #readTrailingBytes() - * @see #readBytes(int) - */ - public void writeTrailingBytes(byte[] value) { - if ((value == null) || (value.length == 0)) { - throw new IllegalArgumentException("Value cannot be null or have 0 elements"); - } - - encodedArrays.add(value); - } - - /** - * Returns the next byte array item from its encoded byte array store and removes the item from - * the store. - * - * @see #writeBytes(byte[]) - */ - public byte[] readBytes() { - if ((encodedArrays == null) - || encodedArrays.isEmpty() - || (encodedArrays.get(0).length - firstArrayPosition <= 0)) { - throw new IllegalArgumentException("Invalid encoded byte array"); - } - - // Determine the length of the decoded array - // We only scan up to "length-2" since a valid string must end with - // a two character terminator: 'ESCAPE1 SEPARATOR' - byte[] store = encodedArrays.get(0); - int decodedLength = 0; - boolean valid = false; - int i = firstArrayPosition; - while (i < store.length - 1) { - byte b = store[i++]; - if (b == ESCAPE1) { - b = store[i++]; - if (b == SEPARATOR) { - valid = true; - break; - } else if (b == NULL_CHARACTER) { - decodedLength++; - } else { - throw new IllegalArgumentException("Invalid encoded byte array"); - } - } else if (b == ESCAPE2) { - b = store[i++]; - if (b == FF_CHARACTER) { - decodedLength++; - } else { - throw new IllegalArgumentException("Invalid encoded byte array"); - } - } else { - decodedLength++; - } - } - if (!valid) { - throw new IllegalArgumentException("Invalid encoded byte array"); - } - - byte[] decodedArray = new byte[decodedLength]; - int copyStart = firstArrayPosition; - int outIndex = 0; - int j = firstArrayPosition; - while (j < store.length - 1) { - byte b = store[j++]; // note that j has been incremented - if (b == ESCAPE1) { - System.arraycopy(store, copyStart, decodedArray, outIndex, j - copyStart - 1); - outIndex += j - copyStart - 1; - // ESCAPE1 SEPARATOR ends component - // ESCAPE1 NULL_CHARACTER represents '\0' - b = store[j++]; - if (b == SEPARATOR) { - if ((store.length - j) == 0) { - // we are done with the first array - encodedArrays.remove(0); - firstArrayPosition = 0; - } else { - firstArrayPosition = j; - } - return decodedArray; - } else if (b == NULL_CHARACTER) { - decodedArray[outIndex++] = 0x00; - } // else not required - handled during length determination - copyStart = j; - } else if (b == ESCAPE2) { - System.arraycopy(store, copyStart, decodedArray, outIndex, j - copyStart - 1); - outIndex += j - copyStart - 1; - // ESCAPE2 FF_CHARACTER represents '\xff' - // ESCAPE2 INFINITY is an error - b = store[j++]; - if (b == FF_CHARACTER) { - decodedArray[outIndex++] = (byte) 0xff; - } // else not required - handled during length determination - copyStart = j; - } - } - // not required due to the first phase, but need to entertain the compiler - throw new IllegalArgumentException("Invalid encoded byte array"); - } - - /** - * Returns the next long item (encoded in big-endian format via {@code writeNumIncreasing(long)}) - * from its internal encoded byte array store and removes the item from the store. - * - * @see #writeNumIncreasing(long) - */ - public long readNumIncreasing() { - if ((encodedArrays == null) - || encodedArrays.isEmpty() - || (encodedArrays.get(0).length - firstArrayPosition < 1)) { - throw new IllegalArgumentException("Invalid encoded byte array"); - } - - byte[] store = encodedArrays.get(0); - // Decode length byte - int len = store[firstArrayPosition]; - if ((firstArrayPosition + len + 1 > store.length) || len > 8) { - throw new IllegalArgumentException("Invalid encoded byte array"); - } - - long result = 0; - for (int i = 0; i < len; i++) { - result <<= 8; - result |= (store[firstArrayPosition + i + 1] & 0xff); - } - - if ((store.length - firstArrayPosition - len - 1) == 0) { - // we are done with the first array - encodedArrays.remove(0); - firstArrayPosition = 0; - } else { - firstArrayPosition = firstArrayPosition + len + 1; - } - - return result; - } - - /** - * Returns the next long item (encoded via {@code writeSignedNumIncreasing(long)}) from its - * internal encoded byte array store and removes the item from the store. - * - * @see #writeSignedNumIncreasing(long) - */ - public long readSignedNumIncreasing() { - if ((encodedArrays == null) - || encodedArrays.isEmpty() - || (encodedArrays.get(0).length - firstArrayPosition < 1)) { - throw new IllegalArgumentException("Invalid encoded byte array"); - } - - byte[] store = encodedArrays.get(0); - - long xorMask = ((store[firstArrayPosition] & 0x80) == 0) ? ~0L : 0L; - // Store first byte as an int rather than a (signed) byte -- to avoid - // accidental byte-to-int promotion later, which would extend the byte's - // sign bit (if any). - int firstByte = (store[firstArrayPosition] & 0xff) ^ (int) (xorMask & 0xff); - - // Now calculate and test length, and set x to raw (unmasked) result. - int len; - long x; - if (firstByte != 0xff) { - len = 7 - log2Floor(firstByte ^ 0xff); - if (store.length - firstArrayPosition < len) { - throw new IllegalArgumentException("Invalid encoded byte array"); - } - x = xorMask; // Sign extend using xorMask. - for (int i = firstArrayPosition; i < firstArrayPosition + len; i++) { - x = (x << 8) | (store[i] & 0xff); - } - } else { - len = 8; - if (store.length - firstArrayPosition < len) { - throw new IllegalArgumentException("Invalid encoded byte array"); - } - int secondByte = (store[firstArrayPosition + 1] & 0xff) ^ (int) (xorMask & 0xff); - if (secondByte >= 0x80) { - if (secondByte < 0xc0) { - len = 9; - } else { - int thirdByte = (store[firstArrayPosition + 2] & 0xff) ^ (int) (xorMask & 0xff); - if (secondByte == 0xc0 && thirdByte < 0x80) { - len = 10; - } else { - // Either len > 10 or len == 10 and #bits > 63. - throw new IllegalArgumentException("Invalid encoded byte array"); - } - } - if (store.length - firstArrayPosition < len) { - throw new IllegalArgumentException("Invalid encoded byte array"); - } - } - x = - Longs.fromByteArray( - Arrays.copyOfRange(store, firstArrayPosition + len - 8, firstArrayPosition + len)); - } - - x ^= LENGTH_TO_MASK[len]; // Remove spurious header bits. - - if (len != getSignedEncodingLength(x)) { - throw new IllegalArgumentException("Invalid encoded byte array"); - } - - if ((store.length - firstArrayPosition - len) == 0) { - // We are done with the first array. - encodedArrays.remove(0); - firstArrayPosition = 0; - } else { - firstArrayPosition = firstArrayPosition + len; - } - - return x; - } - - /** - * Removes INFINITY item from its internal encoded byte array store if present. Returns whether - * INFINITY was present. - * - * @see #writeInfinity() - */ - public boolean readInfinity() { - if ((encodedArrays == null) - || encodedArrays.isEmpty() - || (encodedArrays.get(0).length - firstArrayPosition < 1)) { - throw new IllegalArgumentException("Invalid encoded byte array"); - } - byte[] store = encodedArrays.get(0); - if (store.length - firstArrayPosition < 2) { - return false; - } - if ((store[firstArrayPosition] == ESCAPE2) && (store[firstArrayPosition + 1] == INFINITY)) { - if ((store.length - firstArrayPosition - 2) == 0) { - // we are done with the first array - encodedArrays.remove(0); - firstArrayPosition = 0; - } else { - firstArrayPosition = firstArrayPosition + 2; - } - return true; - } else { - return false; - } - } - - /** - * Returns the trailing byte array item from its internal encoded byte array store and removes the - * item from the store. - * - * @see #writeTrailingBytes(byte[]) - */ - public byte[] readTrailingBytes() { - // one item is contained within one byte array - if ((encodedArrays == null) || (encodedArrays.size() != 1)) { - throw new IllegalArgumentException("Invalid encoded byte array"); - } - - byte[] store = encodedArrays.get(0); - encodedArrays.remove(0); - assert encodedArrays.isEmpty(); - return Arrays.copyOfRange(store, firstArrayPosition, store.length); - } - - /** - * Reads (unencoded) {@code len} bytes. - * - * @see #writeTrailingBytes(byte[]) - */ - public byte[] readBytes(int len) { - if ((encodedArrays == null) - || encodedArrays.isEmpty() - || (encodedArrays.get(0).length - firstArrayPosition < len)) { - throw new IllegalArgumentException("Invalid encoded byte array"); - } - - byte[] store = encodedArrays.get(0); - - byte[] result; - if (store.length - firstArrayPosition == len) { - // We are done with the first array. - result = encodedArrays.remove(0); - firstArrayPosition = 0; - } else { - result = new byte[len]; - System.arraycopy(store, firstArrayPosition, result, 0, len); - firstArrayPosition = firstArrayPosition + len; - } - return result; - } - - /** - * Returns the encoded bytes that represent the current state of the OrderedCode. - * - *

NOTE: This method returns OrderedCode's internal array (not a copy) for better - * performance. Therefore the returned array should not be modified. - */ - public byte[] getEncodedBytes() { - if (encodedArrays.isEmpty()) { - return new byte[0]; - } - if ((encodedArrays.size() == 1) && (firstArrayPosition == 0)) { - return encodedArrays.get(0); - } - - int totalLength = 0; - - for (int i = 0; i < encodedArrays.size(); i++) { - byte[] bytes = encodedArrays.get(i); - if (i == 0) { - totalLength += bytes.length - firstArrayPosition; - } else { - totalLength += bytes.length; - } - } - - byte[] encodedBytes = new byte[totalLength]; - int destPos = 0; - for (int i = 0; i < encodedArrays.size(); i++) { - byte[] bytes = encodedArrays.get(i); - if (i == 0) { - System.arraycopy( - bytes, firstArrayPosition, encodedBytes, destPos, bytes.length - firstArrayPosition); - destPos += bytes.length - firstArrayPosition; - } else { - System.arraycopy(bytes, 0, encodedBytes, destPos, bytes.length); - destPos += bytes.length; - } - } - - // replace the store with merged array, so that repeated calls - // don't need to merge. The reads can handle both the versions. - encodedArrays.clear(); - encodedArrays.add(encodedBytes); - firstArrayPosition = 0; - - return encodedBytes; - } - - /** - * Returns true if this has more encoded bytes that haven't been read, false otherwise. Return - * value of true doesn't imply anything about validity of remaining data. - * - * @return true if it has more encoded bytes that haven't been read, false otherwise. - */ - public boolean hasRemainingEncodedBytes() { - // We delete an array after fully consuming it. - return encodedArrays != null && encodedArrays.size() != 0; - } -} diff --git a/runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/OrderedCodeTest.java b/runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/OrderedCodeTest.java deleted file mode 100644 index 58e2413e887..00000000000 --- a/runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/OrderedCodeTest.java +++ /dev/null @@ -1,489 +0,0 @@ -/* - * 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.runners.dataflow.worker; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.io.BaseEncoding; -import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.primitives.Bytes; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -/** Tests for OrderedCode. */ -@RunWith(JUnit4.class) -public class OrderedCodeTest { - @Test - public void testWriteInfinity() { - OrderedCode orderedCode = new OrderedCode(); - try { - orderedCode.readInfinity(); - fail("Expected IllegalArgumentException."); - } catch (IllegalArgumentException e) { - // expected - } - orderedCode.writeInfinity(); - assertTrue(orderedCode.readInfinity()); - try { - orderedCode.readInfinity(); - fail("Expected IllegalArgumentException."); - } catch (IllegalArgumentException e) { - // expected - } - } - - @Test - public void testWriteBytes() { - byte[] first = {'a', 'b', 'c'}; - byte[] second = {'d', 'e', 'f'}; - byte[] last = {'x', 'y', 'z'}; - OrderedCode orderedCode = new OrderedCode(); - orderedCode.writeBytes(first); - byte[] firstEncoded = orderedCode.getEncodedBytes(); - assertArrayEquals(orderedCode.readBytes(), first); - - orderedCode.writeBytes(first); - orderedCode.writeBytes(second); - orderedCode.writeBytes(last); - byte[] allEncoded = orderedCode.getEncodedBytes(); - assertArrayEquals(orderedCode.readBytes(), first); - assertArrayEquals(orderedCode.readBytes(), second); - assertArrayEquals(orderedCode.readBytes(), last); - - orderedCode = new OrderedCode(firstEncoded); - orderedCode.writeBytes(second); - orderedCode.writeBytes(last); - assertArrayEquals(orderedCode.getEncodedBytes(), allEncoded); - assertArrayEquals(orderedCode.readBytes(), first); - assertArrayEquals(orderedCode.readBytes(), second); - assertArrayEquals(orderedCode.readBytes(), last); - - orderedCode = new OrderedCode(allEncoded); - assertArrayEquals(orderedCode.readBytes(), first); - assertArrayEquals(orderedCode.readBytes(), second); - assertArrayEquals(orderedCode.readBytes(), last); - } - - @Test - public void testWriteNumIncreasing() { - OrderedCode orderedCode = new OrderedCode(); - orderedCode.writeNumIncreasing(0); - orderedCode.writeNumIncreasing(1); - orderedCode.writeNumIncreasing(Long.MIN_VALUE); - orderedCode.writeNumIncreasing(Long.MAX_VALUE); - assertEquals(0, orderedCode.readNumIncreasing()); - assertEquals(1, orderedCode.readNumIncreasing()); - assertEquals(Long.MIN_VALUE, orderedCode.readNumIncreasing()); - assertEquals(Long.MAX_VALUE, orderedCode.readNumIncreasing()); - } - - /** - * Assert that encoding the specified long via {@link OrderedCode#writeSignedNumIncreasing(long)} - * results in the bytes represented by the specified string of hex digits. E.g. - * assertSignedNumIncreasingEncodingEquals("3fbf", -65) asserts that -65 is encoded as { (byte) - * 0x3f, (byte) 0xbf }. - */ - private static void assertSignedNumIncreasingEncodingEquals( - String expectedHexEncoding, long num) { - OrderedCode orderedCode = new OrderedCode(); - orderedCode.writeSignedNumIncreasing(num); - assertEquals( - "Unexpected encoding for " + num, - expectedHexEncoding, - BaseEncoding.base16().lowerCase().encode(orderedCode.getEncodedBytes())); - } - - /** - * Assert that encoding various long values via {@link OrderedCode#writeSignedNumIncreasing(long)} - * produces the expected bytes. Expected byte sequences were generated via the c++ (authoritative) - * implementation of OrderedCode::WriteSignedNumIncreasing. - */ - @Test - public void testSignedNumIncreasing_write() { - assertSignedNumIncreasingEncodingEquals("003f8000000000000000", Long.MIN_VALUE); - assertSignedNumIncreasingEncodingEquals("003f8000000000000001", Long.MIN_VALUE + 1); - assertSignedNumIncreasingEncodingEquals("077fffffff", Integer.MIN_VALUE - 1L); - assertSignedNumIncreasingEncodingEquals("0780000000", Integer.MIN_VALUE); - assertSignedNumIncreasingEncodingEquals("0780000001", Integer.MIN_VALUE + 1); - assertSignedNumIncreasingEncodingEquals("3fbf", -65); - assertSignedNumIncreasingEncodingEquals("40", -64); - assertSignedNumIncreasingEncodingEquals("41", -63); - assertSignedNumIncreasingEncodingEquals("7d", -3); - assertSignedNumIncreasingEncodingEquals("7e", -2); - assertSignedNumIncreasingEncodingEquals("7f", -1); - assertSignedNumIncreasingEncodingEquals("80", 0); - assertSignedNumIncreasingEncodingEquals("81", 1); - assertSignedNumIncreasingEncodingEquals("82", 2); - assertSignedNumIncreasingEncodingEquals("83", 3); - assertSignedNumIncreasingEncodingEquals("bf", 63); - assertSignedNumIncreasingEncodingEquals("c040", 64); - assertSignedNumIncreasingEncodingEquals("c041", 65); - assertSignedNumIncreasingEncodingEquals("f87ffffffe", Integer.MAX_VALUE - 1); - assertSignedNumIncreasingEncodingEquals("f87fffffff", Integer.MAX_VALUE); - assertSignedNumIncreasingEncodingEquals("f880000000", Integer.MAX_VALUE + 1L); - assertSignedNumIncreasingEncodingEquals("ffc07ffffffffffffffe", Long.MAX_VALUE - 1); - assertSignedNumIncreasingEncodingEquals("ffc07fffffffffffffff", Long.MAX_VALUE); - } - - /** - * Convert a string of hex digits (e.g. "3fbf") to a byte[] (e.g. { (byte) 0x3f, (byte) 0xbf }). - */ - private static byte[] bytesFromHexString(String hexDigits) { - return BaseEncoding.base16().lowerCase().decode(hexDigits); - } - - /** - * Assert that decoding (via {@link OrderedCode#readSignedNumIncreasing()}) the bytes represented - * by the specified string of hex digits results in the expected long value. E.g. - * assertDecodedSignedNumIncreasingEquals(-65, "3fbf") asserts that the byte array { (byte) 0x3f, - * (byte) 0xbf } is decoded as -65. - */ - private static void assertDecodedSignedNumIncreasingEquals( - long expectedNum, String encodedHexString) { - OrderedCode orderedCode = new OrderedCode(bytesFromHexString(encodedHexString)); - assertEquals( - "Unexpected value when decoding 0x" + encodedHexString, - expectedNum, - orderedCode.readSignedNumIncreasing()); - assertFalse( - "Unexpected encoded bytes remain after decoding 0x" + encodedHexString, - orderedCode.hasRemainingEncodedBytes()); - } - - /** - * Assert that decoding various sequences of bytes via {@link - * OrderedCode#readSignedNumIncreasing()} produces the expected long value. Input byte sequences - * were generated via the c++ (authoritative) implementation of - * OrderedCode::WriteSignedNumIncreasing. - */ - @Test - public void testSignedNumIncreasing_read() { - assertDecodedSignedNumIncreasingEquals(Long.MIN_VALUE, "003f8000000000000000"); - assertDecodedSignedNumIncreasingEquals(Long.MIN_VALUE + 1, "003f8000000000000001"); - assertDecodedSignedNumIncreasingEquals(Integer.MIN_VALUE - 1L, "077fffffff"); - assertDecodedSignedNumIncreasingEquals(Integer.MIN_VALUE, "0780000000"); - assertDecodedSignedNumIncreasingEquals(Integer.MIN_VALUE + 1, "0780000001"); - assertDecodedSignedNumIncreasingEquals(-65, "3fbf"); - assertDecodedSignedNumIncreasingEquals(-64, "40"); - assertDecodedSignedNumIncreasingEquals(-63, "41"); - assertDecodedSignedNumIncreasingEquals(-3, "7d"); - assertDecodedSignedNumIncreasingEquals(-2, "7e"); - assertDecodedSignedNumIncreasingEquals(-1, "7f"); - assertDecodedSignedNumIncreasingEquals(0, "80"); - assertDecodedSignedNumIncreasingEquals(1, "81"); - assertDecodedSignedNumIncreasingEquals(2, "82"); - assertDecodedSignedNumIncreasingEquals(3, "83"); - assertDecodedSignedNumIncreasingEquals(63, "bf"); - assertDecodedSignedNumIncreasingEquals(64, "c040"); - assertDecodedSignedNumIncreasingEquals(65, "c041"); - assertDecodedSignedNumIncreasingEquals(Integer.MAX_VALUE - 1, "f87ffffffe"); - assertDecodedSignedNumIncreasingEquals(Integer.MAX_VALUE, "f87fffffff"); - assertDecodedSignedNumIncreasingEquals(Integer.MAX_VALUE + 1L, "f880000000"); - assertDecodedSignedNumIncreasingEquals(Long.MAX_VALUE - 1, "ffc07ffffffffffffffe"); - assertDecodedSignedNumIncreasingEquals(Long.MAX_VALUE, "ffc07fffffffffffffff"); - } - - /** - * Assert that encoding (via {@link OrderedCode#writeSignedNumIncreasing(long)}) the specified - * long value and then decoding (via {@link OrderedCode#readSignedNumIncreasing()}) results in the - * original value. - */ - private static void assertSignedNumIncreasingWriteAndReadIsLossless(long num) { - OrderedCode orderedCode = new OrderedCode(); - orderedCode.writeSignedNumIncreasing(num); - assertEquals( - "Unexpected result when decoding writeSignedNumIncreasing(" + num + ")", - num, - orderedCode.readSignedNumIncreasing()); - assertFalse( - "Unexpected remaining encoded bytes after decoding " + num, - orderedCode.hasRemainingEncodedBytes()); - } - - /** - * Assert that for various long values, encoding (via {@link - * OrderedCode#writeSignedNumIncreasing(long)}) and then decoding (via {@link - * OrderedCode#readSignedNumIncreasing()}) results in the original value. - */ - @Test - public void testSignedNumIncreasing_writeAndRead() { - assertSignedNumIncreasingWriteAndReadIsLossless(Long.MIN_VALUE); - assertSignedNumIncreasingWriteAndReadIsLossless(Long.MIN_VALUE + 1); - assertSignedNumIncreasingWriteAndReadIsLossless(Integer.MIN_VALUE - 1L); - assertSignedNumIncreasingWriteAndReadIsLossless(Integer.MIN_VALUE); - assertSignedNumIncreasingWriteAndReadIsLossless(Integer.MIN_VALUE + 1); - assertSignedNumIncreasingWriteAndReadIsLossless(-65); - assertSignedNumIncreasingWriteAndReadIsLossless(-64); - assertSignedNumIncreasingWriteAndReadIsLossless(-63); - assertSignedNumIncreasingWriteAndReadIsLossless(-3); - assertSignedNumIncreasingWriteAndReadIsLossless(-2); - assertSignedNumIncreasingWriteAndReadIsLossless(-1); - assertSignedNumIncreasingWriteAndReadIsLossless(0); - assertSignedNumIncreasingWriteAndReadIsLossless(1); - assertSignedNumIncreasingWriteAndReadIsLossless(2); - assertSignedNumIncreasingWriteAndReadIsLossless(3); - assertSignedNumIncreasingWriteAndReadIsLossless(63); - assertSignedNumIncreasingWriteAndReadIsLossless(64); - assertSignedNumIncreasingWriteAndReadIsLossless(65); - assertSignedNumIncreasingWriteAndReadIsLossless(Integer.MAX_VALUE - 1); - assertSignedNumIncreasingWriteAndReadIsLossless(Integer.MAX_VALUE); - assertSignedNumIncreasingWriteAndReadIsLossless(Integer.MAX_VALUE + 1L); - assertSignedNumIncreasingWriteAndReadIsLossless(Long.MAX_VALUE - 1); - assertSignedNumIncreasingWriteAndReadIsLossless(Long.MAX_VALUE); - } - - @Test - public void testLog2Floor_Positive() { - OrderedCode orderedCode = new OrderedCode(); - assertEquals(0, orderedCode.log2Floor(1)); - assertEquals(1, orderedCode.log2Floor(2)); - assertEquals(1, orderedCode.log2Floor(3)); - assertEquals(2, orderedCode.log2Floor(4)); - assertEquals(5, orderedCode.log2Floor(63)); - assertEquals(6, orderedCode.log2Floor(64)); - assertEquals(62, orderedCode.log2Floor(Long.MAX_VALUE)); - } - - /** OrderedCode.log2Floor(long) is defined to return -1 given an input of zero. */ - @Test - public void testLog2Floor_zero() { - OrderedCode orderedCode = new OrderedCode(); - assertEquals(-1, orderedCode.log2Floor(0)); - } - - @Test - public void testLog2Floor_negative() { - OrderedCode orderedCode = new OrderedCode(); - try { - orderedCode.log2Floor(-1); - fail("Expected an IllegalArgumentException."); - } catch (IllegalArgumentException expected) { - // Expected! - } - } - - @Test - public void testGetSignedEncodingLength() { - OrderedCode orderedCode = new OrderedCode(); - assertEquals(10, orderedCode.getSignedEncodingLength(Long.MIN_VALUE)); - assertEquals(10, orderedCode.getSignedEncodingLength(~(1L << 62))); - assertEquals(9, orderedCode.getSignedEncodingLength(~(1L << 62) + 1)); - assertEquals(3, orderedCode.getSignedEncodingLength(-8193)); - assertEquals(2, orderedCode.getSignedEncodingLength(-8192)); - assertEquals(2, orderedCode.getSignedEncodingLength(-65)); - assertEquals(1, orderedCode.getSignedEncodingLength(-64)); - assertEquals(1, orderedCode.getSignedEncodingLength(-2)); - assertEquals(1, orderedCode.getSignedEncodingLength(-1)); - assertEquals(1, orderedCode.getSignedEncodingLength(0)); - assertEquals(1, orderedCode.getSignedEncodingLength(1)); - assertEquals(1, orderedCode.getSignedEncodingLength(63)); - assertEquals(2, orderedCode.getSignedEncodingLength(64)); - assertEquals(2, orderedCode.getSignedEncodingLength(8191)); - assertEquals(3, orderedCode.getSignedEncodingLength(8192)); - assertEquals(9, orderedCode.getSignedEncodingLength((1L << 62)) - 1); - assertEquals(10, orderedCode.getSignedEncodingLength(1L << 62)); - assertEquals(10, orderedCode.getSignedEncodingLength(Long.MAX_VALUE)); - } - - @Test - public void testWriteTrailingBytes() { - byte[] escapeChars = - new byte[] { - OrderedCode.ESCAPE1, - OrderedCode.NULL_CHARACTER, - OrderedCode.SEPARATOR, - OrderedCode.ESCAPE2, - OrderedCode.INFINITY, - OrderedCode.FF_CHARACTER - }; - byte[] anotherArray = new byte[] {'a', 'b', 'c', 'd', 'e'}; - - OrderedCode orderedCode = new OrderedCode(); - orderedCode.writeTrailingBytes(escapeChars); - assertArrayEquals(orderedCode.getEncodedBytes(), escapeChars); - assertArrayEquals(orderedCode.readTrailingBytes(), escapeChars); - try { - orderedCode.readInfinity(); - fail("Expected IllegalArgumentException."); - } catch (IllegalArgumentException e) { - // expected - } - - orderedCode = new OrderedCode(); - orderedCode.writeTrailingBytes(anotherArray); - assertArrayEquals(orderedCode.getEncodedBytes(), anotherArray); - assertArrayEquals(orderedCode.readTrailingBytes(), anotherArray); - } - - @Test - public void testMixedWrite() { - byte[] first = {'a', 'b', 'c'}; - byte[] second = {'d', 'e', 'f'}; - byte[] last = {'x', 'y', 'z'}; - byte[] escapeChars = - new byte[] { - OrderedCode.ESCAPE1, - OrderedCode.NULL_CHARACTER, - OrderedCode.SEPARATOR, - OrderedCode.ESCAPE2, - OrderedCode.INFINITY, - OrderedCode.FF_CHARACTER - }; - - OrderedCode orderedCode = new OrderedCode(); - orderedCode.writeBytes(first); - orderedCode.writeBytes(second); - orderedCode.writeBytes(last); - orderedCode.writeInfinity(); - orderedCode.writeNumIncreasing(0); - orderedCode.writeNumIncreasing(1); - orderedCode.writeNumIncreasing(Long.MIN_VALUE); - orderedCode.writeNumIncreasing(Long.MAX_VALUE); - orderedCode.writeSignedNumIncreasing(0); - orderedCode.writeSignedNumIncreasing(1); - orderedCode.writeSignedNumIncreasing(Long.MIN_VALUE); - orderedCode.writeSignedNumIncreasing(Long.MAX_VALUE); - orderedCode.writeTrailingBytes(escapeChars); - byte[] allEncoded = orderedCode.getEncodedBytes(); - assertArrayEquals(orderedCode.readBytes(), first); - assertArrayEquals(orderedCode.readBytes(), second); - assertFalse(orderedCode.readInfinity()); - assertArrayEquals(orderedCode.readBytes(), last); - assertTrue(orderedCode.readInfinity()); - assertEquals(0, orderedCode.readNumIncreasing()); - assertEquals(1, orderedCode.readNumIncreasing()); - assertFalse(orderedCode.readInfinity()); - assertEquals(Long.MIN_VALUE, orderedCode.readNumIncreasing()); - assertEquals(Long.MAX_VALUE, orderedCode.readNumIncreasing()); - assertEquals(0, orderedCode.readSignedNumIncreasing()); - assertEquals(1, orderedCode.readSignedNumIncreasing()); - assertFalse(orderedCode.readInfinity()); - assertEquals(Long.MIN_VALUE, orderedCode.readSignedNumIncreasing()); - assertEquals(Long.MAX_VALUE, orderedCode.readSignedNumIncreasing()); - assertArrayEquals(orderedCode.getEncodedBytes(), escapeChars); - assertArrayEquals(orderedCode.readTrailingBytes(), escapeChars); - - orderedCode = new OrderedCode(allEncoded); - assertArrayEquals(orderedCode.readBytes(), first); - assertArrayEquals(orderedCode.readBytes(), second); - assertFalse(orderedCode.readInfinity()); - assertArrayEquals(orderedCode.readBytes(), last); - assertTrue(orderedCode.readInfinity()); - assertEquals(0, orderedCode.readNumIncreasing()); - assertEquals(1, orderedCode.readNumIncreasing()); - assertFalse(orderedCode.readInfinity()); - assertEquals(Long.MIN_VALUE, orderedCode.readNumIncreasing()); - assertEquals(Long.MAX_VALUE, orderedCode.readNumIncreasing()); - assertEquals(0, orderedCode.readSignedNumIncreasing()); - assertEquals(1, orderedCode.readSignedNumIncreasing()); - assertFalse(orderedCode.readInfinity()); - assertEquals(Long.MIN_VALUE, orderedCode.readSignedNumIncreasing()); - assertEquals(Long.MAX_VALUE, orderedCode.readSignedNumIncreasing()); - assertArrayEquals(orderedCode.getEncodedBytes(), escapeChars); - assertArrayEquals(orderedCode.readTrailingBytes(), escapeChars); - } - - @Test - public void testEdgeCases() { - byte[] ffChar = {OrderedCode.FF_CHARACTER}; - byte[] nullChar = {OrderedCode.NULL_CHARACTER}; - - byte[] separatorEncoded = {OrderedCode.ESCAPE1, OrderedCode.SEPARATOR}; - byte[] ffCharEncoded = {OrderedCode.ESCAPE1, OrderedCode.NULL_CHARACTER}; - byte[] nullCharEncoded = {OrderedCode.ESCAPE2, OrderedCode.FF_CHARACTER}; - byte[] infinityEncoded = {OrderedCode.ESCAPE2, OrderedCode.INFINITY}; - - OrderedCode orderedCode = new OrderedCode(); - orderedCode.writeBytes(ffChar); - orderedCode.writeBytes(nullChar); - orderedCode.writeInfinity(); - assertArrayEquals( - orderedCode.getEncodedBytes(), - Bytes.concat( - ffCharEncoded, separatorEncoded, nullCharEncoded, separatorEncoded, infinityEncoded)); - assertArrayEquals(orderedCode.readBytes(), ffChar); - assertArrayEquals(orderedCode.readBytes(), nullChar); - assertTrue(orderedCode.readInfinity()); - - orderedCode = new OrderedCode(Bytes.concat(ffCharEncoded, separatorEncoded)); - assertArrayEquals(orderedCode.readBytes(), ffChar); - - orderedCode = new OrderedCode(Bytes.concat(nullCharEncoded, separatorEncoded)); - assertArrayEquals(orderedCode.readBytes(), nullChar); - - byte[] invalidEncodingForRead = { - OrderedCode.ESCAPE2, OrderedCode.ESCAPE2, OrderedCode.ESCAPE1, OrderedCode.SEPARATOR - }; - orderedCode = new OrderedCode(invalidEncodingForRead); - try { - orderedCode.readBytes(); - fail("Should have failed."); - } catch (Exception e) { - // Expected - } - assertTrue(orderedCode.hasRemainingEncodedBytes()); - } - - @Test - public void testHasRemainingEncodedBytes() { - byte[] bytes = {'a', 'b', 'c'}; - long number = 12345; - - // Empty - OrderedCode orderedCode = new OrderedCode(); - assertFalse(orderedCode.hasRemainingEncodedBytes()); - - // First and only field of each type. - orderedCode.writeBytes(bytes); - assertTrue(orderedCode.hasRemainingEncodedBytes()); - assertArrayEquals(orderedCode.readBytes(), bytes); - assertFalse(orderedCode.hasRemainingEncodedBytes()); - - orderedCode.writeNumIncreasing(number); - assertTrue(orderedCode.hasRemainingEncodedBytes()); - assertEquals(orderedCode.readNumIncreasing(), number); - assertFalse(orderedCode.hasRemainingEncodedBytes()); - - orderedCode.writeSignedNumIncreasing(number); - assertTrue(orderedCode.hasRemainingEncodedBytes()); - assertEquals(orderedCode.readSignedNumIncreasing(), number); - assertFalse(orderedCode.hasRemainingEncodedBytes()); - - orderedCode.writeInfinity(); - assertTrue(orderedCode.hasRemainingEncodedBytes()); - assertTrue(orderedCode.readInfinity()); - assertFalse(orderedCode.hasRemainingEncodedBytes()); - - orderedCode.writeTrailingBytes(bytes); - assertTrue(orderedCode.hasRemainingEncodedBytes()); - assertArrayEquals(orderedCode.readTrailingBytes(), bytes); - assertFalse(orderedCode.hasRemainingEncodedBytes()); - - // Two fields of same type. - orderedCode.writeBytes(bytes); - orderedCode.writeBytes(bytes); - assertTrue(orderedCode.hasRemainingEncodedBytes()); - assertArrayEquals(orderedCode.readBytes(), bytes); - assertArrayEquals(orderedCode.readBytes(), bytes); - assertFalse(orderedCode.hasRemainingEncodedBytes()); - } -}