dev.cel.common.internal.ProtoEquality Maven / Gradle / Ivy
Show all versions of runtime Show documentation
// Copyright 2022 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.internal;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableMap;
import com.google.errorprone.annotations.CheckReturnValue;
import com.google.errorprone.annotations.Immutable;
import com.google.protobuf.Any;
import com.google.protobuf.ByteString;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Message;
import dev.cel.common.annotations.Internal;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* The {@code ProtoEquality} implementation is an alternative to the {@link Message#equals}
* implementation that is consistent with the C++ {@code MessageDifferencer::Equals} definition.
*
* These are the key differences between CEL's proto equality and Java's proto equality:
*
*
* NaN is not equal to itself.
* Any values are unpacked before comparison.
* If two Any values cannot be unpacked, they are compared by bytes.
*
*
* CEL Library Internals. Do Not Use.
*/
@CheckReturnValue
@Immutable
@Internal
public final class ProtoEquality {
private static final String ANY_FULL_NAME = Any.getDescriptor().getFullName();
private final DynamicProto dynamicProto;
public ProtoEquality(DynamicProto dynamicProto) {
this.dynamicProto = dynamicProto;
}
public boolean equals(Message message1, Message message2) {
if (message1 == message2) {
return true;
}
if (!message1.getDescriptorForType().equals(message2.getDescriptorForType())) {
return false;
}
// both messages must share the same descriptor, so if one is an Any, then both are.
if (isAny(message1)) {
// Convert from Any. The value here could be either Any or DynamicMessage
// Test whether the typeUrl values are the same, if not return false.
// Use the dynamicProto.unpack(message1), dynamicProto.unpack(message2)
// and assign the results to message1 and message2.
Optional unpackedAny1 = dynamicProto.maybeUnpackAny(message1);
Optional unpackedAny2 = dynamicProto.maybeUnpackAny(message2);
if (unpackedAny1.isPresent() && unpackedAny2.isPresent()) {
return equals(unpackedAny1.get(), unpackedAny2.get());
}
return anyValue(message1).equals(anyValue(message2));
}
if (!message1.getUnknownFields().equals(message2.getUnknownFields())) {
return false;
}
Map message1Fields = message1.getAllFields();
Map message2Fields = message2.getAllFields();
if (message1Fields.size() != message2Fields.size()) {
return false;
}
// No need to do a generic for-each, can use an iterator more efficiently.
for (Map.Entry entry : message1Fields.entrySet()) {
FieldDescriptor field = entry.getKey();
Object value1 = entry.getValue();
Object value2 = message2Fields.get(entry.getKey());
if (value2 == null) {
return false;
}
if (field.isMapField()) {
List> mapEntries1 = (List>) value1;
List> mapEntries2 = (List>) value2;
if (mapEntries1.size() != mapEntries2.size()) {
return false;
}
ProtoMap protoMap1 = protoMap(mapEntries1);
ProtoMap protoMap2 = protoMap(mapEntries2);
if (!protoMap1.valueField().equals(protoMap2.valueField())) {
return false;
}
for (Map.Entry