aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/google/protobuf/util
diff options
context:
space:
mode:
authorGravatar Jisi Liu <jisi.liu@gmail.com>2017-07-18 15:38:30 -0700
committerGravatar Jisi Liu <jisi.liu@gmail.com>2017-07-18 15:38:30 -0700
commit09354db1434859a31a3c81abebcc4018d42f2715 (patch)
treeb87c7cdc2255e6c8062ab92b4082665cd698d753 /src/google/protobuf/util
parent9053033a5076f82cf18b823c31f352e95e5bfd8d (diff)
Merge from Google internal for 3.4 release
Diffstat (limited to 'src/google/protobuf/util')
-rw-r--r--src/google/protobuf/util/field_comparator.h2
-rw-r--r--src/google/protobuf/util/field_mask_util.cc69
-rw-r--r--src/google/protobuf/util/field_mask_util.h24
-rw-r--r--src/google/protobuf/util/field_mask_util_test.cc80
-rw-r--r--src/google/protobuf/util/internal/default_value_objectwriter.cc1
-rw-r--r--src/google/protobuf/util/internal/default_value_objectwriter.h6
-rw-r--r--src/google/protobuf/util/internal/protostream_objectsource.cc19
-rw-r--r--src/google/protobuf/util/internal/protostream_objectsource.h3
-rw-r--r--src/google/protobuf/util/internal/protostream_objectsource_test.cc31
-rw-r--r--src/google/protobuf/util/internal/protostream_objectwriter.cc2
-rw-r--r--src/google/protobuf/util/internal/utility.cc5
-rw-r--r--src/google/protobuf/util/message_differencer.cc104
-rw-r--r--src/google/protobuf/util/message_differencer.h33
-rwxr-xr-xsrc/google/protobuf/util/message_differencer_unittest.cc168
14 files changed, 490 insertions, 57 deletions
diff --git a/src/google/protobuf/util/field_comparator.h b/src/google/protobuf/util/field_comparator.h
index ad560ebc..26a7ba4d 100644
--- a/src/google/protobuf/util/field_comparator.h
+++ b/src/google/protobuf/util/field_comparator.h
@@ -237,7 +237,7 @@ class LIBPROTOBUF_EXPORT DefaultFieldComparator : public FieldComparator {
// True iff default_tolerance_ has been explicitly set.
//
- // If false, then the default tolerance for flaots and doubles is that which
+ // If false, then the default tolerance for floats and doubles is that which
// is used by MathUtil::AlmostEquals().
bool has_default_tolerance_;
diff --git a/src/google/protobuf/util/field_mask_util.cc b/src/google/protobuf/util/field_mask_util.cc
index 85cecec5..982d6407 100644
--- a/src/google/protobuf/util/field_mask_util.cc
+++ b/src/google/protobuf/util/field_mask_util.cc
@@ -207,6 +207,18 @@ class FieldMaskTree {
MergeMessage(&root_, source, options, destination);
}
+ // Add required field path of the message to this tree based on current tree
+ // structure. If a message is present in the tree, add the path of its
+ // required field to the tree. This is to make sure that after trimming a
+ // message with required fields are set, check IsInitialized() will not fail.
+ void AddRequiredFieldPath(const Descriptor* descriptor) {
+ // Do nothing if the tree is empty.
+ if (root_.children.empty()) {
+ return;
+ }
+ AddRequiredFieldPath(&root_, descriptor);
+ }
+
// Trims all fields not specified by this tree from the given message.
void TrimMessage(Message* message) {
// Do nothing if the tree is empty.
@@ -249,6 +261,12 @@ class FieldMaskTree {
const FieldMaskUtil::MergeOptions& options,
Message* destination);
+ // Add required field path of the message to this tree based on current tree
+ // structure. If a message is present in the tree, add the path of its
+ // required field to the tree. This is to make sure that after trimming a
+ // message with required fields are set, check IsInitialized() will not fail.
+ void AddRequiredFieldPath(Node* node, const Descriptor* descriptor);
+
// Trims all fields not specified by this sub-tree from the given message.
void TrimMessage(const Node* node, Message* message);
@@ -456,6 +474,41 @@ void FieldMaskTree::MergeMessage(const Node* node, const Message& source,
}
}
+void FieldMaskTree::AddRequiredFieldPath(
+ Node* node, const Descriptor* descriptor) {
+ const int32 field_count = descriptor->field_count();
+ for (int index = 0; index < field_count; ++index) {
+ const FieldDescriptor* field = descriptor->field(index);
+ if (field->is_required()) {
+ const string& node_name = field->name();
+ Node*& child = node->children[node_name];
+ if (child == NULL) {
+ // Add required field path to the tree
+ child = new Node();
+ } else if (child->children.empty()){
+ // If the required field is in the tree and does not have any children,
+ // do nothing.
+ continue;
+ }
+ // Add required field in the children to the tree if the field is message.
+ if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
+ AddRequiredFieldPath(child, field->message_type());
+ }
+ } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
+ std::map<string, Node*>::const_iterator it =
+ node->children.find(field->name());
+ if (it != node->children.end()) {
+ // Add required fields in the children to the
+ // tree if the field is a message and present in the tree.
+ Node* child = it->second;
+ if (!child->children.empty()) {
+ AddRequiredFieldPath(child, field->message_type());
+ }
+ }
+ }
+ }
+}
+
void FieldMaskTree::TrimMessage(const Node* node, Message* message) {
GOOGLE_DCHECK(!node->children.empty());
const Reflection* reflection = message->GetReflection();
@@ -470,7 +523,7 @@ void FieldMaskTree::TrimMessage(const Node* node, Message* message) {
} else {
if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
Node* child = it->second;
- if (!child->children.empty()) {
+ if (!child->children.empty() && reflection->HasField(*message, field)) {
TrimMessage(child, reflection->MutableMessage(message, field));
}
}
@@ -542,6 +595,20 @@ void FieldMaskUtil::TrimMessage(const FieldMask& mask, Message* destination) {
tree.TrimMessage(GOOGLE_CHECK_NOTNULL(destination));
}
+void FieldMaskUtil::TrimMessage(const FieldMask& mask, Message* destination,
+ const TrimOptions& options) {
+ // Build a FieldMaskTree and walk through the tree to merge all specified
+ // fields.
+ FieldMaskTree tree;
+ tree.MergeFromFieldMask(mask);
+ // If keep_required_fields is true, implicitely add required fields of
+ // a message present in the tree to prevent from trimming.
+ if (options.keep_required_fields()) {
+ tree.AddRequiredFieldPath(GOOGLE_CHECK_NOTNULL(destination->GetDescriptor()));
+ }
+ tree.TrimMessage(GOOGLE_CHECK_NOTNULL(destination));
+}
+
} // namespace util
} // namespace protobuf
} // namespace google
diff --git a/src/google/protobuf/util/field_mask_util.h b/src/google/protobuf/util/field_mask_util.h
index ab1f2e94..71c68fec 100644
--- a/src/google/protobuf/util/field_mask_util.h
+++ b/src/google/protobuf/util/field_mask_util.h
@@ -124,10 +124,17 @@ class LIBPROTOBUF_EXPORT FieldMaskUtil {
static void MergeMessageTo(const Message& source, const FieldMask& mask,
const MergeOptions& options, Message* destination);
+ class TrimOptions;
// Removes from 'message' any field that is not represented in the given
// FieldMask. If the FieldMask is empty, does nothing.
static void TrimMessage(const FieldMask& mask, Message* message);
+ // Removes from 'message' any field that is not represented in the given
+ // FieldMask with customized TrimOptions.
+ // If the FieldMask is empty, does nothing.
+ static void TrimMessage(const FieldMask& mask, Message* message,
+ const TrimOptions& options);
+
private:
friend class SnakeCaseCamelCaseTest;
// Converts a field name from snake_case to camelCase:
@@ -194,6 +201,23 @@ class LIBPROTOBUF_EXPORT FieldMaskUtil::MergeOptions {
bool replace_repeated_fields_;
};
+class LIBPROTOBUF_EXPORT FieldMaskUtil::TrimOptions {
+ public:
+ TrimOptions()
+ : keep_required_fields_(false) {}
+ // When trimming message fields, the default behavior is to trim required
+ // fields of the present message if they are not specified in the field mask.
+ // If you instead want to keep required fields of the present message even
+ // they are not speicifed in the field mask, set this flag to true.
+ void set_keep_required_fields(bool value) {
+ keep_required_fields_ = value;
+ }
+ bool keep_required_fields() const { return keep_required_fields_; }
+
+ private:
+ bool keep_required_fields_;
+};
+
} // namespace util
} // namespace protobuf
diff --git a/src/google/protobuf/util/field_mask_util_test.cc b/src/google/protobuf/util/field_mask_util_test.cc
index f952786f..24943ed1 100644
--- a/src/google/protobuf/util/field_mask_util_test.cc
+++ b/src/google/protobuf/util/field_mask_util_test.cc
@@ -114,6 +114,8 @@ TEST_F(SnakeCaseCamelCaseTest, RoundTripTest) {
}
using protobuf_unittest::TestAllTypes;
+using protobuf_unittest::TestRequired;
+using protobuf_unittest::TestRequiredMessage;
using protobuf_unittest::NestedTestAllTypes;
using google::protobuf::FieldMask;
@@ -618,6 +620,84 @@ TEST(FieldMaskUtilTest, TrimMessage) {
FieldMask empty_mask;
FieldMaskUtil::TrimMessage(empty_mask, &trimmed_all_types);
EXPECT_EQ(trimmed_all_types.DebugString(), all_types_msg.DebugString());
+
+ // Test trim required fields with keep_required_fields is set true.
+ FieldMaskUtil::TrimOptions options;
+ TestRequired required_msg_1;
+ required_msg_1.set_a(1234);
+ required_msg_1.set_b(3456);
+ required_msg_1.set_c(5678);
+ TestRequired trimmed_required_msg_1(required_msg_1);
+ FieldMaskUtil::FromString("dummy2", &mask);
+ options.set_keep_required_fields(true);
+ FieldMaskUtil::TrimMessage(mask, &trimmed_required_msg_1, options);
+ EXPECT_EQ(trimmed_required_msg_1.DebugString(), required_msg_1.DebugString());
+
+ // Test trim required fields with keep_required_fields is set false.
+ required_msg_1.clear_a();
+ required_msg_1.clear_b();
+ required_msg_1.clear_c();
+ options.set_keep_required_fields(false);
+ FieldMaskUtil::TrimMessage(mask, &trimmed_required_msg_1, options);
+ EXPECT_EQ(trimmed_required_msg_1.DebugString(), required_msg_1.DebugString());
+
+ // Test trim required message with keep_required_fields is set true.
+ TestRequiredMessage required_msg_2;
+ required_msg_2.mutable_optional_message()->set_a(1234);
+ required_msg_2.mutable_optional_message()->set_b(3456);
+ required_msg_2.mutable_optional_message()->set_c(5678);
+ required_msg_2.mutable_required_message()->set_a(1234);
+ required_msg_2.mutable_required_message()->set_b(3456);
+ required_msg_2.mutable_required_message()->set_c(5678);
+ required_msg_2.mutable_required_message()->set_dummy2(7890);
+ TestRequired* repeated_msg = required_msg_2.add_repeated_message();
+ repeated_msg->set_a(1234);
+ repeated_msg->set_b(3456);
+ repeated_msg->set_c(5678);
+ TestRequiredMessage trimmed_required_msg_2(required_msg_2);
+ FieldMaskUtil::FromString("optional_message.dummy2", &mask);
+ options.set_keep_required_fields(true);
+ required_msg_2.clear_repeated_message();
+ required_msg_2.mutable_required_message()->clear_dummy2();
+ FieldMaskUtil::TrimMessage(mask, &trimmed_required_msg_2, options);
+ EXPECT_EQ(trimmed_required_msg_2.DebugString(),
+ required_msg_2.DebugString());
+
+ FieldMaskUtil::FromString("required_message", &mask);
+ required_msg_2.mutable_required_message()->set_dummy2(7890);
+ trimmed_required_msg_2.mutable_required_message()->set_dummy2(7890);
+ required_msg_2.clear_optional_message();
+ FieldMaskUtil::TrimMessage(mask, &trimmed_required_msg_2, options);
+ EXPECT_EQ(trimmed_required_msg_2.DebugString(),
+ required_msg_2.DebugString());
+
+ // Test trim required message with keep_required_fields is set false.
+ FieldMaskUtil::FromString("required_message.dummy2", &mask);
+ required_msg_2.mutable_required_message()->clear_a();
+ required_msg_2.mutable_required_message()->clear_b();
+ required_msg_2.mutable_required_message()->clear_c();
+ options.set_keep_required_fields(false);
+ FieldMaskUtil::TrimMessage(mask, &trimmed_required_msg_2, options);
+ EXPECT_EQ(trimmed_required_msg_2.DebugString(),
+ required_msg_2.DebugString());
+
+ // Verify that trimming an empty message has no effect. In particular, fields
+ // mentioned in the field mask should not be created or changed.
+ TestAllTypes empty_msg;
+ FieldMaskUtil::FromString(
+ "optional_int32,optional_bytes,optional_nested_message.bb", &mask);
+ FieldMaskUtil::TrimMessage(mask, &empty_msg);
+ EXPECT_FALSE(empty_msg.has_optional_int32());
+ EXPECT_FALSE(empty_msg.has_optional_bytes());
+ EXPECT_FALSE(empty_msg.has_optional_nested_message());
+
+ // Verify trimming of oneof fields. This should work as expected even if
+ // multiple elements of the same oneof are included in the FieldMask.
+ TestAllTypes oneof_msg;
+ oneof_msg.set_oneof_uint32(11);
+ FieldMaskUtil::FromString("oneof_uint32,oneof_nested_message.bb", &mask);
+ FieldMaskUtil::TrimMessage(mask, &oneof_msg);
+ EXPECT_EQ(11, oneof_msg.oneof_uint32());
}
diff --git a/src/google/protobuf/util/internal/default_value_objectwriter.cc b/src/google/protobuf/util/internal/default_value_objectwriter.cc
index 5763d0c6..95b3a17d 100644
--- a/src/google/protobuf/util/internal/default_value_objectwriter.cc
+++ b/src/google/protobuf/util/internal/default_value_objectwriter.cc
@@ -637,6 +637,7 @@ void DefaultValueObjectWriter::RenderDataPiece(StringPiece name,
current_->AddChild(node.release());
} else {
child->set_data(data);
+ child->set_is_placeholder(false);
}
}
diff --git a/src/google/protobuf/util/internal/default_value_objectwriter.h b/src/google/protobuf/util/internal/default_value_objectwriter.h
index ef2cc981..09c6d23f 100644
--- a/src/google/protobuf/util/internal/default_value_objectwriter.h
+++ b/src/google/protobuf/util/internal/default_value_objectwriter.h
@@ -172,7 +172,7 @@ class LIBPROTOBUF_EXPORT DefaultValueObjectWriter : public ObjectWriter {
// If this node is a leaf (has data), writes the current node to the
// ObjectWriter; if not, then recursively writes the children to the
// ObjectWriter.
- void WriteTo(ObjectWriter* ow);
+ virtual void WriteTo(ObjectWriter* ow);
// Accessors
const string& name() const { return name_; }
@@ -262,6 +262,10 @@ class LIBPROTOBUF_EXPORT DefaultValueObjectWriter : public ObjectWriter {
static DataPiece CreateDefaultDataPieceForField(
const google::protobuf::Field& field, const TypeInfo* typeinfo);
+ protected:
+ // Returns a pointer to current Node in tree.
+ Node* current() { return current_; }
+
private:
// Populates children of "node" if it is an "any" Node and its real type has
// been given.
diff --git a/src/google/protobuf/util/internal/protostream_objectsource.cc b/src/google/protobuf/util/internal/protostream_objectsource.cc
index 025fbd40..02360a1a 100644
--- a/src/google/protobuf/util/internal/protostream_objectsource.cc
+++ b/src/google/protobuf/util/internal/protostream_objectsource.cc
@@ -125,6 +125,7 @@ ProtoStreamObjectSource::ProtoStreamObjectSource(
recursion_depth_(0),
max_recursion_depth_(kDefaultMaxRecursionDepth),
render_unknown_fields_(false),
+ render_unknown_enum_values_(true),
add_trailing_zeros_for_timestamp_and_duration_(false) {
GOOGLE_LOG_IF(DFATAL, stream == NULL) << "Input stream is NULL.";
}
@@ -142,6 +143,7 @@ ProtoStreamObjectSource::ProtoStreamObjectSource(
recursion_depth_(0),
max_recursion_depth_(kDefaultMaxRecursionDepth),
render_unknown_fields_(false),
+ render_unknown_enum_values_(true),
add_trailing_zeros_for_timestamp_and_duration_(false) {
GOOGLE_LOG_IF(DFATAL, stream == NULL) << "Input stream is NULL.";
}
@@ -866,12 +868,6 @@ Status ProtoStreamObjectSource::RenderNonMessageField(
break;
}
- // No need to lookup enum type if we need to render int.
- if (use_ints_for_enums_) {
- ow->RenderInt32(field_name, buffer32);
- break;
- }
-
// Get the nested enum type for this field.
// TODO(skarvaje): Avoid string manipulation. Find ways to speed this
// up.
@@ -883,14 +879,17 @@ Status ProtoStreamObjectSource::RenderNonMessageField(
const google::protobuf::EnumValue* enum_value =
FindEnumValueByNumber(*en, buffer32);
if (enum_value != NULL) {
- if (use_lower_camel_for_enums_)
+ if (use_ints_for_enums_) {
+ ow->RenderInt32(field_name, buffer32);
+ } else if (use_lower_camel_for_enums_) {
ow->RenderString(field_name, ToCamelCase(enum_value->name()));
- else
+ } else {
ow->RenderString(field_name, enum_value->name());
- } else {
+ }
+ } else if (render_unknown_enum_values_) {
ow->RenderInt32(field_name, buffer32);
}
- } else {
+ } else if (render_unknown_enum_values_) {
ow->RenderInt32(field_name, buffer32);
}
break;
diff --git a/src/google/protobuf/util/internal/protostream_objectsource.h b/src/google/protobuf/util/internal/protostream_objectsource.h
index 58d77c2c..b56efdf4 100644
--- a/src/google/protobuf/util/internal/protostream_objectsource.h
+++ b/src/google/protobuf/util/internal/protostream_objectsource.h
@@ -309,6 +309,9 @@ class LIBPROTOBUF_EXPORT ProtoStreamObjectSource : public ObjectSource {
// Whether to render unknown fields.
bool render_unknown_fields_;
+ // Whether to render unknown enum values.
+ bool render_unknown_enum_values_;
+
// Whether to add trailing zeros for timestamp and duration.
bool add_trailing_zeros_for_timestamp_and_duration_;
diff --git a/src/google/protobuf/util/internal/protostream_objectsource_test.cc b/src/google/protobuf/util/internal/protostream_objectsource_test.cc
index 06c9bb6d..36bb1ba9 100644
--- a/src/google/protobuf/util/internal/protostream_objectsource_test.cc
+++ b/src/google/protobuf/util/internal/protostream_objectsource_test.cc
@@ -103,7 +103,8 @@ class ProtostreamObjectSourceTest
ow_(&mock_),
use_lower_camel_for_enums_(false),
use_ints_for_enums_(false),
- add_trailing_zeros_(false) {
+ add_trailing_zeros_(false),
+ render_unknown_enum_values_(true) {
helper_.ResetTypeInfo(Book::descriptor(), Proto3Message::descriptor());
}
@@ -276,6 +277,10 @@ class ProtostreamObjectSourceTest
void AddTrailingZeros() { add_trailing_zeros_ = true; }
+ void SetRenderUnknownEnumValues(bool value) {
+ render_unknown_enum_values_ = value;
+ }
+
testing::TypeInfoTestHelper helper_;
::testing::NiceMock<MockObjectWriter> mock_;
@@ -283,6 +288,7 @@ class ProtostreamObjectSourceTest
bool use_lower_camel_for_enums_;
bool use_ints_for_enums_;
bool add_trailing_zeros_;
+ bool render_unknown_enum_values_;
};
INSTANTIATE_TEST_CASE_P(DifferentTypeInfoSourceTest,
@@ -513,12 +519,27 @@ TEST_P(ProtostreamObjectSourceTest, UseIntsForEnumsTest) {
DoTest(book, Book::descriptor());
}
-TEST_P(ProtostreamObjectSourceTest, UnknownEnum) {
+TEST_P(ProtostreamObjectSourceTest,
+ UnknownEnumAreDroppedWhenRenderUnknownEnumValuesIsUnset) {
Proto3Message message;
message.set_enum_value(static_cast<Proto3Message::NestedEnum>(1234));
- ow_.StartObject("")
- ->RenderInt32("enumValue", 1234)
- ->EndObject();
+
+ SetRenderUnknownEnumValues(false);
+
+ // Unknown enum values are not output.
+ ow_.StartObject("")->EndObject();
+ DoTest(message, Proto3Message::descriptor());
+}
+
+TEST_P(ProtostreamObjectSourceTest,
+ UnknownEnumAreOutputWhenRenderUnknownEnumValuesIsSet) {
+ Proto3Message message;
+ message.set_enum_value(static_cast<Proto3Message::NestedEnum>(1234));
+
+ SetRenderUnknownEnumValues(true);
+
+ // Unknown enum values are output.
+ ow_.StartObject("")->RenderInt32("enumValue", 1234)->EndObject();
DoTest(message, Proto3Message::descriptor());
}
diff --git a/src/google/protobuf/util/internal/protostream_objectwriter.cc b/src/google/protobuf/util/internal/protostream_objectwriter.cc
index d4e15bca..97f96819 100644
--- a/src/google/protobuf/util/internal/protostream_objectwriter.cc
+++ b/src/google/protobuf/util/internal/protostream_objectwriter.cc
@@ -962,7 +962,7 @@ Status ProtoStreamObjectWriter::RenderFieldMask(ProtoStreamObjectWriter* ow,
// conversions as much as possible. Because ToSnakeCase sometimes returns the
// wrong value.
google::protobuf::scoped_ptr<ResultCallback1<util::Status, StringPiece> > callback(
- NewPermanentCallback(&RenderOneFieldPath, ow));
+ ::google::protobuf::NewPermanentCallback(&RenderOneFieldPath, ow));
return DecodeCompactFieldMaskPaths(data.str(), callback.get());
}
diff --git a/src/google/protobuf/util/internal/utility.cc b/src/google/protobuf/util/internal/utility.cc
index 8cf42e49..11780ee8 100644
--- a/src/google/protobuf/util/internal/utility.cc
+++ b/src/google/protobuf/util/internal/utility.cc
@@ -124,7 +124,10 @@ const StringPiece GetTypeWithoutUrl(StringPiece type_url) {
return type_url.substr(kTypeUrlSize + 1);
} else {
size_t idx = type_url.rfind('/');
- return type_url.substr(idx + 1);
+ if (idx != type_url.npos) {
+ type_url.remove_prefix(idx + 1);
+ }
+ return type_url;
}
}
diff --git a/src/google/protobuf/util/message_differencer.cc b/src/google/protobuf/util/message_differencer.cc
index 830850be..d62038ad 100644
--- a/src/google/protobuf/util/message_differencer.cc
+++ b/src/google/protobuf/util/message_differencer.cc
@@ -53,6 +53,7 @@
#include <google/protobuf/io/printer.h>
#include <google/protobuf/io/zero_copy_stream.h>
#include <google/protobuf/io/zero_copy_stream_impl.h>
+#include <google/protobuf/descriptor.pb.h>
#include <google/protobuf/dynamic_message.h>
#include <google/protobuf/text_format.h>
#include <google/protobuf/util/field_comparator.h>
@@ -150,6 +151,32 @@ class MessageDifferencer::MultipleFieldsMapKeyComparator
GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(MultipleFieldsMapKeyComparator);
};
+MessageDifferencer::MapEntryKeyComparator::MapEntryKeyComparator(
+ MessageDifferencer* message_differencer)
+ : message_differencer_(message_differencer) {}
+
+bool MessageDifferencer::MapEntryKeyComparator::IsMatch(
+ const Message& message1, const Message& message2,
+ const std::vector<SpecificField>& parent_fields) const {
+ // Map entry has its key in the field with tag 1. See the comment for
+ // map_entry in MessageOptions.
+ const FieldDescriptor* key = message1.GetDescriptor()->FindFieldByNumber(1);
+ // If key is not present in message1 and we're doing partial comparison or if
+ // map key is explicitly ignored treat the field as set instead,
+ const bool treat_as_set =
+ (message_differencer_->scope() == PARTIAL &&
+ !message1.GetReflection()->HasField(message1, key)) ||
+ message_differencer_->IsIgnored(message1, message2, key, parent_fields);
+
+ std::vector<SpecificField> current_parent_fields(parent_fields);
+ if (treat_as_set) {
+ return message_differencer_->Compare(message1, message2,
+ &current_parent_fields);
+ }
+ return message_differencer_->CompareFieldValueUsingParentFields(
+ message1, message2, key, -1, -1, &current_parent_fields);
+}
+
bool MessageDifferencer::Equals(const Message& message1,
const Message& message2) {
MessageDifferencer differencer;
@@ -191,8 +218,10 @@ MessageDifferencer::MessageDifferencer()
message_field_comparison_(EQUAL),
scope_(FULL),
repeated_field_comparison_(AS_LIST),
+ map_entry_key_comparator_(this),
report_matches_(false),
- output_string_(NULL) { }
+ report_moves_(true),
+ output_string_(NULL) {}
MessageDifferencer::~MessageDifferencer() {
for (int i = 0; i < owned_key_comparators_.size(); ++i) {
@@ -484,8 +513,22 @@ bool MessageDifferencer::Compare(
std::vector<const FieldDescriptor*> message2_fields;
message2_fields.reserve(1 + message2.GetDescriptor()->field_count());
- reflection1->ListFields(message1, &message1_fields);
- reflection2->ListFields(message2, &message2_fields);
+ if (descriptor1->options().map_entry()) {
+ if (scope_ == PARTIAL) {
+ reflection1->ListFields(message1, &message1_fields);
+ } else {
+ // Map entry fields are always considered present.
+ for (int i = 0; i < descriptor1->field_count(); i++) {
+ message1_fields.push_back(descriptor1->field(i));
+ }
+ }
+ for (int i = 0; i < descriptor1->field_count(); i++) {
+ message2_fields.push_back(descriptor1->field(i));
+ }
+ } else {
+ reflection1->ListFields(message1, &message1_fields);
+ reflection2->ListFields(message2, &message2_fields);
+ }
// Add sentinel values to deal with the
// case where the number of the fields in
@@ -783,6 +826,8 @@ bool MessageDifferencer::IsMatch(
reflection2->GetRepeatedMessage(*message2, repeated_field, index2);
SpecificField specific_field;
specific_field.field = repeated_field;
+ specific_field.index = index1;
+ specific_field.new_index = index2;
current_parent_fields.push_back(specific_field);
match = key_comparator->IsMatch(m1, m2, current_parent_fields);
}
@@ -854,7 +899,7 @@ bool MessageDifferencer::CompareRepeatedField(
fieldDifferent = true;
} else if (reporter_ != NULL &&
specific_field.index != specific_field.new_index &&
- !specific_field.field->is_map()) {
+ !specific_field.field->is_map() && report_moves_) {
parent_fields->push_back(specific_field);
reporter_->ReportMoved(message1, message2, *parent_fields);
parent_fields->pop_back();
@@ -945,6 +990,8 @@ bool MessageDifferencer::CompareFieldValueUsingParentFields(
bool MessageDifferencer::CheckPathChanged(
const std::vector<SpecificField>& field_path) {
for (int i = 0; i < field_path.size(); ++i) {
+ // Don't check indexes for map entries -- maps are unordered.
+ if (field_path[i].field != NULL && field_path[i].field->is_map()) continue;
if (field_path[i].index != field_path[i].new_index) return true;
}
return false;
@@ -952,7 +999,6 @@ bool MessageDifferencer::CheckPathChanged(
bool MessageDifferencer::IsTreatedAsSet(const FieldDescriptor* field) {
if (!field->is_repeated()) return false;
- if (field->is_map()) return true;
if (repeated_field_comparison_ == AS_SET)
return list_fields_.find(field) == list_fields_.end();
return (set_fields_.find(field) != set_fields_.end());
@@ -993,12 +1039,18 @@ bool MessageDifferencer::IsUnknownFieldIgnored(
return false;
}
-const MessageDifferencer::MapKeyComparator* MessageDifferencer
- ::GetMapKeyComparator(const FieldDescriptor* field) {
+const MessageDifferencer::MapKeyComparator*
+MessageDifferencer ::GetMapKeyComparator(const FieldDescriptor* field) const {
if (!field->is_repeated()) return NULL;
- if (map_field_key_comparator_.find(field) !=
- map_field_key_comparator_.end()) {
- return map_field_key_comparator_[field];
+ FieldKeyComparatorMap::const_iterator it =
+ map_field_key_comparator_.find(field);
+ if (it != map_field_key_comparator_.end()) {
+ return it->second;
+ }
+ if (field->is_map()) {
+ // field cannot already be treated as list or set since TreatAsList() and
+ // TreatAsSet() call GetMapKeyComparator() and fail if it returns non-NULL.
+ return &map_entry_key_comparator_;
}
return NULL;
}
@@ -1400,7 +1452,7 @@ bool MessageDifferencer::MatchRepeatedFieldIndices(
// algorithm will fail to find a maximum matching.
// Here we use the argumenting path algorithm.
MaximumMatcher::NodeMatchCallback* callback =
- NewPermanentCallback(
+ ::google::protobuf::NewPermanentCallback(
this, &MessageDifferencer::IsMatch,
repeated_field, key_comparator,
&message1, &message2, parent_fields);
@@ -1524,6 +1576,12 @@ void MessageDifferencer::StreamReporter::PrintPath(
}
}
+void MessageDifferencer::StreamReporter::PrintPath(
+ const std::vector<SpecificField>& field_path, bool left_side,
+ const Message& message) {
+ PrintPath(field_path, left_side);
+}
+
void MessageDifferencer::
StreamReporter::PrintValue(const Message& message,
const std::vector<SpecificField>& field_path,
@@ -1601,7 +1659,7 @@ void MessageDifferencer::StreamReporter::ReportAdded(
const Message& message2,
const std::vector<SpecificField>& field_path) {
printer_->Print("added: ");
- PrintPath(field_path, false);
+ PrintPath(field_path, false, message2);
printer_->Print(": ");
PrintValue(message2, field_path, false);
printer_->Print("\n"); // Print for newlines.
@@ -1612,7 +1670,7 @@ void MessageDifferencer::StreamReporter::ReportDeleted(
const Message& message2,
const std::vector<SpecificField>& field_path) {
printer_->Print("deleted: ");
- PrintPath(field_path, true);
+ PrintPath(field_path, true, message1);
printer_->Print(": ");
PrintValue(message1, field_path, true);
printer_->Print("\n"); // Print for newlines
@@ -1636,10 +1694,10 @@ void MessageDifferencer::StreamReporter::ReportModified(
}
printer_->Print("modified: ");
- PrintPath(field_path, true);
+ PrintPath(field_path, true, message1);
if (CheckPathChanged(field_path)) {
printer_->Print(" -> ");
- PrintPath(field_path, false);
+ PrintPath(field_path, false, message2);
}
printer_->Print(": ");
PrintValue(message1, field_path, true);
@@ -1653,9 +1711,9 @@ void MessageDifferencer::StreamReporter::ReportMoved(
const Message& message2,
const std::vector<SpecificField>& field_path) {
printer_->Print("moved: ");
- PrintPath(field_path, true);
+ PrintPath(field_path, true, message1);
printer_->Print(" -> ");
- PrintPath(field_path, false);
+ PrintPath(field_path, false, message2);
printer_->Print(" : ");
PrintValue(message1, field_path, true);
printer_->Print("\n"); // Print for newlines.
@@ -1666,10 +1724,10 @@ void MessageDifferencer::StreamReporter::ReportMatched(
const Message& message2,
const std::vector<SpecificField>& field_path) {
printer_->Print("matched: ");
- PrintPath(field_path, true);
+ PrintPath(field_path, true, message1);
if (CheckPathChanged(field_path)) {
printer_->Print(" -> ");
- PrintPath(field_path, false);
+ PrintPath(field_path, false, message2);
}
printer_->Print(" : ");
PrintValue(message1, field_path, true);
@@ -1681,10 +1739,10 @@ void MessageDifferencer::StreamReporter::ReportIgnored(
const Message& message2,
const std::vector<SpecificField>& field_path) {
printer_->Print("ignored: ");
- PrintPath(field_path, true);
+ PrintPath(field_path, true, message1);
if (CheckPathChanged(field_path)) {
printer_->Print(" -> ");
- PrintPath(field_path, false);
+ PrintPath(field_path, false, message2);
}
printer_->Print("\n"); // Print for newlines.
}
@@ -1693,10 +1751,10 @@ void MessageDifferencer::StreamReporter::ReportUnknownFieldIgnored(
const Message& message1, const Message& message2,
const std::vector<SpecificField>& field_path) {
printer_->Print("ignored: ");
- PrintPath(field_path, true);
+ PrintPath(field_path, true, message1);
if (CheckPathChanged(field_path)) {
printer_->Print(" -> ");
- PrintPath(field_path, false);
+ PrintPath(field_path, false, message2);
}
printer_->Print("\n"); // Print for newlines.
}
diff --git a/src/google/protobuf/util/message_differencer.h b/src/google/protobuf/util/message_differencer.h
index d99223cb..fdbf04e4 100644
--- a/src/google/protobuf/util/message_differencer.h
+++ b/src/google/protobuf/util/message_differencer.h
@@ -518,6 +518,13 @@ class LIBPROTOBUF_EXPORT MessageDifferencer {
report_matches_ = report_matches;
}
+ // Tells the differencer whether or not to report moves (in a set or map
+ // repeated field). This method must be called before Compare. The default for
+ // a new differencer is true.
+ void set_report_moves(bool report_moves) {
+ report_moves_ = report_moves;
+ }
+
// Sets the scope of the comparison (as defined in the Scope enumeration
// above) that is used by this differencer when determining which fields to
// compare between the messages.
@@ -620,6 +627,11 @@ class LIBPROTOBUF_EXPORT MessageDifferencer {
const std::vector<SpecificField>& field_path);
protected:
+ // Prints the specified path of fields to the buffer. message is used to
+ // print map keys.
+ virtual void PrintPath(const std::vector<SpecificField>& field_path,
+ bool left_side, const Message& message);
+
// Prints the specified path of fields to the buffer.
virtual void PrintPath(const std::vector<SpecificField>& field_path,
bool left_side);
@@ -653,6 +665,18 @@ class LIBPROTOBUF_EXPORT MessageDifferencer {
// relies on some private methods of MessageDifferencer. That's why this
// class is declared as a nested class of MessageDifferencer.
class MultipleFieldsMapKeyComparator;
+
+ // A MapKeyComparator for use with map_entries.
+ class LIBPROTOBUF_EXPORT MapEntryKeyComparator : public MapKeyComparator {
+ public:
+ explicit MapEntryKeyComparator(MessageDifferencer* message_differencer);
+ virtual bool IsMatch(const Message& message1, const Message& message2,
+ const std::vector<SpecificField>& parent_fields) const;
+
+ private:
+ MessageDifferencer* message_differencer_;
+ };
+
// Returns true if field1's number() is less than field2's.
static bool FieldBefore(const FieldDescriptor* field1,
const FieldDescriptor* field2);
@@ -765,9 +789,10 @@ class LIBPROTOBUF_EXPORT MessageDifferencer {
const SpecificField& field,
const std::vector<SpecificField>& parent_fields);
- // Returns MapKeyComparator* when this field has been configured to
- // be treated as a map. If not, returns NULL.
- const MapKeyComparator* GetMapKeyComparator(const FieldDescriptor* field);
+ // Returns MapKeyComparator* when this field has been configured to be treated
+ // as a map or its is_map() return true. If not, returns NULL.
+ const MapKeyComparator* GetMapKeyComparator(
+ const FieldDescriptor* field) const;
// Attempts to match indices of a repeated field, so that the contained values
// match. Clears output vectors and sets their values to indices of paired
@@ -817,11 +842,13 @@ class LIBPROTOBUF_EXPORT MessageDifferencer {
// MapKeyComparator is created for comparison purpose.
std::vector<MapKeyComparator*> owned_key_comparators_;
FieldKeyComparatorMap map_field_key_comparator_;
+ MapEntryKeyComparator map_entry_key_comparator_;
std::vector<IgnoreCriteria*> ignore_criteria_;
FieldSet ignored_fields_;
bool report_matches_;
+ bool report_moves_;
string* output_string_;
diff --git a/src/google/protobuf/util/message_differencer_unittest.cc b/src/google/protobuf/util/message_differencer_unittest.cc
index 850b3977..75cffd9f 100755
--- a/src/google/protobuf/util/message_differencer_unittest.cc
+++ b/src/google/protobuf/util/message_differencer_unittest.cc
@@ -1558,6 +1558,43 @@ TEST(MessageDifferencerTest, RepeatedFieldMapTest_CustomMapKeyComparator) {
EXPECT_EQ("ignored: item[0].ra\n", output);
}
+// Compares fields by their index offset by one, so index 0 matches with 1, etc.
+class OffsetByOneMapKeyComparator
+ : public util::MessageDifferencer::MapKeyComparator {
+ public:
+ typedef util::MessageDifferencer::SpecificField SpecificField;
+ virtual bool IsMatch(const Message& message1, const Message& message2,
+ const std::vector<SpecificField>& parent_fields) const {
+ return parent_fields.back().index + 1 == parent_fields.back().new_index;
+ }
+};
+
+TEST(MessageDifferencerTest, RepeatedFieldMapTest_CustomIndexMapKeyComparator) {
+ protobuf_unittest::TestDiffMessage msg1;
+ protobuf_unittest::TestDiffMessage msg2;
+ // Treat "item" as Map, using custom key comparator to determine if two
+ // elements have the same key.
+ protobuf_unittest::TestDiffMessage::Item* item = msg1.add_item();
+ item->set_b("one");
+ item = msg2.add_item();
+ item->set_b("zero");
+ item = msg2.add_item();
+ item->set_b("one");
+ util::MessageDifferencer differencer;
+ OffsetByOneMapKeyComparator key_comparator;
+ differencer.TreatAsMapUsingKeyComparator(GetFieldDescriptor(msg1, "item"),
+ &key_comparator);
+ string output;
+ differencer.ReportDifferencesToString(&output);
+ // With the offset by one comparator msg1.item[0] should be compared to
+ // msg2.item[1] and thus be moved, msg2.item[0] should be marked as added.
+ EXPECT_FALSE(differencer.Compare(msg1, msg2));
+ EXPECT_EQ(
+ "moved: item[0] -> item[1] : { b: \"one\" }\n"
+ "added: item[0]: { b: \"zero\" }\n",
+ output);
+}
+
TEST(MessageDifferencerTest, RepeatedFieldSetTest_Subset) {
protobuf_unittest::TestDiffMessage msg1;
protobuf_unittest::TestDiffMessage msg2;
@@ -2806,21 +2843,130 @@ TEST_F(ComparisonTest, EquivalentIgnoresUnknown) {
}
TEST_F(ComparisonTest, MapTest) {
- repeated_field_as_set();
+ Map<string, string>& map1 = *map_proto1_.mutable_map_string_string();
+ map1["key1"] = "1";
+ map1["key2"] = "2";
+ map1["key3"] = "3";
+ Map<string, string>& map2 = *map_proto2_.mutable_map_string_string();
+ map2["key3"] = "0";
+ map2["key2"] = "2";
+ map2["key1"] = "1";
+
+ EXPECT_EQ("modified: map_string_string.value: \"3\" -> \"0\"\n",
+ Run(map_proto1_, map_proto2_));
+}
+TEST_F(ComparisonTest, MapIgnoreKeyTest) {
Map<string, string>& map1 = *map_proto1_.mutable_map_string_string();
- map1["1"] = "1";
- map1["2"] = "2";
- map1["3"] = "3";
+ map1["key1"] = "1";
+ map1["key2"] = "2";
+ map1["key3"] = "3";
Map<string, string>& map2 = *map_proto2_.mutable_map_string_string();
- map2["3"] = "0";
- map2["2"] = "2";
- map2["1"] = "1";
+ map2["key4"] = "2";
+ map2["key5"] = "3";
+ map2["key6"] = "1";
- EXPECT_EQ(
- "added: map_string_string: { key: \"3\" value: \"0\" }\n"
- "deleted: map_string_string: { key: \"3\" value: \"3\" }\n",
- Run(map_proto1_, map_proto2_));
+ util::MessageDifferencer differencer;
+ differencer.IgnoreField(
+ GetFieldDescriptor(map_proto1_, "map_string_string.key"));
+ EXPECT_TRUE(differencer.Compare(map_proto1_, map_proto2_));
+}
+
+TEST_F(ComparisonTest, MapRoundTripSyncTest) {
+ google::protobuf::TextFormat::Parser parser;
+ unittest::TestMap map_reflection1;
+
+ // By setting via reflection, data exists in repeated field.
+ ASSERT_TRUE(parser.ParseFromString(
+ "map_int32_foreign_message { key: 1 }", &map_reflection1));
+
+ // During copy, data is synced from repeated field to map.
+ unittest::TestMap map_reflection2 = map_reflection1;
+
+ // During comparison, data is synced from map to repeated field.
+ EXPECT_TRUE(
+ util::MessageDifferencer::Equals(map_reflection1, map_reflection2));
+}
+
+TEST_F(ComparisonTest, MapEntryPartialTest) {
+ google::protobuf::TextFormat::Parser parser;
+ unittest::TestMap map1;
+ unittest::TestMap map2;
+
+ string output;
+ util::MessageDifferencer differencer;
+ differencer.set_scope(util::MessageDifferencer::PARTIAL);
+ differencer.ReportDifferencesToString(&output);
+
+ ASSERT_TRUE(parser.ParseFromString(
+ "map_int32_foreign_message { key: 1 value { c: 1 } }", &map1));
+ ASSERT_TRUE(parser.ParseFromString(
+ "map_int32_foreign_message { key: 1 value { c: 2 }}", &map2));
+ EXPECT_FALSE(differencer.Compare(map1, map2));
+ EXPECT_EQ("modified: map_int32_foreign_message.value.c: 1 -> 2\n", output);
+
+ ASSERT_TRUE(
+ parser.ParseFromString("map_int32_foreign_message { key: 1 }", &map1));
+ EXPECT_TRUE(differencer.Compare(map1, map2));
+}
+
+TEST_F(ComparisonTest, MapEntryPartialEmptyKeyTest) {
+ google::protobuf::TextFormat::Parser parser;
+ unittest::TestMap map1;
+ unittest::TestMap map2;
+ ASSERT_TRUE(parser.ParseFromString("map_int32_foreign_message {}", &map1));
+ ASSERT_TRUE(
+ parser.ParseFromString("map_int32_foreign_message { key: 1 }", &map2));
+
+ util::MessageDifferencer differencer;
+ differencer.set_scope(util::MessageDifferencer::PARTIAL);
+ EXPECT_TRUE(differencer.Compare(map1, map2));
+}
+
+// Considers strings keys as equal if they have equal lengths.
+class LengthMapKeyComparator
+ : public util::MessageDifferencer::MapKeyComparator {
+ public:
+ typedef util::MessageDifferencer::SpecificField SpecificField;
+ virtual bool IsMatch(const Message& message1, const Message& message2,
+ const std::vector<SpecificField>& parent_fields) const {
+ const Reflection* reflection1 = message1.GetReflection();
+ const Reflection* reflection2 = message2.GetReflection();
+ const FieldDescriptor* key_field =
+ message1.GetDescriptor()->FindFieldByName("key");
+ return reflection1->GetString(message1, key_field).size() ==
+ reflection2->GetString(message2, key_field).size();
+ }
+};
+
+TEST_F(ComparisonTest, MapEntryCustomMapKeyComparator) {
+ google::protobuf::TextFormat::Parser parser;
+ protobuf_unittest::TestMap msg1;
+ protobuf_unittest::TestMap msg2;
+
+ ASSERT_TRUE(parser.ParseFromString(
+ "map_string_foreign_message { key: 'key1' value { c: 1 }}", &msg1));
+ ASSERT_TRUE(parser.ParseFromString(
+ "map_string_foreign_message { key: 'key2' value { c: 1 }}", &msg2));
+
+ util::MessageDifferencer differencer;
+ LengthMapKeyComparator key_comparator;
+ differencer.TreatAsMapUsingKeyComparator(
+ GetFieldDescriptor(msg1, "map_string_foreign_message"), &key_comparator);
+ string output;
+ differencer.ReportDifferencesToString(&output);
+ // Though the above two messages have different keys for their map entries,
+ // they are considered the same by key_comparator because their lengths are
+ // equal. However, in value comparison, all fields of the message are taken
+ // into consideration, so they are reported as different.
+ EXPECT_FALSE(differencer.Compare(msg1, msg2));
+ EXPECT_EQ("modified: map_string_foreign_message.key: \"key1\" -> \"key2\"\n",
+ output);
+ differencer.IgnoreField(
+ GetFieldDescriptor(msg1, "map_string_foreign_message.key"));
+ output.clear();
+ EXPECT_TRUE(differencer.Compare(msg1, msg2));
+ EXPECT_EQ("ignored: map_string_foreign_message.key\n", output);
}
class MatchingTest : public testing::Test {