Skip to content

Commit

Permalink
Implement immutable byte string class for CEL
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 576681833
  • Loading branch information
l46kok authored and copybara-github committed Oct 31, 2023
1 parent 22a2c8e commit ccfbc0a
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 0 deletions.
19 changes: 19 additions & 0 deletions common/src/main/java/dev/cel/common/values/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package(
default_applicable_licenses = [
"//:license",
],
default_visibility = [
"//common/values:__pkg__",
],
)

java_library(
name = "cel_byte_string",
srcs = ["CelByteString.java"],
tags = [
],
deps = [
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
],
)
93 changes: 93 additions & 0 deletions common/src/main/java/dev/cel/common/values/CelByteString.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright 2023 Google LLC
//
// 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
//
// https://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 dev.cel.common.values;

import com.google.common.base.Preconditions;
import com.google.errorprone.annotations.Immutable;
import java.util.Arrays;

/** CelByteString is an immutable sequence of a byte array. */
@Immutable
@SuppressWarnings("Immutable") // We make defensive copies on the byte array.
public final class CelByteString {
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
public static final CelByteString EMPTY = new CelByteString(EMPTY_BYTE_ARRAY);

private final byte[] data;

private volatile int hash = 0;

public static CelByteString of(byte[] buffer) {
Preconditions.checkNotNull(buffer);
if (buffer.length == 0) {
return EMPTY;
}
return new CelByteString(buffer);
}

public int size() {
return data.length;
}

public boolean isEmpty() {
return data.length == 0;
}

public byte[] toByteArray() {
int size = size();
if (size == 0) {
return EMPTY_BYTE_ARRAY;
}

return Arrays.copyOf(data, size);
}

private CelByteString(byte[] buffer) {
data = Arrays.copyOf(buffer, buffer.length);
}

@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}

if (!(o instanceof CelByteString)) {
return false;
}

return Arrays.equals(data, ((CelByteString) o).data);
}

/**
* Note that we do not use Arrays.hashCode directly due to its implementation using 31 as an odd
* prime, which is outdated and is more prone to hash collisions. This code is very similar to
* what AutoValue generates.
*/
@Override
public int hashCode() {
if (hash == 0) {
int h = 1;
h *= 1000003;
h ^= Arrays.hashCode(data);
if (h == 0) {
h = 1;
}
hash = h;
}

return hash;
}
}
24 changes: 24 additions & 0 deletions common/src/test/java/dev/cel/common/values/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
load("//:testing.bzl", "junit4_test_suites")

package(default_applicable_licenses = ["//:license"])

java_library(
name = "tests",
testonly = True,
srcs = glob(["*.java"]),
deps = [
"//:java_truth",
"//common/values:cel_byte_string",
"@maven//:com_google_guava_guava_testlib",
"@maven//:junit_junit",
],
)

junit4_test_suites(
name = "test_suites",
sizes = [
"small",
],
src_dir = "src/test/java",
deps = [":tests"],
)
88 changes: 88 additions & 0 deletions common/src/test/java/dev/cel/common/values/CelByteStringTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright 2023 Google LLC
//
// 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
//
// https://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 dev.cel.common.values;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;

import com.google.common.testing.ClassSanityTester;
import com.google.common.testing.EqualsTester;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

@RunWith(JUnit4.class)
public class CelByteStringTest {

@Test
public void emptyBytes() {
CelByteString byteString = CelByteString.of(new byte[0]);

assertThat(byteString).isEqualTo(CelByteString.EMPTY);
assertThat(byteString.isEmpty()).isTrue();
assertThat(byteString.toByteArray()).isEqualTo(new byte[0]);
}

@Test
public void equalityTest() {
new EqualsTester()
.addEqualityGroup(CelByteString.of(new byte[0]), CelByteString.EMPTY)
.addEqualityGroup(CelByteString.of(new byte[] {0xa}), CelByteString.of(new byte[] {0xa}))
.addEqualityGroup(
CelByteString.of(new byte[] {0x1, 0x5, 0xc}),
CelByteString.of(new byte[] {0x1, 0x5, 0xc}))
.addEqualityGroup(CelByteString.of(new byte[] {0xf}))
.testEquals();
}

@Test
public void sanityTest() throws Exception {
new ClassSanityTester()
.setDefault(CelByteString.class, CelByteString.of(new byte[] {0x1, 0x5, 0xc}))
.setDistinctValues(
CelByteString.class,
CelByteString.of(new byte[] {0x1, 0x5, 0xc}),
CelByteString.of(new byte[] {0x2, 0x6, 0xc}))
.forAllPublicStaticMethods(CelByteString.class)
.thatReturn(CelByteString.class)
.testEquals()
.testNulls();
}

@Test
public void hashCode_smokeTest() {
assertThat(CelByteString.of(new byte[0]).hashCode()).isEqualTo(1000002);
assertThat(CelByteString.of(new byte[] {0x1}).hashCode()).isEqualTo(1000035);
assertThat(CelByteString.of(new byte[] {0x1, 0x5}).hashCode()).isEqualTo(999846);
assertThat(CelByteString.of(new byte[] {0x1, 0x5, 0xc}).hashCode()).isEqualTo(998020);
}

@Test
public void byteString_isImmutable() {
byte[] bytes = {0x1, 0xa, 0x3};
CelByteString byteString = CelByteString.of(bytes);

bytes[0] = 0x2;
byte[] newByteArray = byteString.toByteArray();
assertThat(newByteArray).isNotEqualTo(bytes);
bytes[0] = 0x1;
assertThat(newByteArray).isEqualTo(bytes);
}

@Test
public void nullBytes_throws() {
assertThrows(NullPointerException.class, () -> CelByteString.of(null));
}
}
9 changes: 9 additions & 0 deletions common/values/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package(
default_applicable_licenses = ["//:license"],
default_visibility = ["//visibility:public"], # TODO: Expose to public when ready
)

java_library(
name = "cel_byte_string",
exports = ["//common/src/main/java/dev/cel/common/values:cel_byte_string"],
)

0 comments on commit ccfbc0a

Please sign in to comment.