From e96ff30120a3834f7d1e31e43e591bf7cfbd731f Mon Sep 17 00:00:00 2001 From: Feng Xiao Date: Mon, 15 Jun 2015 18:21:48 -0700 Subject: Down-integrate from internal code base. [ci skip] Change-Id: I9391c09640e0b0b2b21c45a97a1fc91814d95c5d --- Makefile.am | 27 +- README.md | 4 +- autogen.sh | 23 +- cmake/README.md | 10 +- cmake/extract_includes.bat.in | 6 + cmake/libprotobuf-lite.cmake | 5 + cmake/libprotobuf.cmake | 17 + cmake/tests.cmake | 44 +- configure.ac | 6 +- src/Makefile.am | 289 +- src/google/protobuf/compiler/cpp/cpp_message.cc | 6 +- src/google/protobuf/stubs/bytestream.cc | 196 ++ src/google/protobuf/stubs/bytestream.h | 348 +++ src/google/protobuf/stubs/bytestream_unittest.cc | 146 + src/google/protobuf/stubs/casts.h | 10 + src/google/protobuf/stubs/common.cc | 13 + src/google/protobuf/stubs/common.h | 303 +- src/google/protobuf/stubs/mathlimits.cc | 144 + src/google/protobuf/stubs/mathlimits.h | 277 ++ src/google/protobuf/stubs/mathutil.h | 149 + src/google/protobuf/stubs/status.cc | 133 + src/google/protobuf/stubs/status.h | 116 + src/google/protobuf/stubs/status_macros.h | 89 + src/google/protobuf/stubs/status_test.cc | 131 + src/google/protobuf/stubs/statusor.cc | 46 + src/google/protobuf/stubs/statusor.h | 259 ++ src/google/protobuf/stubs/statusor_test.cc | 274 ++ src/google/protobuf/stubs/stringpiece.cc | 268 ++ src/google/protobuf/stubs/stringpiece.h | 437 +++ src/google/protobuf/stubs/stringpiece_unittest.cc | 793 +++++ src/google/protobuf/stubs/strutil.cc | 768 ++++- src/google/protobuf/stubs/strutil.h | 214 +- src/google/protobuf/stubs/strutil_unittest.cc | 736 ++++- src/google/protobuf/stubs/time.cc | 364 +++ src/google/protobuf/stubs/time.h | 75 + src/google/protobuf/stubs/time_test.cc | 208 ++ src/google/protobuf/text_format.cc | 4 +- src/google/protobuf/util/field_comparator.cc | 187 ++ src/google/protobuf/util/field_comparator.h | 259 ++ src/google/protobuf/util/field_comparator_test.cc | 483 +++ src/google/protobuf/util/internal/constants.h | 93 + src/google/protobuf/util/internal/datapiece.cc | 285 ++ src/google/protobuf/util/internal/datapiece.h | 212 ++ .../util/internal/default_value_objectwriter.cc | 515 ++++ .../util/internal/default_value_objectwriter.h | 238 ++ .../internal/default_value_objectwriter_test.cc | 139 + .../protobuf/util/internal/error_listener.cc | 42 + src/google/protobuf/util/internal/error_listener.h | 99 + .../util/internal/expecting_objectwriter.h | 238 ++ .../protobuf/util/internal/field_mask_utility.cc | 228 ++ .../protobuf/util/internal/field_mask_utility.h | 72 + src/google/protobuf/util/internal/json_escaping.cc | 403 +++ src/google/protobuf/util/internal/json_escaping.h | 91 + .../protobuf/util/internal/json_objectwriter.cc | 175 ++ .../protobuf/util/internal/json_objectwriter.h | 206 ++ .../util/internal/json_objectwriter_test.cc | 284 ++ .../protobuf/util/internal/json_stream_parser.cc | 740 +++++ .../protobuf/util/internal/json_stream_parser.h | 256 ++ .../util/internal/json_stream_parser_test.cc | 697 +++++ .../protobuf/util/internal/location_tracker.h | 65 + .../protobuf/util/internal/mock_error_listener.h | 63 + .../util/internal/object_location_tracker.h | 64 + src/google/protobuf/util/internal/object_source.h | 79 + src/google/protobuf/util/internal/object_writer.cc | 92 + src/google/protobuf/util/internal/object_writer.h | 126 + .../util/internal/protostream_objectsource.cc | 1051 +++++++ .../util/internal/protostream_objectsource.h | 245 ++ .../util/internal/protostream_objectsource_test.cc | 824 +++++ .../util/internal/protostream_objectwriter.cc | 1557 ++++++++++ .../util/internal/protostream_objectwriter.h | 455 +++ .../util/internal/protostream_objectwriter_test.cc | 1513 ++++++++++ .../util/internal/snake2camel_objectwriter.h | 187 ++ .../util/internal/snake2camel_objectwriter_test.cc | 311 ++ .../util/internal/structured_objectwriter.h | 118 + .../protobuf/util/internal/testdata/anys.proto | 53 + .../protobuf/util/internal/testdata/books.proto | 171 ++ .../util/internal/testdata/default_value.proto | 162 + .../internal/testdata/default_value_test.proto | 46 + .../util/internal/testdata/field_mask.proto | 71 + .../protobuf/util/internal/testdata/maps.proto | 57 + .../protobuf/util/internal/testdata/struct.proto | 45 + .../internal/testdata/timestamp_duration.proto | 47 + .../protobuf/util/internal/testdata/wrappers.proto | 100 + src/google/protobuf/util/internal/type_info.cc | 171 ++ src/google/protobuf/util/internal/type_info.h | 87 + .../util/internal/type_info_test_helper.cc | 130 + .../protobuf/util/internal/type_info_test_helper.h | 98 + src/google/protobuf/util/internal/utility.cc | 332 +++ src/google/protobuf/util/internal/utility.h | 187 ++ src/google/protobuf/util/json_format_proto3.proto | 157 + src/google/protobuf/util/json_util.cc | 142 + src/google/protobuf/util/json_util.h | 132 + src/google/protobuf/util/json_util_test.cc | 277 ++ src/google/protobuf/util/message_differencer.cc | 1629 ++++++++++ src/google/protobuf/util/message_differencer.h | 817 +++++ .../protobuf/util/message_differencer_unittest.cc | 3132 ++++++++++++++++++++ .../util/message_differencer_unittest.proto | 74 + src/google/protobuf/util/type_resolver.h | 75 + src/google/protobuf/util/type_resolver_util.cc | 212 ++ src/google/protobuf/util/type_resolver_util.h | 52 + .../protobuf/util/type_resolver_util_test.cc | 338 +++ 101 files changed, 28226 insertions(+), 198 deletions(-) create mode 100644 src/google/protobuf/stubs/bytestream.cc create mode 100644 src/google/protobuf/stubs/bytestream.h create mode 100644 src/google/protobuf/stubs/bytestream_unittest.cc create mode 100644 src/google/protobuf/stubs/mathlimits.cc create mode 100644 src/google/protobuf/stubs/mathlimits.h create mode 100644 src/google/protobuf/stubs/mathutil.h create mode 100644 src/google/protobuf/stubs/status.cc create mode 100644 src/google/protobuf/stubs/status.h create mode 100644 src/google/protobuf/stubs/status_macros.h create mode 100644 src/google/protobuf/stubs/status_test.cc create mode 100644 src/google/protobuf/stubs/statusor.cc create mode 100644 src/google/protobuf/stubs/statusor.h create mode 100644 src/google/protobuf/stubs/statusor_test.cc create mode 100644 src/google/protobuf/stubs/stringpiece.cc create mode 100644 src/google/protobuf/stubs/stringpiece.h create mode 100644 src/google/protobuf/stubs/stringpiece_unittest.cc create mode 100644 src/google/protobuf/stubs/time.cc create mode 100644 src/google/protobuf/stubs/time.h create mode 100644 src/google/protobuf/stubs/time_test.cc create mode 100644 src/google/protobuf/util/field_comparator.cc create mode 100644 src/google/protobuf/util/field_comparator.h create mode 100644 src/google/protobuf/util/field_comparator_test.cc create mode 100644 src/google/protobuf/util/internal/constants.h create mode 100644 src/google/protobuf/util/internal/datapiece.cc create mode 100644 src/google/protobuf/util/internal/datapiece.h create mode 100644 src/google/protobuf/util/internal/default_value_objectwriter.cc create mode 100644 src/google/protobuf/util/internal/default_value_objectwriter.h create mode 100644 src/google/protobuf/util/internal/default_value_objectwriter_test.cc create mode 100644 src/google/protobuf/util/internal/error_listener.cc create mode 100644 src/google/protobuf/util/internal/error_listener.h create mode 100644 src/google/protobuf/util/internal/expecting_objectwriter.h create mode 100644 src/google/protobuf/util/internal/field_mask_utility.cc create mode 100644 src/google/protobuf/util/internal/field_mask_utility.h create mode 100644 src/google/protobuf/util/internal/json_escaping.cc create mode 100644 src/google/protobuf/util/internal/json_escaping.h create mode 100644 src/google/protobuf/util/internal/json_objectwriter.cc create mode 100644 src/google/protobuf/util/internal/json_objectwriter.h create mode 100644 src/google/protobuf/util/internal/json_objectwriter_test.cc create mode 100644 src/google/protobuf/util/internal/json_stream_parser.cc create mode 100644 src/google/protobuf/util/internal/json_stream_parser.h create mode 100644 src/google/protobuf/util/internal/json_stream_parser_test.cc create mode 100644 src/google/protobuf/util/internal/location_tracker.h create mode 100644 src/google/protobuf/util/internal/mock_error_listener.h create mode 100644 src/google/protobuf/util/internal/object_location_tracker.h create mode 100644 src/google/protobuf/util/internal/object_source.h create mode 100644 src/google/protobuf/util/internal/object_writer.cc create mode 100644 src/google/protobuf/util/internal/object_writer.h create mode 100644 src/google/protobuf/util/internal/protostream_objectsource.cc create mode 100644 src/google/protobuf/util/internal/protostream_objectsource.h create mode 100644 src/google/protobuf/util/internal/protostream_objectsource_test.cc create mode 100644 src/google/protobuf/util/internal/protostream_objectwriter.cc create mode 100644 src/google/protobuf/util/internal/protostream_objectwriter.h create mode 100644 src/google/protobuf/util/internal/protostream_objectwriter_test.cc create mode 100644 src/google/protobuf/util/internal/snake2camel_objectwriter.h create mode 100644 src/google/protobuf/util/internal/snake2camel_objectwriter_test.cc create mode 100644 src/google/protobuf/util/internal/structured_objectwriter.h create mode 100644 src/google/protobuf/util/internal/testdata/anys.proto create mode 100644 src/google/protobuf/util/internal/testdata/books.proto create mode 100644 src/google/protobuf/util/internal/testdata/default_value.proto create mode 100644 src/google/protobuf/util/internal/testdata/default_value_test.proto create mode 100644 src/google/protobuf/util/internal/testdata/field_mask.proto create mode 100644 src/google/protobuf/util/internal/testdata/maps.proto create mode 100644 src/google/protobuf/util/internal/testdata/struct.proto create mode 100644 src/google/protobuf/util/internal/testdata/timestamp_duration.proto create mode 100644 src/google/protobuf/util/internal/testdata/wrappers.proto create mode 100644 src/google/protobuf/util/internal/type_info.cc create mode 100644 src/google/protobuf/util/internal/type_info.h create mode 100644 src/google/protobuf/util/internal/type_info_test_helper.cc create mode 100644 src/google/protobuf/util/internal/type_info_test_helper.h create mode 100644 src/google/protobuf/util/internal/utility.cc create mode 100644 src/google/protobuf/util/internal/utility.h create mode 100644 src/google/protobuf/util/json_format_proto3.proto create mode 100644 src/google/protobuf/util/json_util.cc create mode 100644 src/google/protobuf/util/json_util.h create mode 100644 src/google/protobuf/util/json_util_test.cc create mode 100644 src/google/protobuf/util/message_differencer.cc create mode 100644 src/google/protobuf/util/message_differencer.h create mode 100755 src/google/protobuf/util/message_differencer_unittest.cc create mode 100644 src/google/protobuf/util/message_differencer_unittest.proto create mode 100644 src/google/protobuf/util/type_resolver.h create mode 100644 src/google/protobuf/util/type_resolver_util.cc create mode 100644 src/google/protobuf/util/type_resolver_util.h create mode 100644 src/google/protobuf/util/type_resolver_util_test.cc diff --git a/Makefile.am b/Makefile.am index 0eb80202..b506ef33 100644 --- a/Makefile.am +++ b/Makefile.am @@ -8,28 +8,29 @@ AUTOMAKE_OPTIONS = foreign # the right time. SUBDIRS = . src -# Always include gtest in distributions. +# Always include gmock in distributions. DIST_SUBDIRS = $(subdirs) src conformance -# Build gtest before we build protobuf tests. We don't add gtest to SUBDIRS -# because then "make check" would also build and run all of gtest's own tests, +# Build gmock before we build protobuf tests. We don't add gmock to SUBDIRS +# because then "make check" would also build and run all of gmock's own tests, # which takes a lot of time and is generally not useful to us. Also, we don't -# want "make install" to recurse into gtest since we don't want to overwrite -# the installed version of gtest if there is one. +# want "make install" to recurse into gmock since we don't want to overwrite +# the installed version of gmock if there is one. check-local: - @echo "Making lib/libgtest.a lib/libgtest_main.a in gtest" - @cd gtest && $(MAKE) $(AM_MAKEFLAGS) lib/libgtest.la lib/libgtest_main.la + @echo "Making lib/libgmock.a lib/libgmock_main.a in gmock" + @cd gmock && $(MAKE) $(AM_MAKEFLAGS) lib/libgmock.la lib/libgmock_main.la + @cd gmock/gtest && $(MAKE) $(AM_MAKEFLAGS) lib/libgtest.la lib/libgtest_main.la -# We would like to clean gtest when "make clean" is invoked. But we have to +# We would like to clean gmock when "make clean" is invoked. But we have to # be careful because clean-local is also invoked during "make distclean", but -# "make distclean" already recurses into gtest because it's listed among the -# DIST_SUBDIRS. distclean will delete gtest/Makefile, so if we then try to +# "make distclean" already recurses into gmock because it's listed among the +# DIST_SUBDIRS. distclean will delete gmock/Makefile, so if we then try to # cd to the directory again and "make clean" it will fail. So, check that the # Makefile exists before recursing. clean-local: - @if test -e gtest/Makefile; then \ - echo "Making clean in gtest"; \ - cd gtest && $(MAKE) $(AM_MAKEFLAGS) clean; \ + @if test -e gmock/Makefile; then \ + echo "Making clean in gmock"; \ + cd gmock && $(MAKE) $(AM_MAKEFLAGS) clean; \ fi; \ if test -e conformance/Makefile; then \ echo "Making clean in conformance"; \ diff --git a/README.md b/README.md index 51377ad7..a974d301 100644 --- a/README.md +++ b/README.md @@ -15,12 +15,12 @@ first: $ ./autogen.sh -This will download gtest source (which is used for C++ Protocol Buffer +This will download gmock source (which is used for C++ Protocol Buffer unit-tests) to the current directory and run automake, autoconf, etc. to generate the configure script and various template makefiles. You can skip this step if you are using a release package (which already -contains gtest and the configure script). +contains gmock and the configure script). To build and install the C++ Protocol Buffer runtime and the Protocol Buffer compiler (protoc) execute the following: diff --git a/autogen.sh b/autogen.sh index 08966c63..8160313e 100755 --- a/autogen.sh +++ b/autogen.sh @@ -15,27 +15,18 @@ __EOF__ exit 1 fi -# Check that gtest is present. Usually it is already there since the +# Check that gmock is present. Usually it is already there since the # directory is set up as an SVN external. -if test ! -e gtest; then - echo "Google Test not present. Fetching gtest-1.7.0 from the web..." - curl -O https://googletest.googlecode.com/files/gtest-1.7.0.zip - unzip -q gtest-1.7.0.zip - rm gtest-1.7.0.zip - mv gtest-1.7.0 gtest +if test ! -e gmock; then + echo "Google Mock not present. Fetching gmock-1.7.0 from the web..." + curl -O https://googlemock.googlecode.com/files/gmock-1.7.0.zip + unzip -q gmock-1.7.0.zip + rm gmock-1.7.0.zip + mv gmock-1.7.0 gmock fi set -ex -# Temporary hack: Must change C runtime library to "multi-threaded DLL", -# otherwise it will be set to "multi-threaded static" when MSVC upgrades -# the project file to MSVC 2005/2008. vladl of Google Test says gtest will -# probably change their default to match, then this will be unnecessary. -# One of these mappings converts the debug configuration and the other -# converts the release configuration. I don't know which is which. -sed -i -e 's/RuntimeLibrary="5"/RuntimeLibrary="3"/g; - s/RuntimeLibrary="4"/RuntimeLibrary="2"/g;' gtest/msvc/*.vcproj - # TODO(kenton): Remove the ",no-obsolete" part and fix the resulting warnings. autoreconf -f -i -Wall,no-obsolete diff --git a/cmake/README.md b/cmake/README.md index 1d5c8bc1..0abe078e 100644 --- a/cmake/README.md +++ b/cmake/README.md @@ -5,17 +5,17 @@ on your computer before proceeding. Compiling and Installing ======================== -1. Check whether a gtest directory exists in the upper level directory. If you - checkout the code from github via "git clone", this gtest directory won't +1. Check whether a gmock directory exists in the upper level directory. If you + checkout the code from github via "git clone", this gmock directory won't exist and you won't be able to build protobuf unit-tests. Consider using one of the release tar balls instead: https://github.com/google/protobuf/releases These release tar balls are more stable versions of protobuf and already - have the gtest directory included. + have the gmock directory included. - You can also download gtest by yourself and put it in the right place. + You can also download gmock by yourself and put it in the right place. If you absolutely don't want to build and run protobuf unit-tests, skip this step and use protobuf at your own risk. @@ -29,7 +29,7 @@ Compiling and Installing $ cd build $ cmake -G "Visual Studio 9 2008" .. - If you don't have gtest, skip the build of tests by turning off the + If you don't have gmock, skip the build of tests by turning off the BUILD_TESTING option: $ cmake -G "Visutal Studio 9 2008" -DBUILD_TESTING=OFF .. diff --git a/cmake/extract_includes.bat.in b/cmake/extract_includes.bat.in index b2e9444d..d0c6dbfb 100644 --- a/cmake/extract_includes.bat.in +++ b/cmake/extract_includes.bat.in @@ -11,6 +11,7 @@ mkdir include\google\protobuf\compiler\python mkdir include\google\protobuf\compiler\ruby mkdir include\google\protobuf\io mkdir include\google\protobuf\stubs +mkdir include\google\protobuf\util copy ${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\any.h include\google\protobuf\any.h copy ${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\any.pb.h include\google\protobuf\any.pb.h copy ${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\api.pb.h include\google\protobuf\api.pb.h @@ -98,6 +99,11 @@ copy ${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\text_format.h include\ copy ${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\timestamp.pb.h include\google\protobuf\timestamp.pb.h copy ${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\type.pb.h include\google\protobuf\type.pb.h copy ${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\unknown_field_set.h include\google\protobuf\unknown_field_set.h +copy ${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\util\field_comparator.h include\google\protobuf\util\field_comparator.h +copy ${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\util\json_util.h include\google\protobuf\util\json_util.h +copy ${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\util\message_differencer.h include\google\protobuf\util\message_differencer.h +copy ${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\util\type_resolver.h include\google\protobuf\util\type_resolver.h +copy ${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\util\type_resolver_util.h include\google\protobuf\util\type_resolver_util.h copy ${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\wire_format.h include\google\protobuf\wire_format.h copy ${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\wire_format_lite.h include\google\protobuf\wire_format_lite.h copy ${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\wire_format_lite_inl.h include\google\protobuf\wire_format_lite_inl.h diff --git a/cmake/libprotobuf-lite.cmake b/cmake/libprotobuf-lite.cmake index 32c2d026..8d995df4 100644 --- a/cmake/libprotobuf-lite.cmake +++ b/cmake/libprotobuf-lite.cmake @@ -10,9 +10,14 @@ set(libprotobuf_lite_files ${protobuf_source_dir}/src/google/protobuf/repeated_field.cc ${protobuf_source_dir}/src/google/protobuf/stubs/atomicops_internals_x86_gcc.cc ${protobuf_source_dir}/src/google/protobuf/stubs/atomicops_internals_x86_msvc.cc + ${protobuf_source_dir}/src/google/protobuf/stubs/bytestream.cc ${protobuf_source_dir}/src/google/protobuf/stubs/common.cc ${protobuf_source_dir}/src/google/protobuf/stubs/once.cc + ${protobuf_source_dir}/src/google/protobuf/stubs/status.cc + ${protobuf_source_dir}/src/google/protobuf/stubs/statusor.cc + ${protobuf_source_dir}/src/google/protobuf/stubs/stringpiece.cc ${protobuf_source_dir}/src/google/protobuf/stubs/stringprintf.cc + ${protobuf_source_dir}/src/google/protobuf/stubs/time.cc ${protobuf_source_dir}/src/google/protobuf/wire_format_lite.cc ) diff --git a/cmake/libprotobuf.cmake b/cmake/libprotobuf.cmake index b1f2dc3e..850d93a7 100644 --- a/cmake/libprotobuf.cmake +++ b/cmake/libprotobuf.cmake @@ -24,6 +24,7 @@ set(libprotobuf_files ${protobuf_source_dir}/src/google/protobuf/service.cc ${protobuf_source_dir}/src/google/protobuf/source_context.pb.cc ${protobuf_source_dir}/src/google/protobuf/struct.pb.cc + ${protobuf_source_dir}/src/google/protobuf/stubs/mathlimits.cc ${protobuf_source_dir}/src/google/protobuf/stubs/structurally_valid.cc ${protobuf_source_dir}/src/google/protobuf/stubs/strutil.cc ${protobuf_source_dir}/src/google/protobuf/stubs/substitute.cc @@ -31,6 +32,22 @@ set(libprotobuf_files ${protobuf_source_dir}/src/google/protobuf/timestamp.pb.cc ${protobuf_source_dir}/src/google/protobuf/type.pb.cc ${protobuf_source_dir}/src/google/protobuf/unknown_field_set.cc + ${protobuf_source_dir}/src/google/protobuf/util/field_comparator.cc + ${protobuf_source_dir}/src/google/protobuf/util/internal/datapiece.cc + ${protobuf_source_dir}/src/google/protobuf/util/internal/default_value_objectwriter.cc + ${protobuf_source_dir}/src/google/protobuf/util/internal/error_listener.cc + ${protobuf_source_dir}/src/google/protobuf/util/internal/field_mask_utility.cc + ${protobuf_source_dir}/src/google/protobuf/util/internal/json_escaping.cc + ${protobuf_source_dir}/src/google/protobuf/util/internal/json_objectwriter.cc + ${protobuf_source_dir}/src/google/protobuf/util/internal/json_stream_parser.cc + ${protobuf_source_dir}/src/google/protobuf/util/internal/object_writer.cc + ${protobuf_source_dir}/src/google/protobuf/util/internal/protostream_objectsource.cc + ${protobuf_source_dir}/src/google/protobuf/util/internal/protostream_objectwriter.cc + ${protobuf_source_dir}/src/google/protobuf/util/internal/type_info.cc + ${protobuf_source_dir}/src/google/protobuf/util/internal/utility.cc + ${protobuf_source_dir}/src/google/protobuf/util/json_util.cc + ${protobuf_source_dir}/src/google/protobuf/util/message_differencer.cc + ${protobuf_source_dir}/src/google/protobuf/util/type_resolver_util.cc ${protobuf_source_dir}/src/google/protobuf/wire_format.cc ${protobuf_source_dir}/src/google/protobuf/wrappers.pb.cc ) diff --git a/cmake/tests.cmake b/cmake/tests.cmake index 8fb5eef1..4396a657 100644 --- a/cmake/tests.cmake +++ b/cmake/tests.cmake @@ -1,10 +1,13 @@ -include_directories( - ${protobuf_source_dir}/gtest/include - ${protobuf_source_dir}/gtest) +if (NOT EXISTS "${PROJECT_SOURCE_DIR}/../gmock/CMakeLists.txt") + message(FATAL_ERROR "Cannot find gmock directory.") +endif() + +add_subdirectory(../gmock ${PROJECT_BINARY_DIR}/gmock) -add_library(gtest STATIC ${protobuf_source_dir}/gtest/src/gtest-all.cc) -add_library(gtest_main STATIC ${protobuf_source_dir}/gtest/src/gtest_main.cc) -target_link_libraries(gtest_main gtest) +include_directories( + ${protobuf_source_dir}/gmock/gtest/include + ${protobuf_source_dir}/gmock/include +) set(lite_test_protos google/protobuf/map_lite_unittest.proto @@ -39,6 +42,15 @@ set(tests_protos google/protobuf/unittest_preserve_unknown_enum2.proto google/protobuf/unittest_proto3_arena.proto google/protobuf/unittest_well_known_types.proto + google/protobuf/util/internal/testdata/anys.proto + google/protobuf/util/internal/testdata/books.proto + google/protobuf/util/internal/testdata/default_value.proto + google/protobuf/util/internal/testdata/default_value_test.proto + google/protobuf/util/internal/testdata/field_mask.proto + google/protobuf/util/internal/testdata/maps.proto + google/protobuf/util/internal/testdata/struct.proto + google/protobuf/util/internal/testdata/timestamp_duration.proto + google/protobuf/util/json_format_proto3.proto ) macro(compile_proto_file filename) @@ -46,10 +58,10 @@ macro(compile_proto_file filename) get_filename_component(basename ${filename} NAME_WE) add_custom_command( OUTPUT ${protobuf_source_dir}/src/${dirname}/${basename}.pb.cc + DEPENDS protoc ${protobuf_source_dir}/src/${dirname}/${basename}.proto COMMAND protoc ${protobuf_source_dir}/src/${dirname}/${basename}.proto --proto_path=${protobuf_source_dir}/src --cpp_out=${protobuf_source_dir}/src - DEPENDS protoc ) endmacro(compile_proto_file) @@ -113,21 +125,35 @@ set(tests_files ${protobuf_source_dir}/src/google/protobuf/reflection_ops_unittest.cc ${protobuf_source_dir}/src/google/protobuf/repeated_field_reflection_unittest.cc ${protobuf_source_dir}/src/google/protobuf/repeated_field_unittest.cc + ${protobuf_source_dir}/src/google/protobuf/stubs/bytestream_unittest.cc ${protobuf_source_dir}/src/google/protobuf/stubs/common_unittest.cc ${protobuf_source_dir}/src/google/protobuf/stubs/once_unittest.cc + ${protobuf_source_dir}/src/google/protobuf/stubs/status_test.cc + ${protobuf_source_dir}/src/google/protobuf/stubs/statusor_test.cc + ${protobuf_source_dir}/src/google/protobuf/stubs/stringpiece_unittest.cc ${protobuf_source_dir}/src/google/protobuf/stubs/stringprintf_unittest.cc ${protobuf_source_dir}/src/google/protobuf/stubs/structurally_valid_unittest.cc ${protobuf_source_dir}/src/google/protobuf/stubs/strutil_unittest.cc ${protobuf_source_dir}/src/google/protobuf/stubs/template_util_unittest.cc + ${protobuf_source_dir}/src/google/protobuf/stubs/time_test.cc ${protobuf_source_dir}/src/google/protobuf/stubs/type_traits_unittest.cc ${protobuf_source_dir}/src/google/protobuf/text_format_unittest.cc ${protobuf_source_dir}/src/google/protobuf/unknown_field_set_unittest.cc + ${protobuf_source_dir}/src/google/protobuf/util/field_comparator_test.cc + ${protobuf_source_dir}/src/google/protobuf/util/internal/default_value_objectwriter_test.cc + ${protobuf_source_dir}/src/google/protobuf/util/internal/json_objectwriter_test.cc + ${protobuf_source_dir}/src/google/protobuf/util/internal/json_stream_parser_test.cc + ${protobuf_source_dir}/src/google/protobuf/util/internal/protostream_objectsource_test.cc + ${protobuf_source_dir}/src/google/protobuf/util/internal/protostream_objectwriter_test.cc + ${protobuf_source_dir}/src/google/protobuf/util/internal/type_info_test_helper.cc + ${protobuf_source_dir}/src/google/protobuf/util/json_util_test.cc + ${protobuf_source_dir}/src/google/protobuf/util/type_resolver_util_test.cc ${protobuf_source_dir}/src/google/protobuf/well_known_types_unittest.cc ${protobuf_source_dir}/src/google/protobuf/wire_format_unittest.cc ) add_executable(tests ${tests_files} ${common_test_files} ${tests_proto_files} ${lite_test_proto_files}) -target_link_libraries(tests libprotoc libprotobuf gtest_main) +target_link_libraries(tests libprotoc libprotobuf gmock_main) set(test_plugin_files ${protobuf_source_dir}/src/google/protobuf/compiler/mock_code_generator.cc @@ -137,7 +163,7 @@ set(test_plugin_files ) add_executable(test_plugin ${test_plugin_files}) -target_link_libraries(test_plugin libprotoc libprotobuf gtest) +target_link_libraries(test_plugin libprotoc libprotobuf gmock) set(lite_test_files ${protobuf_source_dir}/src/google/protobuf/arena_test_util.cc diff --git a/configure.ac b/configure.ac index 8018cc75..764e5d43 100644 --- a/configure.ac +++ b/configure.ac @@ -157,12 +157,12 @@ case "$target_os" in ;; esac -# HACK: Make gtest's configure script pick up our copy of CFLAGS and CXXFLAGS, -# since the flags added by ACX_CHECK_SUNCC must be used when compiling gtest +# HACK: Make gmock's configure script pick up our copy of CFLAGS and CXXFLAGS, +# since the flags added by ACX_CHECK_SUNCC must be used when compiling gmock # too. export CFLAGS export CXXFLAGS -AC_CONFIG_SUBDIRS([gtest]) +AC_CONFIG_SUBDIRS([gmock]) AC_CONFIG_FILES([Makefile src/Makefile conformance/Makefile protobuf.pc protobuf-lite.pc]) AC_OUTPUT diff --git a/src/Makefile.am b/src/Makefile.am index ac6ec6f4..de6a88e4 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -139,7 +139,12 @@ nobase_include_HEADERS = \ google/protobuf/compiler/objectivec/objectivec_helpers.h \ google/protobuf/compiler/python/python_generator.h \ google/protobuf/compiler/ruby/ruby_generator.h \ - google/protobuf/compiler/csharp/csharp_generator.h + google/protobuf/compiler/csharp/csharp_generator.h \ + google/protobuf/util/type_resolver.h \ + google/protobuf/util/type_resolver_util.h \ + google/protobuf/util/json_util.h \ + google/protobuf/util/field_comparator.h \ + google/protobuf/util/message_differencer.h lib_LTLIBRARIES = libprotobuf-lite.la libprotobuf.la libprotoc.la @@ -148,13 +153,25 @@ libprotobuf_lite_la_LDFLAGS = -version-info 10:0:0 -export-dynamic -no-undefined libprotobuf_lite_la_SOURCES = \ google/protobuf/stubs/atomicops_internals_x86_gcc.cc \ google/protobuf/stubs/atomicops_internals_x86_msvc.cc \ + google/protobuf/stubs/bytestream.cc \ + google/protobuf/stubs/bytestream.h \ google/protobuf/stubs/common.cc \ - google/protobuf/stubs/once.cc \ google/protobuf/stubs/hash.h \ google/protobuf/stubs/map_util.h \ + google/protobuf/stubs/mathutil.h \ + google/protobuf/stubs/once.cc \ google/protobuf/stubs/shared_ptr.h \ + google/protobuf/stubs/status.cc \ + google/protobuf/stubs/status.h \ + google/protobuf/stubs/status_macros.h \ + google/protobuf/stubs/statusor.cc \ + google/protobuf/stubs/statusor.h \ + google/protobuf/stubs/stringpiece.cc \ + google/protobuf/stubs/stringpiece.h \ google/protobuf/stubs/stringprintf.cc \ google/protobuf/stubs/stringprintf.h \ + google/protobuf/stubs/time.cc \ + google/protobuf/stubs/time.h \ google/protobuf/arena.cc \ google/protobuf/arenastring.cc \ google/protobuf/extension_set.cc \ @@ -173,6 +190,8 @@ libprotobuf_la_SOURCES = \ $(libprotobuf_lite_la_SOURCES) \ google/protobuf/any.pb.cc \ google/protobuf/api.pb.cc \ + google/protobuf/stubs/mathlimits.h \ + google/protobuf/stubs/mathlimits.cc \ google/protobuf/any.cc \ google/protobuf/descriptor.cc \ google/protobuf/descriptor_database.cc \ @@ -207,7 +226,39 @@ libprotobuf_la_SOURCES = \ google/protobuf/io/tokenizer.cc \ google/protobuf/io/zero_copy_stream_impl.cc \ google/protobuf/compiler/importer.cc \ - google/protobuf/compiler/parser.cc + google/protobuf/compiler/parser.cc \ + google/protobuf/util/field_comparator.cc \ + google/protobuf/util/internal/constants.h \ + google/protobuf/util/internal/datapiece.cc \ + google/protobuf/util/internal/datapiece.h \ + google/protobuf/util/internal/default_value_objectwriter.cc \ + google/protobuf/util/internal/default_value_objectwriter.h \ + google/protobuf/util/internal/error_listener.cc \ + google/protobuf/util/internal/error_listener.h \ + google/protobuf/util/internal/field_mask_utility.cc \ + google/protobuf/util/internal/field_mask_utility.h \ + google/protobuf/util/internal/json_escaping.cc \ + google/protobuf/util/internal/json_escaping.h \ + google/protobuf/util/internal/json_objectwriter.cc \ + google/protobuf/util/internal/json_objectwriter.h \ + google/protobuf/util/internal/json_stream_parser.cc \ + google/protobuf/util/internal/json_stream_parser.h \ + google/protobuf/util/internal/location_tracker.h \ + google/protobuf/util/internal/object_source.h \ + google/protobuf/util/internal/object_writer.cc \ + google/protobuf/util/internal/object_writer.h \ + google/protobuf/util/internal/object_writer.h \ + google/protobuf/util/internal/protostream_objectsource.cc \ + google/protobuf/util/internal/protostream_objectsource.h \ + google/protobuf/util/internal/protostream_objectwriter.cc \ + google/protobuf/util/internal/protostream_objectwriter.h \ + google/protobuf/util/internal/type_info.cc \ + google/protobuf/util/internal/type_info.h \ + google/protobuf/util/internal/utility.cc \ + google/protobuf/util/internal/utility.h \ + google/protobuf/util/json_util.cc \ + google/protobuf/util/type_resolver_util.cc \ + google/protobuf/util/message_differencer.cc nodist_libprotobuf_la_SOURCES = $(nodist_libprotobuf_lite_la_SOURCES) libprotoc_la_LIBADD = $(PTHREAD_LIBS) libprotobuf.la @@ -386,35 +437,44 @@ protoc_SOURCES = google/protobuf/compiler/main.cc # Tests ============================================================== -protoc_inputs = \ - google/protobuf/any_test.proto \ - google/protobuf/map_lite_unittest.proto \ - google/protobuf/map_proto2_unittest.proto \ - google/protobuf/map_unittest.proto \ - google/protobuf/unittest.proto \ - google/protobuf/unittest_arena.proto \ - google/protobuf/unittest_custom_options.proto \ - google/protobuf/unittest_drop_unknown_fields.proto \ - google/protobuf/unittest_embed_optimize_for.proto \ - google/protobuf/unittest_empty.proto \ - google/protobuf/unittest_enormous_descriptor.proto \ - google/protobuf/unittest_import_lite.proto \ - google/protobuf/unittest_import.proto \ - google/protobuf/unittest_import_public_lite.proto \ - google/protobuf/unittest_import_public.proto \ - google/protobuf/unittest_lite_imports_nonlite.proto \ - google/protobuf/unittest_lite.proto \ - google/protobuf/unittest_mset.proto \ - google/protobuf/unittest_no_arena_import.proto \ - google/protobuf/unittest_no_arena.proto \ - google/protobuf/unittest_no_field_presence.proto \ - google/protobuf/unittest_no_generic_services.proto \ - google/protobuf/unittest_optimize_for.proto \ - google/protobuf/unittest_preserve_unknown_enum.proto \ - google/protobuf/unittest_preserve_unknown_enum2.proto \ - google/protobuf/unittest_proto3_arena.proto \ - google/protobuf/unittest_well_known_types.proto \ - google/protobuf/compiler/cpp/cpp_test_bad_identifiers.proto \ +protoc_inputs = \ + google/protobuf/any_test.proto \ + google/protobuf/compiler/cpp/cpp_test_bad_identifiers.proto \ + google/protobuf/map_lite_unittest.proto \ + google/protobuf/map_proto2_unittest.proto \ + google/protobuf/map_unittest.proto \ + google/protobuf/unittest_arena.proto \ + google/protobuf/unittest_custom_options.proto \ + google/protobuf/unittest_drop_unknown_fields.proto \ + google/protobuf/unittest_embed_optimize_for.proto \ + google/protobuf/unittest_empty.proto \ + google/protobuf/unittest_enormous_descriptor.proto \ + google/protobuf/unittest_import_lite.proto \ + google/protobuf/unittest_import.proto \ + google/protobuf/unittest_import_public_lite.proto \ + google/protobuf/unittest_import_public.proto \ + google/protobuf/unittest_lite_imports_nonlite.proto \ + google/protobuf/unittest_lite.proto \ + google/protobuf/unittest_mset.proto \ + google/protobuf/unittest_no_arena_import.proto \ + google/protobuf/unittest_no_arena.proto \ + google/protobuf/unittest_no_field_presence.proto \ + google/protobuf/unittest_no_generic_services.proto \ + google/protobuf/unittest_optimize_for.proto \ + google/protobuf/unittest_preserve_unknown_enum2.proto \ + google/protobuf/unittest_preserve_unknown_enum.proto \ + google/protobuf/unittest.proto \ + google/protobuf/unittest_proto3_arena.proto \ + google/protobuf/unittest_well_known_types.proto \ + google/protobuf/util/internal/testdata/anys.proto \ + google/protobuf/util/internal/testdata/books.proto \ + google/protobuf/util/internal/testdata/default_value.proto \ + google/protobuf/util/internal/testdata/default_value_test.proto \ + google/protobuf/util/internal/testdata/field_mask.proto \ + google/protobuf/util/internal/testdata/maps.proto \ + google/protobuf/util/internal/testdata/struct.proto \ + google/protobuf/util/internal/testdata/timestamp_duration.proto \ + google/protobuf/util/json_format_proto3.proto \ google/protobuf/compiler/cpp/cpp_test_large_enum_value.proto EXTRA_DIST = \ @@ -451,58 +511,76 @@ protoc_lite_outputs = \ google/protobuf/unittest_import_public_lite.pb.cc \ google/protobuf/unittest_import_public_lite.pb.h -protoc_outputs = \ - $(protoc_lite_outputs) \ - google/protobuf/any_test.pb.cc \ - google/protobuf/any_test.pb.h \ - google/protobuf/map_proto2_unittest.pb.cc \ - google/protobuf/map_proto2_unittest.pb.h \ - google/protobuf/map_unittest.pb.cc \ - google/protobuf/map_unittest.pb.h \ - google/protobuf/unittest.pb.cc \ - google/protobuf/unittest.pb.h \ - google/protobuf/unittest_arena.pb.cc \ - google/protobuf/unittest_arena.pb.h \ - google/protobuf/unittest_custom_options.pb.cc \ - google/protobuf/unittest_custom_options.pb.h \ - google/protobuf/unittest_drop_unknown_fields.pb.cc \ - google/protobuf/unittest_drop_unknown_fields.pb.h \ - google/protobuf/unittest_embed_optimize_for.pb.cc \ - google/protobuf/unittest_embed_optimize_for.pb.h \ - google/protobuf/unittest_empty.pb.cc \ - google/protobuf/unittest_empty.pb.h \ - google/protobuf/unittest_enormous_descriptor.pb.cc \ - google/protobuf/unittest_enormous_descriptor.pb.h \ - google/protobuf/unittest_import.pb.cc \ - google/protobuf/unittest_import.pb.h \ - google/protobuf/unittest_import_public.pb.cc \ - google/protobuf/unittest_import_public.pb.h \ - google/protobuf/unittest_lite_imports_nonlite.pb.cc \ - google/protobuf/unittest_lite_imports_nonlite.pb.h \ - google/protobuf/unittest_mset.pb.cc \ - google/protobuf/unittest_mset.pb.h \ - google/protobuf/unittest_no_arena.pb.cc \ - google/protobuf/unittest_no_arena.pb.h \ - google/protobuf/unittest_no_arena_import.pb.cc \ - google/protobuf/unittest_no_arena_import.pb.h \ - google/protobuf/unittest_no_field_presence.pb.cc \ - google/protobuf/unittest_no_field_presence.pb.h \ - google/protobuf/unittest_no_generic_services.pb.cc \ - google/protobuf/unittest_no_generic_services.pb.h \ - google/protobuf/unittest_optimize_for.pb.cc \ - google/protobuf/unittest_optimize_for.pb.h \ - google/protobuf/unittest_preserve_unknown_enum.pb.cc \ - google/protobuf/unittest_preserve_unknown_enum.pb.h \ - google/protobuf/unittest_preserve_unknown_enum2.pb.cc \ - google/protobuf/unittest_preserve_unknown_enum2.pb.h \ - google/protobuf/unittest_proto3_arena.pb.cc \ - google/protobuf/unittest_proto3_arena.pb.h \ - google/protobuf/unittest_well_known_types.pb.cc \ - google/protobuf/unittest_well_known_types.pb.h \ - google/protobuf/compiler/cpp/cpp_test_large_enum_value.pb.cc \ - google/protobuf/compiler/cpp/cpp_test_large_enum_value.pb.h \ - google/protobuf/compiler/cpp/cpp_test_bad_identifiers.pb.cc \ - google/protobuf/compiler/cpp/cpp_test_bad_identifiers.pb.h +protoc_outputs = \ + $(protoc_lite_outputs) \ + google/protobuf/any_test.pb.cc \ + google/protobuf/any_test.pb.h \ + google/protobuf/compiler/cpp/cpp_test_bad_identifiers.pb.cc \ + google/protobuf/compiler/cpp/cpp_test_bad_identifiers.pb.h \ + google/protobuf/compiler/cpp/cpp_test_large_enum_value.pb.cc \ + google/protobuf/compiler/cpp/cpp_test_large_enum_value.pb.h \ + google/protobuf/map_proto2_unittest.pb.cc \ + google/protobuf/map_proto2_unittest.pb.h \ + google/protobuf/map_unittest.pb.cc \ + google/protobuf/map_unittest.pb.h \ + google/protobuf/unittest_arena.pb.cc \ + google/protobuf/unittest_arena.pb.h \ + google/protobuf/unittest_custom_options.pb.cc \ + google/protobuf/unittest_custom_options.pb.h \ + google/protobuf/unittest_drop_unknown_fields.pb.cc \ + google/protobuf/unittest_drop_unknown_fields.pb.h \ + google/protobuf/unittest_embed_optimize_for.pb.cc \ + google/protobuf/unittest_embed_optimize_for.pb.h \ + google/protobuf/unittest_empty.pb.cc \ + google/protobuf/unittest_empty.pb.h \ + google/protobuf/unittest_enormous_descriptor.pb.cc \ + google/protobuf/unittest_enormous_descriptor.pb.h \ + google/protobuf/unittest_import.pb.cc \ + google/protobuf/unittest_import.pb.h \ + google/protobuf/unittest_import_public.pb.cc \ + google/protobuf/unittest_import_public.pb.h \ + google/protobuf/unittest_lite_imports_nonlite.pb.cc \ + google/protobuf/unittest_lite_imports_nonlite.pb.h \ + google/protobuf/unittest_mset.pb.cc \ + google/protobuf/unittest_mset.pb.h \ + google/protobuf/unittest_no_arena_import.pb.cc \ + google/protobuf/unittest_no_arena_import.pb.h \ + google/protobuf/unittest_no_arena.pb.cc \ + google/protobuf/unittest_no_arena.pb.h \ + google/protobuf/unittest_no_field_presence.pb.cc \ + google/protobuf/unittest_no_field_presence.pb.h \ + google/protobuf/unittest_no_generic_services.pb.cc \ + google/protobuf/unittest_no_generic_services.pb.h \ + google/protobuf/unittest_optimize_for.pb.cc \ + google/protobuf/unittest_optimize_for.pb.h \ + google/protobuf/unittest.pb.cc \ + google/protobuf/unittest.pb.h \ + google/protobuf/unittest_preserve_unknown_enum2.pb.cc \ + google/protobuf/unittest_preserve_unknown_enum2.pb.h \ + google/protobuf/unittest_preserve_unknown_enum.pb.cc \ + google/protobuf/unittest_preserve_unknown_enum.pb.h \ + google/protobuf/unittest_proto3_arena.pb.cc \ + google/protobuf/unittest_proto3_arena.pb.h \ + google/protobuf/unittest_well_known_types.pb.cc \ + google/protobuf/unittest_well_known_types.pb.h \ + google/protobuf/util/internal/testdata/anys.pb.cc \ + google/protobuf/util/internal/testdata/anys.pb.h \ + google/protobuf/util/internal/testdata/books.pb.cc \ + google/protobuf/util/internal/testdata/books.pb.h \ + google/protobuf/util/internal/testdata/default_value.pb.cc \ + google/protobuf/util/internal/testdata/default_value.pb.h \ + google/protobuf/util/internal/testdata/default_value_test.pb.cc \ + google/protobuf/util/internal/testdata/default_value_test.pb.h \ + google/protobuf/util/internal/testdata/field_mask.pb.cc \ + google/protobuf/util/internal/testdata/field_mask.pb.h \ + google/protobuf/util/internal/testdata/maps.pb.cc \ + google/protobuf/util/internal/testdata/maps.pb.h \ + google/protobuf/util/internal/testdata/struct.pb.cc \ + google/protobuf/util/internal/testdata/struct.pb.h \ + google/protobuf/util/internal/testdata/timestamp_duration.pb.cc \ + google/protobuf/util/internal/testdata/timestamp_duration.pb.h \ + google/protobuf/util/json_format_proto3.pb.cc \ + google/protobuf/util/json_format_proto3.pb.h BUILT_SOURCES = $(protoc_outputs) @@ -541,21 +619,27 @@ COMMON_TEST_SOURCES = \ check_PROGRAMS = protoc protobuf-test protobuf-lazy-descriptor-test \ protobuf-lite-test test_plugin $(GZCHECKPROGRAMS) protobuf_test_LDADD = $(PTHREAD_LIBS) libprotobuf.la libprotoc.la \ - $(top_builddir)/gtest/lib/libgtest.la \ - $(top_builddir)/gtest/lib/libgtest_main.la -protobuf_test_CPPFLAGS = -I$(top_srcdir)/gtest/include \ - -I$(top_builddir)/gtest/include + $(top_builddir)/gmock/gtest/lib/libgtest.la \ + $(top_builddir)/gmock/lib/libgmock.la \ + $(top_builddir)/gmock/lib/libgmock_main.la +protobuf_test_CPPFLAGS = -I$(top_builddir)/gmock/gtest/include \ + -I$(top_builddir)/gmock/include # Disable optimization for tests unless the user explicitly asked for it, # since test_util.cc takes forever to compile with optimization (with GCC). # See configure.ac for more info. protobuf_test_CXXFLAGS = $(NO_OPT_CXXFLAGS) protobuf_test_SOURCES = \ + google/protobuf/stubs/bytestream_unittest.cc \ google/protobuf/stubs/common_unittest.cc \ google/protobuf/stubs/once_unittest.cc \ - google/protobuf/stubs/strutil_unittest.cc \ - google/protobuf/stubs/structurally_valid_unittest.cc \ + google/protobuf/stubs/statusor_test.cc \ + google/protobuf/stubs/status_test.cc \ + google/protobuf/stubs/stringpiece_unittest.cc \ google/protobuf/stubs/stringprintf_unittest.cc \ + google/protobuf/stubs/structurally_valid_unittest.cc \ + google/protobuf/stubs/strutil_unittest.cc \ google/protobuf/stubs/template_util_unittest.cc \ + google/protobuf/stubs/time_test.cc \ google/protobuf/stubs/type_traits_unittest.cc \ google/protobuf/any_test.cc \ google/protobuf/arenastring_unittest.cc \ @@ -598,16 +682,28 @@ protobuf_test_SOURCES = \ google/protobuf/compiler/python/python_plugin_unittest.cc \ google/protobuf/compiler/ruby/ruby_generator_unittest.cc \ google/protobuf/compiler/csharp/csharp_generator_unittest.cc \ + google/protobuf/util/field_comparator_test.cc \ + google/protobuf/util/internal/default_value_objectwriter_test.cc \ + google/protobuf/util/internal/json_objectwriter_test.cc \ + google/protobuf/util/internal/json_stream_parser_test.cc \ + google/protobuf/util/internal/protostream_objectsource_test.cc \ + google/protobuf/util/internal/protostream_objectwriter_test.cc \ + google/protobuf/util/internal/type_info_test_helper.cc \ + google/protobuf/util/json_util_test.cc \ + google/protobuf/util/type_resolver_util_test.cc \ $(COMMON_TEST_SOURCES) + + nodist_protobuf_test_SOURCES = $(protoc_outputs) # Run cpp_unittest again with PROTOBUF_TEST_NO_DESCRIPTORS defined. protobuf_lazy_descriptor_test_LDADD = $(PTHREAD_LIBS) libprotobuf.la \ libprotoc.la \ - $(top_builddir)/gtest/lib/libgtest.la \ - $(top_builddir)/gtest/lib/libgtest_main.la -protobuf_lazy_descriptor_test_CPPFLAGS = -I$(top_srcdir)/gtest/include \ - -I$(top_builddir)/gtest/include \ + $(top_builddir)/gmock/gtest/lib/libgtest.la \ + $(top_builddir)/gmock/lib/libgmock.la \ + $(top_builddir)/gmock/lib/libgmock_main.la +protobuf_lazy_descriptor_test_CPPFLAGS = -I$(top_builddir)/gmock/include \ + -I$(top_builddir)/gmock/gtest/include \ -DPROTOBUF_TEST_NO_DESCRIPTORS protobuf_lazy_descriptor_test_CXXFLAGS = $(NO_OPT_CXXFLAGS) protobuf_lazy_descriptor_test_SOURCES = \ @@ -632,9 +728,8 @@ nodist_protobuf_lite_test_SOURCES = $(protoc_lite_outputs) # Test plugin binary. test_plugin_LDADD = $(PTHREAD_LIBS) libprotobuf.la libprotoc.la \ - $(top_builddir)/gtest/lib/libgtest.la -test_plugin_CPPFLAGS = -I$(top_srcdir)/gtest/include \ - -I$(top_builddir)/gtest/include + $(top_builddir)/gmock/gtest/lib/libgtest.la +test_plugin_CPPFLAGS = -I$(top_builddir)/gmock/gtest/include test_plugin_SOURCES = \ google/protobuf/compiler/mock_code_generator.cc \ google/protobuf/testing/file.cc \ diff --git a/src/google/protobuf/compiler/cpp/cpp_message.cc b/src/google/protobuf/compiler/cpp/cpp_message.cc index af85919a..17f67a7b 100644 --- a/src/google/protobuf/compiler/cpp/cpp_message.cc +++ b/src/google/protobuf/compiler/cpp/cpp_message.cc @@ -621,7 +621,7 @@ GenerateSingularFieldHasBits(const FieldDescriptor* field, // has_$name$() methods. vars["has_array_index"] = SimpleItoa(field->index() / 32); vars["has_mask"] = StrCat(strings::Hex(1u << (field->index() % 32), - strings::Hex::ZERO_PAD_8)); + strings::ZERO_PAD_8)); printer->Print(vars, "$inline$" "bool $classname$::has_$name$() const {\n" @@ -3364,7 +3364,7 @@ static string ConditionalToCheckBitmasks(const vector& masks) { vector parts; for (int i = 0; i < masks.size(); i++) { if (masks[i] == 0) continue; - string m = StrCat("0x", strings::Hex(masks[i], strings::Hex::ZERO_PAD_8)); + string m = StrCat("0x", strings::Hex(masks[i], strings::ZERO_PAD_8)); // Each xor evaluates to 0 if the expected bits are present. parts.push_back(StrCat("((_has_bits_[", i, "] & ", m, ") ^ ", m, ")")); } @@ -3659,7 +3659,7 @@ GenerateIsInitialized(io::Printer* printer) { printer->Print( "if ((_has_bits_[$i$] & 0x$mask$) != 0x$mask$) return false;\n", "i", SimpleItoa(i), - "mask", StrCat(strings::Hex(mask, strings::Hex::ZERO_PAD_8))); + "mask", StrCat(strings::Hex(mask, strings::ZERO_PAD_8))); } } } diff --git a/src/google/protobuf/stubs/bytestream.cc b/src/google/protobuf/stubs/bytestream.cc new file mode 100644 index 00000000..f4af6a50 --- /dev/null +++ b/src/google/protobuf/stubs/bytestream.cc @@ -0,0 +1,196 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include +#include + +namespace google { +namespace protobuf { +namespace strings { + +void ByteSource::CopyTo(ByteSink* sink, size_t n) { + while (n > 0) { + StringPiece fragment = Peek(); + if (fragment.empty()) { + GOOGLE_LOG(DFATAL) << "ByteSource::CopyTo() overran input."; + break; + } + std::size_t fragment_size = std::min(n, fragment.size()); + sink->Append(fragment.data(), fragment_size); + Skip(fragment_size); + n -= fragment_size; + } +} + +void ByteSink::Flush() {} + +void UncheckedArrayByteSink::Append(const char* data, size_t n) { + if (data != dest_) { + // Catch cases where the pointer returned by GetAppendBuffer() was modified. + GOOGLE_DCHECK(!(dest_ <= data && data < (dest_ + n))) + << "Append() data[] overlaps with dest_[]"; + memcpy(dest_, data, n); + } + dest_ += n; +} + +CheckedArrayByteSink::CheckedArrayByteSink(char* outbuf, size_t capacity) + : outbuf_(outbuf), capacity_(capacity), size_(0), overflowed_(false) { +} + +void CheckedArrayByteSink::Append(const char* bytes, size_t n) { + size_t available = capacity_ - size_; + if (n > available) { + n = available; + overflowed_ = true; + } + if (n > 0 && bytes != (outbuf_ + size_)) { + // Catch cases where the pointer returned by GetAppendBuffer() was modified. + GOOGLE_DCHECK(!(outbuf_ <= bytes && bytes < (outbuf_ + capacity_))) + << "Append() bytes[] overlaps with outbuf_[]"; + memcpy(outbuf_ + size_, bytes, n); + } + size_ += n; +} + +GrowingArrayByteSink::GrowingArrayByteSink(size_t estimated_size) + : capacity_(estimated_size), + buf_(new char[estimated_size]), + size_(0) { +} + +GrowingArrayByteSink::~GrowingArrayByteSink() { + delete[] buf_; // Just in case the user didn't call GetBuffer. +} + +void GrowingArrayByteSink::Append(const char* bytes, size_t n) { + size_t available = capacity_ - size_; + if (bytes != (buf_ + size_)) { + // Catch cases where the pointer returned by GetAppendBuffer() was modified. + // We need to test for this before calling Expand() which may reallocate. + GOOGLE_DCHECK(!(buf_ <= bytes && bytes < (buf_ + capacity_))) + << "Append() bytes[] overlaps with buf_[]"; + } + if (n > available) { + Expand(n - available); + } + if (n > 0 && bytes != (buf_ + size_)) { + memcpy(buf_ + size_, bytes, n); + } + size_ += n; +} + +char* GrowingArrayByteSink::GetBuffer(size_t* nbytes) { + ShrinkToFit(); + char* b = buf_; + *nbytes = size_; + buf_ = NULL; + size_ = capacity_ = 0; + return b; +} + +void GrowingArrayByteSink::Expand(size_t amount) { // Expand by at least 50%. + size_t new_capacity = std::max(capacity_ + amount, (3 * capacity_) / 2); + char* bigger = new char[new_capacity]; + memcpy(bigger, buf_, size_); + delete[] buf_; + buf_ = bigger; + capacity_ = new_capacity; +} + +void GrowingArrayByteSink::ShrinkToFit() { + // Shrink only if the buffer is large and size_ is less than 3/4 + // of capacity_. + if (capacity_ > 256 && size_ < (3 * capacity_) / 4) { + char* just_enough = new char[size_]; + memcpy(just_enough, buf_, size_); + delete[] buf_; + buf_ = just_enough; + capacity_ = size_; + } +} + +void StringByteSink::Append(const char* data, size_t n) { + dest_->append(data, n); +} + +size_t ArrayByteSource::Available() const { + return input_.size(); +} + +StringPiece ArrayByteSource::Peek() { + return input_; +} + +void ArrayByteSource::Skip(size_t n) { + GOOGLE_DCHECK_LE(n, input_.size()); + input_.remove_prefix(n); +} + +LimitByteSource::LimitByteSource(ByteSource *source, size_t limit) + : source_(source), + limit_(limit) { +} + +size_t LimitByteSource::Available() const { + size_t available = source_->Available(); + if (available > limit_) { + available = limit_; + } + + return available; +} + +StringPiece LimitByteSource::Peek() { + StringPiece piece(source_->Peek()); + if (piece.size() > limit_) { + piece.set(piece.data(), limit_); + } + + return piece; +} + +void LimitByteSource::Skip(size_t n) { + GOOGLE_DCHECK_LE(n, limit_); + source_->Skip(n); + limit_ -= n; +} + +void LimitByteSource::CopyTo(ByteSink *sink, size_t n) { + GOOGLE_DCHECK_LE(n, limit_); + source_->CopyTo(sink, n); + limit_ -= n; +} + +} // namespace strings +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/stubs/bytestream.h b/src/google/protobuf/stubs/bytestream.h new file mode 100644 index 00000000..c9c9a76e --- /dev/null +++ b/src/google/protobuf/stubs/bytestream.h @@ -0,0 +1,348 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// This file declares the ByteSink and ByteSource abstract interfaces. These +// interfaces represent objects that consume (ByteSink) or produce (ByteSource) +// a sequence of bytes. Using these abstract interfaces in your APIs can help +// make your code work with a variety of input and output types. +// +// This file also declares the following commonly used implementations of these +// interfaces. +// +// ByteSink: +// UncheckedArrayByteSink Writes to an array, without bounds checking +// CheckedArrayByteSink Writes to an array, with bounds checking +// GrowingArrayByteSink Allocates and writes to a growable buffer +// StringByteSink Writes to an STL string +// NullByteSink Consumes a never-ending stream of bytes +// +// ByteSource: +// ArrayByteSource Reads from an array or string/StringPiece +// LimitedByteSource Limits the number of bytes read from an + +#ifndef GOOGLE_PROTOBUF_STUBS_BYTESTREAM_H_ +#define GOOGLE_PROTOBUF_STUBS_BYTESTREAM_H_ + +#include +#include + +#include +#include + +class CordByteSink; +class MemBlock; + +namespace google { +namespace protobuf { +namespace strings { + +// An abstract interface for an object that consumes a sequence of bytes. This +// interface offers 3 different ways to append data, and a Flush() function. +// +// Example: +// +// string my_data; +// ... +// ByteSink* sink = ... +// sink->Append(my_data.data(), my_data.size()); +// sink->Flush(); +// +class ByteSink { + public: + ByteSink() {} + virtual ~ByteSink() {} + + // Appends the "n" bytes starting at "bytes". + virtual void Append(const char* bytes, size_t n) = 0; + + // Flushes internal buffers. The default implemenation does nothing. ByteSink + // subclasses may use internal buffers that require calling Flush() at the end + // of the stream. + virtual void Flush(); + + private: + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(ByteSink); +}; + +// An abstract interface for an object that produces a fixed-size sequence of +// bytes. +// +// Example: +// +// ByteSource* source = ... +// while (source->Available() > 0) { +// StringPiece data = source->Peek(); +// ... do something with "data" ... +// source->Skip(data.length()); +// } +// +class ByteSource { + public: + ByteSource() {} + virtual ~ByteSource() {} + + // Returns the number of bytes left to read from the source. Available() + // should decrease by N each time Skip(N) is called. Available() may not + // increase. Available() returning 0 indicates that the ByteSource is + // exhausted. + // + // Note: Size() may have been a more appropriate name as it's more + // indicative of the fixed-size nature of a ByteSource. + virtual size_t Available() const = 0; + + // Returns a StringPiece of the next contiguous region of the source. Does not + // reposition the source. The returned region is empty iff Available() == 0. + // + // The returned region is valid until the next call to Skip() or until this + // object is destroyed, whichever occurs first. + // + // The length of the returned StringPiece will be <= Available(). + virtual StringPiece Peek() = 0; + + // Skips the next n bytes. Invalidates any StringPiece returned by a previous + // call to Peek(). + // + // REQUIRES: Available() >= n + virtual void Skip(size_t n) = 0; + + // Writes the next n bytes in this ByteSource to the given ByteSink, and + // advances this ByteSource past the copied bytes. The default implementation + // of this method just copies the bytes normally, but subclasses might + // override CopyTo to optimize certain cases. + // + // REQUIRES: Available() >= n + virtual void CopyTo(ByteSink* sink, size_t n); + + private: + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(ByteSource); +}; + +// +// Some commonly used implementations of ByteSink +// + +// Implementation of ByteSink that writes to an unsized byte array. No +// bounds-checking is performed--it is the caller's responsibility to ensure +// that the destination array is large enough. +// +// Example: +// +// char buf[10]; +// UncheckedArrayByteSink sink(buf); +// sink.Append("hi", 2); // OK +// sink.Append(data, 100); // WOOPS! Overflows buf[10]. +// +class UncheckedArrayByteSink : public ByteSink { + public: + explicit UncheckedArrayByteSink(char* dest) : dest_(dest) {} + virtual void Append(const char* data, size_t n); + + // Returns the current output pointer so that a caller can see how many bytes + // were produced. + // + // Note: this method is not part of the ByteSink interface. + char* CurrentDestination() const { return dest_; } + + private: + char* dest_; + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(UncheckedArrayByteSink); +}; + +// Implementation of ByteSink that writes to a sized byte array. This sink will +// not write more than "capacity" bytes to outbuf. Once "capacity" bytes are +// appended, subsequent bytes will be ignored and Overflowed() will return true. +// Overflowed() does not cause a runtime error (i.e., it does not CHECK fail). +// +// Example: +// +// char buf[10]; +// CheckedArrayByteSink sink(buf, 10); +// sink.Append("hi", 2); // OK +// sink.Append(data, 100); // Will only write 8 more bytes +// +class CheckedArrayByteSink : public ByteSink { + public: + CheckedArrayByteSink(char* outbuf, size_t capacity); + virtual void Append(const char* bytes, size_t n); + + // Returns the number of bytes actually written to the sink. + size_t NumberOfBytesWritten() const { return size_; } + + // Returns true if any bytes were discarded, i.e., if there was an + // attempt to write more than 'capacity' bytes. + bool Overflowed() const { return overflowed_; } + + private: + char* outbuf_; + const size_t capacity_; + size_t size_; + bool overflowed_; + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(CheckedArrayByteSink); +}; + +// Implementation of ByteSink that allocates an internal buffer (a char array) +// and expands it as needed to accomodate appended data (similar to a string), +// and allows the caller to take ownership of the internal buffer via the +// GetBuffer() method. The buffer returned from GetBuffer() must be deleted by +// the caller with delete[]. GetBuffer() also sets the internal buffer to be +// empty, and subsequent appends to the sink will create a new buffer. The +// destructor will free the internal buffer if GetBuffer() was not called. +// +// Example: +// +// GrowingArrayByteSink sink(10); +// sink.Append("hi", 2); +// sink.Append(data, n); +// const char* buf = sink.GetBuffer(); // Ownership transferred +// delete[] buf; +// +class GrowingArrayByteSink : public strings::ByteSink { + public: + explicit GrowingArrayByteSink(size_t estimated_size); + virtual ~GrowingArrayByteSink(); + virtual void Append(const char* bytes, size_t n); + + // Returns the allocated buffer, and sets nbytes to its size. The caller takes + // ownership of the buffer and must delete it with delete[]. + char* GetBuffer(size_t* nbytes); + + private: + void Expand(size_t amount); + void ShrinkToFit(); + + size_t capacity_; + char* buf_; + size_t size_; + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(GrowingArrayByteSink); +}; + +// Implementation of ByteSink that appends to the given string. +// Existing contents of "dest" are not modified; new data is appended. +// +// Example: +// +// string dest = "Hello "; +// StringByteSink sink(&dest); +// sink.Append("World", 5); +// assert(dest == "Hello World"); +// +class StringByteSink : public ByteSink { + public: + explicit StringByteSink(string* dest) : dest_(dest) {} + virtual void Append(const char* data, size_t n); + + private: + string* dest_; + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(StringByteSink); +}; + +// Implementation of ByteSink that discards all data. +// +// Example: +// +// NullByteSink sink; +// sink.Append(data, data.size()); // All data ignored. +// +class NullByteSink : public ByteSink { + public: + NullByteSink() {} + virtual void Append(const char *data, size_t n) {} + + private: + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(NullByteSink); +}; + +// +// Some commonly used implementations of ByteSource +// + +// Implementation of ByteSource that reads from a StringPiece. +// +// Example: +// +// string data = "Hello"; +// ArrayByteSource source(data); +// assert(source.Available() == 5); +// assert(source.Peek() == "Hello"); +// +class ArrayByteSource : public ByteSource { + public: + explicit ArrayByteSource(StringPiece s) : input_(s) {} + + virtual size_t Available() const; + virtual StringPiece Peek(); + virtual void Skip(size_t n); + + private: + StringPiece input_; + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(ArrayByteSource); +}; + +// Implementation of ByteSource that wraps another ByteSource, limiting the +// number of bytes returned. +// +// The caller maintains ownership of the underlying source, and may not use the +// underlying source while using the LimitByteSource object. The underlying +// source's pointer is advanced by n bytes every time this LimitByteSource +// object is advanced by n. +// +// Example: +// +// string data = "Hello World"; +// ArrayByteSource abs(data); +// assert(abs.Available() == data.size()); +// +// LimitByteSource limit(abs, 5); +// assert(limit.Available() == 5); +// assert(limit.Peek() == "Hello"); +// +class LimitByteSource : public ByteSource { + public: + // Returns at most "limit" bytes from "source". + LimitByteSource(ByteSource* source, size_t limit); + + virtual size_t Available() const; + virtual StringPiece Peek(); + virtual void Skip(size_t n); + + // We override CopyTo so that we can forward to the underlying source, in + // case it has an efficient implementation of CopyTo. + virtual void CopyTo(ByteSink* sink, size_t n); + + private: + ByteSource* source_; + size_t limit_; +}; + +} // namespace strings +} // namespace protobuf +} // namespace google + +#endif // GOOGLE_PROTOBUF_STUBS_BYTESTREAM_H_ diff --git a/src/google/protobuf/stubs/bytestream_unittest.cc b/src/google/protobuf/stubs/bytestream_unittest.cc new file mode 100644 index 00000000..06f114ab --- /dev/null +++ b/src/google/protobuf/stubs/bytestream_unittest.cc @@ -0,0 +1,146 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include +#include +#include + +#include +#include + +namespace google { +namespace protobuf { +namespace strings { +namespace { + +// We use this class instead of ArrayByteSource to simulate a ByteSource that +// contains multiple fragments. ArrayByteSource returns the entire array in +// one fragment. +class MockByteSource : public ByteSource { + public: + MockByteSource(StringPiece data, int block_size) + : data_(data), block_size_(block_size) {} + + size_t Available() const { return data_.size(); } + StringPiece Peek() { + return data_.substr(0, block_size_); + } + void Skip(size_t n) { data_.remove_prefix(n); } + + private: + StringPiece data_; + int block_size_; +}; + +TEST(ByteSourceTest, CopyTo) { + StringPiece data("Hello world!"); + MockByteSource source(data, 3); + string str; + StringByteSink sink(&str); + + source.CopyTo(&sink, data.size()); + EXPECT_EQ(data, str); +} + +TEST(ByteSourceTest, CopySubstringTo) { + StringPiece data("Hello world!"); + MockByteSource source(data, 3); + source.Skip(1); + string str; + StringByteSink sink(&str); + + source.CopyTo(&sink, data.size() - 2); + EXPECT_EQ(data.substr(1, data.size() - 2), str); + EXPECT_EQ("!", source.Peek()); +} + +TEST(ByteSourceTest, LimitByteSource) { + StringPiece data("Hello world!"); + MockByteSource source(data, 3); + LimitByteSource limit_source(&source, 6); + EXPECT_EQ(6, limit_source.Available()); + limit_source.Skip(1); + EXPECT_EQ(5, limit_source.Available()); + + { + string str; + StringByteSink sink(&str); + limit_source.CopyTo(&sink, limit_source.Available()); + EXPECT_EQ("ello ", str); + EXPECT_EQ(0, limit_source.Available()); + EXPECT_EQ(6, source.Available()); + } + + { + string str; + StringByteSink sink(&str); + source.CopyTo(&sink, source.Available()); + EXPECT_EQ("world!", str); + EXPECT_EQ(0, source.Available()); + } +} + +TEST(ByteSourceTest, CopyToStringByteSink) { + StringPiece data("Hello world!"); + MockByteSource source(data, 3); + string str; + StringByteSink sink(&str); + source.CopyTo(&sink, data.size()); + EXPECT_EQ(data, str); +} + +// Verify that ByteSink is subclassable and Flush() overridable. +class FlushingByteSink : public StringByteSink { + public: + explicit FlushingByteSink(string* dest) : StringByteSink(dest) {} + virtual void Flush() { Append("z", 1); } + private: + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(FlushingByteSink); +}; + +// Write and Flush via the ByteSink superclass interface. +void WriteAndFlush(ByteSink* s) { + s->Append("abc", 3); + s->Flush(); +} + +TEST(ByteSinkTest, Flush) { + string str; + FlushingByteSink f_sink(&str); + WriteAndFlush(&f_sink); + EXPECT_STREQ("abcz", str.c_str()); +} + +} // namespace +} // namespace strings +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/stubs/casts.h b/src/google/protobuf/stubs/casts.h index cccf65a1..be652849 100644 --- a/src/google/protobuf/stubs/casts.h +++ b/src/google/protobuf/stubs/casts.h @@ -111,12 +111,22 @@ inline To down_cast(From& f) { return *static_cast(&f); } +template +inline To bit_cast(const From& from) { + GOOGLE_COMPILE_ASSERT(sizeof(From) == sizeof(To), + bit_cast_with_different_sizes); + To dest; + memcpy(&dest, &from, sizeof(dest)); + return dest; +} + } // namespace internal // We made these internal so that they would show up as such in the docs, // but we don't want to stick "internal::" in front of them everywhere. using internal::implicit_cast; using internal::down_cast; +using internal::bit_cast; } // namespace protobuf } // namespace google diff --git a/src/google/protobuf/stubs/common.cc b/src/google/protobuf/stubs/common.cc index 54e00ccb..b895d915 100644 --- a/src/google/protobuf/stubs/common.cc +++ b/src/google/protobuf/stubs/common.cc @@ -32,6 +32,8 @@ #include #include +#include +#include #include #include #include @@ -148,6 +150,17 @@ LogMessage& LogMessage::operator<<(const char* value) { return *this; } +LogMessage& LogMessage::operator<<(const StringPiece& value) { + message_ += value.ToString(); + return *this; +} + +LogMessage& LogMessage::operator<<( + const ::google::protobuf::util::Status& status) { + message_ += status.ToString(); + return *this; +} + // Since this is just for logging, we don't care if the current locale changes // the results -- in fact, we probably prefer that. So we use snprintf() // instead of Simple*toa(). diff --git a/src/google/protobuf/stubs/common.h b/src/google/protobuf/stubs/common.h index 49d16020..34d79f48 100644 --- a/src/google/protobuf/stubs/common.h +++ b/src/google/protobuf/stubs/common.h @@ -48,6 +48,26 @@ #include #endif +#undef PROTOBUF_LITTLE_ENDIAN +#ifdef _MSC_VER + #if defined(_M_IX86) && \ + !defined(PROTOBUF_DISABLE_LITTLE_ENDIAN_OPT_FOR_TEST) + #define PROTOBUF_LITTLE_ENDIAN 1 + #endif + #if _MSC_VER >= 1300 + // If MSVC has "/RTCc" set, it will complain about truncating casts at + // runtime. This file contains some intentional truncating casts. + #pragma runtime_checks("c", off) + #endif +#else + #include // __BYTE_ORDER + #if ((defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__)) || \ + (defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN)) && \ + !defined(PROTOBUF_DISABLE_LITTLE_ENDIAN_OPT_FOR_TEST) + #define PROTOBUF_LITTLE_ENDIAN 1 + #endif +#endif + #ifndef PROTOBUF_USE_EXCEPTIONS #if defined(_MSC_VER) && defined(_CPPUNWIND) #define PROTOBUF_USE_EXCEPTIONS 1 @@ -100,6 +120,12 @@ namespace protobuf { TypeName(const TypeName&); \ void operator=(const TypeName&) +#undef GOOGLE_DISALLOW_IMPLICIT_CONSTRUCTORS +#define GOOGLE_DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ + TypeName(); \ + TypeName(const TypeName&); \ + void operator=(const TypeName&) + #if defined(_MSC_VER) && defined(PROTOBUF_USE_DLLS) #ifdef LIBPROTOBUF_EXPORTS #define LIBPROTOBUF_EXPORT __declspec(dllexport) @@ -660,6 +686,10 @@ enum LogLevel { #endif }; +class StringPiece; +namespace util { +class Status; +} namespace internal { class LogFinisher; @@ -678,6 +708,8 @@ class LIBPROTOBUF_EXPORT LogMessage { LogMessage& operator<<(unsigned long value); LogMessage& operator<<(double value); LogMessage& operator<<(void* value); + LogMessage& operator<<(const StringPiece& value); + LogMessage& operator<<(const ::google::protobuf::util::Status& status); private: friend class LogFinisher; @@ -696,6 +728,11 @@ class LIBPROTOBUF_EXPORT LogFinisher { void operator=(LogMessage& other); }; +template +bool IsOk(T status) { return status.ok(); } +template<> +inline bool IsOk(bool status) { return status; } + } // namespace internal // Undef everything in case we're being mixed with some other Google library @@ -717,6 +754,7 @@ class LIBPROTOBUF_EXPORT LogFinisher { #undef GOOGLE_DLOG #undef GOOGLE_DCHECK +#undef GOOGLE_DCHECK_OK #undef GOOGLE_DCHECK_EQ #undef GOOGLE_DCHECK_NE #undef GOOGLE_DCHECK_LT @@ -733,7 +771,7 @@ class LIBPROTOBUF_EXPORT LogFinisher { #define GOOGLE_CHECK(EXPRESSION) \ GOOGLE_LOG_IF(FATAL, !(EXPRESSION)) << "CHECK failed: " #EXPRESSION ": " -#define GOOGLE_CHECK_OK(A) GOOGLE_CHECK(A) +#define GOOGLE_CHECK_OK(A) GOOGLE_CHECK(::google::protobuf::internal::IsOk(A)) #define GOOGLE_CHECK_EQ(A, B) GOOGLE_CHECK((A) == (B)) #define GOOGLE_CHECK_NE(A, B) GOOGLE_CHECK((A) != (B)) #define GOOGLE_CHECK_LT(A, B) GOOGLE_CHECK((A) < (B)) @@ -760,6 +798,7 @@ T* CheckNotNull(const char* /* file */, int /* line */, #define GOOGLE_DLOG GOOGLE_LOG_IF(INFO, false) #define GOOGLE_DCHECK(EXPRESSION) while(false) GOOGLE_CHECK(EXPRESSION) +#define GOOGLE_DCHECK_OK(E) GOOGLE_DCHECK(::google::protobuf::internal::IsOk(E)) #define GOOGLE_DCHECK_EQ(A, B) GOOGLE_DCHECK((A) == (B)) #define GOOGLE_DCHECK_NE(A, B) GOOGLE_DCHECK((A) != (B)) #define GOOGLE_DCHECK_LT(A, B) GOOGLE_DCHECK((A) < (B)) @@ -772,6 +811,7 @@ T* CheckNotNull(const char* /* file */, int /* line */, #define GOOGLE_DLOG GOOGLE_LOG #define GOOGLE_DCHECK GOOGLE_CHECK +#define GOOGLE_DCHECK_OK GOOGLE_CHECK_OK #define GOOGLE_DCHECK_EQ GOOGLE_CHECK_EQ #define GOOGLE_DCHECK_NE GOOGLE_CHECK_NE #define GOOGLE_DCHECK_LT GOOGLE_CHECK_LT @@ -883,6 +923,30 @@ class LIBPROTOBUF_EXPORT Closure { GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(Closure); }; +template +class LIBPROTOBUF_EXPORT ResultCallback1 { + public: + ResultCallback1() {} + virtual ~ResultCallback1() {} + + virtual R Run(A1) = 0; + + private: + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(ResultCallback1); +}; + +template +class LIBPROTOBUF_EXPORT ResultCallback2 { + public: + ResultCallback2() {} + virtual ~ResultCallback2() {} + + virtual R Run(A1,A2) = 0; + + private: + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(ResultCallback2); +}; + namespace internal { class LIBPROTOBUF_EXPORT FunctionClosure0 : public Closure { @@ -1021,6 +1085,96 @@ class MethodClosure2 : public Closure { Arg2 arg2_; }; +template +class FunctionResultCallback_0_1 : public ResultCallback1 { + public: + typedef R (*FunctionType)(Arg1 arg1); + + FunctionResultCallback_0_1(FunctionType function, bool self_deleting) + : function_(function), self_deleting_(self_deleting) {} + ~FunctionResultCallback_0_1() {} + + R Run(Arg1 a1) { + bool needs_delete = self_deleting_; // read in case callback deletes + R result = function_(a1); + if (needs_delete) delete this; + return result; + } + + private: + FunctionType function_; + bool self_deleting_; +}; + +template +class FunctionResultCallback_1_1 : public ResultCallback1 { + public: + typedef R (*FunctionType)(P1, A1); + + FunctionResultCallback_1_1(FunctionType function, bool self_deleting, + P1 p1) + : function_(function), self_deleting_(self_deleting), p1_(p1) {} + ~FunctionResultCallback_1_1() {} + + R Run(A1 a1) { + bool needs_delete = self_deleting_; // read in case callback deletes + R result = function_(p1_, a1); + if (needs_delete) delete this; + return result; + } + + private: + FunctionType function_; + bool self_deleting_; + P1 p1_; +}; + +// Duplicate this again in the type_traits.h, due to dependency problems. +template struct internal_remove_reference; +template struct internal_remove_reference { typedef T type; }; +template struct internal_remove_reference { typedef T type; }; + +template +struct InternalConstRef { + typedef typename internal_remove_reference::type base_type; + typedef const base_type& type; +}; + +template +class MethodResultCallback_5_2 : public ResultCallback2 { + public: + typedef R (T::*MethodType)(P1, P2, P3, P4, P5, A1, A2); + MethodResultCallback_5_2(T* object, MethodType method, bool self_deleting, + P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) + : object_(object), + method_(method), + self_deleting_(self_deleting), + p1_(p1), + p2_(p2), + p3_(p3), + p4_(p4), + p5_(p5) {} + ~MethodResultCallback_5_2() {} + + R Run(A1 a1, A2 a2) { + bool needs_delete = self_deleting_; + R result = (object_->*method_)(p1_, p2_, p3_, p4_, p5_, a1, a2); + if (needs_delete) delete this; + return result; + } + + private: + T* object_; + MethodType method_; + bool self_deleting_; + typename internal_remove_reference::type p1_; + typename internal_remove_reference::type p2_; + typename internal_remove_reference::type p3_; + typename internal_remove_reference::type p4_; + typename internal_remove_reference::type p5_; +}; + } // namespace internal // See Closure. @@ -1106,6 +1260,48 @@ inline Closure* NewPermanentCallback( object, method, false, arg1, arg2); } +// See ResultCallback1 +template +inline ResultCallback1* NewCallback(R (*function)(A1)) { + return new internal::FunctionResultCallback_0_1(function, true); +} + +// See ResultCallback1 +template +inline ResultCallback1* NewPermanentCallback(R (*function)(A1)) { + return new internal::FunctionResultCallback_0_1(function, false); +} + +// See ResultCallback1 +template +inline ResultCallback1* NewCallback(R (*function)(P1, A1), P1 p1) { + return new internal::FunctionResultCallback_1_1( + function, true, p1); +} + +// See ResultCallback1 +template +inline ResultCallback1* NewPermanentCallback( + R (*function)(P1, A1), P1 p1) { + return new internal::FunctionResultCallback_1_1( + function, false, p1); +} + +// See MethodResultCallback_5_2 +template +inline ResultCallback2* NewPermanentCallback( + T* object, R (T::*function)(P1, P2, P3, P4, P5, A1, A2), + typename internal::InternalConstRef::type p1, + typename internal::InternalConstRef::type p2, + typename internal::InternalConstRef::type p3, + typename internal::InternalConstRef::type p4, + typename internal::InternalConstRef::type p5) { + return new internal::MethodResultCallback_5_2(object, function, false, p1, + p2, p3, p4, p5); +} + // A function which does nothing. Useful for creating no-op callbacks, e.g.: // Closure* nothing = NewCallback(&DoNothing); void LIBPROTOBUF_EXPORT DoNothing(); @@ -1225,10 +1421,115 @@ LIBPROTOBUF_EXPORT bool IsStructurallyValidUTF8(const char* buf, int len); } // namespace internal +// =================================================================== +// from google3/base/port.h + +// The following guarantees declaration of the byte swap functions, and +// defines __BYTE_ORDER for MSVC +#ifdef _MSC_VER +#include // NOLINT(build/include) +#define __BYTE_ORDER __LITTLE_ENDIAN +#define bswap_16(x) _byteswap_ushort(x) +#define bswap_32(x) _byteswap_ulong(x) +#define bswap_64(x) _byteswap_uint64(x) + +#elif defined(__APPLE__) +// Mac OS X / Darwin features +#include +#define bswap_16(x) OSSwapInt16(x) +#define bswap_32(x) OSSwapInt32(x) +#define bswap_64(x) OSSwapInt64(x) + +#elif defined(__GLIBC__) || defined(__CYGWIN__) +#include // IWYU pragma: export + +#else + +static inline uint16 bswap_16(uint16 x) { + return static_cast(((x & 0xFF) << 8) | ((x & 0xFF00) >> 8)); +} +#define bswap_16(x) bswap_16(x) +static inline uint32 bswap_32(uint32 x) { + return (((x & 0xFF) << 24) | + ((x & 0xFF00) << 8) | + ((x & 0xFF0000) >> 8) | + ((x & 0xFF000000) >> 24)); +} +#define bswap_32(x) bswap_32(x) +static inline uint64 bswap_64(uint64 x) { + return (((x & GG_ULONGLONG(0xFF)) << 56) | + ((x & GG_ULONGLONG(0xFF00)) << 40) | + ((x & GG_ULONGLONG(0xFF0000)) << 24) | + ((x & GG_ULONGLONG(0xFF000000)) << 8) | + ((x & GG_ULONGLONG(0xFF00000000)) >> 8) | + ((x & GG_ULONGLONG(0xFF0000000000)) >> 24) | + ((x & GG_ULONGLONG(0xFF000000000000)) >> 40) | + ((x & GG_ULONGLONG(0xFF00000000000000)) >> 56)); +} +#define bswap_64(x) bswap_64(x) + +#endif + // =================================================================== // from google3/util/endian/endian.h LIBPROTOBUF_EXPORT uint32 ghtonl(uint32 x); +class BigEndian { + public: +#ifdef PROTOBUF_LITTLE_ENDIAN + + static uint16 FromHost16(uint16 x) { return bswap_16(x); } + static uint16 ToHost16(uint16 x) { return bswap_16(x); } + + static uint32 FromHost32(uint32 x) { return bswap_32(x); } + static uint32 ToHost32(uint32 x) { return bswap_32(x); } + + static uint64 FromHost64(uint64 x) { return bswap_64(x); } + static uint64 ToHost64(uint64 x) { return bswap_64(x); } + + static bool IsLittleEndian() { return true; } + +#else + + static uint16 FromHost16(uint16 x) { return x; } + static uint16 ToHost16(uint16 x) { return x; } + + static uint32 FromHost32(uint32 x) { return x; } + static uint32 ToHost32(uint32 x) { return x; } + + static uint64 FromHost64(uint64 x) { return x; } + static uint64 ToHost64(uint64 x) { return x; } + + static bool IsLittleEndian() { return false; } + +#endif /* ENDIAN */ + + // Functions to do unaligned loads and stores in big-endian order. + static uint16 Load16(const void *p) { + return ToHost16(GOOGLE_UNALIGNED_LOAD16(p)); + } + + static void Store16(void *p, uint16 v) { + GOOGLE_UNALIGNED_STORE16(p, FromHost16(v)); + } + + static uint32 Load32(const void *p) { + return ToHost32(GOOGLE_UNALIGNED_LOAD32(p)); + } + + static void Store32(void *p, uint32 v) { + GOOGLE_UNALIGNED_STORE32(p, FromHost32(v)); + } + + static uint64 Load64(const void *p) { + return ToHost64(GOOGLE_UNALIGNED_LOAD64(p)); + } + + static void Store64(void *p, uint64 v) { + GOOGLE_UNALIGNED_STORE64(p, FromHost64(v)); + } +}; + // =================================================================== // Shutdown support. diff --git a/src/google/protobuf/stubs/mathlimits.cc b/src/google/protobuf/stubs/mathlimits.cc new file mode 100644 index 00000000..0373b2bb --- /dev/null +++ b/src/google/protobuf/stubs/mathlimits.cc @@ -0,0 +1,144 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// All Rights Reserved. +// +// Author: Maxim Lifantsev +// + +#include + +#include + +namespace google { +namespace protobuf { + +// MSVC++ 2005 and older compilers think the header declaration was a +// definition, and erroneously flag these as a duplicate definition. +#if defined(COMPILER_MSVC) || __cpluscplus < 201103L + +#define DEF_COMMON_LIMITS(Type) +#define DEF_UNSIGNED_INT_LIMITS(Type) +#define DEF_SIGNED_INT_LIMITS(Type) +#define DEF_PRECISION_LIMITS(Type) + +#else + +#define DEF_COMMON_LIMITS(Type) \ +const bool MathLimits::kIsSigned; \ +const bool MathLimits::kIsInteger; \ +const int MathLimits::kMin10Exp; \ +const int MathLimits::kMax10Exp; + +#define DEF_UNSIGNED_INT_LIMITS(Type) \ +DEF_COMMON_LIMITS(Type) \ +const Type MathLimits::kPosMin; \ +const Type MathLimits::kPosMax; \ +const Type MathLimits::kMin; \ +const Type MathLimits::kMax; \ +const Type MathLimits::kEpsilon; \ +const Type MathLimits::kStdError; + +#define DEF_SIGNED_INT_LIMITS(Type) \ +DEF_UNSIGNED_INT_LIMITS(Type) \ +const Type MathLimits::kNegMin; \ +const Type MathLimits::kNegMax; + +#define DEF_PRECISION_LIMITS(Type) \ +const int MathLimits::kPrecisionDigits; + +#endif // not COMPILER_MSVC + +// http://en.wikipedia.org/wiki/Quadruple_precision_floating-point_format#Double-double_arithmetic +// With some compilers (gcc 4.6.x) on some platforms (powerpc64), +// "long double" is implemented as a pair of double: "double double" format. +// This causes a problem with epsilon (eps). +// eps is the smallest positive number such that 1.0 + eps > 1.0 +// +// Normal format: 1.0 + e = 1.0...01 // N-1 zeros for N fraction bits +// D-D format: 1.0 + e = 1.000...0001 // epsilon can be very small +// +// In the normal format, 1.0 + e has to fit in one stretch of bits. +// The maximum rounding error is half of eps. +// +// In the double-double format, 1.0 + e splits across two doubles: +// 1.0 in the high double, e in the low double, and they do not have to +// be contiguous. The maximum rounding error on a value close to 1.0 is +// much larger than eps. +// +// Some code checks for errors by comparing a computed value to a golden +// value +/- some multiple of the maximum rounding error. The maximum +// rounding error is not available so we use eps as an approximation +// instead. That fails when long double is in the double-double format. +// Therefore, we define kStdError as a multiple of +// max(DBL_EPSILON * DBL_EPSILON, kEpsilon) rather than a multiple of kEpsilon. + +#define DEF_FP_LIMITS(Type, PREFIX) \ +DEF_COMMON_LIMITS(Type) \ +const Type MathLimits::kPosMin = PREFIX##_MIN; \ +const Type MathLimits::kPosMax = PREFIX##_MAX; \ +const Type MathLimits::kMin = -MathLimits::kPosMax; \ +const Type MathLimits::kMax = MathLimits::kPosMax; \ +const Type MathLimits::kNegMin = -MathLimits::kPosMin; \ +const Type MathLimits::kNegMax = -MathLimits::kPosMax; \ +const Type MathLimits::kEpsilon = PREFIX##_EPSILON; \ +/* 32 is 5 bits of mantissa error; should be adequate for common errors */ \ +const Type MathLimits::kStdError = \ + 32 * (DBL_EPSILON * DBL_EPSILON > MathLimits::kEpsilon \ + ? DBL_EPSILON * DBL_EPSILON : MathLimits::kEpsilon); \ +DEF_PRECISION_LIMITS(Type) \ +const Type MathLimits::kNaN = HUGE_VAL - HUGE_VAL; \ +const Type MathLimits::kPosInf = HUGE_VAL; \ +const Type MathLimits::kNegInf = -HUGE_VAL; + +// The following are *not* casts! +DEF_SIGNED_INT_LIMITS(int8) +DEF_SIGNED_INT_LIMITS(int16) // NOLINT(readability/casting) +DEF_SIGNED_INT_LIMITS(int32) // NOLINT(readability/casting) +DEF_SIGNED_INT_LIMITS(int64) // NOLINT(readability/casting) +DEF_UNSIGNED_INT_LIMITS(uint8) +DEF_UNSIGNED_INT_LIMITS(uint16) // NOLINT(readability/casting) +DEF_UNSIGNED_INT_LIMITS(uint32) // NOLINT(readability/casting) +DEF_UNSIGNED_INT_LIMITS(uint64) // NOLINT(readability/casting) + +DEF_SIGNED_INT_LIMITS(long int) +DEF_UNSIGNED_INT_LIMITS(unsigned long int) + +DEF_FP_LIMITS(float, FLT) +DEF_FP_LIMITS(double, DBL) +DEF_FP_LIMITS(long double, LDBL); + +#undef DEF_COMMON_LIMITS +#undef DEF_SIGNED_INT_LIMITS +#undef DEF_UNSIGNED_INT_LIMITS +#undef DEF_FP_LIMITS +#undef DEF_PRECISION_LIMITS +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/stubs/mathlimits.h b/src/google/protobuf/stubs/mathlimits.h new file mode 100644 index 00000000..39957d69 --- /dev/null +++ b/src/google/protobuf/stubs/mathlimits.h @@ -0,0 +1,277 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// All Rights Reserved. +// +// Author: Maxim Lifantsev +// +// Useful integer and floating point limits and type traits. +// +// This partially replaces/duplictes numeric_limits<> from . +// We get a Google-style class that we have a greater control over +// and thus can add new features to it or fix whatever happens to be broken in +// numeric_limits for the compilers we use. +// + +#ifndef UTIL_MATH_MATHLIMITS_H__ +#define UTIL_MATH_MATHLIMITS_H__ + +// lacks a lot of prototypes. However, this file needs to +// access old-fashioned isinf et al. Even worse more: this file must not +// include because that breaks the definition of isinf with gcc 4.9. +// +// TODO(mec): after C++11 everywhere, use and std::isinf in this file. +#include +#include + +#include + +// ========================================================================= // + +// Useful integer and floating point limits and type traits. +// This is just for the documentation; +// real members are defined in our specializations below. +namespace google { +namespace protobuf { +template struct MathLimits { + // Type name. + typedef T Type; + // Unsigned version of the Type with the same byte size. + // Same as Type for floating point and unsigned types. + typedef T UnsignedType; + // If the type supports negative values. + static const bool kIsSigned; + // If the type supports only integer values. + static const bool kIsInteger; + // Magnitude-wise smallest representable positive value. + static const Type kPosMin; + // Magnitude-wise largest representable positive value. + static const Type kPosMax; + // Smallest representable value. + static const Type kMin; + // Largest representable value. + static const Type kMax; + // Magnitude-wise smallest representable negative value. + // Present only if kIsSigned. + static const Type kNegMin; + // Magnitude-wise largest representable negative value. + // Present only if kIsSigned. + static const Type kNegMax; + // Smallest integer x such that 10^x is representable. + static const int kMin10Exp; + // Largest integer x such that 10^x is representable. + static const int kMax10Exp; + // Smallest positive value such that Type(1) + kEpsilon != Type(1) + static const Type kEpsilon; + // Typical rounding error that is enough to cover + // a few simple floating-point operations. + // Slightly larger than kEpsilon to account for a few rounding errors. + // Is zero if kIsInteger. + static const Type kStdError; + // Number of decimal digits of mantissa precision. + // Present only if !kIsInteger. + static const int kPrecisionDigits; + // Not a number, i.e. result of 0/0. + // Present only if !kIsInteger. + static const Type kNaN; + // Positive infinity, i.e. result of 1/0. + // Present only if !kIsInteger. + static const Type kPosInf; + // Negative infinity, i.e. result of -1/0. + // Present only if !kIsInteger. + static const Type kNegInf; + + // NOTE: Special floating point values behave + // in a special (but mathematically-logical) way + // in terms of (in)equalty comparison and mathematical operations + // -- see out unittest for examples. + + // Special floating point value testers. + // Present in integer types for convenience. + static bool IsFinite(const Type x); + static bool IsNaN(const Type x); + static bool IsInf(const Type x); + static bool IsPosInf(const Type x); + static bool IsNegInf(const Type x); +}; + +// ========================================================================= // + +// All #define-s below are simply to refactor the declarations of +// MathLimits template specializations. +// They are all #undef-ined below. + +// The hoop-jumping in *_INT_(MAX|MIN) below is so that the compiler does not +// get an overflow while computing the constants. + +#define SIGNED_INT_MAX(Type) \ + (((Type(1) << (sizeof(Type)*8 - 2)) - 1) + (Type(1) << (sizeof(Type)*8 - 2))) + +#define SIGNED_INT_MIN(Type) \ + (-(Type(1) << (sizeof(Type)*8 - 2)) - (Type(1) << (sizeof(Type)*8 - 2))) + +#define UNSIGNED_INT_MAX(Type) \ + (((Type(1) << (sizeof(Type)*8 - 1)) - 1) + (Type(1) << (sizeof(Type)*8 - 1))) + +// Compile-time selected log10-related constants for integer types. +#define SIGNED_MAX_10_EXP(Type) \ + (sizeof(Type) == 1 ? 2 : ( \ + sizeof(Type) == 2 ? 4 : ( \ + sizeof(Type) == 4 ? 9 : ( \ + sizeof(Type) == 8 ? 18 : -1)))) + +#define UNSIGNED_MAX_10_EXP(Type) \ + (sizeof(Type) == 1 ? 2 : ( \ + sizeof(Type) == 2 ? 4 : ( \ + sizeof(Type) == 4 ? 9 : ( \ + sizeof(Type) == 8 ? 19 : -1)))) + +#define DECL_INT_LIMIT_FUNCS \ + static bool IsFinite(const Type /*x*/) { return true; } \ + static bool IsNaN(const Type /*x*/) { return false; } \ + static bool IsInf(const Type /*x*/) { return false; } \ + static bool IsPosInf(const Type /*x*/) { return false; } \ + static bool IsNegInf(const Type /*x*/) { return false; } + +#define DECL_SIGNED_INT_LIMITS(IntType, UnsignedIntType) \ +template<> \ +struct MathLimits { \ + typedef IntType Type; \ + typedef UnsignedIntType UnsignedType; \ + static const bool kIsSigned = true; \ + static const bool kIsInteger = true; \ + static const Type kPosMin = 1; \ + static const Type kPosMax = SIGNED_INT_MAX(Type); \ + static const Type kMin = SIGNED_INT_MIN(Type); \ + static const Type kMax = kPosMax; \ + static const Type kNegMin = -1; \ + static const Type kNegMax = kMin; \ + static const int kMin10Exp = 0; \ + static const int kMax10Exp = SIGNED_MAX_10_EXP(Type); \ + static const Type kEpsilon = 1; \ + static const Type kStdError = 0; \ + DECL_INT_LIMIT_FUNCS \ +}; + +#define DECL_UNSIGNED_INT_LIMITS(IntType) \ +template<> \ +struct MathLimits { \ + typedef IntType Type; \ + typedef IntType UnsignedType; \ + static const bool kIsSigned = false; \ + static const bool kIsInteger = true; \ + static const Type kPosMin = 1; \ + static const Type kPosMax = UNSIGNED_INT_MAX(Type); \ + static const Type kMin = 0; \ + static const Type kMax = kPosMax; \ + static const int kMin10Exp = 0; \ + static const int kMax10Exp = UNSIGNED_MAX_10_EXP(Type); \ + static const Type kEpsilon = 1; \ + static const Type kStdError = 0; \ + DECL_INT_LIMIT_FUNCS \ +}; + +DECL_SIGNED_INT_LIMITS(signed char, unsigned char) +DECL_SIGNED_INT_LIMITS(signed short int, unsigned short int) +DECL_SIGNED_INT_LIMITS(signed int, unsigned int) +DECL_SIGNED_INT_LIMITS(signed long int, unsigned long int) +DECL_SIGNED_INT_LIMITS(signed long long int, unsigned long long int) +DECL_UNSIGNED_INT_LIMITS(unsigned char) +DECL_UNSIGNED_INT_LIMITS(unsigned short int) +DECL_UNSIGNED_INT_LIMITS(unsigned int) +DECL_UNSIGNED_INT_LIMITS(unsigned long int) +DECL_UNSIGNED_INT_LIMITS(unsigned long long int) + +#undef DECL_SIGNED_INT_LIMITS +#undef DECL_UNSIGNED_INT_LIMITS +#undef SIGNED_INT_MAX +#undef SIGNED_INT_MIN +#undef UNSIGNED_INT_MAX +#undef SIGNED_MAX_10_EXP +#undef UNSIGNED_MAX_10_EXP +#undef DECL_INT_LIMIT_FUNCS + +// ========================================================================= // +#ifdef WIN32 // Lacks built-in isnan() and isinf() +#define DECL_FP_LIMIT_FUNCS \ + static bool IsFinite(const Type x) { return _finite(x); } \ + static bool IsNaN(const Type x) { return _isnan(x); } \ + static bool IsInf(const Type x) { return (_fpclass(x) & (_FPCLASS_NINF | _FPCLASS_PINF)) != 0; } \ + static bool IsPosInf(const Type x) { return _fpclass(x) == _FPCLASS_PINF; } \ + static bool IsNegInf(const Type x) { return _fpclass(x) == _FPCLASS_NINF; } +#else +#define DECL_FP_LIMIT_FUNCS \ + static bool IsFinite(const Type x) { return !isinf(x) && !isnan(x); } \ + static bool IsNaN(const Type x) { return isnan(x); } \ + static bool IsInf(const Type x) { return isinf(x); } \ + static bool IsPosInf(const Type x) { return isinf(x) && x > 0; } \ + static bool IsNegInf(const Type x) { return isinf(x) && x < 0; } +#endif + +// We can't put floating-point constant values in the header here because +// such constants are not considered to be primitive-type constants by gcc. +// CAVEAT: Hence, they are going to be initialized only during +// the global objects construction time. +#define DECL_FP_LIMITS(FP_Type, PREFIX) \ +template<> \ +struct MathLimits { \ + typedef FP_Type Type; \ + typedef FP_Type UnsignedType; \ + static const bool kIsSigned = true; \ + static const bool kIsInteger = false; \ + static const Type kPosMin; \ + static const Type kPosMax; \ + static const Type kMin; \ + static const Type kMax; \ + static const Type kNegMin; \ + static const Type kNegMax; \ + static const int kMin10Exp = PREFIX##_MIN_10_EXP; \ + static const int kMax10Exp = PREFIX##_MAX_10_EXP; \ + static const Type kEpsilon; \ + static const Type kStdError; \ + static const int kPrecisionDigits = PREFIX##_DIG; \ + static const Type kNaN; \ + static const Type kPosInf; \ + static const Type kNegInf; \ + DECL_FP_LIMIT_FUNCS \ +}; + +DECL_FP_LIMITS(float, FLT) +DECL_FP_LIMITS(double, DBL) +DECL_FP_LIMITS(long double, LDBL) + +#undef DECL_FP_LIMITS +#undef DECL_FP_LIMIT_FUNCS + +// ========================================================================= // +} // namespace protobuf +} // namespace google + +#endif // UTIL_MATH_MATHLIMITS_H__ diff --git a/src/google/protobuf/stubs/mathutil.h b/src/google/protobuf/stubs/mathutil.h new file mode 100644 index 00000000..87ca5e91 --- /dev/null +++ b/src/google/protobuf/stubs/mathutil.h @@ -0,0 +1,149 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#ifndef GOOGLE_PROTOBUF_STUBS_MATHUTIL_H_ +#define GOOGLE_PROTOBUF_STUBS_MATHUTIL_H_ + +#include +#include + +#include +#include + +namespace google { +namespace protobuf { +namespace internal { +template +bool IsNan(T value) { + return false; +} +template<> +inline bool IsNan(float value) { return isnan(value); } +template<> +inline bool IsNan(double value) { return isnan(value); } + +template +bool AlmostEquals(T a, T b) { + return a == b; +} +template<> +inline bool AlmostEquals(float a, float b) { + return fabs(a - b) < 32 * FLT_EPSILON; +} + +template<> +inline bool AlmostEquals(double a, double b) { + return fabs(a - b) < 32 * DBL_EPSILON; +} +} // namespace internal + +class MathUtil { + public: + template + static T Sign(T value) { + if (value == T(0) || ::google::protobuf::internal::IsNan(value)) { + return value; + } + return value > T(0) ? value : -value; + } + + template + static bool AlmostEquals(T a, T b) { + return ::google::protobuf::internal::AlmostEquals(a, b); + } + + // Largest of two values. + // Works correctly for special floating point values. + // Note: 0.0 and -0.0 are not differentiated by Max (Max(0.0, -0.0) is -0.0), + // which should be OK because, although they (can) have different + // bit representation, they are observably the same when examined + // with arithmetic and (in)equality operators. + template + static T Max(const T x, const T y) { + return MathLimits::IsNaN(x) || x > y ? x : y; + } + + // Absolute value of x + // Works correctly for unsigned types and + // for special floating point values. + // Note: 0.0 and -0.0 are not differentiated by Abs (Abs(0.0) is -0.0), + // which should be OK: see the comment for Max above. + template + static T Abs(const T x) { + return x > T(0) ? x : -x; + } + + // Absolute value of the difference between two numbers. + // Works correctly for signed types and special floating point values. + template + static typename MathLimits::UnsignedType AbsDiff(const T x, const T y) { + // Carries out arithmetic as unsigned to avoid overflow. + typedef typename MathLimits::UnsignedType R; + return x > y ? R(x) - R(y) : R(y) - R(x); + } + + // If two (usually floating point) numbers are within a certain + // fraction of their magnitude or within a certain absolute margin of error. + // This is the same as the following but faster: + // WithinFraction(x, y, fraction) || WithinMargin(x, y, margin) + // E.g. WithinFraction(0.0, 1e-10, 1e-5) is false but + // WithinFractionOrMargin(0.0, 1e-10, 1e-5, 1e-5) is true. + template + static bool WithinFractionOrMargin(const T x, const T y, + const T fraction, const T margin); +}; + +template +bool MathUtil::WithinFractionOrMargin(const T x, const T y, + const T fraction, const T margin) { + // Not just "0 <= fraction" to fool the compiler for unsigned types. + GOOGLE_DCHECK((T(0) < fraction || T(0) == fraction) && + fraction < T(1) && + margin >= T(0)); + + // Template specialization will convert the if() condition to a constant, + // which will cause the compiler to generate code for either the "if" part + // or the "then" part. In this way we avoid a compiler warning + // about a potential integer overflow in crosstool v12 (gcc 4.3.1). + if (MathLimits::kIsInteger) { + return x == y; + } else { + // IsFinite checks are to make kPosInf and kNegInf not within fraction + if (!MathLimits::IsFinite(x) && !MathLimits::IsFinite(y)) { + return false; + } + T relative_margin = static_cast(fraction * Max(Abs(x), Abs(y))); + return AbsDiff(x, y) <= Max(margin, relative_margin); + } +} + +} // namespace protobuf +} // namespace google + +#endif // GOOGLE_PROTOBUF_STUBS_MATHUTIL_H_ diff --git a/src/google/protobuf/stubs/status.cc b/src/google/protobuf/stubs/status.cc new file mode 100644 index 00000000..9a68fad4 --- /dev/null +++ b/src/google/protobuf/stubs/status.cc @@ -0,0 +1,133 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include + +#include +#include +#include + +namespace google { +namespace protobuf { +namespace util { +namespace error { +inline string CodeEnumToString(error::Code code) { + switch (code) { + case OK: + return "OK"; + case CANCELLED: + return "CANCELLED"; + case UNKNOWN: + return "UNKNOWN"; + case INVALID_ARGUMENT: + return "INVALID_ARGUMENT"; + case DEADLINE_EXCEEDED: + return "DEADLINE_EXCEEDED"; + case NOT_FOUND: + return "NOT_FOUND"; + case ALREADY_EXISTS: + return "ALREADY_EXISTS"; + case PERMISSION_DENIED: + return "PERMISSION_DENIED"; + case UNAUTHENTICATED: + return "UNAUTHENTICATED"; + case RESOURCE_EXHAUSTED: + return "RESOURCE_EXHAUSTED"; + case FAILED_PRECONDITION: + return "FAILED_PRECONDITION"; + case ABORTED: + return "ABORTED"; + case OUT_OF_RANGE: + return "OUT_OF_RANGE"; + case UNIMPLEMENTED: + return "UNIMPLEMENTED"; + case INTERNAL: + return "INTERNAL"; + case UNAVAILABLE: + return "UNAVAILABLE"; + case DATA_LOSS: + return "DATA_LOSS"; + } + + // No default clause, clang will abort if a code is missing from + // above switch. + return "UNKNOWN"; +} +} // namespace error. + +const Status Status::OK = Status(); +const Status Status::CANCELLED = Status(error::CANCELLED, ""); +const Status Status::UNKNOWN = Status(error::UNKNOWN, ""); + +Status::Status() : error_code_(error::OK) { +} + +Status::Status(error::Code error_code, StringPiece error_message) + : error_code_(error_code) { + if (error_code != error::OK) { + error_message_ = error_message.ToString(); + } +} + +Status::Status(const Status& other) + : error_code_(other.error_code_), error_message_(other.error_message_) { +} + +Status& Status::operator=(const Status& other) { + error_code_ = other.error_code_; + error_message_ = other.error_message_; + return *this; +} + +bool Status::operator==(const Status& x) const { + return error_code_ == x.error_code_ && + error_message_ == x.error_message_; +} + +string Status::ToString() const { + if (error_code_ == error::OK) { + return "OK"; + } else { + if (error_message_.empty()) { + return error::CodeEnumToString(error_code_); + } else { + return error::CodeEnumToString(error_code_) + ":" + + error_message_; + } + } +} + +ostream& operator<<(ostream& os, const Status& x) { + os << x.ToString(); + return os; +} + +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/stubs/status.h b/src/google/protobuf/stubs/status.h new file mode 100644 index 00000000..34d6cf12 --- /dev/null +++ b/src/google/protobuf/stubs/status.h @@ -0,0 +1,116 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#ifndef GOOGLE_PROTOBUF_STUBS_STATUS_H_ +#define GOOGLE_PROTOBUF_STUBS_STATUS_H_ + +#include +#include + +#include +#include + +namespace google { +namespace protobuf { +namespace util { +namespace error { +// These values must match error codes defined in google/rpc/code.proto. +enum Code { + OK = 0, + CANCELLED = 1, + UNKNOWN = 2, + INVALID_ARGUMENT = 3, + DEADLINE_EXCEEDED = 4, + NOT_FOUND = 5, + ALREADY_EXISTS = 6, + PERMISSION_DENIED = 7, + UNAUTHENTICATED = 16, + RESOURCE_EXHAUSTED = 8, + FAILED_PRECONDITION = 9, + ABORTED = 10, + OUT_OF_RANGE = 11, + UNIMPLEMENTED = 12, + INTERNAL = 13, + UNAVAILABLE = 14, + DATA_LOSS = 15, +}; +} // namespace error + +class Status { + public: + // Creates a "successful" status. + Status(); + + // Create a status in the canonical error space with the specified + // code, and error message. If "code == 0", error_message is + // ignored and a Status object identical to Status::OK is + // constructed. + Status(error::Code error_code, StringPiece error_message); + Status(const Status&); + Status& operator=(const Status& x); + ~Status() {} + + // Some pre-defined Status objects + static const Status OK; // Identical to 0-arg constructor + static const Status CANCELLED; + static const Status UNKNOWN; + + // Accessor + bool ok() const { + return error_code_ == error::OK; + } + int error_code() const { + return error_code_; + } + StringPiece error_message() const { + return error_message_; + } + + bool operator==(const Status& x) const; + bool operator!=(const Status& x) const { + return !operator==(x); + } + + // Return a combination of the error code name and message. + string ToString() const; + + private: + error::Code error_code_; + string error_message_; +}; + +// Prints a human-readable representation of 'x' to 'os'. +ostream& operator<<(ostream& os, const Status& x); + +#define EXPECT_OK(value) EXPECT_TRUE((value).ok()) + +} // namespace util +} // namespace protobuf +} // namespace google +#endif // GOOGLE_PROTOBUF_STUBS_STATUS_H_ diff --git a/src/google/protobuf/stubs/status_macros.h b/src/google/protobuf/stubs/status_macros.h new file mode 100644 index 00000000..743e79a7 --- /dev/null +++ b/src/google/protobuf/stubs/status_macros.h @@ -0,0 +1,89 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// From: util/task/contrib/status_macros/status_macros.h + +#ifndef GOOGLE_PROTOBUF_STUBS_STATUS_MACROS_H_ +#define GOOGLE_PROTOBUF_STUBS_STATUS_MACROS_H_ + +#include +#include +#include + +namespace google { +namespace protobuf { +namespace util { + +// Run a command that returns a util::Status. If the called code returns an +// error status, return that status up out of this method too. +// +// Example: +// RETURN_IF_ERROR(DoThings(4)); +#define RETURN_IF_ERROR(expr) \ + do { \ + /* Using _status below to avoid capture problems if expr is "status". */ \ + const ::google::protobuf::util::Status _status = (expr); \ + if (GOOGLE_PREDICT_FALSE(!_status.ok())) return _status; \ + } while (0) + +// Internal helper for concatenating macro values. +#define STATUS_MACROS_CONCAT_NAME_INNER(x, y) x##y +#define STATUS_MACROS_CONCAT_NAME(x, y) STATUS_MACROS_CONCAT_NAME_INNER(x, y) + +template +Status DoAssignOrReturn(T& lhs, StatusOr result) { + if (result.ok()) { + lhs = result.ValueOrDie(); + } + return result.status(); +} + +#define ASSIGN_OR_RETURN_IMPL(status, lhs, rexpr) \ + Status status = DoAssignOrReturn(lhs, (rexpr)); \ + if (GOOGLE_PREDICT_FALSE(!status.ok())) return status; + +// Executes an expression that returns a util::StatusOr, extracting its value +// into the variable defined by lhs (or returning on error). +// +// Example: Assigning to an existing value +// ValueType value; +// ASSIGN_OR_RETURN(value, MaybeGetValue(arg)); +// +// WARNING: ASSIGN_OR_RETURN expands into multiple statements; it cannot be used +// in a single statement (e.g. as the body of an if statement without {})! +#define ASSIGN_OR_RETURN(lhs, rexpr) \ + ASSIGN_OR_RETURN_IMPL( \ + STATUS_MACROS_CONCAT_NAME(_status_or_value, __COUNTER__), lhs, rexpr); + +} // namespace util +} // namespace protobuf +} // namespace google + +#endif // GOOGLE_PROTOBUF_STUBS_STATUS_H_ diff --git a/src/google/protobuf/stubs/status_test.cc b/src/google/protobuf/stubs/status_test.cc new file mode 100644 index 00000000..c70c33c4 --- /dev/null +++ b/src/google/protobuf/stubs/status_test.cc @@ -0,0 +1,131 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include + +#include + +#include +#include + +namespace google { +namespace protobuf { +namespace { +TEST(Status, Empty) { + util::Status status; + EXPECT_EQ(util::error::OK, util::Status::OK.error_code()); + EXPECT_EQ("OK", util::Status::OK.ToString()); +} + +TEST(Status, GenericCodes) { + EXPECT_EQ(util::error::OK, util::Status::OK.error_code()); + EXPECT_EQ(util::error::CANCELLED, util::Status::CANCELLED.error_code()); + EXPECT_EQ(util::error::UNKNOWN, util::Status::UNKNOWN.error_code()); +} + +TEST(Status, ConstructorZero) { + util::Status status(util::error::OK, "msg"); + EXPECT_TRUE(status.ok()); + EXPECT_EQ("OK", status.ToString()); +} + +TEST(Status, CheckOK) { + util::Status status; + GOOGLE_CHECK_OK(status); + GOOGLE_CHECK_OK(status) << "Failed"; + GOOGLE_DCHECK_OK(status) << "Failed"; +} + +TEST(Status, ErrorMessage) { + util::Status status(util::error::INVALID_ARGUMENT, ""); + EXPECT_FALSE(status.ok()); + EXPECT_EQ("", status.error_message().ToString()); + EXPECT_EQ("INVALID_ARGUMENT", status.ToString()); + status = util::Status(util::error::INVALID_ARGUMENT, "msg"); + EXPECT_FALSE(status.ok()); + EXPECT_EQ("msg", status.error_message().ToString()); + EXPECT_EQ("INVALID_ARGUMENT:msg", status.ToString()); + status = util::Status(util::error::OK, "msg"); + EXPECT_TRUE(status.ok()); + EXPECT_EQ("", status.error_message().ToString()); + EXPECT_EQ("OK", status.ToString()); +} + +TEST(Status, Copy) { + util::Status a(util::error::UNKNOWN, "message"); + util::Status b(a); + ASSERT_EQ(a.ToString(), b.ToString()); +} + +TEST(Status, Assign) { + util::Status a(util::error::UNKNOWN, "message"); + util::Status b; + b = a; + ASSERT_EQ(a.ToString(), b.ToString()); +} + +TEST(Status, AssignEmpty) { + util::Status a(util::error::UNKNOWN, "message"); + util::Status b; + a = b; + ASSERT_EQ(string("OK"), a.ToString()); + ASSERT_TRUE(b.ok()); + ASSERT_TRUE(a.ok()); +} + +TEST(Status, EqualsOK) { + ASSERT_EQ(util::Status::OK, util::Status()); +} + +TEST(Status, EqualsSame) { + const util::Status a = util::Status(util::error::CANCELLED, "message"); + const util::Status b = util::Status(util::error::CANCELLED, "message"); + ASSERT_EQ(a, b); +} + +TEST(Status, EqualsCopy) { + const util::Status a = util::Status(util::error::CANCELLED, "message"); + const util::Status b = a; + ASSERT_EQ(a, b); +} + +TEST(Status, EqualsDifferentCode) { + const util::Status a = util::Status(util::error::CANCELLED, "message"); + const util::Status b = util::Status(util::error::UNKNOWN, "message"); + ASSERT_NE(a, b); +} + +TEST(Status, EqualsDifferentMessage) { + const util::Status a = util::Status(util::error::CANCELLED, "message"); + const util::Status b = util::Status(util::error::CANCELLED, "another"); + ASSERT_NE(a, b); +} +} // namespace +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/stubs/statusor.cc b/src/google/protobuf/stubs/statusor.cc new file mode 100644 index 00000000..48d1402a --- /dev/null +++ b/src/google/protobuf/stubs/statusor.cc @@ -0,0 +1,46 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +namespace google { +namespace protobuf { +namespace util { +namespace internal { + +void StatusOrHelper::Crash(const Status& status) { + GOOGLE_LOG(FATAL) << "Attempting to fetch value instead of handling error " + << status.ToString(); +} + +} // namespace internal +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/stubs/statusor.h b/src/google/protobuf/stubs/statusor.h new file mode 100644 index 00000000..9495107e --- /dev/null +++ b/src/google/protobuf/stubs/statusor.h @@ -0,0 +1,259 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// StatusOr is the union of a Status object and a T +// object. StatusOr models the concept of an object that is either a +// usable value, or an error Status explaining why such a value is +// not present. To this end, StatusOr does not allow its Status +// value to be Status::OK. Further, StatusOr does not allow the +// contained pointer to be NULL. +// +// The primary use-case for StatusOr is as the return value of a +// function which may fail. +// +// Example client usage for a StatusOr, where T is not a pointer: +// +// StatusOr result = DoBigCalculationThatCouldFail(); +// if (result.ok()) { +// float answer = result.ValueOrDie(); +// printf("Big calculation yielded: %f", answer); +// } else { +// LOG(ERROR) << result.status(); +// } +// +// Example client usage for a StatusOr: +// +// StatusOr result = FooFactory::MakeNewFoo(arg); +// if (result.ok()) { +// std::unique_ptr foo(result.ValueOrDie()); +// foo->DoSomethingCool(); +// } else { +// LOG(ERROR) << result.status(); +// } +// +// Example client usage for a StatusOr>: +// +// StatusOr> result = FooFactory::MakeNewFoo(arg); +// if (result.ok()) { +// std::unique_ptr foo = result.ConsumeValueOrDie(); +// foo->DoSomethingCool(); +// } else { +// LOG(ERROR) << result.status(); +// } +// +// Example factory implementation returning StatusOr: +// +// StatusOr FooFactory::MakeNewFoo(int arg) { +// if (arg <= 0) { +// return ::util::Status(::util::error::INVALID_ARGUMENT, +// "Arg must be positive"); +// } else { +// return new Foo(arg); +// } +// } +// + +#ifndef GOOGLE_PROTOBUF_STUBS_STATUSOR_H_ +#define GOOGLE_PROTOBUF_STUBS_STATUSOR_H_ + +#include +#include +#include + +#include + +namespace google { +namespace protobuf { +namespace util { + +template +class StatusOr { + template friend class StatusOr; + + public: + // Construct a new StatusOr with Status::UNKNOWN status + StatusOr(); + + // Construct a new StatusOr with the given non-ok status. After calling + // this constructor, calls to ValueOrDie() will CHECK-fail. + // + // NOTE: Not explicit - we want to use StatusOr as a return + // value, so it is convenient and sensible to be able to do 'return + // Status()' when the return type is StatusOr. + // + // REQUIRES: status != Status::OK. This requirement is DCHECKed. + // In optimized builds, passing Status::OK here will have the effect + // of passing PosixErrorSpace::EINVAL as a fallback. + StatusOr(const Status& status); // NOLINT + + // Construct a new StatusOr with the given value. If T is a plain pointer, + // value must not be NULL. After calling this constructor, calls to + // ValueOrDie() will succeed, and calls to status() will return OK. + // + // NOTE: Not explicit - we want to use StatusOr as a return type + // so it is convenient and sensible to be able to do 'return T()' + // when when the return type is StatusOr. + // + // REQUIRES: if T is a plain pointer, value != NULL. This requirement is + // DCHECKed. In optimized builds, passing a NULL pointer here will have + // the effect of passing PosixErrorSpace::EINVAL as a fallback. + StatusOr(const T& value); // NOLINT + + // Copy constructor. + StatusOr(const StatusOr& other); + + // Conversion copy constructor, T must be copy constructible from U + template + StatusOr(const StatusOr& other); + + // Assignment operator. + StatusOr& operator=(const StatusOr& other); + + // Conversion assignment operator, T must be assignable from U + template + StatusOr& operator=(const StatusOr& other); + + // Returns a reference to our status. If this contains a T, then + // returns Status::OK. + const Status& status() const; + + // Returns this->status().ok() + bool ok() const; + + // Returns a reference to our current value, or CHECK-fails if !this->ok(). + // If you need to initialize a T object from the stored value, + // ConsumeValueOrDie() may be more efficient. + const T& ValueOrDie() const; + + private: + Status status_; + T value_; +}; + +//////////////////////////////////////////////////////////////////////////////// +// Implementation details for StatusOr + +namespace internal { + +class StatusOrHelper { + public: + // Move type-agnostic error handling to the .cc. + static void Crash(const util::Status& status); + + // Customized behavior for StatusOr vs. StatusOr + template + struct Specialize; +}; + +template +struct StatusOrHelper::Specialize { + // For non-pointer T, a reference can never be NULL. + static inline bool IsValueNull(const T& t) { return false; } +}; + +template +struct StatusOrHelper::Specialize { + static inline bool IsValueNull(const T* t) { return t == NULL; } +}; + +} // namespace internal + +template +inline StatusOr::StatusOr() + : status_(util::Status::UNKNOWN) { +} + +template +inline StatusOr::StatusOr(const Status& status) { + if (status.ok()) { + status_ = Status(error::INTERNAL, "Status::OK is not a valid argument."); + } else { + status_ = status; + } +} + +template +inline StatusOr::StatusOr(const T& value) { + if (internal::StatusOrHelper::Specialize::IsValueNull(value)) { + status_ = Status(error::INTERNAL, "NULL is not a vaild argument."); + } else { + status_ = Status::OK; + value_ = value; + } +} + +template +inline StatusOr::StatusOr(const StatusOr& other) + : status_(other.status_), value_(other.value_) { +} + +template +inline StatusOr& StatusOr::operator=(const StatusOr& other) { + status_ = other.status_; + value_ = other.value_; + return *this; +} + +template +template +inline StatusOr::StatusOr(const StatusOr& other) + : status_(other.status_), value_(other.value_) { +} + +template +template +inline StatusOr& StatusOr::operator=(const StatusOr& other) { + status_ = other.status_; + value_ = other.value_; + return *this; +} + +template +inline const Status& StatusOr::status() const { + return status_; +} + +template +inline bool StatusOr::ok() const { + return status().ok(); +} + +template +inline const T& StatusOr::ValueOrDie() const { + if (!status_.ok()) { + internal::StatusOrHelper::Crash(status_); + } + return value_; +} +} // namespace util +} // namespace protobuf +} // namespace google + +#endif // GOOGLE_PROTOBUF_STUBS_STATUSOR_H_ diff --git a/src/google/protobuf/stubs/statusor_test.cc b/src/google/protobuf/stubs/statusor_test.cc new file mode 100644 index 00000000..6e2a9e55 --- /dev/null +++ b/src/google/protobuf/stubs/statusor_test.cc @@ -0,0 +1,274 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include +#include + +#include +#include + +namespace google { +namespace protobuf { +namespace util { +namespace { + +class Base1 { + public: + virtual ~Base1() {} + int pad; +}; + +class Base2 { + public: + virtual ~Base2() {} + int yetotherpad; +}; + +class Derived : public Base1, public Base2 { + public: + virtual ~Derived() {} + int evenmorepad; +}; + +class CopyNoAssign { + public: + explicit CopyNoAssign(int value) : foo(value) {} + CopyNoAssign(const CopyNoAssign& other) : foo(other.foo) {} + int foo; + private: + const CopyNoAssign& operator=(const CopyNoAssign&); +}; + +TEST(StatusOr, TestDefaultCtor) { + StatusOr thing; + EXPECT_FALSE(thing.ok()); + EXPECT_EQ(Status::UNKNOWN, thing.status()); +} + +TEST(StatusOr, TestStatusCtor) { + StatusOr thing(Status::CANCELLED); + EXPECT_FALSE(thing.ok()); + EXPECT_EQ(Status::CANCELLED, thing.status()); +} + +TEST(StatusOr, TestValueCtor) { + const int kI = 4; + StatusOr thing(kI); + EXPECT_TRUE(thing.ok()); + EXPECT_EQ(kI, thing.ValueOrDie()); +} + +TEST(StatusOr, TestCopyCtorStatusOk) { + const int kI = 4; + StatusOr original(kI); + StatusOr copy(original); + EXPECT_EQ(original.status(), copy.status()); + EXPECT_EQ(original.ValueOrDie(), copy.ValueOrDie()); +} + +TEST(StatusOr, TestCopyCtorStatusNotOk) { + StatusOr original(Status::CANCELLED); + StatusOr copy(original); + EXPECT_EQ(original.status(), copy.status()); +} + +TEST(StatusOr, TestCopyCtorStatusOKConverting) { + const int kI = 4; + StatusOr original(kI); + StatusOr copy(original); + EXPECT_EQ(original.status(), copy.status()); + EXPECT_EQ(original.ValueOrDie(), copy.ValueOrDie()); +} + +TEST(StatusOr, TestCopyCtorStatusNotOkConverting) { + StatusOr original(Status::CANCELLED); + StatusOr copy(original); + EXPECT_EQ(original.status(), copy.status()); +} + +TEST(StatusOr, TestAssignmentStatusOk) { + const int kI = 4; + StatusOr source(kI); + StatusOr target; + target = source; + EXPECT_EQ(source.status(), target.status()); + EXPECT_EQ(source.ValueOrDie(), target.ValueOrDie()); +} + +TEST(StatusOr, TestAssignmentStatusNotOk) { + StatusOr source(Status::CANCELLED); + StatusOr target; + target = source; + EXPECT_EQ(source.status(), target.status()); +} + +TEST(StatusOr, TestAssignmentStatusOKConverting) { + const int kI = 4; + StatusOr source(kI); + StatusOr target; + target = source; + EXPECT_EQ(source.status(), target.status()); + EXPECT_DOUBLE_EQ(source.ValueOrDie(), target.ValueOrDie()); +} + +TEST(StatusOr, TestAssignmentStatusNotOkConverting) { + StatusOr source(Status::CANCELLED); + StatusOr target; + target = source; + EXPECT_EQ(source.status(), target.status()); +} + +TEST(StatusOr, TestStatus) { + StatusOr good(4); + EXPECT_TRUE(good.ok()); + StatusOr bad(Status::CANCELLED); + EXPECT_FALSE(bad.ok()); + EXPECT_EQ(Status::CANCELLED, bad.status()); +} + +TEST(StatusOr, TestValue) { + const int kI = 4; + StatusOr thing(kI); + EXPECT_EQ(kI, thing.ValueOrDie()); +} + +TEST(StatusOr, TestValueConst) { + const int kI = 4; + const StatusOr thing(kI); + EXPECT_EQ(kI, thing.ValueOrDie()); +} + +TEST(StatusOr, TestPointerDefaultCtor) { + StatusOr thing; + EXPECT_FALSE(thing.ok()); + EXPECT_EQ(Status::UNKNOWN, thing.status()); +} + +TEST(StatusOr, TestPointerStatusCtor) { + StatusOr thing(Status::CANCELLED); + EXPECT_FALSE(thing.ok()); + EXPECT_EQ(Status::CANCELLED, thing.status()); +} + +TEST(StatusOr, TestPointerValueCtor) { + const int kI = 4; + StatusOr thing(&kI); + EXPECT_TRUE(thing.ok()); + EXPECT_EQ(&kI, thing.ValueOrDie()); +} + +TEST(StatusOr, TestPointerCopyCtorStatusOk) { + const int kI = 0; + StatusOr original(&kI); + StatusOr copy(original); + EXPECT_EQ(original.status(), copy.status()); + EXPECT_EQ(original.ValueOrDie(), copy.ValueOrDie()); +} + +TEST(StatusOr, TestPointerCopyCtorStatusNotOk) { + StatusOr original(Status::CANCELLED); + StatusOr copy(original); + EXPECT_EQ(original.status(), copy.status()); +} + +TEST(StatusOr, TestPointerCopyCtorStatusOKConverting) { + Derived derived; + StatusOr original(&derived); + StatusOr copy(original); + EXPECT_EQ(original.status(), copy.status()); + EXPECT_EQ(static_cast(original.ValueOrDie()), + copy.ValueOrDie()); +} + +TEST(StatusOr, TestPointerCopyCtorStatusNotOkConverting) { + StatusOr original(Status::CANCELLED); + StatusOr copy(original); + EXPECT_EQ(original.status(), copy.status()); +} + +TEST(StatusOr, TestPointerAssignmentStatusOk) { + const int kI = 0; + StatusOr source(&kI); + StatusOr target; + target = source; + EXPECT_EQ(source.status(), target.status()); + EXPECT_EQ(source.ValueOrDie(), target.ValueOrDie()); +} + +TEST(StatusOr, TestPointerAssignmentStatusNotOk) { + StatusOr source(Status::CANCELLED); + StatusOr target; + target = source; + EXPECT_EQ(source.status(), target.status()); +} + +TEST(StatusOr, TestPointerAssignmentStatusOKConverting) { + Derived derived; + StatusOr source(&derived); + StatusOr target; + target = source; + EXPECT_EQ(source.status(), target.status()); + EXPECT_EQ(static_cast(source.ValueOrDie()), + target.ValueOrDie()); +} + +TEST(StatusOr, TestPointerAssignmentStatusNotOkConverting) { + StatusOr source(Status::CANCELLED); + StatusOr target; + target = source; + EXPECT_EQ(source.status(), target.status()); +} + +TEST(StatusOr, TestPointerStatus) { + const int kI = 0; + StatusOr good(&kI); + EXPECT_TRUE(good.ok()); + StatusOr bad(Status::CANCELLED); + EXPECT_EQ(Status::CANCELLED, bad.status()); +} + +TEST(StatusOr, TestPointerValue) { + const int kI = 0; + StatusOr thing(&kI); + EXPECT_EQ(&kI, thing.ValueOrDie()); +} + +TEST(StatusOr, TestPointerValueConst) { + const int kI = 0; + const StatusOr thing(&kI); + EXPECT_EQ(&kI, thing.ValueOrDie()); +} + +} // namespace +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/stubs/stringpiece.cc b/src/google/protobuf/stubs/stringpiece.cc new file mode 100644 index 00000000..989474b7 --- /dev/null +++ b/src/google/protobuf/stubs/stringpiece.cc @@ -0,0 +1,268 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include + +#include +#include +#include +#include +#include + +namespace google { +namespace protobuf { +std::ostream& operator<<(std::ostream& o, StringPiece piece) { + o.write(piece.data(), piece.size()); + return o; +} + +// Out-of-line error path. +void StringPiece::LogFatalSizeTooBig(size_t size, const char* details) { + GOOGLE_LOG(FATAL) << "size too big: " << size << " details: " << details; +} + +StringPiece::StringPiece(StringPiece x, stringpiece_ssize_type pos) + : ptr_(x.ptr_ + pos), length_(x.length_ - pos) { + GOOGLE_DCHECK_LE(0, pos); + GOOGLE_DCHECK_LE(pos, x.length_); +} + +StringPiece::StringPiece(StringPiece x, + stringpiece_ssize_type pos, + stringpiece_ssize_type len) + : ptr_(x.ptr_ + pos), length_(std::min(len, x.length_ - pos)) { + GOOGLE_DCHECK_LE(0, pos); + GOOGLE_DCHECK_LE(pos, x.length_); + GOOGLE_DCHECK_GE(len, 0); +} + +void StringPiece::CopyToString(string* target) const { + target->assign(ptr_, length_); +} + +void StringPiece::AppendToString(string* target) const { + target->append(ptr_, length_); +} + +bool StringPiece::Consume(StringPiece x) { + if (starts_with(x)) { + ptr_ += x.length_; + length_ -= x.length_; + return true; + } + return false; +} + +bool StringPiece::ConsumeFromEnd(StringPiece x) { + if (ends_with(x)) { + length_ -= x.length_; + return true; + } + return false; +} + +stringpiece_ssize_type StringPiece::copy(char* buf, + size_type n, + size_type pos) const { + stringpiece_ssize_type ret = std::min(length_ - pos, n); + memcpy(buf, ptr_ + pos, ret); + return ret; +} + +bool StringPiece::contains(StringPiece s) const { + return find(s, 0) != npos; +} + +stringpiece_ssize_type StringPiece::find(StringPiece s, size_type pos) const { + if (length_ <= 0 || pos > static_cast(length_)) { + if (length_ == 0 && pos == 0 && s.length_ == 0) return 0; + return npos; + } + const char *result = std::search(ptr_ + pos, ptr_ + length_, + s.ptr_, s.ptr_ + s.length_); + return result == ptr_ + length_ ? npos : result - ptr_; +} + +stringpiece_ssize_type StringPiece::find(char c, size_type pos) const { + if (length_ <= 0 || pos >= static_cast(length_)) { + return npos; + } + const char* result = static_cast( + memchr(ptr_ + pos, c, length_ - pos)); + return result != NULL ? result - ptr_ : npos; +} + +stringpiece_ssize_type StringPiece::rfind(StringPiece s, size_type pos) const { + if (length_ < s.length_) return npos; + const size_t ulen = length_; + if (s.length_ == 0) return std::min(ulen, pos); + + const char* last = ptr_ + std::min(ulen - s.length_, pos) + s.length_; + const char* result = std::find_end(ptr_, last, s.ptr_, s.ptr_ + s.length_); + return result != last ? result - ptr_ : npos; +} + +// Search range is [0..pos] inclusive. If pos == npos, search everything. +stringpiece_ssize_type StringPiece::rfind(char c, size_type pos) const { + // Note: memrchr() is not available on Windows. + if (length_ <= 0) return npos; + for (stringpiece_ssize_type i = + std::min(pos, static_cast(length_ - 1)); + i >= 0; --i) { + if (ptr_[i] == c) { + return i; + } + } + return npos; +} + +// For each character in characters_wanted, sets the index corresponding +// to the ASCII code of that character to 1 in table. This is used by +// the find_.*_of methods below to tell whether or not a character is in +// the lookup table in constant time. +// The argument `table' must be an array that is large enough to hold all +// the possible values of an unsigned char. Thus it should be be declared +// as follows: +// bool table[UCHAR_MAX + 1] +static inline void BuildLookupTable(StringPiece characters_wanted, + bool* table) { + const stringpiece_ssize_type length = characters_wanted.length(); + const char* const data = characters_wanted.data(); + for (stringpiece_ssize_type i = 0; i < length; ++i) { + table[static_cast(data[i])] = true; + } +} + +stringpiece_ssize_type StringPiece::find_first_of(StringPiece s, + size_type pos) const { + if (length_ <= 0 || s.length_ <= 0) { + return npos; + } + // Avoid the cost of BuildLookupTable() for a single-character search. + if (s.length_ == 1) return find_first_of(s.ptr_[0], pos); + + bool lookup[UCHAR_MAX + 1] = { false }; + BuildLookupTable(s, lookup); + for (stringpiece_ssize_type i = pos; i < length_; ++i) { + if (lookup[static_cast(ptr_[i])]) { + return i; + } + } + return npos; +} + +stringpiece_ssize_type StringPiece::find_first_not_of(StringPiece s, + size_type pos) const { + if (length_ <= 0) return npos; + if (s.length_ <= 0) return 0; + // Avoid the cost of BuildLookupTable() for a single-character search. + if (s.length_ == 1) return find_first_not_of(s.ptr_[0], pos); + + bool lookup[UCHAR_MAX + 1] = { false }; + BuildLookupTable(s, lookup); + for (stringpiece_ssize_type i = pos; i < length_; ++i) { + if (!lookup[static_cast(ptr_[i])]) { + return i; + } + } + return npos; +} + +stringpiece_ssize_type StringPiece::find_first_not_of(char c, + size_type pos) const { + if (length_ <= 0) return npos; + + for (; pos < static_cast(length_); ++pos) { + if (ptr_[pos] != c) { + return pos; + } + } + return npos; +} + +stringpiece_ssize_type StringPiece::find_last_of(StringPiece s, + size_type pos) const { + if (length_ <= 0 || s.length_ <= 0) return npos; + // Avoid the cost of BuildLookupTable() for a single-character search. + if (s.length_ == 1) return find_last_of(s.ptr_[0], pos); + + bool lookup[UCHAR_MAX + 1] = { false }; + BuildLookupTable(s, lookup); + for (stringpiece_ssize_type i = + std::min(pos, static_cast(length_ - 1)); i >= 0; --i) { + if (lookup[static_cast(ptr_[i])]) { + return i; + } + } + return npos; +} + +stringpiece_ssize_type StringPiece::find_last_not_of(StringPiece s, + size_type pos) const { + if (length_ <= 0) return npos; + + stringpiece_ssize_type i = std::min(pos, static_cast(length_ - 1)); + if (s.length_ <= 0) return i; + + // Avoid the cost of BuildLookupTable() for a single-character search. + if (s.length_ == 1) return find_last_not_of(s.ptr_[0], pos); + + bool lookup[UCHAR_MAX + 1] = { false }; + BuildLookupTable(s, lookup); + for (; i >= 0; --i) { + if (!lookup[static_cast(ptr_[i])]) { + return i; + } + } + return npos; +} + +stringpiece_ssize_type StringPiece::find_last_not_of(char c, + size_type pos) const { + if (length_ <= 0) return npos; + + for (stringpiece_ssize_type i = + std::min(pos, static_cast(length_ - 1)); i >= 0; --i) { + if (ptr_[i] != c) { + return i; + } + } + return npos; +} + +StringPiece StringPiece::substr(size_type pos, size_type n) const { + if (pos > length_) pos = length_; + if (n > length_ - pos) n = length_ - pos; + return StringPiece(ptr_ + pos, n); +} + +const StringPiece::size_type StringPiece::npos = size_type(-1); + +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/stubs/stringpiece.h b/src/google/protobuf/stubs/stringpiece.h new file mode 100644 index 00000000..5d4d5fe4 --- /dev/null +++ b/src/google/protobuf/stubs/stringpiece.h @@ -0,0 +1,437 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// A StringPiece points to part or all of a string, Cord, double-quoted string +// literal, or other string-like object. A StringPiece does *not* own the +// string to which it points. A StringPiece is not null-terminated. +// +// You can use StringPiece as a function or method parameter. A StringPiece +// parameter can receive a double-quoted string literal argument, a "const +// char*" argument, a string argument, or a StringPiece argument with no data +// copying. Systematic use of StringPiece for arguments reduces data +// copies and strlen() calls. +// +// Prefer passing StringPieces by value: +// void MyFunction(StringPiece arg); +// If circumstances require, you may also pass by const reference: +// void MyFunction(const StringPiece& arg); // not preferred +// Both of these have the same lifetime semantics. Passing by value +// generates slightly smaller code. For more discussion, see the thread +// go/stringpiecebyvalue on c-users. +// +// StringPiece is also suitable for local variables if you know that +// the lifetime of the underlying object is longer than the lifetime +// of your StringPiece variable. +// +// Beware of binding a StringPiece to a temporary: +// StringPiece sp = obj.MethodReturningString(); // BAD: lifetime problem +// +// This code is okay: +// string str = obj.MethodReturningString(); // str owns its contents +// StringPiece sp(str); // GOOD, because str outlives sp +// +// StringPiece is sometimes a poor choice for a return value and usually a poor +// choice for a data member. If you do use a StringPiece this way, it is your +// responsibility to ensure that the object pointed to by the StringPiece +// outlives the StringPiece. +// +// A StringPiece may represent just part of a string; thus the name "Piece". +// For example, when splitting a string, vector is a natural data +// type for the output. For another example, a Cord is a non-contiguous, +// potentially very long string-like object. The Cord class has an interface +// that iteratively provides StringPiece objects that point to the +// successive pieces of a Cord object. +// +// A StringPiece is not null-terminated. If you write code that scans a +// StringPiece, you must check its length before reading any characters. +// Common idioms that work on null-terminated strings do not work on +// StringPiece objects. +// +// There are several ways to create a null StringPiece: +// StringPiece() +// StringPiece(NULL) +// StringPiece(NULL, 0) +// For all of the above, sp.data() == NULL, sp.length() == 0, +// and sp.empty() == true. Also, if you create a StringPiece with +// a non-NULL pointer then sp.data() != NULL. Once created, +// sp.data() will stay either NULL or not-NULL, except if you call +// sp.clear() or sp.set(). +// +// Thus, you can use StringPiece(NULL) to signal an out-of-band value +// that is different from other StringPiece values. This is similar +// to the way that const char* p1 = NULL; is different from +// const char* p2 = "";. +// +// There are many ways to create an empty StringPiece: +// StringPiece() +// StringPiece(NULL) +// StringPiece(NULL, 0) +// StringPiece("") +// StringPiece("", 0) +// StringPiece("abcdef", 0) +// StringPiece("abcdef"+6, 0) +// For all of the above, sp.length() will be 0 and sp.empty() will be true. +// For some empty StringPiece values, sp.data() will be NULL. +// For some empty StringPiece values, sp.data() will not be NULL. +// +// Be careful not to confuse: null StringPiece and empty StringPiece. +// The set of empty StringPieces properly includes the set of null StringPieces. +// That is, every null StringPiece is an empty StringPiece, +// but some non-null StringPieces are empty Stringpieces too. +// +// All empty StringPiece values compare equal to each other. +// Even a null StringPieces compares equal to a non-null empty StringPiece: +// StringPiece() == StringPiece("", 0) +// StringPiece(NULL) == StringPiece("abc", 0) +// StringPiece(NULL, 0) == StringPiece("abcdef"+6, 0) +// +// Look carefully at this example: +// StringPiece("") == NULL +// True or false? TRUE, because StringPiece::operator== converts +// the right-hand side from NULL to StringPiece(NULL), +// and then compares two zero-length spans of characters. +// However, we are working to make this example produce a compile error. +// +// Suppose you want to write: +// bool TestWhat?(StringPiece sp) { return sp == NULL; } // BAD +// Do not do that. Write one of these instead: +// bool TestNull(StringPiece sp) { return sp.data() == NULL; } +// bool TestEmpty(StringPiece sp) { return sp.empty(); } +// The intent of TestWhat? is unclear. Did you mean TestNull or TestEmpty? +// Right now, TestWhat? behaves likes TestEmpty. +// We are working to make TestWhat? produce a compile error. +// TestNull is good to test for an out-of-band signal. +// TestEmpty is good to test for an empty StringPiece. +// +// Caveats (again): +// (1) The lifetime of the pointed-to string (or piece of a string) +// must be longer than the lifetime of the StringPiece. +// (2) There may or may not be a '\0' character after the end of +// StringPiece data. +// (3) A null StringPiece is empty. +// An empty StringPiece may or may not be a null StringPiece. + +#ifndef GOOGLE_PROTOBUF_STUBS_STRINGPIECE_H_ +#define GOOGLE_PROTOBUF_STUBS_STRINGPIECE_H_ + +#include +#include +#include +#include +#include +#include + +#include + +namespace google { +namespace protobuf { +// StringPiece has *two* size types. +// StringPiece::size_type +// is unsigned +// is 32 bits in LP32, 64 bits in LP64, 64 bits in LLP64 +// no future changes intended +// stringpiece_ssize_type +// is signed +// is 32 bits in LP32, 64 bits in LP64, 64 bits in LLP64 +// future changes intended: http://go/64BitStringPiece +// +typedef string::difference_type stringpiece_ssize_type; + +// STRINGPIECE_CHECK_SIZE protects us from 32-bit overflows. +// TODO(mec): delete this after stringpiece_ssize_type goes 64 bit. +#if !defined(NDEBUG) +#define STRINGPIECE_CHECK_SIZE 1 +#elif defined(_FORTIFY_SOURCE) && _FORTIFY_SOURCE > 0 +#define STRINGPIECE_CHECK_SIZE 1 +#else +#define STRINGPIECE_CHECK_SIZE 0 +#endif + +class StringPiece { + private: + const char* ptr_; + stringpiece_ssize_type length_; + + // Prevent overflow in debug mode or fortified mode. + // sizeof(stringpiece_ssize_type) may be smaller than sizeof(size_t). + static stringpiece_ssize_type CheckedSsizeTFromSizeT(size_t size) { +#if STRINGPIECE_CHECK_SIZE > 0 + if (size > static_cast( + std::numeric_limits::max())) { + // Some people grep for this message in logs + // so take care if you ever change it. + LogFatalSizeTooBig(size, "size_t to int conversion"); + } +#endif + return static_cast(size); + } + + // Out-of-line error path. + static void LogFatalSizeTooBig(size_t size, const char* details); + + public: + // We provide non-explicit singleton constructors so users can pass + // in a "const char*" or a "string" wherever a "StringPiece" is + // expected. + // + // Style guide exception granted: + // http://goto/style-guide-exception-20978288 + StringPiece() : ptr_(NULL), length_(0) {} + + StringPiece(const char* str) // NOLINT(runtime/explicit) + : ptr_(str), length_(0) { + if (str != NULL) { + length_ = CheckedSsizeTFromSizeT(strlen(str)); + } + } + + template + StringPiece( // NOLINT(runtime/explicit) + const std::basic_string, Allocator>& str) + : ptr_(str.data()), length_(0) { + length_ = CheckedSsizeTFromSizeT(str.size()); + } +#if defined(HAS_GLOBAL_STRING) + template + StringPiece( // NOLINT(runtime/explicit) + const basic_string, Allocator>& str) + : ptr_(str.data()), length_(0) { + length_ = CheckedSsizeTFromSizeT(str.size()); + } +#endif + + StringPiece(const char* offset, stringpiece_ssize_type len) + : ptr_(offset), length_(len) { + assert(len >= 0); + } + + // Substring of another StringPiece. + // pos must be non-negative and <= x.length(). + StringPiece(StringPiece x, stringpiece_ssize_type pos); + // Substring of another StringPiece. + // pos must be non-negative and <= x.length(). + // len must be non-negative and will be pinned to at most x.length() - pos. + StringPiece(StringPiece x, + stringpiece_ssize_type pos, + stringpiece_ssize_type len); + + // data() may return a pointer to a buffer with embedded NULs, and the + // returned buffer may or may not be null terminated. Therefore it is + // typically a mistake to pass data() to a routine that expects a NUL + // terminated string. + const char* data() const { return ptr_; } + stringpiece_ssize_type size() const { return length_; } + stringpiece_ssize_type length() const { return length_; } + bool empty() const { return length_ == 0; } + + void clear() { + ptr_ = NULL; + length_ = 0; + } + + void set(const char* data, stringpiece_ssize_type len) { + assert(len >= 0); + ptr_ = data; + length_ = len; + } + + void set(const char* str) { + ptr_ = str; + if (str != NULL) + length_ = CheckedSsizeTFromSizeT(strlen(str)); + else + length_ = 0; + } + + void set(const void* data, stringpiece_ssize_type len) { + ptr_ = reinterpret_cast(data); + length_ = len; + } + + char operator[](stringpiece_ssize_type i) const { + assert(0 <= i); + assert(i < length_); + return ptr_[i]; + } + + void remove_prefix(stringpiece_ssize_type n) { + assert(length_ >= n); + ptr_ += n; + length_ -= n; + } + + void remove_suffix(stringpiece_ssize_type n) { + assert(length_ >= n); + length_ -= n; + } + + // returns {-1, 0, 1} + int compare(StringPiece x) const { + const stringpiece_ssize_type min_size = + length_ < x.length_ ? length_ : x.length_; + int r = memcmp(ptr_, x.ptr_, min_size); + if (r < 0) return -1; + if (r > 0) return 1; + if (length_ < x.length_) return -1; + if (length_ > x.length_) return 1; + return 0; + } + + string as_string() const { + return ToString(); + } + // We also define ToString() here, since many other string-like + // interfaces name the routine that converts to a C++ string + // "ToString", and it's confusing to have the method that does that + // for a StringPiece be called "as_string()". We also leave the + // "as_string()" method defined here for existing code. + string ToString() const { + if (ptr_ == NULL) return string(); + return string(data(), size()); + } + + operator string() const { + return ToString(); + } + + void CopyToString(string* target) const; + void AppendToString(string* target) const; + + bool starts_with(StringPiece x) const { + return (length_ >= x.length_) && (memcmp(ptr_, x.ptr_, x.length_) == 0); + } + + bool ends_with(StringPiece x) const { + return ((length_ >= x.length_) && + (memcmp(ptr_ + (length_-x.length_), x.ptr_, x.length_) == 0)); + } + + // Checks whether StringPiece starts with x and if so advances the beginning + // of it to past the match. It's basically a shortcut for starts_with + // followed by remove_prefix. + bool Consume(StringPiece x); + // Like above but for the end of the string. + bool ConsumeFromEnd(StringPiece x); + + // standard STL container boilerplate + typedef char value_type; + typedef const char* pointer; + typedef const char& reference; + typedef const char& const_reference; + typedef size_t size_type; + typedef ptrdiff_t difference_type; + static const size_type npos; + typedef const char* const_iterator; + typedef const char* iterator; + typedef std::reverse_iterator const_reverse_iterator; + typedef std::reverse_iterator reverse_iterator; + iterator begin() const { return ptr_; } + iterator end() const { return ptr_ + length_; } + const_reverse_iterator rbegin() const { + return const_reverse_iterator(ptr_ + length_); + } + const_reverse_iterator rend() const { + return const_reverse_iterator(ptr_); + } + stringpiece_ssize_type max_size() const { return length_; } + stringpiece_ssize_type capacity() const { return length_; } + + // cpplint.py emits a false positive [build/include_what_you_use] + stringpiece_ssize_type copy(char* buf, size_type n, size_type pos = 0) const; // NOLINT + + bool contains(StringPiece s) const; + + stringpiece_ssize_type find(StringPiece s, size_type pos = 0) const; + stringpiece_ssize_type find(char c, size_type pos = 0) const; + stringpiece_ssize_type rfind(StringPiece s, size_type pos = npos) const; + stringpiece_ssize_type rfind(char c, size_type pos = npos) const; + + stringpiece_ssize_type find_first_of(StringPiece s, size_type pos = 0) const; + stringpiece_ssize_type find_first_of(char c, size_type pos = 0) const { + return find(c, pos); + } + stringpiece_ssize_type find_first_not_of(StringPiece s, + size_type pos = 0) const; + stringpiece_ssize_type find_first_not_of(char c, size_type pos = 0) const; + stringpiece_ssize_type find_last_of(StringPiece s, + size_type pos = npos) const; + stringpiece_ssize_type find_last_of(char c, size_type pos = npos) const { + return rfind(c, pos); + } + stringpiece_ssize_type find_last_not_of(StringPiece s, + size_type pos = npos) const; + stringpiece_ssize_type find_last_not_of(char c, size_type pos = npos) const; + + StringPiece substr(size_type pos, size_type n = npos) const; +}; + +// This large function is defined inline so that in a fairly common case where +// one of the arguments is a literal, the compiler can elide a lot of the +// following comparisons. +inline bool operator==(StringPiece x, StringPiece y) { + stringpiece_ssize_type len = x.size(); + if (len != y.size()) { + return false; + } + + return x.data() == y.data() || len <= 0 || + memcmp(x.data(), y.data(), len) == 0; +} + +inline bool operator!=(StringPiece x, StringPiece y) { + return !(x == y); +} + +inline bool operator<(StringPiece x, StringPiece y) { + const stringpiece_ssize_type min_size = + x.size() < y.size() ? x.size() : y.size(); + const int r = memcmp(x.data(), y.data(), min_size); + return (r < 0) || (r == 0 && x.size() < y.size()); +} + +inline bool operator>(StringPiece x, StringPiece y) { + return y < x; +} + +inline bool operator<=(StringPiece x, StringPiece y) { + return !(x > y); +} + +inline bool operator>=(StringPiece x, StringPiece y) { + return !(x < y); +} + +// allow StringPiece to be logged +extern std::ostream& operator<<(std::ostream& o, StringPiece piece); + +} // namespace protobuf +} // namespace google + +#endif // STRINGS_STRINGPIECE_H_ diff --git a/src/google/protobuf/stubs/stringpiece_unittest.cc b/src/google/protobuf/stubs/stringpiece_unittest.cc new file mode 100644 index 00000000..9b5dae13 --- /dev/null +++ b/src/google/protobuf/stubs/stringpiece_unittest.cc @@ -0,0 +1,793 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace google { +namespace protobuf { +namespace { +TEST(StringPiece, Ctor) { + { + // Null. + StringPiece s10; + EXPECT_TRUE(s10.data() == NULL); + EXPECT_EQ(0, s10.length()); + } + + { + // const char* without length. + const char* hello = "hello"; + StringPiece s20(hello); + EXPECT_TRUE(s20.data() == hello); + EXPECT_EQ(5, s20.length()); + + // const char* with length. + StringPiece s21(hello, 4); + EXPECT_TRUE(s21.data() == hello); + EXPECT_EQ(4, s21.length()); + + // Not recommended, but valid C++ + StringPiece s22(hello, 6); + EXPECT_TRUE(s22.data() == hello); + EXPECT_EQ(6, s22.length()); + } + + { + // std::string. + std::string hola = "hola"; + StringPiece s30(hola); + EXPECT_TRUE(s30.data() == hola.data()); + EXPECT_EQ(4, s30.length()); + + // std::string with embedded '\0'. + hola.push_back('\0'); + hola.append("h2"); + hola.push_back('\0'); + StringPiece s31(hola); + EXPECT_TRUE(s31.data() == hola.data()); + EXPECT_EQ(8, s31.length()); + } + +#if defined(HAS_GLOBAL_STRING) + { + // ::string + string bonjour = "bonjour"; + StringPiece s40(bonjour); + EXPECT_TRUE(s40.data() == bonjour.data()); + EXPECT_EQ(7, s40.length()); + } +#endif + + // TODO(mec): StringPiece(StringPiece x, int pos); + // TODO(mec): StringPiece(StringPiece x, int pos, int len); + // TODO(mec): StringPiece(const StringPiece&); +} + +TEST(StringPiece, STLComparator) { + string s1("foo"); + string s2("bar"); + string s3("baz"); + + StringPiece p1(s1); + StringPiece p2(s2); + StringPiece p3(s3); + + typedef std::map TestMap; + TestMap map; + + map.insert(std::make_pair(p1, 0)); + map.insert(std::make_pair(p2, 1)); + map.insert(std::make_pair(p3, 2)); + EXPECT_EQ(map.size(), 3); + + TestMap::const_iterator iter = map.begin(); + EXPECT_EQ(iter->second, 1); + ++iter; + EXPECT_EQ(iter->second, 2); + ++iter; + EXPECT_EQ(iter->second, 0); + ++iter; + EXPECT_TRUE(iter == map.end()); + + TestMap::iterator new_iter = map.find("zot"); + EXPECT_TRUE(new_iter == map.end()); + + new_iter = map.find("bar"); + EXPECT_TRUE(new_iter != map.end()); + + map.erase(new_iter); + EXPECT_EQ(map.size(), 2); + + iter = map.begin(); + EXPECT_EQ(iter->second, 2); + ++iter; + EXPECT_EQ(iter->second, 0); + ++iter; + EXPECT_TRUE(iter == map.end()); +} + +TEST(StringPiece, ComparisonOperators) { +#define COMPARE(result, op, x, y) \ + EXPECT_EQ(result, StringPiece((x)) op StringPiece((y))); \ + EXPECT_EQ(result, StringPiece((x)).compare(StringPiece((y))) op 0) + + COMPARE(true, ==, "", ""); + COMPARE(true, ==, "", NULL); + COMPARE(true, ==, NULL, ""); + COMPARE(true, ==, "a", "a"); + COMPARE(true, ==, "aa", "aa"); + COMPARE(false, ==, "a", ""); + COMPARE(false, ==, "", "a"); + COMPARE(false, ==, "a", "b"); + COMPARE(false, ==, "a", "aa"); + COMPARE(false, ==, "aa", "a"); + + COMPARE(false, !=, "", ""); + COMPARE(false, !=, "a", "a"); + COMPARE(false, !=, "aa", "aa"); + COMPARE(true, !=, "a", ""); + COMPARE(true, !=, "", "a"); + COMPARE(true, !=, "a", "b"); + COMPARE(true, !=, "a", "aa"); + COMPARE(true, !=, "aa", "a"); + + COMPARE(true, <, "a", "b"); + COMPARE(true, <, "a", "aa"); + COMPARE(true, <, "aa", "b"); + COMPARE(true, <, "aa", "bb"); + COMPARE(false, <, "a", "a"); + COMPARE(false, <, "b", "a"); + COMPARE(false, <, "aa", "a"); + COMPARE(false, <, "b", "aa"); + COMPARE(false, <, "bb", "aa"); + + COMPARE(true, <=, "a", "a"); + COMPARE(true, <=, "a", "b"); + COMPARE(true, <=, "a", "aa"); + COMPARE(true, <=, "aa", "b"); + COMPARE(true, <=, "aa", "bb"); + COMPARE(false, <=, "b", "a"); + COMPARE(false, <=, "aa", "a"); + COMPARE(false, <=, "b", "aa"); + COMPARE(false, <=, "bb", "aa"); + + COMPARE(false, >=, "a", "b"); + COMPARE(false, >=, "a", "aa"); + COMPARE(false, >=, "aa", "b"); + COMPARE(false, >=, "aa", "bb"); + COMPARE(true, >=, "a", "a"); + COMPARE(true, >=, "b", "a"); + COMPARE(true, >=, "aa", "a"); + COMPARE(true, >=, "b", "aa"); + COMPARE(true, >=, "bb", "aa"); + + COMPARE(false, >, "a", "a"); + COMPARE(false, >, "a", "b"); + COMPARE(false, >, "a", "aa"); + COMPARE(false, >, "aa", "b"); + COMPARE(false, >, "aa", "bb"); + COMPARE(true, >, "b", "a"); + COMPARE(true, >, "aa", "a"); + COMPARE(true, >, "b", "aa"); + COMPARE(true, >, "bb", "aa"); + + string x; + for (int i = 0; i < 256; i++) { + x += 'a'; + string y = x; + COMPARE(true, ==, x, y); + for (int j = 0; j < i; j++) { + string z = x; + z[j] = 'b'; // Differs in position 'j' + COMPARE(false, ==, x, z); + COMPARE(true, <, x, z); + COMPARE(true, >, z, x); + if (j + 1 < i) { + z[j + 1] = 'A'; // Differs in position 'j+1' as well + COMPARE(false, ==, x, z); + COMPARE(true, <, x, z); + COMPARE(true, >, z, x); + z[j + 1] = 'z'; // Differs in position 'j+1' as well + COMPARE(false, ==, x, z); + COMPARE(true, <, x, z); + COMPARE(true, >, z, x); + } + } + } + +#undef COMPARE +} + +TEST(StringPiece, STL1) { + const StringPiece a("abcdefghijklmnopqrstuvwxyz"); + const StringPiece b("abc"); + const StringPiece c("xyz"); + const StringPiece d("foobar"); + const StringPiece e; + string temp("123"); + temp += '\0'; + temp += "456"; + const StringPiece f(temp); + + EXPECT_EQ(a[6], 'g'); + EXPECT_EQ(b[0], 'a'); + EXPECT_EQ(c[2], 'z'); + EXPECT_EQ(f[3], '\0'); + EXPECT_EQ(f[5], '5'); + + EXPECT_EQ(*d.data(), 'f'); + EXPECT_EQ(d.data()[5], 'r'); + EXPECT_TRUE(e.data() == NULL); + + EXPECT_EQ(*a.begin(), 'a'); + EXPECT_EQ(*(b.begin() + 2), 'c'); + EXPECT_EQ(*(c.end() - 1), 'z'); + + EXPECT_EQ(*a.rbegin(), 'z'); + EXPECT_EQ(*(b.rbegin() + 2), 'a'); + EXPECT_EQ(*(c.rend() - 1), 'x'); + EXPECT_TRUE(a.rbegin() + 26 == a.rend()); + + EXPECT_EQ(a.size(), 26); + EXPECT_EQ(b.size(), 3); + EXPECT_EQ(c.size(), 3); + EXPECT_EQ(d.size(), 6); + EXPECT_EQ(e.size(), 0); + EXPECT_EQ(f.size(), 7); + + EXPECT_TRUE(!d.empty()); + EXPECT_TRUE(d.begin() != d.end()); + EXPECT_TRUE(d.begin() + 6 == d.end()); + + EXPECT_TRUE(e.empty()); + EXPECT_TRUE(e.begin() == e.end()); + + EXPECT_GE(a.max_size(), a.capacity()); + EXPECT_GE(a.capacity(), a.size()); + + char buf[4] = { '%', '%', '%', '%' }; + EXPECT_EQ(a.copy(buf, 4), 4); + EXPECT_EQ(buf[0], a[0]); + EXPECT_EQ(buf[1], a[1]); + EXPECT_EQ(buf[2], a[2]); + EXPECT_EQ(buf[3], a[3]); + EXPECT_EQ(a.copy(buf, 3, 7), 3); + EXPECT_EQ(buf[0], a[7]); + EXPECT_EQ(buf[1], a[8]); + EXPECT_EQ(buf[2], a[9]); + EXPECT_EQ(buf[3], a[3]); + EXPECT_EQ(c.copy(buf, 99), 3); + EXPECT_EQ(buf[0], c[0]); + EXPECT_EQ(buf[1], c[1]); + EXPECT_EQ(buf[2], c[2]); + EXPECT_EQ(buf[3], a[3]); +} + +// Separated from STL1() because some compilers produce an overly +// large stack frame for the combined function. +TEST(StringPiece, STL2) { + const StringPiece a("abcdefghijklmnopqrstuvwxyz"); + const StringPiece b("abc"); + const StringPiece c("xyz"); + StringPiece d("foobar"); + const StringPiece e; + const StringPiece f("123" "\0" "456", 7); + + d.clear(); + EXPECT_EQ(d.size(), 0); + EXPECT_TRUE(d.empty()); + EXPECT_TRUE(d.data() == NULL); + EXPECT_TRUE(d.begin() == d.end()); + + EXPECT_EQ(StringPiece::npos, string::npos); + + EXPECT_EQ(a.find(b), 0); + EXPECT_EQ(a.find(b, 1), StringPiece::npos); + EXPECT_EQ(a.find(c), 23); + EXPECT_EQ(a.find(c, 9), 23); + EXPECT_EQ(a.find(c, StringPiece::npos), StringPiece::npos); + EXPECT_EQ(b.find(c), StringPiece::npos); + EXPECT_EQ(b.find(c, StringPiece::npos), StringPiece::npos); + EXPECT_EQ(a.find(d), 0); + EXPECT_EQ(a.find(e), 0); + EXPECT_EQ(a.find(d, 12), 12); + EXPECT_EQ(a.find(e, 17), 17); + StringPiece g("xx not found bb"); + EXPECT_EQ(a.find(g), StringPiece::npos); + // empty string nonsense + EXPECT_EQ(d.find(b), StringPiece::npos); + EXPECT_EQ(e.find(b), StringPiece::npos); + EXPECT_EQ(d.find(b, 4), StringPiece::npos); + EXPECT_EQ(e.find(b, 7), StringPiece::npos); + + size_t empty_search_pos = string().find(string()); + EXPECT_EQ(d.find(d), empty_search_pos); + EXPECT_EQ(d.find(e), empty_search_pos); + EXPECT_EQ(e.find(d), empty_search_pos); + EXPECT_EQ(e.find(e), empty_search_pos); + EXPECT_EQ(d.find(d, 4), string().find(string(), 4)); + EXPECT_EQ(d.find(e, 4), string().find(string(), 4)); + EXPECT_EQ(e.find(d, 4), string().find(string(), 4)); + EXPECT_EQ(e.find(e, 4), string().find(string(), 4)); + + EXPECT_EQ(a.find('a'), 0); + EXPECT_EQ(a.find('c'), 2); + EXPECT_EQ(a.find('z'), 25); + EXPECT_EQ(a.find('$'), StringPiece::npos); + EXPECT_EQ(a.find('\0'), StringPiece::npos); + EXPECT_EQ(f.find('\0'), 3); + EXPECT_EQ(f.find('3'), 2); + EXPECT_EQ(f.find('5'), 5); + EXPECT_EQ(g.find('o'), 4); + EXPECT_EQ(g.find('o', 4), 4); + EXPECT_EQ(g.find('o', 5), 8); + EXPECT_EQ(a.find('b', 5), StringPiece::npos); + // empty string nonsense + EXPECT_EQ(d.find('\0'), StringPiece::npos); + EXPECT_EQ(e.find('\0'), StringPiece::npos); + EXPECT_EQ(d.find('\0', 4), StringPiece::npos); + EXPECT_EQ(e.find('\0', 7), StringPiece::npos); + EXPECT_EQ(d.find('x'), StringPiece::npos); + EXPECT_EQ(e.find('x'), StringPiece::npos); + EXPECT_EQ(d.find('x', 4), StringPiece::npos); + EXPECT_EQ(e.find('x', 7), StringPiece::npos); + + EXPECT_EQ(a.rfind(b), 0); + EXPECT_EQ(a.rfind(b, 1), 0); + EXPECT_EQ(a.rfind(c), 23); + EXPECT_EQ(a.rfind(c, 22), StringPiece::npos); + EXPECT_EQ(a.rfind(c, 1), StringPiece::npos); + EXPECT_EQ(a.rfind(c, 0), StringPiece::npos); + EXPECT_EQ(b.rfind(c), StringPiece::npos); + EXPECT_EQ(b.rfind(c, 0), StringPiece::npos); + EXPECT_EQ(a.rfind(d), a.as_string().rfind(string())); + EXPECT_EQ(a.rfind(e), a.as_string().rfind(string())); + EXPECT_EQ(a.rfind(d, 12), 12); + EXPECT_EQ(a.rfind(e, 17), 17); + EXPECT_EQ(a.rfind(g), StringPiece::npos); + EXPECT_EQ(d.rfind(b), StringPiece::npos); + EXPECT_EQ(e.rfind(b), StringPiece::npos); + EXPECT_EQ(d.rfind(b, 4), StringPiece::npos); + EXPECT_EQ(e.rfind(b, 7), StringPiece::npos); + // empty string nonsense + EXPECT_EQ(d.rfind(d, 4), string().rfind(string())); + EXPECT_EQ(e.rfind(d, 7), string().rfind(string())); + EXPECT_EQ(d.rfind(e, 4), string().rfind(string())); + EXPECT_EQ(e.rfind(e, 7), string().rfind(string())); + EXPECT_EQ(d.rfind(d), string().rfind(string())); + EXPECT_EQ(e.rfind(d), string().rfind(string())); + EXPECT_EQ(d.rfind(e), string().rfind(string())); + EXPECT_EQ(e.rfind(e), string().rfind(string())); + + EXPECT_EQ(g.rfind('o'), 8); + EXPECT_EQ(g.rfind('q'), StringPiece::npos); + EXPECT_EQ(g.rfind('o', 8), 8); + EXPECT_EQ(g.rfind('o', 7), 4); + EXPECT_EQ(g.rfind('o', 3), StringPiece::npos); + EXPECT_EQ(f.rfind('\0'), 3); + EXPECT_EQ(f.rfind('\0', 12), 3); + EXPECT_EQ(f.rfind('3'), 2); + EXPECT_EQ(f.rfind('5'), 5); + // empty string nonsense + EXPECT_EQ(d.rfind('o'), StringPiece::npos); + EXPECT_EQ(e.rfind('o'), StringPiece::npos); + EXPECT_EQ(d.rfind('o', 4), StringPiece::npos); + EXPECT_EQ(e.rfind('o', 7), StringPiece::npos); + + EXPECT_EQ(a.find_first_of(b), 0); + EXPECT_EQ(a.find_first_of(b, 0), 0); + EXPECT_EQ(a.find_first_of(b, 1), 1); + EXPECT_EQ(a.find_first_of(b, 2), 2); + EXPECT_EQ(a.find_first_of(b, 3), StringPiece::npos); + EXPECT_EQ(a.find_first_of(c), 23); + EXPECT_EQ(a.find_first_of(c, 23), 23); + EXPECT_EQ(a.find_first_of(c, 24), 24); + EXPECT_EQ(a.find_first_of(c, 25), 25); + EXPECT_EQ(a.find_first_of(c, 26), StringPiece::npos); + EXPECT_EQ(g.find_first_of(b), 13); + EXPECT_EQ(g.find_first_of(c), 0); + EXPECT_EQ(a.find_first_of(f), StringPiece::npos); + EXPECT_EQ(f.find_first_of(a), StringPiece::npos); + // empty string nonsense + EXPECT_EQ(a.find_first_of(d), StringPiece::npos); + EXPECT_EQ(a.find_first_of(e), StringPiece::npos); + EXPECT_EQ(d.find_first_of(b), StringPiece::npos); + EXPECT_EQ(e.find_first_of(b), StringPiece::npos); + EXPECT_EQ(d.find_first_of(d), StringPiece::npos); + EXPECT_EQ(e.find_first_of(d), StringPiece::npos); + EXPECT_EQ(d.find_first_of(e), StringPiece::npos); + EXPECT_EQ(e.find_first_of(e), StringPiece::npos); + + EXPECT_EQ(a.find_first_not_of(b), 3); + EXPECT_EQ(a.find_first_not_of(c), 0); + EXPECT_EQ(b.find_first_not_of(a), StringPiece::npos); + EXPECT_EQ(c.find_first_not_of(a), StringPiece::npos); + EXPECT_EQ(f.find_first_not_of(a), 0); + EXPECT_EQ(a.find_first_not_of(f), 0); + EXPECT_EQ(a.find_first_not_of(d), 0); + EXPECT_EQ(a.find_first_not_of(e), 0); + // empty string nonsense + EXPECT_EQ(d.find_first_not_of(a), StringPiece::npos); + EXPECT_EQ(e.find_first_not_of(a), StringPiece::npos); + EXPECT_EQ(d.find_first_not_of(d), StringPiece::npos); + EXPECT_EQ(e.find_first_not_of(d), StringPiece::npos); + EXPECT_EQ(d.find_first_not_of(e), StringPiece::npos); + EXPECT_EQ(e.find_first_not_of(e), StringPiece::npos); + + StringPiece h("===="); + EXPECT_EQ(h.find_first_not_of('='), StringPiece::npos); + EXPECT_EQ(h.find_first_not_of('=', 3), StringPiece::npos); + EXPECT_EQ(h.find_first_not_of('\0'), 0); + EXPECT_EQ(g.find_first_not_of('x'), 2); + EXPECT_EQ(f.find_first_not_of('\0'), 0); + EXPECT_EQ(f.find_first_not_of('\0', 3), 4); + EXPECT_EQ(f.find_first_not_of('\0', 2), 2); + // empty string nonsense + EXPECT_EQ(d.find_first_not_of('x'), StringPiece::npos); + EXPECT_EQ(e.find_first_not_of('x'), StringPiece::npos); + EXPECT_EQ(d.find_first_not_of('\0'), StringPiece::npos); + EXPECT_EQ(e.find_first_not_of('\0'), StringPiece::npos); + + // StringPiece g("xx not found bb"); + StringPiece i("56"); + EXPECT_EQ(h.find_last_of(a), StringPiece::npos); + EXPECT_EQ(g.find_last_of(a), g.size()-1); + EXPECT_EQ(a.find_last_of(b), 2); + EXPECT_EQ(a.find_last_of(c), a.size()-1); + EXPECT_EQ(f.find_last_of(i), 6); + EXPECT_EQ(a.find_last_of('a'), 0); + EXPECT_EQ(a.find_last_of('b'), 1); + EXPECT_EQ(a.find_last_of('z'), 25); + EXPECT_EQ(a.find_last_of('a', 5), 0); + EXPECT_EQ(a.find_last_of('b', 5), 1); + EXPECT_EQ(a.find_last_of('b', 0), StringPiece::npos); + EXPECT_EQ(a.find_last_of('z', 25), 25); + EXPECT_EQ(a.find_last_of('z', 24), StringPiece::npos); + EXPECT_EQ(f.find_last_of(i, 5), 5); + EXPECT_EQ(f.find_last_of(i, 6), 6); + EXPECT_EQ(f.find_last_of(a, 4), StringPiece::npos); + // empty string nonsense + EXPECT_EQ(f.find_last_of(d), StringPiece::npos); + EXPECT_EQ(f.find_last_of(e), StringPiece::npos); + EXPECT_EQ(f.find_last_of(d, 4), StringPiece::npos); + EXPECT_EQ(f.find_last_of(e, 4), StringPiece::npos); + EXPECT_EQ(d.find_last_of(d), StringPiece::npos); + EXPECT_EQ(d.find_last_of(e), StringPiece::npos); + EXPECT_EQ(e.find_last_of(d), StringPiece::npos); + EXPECT_EQ(e.find_last_of(e), StringPiece::npos); + EXPECT_EQ(d.find_last_of(f), StringPiece::npos); + EXPECT_EQ(e.find_last_of(f), StringPiece::npos); + EXPECT_EQ(d.find_last_of(d, 4), StringPiece::npos); + EXPECT_EQ(d.find_last_of(e, 4), StringPiece::npos); + EXPECT_EQ(e.find_last_of(d, 4), StringPiece::npos); + EXPECT_EQ(e.find_last_of(e, 4), StringPiece::npos); + EXPECT_EQ(d.find_last_of(f, 4), StringPiece::npos); + EXPECT_EQ(e.find_last_of(f, 4), StringPiece::npos); + + EXPECT_EQ(a.find_last_not_of(b), a.size()-1); + EXPECT_EQ(a.find_last_not_of(c), 22); + EXPECT_EQ(b.find_last_not_of(a), StringPiece::npos); + EXPECT_EQ(b.find_last_not_of(b), StringPiece::npos); + EXPECT_EQ(f.find_last_not_of(i), 4); + EXPECT_EQ(a.find_last_not_of(c, 24), 22); + EXPECT_EQ(a.find_last_not_of(b, 3), 3); + EXPECT_EQ(a.find_last_not_of(b, 2), StringPiece::npos); + // empty string nonsense + EXPECT_EQ(f.find_last_not_of(d), f.size()-1); + EXPECT_EQ(f.find_last_not_of(e), f.size()-1); + EXPECT_EQ(f.find_last_not_of(d, 4), 4); + EXPECT_EQ(f.find_last_not_of(e, 4), 4); + EXPECT_EQ(d.find_last_not_of(d), StringPiece::npos); + EXPECT_EQ(d.find_last_not_of(e), StringPiece::npos); + EXPECT_EQ(e.find_last_not_of(d), StringPiece::npos); + EXPECT_EQ(e.find_last_not_of(e), StringPiece::npos); + EXPECT_EQ(d.find_last_not_of(f), StringPiece::npos); + EXPECT_EQ(e.find_last_not_of(f), StringPiece::npos); + EXPECT_EQ(d.find_last_not_of(d, 4), StringPiece::npos); + EXPECT_EQ(d.find_last_not_of(e, 4), StringPiece::npos); + EXPECT_EQ(e.find_last_not_of(d, 4), StringPiece::npos); + EXPECT_EQ(e.find_last_not_of(e, 4), StringPiece::npos); + EXPECT_EQ(d.find_last_not_of(f, 4), StringPiece::npos); + EXPECT_EQ(e.find_last_not_of(f, 4), StringPiece::npos); + + EXPECT_EQ(h.find_last_not_of('x'), h.size() - 1); + EXPECT_EQ(h.find_last_not_of('='), StringPiece::npos); + EXPECT_EQ(b.find_last_not_of('c'), 1); + EXPECT_EQ(h.find_last_not_of('x', 2), 2); + EXPECT_EQ(h.find_last_not_of('=', 2), StringPiece::npos); + EXPECT_EQ(b.find_last_not_of('b', 1), 0); + // empty string nonsense + EXPECT_EQ(d.find_last_not_of('x'), StringPiece::npos); + EXPECT_EQ(e.find_last_not_of('x'), StringPiece::npos); + EXPECT_EQ(d.find_last_not_of('\0'), StringPiece::npos); + EXPECT_EQ(e.find_last_not_of('\0'), StringPiece::npos); + + EXPECT_EQ(a.substr(0, 3), b); + EXPECT_EQ(a.substr(23), c); + EXPECT_EQ(a.substr(23, 3), c); + EXPECT_EQ(a.substr(23, 99), c); + EXPECT_EQ(a.substr(0), a); + EXPECT_EQ(a.substr(3, 2), "de"); + // empty string nonsense + EXPECT_EQ(a.substr(99, 2), e); + EXPECT_EQ(d.substr(99), e); + EXPECT_EQ(d.substr(0, 99), e); + EXPECT_EQ(d.substr(99, 99), e); + // use of npos + EXPECT_EQ(a.substr(0, StringPiece::npos), a); + EXPECT_EQ(a.substr(23, StringPiece::npos), c); + EXPECT_EQ(a.substr(StringPiece::npos, 0), e); + EXPECT_EQ(a.substr(StringPiece::npos, 1), e); + EXPECT_EQ(a.substr(StringPiece::npos, StringPiece::npos), e); + + // Substring constructors. + EXPECT_EQ(StringPiece(a, 0, 3), b); + EXPECT_EQ(StringPiece(a, 23), c); + EXPECT_EQ(StringPiece(a, 23, 3), c); + EXPECT_EQ(StringPiece(a, 23, 99), c); + EXPECT_EQ(StringPiece(a, 0), a); + EXPECT_EQ(StringPiece(a, 3, 2), "de"); + // empty string nonsense + EXPECT_EQ(StringPiece(d, 0, 99), e); + // Verify that they work taking an actual string, not just a StringPiece. + string a2 = a.as_string(); + EXPECT_EQ(StringPiece(a2, 0, 3), b); + EXPECT_EQ(StringPiece(a2, 23), c); + EXPECT_EQ(StringPiece(a2, 23, 3), c); + EXPECT_EQ(StringPiece(a2, 23, 99), c); + EXPECT_EQ(StringPiece(a2, 0), a); + EXPECT_EQ(StringPiece(a2, 3, 2), "de"); +} + +TEST(StringPiece, Custom) { + StringPiece a("foobar"); + string s1("123"); + s1 += '\0'; + s1 += "456"; + StringPiece b(s1); + StringPiece e; + string s2; + + // CopyToString + a.CopyToString(&s2); + EXPECT_EQ(s2.size(), 6); + EXPECT_EQ(s2, "foobar"); + b.CopyToString(&s2); + EXPECT_EQ(s2.size(), 7); + EXPECT_EQ(s1, s2); + e.CopyToString(&s2); + EXPECT_TRUE(s2.empty()); + + // AppendToString + s2.erase(); + a.AppendToString(&s2); + EXPECT_EQ(s2.size(), 6); + EXPECT_EQ(s2, "foobar"); + a.AppendToString(&s2); + EXPECT_EQ(s2.size(), 12); + EXPECT_EQ(s2, "foobarfoobar"); + + // starts_with + EXPECT_TRUE(a.starts_with(a)); + EXPECT_TRUE(a.starts_with("foo")); + EXPECT_TRUE(a.starts_with(e)); + EXPECT_TRUE(b.starts_with(s1)); + EXPECT_TRUE(b.starts_with(b)); + EXPECT_TRUE(b.starts_with(e)); + EXPECT_TRUE(e.starts_with("")); + EXPECT_TRUE(!a.starts_with(b)); + EXPECT_TRUE(!b.starts_with(a)); + EXPECT_TRUE(!e.starts_with(a)); + + // ends with + EXPECT_TRUE(a.ends_with(a)); + EXPECT_TRUE(a.ends_with("bar")); + EXPECT_TRUE(a.ends_with(e)); + EXPECT_TRUE(b.ends_with(s1)); + EXPECT_TRUE(b.ends_with(b)); + EXPECT_TRUE(b.ends_with(e)); + EXPECT_TRUE(e.ends_with("")); + EXPECT_TRUE(!a.ends_with(b)); + EXPECT_TRUE(!b.ends_with(a)); + EXPECT_TRUE(!e.ends_with(a)); + + // remove_prefix + StringPiece c(a); + c.remove_prefix(3); + EXPECT_EQ(c, "bar"); + c = a; + c.remove_prefix(0); + EXPECT_EQ(c, a); + c.remove_prefix(c.size()); + EXPECT_EQ(c, e); + + // remove_suffix + c = a; + c.remove_suffix(3); + EXPECT_EQ(c, "foo"); + c = a; + c.remove_suffix(0); + EXPECT_EQ(c, a); + c.remove_suffix(c.size()); + EXPECT_EQ(c, e); + + // set + c.set("foobar", 6); + EXPECT_EQ(c, a); + c.set("foobar", 0); + EXPECT_EQ(c, e); + c.set("foobar", 7); + EXPECT_NE(c, a); + + c.set("foobar"); + EXPECT_EQ(c, a); + + c.set(static_cast("foobar"), 6); + EXPECT_EQ(c, a); + c.set(static_cast("foobar"), 0); + EXPECT_EQ(c, e); + c.set(static_cast("foobar"), 7); + EXPECT_NE(c, a); + + // as_string + string s3(a.as_string().c_str(), 7); + EXPECT_EQ(c, s3); + string s4(e.as_string()); + EXPECT_TRUE(s4.empty()); + + // ToString + { + string s5(a.ToString().c_str(), 7); + EXPECT_EQ(c, s5); + string s6(e.ToString()); + EXPECT_TRUE(s6.empty()); + } + + // Consume + a.set("foobar"); + EXPECT_TRUE(a.Consume("foo")); + EXPECT_EQ(a, "bar"); + EXPECT_FALSE(a.Consume("foo")); + EXPECT_FALSE(a.Consume("barbar")); + EXPECT_FALSE(a.Consume("ar")); + EXPECT_EQ(a, "bar"); + + a.set("foobar"); + EXPECT_TRUE(a.ConsumeFromEnd("bar")); + EXPECT_EQ(a, "foo"); + EXPECT_FALSE(a.ConsumeFromEnd("bar")); + EXPECT_FALSE(a.ConsumeFromEnd("foofoo")); + EXPECT_FALSE(a.ConsumeFromEnd("fo")); + EXPECT_EQ(a, "foo"); +} + +TEST(StringPiece, Contains) { + StringPiece a("abcdefg"); + StringPiece b("abcd"); + StringPiece c("efg"); + StringPiece d("gh"); + EXPECT_TRUE(a.contains(b)); + EXPECT_TRUE(a.contains(c)); + EXPECT_TRUE(!a.contains(d)); +} + +TEST(StringPiece, NULLInput) { + // we used to crash here, but now we don't. + StringPiece s(NULL); + EXPECT_EQ(s.data(), (const char*)NULL); + EXPECT_EQ(s.size(), 0); + + s.set(NULL); + EXPECT_EQ(s.data(), (const char*)NULL); + EXPECT_EQ(s.size(), 0); + + // .ToString() on a StringPiece with NULL should produce the empty string. + EXPECT_EQ("", s.ToString()); + EXPECT_EQ("", s.as_string()); +} + +TEST(StringPiece, Comparisons2) { + StringPiece abc("abcdefghijklmnopqrstuvwxyz"); + + // check comparison operations on strings longer than 4 bytes. + EXPECT_EQ(abc, StringPiece("abcdefghijklmnopqrstuvwxyz")); + EXPECT_EQ(abc.compare(StringPiece("abcdefghijklmnopqrstuvwxyz")), 0); + + EXPECT_LT(abc, StringPiece("abcdefghijklmnopqrstuvwxzz")); + EXPECT_LT(abc.compare(StringPiece("abcdefghijklmnopqrstuvwxzz")), 0); + + EXPECT_GT(abc, StringPiece("abcdefghijklmnopqrstuvwxyy")); + EXPECT_GT(abc.compare(StringPiece("abcdefghijklmnopqrstuvwxyy")), 0); + + // starts_with + EXPECT_TRUE(abc.starts_with(abc)); + EXPECT_TRUE(abc.starts_with("abcdefghijklm")); + EXPECT_TRUE(!abc.starts_with("abcdefguvwxyz")); + + // ends_with + EXPECT_TRUE(abc.ends_with(abc)); + EXPECT_TRUE(!abc.ends_with("abcdefguvwxyz")); + EXPECT_TRUE(abc.ends_with("nopqrstuvwxyz")); +} + +TEST(ComparisonOpsTest, StringCompareNotAmbiguous) { + EXPECT_EQ("hello", string("hello")); + EXPECT_LT("hello", string("world")); +} + +TEST(ComparisonOpsTest, HeterogenousStringPieceEquals) { + EXPECT_EQ(StringPiece("hello"), string("hello")); + EXPECT_EQ("hello", StringPiece("hello")); +} + +TEST(FindOneCharTest, EdgeCases) { + StringPiece a("xxyyyxx"); + + // Set a = "xyyyx". + a.remove_prefix(1); + a.remove_suffix(1); + + EXPECT_EQ(0, a.find('x')); + EXPECT_EQ(0, a.find('x', 0)); + EXPECT_EQ(4, a.find('x', 1)); + EXPECT_EQ(4, a.find('x', 4)); + EXPECT_EQ(StringPiece::npos, a.find('x', 5)); + + EXPECT_EQ(4, a.rfind('x')); + EXPECT_EQ(4, a.rfind('x', 5)); + EXPECT_EQ(4, a.rfind('x', 4)); + EXPECT_EQ(0, a.rfind('x', 3)); + EXPECT_EQ(0, a.rfind('x', 0)); + + // Set a = "yyy". + a.remove_prefix(1); + a.remove_suffix(1); + + EXPECT_EQ(StringPiece::npos, a.find('x')); + EXPECT_EQ(StringPiece::npos, a.rfind('x')); +} + +#ifndef NDEBUG +TEST(NonNegativeLenTest, NonNegativeLen) { + EXPECT_DEATH(StringPiece("xyz", -1), "len >= 0"); +} +#endif // ndef DEBUG + +} // namespace +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/stubs/strutil.cc b/src/google/protobuf/stubs/strutil.cc index 7ecc17ee..99e8bf1d 100644 --- a/src/google/protobuf/stubs/strutil.cc +++ b/src/google/protobuf/stubs/strutil.cc @@ -31,6 +31,7 @@ // from google3/strings/strutil.cc #include + #include #include // FLT_DIG and DBL_DIG #include @@ -38,6 +39,8 @@ #include #include +#include + #ifdef _WIN32 // MSVC has only _snprintf, not snprintf. // @@ -309,17 +312,6 @@ void JoinStrings(const vector& components, #define IS_OCTAL_DIGIT(c) (((c) >= '0') && ((c) <= '7')) -inline int hex_digit_to_int(char c) { - /* Assume ASCII. */ - assert('0' == 0x30 && 'A' == 0x41 && 'a' == 0x61); - assert(isxdigit(c)); - int x = static_cast(c); - if (x > '9') { - x += 9; - } - return x & 0xf; -} - // Protocol buffers doesn't ever care about errors, but I don't want to remove // the code. #define LOG_STRING(LEVEL, VECTOR) GOOGLE_LOG_IF(LEVEL, false) @@ -652,14 +644,15 @@ inline bool safe_parse_sign(string* text /*inout*/, return true; } -inline bool safe_parse_positive_int( - string text, int32* value_p) { +template +bool safe_parse_positive_int( + string text, IntType* value_p) { int base = 10; - int32 value = 0; - const int32 vmax = std::numeric_limits::max(); + IntType value = 0; + const IntType vmax = std::numeric_limits::max(); assert(vmax > 0); assert(vmax >= base); - const int32 vmax_over_base = vmax / base; + const IntType vmax_over_base = vmax / base; const char* start = text.data(); const char* end = start + text.size(); // loop over digits @@ -685,14 +678,15 @@ inline bool safe_parse_positive_int( return true; } -inline bool safe_parse_negative_int( - string text, int32* value_p) { +template +bool safe_parse_negative_int( + const string& text, IntType* value_p) { int base = 10; - int32 value = 0; - const int32 vmin = std::numeric_limits::min(); + IntType value = 0; + const IntType vmin = std::numeric_limits::min(); assert(vmin < 0); assert(vmin <= 0 - base); - int32 vmin_over_base = vmin / base; + IntType vmin_over_base = vmin / base; // 2003 c++ standard [expr.mul] // "... the sign of the remainder is implementation-defined." // Although (vmin/base)*base + vmin%base is always vmin. @@ -725,7 +719,8 @@ inline bool safe_parse_negative_int( return true; } -bool safe_int(string text, int32* value_p) { +template +bool safe_int_internal(string text, IntType* value_p) { *value_p = 0; bool negative; if (!safe_parse_sign(&text, &negative)) { @@ -738,6 +733,16 @@ bool safe_int(string text, int32* value_p) { } } +template +bool safe_uint_internal(string text, IntType* value_p) { + *value_p = 0; + bool negative; + if (!safe_parse_sign(&text, &negative) || negative) { + return false; + } + return safe_parse_positive_int(text, value_p); +} + // ---------------------------------------------------------------------- // FastIntToBuffer() // FastInt64ToBuffer() @@ -1236,6 +1241,41 @@ char* DoubleToBuffer(double value, char* buffer) { return buffer; } +static int memcasecmp(const char *s1, const char *s2, size_t len) { + const unsigned char *us1 = reinterpret_cast(s1); + const unsigned char *us2 = reinterpret_cast(s2); + + for ( int i = 0; i < len; i++ ) { + const int diff = + static_cast(static_cast(ascii_tolower(us1[i]))) - + static_cast(static_cast(ascii_tolower(us2[i]))); + if (diff != 0) return diff; + } + return 0; +} + +inline bool CaseEqual(StringPiece s1, StringPiece s2) { + if (s1.size() != s2.size()) return false; + return memcasecmp(s1.data(), s2.data(), s1.size()) == 0; +} + +bool safe_strtob(StringPiece str, bool* value) { + GOOGLE_CHECK(value != NULL) << "NULL output boolean given."; + if (CaseEqual(str, "true") || CaseEqual(str, "t") || + CaseEqual(str, "yes") || CaseEqual(str, "y") || + CaseEqual(str, "1")) { + *value = true; + return true; + } + if (CaseEqual(str, "false") || CaseEqual(str, "f") || + CaseEqual(str, "no") || CaseEqual(str, "n") || + CaseEqual(str, "0")) { + *value = false; + return true; + } + return false; +} + bool safe_strtof(const char* str, float* value) { char* endptr; errno = 0; // errno only gets set on errors @@ -1247,6 +1287,34 @@ bool safe_strtof(const char* str, float* value) { return *str != 0 && *endptr == 0 && errno == 0; } +bool safe_strtod(const char* str, double* value) { + char* endptr; + *value = strtod(str, &endptr); + if (endptr != str) { + while (ascii_isspace(*endptr)) ++endptr; + } + // Ignore range errors from strtod. The values it + // returns on underflow and overflow are the right + // fallback in a robust setting. + return *str != '\0' && *endptr == '\0'; +} + +bool safe_strto32(const string& str, int32* value) { + return safe_int_internal(str, value); +} + +bool safe_strtou32(const string& str, uint32* value) { + return safe_uint_internal(str, value); +} + +bool safe_strto64(const string& str, int64* value) { + return safe_int_internal(str, value); +} + +bool safe_strtou64(const string& str, uint64* value) { + return safe_uint_internal(str, value); +} + char* FloatToBuffer(float value, char* buffer) { // FLT_DIG is 6 for IEEE-754 floats, which are used on almost all // platforms these days. Just in case some system exists where FLT_DIG @@ -1518,5 +1586,661 @@ int GlobalReplaceSubstring(const string& substring, return num_replacements; } +int CalculateBase64EscapedLen(int input_len, bool do_padding) { + // Base64 encodes three bytes of input at a time. If the input is not + // divisible by three, we pad as appropriate. + // + // (from http://tools.ietf.org/html/rfc3548) + // Special processing is performed if fewer than 24 bits are available + // at the end of the data being encoded. A full encoding quantum is + // always completed at the end of a quantity. When fewer than 24 input + // bits are available in an input group, zero bits are added (on the + // right) to form an integral number of 6-bit groups. Padding at the + // end of the data is performed using the '=' character. Since all base + // 64 input is an integral number of octets, only the following cases + // can arise: + + + // Base64 encodes each three bytes of input into four bytes of output. + int len = (input_len / 3) * 4; + + if (input_len % 3 == 0) { + // (from http://tools.ietf.org/html/rfc3548) + // (1) the final quantum of encoding input is an integral multiple of 24 + // bits; here, the final unit of encoded output will be an integral + // multiple of 4 characters with no "=" padding, + } else if (input_len % 3 == 1) { + // (from http://tools.ietf.org/html/rfc3548) + // (2) the final quantum of encoding input is exactly 8 bits; here, the + // final unit of encoded output will be two characters followed by two + // "=" padding characters, or + len += 2; + if (do_padding) { + len += 2; + } + } else { // (input_len % 3 == 2) + // (from http://tools.ietf.org/html/rfc3548) + // (3) the final quantum of encoding input is exactly 16 bits; here, the + // final unit of encoded output will be three characters followed by one + // "=" padding character. + len += 3; + if (do_padding) { + len += 1; + } + } + + assert(len >= input_len); // make sure we didn't overflow + return len; +} + +// Base64Escape does padding, so this calculation includes padding. +int CalculateBase64EscapedLen(int input_len) { + return CalculateBase64EscapedLen(input_len, true); +} + +// ---------------------------------------------------------------------- +// int Base64Unescape() - base64 decoder +// int Base64Escape() - base64 encoder +// int WebSafeBase64Unescape() - Google's variation of base64 decoder +// int WebSafeBase64Escape() - Google's variation of base64 encoder +// +// Check out +// http://tools.ietf.org/html/rfc2045 for formal description, but what we +// care about is that... +// Take the encoded stuff in groups of 4 characters and turn each +// character into a code 0 to 63 thus: +// A-Z map to 0 to 25 +// a-z map to 26 to 51 +// 0-9 map to 52 to 61 +// +(- for WebSafe) maps to 62 +// /(_ for WebSafe) maps to 63 +// There will be four numbers, all less than 64 which can be represented +// by a 6 digit binary number (aaaaaa, bbbbbb, cccccc, dddddd respectively). +// Arrange the 6 digit binary numbers into three bytes as such: +// aaaaaabb bbbbcccc ccdddddd +// Equals signs (one or two) are used at the end of the encoded block to +// indicate that the text was not an integer multiple of three bytes long. +// ---------------------------------------------------------------------- + +int Base64UnescapeInternal(const char *src_param, int szsrc, + char *dest, int szdest, + const signed char* unbase64) { + static const char kPad64Equals = '='; + static const char kPad64Dot = '.'; + + int decode = 0; + int destidx = 0; + int state = 0; + unsigned int ch = 0; + unsigned int temp = 0; + + // If "char" is signed by default, using *src as an array index results in + // accessing negative array elements. Treat the input as a pointer to + // unsigned char to avoid this. + const unsigned char *src = reinterpret_cast(src_param); + + // The GET_INPUT macro gets the next input character, skipping + // over any whitespace, and stopping when we reach the end of the + // string or when we read any non-data character. The arguments are + // an arbitrary identifier (used as a label for goto) and the number + // of data bytes that must remain in the input to avoid aborting the + // loop. +#define GET_INPUT(label, remain) \ + label: \ + --szsrc; \ + ch = *src++; \ + decode = unbase64[ch]; \ + if (decode < 0) { \ + if (ascii_isspace(ch) && szsrc >= remain) \ + goto label; \ + state = 4 - remain; \ + break; \ + } + + // if dest is null, we're just checking to see if it's legal input + // rather than producing output. (I suspect this could just be done + // with a regexp...). We duplicate the loop so this test can be + // outside it instead of in every iteration. + + if (dest) { + // This loop consumes 4 input bytes and produces 3 output bytes + // per iteration. We can't know at the start that there is enough + // data left in the string for a full iteration, so the loop may + // break out in the middle; if so 'state' will be set to the + // number of input bytes read. + + while (szsrc >= 4) { + // We'll start by optimistically assuming that the next four + // bytes of the string (src[0..3]) are four good data bytes + // (that is, no nulls, whitespace, padding chars, or illegal + // chars). We need to test src[0..2] for nulls individually + // before constructing temp to preserve the property that we + // never read past a null in the string (no matter how long + // szsrc claims the string is). + + if (!src[0] || !src[1] || !src[2] || + (temp = ((unsigned(unbase64[src[0]]) << 18) | + (unsigned(unbase64[src[1]]) << 12) | + (unsigned(unbase64[src[2]]) << 6) | + (unsigned(unbase64[src[3]])))) & 0x80000000) { + // Iff any of those four characters was bad (null, illegal, + // whitespace, padding), then temp's high bit will be set + // (because unbase64[] is -1 for all bad characters). + // + // We'll back up and resort to the slower decoder, which knows + // how to handle those cases. + + GET_INPUT(first, 4); + temp = decode; + GET_INPUT(second, 3); + temp = (temp << 6) | decode; + GET_INPUT(third, 2); + temp = (temp << 6) | decode; + GET_INPUT(fourth, 1); + temp = (temp << 6) | decode; + } else { + // We really did have four good data bytes, so advance four + // characters in the string. + + szsrc -= 4; + src += 4; + decode = -1; + ch = '\0'; + } + + // temp has 24 bits of input, so write that out as three bytes. + + if (destidx+3 > szdest) return -1; + dest[destidx+2] = temp; + temp >>= 8; + dest[destidx+1] = temp; + temp >>= 8; + dest[destidx] = temp; + destidx += 3; + } + } else { + while (szsrc >= 4) { + if (!src[0] || !src[1] || !src[2] || + (temp = ((unsigned(unbase64[src[0]]) << 18) | + (unsigned(unbase64[src[1]]) << 12) | + (unsigned(unbase64[src[2]]) << 6) | + (unsigned(unbase64[src[3]])))) & 0x80000000) { + GET_INPUT(first_no_dest, 4); + GET_INPUT(second_no_dest, 3); + GET_INPUT(third_no_dest, 2); + GET_INPUT(fourth_no_dest, 1); + } else { + szsrc -= 4; + src += 4; + decode = -1; + ch = '\0'; + } + destidx += 3; + } + } + +#undef GET_INPUT + + // if the loop terminated because we read a bad character, return + // now. + if (decode < 0 && ch != '\0' && + ch != kPad64Equals && ch != kPad64Dot && !ascii_isspace(ch)) + return -1; + + if (ch == kPad64Equals || ch == kPad64Dot) { + // if we stopped by hitting an '=' or '.', un-read that character -- we'll + // look at it again when we count to check for the proper number of + // equals signs at the end. + ++szsrc; + --src; + } else { + // This loop consumes 1 input byte per iteration. It's used to + // clean up the 0-3 input bytes remaining when the first, faster + // loop finishes. 'temp' contains the data from 'state' input + // characters read by the first loop. + while (szsrc > 0) { + --szsrc; + ch = *src++; + decode = unbase64[ch]; + if (decode < 0) { + if (ascii_isspace(ch)) { + continue; + } else if (ch == '\0') { + break; + } else if (ch == kPad64Equals || ch == kPad64Dot) { + // back up one character; we'll read it again when we check + // for the correct number of pad characters at the end. + ++szsrc; + --src; + break; + } else { + return -1; + } + } + + // Each input character gives us six bits of output. + temp = (temp << 6) | decode; + ++state; + if (state == 4) { + // If we've accumulated 24 bits of output, write that out as + // three bytes. + if (dest) { + if (destidx+3 > szdest) return -1; + dest[destidx+2] = temp; + temp >>= 8; + dest[destidx+1] = temp; + temp >>= 8; + dest[destidx] = temp; + } + destidx += 3; + state = 0; + temp = 0; + } + } + } + + // Process the leftover data contained in 'temp' at the end of the input. + int expected_equals = 0; + switch (state) { + case 0: + // Nothing left over; output is a multiple of 3 bytes. + break; + + case 1: + // Bad input; we have 6 bits left over. + return -1; + + case 2: + // Produce one more output byte from the 12 input bits we have left. + if (dest) { + if (destidx+1 > szdest) return -1; + temp >>= 4; + dest[destidx] = temp; + } + ++destidx; + expected_equals = 2; + break; + + case 3: + // Produce two more output bytes from the 18 input bits we have left. + if (dest) { + if (destidx+2 > szdest) return -1; + temp >>= 2; + dest[destidx+1] = temp; + temp >>= 8; + dest[destidx] = temp; + } + destidx += 2; + expected_equals = 1; + break; + + default: + // state should have no other values at this point. + GOOGLE_LOG(FATAL) << "This can't happen; base64 decoder state = " << state; + } + + // The remainder of the string should be all whitespace, mixed with + // exactly 0 equals signs, or exactly 'expected_equals' equals + // signs. (Always accepting 0 equals signs is a google extension + // not covered in the RFC, as is accepting dot as the pad character.) + + int equals = 0; + while (szsrc > 0 && *src) { + if (*src == kPad64Equals || *src == kPad64Dot) + ++equals; + else if (!ascii_isspace(*src)) + return -1; + --szsrc; + ++src; + } + + return (equals == 0 || equals == expected_equals) ? destidx : -1; +} + +// The arrays below were generated by the following code +// #include +// #include +// #include +// main() +// { +// static const char Base64[] = +// "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +// char *pos; +// int idx, i, j; +// printf(" "); +// for (i = 0; i < 255; i += 8) { +// for (j = i; j < i + 8; j++) { +// pos = strchr(Base64, j); +// if ((pos == NULL) || (j == 0)) +// idx = -1; +// else +// idx = pos - Base64; +// if (idx == -1) +// printf(" %2d, ", idx); +// else +// printf(" %2d/*%c*/,", idx, j); +// } +// printf("\n "); +// } +// } +// +// where the value of "Base64[]" was replaced by one of the base-64 conversion +// tables from the functions below. +static const signed char kUnBase64[] = { + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 62/*+*/, -1, -1, -1, 63/*/ */, + 52/*0*/, 53/*1*/, 54/*2*/, 55/*3*/, 56/*4*/, 57/*5*/, 58/*6*/, 59/*7*/, + 60/*8*/, 61/*9*/, -1, -1, -1, -1, -1, -1, + -1, 0/*A*/, 1/*B*/, 2/*C*/, 3/*D*/, 4/*E*/, 5/*F*/, 6/*G*/, + 07/*H*/, 8/*I*/, 9/*J*/, 10/*K*/, 11/*L*/, 12/*M*/, 13/*N*/, 14/*O*/, + 15/*P*/, 16/*Q*/, 17/*R*/, 18/*S*/, 19/*T*/, 20/*U*/, 21/*V*/, 22/*W*/, + 23/*X*/, 24/*Y*/, 25/*Z*/, -1, -1, -1, -1, -1, + -1, 26/*a*/, 27/*b*/, 28/*c*/, 29/*d*/, 30/*e*/, 31/*f*/, 32/*g*/, + 33/*h*/, 34/*i*/, 35/*j*/, 36/*k*/, 37/*l*/, 38/*m*/, 39/*n*/, 40/*o*/, + 41/*p*/, 42/*q*/, 43/*r*/, 44/*s*/, 45/*t*/, 46/*u*/, 47/*v*/, 48/*w*/, + 49/*x*/, 50/*y*/, 51/*z*/, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1 +}; +static const signed char kUnWebSafeBase64[] = { + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 62/*-*/, -1, -1, + 52/*0*/, 53/*1*/, 54/*2*/, 55/*3*/, 56/*4*/, 57/*5*/, 58/*6*/, 59/*7*/, + 60/*8*/, 61/*9*/, -1, -1, -1, -1, -1, -1, + -1, 0/*A*/, 1/*B*/, 2/*C*/, 3/*D*/, 4/*E*/, 5/*F*/, 6/*G*/, + 07/*H*/, 8/*I*/, 9/*J*/, 10/*K*/, 11/*L*/, 12/*M*/, 13/*N*/, 14/*O*/, + 15/*P*/, 16/*Q*/, 17/*R*/, 18/*S*/, 19/*T*/, 20/*U*/, 21/*V*/, 22/*W*/, + 23/*X*/, 24/*Y*/, 25/*Z*/, -1, -1, -1, -1, 63/*_*/, + -1, 26/*a*/, 27/*b*/, 28/*c*/, 29/*d*/, 30/*e*/, 31/*f*/, 32/*g*/, + 33/*h*/, 34/*i*/, 35/*j*/, 36/*k*/, 37/*l*/, 38/*m*/, 39/*n*/, 40/*o*/, + 41/*p*/, 42/*q*/, 43/*r*/, 44/*s*/, 45/*t*/, 46/*u*/, 47/*v*/, 48/*w*/, + 49/*x*/, 50/*y*/, 51/*z*/, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1 +}; + +int WebSafeBase64Unescape(const char *src, int szsrc, char *dest, int szdest) { + return Base64UnescapeInternal(src, szsrc, dest, szdest, kUnWebSafeBase64); +} + +static bool Base64UnescapeInternal(const char* src, int slen, string* dest, + const signed char* unbase64) { + // Determine the size of the output string. Base64 encodes every 3 bytes into + // 4 characters. any leftover chars are added directly for good measure. + // This is documented in the base64 RFC: http://tools.ietf.org/html/rfc3548 + const int dest_len = 3 * (slen / 4) + (slen % 4); + + dest->resize(dest_len); + + // We are getting the destination buffer by getting the beginning of the + // string and converting it into a char *. + const int len = Base64UnescapeInternal(src, slen, string_as_array(dest), + dest_len, unbase64); + if (len < 0) { + dest->clear(); + return false; + } + + // could be shorter if there was padding + GOOGLE_DCHECK_LE(len, dest_len); + dest->erase(len); + + return true; +} + +bool Base64Unescape(StringPiece src, string* dest) { + return Base64UnescapeInternal(src.data(), src.size(), dest, kUnBase64); +} + +bool WebSafeBase64Unescape(StringPiece src, string* dest) { + return Base64UnescapeInternal(src.data(), src.size(), dest, kUnWebSafeBase64); +} + +int Base64EscapeInternal(const unsigned char *src, int szsrc, + char *dest, int szdest, const char *base64, + bool do_padding) { + static const char kPad64 = '='; + + if (szsrc <= 0) return 0; + + if (szsrc * 4 > szdest * 3) return 0; + + char *cur_dest = dest; + const unsigned char *cur_src = src; + + char *limit_dest = dest + szdest; + const unsigned char *limit_src = src + szsrc; + + // Three bytes of data encodes to four characters of cyphertext. + // So we can pump through three-byte chunks atomically. + while (cur_src < limit_src - 3) { // keep going as long as we have >= 32 bits + uint32 in = BigEndian::Load32(cur_src) >> 8; + + cur_dest[0] = base64[in >> 18]; + in &= 0x3FFFF; + cur_dest[1] = base64[in >> 12]; + in &= 0xFFF; + cur_dest[2] = base64[in >> 6]; + in &= 0x3F; + cur_dest[3] = base64[in]; + + cur_dest += 4; + cur_src += 3; + } + // To save time, we didn't update szdest or szsrc in the loop. So do it now. + szdest = limit_dest - cur_dest; + szsrc = limit_src - cur_src; + + /* now deal with the tail (<=3 bytes) */ + switch (szsrc) { + case 0: + // Nothing left; nothing more to do. + break; + case 1: { + // One byte left: this encodes to two characters, and (optionally) + // two pad characters to round out the four-character cypherblock. + if ((szdest -= 2) < 0) return 0; + uint32 in = cur_src[0]; + cur_dest[0] = base64[in >> 2]; + in &= 0x3; + cur_dest[1] = base64[in << 4]; + cur_dest += 2; + if (do_padding) { + if ((szdest -= 2) < 0) return 0; + cur_dest[0] = kPad64; + cur_dest[1] = kPad64; + cur_dest += 2; + } + break; + } + case 2: { + // Two bytes left: this encodes to three characters, and (optionally) + // one pad character to round out the four-character cypherblock. + if ((szdest -= 3) < 0) return 0; + uint32 in = BigEndian::Load16(cur_src); + cur_dest[0] = base64[in >> 10]; + in &= 0x3FF; + cur_dest[1] = base64[in >> 4]; + in &= 0x00F; + cur_dest[2] = base64[in << 2]; + cur_dest += 3; + if (do_padding) { + if ((szdest -= 1) < 0) return 0; + cur_dest[0] = kPad64; + cur_dest += 1; + } + break; + } + case 3: { + // Three bytes left: same as in the big loop above. We can't do this in + // the loop because the loop above always reads 4 bytes, and the fourth + // byte is past the end of the input. + if ((szdest -= 4) < 0) return 0; + uint32 in = (cur_src[0] << 16) + BigEndian::Load16(cur_src + 1); + cur_dest[0] = base64[in >> 18]; + in &= 0x3FFFF; + cur_dest[1] = base64[in >> 12]; + in &= 0xFFF; + cur_dest[2] = base64[in >> 6]; + in &= 0x3F; + cur_dest[3] = base64[in]; + cur_dest += 4; + break; + } + default: + // Should not be reached: blocks of 4 bytes are handled + // in the while loop before this switch statement. + GOOGLE_LOG(FATAL) << "Logic problem? szsrc = " << szsrc; + break; + } + return (cur_dest - dest); +} + +static const char kBase64Chars[] = +"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static const char kWebSafeBase64Chars[] = +"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + +int Base64Escape(const unsigned char *src, int szsrc, char *dest, int szdest) { + return Base64EscapeInternal(src, szsrc, dest, szdest, kBase64Chars, true); +} +int WebSafeBase64Escape(const unsigned char *src, int szsrc, char *dest, + int szdest, bool do_padding) { + return Base64EscapeInternal(src, szsrc, dest, szdest, + kWebSafeBase64Chars, do_padding); +} + +void Base64EscapeInternal(const unsigned char* src, int szsrc, + string* dest, bool do_padding, + const char* base64_chars) { + const int calc_escaped_size = + CalculateBase64EscapedLen(szsrc, do_padding); + dest->resize(calc_escaped_size); + const int escaped_len = Base64EscapeInternal(src, szsrc, + string_as_array(dest), + dest->size(), + base64_chars, + do_padding); + GOOGLE_DCHECK_EQ(calc_escaped_size, escaped_len); + dest->erase(escaped_len); +} + +void Base64Escape(const unsigned char *src, int szsrc, + string* dest, bool do_padding) { + Base64EscapeInternal(src, szsrc, dest, do_padding, kBase64Chars); +} + +void WebSafeBase64Escape(const unsigned char *src, int szsrc, + string *dest, bool do_padding) { + Base64EscapeInternal(src, szsrc, dest, do_padding, kWebSafeBase64Chars); +} + +void Base64Escape(StringPiece src, string* dest) { + Base64Escape(reinterpret_cast(src.data()), + src.size(), dest, true); +} + +void WebSafeBase64Escape(StringPiece src, string* dest) { + WebSafeBase64Escape(reinterpret_cast(src.data()), + src.size(), dest, false); +} + +void WebSafeBase64EscapeWithPadding(StringPiece src, string* dest) { + WebSafeBase64Escape(reinterpret_cast(src.data()), + src.size(), dest, true); +} + +// Helper to append a Unicode code point to a string as UTF8, without bringing +// in any external dependencies. +int EncodeAsUTF8Char(uint32 code_point, char* output) { + uint32 tmp = 0; + int len = 0; + if (code_point <= 0x7f) { + tmp = code_point; + len = 1; + } else if (code_point <= 0x07ff) { + tmp = 0x0000c080 | + ((code_point & 0x07c0) << 2) | + (code_point & 0x003f); + len = 2; + } else if (code_point <= 0xffff) { + tmp = 0x00e08080 | + ((code_point & 0xf000) << 4) | + ((code_point & 0x0fc0) << 2) | + (code_point & 0x003f); + len = 3; + } else { + // UTF-16 is only defined for code points up to 0x10FFFF, and UTF-8 is + // normally only defined up to there as well. + tmp = 0xf0808080 | + ((code_point & 0x1c0000) << 6) | + ((code_point & 0x03f000) << 4) | + ((code_point & 0x000fc0) << 2) | + (code_point & 0x003f); + len = 4; + } + tmp = ghtonl(tmp); + memcpy(output, reinterpret_cast(&tmp) + sizeof(tmp) - len, len); + return len; +} + +// Table of UTF-8 character lengths, based on first byte +static const unsigned char kUTF8LenTbl[256] = { + 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, + + 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, + 3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 4,4,4,4,4,4,4,4 +}; + +// Return length of a single UTF-8 source character +int UTF8FirstLetterNumBytes(const char* src, int len) { + if (len == 0) { + return 0; + } + return kUTF8LenTbl[*reinterpret_cast(src)]; +} + } // namespace protobuf } // namespace google diff --git a/src/google/protobuf/stubs/strutil.h b/src/google/protobuf/stubs/strutil.h index 397122ef..b22066b6 100644 --- a/src/google/protobuf/stubs/strutil.h +++ b/src/google/protobuf/stubs/strutil.h @@ -36,6 +36,7 @@ #include #include #include +#include namespace google { namespace protobuf { @@ -72,7 +73,33 @@ inline bool ascii_isdigit(char c) { } inline bool ascii_isspace(char c) { - return c == ' '; + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || + c == '\r'; +} + +inline bool ascii_isupper(char c) { + return c >= 'A' && c <= 'Z'; +} + +inline bool ascii_islower(char c) { + return c >= 'a' && c <= 'z'; +} + +inline char ascii_toupper(char c) { + return ascii_islower(c) ? c - ('a' - 'A') : c; +} + +inline char ascii_tolower(char c) { + return ascii_isupper(c) ? c + ('a' - 'A') : c; +} + +inline int hex_digit_to_int(char c) { + /* Assume ASCII. */ + int x = static_cast(c); + if (x > '9') { + x += 9; + } + return x & 0xf; } // ---------------------------------------------------------------------- @@ -360,12 +387,59 @@ inline uint64 strtou64(const char *nptr, char **endptr, int base) { } // ---------------------------------------------------------------------- +// safe_strtob() // safe_strto32() -// ---------------------------------------------------------------------- -LIBPROTOBUF_EXPORT bool safe_int(string text, int32* value_p); +// safe_strtou32() +// safe_strto64() +// safe_strtou64() +// safe_strtof() +// safe_strtod() +// ---------------------------------------------------------------------- +LIBPROTOBUF_EXPORT bool safe_strtob(StringPiece str, bool* value); + +LIBPROTOBUF_EXPORT bool safe_strto32(const string& str, int32* value); +LIBPROTOBUF_EXPORT bool safe_strtou32(const string& str, uint32* value); +inline bool safe_strto32(const char* str, int32* value) { + return safe_strto32(string(str), value); +} +inline bool safe_strto32(StringPiece str, int32* value) { + return safe_strto32(str.ToString(), value); +} +inline bool safe_strtou32(const char* str, uint32* value) { + return safe_strtou32(string(str), value); +} +inline bool safe_strtou32(StringPiece str, uint32* value) { + return safe_strtou32(str.ToString(), value); +} -inline bool safe_strto32(string text, int32* value) { - return safe_int(text, value); +LIBPROTOBUF_EXPORT bool safe_strto64(const string& str, int64* value); +LIBPROTOBUF_EXPORT bool safe_strtou64(const string& str, uint64* value); +inline bool safe_strto64(const char* str, int64* value) { + return safe_strto64(string(str), value); +} +inline bool safe_strto64(StringPiece str, int64* value) { + return safe_strto64(str.ToString(), value); +} +inline bool safe_strtou64(const char* str, uint64* value) { + return safe_strtou64(string(str), value); +} +inline bool safe_strtou64(StringPiece str, uint64* value) { + return safe_strtou64(str.ToString(), value); +} + +LIBPROTOBUF_EXPORT bool safe_strtof(const char* str, float* value); +LIBPROTOBUF_EXPORT bool safe_strtod(const char* str, double* value); +inline bool safe_strtof(const string& str, float* value) { + return safe_strtof(str.c_str(), value); +} +inline bool safe_strtod(const string& str, double* value) { + return safe_strtod(str.c_str(), value); +} +inline bool safe_strtof(StringPiece str, float* value) { + return safe_strtof(str.ToString(), value); +} +inline bool safe_strtod(StringPiece str, double* value) { + return safe_strtod(str.ToString(), value); } // ---------------------------------------------------------------------- @@ -451,6 +525,10 @@ inline char* FastUInt64ToBuffer(uint64 i, char* buffer) { return buffer; } +inline string SimpleBtoa(bool value) { + return value ? "true" : "false"; +} + // ---------------------------------------------------------------------- // SimpleItoa() // Description: converts an integer to a string. @@ -497,28 +575,30 @@ static const int kFloatToBufferSize = 24; namespace strings { +enum PadSpec { + NO_PAD = 1, + ZERO_PAD_2, + ZERO_PAD_3, + ZERO_PAD_4, + ZERO_PAD_5, + ZERO_PAD_6, + ZERO_PAD_7, + ZERO_PAD_8, + ZERO_PAD_9, + ZERO_PAD_10, + ZERO_PAD_11, + ZERO_PAD_12, + ZERO_PAD_13, + ZERO_PAD_14, + ZERO_PAD_15, + ZERO_PAD_16, +}; + struct Hex { uint64 value; - enum PadSpec { - NONE = 1, - ZERO_PAD_2, - ZERO_PAD_3, - ZERO_PAD_4, - ZERO_PAD_5, - ZERO_PAD_6, - ZERO_PAD_7, - ZERO_PAD_8, - ZERO_PAD_9, - ZERO_PAD_10, - ZERO_PAD_11, - ZERO_PAD_12, - ZERO_PAD_13, - ZERO_PAD_14, - ZERO_PAD_15, - ZERO_PAD_16, - } spec; + enum PadSpec spec; template - explicit Hex(Int v, PadSpec s = NONE) + explicit Hex(Int v, PadSpec s = NO_PAD) : spec(s) { // Prevent sign-extension by casting integers to // their unsigned counterparts. @@ -571,6 +651,9 @@ struct LIBPROTOBUF_EXPORT AlphaNum { AlphaNum(const string& str) : piece_data_(str.data()), piece_size_(str.size()) {} + AlphaNum(StringPiece str) + : piece_data_(str.data()), piece_size_(str.size()) {} + size_t size() const { return piece_size_; } const char *data() const { return piece_data_; } @@ -691,6 +774,12 @@ string Join(const Range& components, return result; } +// ---------------------------------------------------------------------- +// ToHex() +// Return a lower-case hex string representation of the given integer. +// ---------------------------------------------------------------------- +LIBPROTOBUF_EXPORT string ToHex(uint64 num); + // ---------------------------------------------------------------------- // GlobalReplaceSubstring() // Replaces all instances of a substring in a string. Does nothing @@ -702,6 +791,83 @@ LIBPROTOBUF_EXPORT int GlobalReplaceSubstring(const string& substring, const string& replacement, string* s); +// ---------------------------------------------------------------------- +// Base64Unescape() +// Converts "src" which is encoded in Base64 to its binary equivalent and +// writes it to "dest". If src contains invalid characters, dest is cleared +// and the function returns false. Returns true on success. +// ---------------------------------------------------------------------- +LIBPROTOBUF_EXPORT bool Base64Unescape(StringPiece src, string* dest); + +// ---------------------------------------------------------------------- +// WebSafeBase64Unescape() +// This is a variation of Base64Unescape which uses '-' instead of '+', and +// '_' instead of '/'. src is not null terminated, instead specify len. I +// recommend that slen(base64_tests[i].plaintext); + + StringPiece plaintext(base64_tests[i].plaintext, + base64_tests[i].plain_length); + + cypher_length = strlen(base64_tests[i].cyphertext); + + // The basic escape function: + memset(encode_buffer, 0, sizeof(encode_buffer)); + encode_length = Base64Escape(unsigned_plaintext, + base64_tests[i].plain_length, + encode_buffer, + sizeof(encode_buffer)); + // Is it of the expected length? + EXPECT_EQ(encode_length, cypher_length); + // Would it have been okay to allocate only CalculateBase64EscapeLen()? + EXPECT_EQ(CalculateBase64EscapedLen(base64_tests[i].plain_length), + encode_length); + + // Is it the expected encoded value? + EXPECT_STREQ(encode_buffer, base64_tests[i].cyphertext); + + // If we encode it into a buffer of exactly the right length... + memset(encode_buffer, 0, sizeof(encode_buffer)); + encode_length = Base64Escape(unsigned_plaintext, + base64_tests[i].plain_length, + encode_buffer, + cypher_length); + // Is it still of the expected length? + EXPECT_EQ(encode_length, cypher_length); + + // And is the value still correct? (i.e., not losing the last byte) + EXPECT_STREQ(encode_buffer, base64_tests[i].cyphertext); + + // If we decode it back: + decode_str.clear(); + EXPECT_TRUE(Base64Unescape( + StringPiece(encode_buffer, cypher_length), &decode_str)); + + // Is it of the expected length? + EXPECT_EQ(base64_tests[i].plain_length, decode_str.length()); + + // Is it the expected decoded value? + EXPECT_EQ(plaintext, decode_str); + + // Let's try with a pre-populated string. + string encoded("this junk should be ignored"); + Base64Escape(string(base64_tests[i].plaintext, + base64_tests[i].plain_length), + &encoded); + EXPECT_EQ(encoded, string(encode_buffer, cypher_length)); + + string decoded("this junk should be ignored"); + EXPECT_TRUE(Base64Unescape( + StringPiece(encode_buffer, cypher_length), &decoded)); + EXPECT_EQ(decoded.size(), base64_tests[i].plain_length); + EXPECT_EQ_ARRAY(decoded.size(), decoded, base64_tests[i].plaintext, i); + + // Our decoder treats the padding '=' characters at the end as + // optional (but if there are any, there must be the correct + // number of them.) If encode_buffer has any, run some additional + // tests that fiddle with them. + char* first_equals = strchr(encode_buffer, '='); + if (first_equals) { + // How many equals signs does the string start with? + int equals = (*(first_equals+1) == '=') ? 2 : 1; + + // Try chopping off the equals sign(s) entirely. The decoder + // should still be okay with this. + string decoded2("this junk should also be ignored"); + *first_equals = '\0'; + EXPECT_TRUE(Base64Unescape( + StringPiece(encode_buffer, first_equals - encode_buffer), &decoded2)); + EXPECT_EQ(decoded.size(), base64_tests[i].plain_length); + EXPECT_EQ_ARRAY(decoded.size(), decoded, base64_tests[i].plaintext, i); + + // Now test chopping off the equals sign(s) and adding + // whitespace. Our decoder should still accept this. + decoded2.assign("this junk should be ignored"); + *first_equals = ' '; + *(first_equals+1) = '\0'; + EXPECT_TRUE(Base64Unescape( + StringPiece(encode_buffer, first_equals - encode_buffer + 1), + &decoded2)); + EXPECT_EQ(decoded.size(), base64_tests[i].plain_length); + EXPECT_EQ_ARRAY(decoded.size(), decoded, base64_tests[i].plaintext, i); + + // Now stick a bad character at the end of the string. The decoder + // should refuse this string. + decoded2.assign("this junk should be ignored"); + *first_equals = '?'; + *(first_equals+1) = '\0'; + EXPECT_TRUE( + !Base64Unescape( + StringPiece(encode_buffer, first_equals - encode_buffer + 1), + &decoded2)); + + int len; + + // Test whitespace mixed with the padding. (eg "AA = = ") The + // decoder should accept this. + if (equals == 2) { + snprintf(first_equals, 6, " = = "); + len = first_equals - encode_buffer + 5; + } else { + snprintf(first_equals, 6, " = "); + len = first_equals - encode_buffer + 3; + } + decoded2.assign("this junk should be ignored"); + EXPECT_TRUE( + Base64Unescape(StringPiece(encode_buffer, len), &decoded2)); + EXPECT_EQ(decoded.size(), base64_tests[i].plain_length); + EXPECT_EQ_ARRAY(decoded.size(), decoded, base64_tests[i].plaintext, i); + + // Test whitespace mixed with the padding, but with the wrong + // number of equals signs (eg "AA = "). The decoder should + // refuse these strings. + if (equals == 1) { + snprintf(first_equals, 6, " = = "); + len = first_equals - encode_buffer + 5; + } else { + snprintf(first_equals, 6, " = "); + len = first_equals - encode_buffer + 3; + } + EXPECT_TRUE( + !Base64Unescape(StringPiece(encode_buffer, len), &decoded2)); + } + + // Cool! the basic Base64 encoder/decoder works. + // Let's try the alternate alphabet: tr -- '+/' '-_' + + char websafe[100]; + memset(websafe, 0, sizeof(websafe)); + strncpy(websafe, base64_tests[i].cyphertext, cypher_length); + for (int c = 0; c < sizeof(websafe); ++c) { + if ('+' == websafe[c]) { websafe[c] = '-'; } + if ('/' == websafe[c]) { websafe[c] = '_'; } + } + + // The websafe escape function: + memset(encode_buffer, 0, sizeof(encode_buffer)); + encode_length = WebSafeBase64Escape(unsigned_plaintext, + base64_tests[i].plain_length, + encode_buffer, + sizeof(encode_buffer), + true); + // Is it of the expected length? + EXPECT_EQ(encode_length, cypher_length); + EXPECT_EQ( + CalculateBase64EscapedLen(base64_tests[i].plain_length, true), + encode_length); + + // Is it the expected encoded value? + EXPECT_STREQ(encode_buffer, websafe); + + // If we encode it into a buffer of exactly the right length... + memset(encode_buffer, 0, sizeof(encode_buffer)); + encode_length = WebSafeBase64Escape(unsigned_plaintext, + base64_tests[i].plain_length, + encode_buffer, + cypher_length, + true); + // Is it still of the expected length? + EXPECT_EQ(encode_length, cypher_length); + + // And is the value still correct? (i.e., not losing the last byte) + EXPECT_STREQ(encode_buffer, websafe); + + // Let's try the string version of the encoder + encoded = "this junk should be ignored"; + WebSafeBase64Escape( + unsigned_plaintext, base64_tests[i].plain_length, + &encoded, true); + EXPECT_EQ(encoded.size(), cypher_length); + EXPECT_STREQ(encoded.c_str(), websafe); + + // If we decode it back: + memset(decode_buffer, 0, sizeof(decode_buffer)); + decode_length = WebSafeBase64Unescape(encode_buffer, + cypher_length, + decode_buffer, + sizeof(decode_buffer)); + + // Is it of the expected length? + EXPECT_EQ(decode_length, base64_tests[i].plain_length); + + // Is it the expected decoded value? + EXPECT_EQ(0, + memcmp(decode_buffer, base64_tests[i].plaintext, decode_length)); + + // If we decode it into a buffer of exactly the right length... + memset(decode_buffer, 0, sizeof(decode_buffer)); + decode_length = WebSafeBase64Unescape(encode_buffer, + cypher_length, + decode_buffer, + decode_length); + + // Is it still of the expected length? + EXPECT_EQ(decode_length, base64_tests[i].plain_length); + + // And is it the expected decoded value? + EXPECT_EQ(0, + memcmp(decode_buffer, base64_tests[i].plaintext, decode_length)); + + // Try using '.' for the pad character. + for (int c = cypher_length - 1; c >= 0 && '=' == encode_buffer[c]; --c) { + encode_buffer[c] = '.'; + } + + // If we decode it back: + memset(decode_buffer, 0, sizeof(decode_buffer)); + decode_length = WebSafeBase64Unescape(encode_buffer, + cypher_length, + decode_buffer, + sizeof(decode_buffer)); + + // Is it of the expected length? + EXPECT_EQ(decode_length, base64_tests[i].plain_length); + + // Is it the expected decoded value? + EXPECT_EQ(0, + memcmp(decode_buffer, base64_tests[i].plaintext, decode_length)); + + // If we decode it into a buffer of exactly the right length... + memset(decode_buffer, 0, sizeof(decode_buffer)); + decode_length = WebSafeBase64Unescape(encode_buffer, + cypher_length, + decode_buffer, + decode_length); + + // Is it still of the expected length? + EXPECT_EQ(decode_length, base64_tests[i].plain_length); + + // And is it the expected decoded value? + EXPECT_EQ(0, + memcmp(decode_buffer, base64_tests[i].plaintext, decode_length)); + + // Let's try the string version of the decoder + decoded = "this junk should be ignored"; + EXPECT_TRUE(WebSafeBase64Unescape( + StringPiece(encode_buffer, cypher_length), &decoded)); + EXPECT_EQ(decoded.size(), base64_tests[i].plain_length); + EXPECT_EQ_ARRAY(decoded.size(), decoded, base64_tests[i].plaintext, i); + + // Okay! the websafe Base64 encoder/decoder works. + // Let's try the unpadded version + + for (int c = 0; c < sizeof(websafe); ++c) { + if ('=' == websafe[c]) { + websafe[c] = '\0'; + cypher_length = c; + break; + } + } + + // The websafe escape function: + memset(encode_buffer, 0, sizeof(encode_buffer)); + encode_length = WebSafeBase64Escape(unsigned_plaintext, + base64_tests[i].plain_length, + encode_buffer, + sizeof(encode_buffer), + false); + // Is it of the expected length? + EXPECT_EQ(encode_length, cypher_length); + EXPECT_EQ( + CalculateBase64EscapedLen(base64_tests[i].plain_length, false), + encode_length); + + // Is it the expected encoded value? + EXPECT_STREQ(encode_buffer, websafe); + + // If we encode it into a buffer of exactly the right length... + memset(encode_buffer, 0, sizeof(encode_buffer)); + encode_length = WebSafeBase64Escape(unsigned_plaintext, + base64_tests[i].plain_length, + encode_buffer, + cypher_length, + false); + // Is it still of the expected length? + EXPECT_EQ(encode_length, cypher_length); + + // And is the value still correct? (i.e., not losing the last byte) + EXPECT_STREQ(encode_buffer, websafe); + + // Let's try the (other) string version of the encoder + string plain(base64_tests[i].plaintext, base64_tests[i].plain_length); + encoded = "this junk should be ignored"; + WebSafeBase64Escape(plain, &encoded); + EXPECT_EQ(encoded.size(), cypher_length); + EXPECT_STREQ(encoded.c_str(), websafe); + + // If we decode it back: + memset(decode_buffer, 0, sizeof(decode_buffer)); + decode_length = WebSafeBase64Unescape(encode_buffer, + cypher_length, + decode_buffer, + sizeof(decode_buffer)); + + // Is it of the expected length? + EXPECT_EQ(decode_length, base64_tests[i].plain_length); + + // Is it the expected decoded value? + EXPECT_EQ(0, + memcmp(decode_buffer, base64_tests[i].plaintext, decode_length)); + + // If we decode it into a buffer of exactly the right length... + memset(decode_buffer, 0, sizeof(decode_buffer)); + decode_length = WebSafeBase64Unescape(encode_buffer, + cypher_length, + decode_buffer, + decode_length); + + // Is it still of the expected length? + EXPECT_EQ(decode_length, base64_tests[i].plain_length); + + // And is it the expected decoded value? + EXPECT_EQ(0, + memcmp(decode_buffer, base64_tests[i].plaintext, decode_length)); + + + // Let's try the string version of the decoder + decoded = "this junk should be ignored"; + EXPECT_TRUE(WebSafeBase64Unescape( + StringPiece(encode_buffer, cypher_length), &decoded)); + EXPECT_EQ(decoded.size(), base64_tests[i].plain_length); + EXPECT_EQ_ARRAY(decoded.size(), decoded, base64_tests[i].plaintext, i); + + // This value works. Try the next. + } + + // Now try the long strings, this tests the streaming + for (int i = 0; i < sizeof(base64_strings) / sizeof(base64_strings[0]); + ++i) { + + const unsigned char* unsigned_plaintext = + reinterpret_cast(base64_strings[i].plaintext); + int plain_length = strlen(base64_strings[i].plaintext); + int cypher_length = strlen(base64_strings[i].cyphertext); + vector buffer(cypher_length+1); + int encode_length = WebSafeBase64Escape(unsigned_plaintext, + plain_length, + &buffer[0], + buffer.size(), + false); + EXPECT_EQ(cypher_length, encode_length); + EXPECT_EQ( + CalculateBase64EscapedLen(plain_length, false), encode_length); + buffer[ encode_length ] = '\0'; + EXPECT_STREQ(base64_strings[i].cyphertext, &buffer[0]); + } + + // Verify the behavior when decoding bad data + { + const char* bad_data = "ab-/"; + string buf; + EXPECT_FALSE(Base64Unescape(StringPiece(bad_data), &buf)); + EXPECT_TRUE(!WebSafeBase64Unescape(bad_data, &buf)); + EXPECT_TRUE(buf.empty()); + } +} + } // anonymous namespace } // namespace protobuf } // namespace google diff --git a/src/google/protobuf/stubs/time.cc b/src/google/protobuf/stubs/time.cc new file mode 100644 index 00000000..73b99af6 --- /dev/null +++ b/src/google/protobuf/stubs/time.cc @@ -0,0 +1,364 @@ +#include + +#include +#include + +namespace google { +namespace protobuf { +namespace internal { + +namespace { +static const int64 kSecondsPerMinute = 60; +static const int64 kSecondsPerHour = 3600; +static const int64 kSecondsPerDay = kSecondsPerHour * 24; +static const int64 kSecondsPer400Years = + kSecondsPerDay * (400 * 365 + 400 / 4 - 3); +// Seconds from 0001-01-01T00:00:00 to 1970-01-01T:00:00:00 +static const int64 kSecondsFromEraToEpoch = 62135596800LL; +// The range of timestamp values we support. +static const int64 kMinTime = -62135596800LL; // 0001-01-01T00:00:00 +static const int64 kMaxTime = 253402300799LL; // 9999-12-31T23:59:59 + +static const int kNanosPerSecond = 1000000000; +static const int kNanosPerMillisecond = 1000000; +static const int kNanosPerMicrosecond = 1000; + +// Count the seconds from the given year (start at Jan 1, 00:00) to 100 years +// after. +int64 SecondsPer100Years(int year) { + if (year % 400 == 0 || year % 400 > 300) { + return kSecondsPerDay * (100 * 365 + 100 / 4); + } else { + return kSecondsPerDay * (100 * 365 + 100 / 4 - 1); + } +} + +// Count the seconds from the given year (start at Jan 1, 00:00) to 4 years +// after. +int64 SecondsPer4Years(int year) { + if ((year % 100 == 0 || year % 100 > 96) && + !(year % 400 == 0 || year % 400 > 396)) { + // No leap years. + return kSecondsPerDay * (4 * 365); + } else { + // One leap years. + return kSecondsPerDay * (4 * 365 + 1); + } +} + +bool IsLeapYear(int year) { + return year % 400 == 0 || (year % 4 == 0 && year % 100 != 0); +} + +int64 SecondsPerYear(int year) { + return kSecondsPerDay * (IsLeapYear(year) ? 366 : 365); +} + +static const int kDaysInMonth[13] = { + 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 +}; + +int64 SecondsPerMonth(int month, bool leap) { + if (month == 2 && leap) { + return kSecondsPerDay * (kDaysInMonth[month] + 1); + } + return kSecondsPerDay * kDaysInMonth[month]; +} + +static const int kDaysSinceJan[13] = { + 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, +}; + +bool ValidateDateTime(const DateTime& time) { + if (time.year < 1 || time.year > 9999 || + time.month < 1 || time.month > 12 || + time.day < 1 || time.day > 31 || + time.hour < 0 || time.hour > 23 || + time.minute < 0 || time.minute > 59 || + time.second < 0 || time.second > 59) { + return false; + } + if (time.month == 2 && IsLeapYear(time.year)) { + return time.month <= kDaysInMonth[time.month] + 1; + } else { + return time.month <= kDaysInMonth[time.month]; + } +} + +// Count the number of seconds elapsed from 0001-01-01T00:00:00 to the given +// time. +int64 SecondsSinceCommonEra(const DateTime& time) { + int64 result = 0; + // Years should be between 1 and 9999. + assert(time.year >= 1 && time.year <= 9999); + int year = 1; + if ((time.year - year) >= 400) { + int count_400years = (time.year - year) / 400; + result += kSecondsPer400Years * count_400years; + year += count_400years * 400; + } + while ((time.year - year) >= 100) { + result += SecondsPer100Years(year); + year += 100; + } + while ((time.year - year) >= 4) { + result += SecondsPer4Years(year); + year += 4; + } + while (time.year > year) { + result += SecondsPerYear(year); + ++year; + } + // Months should be between 1 and 12. + assert(time.month >= 1 && time.month <= 12); + int month = time.month; + result += kSecondsPerDay * kDaysSinceJan[month]; + if (month > 2 && IsLeapYear(year)) { + result += kSecondsPerDay; + } + assert(time.day >= 1 && + time.day <= (month == 2 && IsLeapYear(year) + ? kDaysInMonth[month] + 1 + : kDaysInMonth[month])); + result += kSecondsPerDay * (time.day - 1); + result += kSecondsPerHour * time.hour + + kSecondsPerMinute * time.minute + + time.second; + return result; +} + +// Format nanoseconds with either 3, 6, or 9 digits depending on the required +// precision to represent the exact value. +string FormatNanos(int32 nanos) { + if (nanos % kNanosPerMillisecond == 0) { + return StringPrintf("%03d", nanos / kNanosPerMillisecond); + } else if (nanos % kNanosPerMicrosecond == 0) { + return StringPrintf("%06d", nanos / kNanosPerMicrosecond); + } else { + return StringPrintf("%09d", nanos); + } +} + +// Parses an integer from a null-terminated char sequence. The method +// consumes at most "width" chars. Returns a pointer after the consumed +// integer, or NULL if the data does not start with an integer or the +// integer value does not fall in the range of [min_value, max_value]. +const char* ParseInt(const char* data, int width, int min_value, + int max_value, int* result) { + if (!ascii_isdigit(*data)) { + return NULL; + } + int value = 0; + for (int i = 0; i < width; ++i, ++data) { + if (ascii_isdigit(*data)) { + value = value * 10 + (*data - '0'); + } else { + break; + } + } + if (value >= min_value && value <= max_value) { + *result = value; + return data; + } else { + return NULL; + } +} + +// Consumes the fractional parts of a second into nanos. For example, +// "010" will be parsed to 10000000 nanos. +const char* ParseNanos(const char* data, int32* nanos) { + if (!ascii_isdigit(*data)) { + return NULL; + } + int value = 0; + int len = 0; + // Consume as many digits as there are but only take the first 9 into + // account. + while (ascii_isdigit(*data)) { + if (len < 9) { + value = value * 10 + *data - '0'; + } + ++len; + ++data; + } + while (len < 9) { + value = value * 10; + ++len; + } + *nanos = value; + return data; +} + +const char* ParseTimezoneOffset(const char* data, int64* offset) { + // Accept format "HH:MM". E.g., "08:00" + int hour; + if ((data = ParseInt(data, 2, 0, 23, &hour)) == NULL) { + return NULL; + } + if (*data++ != ':') { + return NULL; + } + int minute; + if ((data = ParseInt(data, 2, 0, 59, &minute)) == NULL) { + return NULL; + } + *offset = (hour * 60 + minute) * 60; + return data; +} +} // namespace + +bool SecondsToDateTime(int64 seconds, DateTime* time) { + if (seconds < kMinTime || seconds > kMaxTime) { + return false; + } + // It's easier to calcuate the DateTime starting from 0001-01-01T00:00:00 + seconds = seconds + kSecondsFromEraToEpoch; + int year = 1; + if (seconds >= kSecondsPer400Years) { + int count_400years = seconds / kSecondsPer400Years; + year += 400 * count_400years; + seconds %= kSecondsPer400Years; + } + while (seconds >= SecondsPer100Years(year)) { + seconds -= SecondsPer100Years(year); + year += 100; + } + while (seconds >= SecondsPer4Years(year)) { + seconds -= SecondsPer4Years(year); + year += 4; + } + while (seconds >= SecondsPerYear(year)) { + seconds -= SecondsPerYear(year); + year += 1; + } + bool leap = IsLeapYear(year); + int month = 1; + while (seconds >= SecondsPerMonth(month, leap)) { + seconds -= SecondsPerMonth(month, leap); + ++month; + } + int day = 1 + seconds / kSecondsPerDay; + seconds %= kSecondsPerDay; + int hour = seconds / kSecondsPerHour; + seconds %= kSecondsPerHour; + int minute = seconds / kSecondsPerMinute; + seconds %= kSecondsPerMinute; + time->year = year; + time->month = month; + time->day = day; + time->hour = hour; + time->minute = minute; + time->second = static_cast(seconds); + return true; +} + +bool DateTimeToSeconds(const DateTime& time, int64* seconds) { + if (!ValidateDateTime(time)) { + return false; + } + *seconds = SecondsSinceCommonEra(time) - kSecondsFromEraToEpoch; + return true; +} + +void GetCurrentTime(int64* seconds, int32* nanos) { + // TODO(xiaofeng): Improve the accuracy of this implementation (or just + // remove this method from protobuf). + *seconds = time(NULL); + *nanos = 0; +} + +string FormatTime(int64 seconds, int32 nanos) { + DateTime time; + if (nanos < 0 || nanos > 999999999 || !SecondsToDateTime(seconds, &time)) { + return "InvalidTime"; + } + string result = StringPrintf("%04d-%02d-%02dT%02d:%02d:%02d", + time.year, time.month, time.day, + time.hour, time.minute, time.second); + if (nanos != 0) { + result += "." + FormatNanos(nanos); + } + return result + "Z"; +} + +bool ParseTime(const string& value, int64* seconds, int32* nanos) { + DateTime time; + const char* data = value.c_str(); + // We only accept: + // Z-normalized: 2015-05-20T13:29:35.120Z + // With UTC offset: 2015-05-20T13:29:35.120-08:00 + + // Parse year + if ((data = ParseInt(data, 4, 1, 9999, &time.year)) == NULL) { + return false; + } + // Expect '-' + if (*data++ != '-') return false; + // Parse month + if ((data = ParseInt(data, 2, 1, 12, &time.month)) == NULL) { + return false; + } + // Expect '-' + if (*data++ != '-') return false; + // Parse day + if ((data = ParseInt(data, 2, 1, 31, &time.day)) == NULL) { + return false; + } + // Expect 'T' + if (*data++ != 'T') return false; + // Parse hour + if ((data = ParseInt(data, 2, 0, 23, &time.hour)) == NULL) { + return false; + } + // Expect ':' + if (*data++ != ':') return false; + // Parse minute + if ((data = ParseInt(data, 2, 0, 59, &time.minute)) == NULL) { + return false; + } + // Expect ':' + if (*data++ != ':') return false; + // Parse second + if ((data = ParseInt(data, 2, 0, 59, &time.second)) == NULL) { + return false; + } + if (!DateTimeToSeconds(time, seconds)) { + return false; + } + // Parse nanoseconds. + if (*data == '.') { + ++data; + // Parse nanoseconds. + if ((data = ParseNanos(data, nanos)) == NULL) { + return false; + } + } else { + *nanos = 0; + } + // Parse UTC offsets. + if (*data == 'Z') { + ++data; + } else if (*data == '+') { + ++data; + int64 offset; + if ((data = ParseTimezoneOffset(data, &offset)) == NULL) { + return false; + } + *seconds -= offset; + } else if (*data == '-') { + ++data; + int64 offset; + if ((data = ParseTimezoneOffset(data, &offset)) == NULL) { + return false; + } + *seconds += offset; + } else { + return false; + } + // Done with parsing. + return *data == 0; +} + +} // namespace internal +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/stubs/time.h b/src/google/protobuf/stubs/time.h new file mode 100644 index 00000000..0f641dc9 --- /dev/null +++ b/src/google/protobuf/stubs/time.h @@ -0,0 +1,75 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#ifndef GOOGLE_PROTOBUF_STUBS_TIME_H_ +#define GOOGLE_PROTOBUF_STUBS_TIME_H_ + +#include + +namespace google { +namespace protobuf { +namespace internal { + +struct DateTime { + int year; + int month; + int day; + int hour; + int minute; + int second; +}; + +// Converts a timestamp (seconds elapsed since 1970-01-01T00:00:00, could be +// negative to represent time before 1970-01-01) to DateTime. Returns false +// if the timestamp is not in the range between 0001-01-01T00:00:00 and +// 9999-12-31T23:59:59. +bool SecondsToDateTime(int64 seconds, DateTime* time); +// Converts DateTime to a timestamp (seconds since 1970-01-01T00:00:00). +// Returns false if the DateTime is not valid or is not in the valid range. +bool DateTimeToSeconds(const DateTime& time, int64* seconds); + +void GetCurrentTime(int64* seconds, int32* nanos); + +// Formats a time string in RFC3339 fromat. +// +// For example, "2015-05-20T13:29:35.120Z". For nanos, 0, 3, 6 or 9 fractional +// digits will be used depending on how many are required to represent the exact +// value. +// +// Note that "nanos" must in the range of [0, 999999999]. +string FormatTime(int64 seconds, int32 nanos); +// Parses a time string. This method accepts RFC3339 date/time string with UTC +// offset. For example, "2015-05-20T13:29:35.120-08:00". +bool ParseTime(const string& vaule, int64* seconds, int32* nanos); + +} // namespace internal +} // namespace protobuf +} // namespace google + +#endif // GOOGLE_PROTOBUF_STUBS_TIME_H_ diff --git a/src/google/protobuf/stubs/time_test.cc b/src/google/protobuf/stubs/time_test.cc new file mode 100644 index 00000000..59e9d1c7 --- /dev/null +++ b/src/google/protobuf/stubs/time_test.cc @@ -0,0 +1,208 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include + +#include +#include + +namespace google { +namespace protobuf { +namespace internal { +namespace { +static const int64 kSecondsPerDay = 3600 * 24; + +// For DateTime, tests will mostly focuse on the date part because that's +// the tricky one. +int64 CreateTimestamp(int year, int month, int day) { + DateTime time; + time.year = year; + time.month = month; + time.day = day; + time.hour = time.minute = time.second = 0; + int64 result; + GOOGLE_CHECK(DateTimeToSeconds(time, &result)); + // Check that a roundtrip produces the same result. + GOOGLE_CHECK(SecondsToDateTime(result, &time)); + GOOGLE_CHECK(time.year == year); + GOOGLE_CHECK(time.month == month); + GOOGLE_CHECK(time.day == day); + return result; +} + +TEST(DateTimeTest, SimpleTime) { + DateTime time; + ASSERT_TRUE(SecondsToDateTime(1, &time)); + EXPECT_EQ(1970, time.year); + EXPECT_EQ(1, time.month); + EXPECT_EQ(1, time.day); + EXPECT_EQ(0, time.hour); + EXPECT_EQ(0, time.minute); + EXPECT_EQ(1, time.second); + int64 seconds; + ASSERT_TRUE(DateTimeToSeconds(time, &seconds)); + EXPECT_EQ(1, seconds); + + ASSERT_TRUE(SecondsToDateTime(-1, &time)); + EXPECT_EQ(1969, time.year); + EXPECT_EQ(12, time.month); + EXPECT_EQ(31, time.day); + EXPECT_EQ(23, time.hour); + EXPECT_EQ(59, time.minute); + EXPECT_EQ(59, time.second); + ASSERT_TRUE(DateTimeToSeconds(time, &seconds)); + EXPECT_EQ(-1, seconds); + + DateTime start, end; + start.year = 1; + start.month = 1; + start.day = 1; + start.hour = 0; + start.minute = 0; + start.second = 0; + end.year = 9999; + end.month = 12; + end.day = 31; + end.hour = 23; + end.minute = 59; + end.second = 59; + int64 start_time, end_time; + ASSERT_TRUE(DateTimeToSeconds(start, &start_time)); + ASSERT_TRUE(DateTimeToSeconds(end, &end_time)); + EXPECT_EQ(315537897599LL, end_time - start_time); + ASSERT_TRUE(SecondsToDateTime(start_time, &time)); + ASSERT_TRUE(DateTimeToSeconds(time, &seconds)); + EXPECT_EQ(start_time, seconds); + ASSERT_TRUE(SecondsToDateTime(end_time, &time)); + ASSERT_TRUE(DateTimeToSeconds(time, &seconds)); + EXPECT_EQ(end_time, seconds); +} + +TEST(DateTimeTest, DayInMonths) { + // Check that month boundaries are handled correctly. + EXPECT_EQ(kSecondsPerDay, + CreateTimestamp(2015, 1, 1) - CreateTimestamp(2014, 12, 31)); + EXPECT_EQ(kSecondsPerDay, + CreateTimestamp(2015, 2, 1) - CreateTimestamp(2015, 1, 31)); + EXPECT_EQ(kSecondsPerDay, + CreateTimestamp(2015, 3, 1) - CreateTimestamp(2015, 2, 28)); + EXPECT_EQ(kSecondsPerDay, + CreateTimestamp(2015, 4, 1) - CreateTimestamp(2015, 3, 31)); + EXPECT_EQ(kSecondsPerDay, + CreateTimestamp(2015, 5, 1) - CreateTimestamp(2015, 4, 30)); + EXPECT_EQ(kSecondsPerDay, + CreateTimestamp(2015, 6, 1) - CreateTimestamp(2015, 5, 31)); + EXPECT_EQ(kSecondsPerDay, + CreateTimestamp(2015, 7, 1) - CreateTimestamp(2015, 6, 30)); + EXPECT_EQ(kSecondsPerDay, + CreateTimestamp(2015, 8, 1) - CreateTimestamp(2015, 7, 31)); + EXPECT_EQ(kSecondsPerDay, + CreateTimestamp(2015, 9, 1) - CreateTimestamp(2015, 8, 31)); + EXPECT_EQ(kSecondsPerDay, + CreateTimestamp(2015, 10, 1) - CreateTimestamp(2015, 9, 30)); + EXPECT_EQ(kSecondsPerDay, + CreateTimestamp(2015, 11, 1) - CreateTimestamp(2015, 10, 31)); + EXPECT_EQ(kSecondsPerDay, + CreateTimestamp(2015, 12, 1) - CreateTimestamp(2015, 11, 30)); + EXPECT_EQ(kSecondsPerDay, + CreateTimestamp(2016, 1, 1) - CreateTimestamp(2015, 12, 31)); +} + +TEST(DateTimeTest, LeapYear) { + // Non-leap year. + EXPECT_EQ(kSecondsPerDay, + CreateTimestamp(2015, 3, 1) - CreateTimestamp(2015, 2, 28)); + // Leap year. + EXPECT_EQ(kSecondsPerDay, + CreateTimestamp(2016, 3, 1) - CreateTimestamp(2016, 2, 29)); + // Non-leap year. + EXPECT_EQ(kSecondsPerDay, + CreateTimestamp(2100, 3, 1) - CreateTimestamp(2100, 2, 28)); + // Leap year. + EXPECT_EQ(kSecondsPerDay, + CreateTimestamp(2400, 3, 1) - CreateTimestamp(2400, 2, 29)); +} + +TEST(DateTimeTest, StringFormat) { + DateTime start, end; + start.year = 1; + start.month = 1; + start.day = 1; + start.hour = 0; + start.minute = 0; + start.second = 0; + end.year = 9999; + end.month = 12; + end.day = 31; + end.hour = 23; + end.minute = 59; + end.second = 59; + int64 start_time, end_time; + ASSERT_TRUE(DateTimeToSeconds(start, &start_time)); + ASSERT_TRUE(DateTimeToSeconds(end, &end_time)); + + EXPECT_EQ("0001-01-01T00:00:00Z", FormatTime(start_time, 0)); + EXPECT_EQ("9999-12-31T23:59:59Z", FormatTime(end_time, 0)); + + // Make sure the nanoseconds part is formated correctly. + EXPECT_EQ("1970-01-01T00:00:00.010Z", FormatTime(0, 10000000)); + EXPECT_EQ("1970-01-01T00:00:00.000010Z", FormatTime(0, 10000)); + EXPECT_EQ("1970-01-01T00:00:00.000000010Z", FormatTime(0, 10)); +} + +TEST(DateTimeTest, ParseString) { + int64 seconds; + int32 nanos; + ASSERT_TRUE(ParseTime("0001-01-01T00:00:00Z", &seconds, &nanos)); + EXPECT_EQ("0001-01-01T00:00:00Z", FormatTime(seconds, nanos)); + ASSERT_TRUE(ParseTime("9999-12-31T23:59:59.999999999Z", &seconds, &nanos)); + EXPECT_EQ("9999-12-31T23:59:59.999999999Z", FormatTime(seconds, nanos)); + + // Test time zone offsets. + ASSERT_TRUE(ParseTime("1970-01-01T00:00:00-08:00", &seconds, &nanos)); + EXPECT_EQ("1970-01-01T08:00:00Z", FormatTime(seconds, nanos)); + ASSERT_TRUE(ParseTime("1970-01-01T00:00:00+08:00", &seconds, &nanos)); + EXPECT_EQ("1969-12-31T16:00:00Z", FormatTime(seconds, nanos)); + + // Test nanoseconds. + ASSERT_TRUE(ParseTime("1970-01-01T00:00:00.01Z", &seconds, &nanos)); + EXPECT_EQ("1970-01-01T00:00:00.010Z", FormatTime(seconds, nanos)); + ASSERT_TRUE(ParseTime("1970-01-01T00:00:00.00001-08:00", &seconds, &nanos)); + EXPECT_EQ("1970-01-01T08:00:00.000010Z", FormatTime(seconds, nanos)); + ASSERT_TRUE(ParseTime("1970-01-01T00:00:00.00000001+08:00", &seconds, &nanos)); + EXPECT_EQ("1969-12-31T16:00:00.000000010Z", FormatTime(seconds, nanos)); + // Fractional parts less than 1 nanosecond will be ignored. + ASSERT_TRUE(ParseTime("1970-01-01T00:00:00.0123456789Z", &seconds, &nanos)); + EXPECT_EQ("1970-01-01T00:00:00.012345678Z", FormatTime(seconds, nanos)); +} + +} // namespace +} // namespace internal +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/text_format.cc b/src/google/protobuf/text_format.cc index 61dfa5d6..4d8c1f91 100644 --- a/src/google/protobuf/text_format.cc +++ b/src/google/protobuf/text_format.cc @@ -1880,7 +1880,7 @@ void TextFormat::Printer::PrintUnknownFields( generator.Print(field_number); generator.Print(": 0x"); generator.Print( - StrCat(strings::Hex(field.fixed32(), strings::Hex::ZERO_PAD_8))); + StrCat(strings::Hex(field.fixed32(), strings::ZERO_PAD_8))); if (single_line_mode_) { generator.Print(" "); } else { @@ -1892,7 +1892,7 @@ void TextFormat::Printer::PrintUnknownFields( generator.Print(field_number); generator.Print(": 0x"); generator.Print( - StrCat(strings::Hex(field.fixed64(), strings::Hex::ZERO_PAD_16))); + StrCat(strings::Hex(field.fixed64(), strings::ZERO_PAD_16))); if (single_line_mode_) { generator.Print(" "); } else { diff --git a/src/google/protobuf/util/field_comparator.cc b/src/google/protobuf/util/field_comparator.cc new file mode 100644 index 00000000..b7676a88 --- /dev/null +++ b/src/google/protobuf/util/field_comparator.cc @@ -0,0 +1,187 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: ksroka@google.com (Krzysztof Sroka) + +#include + +#include + +#include +#include +#include +#include +#include + +namespace google { +namespace protobuf { +namespace util { + +FieldComparator::FieldComparator() {} +FieldComparator::~FieldComparator() {} + +DefaultFieldComparator::DefaultFieldComparator() + : float_comparison_(EXACT), + treat_nan_as_equal_(false), + has_default_tolerance_(false) { +} + +DefaultFieldComparator::~DefaultFieldComparator() {} + +FieldComparator::ComparisonResult DefaultFieldComparator::Compare( + const google::protobuf::Message& message_1, + const google::protobuf::Message& message_2, + const google::protobuf::FieldDescriptor* field, + int index_1, int index_2, + const google::protobuf::util::FieldContext* field_context) { + const Reflection* reflection_1 = message_1.GetReflection(); + const Reflection* reflection_2 = message_2.GetReflection(); + + switch (field->cpp_type()) { +#define COMPARE_FIELD(METHOD) \ + if (field->is_repeated()) { \ + return ResultFromBoolean(Compare##METHOD( \ + *field, \ + reflection_1->GetRepeated##METHOD(message_1, field, index_1), \ + reflection_2->GetRepeated##METHOD(message_2, field, index_2))); \ + } else { \ + return ResultFromBoolean(Compare##METHOD( \ + *field, \ + reflection_1->Get##METHOD(message_1, field), \ + reflection_2->Get##METHOD(message_2, field))); \ + } \ + break; // Make sure no fall-through is introduced. + + case FieldDescriptor::CPPTYPE_BOOL: + COMPARE_FIELD(Bool); + case FieldDescriptor::CPPTYPE_DOUBLE: + COMPARE_FIELD(Double); + case FieldDescriptor::CPPTYPE_ENUM: + COMPARE_FIELD(Enum); + case FieldDescriptor::CPPTYPE_FLOAT: + COMPARE_FIELD(Float); + case FieldDescriptor::CPPTYPE_INT32: + COMPARE_FIELD(Int32); + case FieldDescriptor::CPPTYPE_INT64: + COMPARE_FIELD(Int64); + case FieldDescriptor::CPPTYPE_STRING: + COMPARE_FIELD(String); + case FieldDescriptor::CPPTYPE_UINT32: + COMPARE_FIELD(UInt32); + case FieldDescriptor::CPPTYPE_UINT64: + COMPARE_FIELD(UInt64); + +#undef COMPARE_FIELD + + case FieldDescriptor::CPPTYPE_MESSAGE: + return RECURSE; + + default: + GOOGLE_LOG(FATAL) << "No comparison code for field " << field->full_name() + << " of CppType = " << field->cpp_type(); + } +} + +void DefaultFieldComparator::SetDefaultFractionAndMargin(double fraction, + double margin) { + default_tolerance_ = Tolerance(fraction, margin); + has_default_tolerance_ = true; +} + +void DefaultFieldComparator::SetFractionAndMargin(const FieldDescriptor* field, + double fraction, + double margin) { + GOOGLE_CHECK(FieldDescriptor::CPPTYPE_FLOAT == field->cpp_type() || + FieldDescriptor::CPPTYPE_DOUBLE == field->cpp_type()) + << "Field has to be float or double type. Field name is: " + << field->full_name(); + map_tolerance_[field] = Tolerance(fraction, margin); +} + +bool DefaultFieldComparator::CompareDouble(const FieldDescriptor& field, + double value_1, double value_2) { + return CompareDoubleOrFloat(field, value_1, value_2); +} + +bool DefaultFieldComparator::CompareEnum(const FieldDescriptor& field, + const EnumValueDescriptor* value_1, + const EnumValueDescriptor* value_2) { + return value_1->number() == value_2->number(); +} + +bool DefaultFieldComparator::CompareFloat(const FieldDescriptor& field, + float value_1, float value_2) { + return CompareDoubleOrFloat(field, value_1, value_2); +} + +template +bool DefaultFieldComparator::CompareDoubleOrFloat(const FieldDescriptor& field, + T value_1, T value_2) { + if (value_1 == value_2) { + // Covers +inf and -inf (which are not within margin or fraction of + // themselves), and is a shortcut for finite values. + return true; + } else if (float_comparison_ == EXACT) { + if (treat_nan_as_equal_ && + MathLimits::IsNaN(value_1) && MathLimits::IsNaN(value_2)) { + return true; + } + return false; + } else { + if (treat_nan_as_equal_ && + MathLimits::IsNaN(value_1) && MathLimits::IsNaN(value_2)) { + return true; + } + // float_comparison_ == APPROXIMATE covers two use cases. + Tolerance* tolerance = FindOrNull(map_tolerance_, &field); + if (tolerance == NULL && has_default_tolerance_) { + tolerance = &default_tolerance_; + } + if (tolerance == NULL) { + return MathUtil::AlmostEquals(value_1, value_2); + } else { + // Use user-provided fraction and margin. Since they are stored as + // doubles, we explicitely cast them to types of values provided. This + // is very likely to fail if provided values are not numeric. + return MathUtil::WithinFractionOrMargin( + value_1, value_2, static_cast(tolerance->fraction), + static_cast(tolerance->margin)); + } + } +} + +FieldComparator::ComparisonResult DefaultFieldComparator::ResultFromBoolean( + bool boolean_result) const { + return boolean_result ? FieldComparator::SAME : FieldComparator::DIFFERENT; +} + +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/util/field_comparator.h b/src/google/protobuf/util/field_comparator.h new file mode 100644 index 00000000..5e893f9c --- /dev/null +++ b/src/google/protobuf/util/field_comparator.h @@ -0,0 +1,259 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: ksroka@google.com (Krzysztof Sroka) + +#ifndef GOOGLE_PROTOBUF_UTIL_FIELD_COMPARATOR_H__ +#define GOOGLE_PROTOBUF_UTIL_FIELD_COMPARATOR_H__ + +#include +#include + +#include + +namespace google { +namespace protobuf { + +class Message; +class EnumValueDescriptor; +class FieldDescriptor; + +namespace util { + +struct FieldContext; + +// Base class specifying the interface for comparing protocol buffer fields. +// Regular users should consider using or subclassing DefaultFieldComparator +// rather than this interface. +// Currently, this does not support comparing unknown fields. +class LIBPROTOBUF_EXPORT FieldComparator { + public: + FieldComparator(); + virtual ~FieldComparator(); + + enum ComparisonResult { + SAME, // Compared fields are equal. In case of comparing submessages, + // user should not recursively compare their contents. + DIFFERENT, // Compared fields are different. In case of comparing + // submessages, user should not recursively compare their + // contents. + RECURSE, // Compared submessages need to be compared recursively. + // FieldComparator does not specify the semantics of recursive + // comparison. This value should not be returned for simple + // values. + }; + + // Compares the values of a field in two protocol buffer messages. + // Returns SAME or DIFFERENT for simple values, and SAME, DIFFERENT or RECURSE + // for submessages. Returning RECURSE for fields not being submessages is + // illegal. + // In case the given FieldDescriptor points to a repeated field, the indices + // need to be valid. Otherwise they should be ignored. + // + // FieldContext contains information about the specific instances of the + // fields being compared, versus FieldDescriptor which only contains general + // type information about the fields. + virtual ComparisonResult Compare( + const google::protobuf::Message& message_1, + const google::protobuf::Message& message_2, + const google::protobuf::FieldDescriptor* field, + int index_1, int index_2, + const google::protobuf::util::FieldContext* field_context) = 0; + + private: + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(FieldComparator); +}; + +// Basic implementation of FieldComparator. Supports four modes of floating +// point value comparison: exact, approximate using MathUtil::AlmostEqual +// method, and arbitrarilly precise using MathUtil::WithinFracionOrMargin. +class LIBPROTOBUF_EXPORT DefaultFieldComparator : public FieldComparator { + public: + enum FloatComparison { + EXACT, // Floats and doubles are compared exactly. + APPROXIMATE, // Floats and doubles are compared using the + // MathUtil::AlmostEqual method or + // MathUtil::WithinFractionOrMargin method. + // TODO(ksroka): Introduce third value to differenciate uses of AlmostEqual + // and WithinFractionOrMargin. + }; + + // Creates new comparator with float comparison set to EXACT. + DefaultFieldComparator(); + + virtual ~DefaultFieldComparator(); + + virtual ComparisonResult Compare( + const google::protobuf::Message& message_1, + const google::protobuf::Message& message_2, + const google::protobuf::FieldDescriptor* field, + int index_1, int index_2, + const google::protobuf::util::FieldContext* field_context); + + void set_float_comparison(FloatComparison float_comparison) { + float_comparison_ = float_comparison; + } + + FloatComparison float_comparison() const { + return float_comparison_; + } + + // Set whether the FieldComparator shall treat floats or doubles that are both + // NaN as equal (treat_nan_as_equal = true) or as different + // (treat_nan_as_equal = false). Default is treating NaNs always as different. + void set_treat_nan_as_equal(bool treat_nan_as_equal) { + treat_nan_as_equal_ = treat_nan_as_equal; + } + + bool treat_nan_as_equal() const { + return treat_nan_as_equal_; + } + + // Sets the fraction and margin for the float comparison of a given field. + // Uses MathUtil::WithinFractionOrMargin to compare the values. + // + // REQUIRES: field->cpp_type == FieldDescriptor::CPPTYPE_DOUBLE or + // field->cpp_type == FieldDescriptor::CPPTYPE_FLOAT + // REQUIRES: float_comparison_ == APPROXIMATE + void SetFractionAndMargin(const FieldDescriptor* field, double fraction, + double margin); + + // Sets the fraction and margin for the float comparison of all float and + // double fields, unless a field has been given a specific setting via + // SetFractionAndMargin() above. + // Uses MathUtil::WithinFractionOrMargin to compare the values. + // + // REQUIRES: float_comparison_ == APPROXIMATE + void SetDefaultFractionAndMargin(double fraction, double margin); + + private: + // Defines the tolerance for floating point comparison (fraction and margin). + struct Tolerance { + double fraction; + double margin; + Tolerance() + : fraction(0.0), + margin(0.0) {} + Tolerance(double f, double m) + : fraction(f), + margin(m) {} + }; + + // Defines the map to store the tolerances for floating point comparison. + typedef map ToleranceMap; + + // The following methods get executed when CompareFields is called for the + // basic types (instead of submessages). They return true on success. One + // can use ResultFromBoolean() to convert that boolean to a ComparisonResult + // value. + bool CompareBool(const google::protobuf::FieldDescriptor& field, + bool value_1, bool value_2) { + return value_1 == value_2; + } + + // Uses CompareDoubleOrFloat, a helper function used by both CompareDouble and + // CompareFloat. + bool CompareDouble(const google::protobuf::FieldDescriptor& field, + double value_1, double value_2); + + bool CompareEnum(const google::protobuf::FieldDescriptor& field, + const EnumValueDescriptor* value_1, + const EnumValueDescriptor* value_2); + + // Uses CompareDoubleOrFloat, a helper function used by both CompareDouble and + // CompareFloat. + bool CompareFloat(const google::protobuf::FieldDescriptor& field, + float value_1, float value_2); + + bool CompareInt32(const google::protobuf::FieldDescriptor& field, + int32 value_1, int32 value_2) { + return value_1 == value_2; + } + + bool CompareInt64(const google::protobuf::FieldDescriptor& field, + int64 value_1, int64 value_2) { + return value_1 == value_2; + } + + bool CompareString(const google::protobuf::FieldDescriptor& field, + const string& value_1, const string& value_2) { + return value_1 == value_2; + } + + bool CompareUInt32(const google::protobuf::FieldDescriptor& field, + uint32 value_1, uint32 value_2) { + return value_1 == value_2; + } + + bool CompareUInt64(const google::protobuf::FieldDescriptor& field, + uint64 value_1, uint64 value_2) { + return value_1 == value_2; + } + + // This function is used by CompareDouble and CompareFloat to avoid code + // duplication. There are no checks done against types of the values passed, + // but it's likely to fail if passed non-numeric arguments. + template + bool CompareDoubleOrFloat(const google::protobuf::FieldDescriptor& field, + T value_1, T value_2); + + // Returns FieldComparator::SAME if boolean_result is true and + // FieldComparator::DIFFERENT otherwise. + ComparisonResult ResultFromBoolean(bool boolean_result) const; + + FloatComparison float_comparison_; + + // If true, floats and doubles that are both NaN are considered to be + // equal. Otherwise, two floats or doubles that are NaN are considered to be + // different. + bool treat_nan_as_equal_; + + // True iff default_tolerance_ has been explicitly set. + // + // If false, then the default tolerance for flaots and doubles is that which + // is used by MathUtil::AlmostEquals(). + bool has_default_tolerance_; + + // Default float/double tolerance. Only meaningful if + // has_default_tolerance_ == true. + Tolerance default_tolerance_; + + // Field-specific float/double tolerances, which override any default for + // those particular fields. + ToleranceMap map_tolerance_; + + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(DefaultFieldComparator); +}; + +} // namespace util +} // namespace protobuf + +} // namespace google +#endif // GOOGLE_PROTOBUF_UTIL_FIELD_COMPARATOR_H__ diff --git a/src/google/protobuf/util/field_comparator_test.cc b/src/google/protobuf/util/field_comparator_test.cc new file mode 100644 index 00000000..748c7d11 --- /dev/null +++ b/src/google/protobuf/util/field_comparator_test.cc @@ -0,0 +1,483 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: ksroka@google.com (Krzysztof Sroka) + +#include + +#include +#include +#include +#include + +namespace google { +namespace protobuf { +namespace util { +namespace { + +using protobuf_unittest::TestAllTypes; + +class DefaultFieldComparatorTest : public ::testing::Test { + protected: + void SetUp() { + descriptor_ = TestAllTypes::descriptor(); + } + + const Descriptor* descriptor_; + DefaultFieldComparator comparator_; + TestAllTypes message_1_; + TestAllTypes message_2_; +}; + +TEST_F(DefaultFieldComparatorTest, RecursesIntoGroup) { + const FieldDescriptor* field = + descriptor_->FindFieldByName("optionalgroup"); + EXPECT_EQ(FieldComparator::RECURSE, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); +} + +TEST_F(DefaultFieldComparatorTest, RecursesIntoNestedMessage) { + const FieldDescriptor* field = + descriptor_->FindFieldByName("optional_nested_message"); + EXPECT_EQ(FieldComparator::RECURSE, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); +} + +TEST_F(DefaultFieldComparatorTest, RecursesIntoForeignMessage) { + const FieldDescriptor* field = + descriptor_->FindFieldByName("optional_foreign_message"); + EXPECT_EQ(FieldComparator::RECURSE, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); +} + +TEST_F(DefaultFieldComparatorTest, Int32Comparison) { + const FieldDescriptor* field = descriptor_->FindFieldByName("optional_int32"); + message_1_.set_optional_int32(1); + message_2_.set_optional_int32(1); + + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); + + message_2_.set_optional_int32(-1); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); +} + +TEST_F(DefaultFieldComparatorTest, Int64Comparison) { + const FieldDescriptor* field = descriptor_->FindFieldByName("optional_int64"); + message_1_.set_optional_int64(1L); + message_2_.set_optional_int64(1L); + + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); + + message_2_.set_optional_int64(-1L); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); +} + +TEST_F(DefaultFieldComparatorTest, UInt32Comparison) { + const FieldDescriptor* field = + descriptor_->FindFieldByName("optional_uint32"); + message_1_.set_optional_uint32(1); + message_2_.set_optional_uint32(1); + + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); + + message_2_.set_optional_uint32(2); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); +} + +TEST_F(DefaultFieldComparatorTest, UInt64Comparison) { + const FieldDescriptor* field = + descriptor_->FindFieldByName("optional_uint64"); + message_1_.set_optional_uint64(1L); + message_2_.set_optional_uint64(1L); + + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); + + message_2_.set_optional_uint64(2L); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); +} + +TEST_F(DefaultFieldComparatorTest, BooleanComparison) { + const FieldDescriptor* field = + descriptor_->FindFieldByName("optional_bool"); + message_1_.set_optional_bool(true); + message_2_.set_optional_bool(true); + + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); + + message_2_.set_optional_bool(false); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); +} + +TEST_F(DefaultFieldComparatorTest, EnumComparison) { + const FieldDescriptor* field = + descriptor_->FindFieldByName("optional_nested_enum"); + message_1_.set_optional_nested_enum(TestAllTypes::BAR); + message_2_.set_optional_nested_enum(TestAllTypes::BAR); + + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); + + message_2_.set_optional_nested_enum(TestAllTypes::BAZ); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); +} + +TEST_F(DefaultFieldComparatorTest, StringComparison) { + const FieldDescriptor* field = + descriptor_->FindFieldByName("optional_string"); + message_1_.set_optional_string("foo"); + message_2_.set_optional_string("foo"); + + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); + + message_2_.set_optional_string("bar"); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field, -1, -1, NULL)); +} + +TEST_F(DefaultFieldComparatorTest, FloatingPointComparisonExact) { + const FieldDescriptor* field_float = + descriptor_->FindFieldByName("optional_float"); + const FieldDescriptor* field_double = + descriptor_->FindFieldByName("optional_double"); + + message_1_.set_optional_float(0.1f); + message_2_.set_optional_float(0.1f); + message_1_.set_optional_double(0.1); + message_2_.set_optional_double(0.1); + + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, + field_float, -1, -1, NULL)); + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, + field_double, -1, -1, NULL)); + + message_2_.set_optional_float(0.2f); + message_2_.set_optional_double(0.2); + + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, + field_float, -1, -1, NULL)); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, + field_double, -1, -1, NULL)); +} + +TEST_F(DefaultFieldComparatorTest, FloatingPointComparisonApproximate) { + const FieldDescriptor* field_float = + descriptor_->FindFieldByName("optional_float"); + const FieldDescriptor* field_double = + descriptor_->FindFieldByName("optional_double"); + + message_1_.set_optional_float(2.300005f); + message_2_.set_optional_float(2.300006f); + message_1_.set_optional_double(2.3000000000000003); + message_2_.set_optional_double(2.3000000000000007); + + // Approximate comparison depends on MathUtil, so we assert on MathUtil + // results first to check if that's where the failure was introduced. + ASSERT_NE(message_1_.optional_float(), message_2_.optional_float()); + ASSERT_NE(message_1_.optional_double(), message_2_.optional_double()); + ASSERT_TRUE(MathUtil::AlmostEquals(message_1_.optional_float(), + message_2_.optional_float())); + ASSERT_TRUE(MathUtil::AlmostEquals(message_1_.optional_double(), + message_2_.optional_double())); + + // DefaultFieldComparator's default float comparison mode is EXACT. + ASSERT_EQ(DefaultFieldComparator::EXACT, comparator_.float_comparison()); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, + field_float, -1, -1, NULL)); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, + field_double, -1, -1, NULL)); + + comparator_.set_float_comparison(DefaultFieldComparator::APPROXIMATE); + + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, + field_float, -1, -1, NULL)); + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, + field_double, -1, -1, NULL)); +} + +TEST_F(DefaultFieldComparatorTest, FloatingPointComparisonTreatNaNsAsEqual) { + const FieldDescriptor* field_float = + descriptor_->FindFieldByName("optional_float"); + const FieldDescriptor* field_double = + descriptor_->FindFieldByName("optional_double"); + + message_1_.set_optional_float(MathLimits::kNaN); + message_2_.set_optional_float(MathLimits::kNaN); + message_1_.set_optional_double(MathLimits::kNaN); + message_2_.set_optional_double(MathLimits::kNaN); + + // DefaultFieldComparator's default float comparison mode is EXACT with + // treating NaNs as different. + ASSERT_EQ(DefaultFieldComparator::EXACT, comparator_.float_comparison()); + ASSERT_EQ(false, comparator_.treat_nan_as_equal()); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, + field_float, -1, -1, NULL)); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, + field_double, -1, -1, NULL)); + comparator_.set_float_comparison(DefaultFieldComparator::APPROXIMATE); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, + field_float, -1, -1, NULL)); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, + field_double, -1, -1, NULL)); + + comparator_.set_treat_nan_as_equal(true); + ASSERT_EQ(true, comparator_.treat_nan_as_equal()); + comparator_.set_float_comparison(DefaultFieldComparator::EXACT); + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, + field_float, -1, -1, NULL)); + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, + field_double, -1, -1, NULL)); + comparator_.set_float_comparison(DefaultFieldComparator::APPROXIMATE); + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, + field_float, -1, -1, NULL)); + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, + field_double, -1, -1, NULL)); +} + +TEST_F(DefaultFieldComparatorTest, + FloatingPointComparisonWithinFractionOrMargin) { + const FieldDescriptor* field_float = + descriptor_->FindFieldByName("optional_float"); + const FieldDescriptor* field_double = + descriptor_->FindFieldByName("optional_double"); + + message_1_.set_optional_float(100.0f); + message_2_.set_optional_float(109.9f); + message_1_.set_optional_double(100.0); + message_2_.set_optional_double(109.9); + + comparator_.set_float_comparison(DefaultFieldComparator::APPROXIMATE); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, + field_float, -1, -1, NULL)); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, + field_double, -1, -1, NULL)); + + // Should fail since the fraction is too low. + comparator_.SetFractionAndMargin(field_float, 0.01, 0.0); + comparator_.SetFractionAndMargin(field_double, 0.01, 0.0); + + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, + field_float, -1, -1, NULL)); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, + field_double, -1, -1, NULL)); + + // Should fail since the margin is too low. + comparator_.SetFractionAndMargin(field_float, 0.0, 9.0); + comparator_.SetFractionAndMargin(field_double, 0.0, 9.0); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, + field_float, -1, -1, NULL)); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, + field_double, -1, -1, NULL)); + + // Should succeed since the fraction is high enough. + comparator_.SetFractionAndMargin(field_float, 0.2, 0.0); + comparator_.SetFractionAndMargin(field_double, 0.2, 0.0); + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, + field_float, -1, -1, NULL)); + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, + field_double, -1, -1, NULL)); + + // Should succeed since the margin is high enough. + comparator_.SetFractionAndMargin(field_float, 0.0, 10.0); + comparator_.SetFractionAndMargin(field_double, 0.0, 10.0); + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, + field_float, -1, -1, NULL)); + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, + field_double, -1, -1, NULL)); + + // Setting values for one of the fields should not affect the other. + comparator_.SetFractionAndMargin(field_double, 0.0, 0.0); + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, + field_float, -1, -1, NULL)); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, + field_double, -1, -1, NULL)); + + // +inf should be equal even though they are not technically within margin or + // fraction. + message_1_.set_optional_float(numeric_limits::infinity()); + message_2_.set_optional_float(numeric_limits::infinity()); + message_1_.set_optional_double(numeric_limits::infinity()); + message_2_.set_optional_double(numeric_limits::infinity()); + comparator_.SetFractionAndMargin(field_float, 0.0, 0.0); + comparator_.SetFractionAndMargin(field_double, 0.0, 0.0); + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, + field_float, -1, -1, NULL)); + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, + field_double, -1, -1, NULL)); + + // -inf should be equal even though they are not technically within margin or + // fraction. + message_1_.set_optional_float(-numeric_limits::infinity()); + message_2_.set_optional_float(-numeric_limits::infinity()); + message_1_.set_optional_double(-numeric_limits::infinity()); + message_2_.set_optional_double(-numeric_limits::infinity()); + comparator_.SetFractionAndMargin(field_float, 0.0, 0.0); + comparator_.SetFractionAndMargin(field_double, 0.0, 0.0); + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, + field_float, -1, -1, NULL)); + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, + field_double, -1, -1, NULL)); +} + +TEST_F(DefaultFieldComparatorTest, + FloatingPointComparisonWithinDefaultFractionOrMargin) { + const FieldDescriptor* field_float = + descriptor_->FindFieldByName("optional_float"); + const FieldDescriptor* field_double = + descriptor_->FindFieldByName("optional_double"); + + message_1_.set_optional_float(100.0f); + message_2_.set_optional_float(109.9f); + message_1_.set_optional_double(100.0); + message_2_.set_optional_double(109.9); + + comparator_.set_float_comparison(DefaultFieldComparator::APPROXIMATE); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, + field_float, -1, -1, NULL)); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, + field_double, -1, -1, NULL)); + + // Set default fraction and margin. + comparator_.SetDefaultFractionAndMargin(0.01, 0.0); + + // Float comparisons should fail since the fraction is too low. + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, + field_float, -1, -1, NULL)); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, + field_double, -1, -1, NULL)); + + // Set field-specific fraction and margin for one field (field_float) but not + // the other (field_double) + comparator_.SetFractionAndMargin(field_float, 0.2, 0.0); + + // The field with the override should succeed, since its field-specific + // fraction is high enough. + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, + field_float, -1, -1, NULL)); + // The field with no override should fail, since the default fraction is too + // low + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, + field_double, -1, -1, NULL)); + + // Set the default fraction and margin high enough so that fields that use + // the default should succeed + comparator_.SetDefaultFractionAndMargin(0.2, 0.0); + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, + field_double, -1, -1, NULL)); + + // The field with an override should still be OK + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, + field_float, -1, -1, NULL)); + + // Set fraction and margin for the field with an override to be too low + comparator_.SetFractionAndMargin(field_float, 0.01, 0.0); + + // Now our default is high enough but field_float's override is too low. + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, + field_float, -1, -1, NULL)); + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, + field_double, -1, -1, NULL)); +} + +// Simple test checking whether we compare values at correct indices. +TEST_F(DefaultFieldComparatorTest, RepeatedFieldComparison) { + const FieldDescriptor* field = + descriptor_->FindFieldByName("repeated_string"); + + message_1_.add_repeated_string("foo"); + message_1_.add_repeated_string("bar"); + message_2_.add_repeated_string("bar"); + message_2_.add_repeated_string("baz"); + + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field, 0, 0, NULL)); + EXPECT_EQ(FieldComparator::DIFFERENT, + comparator_.Compare(message_1_, message_2_, field, 1, 1, NULL)); + EXPECT_EQ(FieldComparator::SAME, + comparator_.Compare(message_1_, message_2_, field, 1, 0, NULL)); +} + +} // namespace util +} // namespace protobuf +} // namespace +} // namespace google diff --git a/src/google/protobuf/util/internal/constants.h b/src/google/protobuf/util/internal/constants.h new file mode 100644 index 00000000..0cb8f6e1 --- /dev/null +++ b/src/google/protobuf/util/internal/constants.h @@ -0,0 +1,93 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_CONSTANTS_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_CONSTANTS_H__ + +#include + +// This file contains constants used by //net/proto2/util/converter. + +namespace google { +namespace protobuf { +namespace util { +namespace converter { +// Prefix for type URLs. +const char kTypeServiceBaseUrl[] = "type.googleapis.com"; + +// Format string for RFC3339 timestamp formatting. +const char kRfc3339TimeFormat[] = "%Y-%m-%dT%H:%M:%S"; + +// Minimum seconds allowed in a google.protobuf.TimeStamp or Duration value. +const int64 kMinSeconds = -315576000000; + +// Maximum seconds allowed in a google.protobuf.TimeStamp or Duration value. +const int64 kMaxSeconds = 315576000000; + +// Nano seconds in a second. +const int32 kNanosPerSecond = 1000000000; + +// Type url representing NULL values in google.protobuf.Struct type. +const char kStructNullValueTypeUrl[] = + "type.googleapis.com/google.protobuf.NullValue"; + +// Type string for google.protobuf.Struct +const char kStructType[] = "google.protobuf.Struct"; + +// Type string for struct.proto's google.protobuf.Value value type. +const char kStructValueType[] = "google.protobuf.Value"; + +// Type string for struct.proto's google.protobuf.ListValue value type. +const char kStructListValueType[] = "google.protobuf.ListValue"; + +// Type string for google.protobuf.Timestamp +const char kTimestampType[] = "google.protobuf.Timestamp"; + +// Type string for google.protobuf.Duration +const char kDurationType[] = "google.protobuf.Duration"; + +// Type URL for struct value type google.protobuf.Value +const char kStructValueTypeUrl[] = "type.googleapis.com/google.protobuf.Value"; + +// Type URL for struct value type google.protobuf.Value +const char kStructTypeUrl[] = "type.googleapis.com/google.protobuf.Struct"; + +// Type string for google.protobuf.Any +const char kAnyType[] = "google.protobuf.Any"; + +// The type URL of google.protobuf.FieldMask; +const char kFieldMaskTypeUrl[] = + "type.googleapis.com/google.protobuf.FieldMask"; + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_CONSTANTS_H__ diff --git a/src/google/protobuf/util/internal/datapiece.cc b/src/google/protobuf/util/internal/datapiece.cc new file mode 100644 index 00000000..72b737e9 --- /dev/null +++ b/src/google/protobuf/util/internal/datapiece.cc @@ -0,0 +1,285 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include +#include +#include +#include +#include +#include + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +using google::protobuf::EnumDescriptor; +using google::protobuf::EnumValueDescriptor; +; +; +using util::error::Code; +using util::Status; +using util::StatusOr; + +namespace { + +inline Status InvalidArgument(StringPiece value_str) { + return Status(util::error::INVALID_ARGUMENT, value_str); +} + +// For general conversion between +// int32, int64, uint32, uint64, double and float +// except conversion between double and float. +template +StatusOr NumberConvertAndCheck(From before) { + if (::google::protobuf::internal::is_same::value) return before; + To after = static_cast(before); + if (after == before && + MathUtil::Sign(before) == MathUtil::Sign(after)) { + return after; + } else { + return InvalidArgument(::google::protobuf::internal::is_integral::value + ? ValueAsString(before) + : ::google::protobuf::internal::is_same::value + ? DoubleAsString(before) + : FloatAsString(before)); + } +} + +// For conversion between double and float only. +template +StatusOr FloatingPointConvertAndCheck(From before) { + if (isnan(before)) return std::numeric_limits::quiet_NaN(); + + To after = static_cast(before); + if (MathUtil::AlmostEquals(after, before)) { + return after; + } else { + return InvalidArgument(::google::protobuf::internal::is_same::value + ? DoubleAsString(before) + : FloatAsString(before)); + } +} + +} // namespace + +StatusOr DataPiece::ToInt32() const { + if (type_ == TYPE_STRING) { + return StringToNumber(safe_strto32); + } + return GenericConvert(); +} + +StatusOr DataPiece::ToUint32() const { + if (type_ == TYPE_STRING) { + return StringToNumber(safe_strtou32); + } + return GenericConvert(); +} + +StatusOr DataPiece::ToInt64() const { + if (type_ == TYPE_STRING) { + return StringToNumber(safe_strto64); + } + return GenericConvert(); +} + +StatusOr DataPiece::ToUint64() const { + if (type_ == TYPE_STRING) { + return StringToNumber(safe_strtou64); + } + return GenericConvert(); +} + +StatusOr DataPiece::ToDouble() const { + if (type_ == TYPE_FLOAT) { + return FloatingPointConvertAndCheck(float_); + } + if (type_ == TYPE_STRING) { + if (str_ == "Infinity") return std::numeric_limits::infinity(); + if (str_ == "-Infinity") return -std::numeric_limits::infinity(); + if (str_ == "NaN") return std::numeric_limits::quiet_NaN(); + return StringToNumber(safe_strtod); + } + return GenericConvert(); +} + +StatusOr DataPiece::ToFloat() const { + if (type_ == TYPE_DOUBLE) { + return FloatingPointConvertAndCheck(double_); + } + if (type_ == TYPE_STRING) { + if (str_ == "Infinity") return std::numeric_limits::infinity(); + if (str_ == "-Infinity") return -std::numeric_limits::infinity(); + if (str_ == "NaN") return std::numeric_limits::quiet_NaN(); + // SafeStrToFloat() is used instead of safe_strtof() because the later + // does not fail on inputs like SimpleDtoa(DBL_MAX). + return StringToNumber(SafeStrToFloat); + } + return GenericConvert(); +} + +StatusOr DataPiece::ToBool() const { + switch (type_) { + case TYPE_BOOL: + return bool_; + case TYPE_STRING: + return StringToNumber(safe_strtob); + default: + return InvalidArgument( + ValueAsStringOrDefault("Wrong type. Cannot convert to Bool.")); + } +} + +StatusOr DataPiece::ToString() const { + switch (type_) { + case TYPE_STRING: + return str_.ToString(); + case TYPE_BYTES: { + string base64; + WebSafeBase64Escape(str_, &base64); + return base64; + } + default: + return InvalidArgument( + ValueAsStringOrDefault("Cannot convert to string.")); + } +} + +string DataPiece::ValueAsStringOrDefault(StringPiece default_string) const { + switch (type_) { + case TYPE_INT32: + return SimpleItoa(i32_); + case TYPE_INT64: + return SimpleItoa(i64_); + case TYPE_UINT32: + return SimpleItoa(u32_); + case TYPE_UINT64: + return SimpleItoa(u64_); + case TYPE_DOUBLE: + return DoubleAsString(double_); + case TYPE_FLOAT: + return FloatAsString(float_); + case TYPE_BOOL: + return SimpleBtoa(bool_); + case TYPE_STRING: + return StrCat("\"", str_.ToString(), "\""); + case TYPE_BYTES: { + string base64; + WebSafeBase64Escape(str_, &base64); + return StrCat("\"", base64, "\""); + } + case TYPE_NULL: + return "null"; + default: + return default_string.ToString(); + } +} + +StatusOr DataPiece::ToBytes() const { + if (type_ == TYPE_BYTES) return str_.ToString(); + if (type_ == TYPE_STRING) { + string decoded; + if (!WebSafeBase64Unescape(str_, &decoded)) { + if (!Base64Unescape(str_, &decoded)) { + return InvalidArgument( + ValueAsStringOrDefault("Invalid data in input.")); + } + } + return decoded; + } else { + return InvalidArgument(ValueAsStringOrDefault( + "Wrong type. Only String or Bytes can be converted to Bytes.")); + } +} + +StatusOr DataPiece::ToEnum(const google::protobuf::Enum* enum_type) const { + if (type_ == TYPE_NULL) return google::protobuf::NULL_VALUE; + + if (type_ == TYPE_STRING) { + // First try the given value as a name. + string enum_name = str_.ToString(); + const google::protobuf::EnumValue* value = + FindEnumValueByNameOrNull(enum_type, enum_name); + if (value != NULL) return value->number(); + // Next try a normalized name. + for (string::iterator it = enum_name.begin(); it != enum_name.end(); ++it) { + *it = *it == '-' ? '_' : ascii_toupper(*it); + } + value = FindEnumValueByNameOrNull(enum_type, enum_name); + if (value != NULL) return value->number(); + } else { + StatusOr value = ToInt32(); + if (value.ok()) { + if (const google::protobuf::EnumValue* enum_value = + FindEnumValueByNumberOrNull(enum_type, value.ValueOrDie())) { + return enum_value->number(); + } + } + } + return InvalidArgument( + ValueAsStringOrDefault("Cannot find enum with given value.")); +} + +template +StatusOr DataPiece::GenericConvert() const { + switch (type_) { + case TYPE_INT32: + return NumberConvertAndCheck(i32_); + case TYPE_INT64: + return NumberConvertAndCheck(i64_); + case TYPE_UINT32: + return NumberConvertAndCheck(u32_); + case TYPE_UINT64: + return NumberConvertAndCheck(u64_); + case TYPE_DOUBLE: + return NumberConvertAndCheck(double_); + case TYPE_FLOAT: + return NumberConvertAndCheck(float_); + default: // TYPE_ENUM, TYPE_STRING, TYPE_CORD, TYPE_BOOL + return InvalidArgument(ValueAsStringOrDefault( + "Wrong type. Bool, Enum, String and Cord not supported in " + "GenericConvert.")); + } +} + +template +StatusOr DataPiece::StringToNumber(bool (*func)(StringPiece, To*)) const { + To result; + if (func(str_, &result)) return result; + return InvalidArgument(StrCat("\"", str_.ToString(), "\"")); +} + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/util/internal/datapiece.h b/src/google/protobuf/util/internal/datapiece.h new file mode 100644 index 00000000..30947252 --- /dev/null +++ b/src/google/protobuf/util/internal/datapiece.h @@ -0,0 +1,212 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_DATAPIECE_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_DATAPIECE_H__ + +#include + +#include +#include +#include + + +namespace google { +namespace protobuf { +class Enum; +} // namespace protobuf + + +namespace protobuf { +namespace util { +namespace converter { + +// Container for a single piece of data together with its data type. +// +// For primitive types (int32, int64, uint32, uint64, double, float, bool), +// the data is stored by value. +// +// For string, a StringPiece is stored. For Cord, a pointer to Cord is stored. +// Just like StringPiece, the DataPiece class does not own the storage for +// the actual string or Cord, so it is the user's responsiblity to guarantee +// that the underlying storage is still valid when the DataPiece is accessed. +class LIBPROTOBUF_EXPORT DataPiece { + public: + // Identifies data type of the value. + // These are the types supported by DataPiece. + enum Type { + TYPE_INT32 = 1, + TYPE_INT64 = 2, + TYPE_UINT32 = 3, + TYPE_UINT64 = 4, + TYPE_DOUBLE = 5, + TYPE_FLOAT = 6, + TYPE_BOOL = 7, + TYPE_ENUM = 8, + TYPE_STRING = 9, + TYPE_BYTES = 10, + TYPE_NULL = 11, // explicit NULL type + }; + + // Constructors and Destructor + explicit DataPiece(const int32 value) : type_(TYPE_INT32), i32_(value) {} + explicit DataPiece(const int64 value) : type_(TYPE_INT64), i64_(value) {} + explicit DataPiece(const uint32 value) : type_(TYPE_UINT32), u32_(value) {} + explicit DataPiece(const uint64 value) : type_(TYPE_UINT64), u64_(value) {} + explicit DataPiece(const double value) : type_(TYPE_DOUBLE), double_(value) {} + explicit DataPiece(const float value) : type_(TYPE_FLOAT), float_(value) {} + explicit DataPiece(const bool value) : type_(TYPE_BOOL), bool_(value) {} + explicit DataPiece(StringPiece value) + : type_(TYPE_STRING), + str_(StringPiecePod::CreateFromStringPiece(value)) {} + // Constructor for bytes. The second parameter is not used. + explicit DataPiece(StringPiece value, bool dummy) + : type_(TYPE_BYTES), str_(StringPiecePod::CreateFromStringPiece(value)) {} + DataPiece(const DataPiece& r) : type_(r.type_), str_(r.str_) {} + DataPiece& operator=(const DataPiece& x) { + type_ = x.type_; + str_ = x.str_; + return *this; + } + + static DataPiece NullData() { return DataPiece(TYPE_NULL, 0); } + + virtual ~DataPiece() {} + + // Accessors + Type type() const { return type_; } + + StringPiece str() const { + GOOGLE_LOG_IF(DFATAL, type_ != TYPE_STRING) << "Not a string type."; + return str_; + } + + + // Parses, casts or converts the value stored in the DataPiece into an int32. + util::StatusOr ToInt32() const; + + // Parses, casts or converts the value stored in the DataPiece into a uint32. + util::StatusOr ToUint32() const; + + // Parses, casts or converts the value stored in the DataPiece into an int64. + util::StatusOr ToInt64() const; + + // Parses, casts or converts the value stored in the DataPiece into a uint64. + util::StatusOr ToUint64() const; + + // Parses, casts or converts the value stored in the DataPiece into a double. + util::StatusOr ToDouble() const; + + // Parses, casts or converts the value stored in the DataPiece into a float. + util::StatusOr ToFloat() const; + + // Parses, casts or converts the value stored in the DataPiece into a bool. + util::StatusOr ToBool() const; + + // Parses, casts or converts the value stored in the DataPiece into a string. + util::StatusOr ToString() const; + + // Tries to convert the value contained in this datapiece to string. If the + // conversion fails, it returns the default_string. + string ValueAsStringOrDefault(StringPiece default_string) const; + + util::StatusOr ToBytes() const; + + // Converts a value into protocol buffer enum number. If the value is a + // string, first attempts conversion by name, trying names as follows: + // 1) the directly provided string value. + // 2) the value upper-cased and replacing '-' by '_' + // If the value is not a string, attempts to convert to a 32-bit integer. + // If none of these succeeds, returns a conversion error status. + util::StatusOr ToEnum(const google::protobuf::Enum* enum_type) const; + + private: + // Disallow implicit constructor. + DataPiece(); + + // Helper to create NULL or ENUM types. + DataPiece(Type type, int32 val) : type_(type), i32_(val) {} + + // For numeric conversion between + // int32, int64, uint32, uint64, double, float and bool + template + util::StatusOr GenericConvert() const; + + // For conversion from string to + // int32, int64, uint32, uint64, double, float and bool + template + util::StatusOr StringToNumber(bool (*func)(StringPiece, To*)) const; + + // Data type for this piece of data. + Type type_; + + // StringPiece is not a POD and can not be used in an union (pre C++11). We + // need a POD version of it. + struct StringPiecePod { + const char* data; + int size; + + // Create from a StringPiece. + static StringPiecePod CreateFromStringPiece(StringPiece str) { + StringPiecePod pod; + pod.data = str.data(); + pod.size = str.size(); + return pod; + } + + // Cast to StringPiece. + operator StringPiece() const { return StringPiece(data, size); } + + bool operator==(const char* value) const { + return StringPiece(data, size) == StringPiece(value); + } + + string ToString() const { return string(data, size); } + }; + + // Stored piece of data. + union { + const int32 i32_; + const int64 i64_; + const uint32 u32_; + const uint64 u64_; + const double double_; + const float float_; + const bool bool_; + StringPiecePod str_; + }; +}; + +} // namespace converter +} // namespace util +} // namespace protobuf + +} // namespace google +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_DATAPIECE_H__ diff --git a/src/google/protobuf/util/internal/default_value_objectwriter.cc b/src/google/protobuf/util/internal/default_value_objectwriter.cc new file mode 100644 index 00000000..267e2cd3 --- /dev/null +++ b/src/google/protobuf/util/internal/default_value_objectwriter.cc @@ -0,0 +1,515 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include + +#include +#include + +namespace google { +namespace protobuf { +namespace util { +using util::Status; +using util::StatusOr; +namespace converter { + +DefaultValueObjectWriter::DefaultValueObjectWriter( + TypeResolver* type_resolver, const google::protobuf::Type& type, + ObjectWriter* ow) + : typeinfo_(TypeInfo::NewTypeInfo(type_resolver)), + type_(type), + disable_normalize_(false), + current_(NULL), + root_(NULL), + ow_(ow) {} + +DefaultValueObjectWriter::~DefaultValueObjectWriter() { + for (int i = 0; i < string_values_.size(); ++i) { + delete string_values_[i]; + } +} + +DefaultValueObjectWriter* DefaultValueObjectWriter::RenderBool(StringPiece name, + bool value) { + if (current_ == NULL) { + ow_->RenderBool(name, value); + } else { + RenderDataPiece(name, DataPiece(value)); + } + return this; +} + +DefaultValueObjectWriter* DefaultValueObjectWriter::RenderInt32( + StringPiece name, int32 value) { + if (current_ == NULL) { + ow_->RenderInt32(name, value); + } else { + RenderDataPiece(name, DataPiece(value)); + } + return this; +} + +DefaultValueObjectWriter* DefaultValueObjectWriter::RenderUint32( + StringPiece name, uint32 value) { + if (current_ == NULL) { + ow_->RenderUint32(name, value); + } else { + RenderDataPiece(name, DataPiece(value)); + } + return this; +} + +DefaultValueObjectWriter* DefaultValueObjectWriter::RenderInt64( + StringPiece name, int64 value) { + if (current_ == NULL) { + ow_->RenderInt64(name, value); + } else { + RenderDataPiece(name, DataPiece(value)); + } + return this; +} + +DefaultValueObjectWriter* DefaultValueObjectWriter::RenderUint64( + StringPiece name, uint64 value) { + if (current_ == NULL) { + ow_->RenderUint64(name, value); + } else { + RenderDataPiece(name, DataPiece(value)); + } + return this; +} + +DefaultValueObjectWriter* DefaultValueObjectWriter::RenderDouble( + StringPiece name, double value) { + if (current_ == NULL) { + ow_->RenderDouble(name, value); + } else { + RenderDataPiece(name, DataPiece(value)); + } + return this; +} + +DefaultValueObjectWriter* DefaultValueObjectWriter::RenderFloat( + StringPiece name, float value) { + if (current_ == NULL) { + ow_->RenderBool(name, value); + } else { + RenderDataPiece(name, DataPiece(value)); + } + return this; +} + +DefaultValueObjectWriter* DefaultValueObjectWriter::RenderString( + StringPiece name, StringPiece value) { + if (current_ == NULL) { + ow_->RenderString(name, value); + } else { + // Since StringPiece is essentially a pointer, takes a copy of "value" to + // avoid ownership issues. + string_values_.push_back(new string(value.ToString())); + RenderDataPiece(name, DataPiece(*string_values_.back())); + } + return this; +} + +DefaultValueObjectWriter* DefaultValueObjectWriter::RenderBytes( + StringPiece name, StringPiece value) { + if (current_ == NULL) { + ow_->RenderBytes(name, value); + } else { + RenderDataPiece(name, DataPiece(value)); + } + return this; +} + +DefaultValueObjectWriter* DefaultValueObjectWriter::RenderNull( + StringPiece name) { + if (current_ == NULL) { + ow_->RenderNull(name); + } else { + RenderDataPiece(name, DataPiece::NullData()); + } + return this; +} + +DefaultValueObjectWriter* +DefaultValueObjectWriter::DisableCaseNormalizationForNextKey() { + disable_normalize_ = true; + return this; +} + +DefaultValueObjectWriter::Node::Node(const string& name, + const google::protobuf::Type* type, + NodeKind kind, const DataPiece& data, + bool is_placeholder) + : name_(name), + type_(type), + kind_(kind), + disable_normalize_(false), + is_any_(false), + data_(data), + is_placeholder_(is_placeholder) {} + +DefaultValueObjectWriter::Node* DefaultValueObjectWriter::Node::FindChild( + StringPiece name) { + if (name.empty() || kind_ != OBJECT) { + return NULL; + } + for (int i = 0; i < children_.size(); ++i) { + Node* child = children_[i]; + if (child->name() == name) { + return child; + } + } + return NULL; +} + +void DefaultValueObjectWriter::Node::WriteTo(ObjectWriter* ow) { + if (disable_normalize_) { + ow->DisableCaseNormalizationForNextKey(); + } + if (kind_ == PRIMITIVE) { + ObjectWriter::RenderDataPieceTo(data_, name_, ow); + return; + } + if (is_placeholder_) { + // If is_placeholder_ = true, we didn't see this node in the response, so + // skip output. + return; + } + if (kind_ == LIST) { + ow->StartList(name_); + } else { + ow->StartObject(name_); + } + for (int i = 0; i < children_.size(); ++i) { + Node* child = children_[i]; + child->WriteTo(ow); + } + if (kind_ == LIST) { + ow->EndList(); + } else { + ow->EndObject(); + } +} + +const google::protobuf::Type* DefaultValueObjectWriter::Node::GetMapValueType( + const google::protobuf::Type& found_type, TypeInfo* typeinfo) { + // If this field is a map, we should use the type of its "Value" as + // the type of the child node. + for (int i = 0; i < found_type.fields_size(); ++i) { + const google::protobuf::Field& sub_field = found_type.fields(i); + if (sub_field.number() != 2) { + continue; + } + if (sub_field.kind() != google::protobuf::Field_Kind_TYPE_MESSAGE) { + // This map's value type is not a message type. We don't need to + // get the field_type in this case. + break; + } + util::StatusOr sub_type = + typeinfo->ResolveTypeUrl(sub_field.type_url()); + if (!sub_type.ok()) { + GOOGLE_LOG(WARNING) << "Cannot resolve type '" << sub_field.type_url() << "'."; + } else { + return sub_type.ValueOrDie(); + } + break; + } + return NULL; +} + +void DefaultValueObjectWriter::Node::PopulateChildren(TypeInfo* typeinfo) { + // Ignores well known types that don't require automatically populating their + // primitive children. For type "Any", we only populate its children when the + // "@type" field is set. + // TODO(tsun): remove "kStructValueType" from the list. It's being checked + // now because of a bug in the tool-chain that causes the "oneof_index" + // of kStructValueType to not be set correctly. + if (type_ == NULL || type_->name() == kAnyType || + type_->name() == kStructType || type_->name() == kTimestampType || + type_->name() == kDurationType || type_->name() == kStructValueType) { + return; + } + std::vector new_children; + hash_map orig_children_map; + + // Creates a map of child nodes to speed up lookup. + for (int i = 0; i < children_.size(); ++i) { + InsertIfNotPresent(&orig_children_map, children_[i]->name_, i); + } + + for (int i = 0; i < type_->fields_size(); ++i) { + const google::protobuf::Field& field = type_->fields(i); + hash_map::iterator found = + orig_children_map.find(field.name()); + // If the child field has already been set, we just add it to the new list + // of children. + if (found != orig_children_map.end()) { + new_children.push_back(children_[found->second]); + children_[found->second] = NULL; + continue; + } + + const google::protobuf::Type* field_type = NULL; + bool is_map = false; + NodeKind kind = PRIMITIVE; + + if (field.kind() == google::protobuf::Field_Kind_TYPE_MESSAGE) { + kind = OBJECT; + util::StatusOr found_result = + typeinfo->ResolveTypeUrl(field.type_url()); + if (!found_result.ok()) { + // "field" is of an unknown type. + GOOGLE_LOG(WARNING) << "Cannot resolve type '" << field.type_url() << "'."; + } else { + const google::protobuf::Type* found_type = found_result.ValueOrDie(); + is_map = IsMap(field, *found_type); + + if (!is_map) { + field_type = found_type; + } else { + // If this field is a map, we should use the type of its "Value" as + // the type of the child node. + field_type = GetMapValueType(*found_type, typeinfo); + kind = MAP; + } + } + } + if (!is_map && + field.cardinality() == + google::protobuf::Field_Cardinality_CARDINALITY_REPEATED) { + kind = LIST; + } + // If the child field is of primitive type, sets its data to the default + // value of its type. + // If oneof_index() != 0, the child field is part of a "oneof", which means + // the child field is optional and we shouldn't populate its default value. + google::protobuf::scoped_ptr child( + new Node(field.name(), field_type, kind, + ((kind == PRIMITIVE && field.oneof_index() == 0) + ? CreateDefaultDataPieceForField(field) + : DataPiece::NullData()), + true)); + new_children.push_back(child.release()); + } + // Adds all leftover nodes in children_ to the beginning of new_child. + for (int i = 0; i < children_.size(); ++i) { + if (children_[i] == NULL) { + continue; + } + new_children.insert(new_children.begin(), children_[i]); + children_[i] = NULL; + } + children_.swap(new_children); +} + +void DefaultValueObjectWriter::MaybePopulateChildrenOfAny(Node* node) { + // If this is an "Any" node with "@type" already given and no other children + // have been added, populates its children. + if (node != NULL && node->is_any() && node->type() != NULL && + node->type()->name() != kAnyType && node->number_of_children() == 1) { + node->PopulateChildren(typeinfo_.get()); + } +} + +DataPiece DefaultValueObjectWriter::CreateDefaultDataPieceForField( + const google::protobuf::Field& field) { + switch (field.kind()) { + case google::protobuf::Field_Kind_TYPE_DOUBLE: { + return DataPiece(static_cast(0)); + } + case google::protobuf::Field_Kind_TYPE_FLOAT: { + return DataPiece(static_cast(0)); + } + case google::protobuf::Field_Kind_TYPE_INT64: + case google::protobuf::Field_Kind_TYPE_SINT64: + case google::protobuf::Field_Kind_TYPE_SFIXED64: { + return DataPiece(static_cast(0)); + } + case google::protobuf::Field_Kind_TYPE_UINT64: + case google::protobuf::Field_Kind_TYPE_FIXED64: { + return DataPiece(static_cast(0)); + } + case google::protobuf::Field_Kind_TYPE_INT32: + case google::protobuf::Field_Kind_TYPE_SINT32: + case google::protobuf::Field_Kind_TYPE_SFIXED32: { + return DataPiece(static_cast(0)); + } + case google::protobuf::Field_Kind_TYPE_BOOL: { + return DataPiece(false); + } + case google::protobuf::Field_Kind_TYPE_STRING: { + return DataPiece(string()); + } + case google::protobuf::Field_Kind_TYPE_BYTES: { + return DataPiece("", false); + } + case google::protobuf::Field_Kind_TYPE_UINT32: + case google::protobuf::Field_Kind_TYPE_FIXED32: { + return DataPiece(static_cast(0)); + } + default: { return DataPiece::NullData(); } + } +} + +DefaultValueObjectWriter* DefaultValueObjectWriter::StartObject( + StringPiece name) { + if (current_ == NULL) { + root_.reset(new Node(name.ToString(), &type_, OBJECT, DataPiece::NullData(), + false)); + root_->set_disable_normalize(GetAndResetDisableNormalize()); + root_->PopulateChildren(typeinfo_.get()); + current_ = root_.get(); + return this; + } + MaybePopulateChildrenOfAny(current_); + Node* child = current_->FindChild(name); + if (current_->kind() == LIST || current_->kind() == MAP || child == NULL) { + // If current_ is a list or a map node, we should create a new child and use + // the type of current_ as the type of the new child. + google::protobuf::scoped_ptr node(new Node( + name.ToString(), ((current_->kind() == LIST || current_->kind() == MAP) + ? current_->type() + : NULL), + OBJECT, DataPiece::NullData(), false)); + child = node.get(); + current_->AddChild(node.release()); + } + + child->set_is_placeholder(false); + child->set_disable_normalize(GetAndResetDisableNormalize()); + if (child->kind() == OBJECT && child->number_of_children() == 0) { + child->PopulateChildren(typeinfo_.get()); + } + + stack_.push(current_); + current_ = child; + return this; +} + +DefaultValueObjectWriter* DefaultValueObjectWriter::EndObject() { + if (stack_.empty()) { + // The root object ends here. Writes out the tree. + WriteRoot(); + return this; + } + current_ = stack_.top(); + stack_.pop(); + return this; +} + +DefaultValueObjectWriter* DefaultValueObjectWriter::StartList( + StringPiece name) { + if (current_ == NULL) { + root_.reset( + new Node(name.ToString(), &type_, LIST, DataPiece::NullData(), false)); + root_->set_disable_normalize(GetAndResetDisableNormalize()); + current_ = root_.get(); + return this; + } + MaybePopulateChildrenOfAny(current_); + Node* child = current_->FindChild(name); + if (child == NULL || child->kind() != LIST) { + GOOGLE_LOG(WARNING) << "Cannot find field '" << name << "'."; + google::protobuf::scoped_ptr node( + new Node(name.ToString(), NULL, LIST, DataPiece::NullData(), false)); + child = node.get(); + current_->AddChild(node.release()); + } + child->set_is_placeholder(false); + child->set_disable_normalize(GetAndResetDisableNormalize()); + + stack_.push(current_); + current_ = child; + return this; +} + +void DefaultValueObjectWriter::WriteRoot() { + root_->WriteTo(ow_); + root_.reset(NULL); + current_ = NULL; +} + +DefaultValueObjectWriter* DefaultValueObjectWriter::EndList() { + if (stack_.empty()) { + WriteRoot(); + return this; + } + current_ = stack_.top(); + stack_.pop(); + return this; +} + +void DefaultValueObjectWriter::RenderDataPiece(StringPiece name, + const DataPiece& data) { + MaybePopulateChildrenOfAny(current_); + util::StatusOr data_string = data.ToString(); + if (current_->type() != NULL && current_->type()->name() == kAnyType && + name == "@type" && data_string.ok()) { + const string& string_value = data_string.ValueOrDie(); + // If the type of current_ is "Any" and its "@type" field is being set here, + // sets the type of current_ to be the type specified by the "@type". + util::StatusOr found_type = + typeinfo_->ResolveTypeUrl(string_value); + if (!found_type.ok()) { + GOOGLE_LOG(WARNING) << "Failed to resolve type '" << string_value << "'."; + } else { + current_->set_type(found_type.ValueOrDie()); + } + current_->set_is_any(true); + // If the "@type" field is placed after other fields, we should populate + // other children of primitive type now. Otherwise, we should wait until the + // first value field is rendered before we populate the children, because + // the "value" field of a Any message could be omitted. + if (current_->number_of_children() > 1 && current_->type() != NULL) { + current_->PopulateChildren(typeinfo_.get()); + } + } + Node* child = current_->FindChild(name); + if (child == NULL || child->kind() != PRIMITIVE) { + GOOGLE_LOG(WARNING) << "Cannot find primitive field '" << name << "'."; + // No children are found, creates a new child. + google::protobuf::scoped_ptr node( + new Node(name.ToString(), NULL, PRIMITIVE, data, false)); + child = node.get(); + current_->AddChild(node.release()); + } else { + child->set_data(data); + } + child->set_disable_normalize(GetAndResetDisableNormalize()); +} + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/util/internal/default_value_objectwriter.h b/src/google/protobuf/util/internal/default_value_objectwriter.h new file mode 100644 index 00000000..ae013aa4 --- /dev/null +++ b/src/google/protobuf/util/internal/default_value_objectwriter.h @@ -0,0 +1,238 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_DEFAULT_VALUE_OBJECTWRITER_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_DEFAULT_VALUE_OBJECTWRITER_H__ + +#include +#ifndef _SHARED_PTR_H +#include +#endif +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +// An ObjectWriter that renders non-repeated primitive fields of proto messages +// with their default values. DefaultValueObjectWriter holds objects, lists and +// fields it receives in a tree structure and writes them out to another +// ObjectWriter when EndObject() is called on the root object. It also writes +// out all non-repeated primitive fields that haven't been explicitly rendered +// with their default values (0 for numbers, "" for strings, etc). +class DefaultValueObjectWriter : public ObjectWriter { + public: + DefaultValueObjectWriter(TypeResolver* type_resolver, + const google::protobuf::Type& type, + ObjectWriter* ow); + + virtual ~DefaultValueObjectWriter(); + + // ObjectWriter methods. + virtual DefaultValueObjectWriter* StartObject(StringPiece name); + + virtual DefaultValueObjectWriter* EndObject(); + + virtual DefaultValueObjectWriter* StartList(StringPiece name); + + virtual DefaultValueObjectWriter* EndList(); + + virtual DefaultValueObjectWriter* RenderBool(StringPiece name, bool value); + + virtual DefaultValueObjectWriter* RenderInt32(StringPiece name, int32 value); + + virtual DefaultValueObjectWriter* RenderUint32(StringPiece name, + uint32 value); + + virtual DefaultValueObjectWriter* RenderInt64(StringPiece name, int64 value); + + virtual DefaultValueObjectWriter* RenderUint64(StringPiece name, + uint64 value); + + virtual DefaultValueObjectWriter* RenderDouble(StringPiece name, + double value); + + virtual DefaultValueObjectWriter* RenderFloat(StringPiece name, float value); + + virtual DefaultValueObjectWriter* RenderString(StringPiece name, + StringPiece value); + virtual DefaultValueObjectWriter* RenderBytes(StringPiece name, + StringPiece value); + + virtual DefaultValueObjectWriter* RenderNull(StringPiece name); + + virtual DefaultValueObjectWriter* DisableCaseNormalizationForNextKey(); + + private: + enum NodeKind { + PRIMITIVE = 0, + OBJECT = 1, + LIST = 2, + MAP = 3, + }; + + // "Node" represents a node in the tree that holds the input of + // DefaultValueObjectWriter. + class Node { + public: + Node(const string& name, const google::protobuf::Type* type, NodeKind kind, + const DataPiece& data, bool is_placeholder); + virtual ~Node() { + for (int i = 0; i < children_.size(); ++i) { + delete children_[i]; + } + } + + // Adds a child to this node. Takes ownership of this child. + void AddChild(Node* child) { children_.push_back(child); } + + // Finds the child given its name. + Node* FindChild(StringPiece name); + + // Populates children of this Node based on its type. If there are already + // children created, they will be merged to the result. Caller should pass + // in TypeInfo for looking up types of the children. + void PopulateChildren(TypeInfo* typeinfo); + + // 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); + + // Accessors + const string& name() const { return name_; } + + const google::protobuf::Type* type() { return type_; } + + void set_type(const google::protobuf::Type* type) { type_ = type; } + + NodeKind kind() { return kind_; } + + int number_of_children() { return children_.size(); } + + void set_data(const DataPiece& data) { data_ = data; } + + void set_disable_normalize(bool disable_normalize) { + disable_normalize_ = disable_normalize; + } + + bool is_any() { return is_any_; } + + void set_is_any(bool is_any) { is_any_ = is_any; } + + void set_is_placeholder(bool is_placeholder) { + is_placeholder_ = is_placeholder; + } + + private: + // Returns the Value Type of a map given the Type of the map entry and a + // TypeInfo instance. + const google::protobuf::Type* GetMapValueType( + const google::protobuf::Type& entry_type, TypeInfo* typeinfo); + + // The name of this node. + string name_; + // google::protobuf::Type of this node. Owned by TypeInfo. + const google::protobuf::Type* type_; + // The kind of this node. + NodeKind kind_; + // Whether to disable case normalization of the name. + bool disable_normalize_; + // Whether this is a node for "Any". + bool is_any_; + // The data of this node when it is a leaf node. + DataPiece data_; + // Children of this node. + std::vector children_; + // Whether this node is a placeholder for an object or list automatically + // generated when creating the parent node. Should be set to false after + // the parent node's StartObject()/StartList() method is called with this + // node's name. + bool is_placeholder_; + }; + + // Populates children of "node" if it is an "any" Node and its real type has + // been given. + void MaybePopulateChildrenOfAny(Node* node); + + // Writes the root_ node to ow_ and resets the root_ and current_ pointer to + // NULL. + void WriteRoot(); + + // Creates a DataPiece containing the default value of the type of the field. + static DataPiece CreateDefaultDataPieceForField( + const google::protobuf::Field& field); + + // Returns disable_normalize_ and reset it to false. + bool GetAndResetDisableNormalize() { + return disable_normalize_ ? (disable_normalize_ = false, true) : false; + } + + // Adds or replaces the data_ of a primitive child node. + void RenderDataPiece(StringPiece name, const DataPiece& data); + + // Type information for all the types used in the descriptor. Used to find + // google::protobuf::Type of nested messages/enums. + google::protobuf::scoped_ptr typeinfo_; + // google::protobuf::Type of the root message type. + const google::protobuf::Type& type_; + // Holds copies of strings passed to RenderString. + vector string_values_; + + // Whether to disable case normalization of the next node. + bool disable_normalize_; + // The current Node. Owned by its parents. + Node* current_; + // The root Node. + google::protobuf::scoped_ptr root_; + // The stack to hold the path of Nodes from current_ to root_; + std::stack stack_; + + ObjectWriter* ow_; + + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(DefaultValueObjectWriter); +}; + +} // namespace converter +} // namespace util +} // namespace protobuf + +} // namespace google +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_DEFAULT_VALUE_OBJECTWRITER_H__ diff --git a/src/google/protobuf/util/internal/default_value_objectwriter_test.cc b/src/google/protobuf/util/internal/default_value_objectwriter_test.cc new file mode 100644 index 00000000..593c7105 --- /dev/null +++ b/src/google/protobuf/util/internal/default_value_objectwriter_test.cc @@ -0,0 +1,139 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include +#include +#include + +namespace google { +namespace protobuf { +namespace util { +namespace converter { +namespace testing { + +using google::protobuf::testing::DefaultValueTest; + +// Tests to cover some basic DefaultValueObjectWriter use cases. More tests are +// in the marshalling_test.cc and translator_integration_test.cc. +class DefaultValueObjectWriterTest + : public ::testing::TestWithParam { + protected: + DefaultValueObjectWriterTest() + : helper_(GetParam()), mock_(), expects_(&mock_) { + helper_.ResetTypeInfo(DefaultValueTest::descriptor()); + testing_.reset(helper_.NewDefaultValueWriter( + string(kTypeServiceBaseUrl) + "/" + + DefaultValueTest::descriptor()->full_name(), + &mock_)); + } + + virtual ~DefaultValueObjectWriterTest() {} + + TypeInfoTestHelper helper_; + MockObjectWriter mock_; + ExpectingObjectWriter expects_; + google::protobuf::scoped_ptr testing_; +}; + +INSTANTIATE_TEST_CASE_P(DifferentTypeInfoSourceTest, + DefaultValueObjectWriterTest, + ::testing::Values( + testing::USE_TYPE_RESOLVER)); + +TEST_P(DefaultValueObjectWriterTest, Empty) { + // Set expectation + expects_.StartObject("") + ->RenderDouble("double_value", 0.0) + ->RenderFloat("float_value", 0.0) + ->RenderInt64("int64_value", 0) + ->RenderUint64("uint64_value", 0) + ->RenderInt32("int32_value", 0) + ->RenderUint32("uint32_value", 0) + ->RenderBool("bool_value", false) + ->RenderString("string_value", "") + ->RenderBytes("bytes_value", "") + ->EndObject(); + + // Actual testing + testing_->StartObject("")->EndObject(); +} + +TEST_P(DefaultValueObjectWriterTest, NonDefaultDouble) { + // Set expectation + expects_.StartObject("") + ->RenderDouble("double_value", 1.0) + ->RenderFloat("float_value", 0.0) + ->RenderInt64("int64_value", 0) + ->RenderUint64("uint64_value", 0) + ->RenderInt32("int32_value", 0) + ->RenderUint32("uint32_value", 0) + ->RenderBool("bool_value", false) + ->RenderString("string_value", "") + ->EndObject(); + + // Actual testing + testing_->StartObject("")->RenderDouble("double_value", 1.0)->EndObject(); +} + +TEST_P(DefaultValueObjectWriterTest, ShouldRetainUnknownField) { + // Set expectation + expects_.StartObject("") + ->RenderDouble("double_value", 1.0) + ->RenderFloat("float_value", 0.0) + ->RenderInt64("int64_value", 0) + ->RenderUint64("uint64_value", 0) + ->RenderInt32("int32_value", 0) + ->RenderUint32("uint32_value", 0) + ->RenderBool("bool_value", false) + ->RenderString("string_value", "") + ->RenderString("unknown", "abc") + ->StartObject("unknown_object") + ->RenderString("unknown", "def") + ->EndObject() + ->EndObject(); + + // Actual testing + testing_->StartObject("") + ->RenderDouble("double_value", 1.0) + ->RenderString("unknown", "abc") + ->StartObject("unknown_object") + ->RenderString("unknown", "def") + ->EndObject() + ->EndObject(); +} + +} // namespace testing +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/util/internal/error_listener.cc b/src/google/protobuf/util/internal/error_listener.cc new file mode 100644 index 00000000..538307ba --- /dev/null +++ b/src/google/protobuf/util/internal/error_listener.cc @@ -0,0 +1,42 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/util/internal/error_listener.h b/src/google/protobuf/util/internal/error_listener.h new file mode 100644 index 00000000..9b907df5 --- /dev/null +++ b/src/google/protobuf/util/internal/error_listener.h @@ -0,0 +1,99 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_ERROR_LISTENER_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_ERROR_LISTENER_H__ + +#include +#ifndef _SHARED_PTR_H +#include +#endif +#include + +#include +#include +#include + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +// Interface for error listener. +class LIBPROTOBUF_EXPORT ErrorListener { + public: + virtual ~ErrorListener() {} + + // Reports an invalid name at the given location. + virtual void InvalidName(const LocationTrackerInterface& loc, + StringPiece unknown_name, StringPiece message) = 0; + + // Reports an invalid value for a field. + virtual void InvalidValue(const LocationTrackerInterface& loc, + StringPiece type_name, StringPiece value) = 0; + + // Reports a missing required field. + virtual void MissingField(const LocationTrackerInterface& loc, + StringPiece missing_name) = 0; + + protected: + ErrorListener() {} + + private: + // Do not add any data members to this class. + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(ErrorListener); +}; + +// An error listener that ignores all errors. +class LIBPROTOBUF_EXPORT NoopErrorListener : public ErrorListener { + public: + NoopErrorListener() {} + virtual ~NoopErrorListener() {} + + virtual void InvalidName(const LocationTrackerInterface& loc, + StringPiece unknown_name, StringPiece message) {} + + virtual void InvalidValue(const LocationTrackerInterface& loc, + StringPiece type_name, StringPiece value) {} + + virtual void MissingField(const LocationTrackerInterface& loc, + StringPiece missing_name) {} + + private: + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(NoopErrorListener); +}; + + +} // namespace converter +} // namespace util +} // namespace protobuf + +} // namespace google +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_ERROR_LISTENER_H__ diff --git a/src/google/protobuf/util/internal/expecting_objectwriter.h b/src/google/protobuf/util/internal/expecting_objectwriter.h new file mode 100644 index 00000000..75096221 --- /dev/null +++ b/src/google/protobuf/util/internal/expecting_objectwriter.h @@ -0,0 +1,238 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_EXPECTING_OBJECTWRITER_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_EXPECTING_OBJECTWRITER_H__ + +// An implementation of ObjectWriter that automatically sets the +// gmock expectations for the response to a method. Every method +// returns the object itself for chaining. +// +// Usage: +// // Setup +// MockObjectWriter mock; +// ExpectingObjectWriter ow(&mock); +// +// // Set expectation +// ow.StartObject("") +// ->RenderString("key", "value") +// ->EndObject(); +// +// // Actual testing +// mock.StartObject(StringPiece()) +// ->RenderString("key", "value") +// ->EndObject(); + +#include +#include +#include + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +using testing::IsEmpty; +using testing::NanSensitiveDoubleEq; +using testing::NanSensitiveFloatEq; +using testing::Return; +using testing::StrEq; +using testing::TypedEq; + +class MockObjectWriter : public ObjectWriter { + public: + MockObjectWriter() {} + + MOCK_METHOD1(StartObject, ObjectWriter*(StringPiece)); + MOCK_METHOD0(EndObject, ObjectWriter*()); + MOCK_METHOD1(StartList, ObjectWriter*(StringPiece)); + MOCK_METHOD0(EndList, ObjectWriter*()); + MOCK_METHOD2(RenderBool, ObjectWriter*(StringPiece, const bool)); + MOCK_METHOD2(RenderInt32, ObjectWriter*(StringPiece, const int32)); + MOCK_METHOD2(RenderUint32, ObjectWriter*(StringPiece, const uint32)); + MOCK_METHOD2(RenderInt64, ObjectWriter*(StringPiece, const int64)); + MOCK_METHOD2(RenderUint64, ObjectWriter*(StringPiece, const uint64)); + MOCK_METHOD2(RenderDouble, ObjectWriter*(StringPiece, const double)); + MOCK_METHOD2(RenderFloat, ObjectWriter*(StringPiece, const float)); + MOCK_METHOD2(RenderString, ObjectWriter*(StringPiece, StringPiece)); + MOCK_METHOD2(RenderBytes, ObjectWriter*(StringPiece, StringPiece)); + MOCK_METHOD1(RenderNull, ObjectWriter*(StringPiece)); +}; + +class ExpectingObjectWriter : public ObjectWriter { + public: + explicit ExpectingObjectWriter(MockObjectWriter* mock) : mock_(mock) {} + + virtual ObjectWriter* StartObject(StringPiece name) { + (name.empty() + ? EXPECT_CALL(*mock_, StartObject(IsEmpty())) + : EXPECT_CALL(*mock_, StartObject(StrEq(name.ToString())))) + .WillOnce(Return(mock_)) + .RetiresOnSaturation(); + return this; + } + + virtual ObjectWriter* EndObject() { + EXPECT_CALL(*mock_, EndObject()) + .WillOnce(Return(mock_)) + .RetiresOnSaturation(); + return this; + } + + virtual ObjectWriter* StartList(StringPiece name) { + (name.empty() + ? EXPECT_CALL(*mock_, StartList(IsEmpty())) + : EXPECT_CALL(*mock_, StartList(StrEq(name.ToString())))) + .WillOnce(Return(mock_)) + .RetiresOnSaturation(); + return this; + } + + virtual ObjectWriter* EndList() { + EXPECT_CALL(*mock_, EndList()) + .WillOnce(Return(mock_)) + .RetiresOnSaturation(); + return this; + } + + virtual ObjectWriter* RenderBool(StringPiece name, const bool value) { + (name.empty() + ? EXPECT_CALL(*mock_, RenderBool(IsEmpty(), TypedEq(value))) + : EXPECT_CALL(*mock_, RenderBool(StrEq(name.ToString()), + TypedEq(value)))) + .WillOnce(Return(mock_)) + .RetiresOnSaturation(); + return this; + } + + virtual ObjectWriter* RenderInt32(StringPiece name, const int32 value) { + (name.empty() + ? EXPECT_CALL(*mock_, RenderInt32(IsEmpty(), TypedEq(value))) + : EXPECT_CALL(*mock_, RenderInt32(StrEq(name.ToString()), + TypedEq(value)))) + .WillOnce(Return(mock_)) + .RetiresOnSaturation(); + return this; + } + + virtual ObjectWriter* RenderUint32(StringPiece name, const uint32 value) { + (name.empty() + ? EXPECT_CALL(*mock_, RenderUint32(IsEmpty(), TypedEq(value))) + : EXPECT_CALL(*mock_, RenderUint32(StrEq(name.ToString()), + TypedEq(value)))) + .WillOnce(Return(mock_)) + .RetiresOnSaturation(); + return this; + } + + virtual ObjectWriter* RenderInt64(StringPiece name, const int64 value) { + (name.empty() + ? EXPECT_CALL(*mock_, RenderInt64(IsEmpty(), TypedEq(value))) + : EXPECT_CALL(*mock_, RenderInt64(StrEq(name.ToString()), + TypedEq(value)))) + .WillOnce(Return(mock_)) + .RetiresOnSaturation(); + return this; + } + + virtual ObjectWriter* RenderUint64(StringPiece name, const uint64 value) { + (name.empty() + ? EXPECT_CALL(*mock_, RenderUint64(IsEmpty(), TypedEq(value))) + : EXPECT_CALL(*mock_, RenderUint64(StrEq(name.ToString()), + TypedEq(value)))) + .WillOnce(Return(mock_)) + .RetiresOnSaturation(); + return this; + } + + virtual ObjectWriter* RenderDouble(StringPiece name, const double value) { + (name.empty() + ? EXPECT_CALL(*mock_, RenderDouble(IsEmpty(), + NanSensitiveDoubleEq(value))) + : EXPECT_CALL(*mock_, RenderDouble(StrEq(name.ToString()), + NanSensitiveDoubleEq(value)))) + .WillOnce(Return(mock_)) + .RetiresOnSaturation(); + return this; + } + + virtual ObjectWriter* RenderFloat(StringPiece name, const float value) { + (name.empty() + ? EXPECT_CALL(*mock_, RenderFloat(IsEmpty(), + NanSensitiveFloatEq(value))) + : EXPECT_CALL(*mock_, RenderFloat(StrEq(name.ToString()), + NanSensitiveFloatEq(value)))) + .WillOnce(Return(mock_)) + .RetiresOnSaturation(); + return this; + } + + virtual ObjectWriter* RenderString(StringPiece name, StringPiece value) { + (name.empty() + ? EXPECT_CALL(*mock_, RenderString(IsEmpty(), + TypedEq(value.ToString()))) + : EXPECT_CALL(*mock_, RenderString(StrEq(name.ToString()), + TypedEq(value.ToString())))) + .WillOnce(Return(mock_)) + .RetiresOnSaturation(); + return this; + } + virtual ObjectWriter* RenderBytes(StringPiece name, StringPiece value) { + (name.empty() + ? EXPECT_CALL(*mock_, RenderBytes(IsEmpty(), TypedEq( + value.ToString()))) + : EXPECT_CALL(*mock_, + RenderBytes(StrEq(name.ToString()), + TypedEq(value.ToString())))) + .WillOnce(Return(mock_)) + .RetiresOnSaturation(); + return this; + } + + virtual ObjectWriter* RenderNull(StringPiece name) { + (name.empty() ? EXPECT_CALL(*mock_, RenderNull(IsEmpty())) + : EXPECT_CALL(*mock_, RenderNull(StrEq(name.ToString()))) + .WillOnce(Return(mock_)) + .RetiresOnSaturation()); + return this; + } + + private: + MockObjectWriter* mock_; + + GOOGLE_DISALLOW_IMPLICIT_CONSTRUCTORS(ExpectingObjectWriter); +}; + +} // namespace converter +} // namespace util +} // namespace protobuf + +} // namespace google +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_EXPECTING_OBJECTWRITER_H__ diff --git a/src/google/protobuf/util/internal/field_mask_utility.cc b/src/google/protobuf/util/internal/field_mask_utility.cc new file mode 100644 index 00000000..92468959 --- /dev/null +++ b/src/google/protobuf/util/internal/field_mask_utility.cc @@ -0,0 +1,228 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include +#include + +namespace google { + +namespace protobuf { +namespace util { +namespace converter { + +namespace { +inline util::Status CallPathSink(PathSinkCallback path_sink, + StringPiece arg) { + return path_sink->Run(arg); +} + +util::Status CreatePublicError(util::error::Code code, + const string& message) { + return util::Status(code, message); +} + +// Appends a FieldMask path segment to a prefix. +string AppendPathSegmentToPrefix(StringPiece prefix, StringPiece segment) { + if (prefix.empty()) { + return segment.ToString(); + } + if (segment.empty()) { + return prefix.ToString(); + } + // If the segment is a map key, appends it to the prefix without the ".". + if (segment.starts_with("[\"")) { + return StrCat(prefix, segment); + } + return StrCat(prefix, ".", segment); +} + +} // namespace + +string ConvertFieldMaskPath(const StringPiece path, + ConverterCallback converter) { + string result; + result.reserve(path.size() << 1); + + bool is_quoted = false; + bool is_escaping = false; + int current_segment_start = 0; + + // Loops until 1 passed the end of the input to make handling the last + // segment easier. + for (size_t i = 0; i <= path.size(); ++i) { + // Outputs quoted string as-is. + if (is_quoted) { + if (i == path.size()) { + break; + } + result.push_back(path[i]); + if (is_escaping) { + is_escaping = false; + } else if (path[i] == '\\') { + is_escaping = true; + } else if (path[i] == '\"') { + current_segment_start = i + 1; + is_quoted = false; + } + continue; + } + if (i == path.size() || path[i] == '.' || path[i] == '(' || + path[i] == ')' || path[i] == '\"') { + result += converter( + path.substr(current_segment_start, i - current_segment_start)); + if (i < path.size()) { + result.push_back(path[i]); + } + current_segment_start = i + 1; + } + if (i < path.size() && path[i] == '\"') { + is_quoted = true; + } + } + return result; +} + +util::Status DecodeCompactFieldMaskPaths(StringPiece paths, + PathSinkCallback path_sink) { + stack prefix; + int length = paths.length(); + int previous_position = 0; + bool in_map_key = false; + bool is_escaping = false; + // Loops until 1 passed the end of the input to make the handle of the last + // segment easier. + for (int i = 0; i <= length; ++i) { + if (i != length) { + // Skips everything in a map key until we hit the end of it, which is + // marked by an un-escaped '"' immediately followed by a ']'. + if (in_map_key) { + if (is_escaping) { + is_escaping = false; + continue; + } + if (paths[i] == '\\') { + is_escaping = true; + continue; + } + if (paths[i] != '\"') { + continue; + } + // Un-escaped '"' must be followed with a ']'. + if (i >= length - 1 || paths[i + 1] != ']') { + return CreatePublicError( + util::error::INVALID_ARGUMENT, + StrCat("Invalid FieldMask '", paths, + "'. Map keys should be represented as [\"some_key\"].")); + } + // The end of the map key ("\"]") has been found. + in_map_key = false; + // Skips ']'. + i++; + // Checks whether the key ends at the end of a path segment. + if (i < length - 1 && paths[i + 1] != '.' && paths[i + 1] != ',' && + paths[i + 1] != ')' && paths[i + 1] != '(') { + return CreatePublicError( + util::error::INVALID_ARGUMENT, + StrCat("Invalid FieldMask '", paths, + "'. Map keys should be at the end of a path segment.")); + } + is_escaping = false; + continue; + } + + // We are not in a map key, look for the start of one. + if (paths[i] == '[') { + if (i >= length - 1 || paths[i + 1] != '\"') { + return CreatePublicError( + util::error::INVALID_ARGUMENT, + StrCat("Invalid FieldMask '", paths, + "'. Map keys should be represented as [\"some_key\"].")); + } + // "[\"" starts a map key. + in_map_key = true; + i++; // Skips the '\"'. + continue; + } + // If the current character is not a special character (',', '(' or ')'), + // continue to the next. + if (paths[i] != ',' && paths[i] != ')' && paths[i] != '(') { + continue; + } + } + // Gets the current segment - sub-string between previous position (after + // '(', ')', ',', or the beginning of the input) and the current position. + StringPiece segment = + paths.substr(previous_position, i - previous_position); + string current_prefix = prefix.empty() ? "" : prefix.top(); + + if (i < length && paths[i] == '(') { + // Builds a prefix and save it into the stack. + prefix.push(AppendPathSegmentToPrefix(current_prefix, segment)); + } else if (!segment.empty()) { + // When the current charactor is ')', ',' or the current position has + // passed the end of the input, builds and outputs a new paths by + // concatenating the last prefix with the current segment. + RETURN_IF_ERROR(CallPathSink( + path_sink, AppendPathSegmentToPrefix(current_prefix, segment))); + } + + // Removes the last prefix after seeing a ')'. + if (i < length && paths[i] == ')') { + if (prefix.empty()) { + return CreatePublicError( + util::error::INVALID_ARGUMENT, + StrCat("Invalid FieldMask '", paths, + "'. Cannot find matching '(' for all ')'.")); + } + prefix.pop(); + } + previous_position = i + 1; + } + if (in_map_key) { + return CreatePublicError( + util::error::INVALID_ARGUMENT, + StrCat("Invalid FieldMask '", paths, + "'. Cannot find matching ']' for all '['.")); + } + if (!prefix.empty()) { + return CreatePublicError( + util::error::INVALID_ARGUMENT, + StrCat("Invalid FieldMask '", paths, + "'. Cannot find matching ')' for all '('.")); + } + return util::Status::OK; +} + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/util/internal/field_mask_utility.h b/src/google/protobuf/util/internal/field_mask_utility.h new file mode 100644 index 00000000..59f36f75 --- /dev/null +++ b/src/google/protobuf/util/internal/field_mask_utility.h @@ -0,0 +1,72 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// FieldMask related utility methods. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_FIELD_MASK_UTILITY_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_FIELD_MASK_UTILITY_H__ + +#include +#include + +#include +#include +#include + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +typedef string (*ConverterCallback)(StringPiece); +typedef ResultCallback1* PathSinkCallback; + +// Applies a 'converter' to each segment of a FieldMask path and returns the +// result. Quoted strings in the 'path' are copied to the output as-is without +// converting their content. Escaping is supported within quoted strings. +// For example, "ab\"_c" will be returned as "ab\"_c" without any changes. +string ConvertFieldMaskPath(const StringPiece path, + ConverterCallback converter); + +// Decodes a compact list of FieldMasks. For example, "a.b,a.c.d,a.c.e" will be +// decoded into a list of field paths - "a.b", "a.c.d", "a.c.e". And the results +// will be sent to 'path_sink', i.e. 'path_sink' will be called once per +// resulting path. +// Note that we also support Apiary style FieldMask form. The above example in +// the Apiary style will look like "a.b,a.c(d,e)". +util::Status DecodeCompactFieldMaskPaths(StringPiece paths, + PathSinkCallback path_sink); + +} // namespace converter +} // namespace util +} // namespace protobuf + +} // namespace google +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_FIELD_MASK_UTILITY_H__ diff --git a/src/google/protobuf/util/internal/json_escaping.cc b/src/google/protobuf/util/internal/json_escaping.cc new file mode 100644 index 00000000..5ac23421 --- /dev/null +++ b/src/google/protobuf/util/internal/json_escaping.cc @@ -0,0 +1,403 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +namespace { + +// Array of hex characters for conversion to hex. +static const char kHex[] = "0123456789abcdef"; + +// Characters 0x00 to 0x9f are very commonly used, so we provide a special +// table lookup. +// +// For unicode code point ch < 0xa0: +// kCommonEscapes[ch] is the escaped string of ch, if escaping is needed; +// or an empty string, if escaping is not needed. +static const char kCommonEscapes[160][7] = { + // C0 (ASCII and derivatives) control characters + "\\u0000", "\\u0001", "\\u0002", "\\u0003", // 0x00 + "\\u0004", "\\u0005", "\\u0006", "\\u0007", + "\\b", "\\t", "\\n", "\\u000b", + "\\f", "\\r", "\\u000e", "\\u000f", + "\\u0010", "\\u0011", "\\u0012", "\\u0013", // 0x10 + "\\u0014", "\\u0015", "\\u0016", "\\u0017", + "\\u0018", "\\u0019", "\\u001a", "\\u001b", + "\\u001c", "\\u001d", "\\u001e", "\\u001f", + // Escaping of " and \ are required by www.json.org string definition. + // Escaping of < and > are required for HTML security. + "", "", "\\\"", "", "", "", "", "", // 0x20 + "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", // 0x30 + "", "", "", "", "\\u003c", "", "\\u003e", "", + "", "", "", "", "", "", "", "", // 0x40 + "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", // 0x50 + "", "", "", "", "\\\\", "", "", "", + "", "", "", "", "", "", "", "", // 0x60 + "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", // 0x70 + "", "", "", "", "", "", "", "\\u007f", + // C1 (ISO 8859 and Unicode) extended control characters + "\\u0080", "\\u0081", "\\u0082", "\\u0083", // 0x80 + "\\u0084", "\\u0085", "\\u0086", "\\u0087", + "\\u0088", "\\u0089", "\\u008a", "\\u008b", + "\\u008c", "\\u008d", "\\u008e", "\\u008f", + "\\u0090", "\\u0091", "\\u0092", "\\u0093", // 0x90 + "\\u0094", "\\u0095", "\\u0096", "\\u0097", + "\\u0098", "\\u0099", "\\u009a", "\\u009b", + "\\u009c", "\\u009d", "\\u009e", "\\u009f" +}; + +// Determines if the given char value is a unicode high-surrogate code unit. +// Such values do not represent characters by themselves, but are used in the +// representation of supplementary characters in the utf-16 encoding. +inline bool IsHighSurrogate(uint16 c) { + // Optimized form of: + // return c >= kMinHighSurrogate && c <= kMaxHighSurrogate; + // (Reduced from 3 ALU instructions to 2 ALU instructions) + return (c & ~(JsonEscaping::kMaxHighSurrogate - + JsonEscaping::kMinHighSurrogate)) + == JsonEscaping::kMinHighSurrogate; +} + +// Determines if the given char value is a unicode low-surrogate code unit. +// Such values do not represent characters by themselves, but are used in the +// representation of supplementary characters in the utf-16 encoding. +inline bool IsLowSurrogate(uint16 c) { + // Optimized form of: + // return c >= kMinLowSurrogate && c <= kMaxLowSurrogate; + // (Reduced from 3 ALU instructions to 2 ALU instructions) + return (c & ~(JsonEscaping::kMaxLowSurrogate - + JsonEscaping::kMinLowSurrogate)) + == JsonEscaping::kMinLowSurrogate; +} + +// Determines if the given char value is a unicode surrogate code unit (either +// high-surrogate or low-surrogate). +inline bool IsSurrogate(uint32 c) { + // Optimized form of: + // return c >= kMinHighSurrogate && c <= kMaxLowSurrogate; + // (Reduced from 3 ALU instructions to 2 ALU instructions) + return (c & 0xfffff800) == JsonEscaping::kMinHighSurrogate; +} + +// Returns true if the given unicode code point cp is +// in the supplementary character range. +inline bool IsSupplementalCodePoint(uint32 cp) { + // Optimized form of: + // return kMinSupplementaryCodePoint <= cp && cp <= kMaxCodePoint; + // (Reduced from 3 ALU instructions to 2 ALU instructions) + return (cp & ~(JsonEscaping::kMinSupplementaryCodePoint - 1)) + < JsonEscaping::kMaxCodePoint; +} + +// Returns true if the given unicode code point cp is a valid +// unicode code point (i.e. in the range 0 <= cp <= kMaxCodePoint). +inline bool IsValidCodePoint(uint32 cp) { + return cp <= JsonEscaping::kMaxCodePoint; +} + +// Converts the specified surrogate pair to its supplementary code point value. +// It is the callers' responsibility to validate the specified surrogate pair. +inline uint32 ToCodePoint(uint16 high, uint16 low) { + // Optimized form of: + // return ((high - kMinHighSurrogate) << 10) + // + (low - kMinLowSurrogate) + // + kMinSupplementaryCodePoint; + // (Reduced from 5 ALU instructions to 3 ALU instructions) + return (high << 10) + low + + (JsonEscaping::kMinSupplementaryCodePoint + - (static_cast(JsonEscaping::kMinHighSurrogate) << 10) + - JsonEscaping::kMinLowSurrogate); +} + +// Returns the low surrogate for the given unicode code point. The result is +// meaningless if the given code point is not a supplementary character. +inline uint16 ToLowSurrogate(uint32 cp) { + return (cp & (JsonEscaping::kMaxLowSurrogate + - JsonEscaping::kMinLowSurrogate)) + + JsonEscaping::kMinLowSurrogate; +} + +// Returns the high surrogate for the given unicode code point. The result is +// meaningless if the given code point is not a supplementary character. +inline uint16 ToHighSurrogate(uint32 cp) { + return (cp >> 10) + (JsonEscaping::kMinHighSurrogate - + (JsonEscaping::kMinSupplementaryCodePoint >> 10)); +} + +// Input str is encoded in UTF-8. A unicode code point could be encoded in +// UTF-8 using anywhere from 1 to 4 characters, and it could span multiple +// reads of the ByteSource. +// +// This function reads the next unicode code point from the input (str) at +// the given position (index), taking into account any left-over partial +// code point from the previous iteration (cp), together with the number +// of characters left to read to complete this code point (num_left). +// +// This function assumes that the input (str) is valid at the given position +// (index). In order words, at least one character could be read successfully. +// +// The code point read (partial or complete) is stored in (cp). Upon return, +// (num_left) stores the number of characters that has yet to be read in +// order to complete the current unicode code point. If the read is complete, +// then (num_left) is 0. Also, (num_read) is the number of characters read. +// +// Returns false if we encounter an invalid UTF-8 string. Returns true +// otherwise, including the case when we reach the end of the input (str) +// before a complete unicode code point is read. +bool ReadCodePoint(StringPiece str, int index, + uint32 *cp, int* num_left, int *num_read) { + if (*num_left == 0) { + // Last read was complete. Start reading a new unicode code point. + *cp = str[index++]; + *num_read = 1; + // The length of the code point is determined from reading the first byte. + // + // If the first byte is between: + // 0..0x7f: that's the value of the code point. + // 0x80..0xbf: + // 0xc0..0xdf: 11-bit code point encoded in 2 bytes. + // bit 10-6, bit 5-0 + // 0xe0..0xef: 16-bit code point encoded in 3 bytes. + // bit 15-12, bit 11-6, bit 5-0 + // 0xf0..0xf7: 21-bit code point encoded in 4 bytes. + // bit 20-18, bit 17-12, bit 11-6, bit 5-0 + // 0xf8..0xff: + // + // Meaning of each bit: + // bit 7: 0 - single byte code point: bits 6-0 are values. + // 1 - multibyte code point + // bit 6: 0 - subsequent bytes of multibyte code point: + // bits 5-0 are values. + // 1 - first byte of multibyte code point + // bit 5: 0 - first byte of 2-byte code point: bits 4-0 are values. + // 1 - first byte of code point with >= 3 bytes. + // bit 4: 0 - first byte of 3-byte code point: bits 3-0 are values. + // 1 - first byte of code point with >= 4 bytes. + // bit 3: 0 - first byte of 4-byte code point: bits 2-0 are values. + // 1 - reserved for future expansion. + if (*cp <= 0x7f) { + return true; + } else if (*cp <= 0xbf) { + return false; + } else if (*cp <= 0xdf) { + *cp &= 0x1f; + *num_left = 1; + } else if (*cp <= 0xef) { + *cp &= 0x0f; + *num_left = 2; + } else if (*cp <= 0xf7) { + *cp &= 0x07; + *num_left = 3; + } else { + return false; + } + } else { + // Last read was partial. Initialize num_read to 0 and continue reading + // the last unicode code point. + *num_read = 0; + } + while (*num_left > 0 && index < str.size()) { + uint32 ch = str[index++]; + --(*num_left); + ++(*num_read); + *cp = (*cp << 6) | (ch & 0x3f); + if (ch < 0x80 || ch > 0xbf) return false; + } + return *num_left > 0 || (!IsSurrogate(*cp) && IsValidCodePoint(*cp)); +} + +// Stores the 16-bit unicode code point as its hexadecimal digits in buffer +// and returns a StringPiece that points to this buffer. The input buffer needs +// to be at least 6 bytes long. +StringPiece ToHex(uint16 cp, char* buffer) { + buffer[5] = kHex[cp & 0x0f]; + cp >>= 4; + buffer[4] = kHex[cp & 0x0f]; + cp >>= 4; + buffer[3] = kHex[cp & 0x0f]; + cp >>= 4; + buffer[2] = kHex[cp & 0x0f]; + return StringPiece(buffer, 0, 6); +} + +// Stores the 32-bit unicode code point as its hexadecimal digits in buffer +// and returns a StringPiece that points to this buffer. The input buffer needs +// to be at least 12 bytes long. +StringPiece ToSurrogateHex(uint32 cp, char* buffer) { + uint16 low = ToLowSurrogate(cp); + uint16 high = ToHighSurrogate(cp); + + buffer[11] = kHex[low & 0x0f]; + low >>= 4; + buffer[10] = kHex[low & 0x0f]; + low >>= 4; + buffer[9] = kHex[low & 0x0f]; + low >>= 4; + buffer[8] = kHex[low & 0x0f]; + + buffer[5] = kHex[high & 0x0f]; + high >>= 4; + buffer[4] = kHex[high & 0x0f]; + high >>= 4; + buffer[3] = kHex[high & 0x0f]; + high >>= 4; + buffer[2] = kHex[high & 0x0f]; + + return StringPiece(buffer, 12); +} + +// If the given unicode code point needs escaping, then returns the +// escaped form. The returned StringPiece either points to statically +// pre-allocated char[] or to the given buffer. The input buffer needs +// to be at least 12 bytes long. +// +// If the given unicode code point does not need escaping, an empty +// StringPiece is returned. +StringPiece EscapeCodePoint(uint32 cp, char* buffer) { + if (cp < 0xa0) return kCommonEscapes[cp]; + switch (cp) { + // These are not required by json spec + // but used to prevent security bugs in javascript. + case 0xfeff: // Zero width no-break space + case 0xfff9: // Interlinear annotation anchor + case 0xfffa: // Interlinear annotation separator + case 0xfffb: // Interlinear annotation terminator + + case 0x00ad: // Soft-hyphen + case 0x06dd: // Arabic end of ayah + case 0x070f: // Syriac abbreviation mark + case 0x17b4: // Khmer vowel inherent Aq + case 0x17b5: // Khmer vowel inherent Aa + return ToHex(cp, buffer); + + default: + if ((cp >= 0x0600 && cp <= 0x0603) || // Arabic signs + (cp >= 0x200b && cp <= 0x200f) || // Zero width etc. + (cp >= 0x2028 && cp <= 0x202e) || // Separators etc. + (cp >= 0x2060 && cp <= 0x2064) || // Invisible etc. + (cp >= 0x206a && cp <= 0x206f)) { // Shaping etc. + return ToHex(cp, buffer); + } + + if (cp == 0x000e0001 || // Language tag + (cp >= 0x0001d173 && cp <= 0x0001d17a) || // Music formatting + (cp >= 0x000e0020 && cp <= 0x000e007f)) { // TAG symbols + return ToSurrogateHex(cp, buffer); + } + } + return StringPiece(); +} + +// Tries to escape the given code point first. If the given code point +// does not need to be escaped, but force_output is true, then render +// the given multi-byte code point in UTF8 in the buffer and returns it. +StringPiece EscapeCodePoint(uint32 cp, char* buffer, bool force_output) { + StringPiece sp = EscapeCodePoint(cp, buffer); + if (force_output && sp.empty()) { + buffer[5] = (cp & 0x3f) | 0x80; + cp >>= 6; + if (cp <= 0x1f) { + buffer[4] = cp | 0xc0; + sp.set(buffer + 4, 2); + return sp; + } + buffer[4] = (cp & 0x3f) | 0x80; + cp >>= 6; + if (cp <= 0x0f) { + buffer[3] = cp | 0xe0; + sp.set(buffer + 3, 3); + return sp; + } + buffer[3] = (cp & 0x3f) | 0x80; + buffer[2] = ((cp >> 6) & 0x07) | 0xf0; + sp.set(buffer + 2, 4); + } + return sp; +} + +} // namespace + +void JsonEscaping::Escape(strings::ByteSource* input, + strings::ByteSink* output) { + char buffer[12] = "\\udead\\ubee"; + uint32 cp = 0; // Current unicode code point. + int num_left = 0; // Num of chars to read to complete the code point. + while (input->Available() > 0) { + StringPiece str = input->Peek(); + StringPiece escaped; + int i = 0; + int num_read; + bool ok; + bool cp_was_split = num_left > 0; + // Loop until we encounter either + // i) a code point that needs to be escaped; or + // ii) a split code point is completely read; or + // iii) a character that is not a valid utf8; or + // iv) end of the StringPiece str is reached. + do { + ok = ReadCodePoint(str, i, &cp, &num_left, &num_read); + if (num_left > 0 || !ok) break; // case iii or iv + escaped = EscapeCodePoint(cp, buffer, cp_was_split); + if (!escaped.empty()) break; // case i or ii + i += num_read; + num_read = 0; + } while (i < str.length()); // case iv + // First copy the un-escaped prefix, if any, to the output ByteSink. + if (i > 0) input->CopyTo(output, i); + if (num_read > 0) input->Skip(num_read); + if (!ok) { + // Case iii: Report error. + // TODO(wpoon): Add error reporting. + num_left = 0; + } else if (num_left == 0 && !escaped.empty()) { + // Case i or ii: Append the escaped code point to the output ByteSink. + output->Append(escaped.data(), escaped.size()); + } + } + if (num_left > 0) { + // Treat as case iii: report error. + // TODO(wpoon): Add error reporting. + } +} + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/util/internal/json_escaping.h b/src/google/protobuf/util/internal/json_escaping.h new file mode 100644 index 00000000..e3e329fc --- /dev/null +++ b/src/google/protobuf/util/internal/json_escaping.h @@ -0,0 +1,91 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef NET_PROTO2_UTIL_CONVERTER_STRINGS_JSON_ESCAPING_H_ +#define NET_PROTO2_UTIL_CONVERTER_STRINGS_JSON_ESCAPING_H_ + +#include +#include + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +class JsonEscaping { + public: + // The minimum value of a unicode high-surrogate code unit in the utf-16 + // encoding. A high-surrogate is also known as a leading-surrogate. + // See http://www.unicode.org/glossary/#high_surrogate_code_unit + static const uint16 kMinHighSurrogate = 0xd800; + + // The maximum value of a unicide high-surrogate code unit in the utf-16 + // encoding. A high-surrogate is also known as a leading-surrogate. + // See http://www.unicode.org/glossary/#high_surrogate_code_unit + static const uint16 kMaxHighSurrogate = 0xdbff; + + // The minimum value of a unicode low-surrogate code unit in the utf-16 + // encoding. A low-surrogate is also known as a trailing-surrogate. + // See http://www.unicode.org/glossary/#low_surrogate_code_unit + static const uint16 kMinLowSurrogate = 0xdc00; + + // The maximum value of a unicode low-surrogate code unit in the utf-16 + // encoding. A low-surrogate is also known as a trailing surrogate. + // See http://www.unicode.org/glossary/#low_surrogate_code_unit + static const uint16 kMaxLowSurrogate = 0xdfff; + + // The minimum value of a unicode supplementary code point. + // See http://www.unicode.org/glossary/#supplementary_code_point + static const uint32 kMinSupplementaryCodePoint = 0x010000; + + // The minimum value of a unicode code point. + // See http://www.unicode.org/glossary/#code_point + static const uint32 kMinCodePoint = 0x000000; + + // The maximum value of a unicode code point. + // See http://www.unicode.org/glossary/#code_point + static const uint32 kMaxCodePoint = 0x10ffff; + + JsonEscaping() {} + virtual ~JsonEscaping() {} + + // Escape the given ByteSource to the given ByteSink. + static void Escape(strings::ByteSource* input, strings::ByteSink* output); + + private: + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(JsonEscaping); +}; + +} // namespace converter +} // namespace util +} // namespace protobuf + +#endif // NET_PROTO2_UTIL_CONVERTER_STRINGS_JSON_ESCAPING_H_ +} // namespace google diff --git a/src/google/protobuf/util/internal/json_objectwriter.cc b/src/google/protobuf/util/internal/json_objectwriter.cc new file mode 100644 index 00000000..d14ae10a --- /dev/null +++ b/src/google/protobuf/util/internal/json_objectwriter.cc @@ -0,0 +1,175 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include + +#include +#include +#include +#include +#include + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +using strings::ArrayByteSource; + +JsonObjectWriter::~JsonObjectWriter() { + if (!element_->is_root()) { + GOOGLE_LOG(WARNING) << "JsonObjectWriter was not fully closed."; + } +} + +JsonObjectWriter* JsonObjectWriter::StartObject(StringPiece name) { + WritePrefix(name); + WriteChar('{'); + Push(); + return this; +} + +JsonObjectWriter* JsonObjectWriter::EndObject() { + Pop(); + WriteChar('}'); + if (element()->is_root()) NewLine(); + return this; +} + +JsonObjectWriter* JsonObjectWriter::StartList(StringPiece name) { + WritePrefix(name); + WriteChar('['); + Push(); + return this; +} + +JsonObjectWriter* JsonObjectWriter::EndList() { + Pop(); + WriteChar(']'); + if (element()->is_root()) NewLine(); + return this; +} + +JsonObjectWriter* JsonObjectWriter::RenderBool(StringPiece name, + const bool value) { + return RenderSimple(name, value ? "true" : "false"); +} + +JsonObjectWriter* JsonObjectWriter::RenderInt32(StringPiece name, + const int32 value) { + return RenderSimple(name, SimpleItoa(value)); +} + +JsonObjectWriter* JsonObjectWriter::RenderUint32(StringPiece name, + const uint32 value) { + return RenderSimple(name, SimpleItoa(value)); +} + +JsonObjectWriter* JsonObjectWriter::RenderInt64(StringPiece name, + const int64 value) { + WritePrefix(name); + WriteChar('"'); + stream_->WriteString(SimpleItoa(value)); + WriteChar('"'); + return this; +} + +JsonObjectWriter* JsonObjectWriter::RenderUint64(StringPiece name, + const uint64 value) { + WritePrefix(name); + WriteChar('"'); + stream_->WriteString(SimpleItoa(value)); + WriteChar('"'); + return this; +} + +JsonObjectWriter* JsonObjectWriter::RenderDouble(StringPiece name, + const double value) { + if (isfinite(value)) return RenderSimple(name, SimpleDtoa(value)); + + // Render quoted with NaN/Infinity-aware DoubleAsString. + return RenderString(name, DoubleAsString(value)); +} + +JsonObjectWriter* JsonObjectWriter::RenderFloat(StringPiece name, + const float value) { + if (isfinite(value)) return RenderSimple(name, SimpleFtoa(value)); + + // Render quoted with NaN/Infinity-aware FloatAsString. + return RenderString(name, FloatAsString(value)); +} + +JsonObjectWriter* JsonObjectWriter::RenderString(StringPiece name, + StringPiece value) { + WritePrefix(name); + WriteChar('"'); + ArrayByteSource source(value); + JsonEscaping::Escape(&source, &sink_); + WriteChar('"'); + return this; +} + +JsonObjectWriter* JsonObjectWriter::RenderBytes(StringPiece name, + StringPiece value) { + WritePrefix(name); + string base64; + WebSafeBase64EscapeWithPadding(value, &base64); + WriteChar('"'); + // TODO(wpoon): Consider a ByteSink solution that writes the base64 bytes + // directly to the stream, rather than first putting them + // into a string and then writing them to the stream. + stream_->WriteRaw(base64.data(), base64.size()); + WriteChar('"'); + return this; +} + +JsonObjectWriter* JsonObjectWriter::RenderNull(StringPiece name) { + return RenderSimple(name, "null"); +} + +void JsonObjectWriter::WritePrefix(StringPiece name) { + bool not_first = !element()->is_first(); + if (not_first) WriteChar(','); + if (not_first || !element()->is_root()) NewLine(); + if (!name.empty()) { + WriteChar('"'); + ArrayByteSource source(name); + JsonEscaping::Escape(&source, &sink_); + stream_->WriteString("\":"); + if (!indent_string_.empty()) WriteChar(' '); + } +} + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/util/internal/json_objectwriter.h b/src/google/protobuf/util/internal/json_objectwriter.h new file mode 100644 index 00000000..761a0a10 --- /dev/null +++ b/src/google/protobuf/util/internal/json_objectwriter.h @@ -0,0 +1,206 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_JSON_OBJECTWRITER_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_JSON_OBJECTWRITER_H__ + +#include +#ifndef _SHARED_PTR_H +#include +#endif +#include + +#include +#include +#include + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +// An ObjectWriter implementation that outputs JSON. This ObjectWriter +// supports writing a compact form or a pretty printed form. +// +// Sample usage: +// string output; +// StringOutputStream* str_stream = new StringOutputStream(&output); +// CodedOutputStream* out_stream = new CodedOutputStream(str_stream); +// JsonObjectWriter* ow = new JsonObjectWriter(" ", out_stream); +// ow->StartObject("") +// ->RenderString("name", "value") +// ->RenderString("emptystring", string()) +// ->StartObject("nested") +// ->RenderInt64("light", 299792458); +// ->RenderDouble("pi", 3.141592653589793); +// ->EndObject() +// ->StartList("empty") +// ->EndList() +// ->EndObject(); +// +// And then the output string would become: +// { +// "name": "value", +// "emptystring": "", +// "nested": { +// "light": "299792458", +// "pi": 3.141592653589793 +// }, +// "empty": [] +// } +// +// JsonObjectWriter does not validate if calls actually result in valid JSON. +// For example, passing an empty name when one would be required won't result +// in an error, just an invalid output. +// +// Note that all int64 and uint64 are rendered as strings instead of numbers. +// This is because JavaScript parses numbers as 64-bit float thus int64 and +// uint64 would lose precision if rendered as numbers. +// +// JsonObjectWriter is thread-unsafe. +class LIBPROTOBUF_EXPORT JsonObjectWriter : public StructuredObjectWriter { + public: + JsonObjectWriter(StringPiece indent_string, + google::protobuf::io::CodedOutputStream* out) + : element_(new Element(NULL)), + stream_(out), sink_(out), + indent_string_(indent_string.ToString()) { + } + virtual ~JsonObjectWriter(); + + // ObjectWriter methods. + virtual JsonObjectWriter* StartObject(StringPiece name); + virtual JsonObjectWriter* EndObject(); + virtual JsonObjectWriter* StartList(StringPiece name); + virtual JsonObjectWriter* EndList(); + virtual JsonObjectWriter* RenderBool(StringPiece name, bool value); + virtual JsonObjectWriter* RenderInt32(StringPiece name, int32 value); + virtual JsonObjectWriter* RenderUint32(StringPiece name, uint32 value); + virtual JsonObjectWriter* RenderInt64(StringPiece name, int64 value); + virtual JsonObjectWriter* RenderUint64(StringPiece name, uint64 value); + virtual JsonObjectWriter* RenderDouble(StringPiece name, double value); + virtual JsonObjectWriter* RenderFloat(StringPiece name, float value); + virtual JsonObjectWriter* RenderString(StringPiece name, StringPiece value); + virtual JsonObjectWriter* RenderBytes(StringPiece name, StringPiece value); + virtual JsonObjectWriter* RenderNull(StringPiece name); + + protected: + class LIBPROTOBUF_EXPORT Element : public BaseElement { + public: + explicit Element(Element* parent) : BaseElement(parent), is_first_(true) {} + + // Called before each field of the Element is to be processed. + // Returns true if this is the first call (processing the first field). + bool is_first() { + if (is_first_) { + is_first_ = false; + return true; + } + return false; + } + + private: + bool is_first_; + + GOOGLE_DISALLOW_IMPLICIT_CONSTRUCTORS(Element); + }; + + virtual Element* element() { return element_.get(); } + + private: + class LIBPROTOBUF_EXPORT ByteSinkWrapper : public strings::ByteSink { + public: + explicit ByteSinkWrapper(google::protobuf::io::CodedOutputStream* stream) + : stream_(stream) {} + virtual ~ByteSinkWrapper() {} + + // ByteSink methods. + virtual void Append(const char* bytes, size_t n) { + stream_->WriteRaw(bytes, n); + } + + private: + google::protobuf::io::CodedOutputStream* stream_; + + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(ByteSinkWrapper); + }; + + // Renders a simple value as a string. By default all non-string Render + // methods convert their argument to a string and call this method. This + // method can then be used to render the simple value without escaping it. + JsonObjectWriter* RenderSimple(StringPiece name, const string& value) { + WritePrefix(name); + stream_->WriteString(value); + return this; + } + + // Pushes a new element to the stack. + void Push() { element_.reset(new Element(element_.release())); } + + // Pops an element off of the stack and deletes the popped element. + void Pop() { + bool needs_newline = !element_->is_first(); + element_.reset(element_->pop()); + if (needs_newline) NewLine(); + } + + // If pretty printing is enabled, this will write a newline to the output, + // followed by optional indentation. Otherwise this method is a noop. + void NewLine() { + if (!indent_string_.empty()) { + WriteChar('\n'); + for (int i = 0; i < element()->level(); i++) { + stream_->WriteString(indent_string_); + } + } + } + + // Writes a prefix. This will write out any pretty printing and + // commas that are required, followed by the name and a ':' if + // the name is not null. + void WritePrefix(StringPiece name); + + // Writes an individual character to the output. + void WriteChar(const char c) { stream_->WriteRaw(&c, sizeof(c)); } + + google::protobuf::scoped_ptr element_; + google::protobuf::io::CodedOutputStream* stream_; + ByteSinkWrapper sink_; + const string indent_string_; + + GOOGLE_DISALLOW_IMPLICIT_CONSTRUCTORS(JsonObjectWriter); +}; + +} // namespace converter +} // namespace util +} // namespace protobuf + +} // namespace google +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_JSON_OBJECTWRITER_H__ diff --git a/src/google/protobuf/util/internal/json_objectwriter_test.cc b/src/google/protobuf/util/internal/json_objectwriter_test.cc new file mode 100644 index 00000000..c8fff8b3 --- /dev/null +++ b/src/google/protobuf/util/internal/json_objectwriter_test.cc @@ -0,0 +1,284 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include +#include + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +using google::protobuf::io::CodedOutputStream; +using google::protobuf::io::StringOutputStream; + +class JsonObjectWriterTest : public ::testing::Test { + protected: + JsonObjectWriterTest() + : str_stream_(new StringOutputStream(&output_)), + out_stream_(new CodedOutputStream(str_stream_)), + ow_(NULL) {} + + virtual ~JsonObjectWriterTest() { + delete ow_; + delete out_stream_; + delete str_stream_; + } + + string output_; + StringOutputStream* const str_stream_; + CodedOutputStream* const out_stream_; + ObjectWriter* ow_; +}; + +TEST_F(JsonObjectWriterTest, EmptyRootObject) { + ow_ = new JsonObjectWriter("", out_stream_); + ow_->StartObject("")->EndObject(); + EXPECT_EQ("{}", output_.substr(0, out_stream_->ByteCount())); +} + +TEST_F(JsonObjectWriterTest, EmptyObject) { + ow_ = new JsonObjectWriter("", out_stream_); + ow_->StartObject("") + ->RenderString("test", "value") + ->StartObject("empty") + ->EndObject() + ->EndObject(); + EXPECT_EQ("{\"test\":\"value\",\"empty\":{}}", + output_.substr(0, out_stream_->ByteCount())); +} + +TEST_F(JsonObjectWriterTest, EmptyRootList) { + ow_ = new JsonObjectWriter("", out_stream_); + ow_->StartList("")->EndList(); + EXPECT_EQ("[]", output_.substr(0, out_stream_->ByteCount())); +} + +TEST_F(JsonObjectWriterTest, EmptyList) { + ow_ = new JsonObjectWriter("", out_stream_); + ow_->StartObject("") + ->RenderString("test", "value") + ->StartList("empty") + ->EndList() + ->EndObject(); + EXPECT_EQ("{\"test\":\"value\",\"empty\":[]}", + output_.substr(0, out_stream_->ByteCount())); +} + +TEST_F(JsonObjectWriterTest, ObjectInObject) { + ow_ = new JsonObjectWriter("", out_stream_); + ow_->StartObject("") + ->StartObject("nested") + ->RenderString("field", "value") + ->EndObject() + ->EndObject(); + EXPECT_EQ("{\"nested\":{\"field\":\"value\"}}", + output_.substr(0, out_stream_->ByteCount())); +} + +TEST_F(JsonObjectWriterTest, ListInObject) { + ow_ = new JsonObjectWriter("", out_stream_); + ow_->StartObject("") + ->StartList("nested") + ->RenderString("", "value") + ->EndList() + ->EndObject(); + EXPECT_EQ("{\"nested\":[\"value\"]}", + output_.substr(0, out_stream_->ByteCount())); +} + +TEST_F(JsonObjectWriterTest, ObjectInList) { + ow_ = new JsonObjectWriter("", out_stream_); + ow_->StartList("") + ->StartObject("") + ->RenderString("field", "value") + ->EndObject() + ->EndList(); + EXPECT_EQ("[{\"field\":\"value\"}]", + output_.substr(0, out_stream_->ByteCount())); +} + +TEST_F(JsonObjectWriterTest, ListInList) { + ow_ = new JsonObjectWriter("", out_stream_); + ow_->StartList("") + ->StartList("") + ->RenderString("", "value") + ->EndList() + ->EndList(); + EXPECT_EQ("[[\"value\"]]", output_.substr(0, out_stream_->ByteCount())); +} + +TEST_F(JsonObjectWriterTest, RenderPrimitives) { + ow_ = new JsonObjectWriter("", out_stream_); + ow_->StartObject("") + ->RenderBool("bool", true) + ->RenderDouble("double", std::numeric_limits::max()) + ->RenderFloat("float", std::numeric_limits::max()) + ->RenderInt32("int", std::numeric_limits::min()) + ->RenderInt64("long", std::numeric_limits::min()) + ->RenderBytes("bytes", "abracadabra") + ->RenderString("string", "string") + ->RenderBytes("emptybytes", "") + ->RenderString("emptystring", string()) + ->EndObject(); + EXPECT_EQ( + "{\"bool\":true," + "\"double\":1.7976931348623157e+308," + "\"float\":3.4028235e+38," + "\"int\":-2147483648," + "\"long\":\"-9223372036854775808\"," + "\"bytes\":\"YWJyYWNhZGFicmE=\"," + "\"string\":\"string\"," + "\"emptybytes\":\"\"," + "\"emptystring\":\"\"}", + output_.substr(0, out_stream_->ByteCount())); +} + +TEST_F(JsonObjectWriterTest, BytesEncodesAsWebSafeBase64) { + string s; + s.push_back('\377'); + s.push_back('\357'); + ow_ = new JsonObjectWriter("", out_stream_); + ow_->StartObject("")->RenderBytes("bytes", s)->EndObject(); + // Non-web-safe would encode this as "/+8=" + EXPECT_EQ("{\"bytes\":\"_-8=\"}", + output_.substr(0, out_stream_->ByteCount())); +} + +TEST_F(JsonObjectWriterTest, PrettyPrintList) { + ow_ = new JsonObjectWriter(" ", out_stream_); + ow_->StartObject("") + ->StartList("items") + ->RenderString("", "item1") + ->RenderString("", "item2") + ->RenderString("", "item3") + ->EndList() + ->StartList("empty") + ->EndList() + ->EndObject(); + EXPECT_EQ( + "{\n" + " \"items\": [\n" + " \"item1\",\n" + " \"item2\",\n" + " \"item3\"\n" + " ],\n" + " \"empty\": []\n" + "}\n", + output_.substr(0, out_stream_->ByteCount())); +} + +TEST_F(JsonObjectWriterTest, PrettyPrintObject) { + ow_ = new JsonObjectWriter(" ", out_stream_); + ow_->StartObject("") + ->StartObject("items") + ->RenderString("key1", "item1") + ->RenderString("key2", "item2") + ->RenderString("key3", "item3") + ->EndObject() + ->StartObject("empty") + ->EndObject() + ->EndObject(); + EXPECT_EQ( + "{\n" + " \"items\": {\n" + " \"key1\": \"item1\",\n" + " \"key2\": \"item2\",\n" + " \"key3\": \"item3\"\n" + " },\n" + " \"empty\": {}\n" + "}\n", + output_.substr(0, out_stream_->ByteCount())); +} + +TEST_F(JsonObjectWriterTest, PrettyPrintEmptyObjectInEmptyList) { + ow_ = new JsonObjectWriter(" ", out_stream_); + ow_->StartObject("") + ->StartList("list") + ->StartObject("") + ->EndObject() + ->EndList() + ->EndObject(); + EXPECT_EQ( + "{\n" + " \"list\": [\n" + " {}\n" + " ]\n" + "}\n", + output_.substr(0, out_stream_->ByteCount())); +} + +TEST_F(JsonObjectWriterTest, PrettyPrintDoubleIndent) { + ow_ = new JsonObjectWriter(" ", out_stream_); + ow_->StartObject("") + ->RenderBool("bool", true) + ->RenderInt32("int", 42) + ->EndObject(); + EXPECT_EQ( + "{\n" + " \"bool\": true,\n" + " \"int\": 42\n" + "}\n", + output_.substr(0, out_stream_->ByteCount())); +} + +TEST_F(JsonObjectWriterTest, StringsEscapedAndEnclosedInDoubleQuotes) { + ow_ = new JsonObjectWriter("", out_stream_); + ow_->StartObject("")->RenderString("string", "'<>&\\\"\r\n")->EndObject(); + EXPECT_EQ("{\"string\":\"'\\u003c\\u003e&\\\\\\\"\\r\\n\"}", + output_.substr(0, out_stream_->ByteCount())); +} + +TEST_F(JsonObjectWriterTest, Stringification) { + ow_ = new JsonObjectWriter("", out_stream_); + ow_->StartObject("") + ->RenderDouble("double_nan", std::numeric_limits::quiet_NaN()) + ->RenderFloat("float_nan", std::numeric_limits::quiet_NaN()) + ->RenderDouble("double_pos", std::numeric_limits::infinity()) + ->RenderFloat("float_pos", std::numeric_limits::infinity()) + ->RenderDouble("double_neg", -std::numeric_limits::infinity()) + ->RenderFloat("float_neg", -std::numeric_limits::infinity()) + ->EndObject(); + EXPECT_EQ( + "{\"double_nan\":\"NaN\"," + "\"float_nan\":\"NaN\"," + "\"double_pos\":\"Infinity\"," + "\"float_pos\":\"Infinity\"," + "\"double_neg\":\"-Infinity\"," + "\"float_neg\":\"-Infinity\"}", + output_.substr(0, out_stream_->ByteCount())); +} + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/util/internal/json_stream_parser.cc b/src/google/protobuf/util/internal/json_stream_parser.cc new file mode 100644 index 00000000..d439a221 --- /dev/null +++ b/src/google/protobuf/util/internal/json_stream_parser.cc @@ -0,0 +1,740 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include +#include +#include +#include +#include +#include +#ifndef _SHARED_PTR_H +#include +#endif + +#include +#include +#include + +namespace google { +namespace protobuf { +namespace util { + +// Allow these symbols to be referenced as util::Status, util::error::* in +// this file. +using util::Status; +namespace error { +using util::error::INTERNAL; +using util::error::INVALID_ARGUMENT; +} // namespace error + +namespace converter { + +// Number of digits in a unicode escape sequence (/uXXXX) +static const int kUnicodeEscapedLength = 6; + +// Length of the true, false, and null literals. +static const int true_len = strlen("true"); +static const int false_len = strlen("false"); +static const int null_len = strlen("null"); + +inline bool IsLetter(char c) { + return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || (c == '_') || + (c == '$'); +} + +inline bool IsAlphanumeric(char c) { + return IsLetter(c) || ('0' <= c && c <= '9'); +} + +static bool ConsumeKey(StringPiece* input, StringPiece* key) { + if (input->empty() || !IsLetter((*input)[0])) return false; + int len = 1; + for (; len < input->size(); ++len) { + if (!IsAlphanumeric((*input)[len])) { + break; + } + } + *key = StringPiece(input->data(), len); + *input = StringPiece(input->data() + len, input->size() - len); + return true; +} + +static bool MatchKey(StringPiece input) { + return !input.empty() && IsLetter(input[0]); +} + +JsonStreamParser::JsonStreamParser(ObjectWriter* ow) + : ow_(ow), + stack_(), + leftover_(), + json_(), + p_(), + key_(), + key_storage_(), + finishing_(false), + parsed_(), + parsed_storage_(), + string_open_(0), + utf8_storage_(), + utf8_length_(0) { + // Initialize the stack with a single value to be parsed. + stack_.push(VALUE); +} + +JsonStreamParser::~JsonStreamParser() {} + +util::Status JsonStreamParser::Parse(StringPiece json) { + return ParseChunk(json); +} + +util::Status JsonStreamParser::FinishParse() { + // If we do not expect anything and there is nothing left to parse we're all + // done. + if (stack_.empty() && leftover_.empty()) { + return util::Status::OK; + } + // Parse the remainder in finishing mode, which reports errors for things like + // unterminated strings or unknown tokens that would normally be retried. + p_ = json_ = StringPiece(leftover_); + finishing_ = true; + util::Status result = RunParser(); + if (result.ok()) { + SkipWhitespace(); + if (!p_.empty()) { + result = ReportFailure("Parsing terminated before end of input."); + } + } + return result; +} + +util::Status JsonStreamParser::ParseChunk(StringPiece chunk) { + // If we have leftovers from a previous chunk, append the new chunk to it and + // create a new StringPiece pointing at the string's data. This could be + // large but we rely on the chunks to be small, assuming they are fragments + // of a Cord. + if (!leftover_.empty()) { + chunk.AppendToString(&leftover_); + p_ = json_ = StringPiece(leftover_); + } else { + p_ = json_ = chunk; + } + + finishing_ = false; + util::Status result = RunParser(); + if (!result.ok()) return result; + + SkipWhitespace(); + if (p_.empty()) { + // If we parsed everything we had, clear the leftover. + leftover_.clear(); + } else { + // If we do not expect anything i.e. stack is empty, and we have non-empty + // string left to parse, we report an error. + if (stack_.empty()) { + return ReportFailure("Parsing terminated before end of input."); + } + // If we expect future data i.e. stack is non-empty, and we have some + // unparsed data left, we save it for later parse. + leftover_ = p_.ToString(); + } + return util::Status::OK; +} + +util::Status JsonStreamParser::RunParser() { + while (!stack_.empty()) { + ParseType type = stack_.top(); + TokenType t = (string_open_ == 0) ? GetNextTokenType() : BEGIN_STRING; + stack_.pop(); + util::Status result; + switch (type) { + case VALUE: + result = ParseValue(t); + break; + + case OBJ_MID: + result = ParseObjectMid(t); + break; + + case ENTRY: + result = ParseEntry(t); + break; + + case ENTRY_MID: + result = ParseEntryMid(t); + break; + + case ARRAY_VALUE: + result = ParseArrayValue(t); + break; + + case ARRAY_MID: + result = ParseArrayMid(t); + break; + + default: + result = util::Status(util::error::INTERNAL, + StrCat("Unknown parse type: ", type)); + break; + } + if (!result.ok()) { + // If we were cancelled, save our state and try again later. + if (!finishing_ && result == util::Status::CANCELLED) { + stack_.push(type); + // If we have a key we still need to render, make sure to save off the + // contents in our own storage. + if (!key_.empty() && key_storage_.empty()) { + key_.AppendToString(&key_storage_); + key_ = StringPiece(key_storage_); + } + result = util::Status::OK; + } + return result; + } + } + return util::Status::OK; +} + +util::Status JsonStreamParser::ParseValue(TokenType type) { + switch (type) { + case BEGIN_OBJECT: + return HandleBeginObject(); + case BEGIN_ARRAY: + return HandleBeginArray(); + case BEGIN_STRING: + return ParseString(); + case BEGIN_NUMBER: + return ParseNumber(); + case BEGIN_TRUE: + return ParseTrue(); + case BEGIN_FALSE: + return ParseFalse(); + case BEGIN_NULL: + return ParseNull(); + case UNKNOWN: + return ReportUnknown("Expected a value."); + default: { + // Special case for having been cut off while parsing, wait for more data. + // This handles things like 'fals' being at the end of the string, we + // don't know if the next char would be e, completing it, or something + // else, making it invalid. + if (!finishing_ && p_.length() < false_len) { + return util::Status::CANCELLED; + } + return ReportFailure("Unexpected token."); + } + } +} + +util::Status JsonStreamParser::ParseString() { + util::Status result = ParseStringHelper(); + if (result.ok()) { + ow_->RenderString(key_, parsed_); + key_.clear(); + parsed_.clear(); + parsed_storage_.clear(); + } + return result; +} + +util::Status JsonStreamParser::ParseStringHelper() { + // If we haven't seen the start quote, grab it and remember it for later. + if (string_open_ == 0) { + string_open_ = *p_.data(); + GOOGLE_DCHECK(string_open_ == '\"' || string_open_ == '\''); + Advance(); + } + // Track where we last copied data from so we can minimize copying. + const char* last = p_.data(); + while (!p_.empty()) { + const char* data = p_.data(); + if (*data == '\\') { + // We're about to handle an escape, copy all bytes from last to data. + if (last < data) { + parsed_storage_.append(last, data - last); + last = data; + } + // If we ran out of string after the \, cancel or report an error + // depending on if we expect more data later. + if (p_.length() == 1) { + if (!finishing_) { + return util::Status::CANCELLED; + } + return ReportFailure("Closing quote expected in string."); + } + // Parse a unicode escape if we found \u in the string. + if (data[1] == 'u') { + util::Status result = ParseUnicodeEscape(); + if (!result.ok()) { + return result; + } + // Move last pointer past the unicode escape and continue. + last = p_.data(); + continue; + } + // Handle the standard set of backslash-escaped characters. + switch (data[1]) { + case 'b': + parsed_storage_.push_back('\b'); + break; + case 'f': + parsed_storage_.push_back('\f'); + break; + case 'n': + parsed_storage_.push_back('\n'); + break; + case 'r': + parsed_storage_.push_back('\r'); + break; + case 't': + parsed_storage_.push_back('\t'); + break; + case 'v': + parsed_storage_.push_back('\v'); + break; + default: + parsed_storage_.push_back(data[1]); + } + // We handled two characters, so advance past them and continue. + p_.remove_prefix(2); + last = p_.data(); + continue; + } + // If we found the closing quote note it, advance past it, and return. + if (*data == string_open_) { + // If we didn't copy anything, reuse the input buffer. + if (parsed_storage_.empty()) { + parsed_ = StringPiece(last, data - last); + } else { + if (last < data) { + parsed_storage_.append(last, data - last); + last = data; + } + parsed_ = StringPiece(parsed_storage_); + } + // Clear the quote char so next time we try to parse a string we'll + // start fresh. + string_open_ = 0; + Advance(); + return util::Status::OK; + } + // Normal character, just advance past it. + Advance(); + } + // If we ran out of characters, copy over what we have so far. + if (last < p_.data()) { + parsed_storage_.append(last, p_.data() - last); + } + // If we didn't find the closing quote but we expect more data, cancel for now + if (!finishing_) { + return util::Status::CANCELLED; + } + // End of string reached without a closing quote, report an error. + string_open_ = 0; + return ReportFailure("Closing quote expected in string."); +} + +// Converts a unicode escaped character to a decimal value stored in a char32 +// for use in UTF8 encoding utility. We assume that str begins with \uhhhh and +// convert that from the hex number to a decimal value. +// +// There are some security exploits with UTF-8 that we should be careful of: +// - http://www.unicode.org/reports/tr36/#UTF-8_Exploit +// - http://sites/intl-eng/design-guide/core-application +util::Status JsonStreamParser::ParseUnicodeEscape() { + if (p_.length() < kUnicodeEscapedLength) { + if (!finishing_) { + return util::Status::CANCELLED; + } + return ReportFailure("Illegal hex string."); + } + GOOGLE_DCHECK_EQ('\\', p_.data()[0]); + GOOGLE_DCHECK_EQ('u', p_.data()[1]); + uint32 code = 0; + for (int i = 2; i < kUnicodeEscapedLength; ++i) { + if (!isxdigit(p_.data()[i])) { + return ReportFailure("Invalid escape sequence."); + } + code = (code << 4) + hex_digit_to_int(p_.data()[i]); + } + char buf[UTFmax]; + int len = EncodeAsUTF8Char(code, buf); + // Advance past the unicode escape. + p_.remove_prefix(kUnicodeEscapedLength); + parsed_storage_.append(buf, len); + return util::Status::OK; +} + +util::Status JsonStreamParser::ParseNumber() { + NumberResult number; + util::Status result = ParseNumberHelper(&number); + if (result.ok()) { + switch (number.type) { + case NumberResult::DOUBLE: + ow_->RenderDouble(key_, number.double_val); + key_.clear(); + break; + + case NumberResult::INT: + ow_->RenderInt64(key_, number.int_val); + key_.clear(); + break; + + case NumberResult::UINT: + ow_->RenderUint64(key_, number.uint_val); + key_.clear(); + break; + + default: + return ReportFailure("Unable to parse number."); + } + } + return result; +} + +util::Status JsonStreamParser::ParseNumberHelper(NumberResult* result) { + const char* data = p_.data(); + int length = p_.length(); + + // Look for the first non-numeric character, or the end of the string. + int index = 0; + bool floating = false; + bool negative = data[index] == '-'; + // Find the first character that cannot be part of the number. Along the way + // detect if the number needs to be parsed as a double. + // Note that this restricts numbers to the JSON specification, so for example + // we do not support hex or octal notations. + for (; index < length; ++index) { + char c = data[index]; + if (isdigit(c)) continue; + if (c == '.' || c == 'e' || c == 'E') { + floating = true; + continue; + } + if (c == '+' || c == '-') continue; + // Not a valid number character, break out. + break; + } + + // If the entire input is a valid number, and we may have more content in the + // future, we abort for now and resume when we know more. + if (index == length && !finishing_) { + return util::Status::CANCELLED; + } + + // Create a string containing just the number, so we can use safe_strtoX + string number = p_.substr(0, index).ToString(); + + // Floating point number, parse as a double. + if (floating) { + if (!safe_strtod(number, &result->double_val)) { + return ReportFailure("Unable to parse number."); + } + result->type = NumberResult::DOUBLE; + p_.remove_prefix(index); + return util::Status::OK; + } + + // Positive non-floating point number, parse as a uint64. + if (!negative) { + if (!safe_strtou64(number, &result->uint_val)) { + return ReportFailure("Unable to parse number."); + } + result->type = NumberResult::UINT; + p_.remove_prefix(index); + return util::Status::OK; + } + + // Negative non-floating point number, parse as an int64. + if (!safe_strto64(number, &result->int_val)) { + return ReportFailure("Unable to parse number."); + } + result->type = NumberResult::INT; + p_.remove_prefix(index); + return util::Status::OK; +} + +util::Status JsonStreamParser::HandleBeginObject() { + GOOGLE_DCHECK_EQ('{', *p_.data()); + Advance(); + ow_->StartObject(key_); + key_.clear(); + stack_.push(ENTRY); + return util::Status::OK; +} + +util::Status JsonStreamParser::ParseObjectMid(TokenType type) { + if (type == UNKNOWN) { + return ReportUnknown("Expected , or } after key:value pair."); + } + + // Object is complete, advance past the comma and render the EndObject. + if (type == END_OBJECT) { + Advance(); + ow_->EndObject(); + return util::Status::OK; + } + // Found a comma, advance past it and get ready for an entry. + if (type == VALUE_SEPARATOR) { + Advance(); + stack_.push(ENTRY); + return util::Status::OK; + } + // Illegal token after key:value pair. + return ReportFailure("Expected , or } after key:value pair."); +} + +util::Status JsonStreamParser::ParseEntry(TokenType type) { + if (type == UNKNOWN) { + return ReportUnknown("Expected an object key or }."); + } + + // Close the object and return. This allows for trailing commas. + if (type == END_OBJECT) { + ow_->EndObject(); + Advance(); + return util::Status::OK; + } + + util::Status result; + if (type == BEGIN_STRING) { + // Key is a string (standard JSON), parse it and store the string. + result = ParseStringHelper(); + if (result.ok()) { + key_storage_.clear(); + if (!parsed_storage_.empty()) { + parsed_storage_.swap(key_storage_); + key_ = StringPiece(key_storage_); + } else { + key_ = parsed_; + } + parsed_.clear(); + } + } else if (type == BEGIN_KEY) { + // Key is a bare key (back compat), create a StringPiece pointing to it. + result = ParseKey(); + } else { + // Unknown key type, report an error. + result = ReportFailure("Expected an object key or }."); + } + // On success we next expect an entry mid ':' then an object mid ',' or '}' + if (result.ok()) { + stack_.push(OBJ_MID); + stack_.push(ENTRY_MID); + } + return result; +} + +util::Status JsonStreamParser::ParseEntryMid(TokenType type) { + if (type == UNKNOWN) { + return ReportUnknown("Expected : between key:value pair."); + } + if (type == ENTRY_SEPARATOR) { + Advance(); + stack_.push(VALUE); + return util::Status::OK; + } + return ReportFailure("Expected : between key:value pair."); +} + +util::Status JsonStreamParser::HandleBeginArray() { + GOOGLE_DCHECK_EQ('[', *p_.data()); + Advance(); + ow_->StartList(key_); + key_.clear(); + stack_.push(ARRAY_VALUE); + return util::Status::OK; +} + +util::Status JsonStreamParser::ParseArrayValue(TokenType type) { + if (type == UNKNOWN) { + return ReportUnknown("Expected a value or ] within an array."); + } + + if (type == END_ARRAY) { + ow_->EndList(); + Advance(); + return util::Status::OK; + } + + // The ParseValue call may push something onto the stack so we need to make + // sure an ARRAY_MID is after it, so we push it on now. + stack_.push(ARRAY_MID); + util::Status result = ParseValue(type); + if (result == util::Status::CANCELLED) { + // If we were cancelled, pop back off the ARRAY_MID so we don't try to + // push it on again when we try over. + stack_.pop(); + } + return result; +} + +util::Status JsonStreamParser::ParseArrayMid(TokenType type) { + if (type == UNKNOWN) { + return ReportUnknown("Expected , or ] after array value."); + } + + if (type == END_ARRAY) { + ow_->EndList(); + Advance(); + return util::Status::OK; + } + + // Found a comma, advance past it and expect an array value next. + if (type == VALUE_SEPARATOR) { + Advance(); + stack_.push(ARRAY_VALUE); + return util::Status::OK; + } + // Illegal token after array value. + return ReportFailure("Expected , or ] after array value."); +} + +util::Status JsonStreamParser::ParseTrue() { + ow_->RenderBool(key_, true); + key_.clear(); + p_.remove_prefix(true_len); + return util::Status::OK; +} + +util::Status JsonStreamParser::ParseFalse() { + ow_->RenderBool(key_, false); + key_.clear(); + p_.remove_prefix(false_len); + return util::Status::OK; +} + +util::Status JsonStreamParser::ParseNull() { + ow_->RenderNull(key_); + key_.clear(); + p_.remove_prefix(null_len); + return util::Status::OK; +} + +util::Status JsonStreamParser::ReportFailure(StringPiece message) { + static const int kContextLength = 20; + const char* p_start = p_.data(); + const char* json_start = json_.data(); + const char* begin = max(p_start - kContextLength, json_start); + const char* end = min(p_start + kContextLength, json_start + json_.size()); + StringPiece segment(begin, end - begin); + string location(p_start - begin, ' '); + location.push_back('^'); + return util::Status(util::error::INVALID_ARGUMENT, + StrCat(message, "\n", segment, "\n", location)); +} + +util::Status JsonStreamParser::ReportUnknown(StringPiece message) { + // If we aren't finishing the parse, cancel parsing and try later. + if (!finishing_) { + return util::Status::CANCELLED; + } + if (p_.empty()) { + return ReportFailure(StrCat("Unexpected end of string. ", message)); + } + return ReportFailure(message); +} + +void JsonStreamParser::SkipWhitespace() { + while (!p_.empty() && ascii_isspace(*p_.data())) { + Advance(); + } +} + +void JsonStreamParser::Advance() { + // Advance by moving one UTF8 character while making sure we don't go beyond + // the length of StringPiece. + p_.remove_prefix( + min(p_.length(), UTF8FirstLetterNumBytes(p_.data(), p_.length()))); +} + +util::Status JsonStreamParser::ParseKey() { + StringPiece original = p_; + if (!ConsumeKey(&p_, &key_)) { + return ReportFailure("Invalid key or variable name."); + } + // If we consumed everything but expect more data, reset p_ and cancel since + // we can't know if the key was complete or not. + if (!finishing_ && p_.empty()) { + p_ = original; + return util::Status::CANCELLED; + } + // Since we aren't using the key storage, clear it out. + key_storage_.clear(); + return util::Status::OK; +} + +JsonStreamParser::TokenType JsonStreamParser::GetNextTokenType() { + SkipWhitespace(); + + int size = p_.size(); + if (size == 0) { + // If we ran out of data, report unknown and we'll place the previous parse + // type onto the stack and try again when we have more data. + return UNKNOWN; + } + // TODO(sven): Split this method based on context since different contexts + // support different tokens. Would slightly speed up processing? + const char* data = p_.data(); + if (*data == '\"' || *data == '\'') return BEGIN_STRING; + if (*data == '-' || ('0' <= *data && *data <= '9')) { + return BEGIN_NUMBER; + } + if (size >= true_len && !strncmp(data, "true", true_len)) { + return BEGIN_TRUE; + } + if (size >= false_len && !strncmp(data, "false", false_len)) { + return BEGIN_FALSE; + } + if (size >= null_len && !strncmp(data, "null", null_len)) { + return BEGIN_NULL; + } + if (*data == '{') return BEGIN_OBJECT; + if (*data == '}') return END_OBJECT; + if (*data == '[') return BEGIN_ARRAY; + if (*data == ']') return END_ARRAY; + if (*data == ':') return ENTRY_SEPARATOR; + if (*data == ',') return VALUE_SEPARATOR; + if (MatchKey(p_)) { + return BEGIN_KEY; + } + + // We don't know that we necessarily have an invalid token here, just that we + // can't parse what we have so far. So we don't report an error and just + // return UNKNOWN so we can try again later when we have more data, or if we + // finish and we have leftovers. + return UNKNOWN; +} + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/util/internal/json_stream_parser.h b/src/google/protobuf/util/internal/json_stream_parser.h new file mode 100644 index 00000000..17b094ae --- /dev/null +++ b/src/google/protobuf/util/internal/json_stream_parser.h @@ -0,0 +1,256 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_JSON_STREAM_PARSER_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_JSON_STREAM_PARSER_H__ + +#include +#include + +#include +#include +#include + +namespace google { +namespace util { +class Status; +} // namespace util + +namespace protobuf { +namespace util { +namespace converter { + +class ObjectWriter; + +// A JSON parser that can parse a stream of JSON chunks rather than needing the +// entire JSON string up front. It is a modified version of the parser in +// //net/proto/json/json-parser.h that has been changed in the following ways: +// - Changed from recursion to an explicit stack to allow resumption +// - Added support for int64 and uint64 numbers +// - Removed support for octal and decimal escapes +// - Removed support for numeric keys +// - Removed support for functions (javascript) +// - Removed some lax-comma support (but kept trailing comma support) +// - Writes directly to an ObjectWriter rather than using subclassing +// +// Here is an example usage: +// JsonStreamParser parser(ow_.get()); +// util::Status result = parser.Parse(chunk1); +// result.Update(parser.Parse(chunk2)); +// result.Update(parser.FinishParse()); +// GOOGLE_DCHECK(result.ok()) << "Failed to parse JSON"; +// +// This parser is thread-compatible as long as only one thread is calling a +// Parse() method at a time. +class LIBPROTOBUF_EXPORT JsonStreamParser { + public: + // Creates a JsonStreamParser that will write to the given ObjectWriter. + explicit JsonStreamParser(ObjectWriter* ow); + virtual ~JsonStreamParser(); + + // Parse a JSON string (UTF-8 encoded). + util::Status Parse(StringPiece json); + + // Finish parsing the JSON string. + util::Status FinishParse(); + + private: + enum TokenType { + BEGIN_STRING, // " or ' + BEGIN_NUMBER, // - or digit + BEGIN_TRUE, // true + BEGIN_FALSE, // false + BEGIN_NULL, // null + BEGIN_OBJECT, // { + END_OBJECT, // } + BEGIN_ARRAY, // [ + END_ARRAY, // ] + ENTRY_SEPARATOR, // : + VALUE_SEPARATOR, // , + BEGIN_KEY, // letter, _, $ or digit. Must begin with non-digit + UNKNOWN // Unknown token or we ran out of the stream. + }; + + enum ParseType { + VALUE, // Expects a {, [, true, false, null, string or number + OBJ_MID, // Expects a ',' or } + ENTRY, // Expects a key or } + ENTRY_MID, // Expects a : + ARRAY_VALUE, // Expects a value or ] + ARRAY_MID // Expects a ',' or ] + }; + + // Holds the result of parsing a number + struct NumberResult { + enum Type { DOUBLE, INT, UINT }; + Type type; + union { + double double_val; + int64 int_val; + uint64 uint_val; + }; + }; + + // Parses a single chunk of JSON, returning an error if the JSON was invalid. + util::Status ParseChunk(StringPiece json); + + // Runs the parser based on stack_ and p_, until the stack is empty or p_ runs + // out of data. If we unexpectedly run out of p_ we push the latest back onto + // the stack and return. + util::Status RunParser(); + + // Parses a value from p_ and writes it to ow_. + // A value may be an object, array, true, false, null, string or number. + util::Status ParseValue(TokenType type); + + // Parses a string and writes it out to the ow_. + util::Status ParseString(); + + // Parses a string, storing the result in parsed_. + util::Status ParseStringHelper(); + + // This function parses unicode escape sequences in strings. It returns an + // error when there's a parsing error, either the size is not the expected + // size or a character is not a hex digit. When it returns str will contain + // what has been successfully parsed so far. + util::Status ParseUnicodeEscape(); + + // Expects p_ to point to a JSON number, writes the number to the writer using + // the appropriate Render method based on the type of number. + util::Status ParseNumber(); + + // Parse a number into a NumberResult, reporting an error if no number could + // be parsed. This method will try to parse into a uint64, int64, or double + // based on whether the number was positive or negative or had a decimal + // component. + util::Status ParseNumberHelper(NumberResult* result); + + // Handles a { during parsing of a value. + util::Status HandleBeginObject(); + + // Parses from the ENTRY state. + util::Status ParseEntry(TokenType type); + + // Parses from the ENTRY_MID state. + util::Status ParseEntryMid(TokenType type); + + // Parses from the OBJ_MID state. + util::Status ParseObjectMid(TokenType type); + + // Handles a [ during parsing of a value. + util::Status HandleBeginArray(); + + // Parses from the ARRAY_VALUE state. + util::Status ParseArrayValue(TokenType type); + + // Parses from the ARRAY_MID state. + util::Status ParseArrayMid(TokenType type); + + // Expects p_ to point to an unquoted literal + util::Status ParseTrue(); + util::Status ParseFalse(); + util::Status ParseNull(); + + // Report a failure as a util::Status. + util::Status ReportFailure(StringPiece message); + + // Report a failure due to an UNKNOWN token type. We check if we hit the + // end of the stream and if we're finishing or not to detect what type of + // status to return in this case. + util::Status ReportUnknown(StringPiece message); + + // Advance p_ past all whitespace or until the end of the string. + void SkipWhitespace(); + + // Advance p_ one UTF-8 character + void Advance(); + + // Expects p_ to point to the beginning of a key. + util::Status ParseKey(); + + // Return the type of the next token at p_. + TokenType GetNextTokenType(); + + // The object writer to write parse events to. + ObjectWriter* ow_; + + // The stack of parsing we still need to do. When the stack runs empty we will + // have parsed a single value from the root (e.g. an object or list). + std::stack stack_; + + // Contains any leftover text from a previous chunk that we weren't able to + // fully parse, for example the start of a key or number. + string leftover_; + + // The current chunk of JSON being parsed. Primarily used for providing + // context during error reporting. + StringPiece json_; + + // A pointer within the current JSON being parsed, used to track location. + StringPiece p_; + + // Stores the last key read, as we separate parsing of keys and values. + StringPiece key_; + + // Storage for key_ if we need to keep ownership, for example between chunks + // or if the key was unescaped from a JSON string. + string key_storage_; + + // True during the FinishParse() call, so we know that any errors are fatal. + // For example an unterminated string will normally result in cancelling and + // trying during the next chunk, but during FinishParse() it is an error. + bool finishing_; + + // String we parsed during a call to ParseStringHelper(). + StringPiece parsed_; + + // Storage for the string we parsed. This may be empty if the string was able + // to be parsed directly from the input. + string parsed_storage_; + + // The character that opened the string, either ' or ". + // A value of 0 indicates that string parsing is not in process. + char string_open_; + + // Storage for utf8-coerced bytes. + google::protobuf::scoped_array utf8_storage_; + + // Length of the storage for utf8-coerced bytes. + int utf8_length_; + + GOOGLE_DISALLOW_IMPLICIT_CONSTRUCTORS(JsonStreamParser); +}; + +} // namespace converter +} // namespace util +} // namespace protobuf + +} // namespace google +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_JSON_STREAM_PARSER_H__ diff --git a/src/google/protobuf/util/internal/json_stream_parser_test.cc b/src/google/protobuf/util/internal/json_stream_parser_test.cc new file mode 100644 index 00000000..bd8ed135 --- /dev/null +++ b/src/google/protobuf/util/internal/json_stream_parser_test.cc @@ -0,0 +1,697 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include +#include +#include +#include +#include +#include +#include + + +namespace google { +namespace protobuf { +namespace util { +using util::Status; +namespace error { +using util::error::INVALID_ARGUMENT; +} // namespace error +namespace converter { + +using util::Status; + +// Tests for the JSON Stream Parser. These tests are intended to be +// comprehensive and cover the following: +// +// Positive tests: +// - true, false, null +// - empty object or array. +// - negative and positive double and int, unsigned int +// - single and double quoted strings +// - string key, unquoted key, numeric key +// - array containing array, object, value +// - object containing array, object, value +// - unicode handling in strings +// - ascii escaping (\b, \f, \n, \r, \t, \v) +// - trailing commas +// +// Negative tests: +// - illegal literals +// - mismatched quotes failure on strings +// - unterminated string failure +// - unexpected end of string failure +// - mismatched object and array closing +// - Failure to close array or object +// - numbers too large +// - invalid unicode escapes. +// - invalid unicode sequences. +// - numbers as keys +// +// For each test we split the input string on every possible character to ensure +// the parser is able to handle arbitrarily split input for all cases. We also +// do a final test of the entire test case one character at a time. +class JsonStreamParserTest : public ::testing::Test { + protected: + JsonStreamParserTest() : mock_(), ow_(&mock_) {} + virtual ~JsonStreamParserTest() {} + + util::Status RunTest(StringPiece json, int split) { + JsonStreamParser parser(&mock_); + + // Special case for split == length, test parsing one character at a time. + if (split == json.length()) { + GOOGLE_LOG(INFO) << "Testing split every char: " << json; + for (int i = 0; i < json.length(); ++i) { + StringPiece single = json.substr(i, 1); + util::Status result = parser.Parse(single); + if (!result.ok()) { + return result; + } + } + return parser.FinishParse(); + } + + // Normal case, split at the split point and parse two substrings. + StringPiece first = json.substr(0, split); + StringPiece rest = json.substr(split); + GOOGLE_LOG(INFO) << "Testing split: " << first << "><" << rest; + util::Status result = parser.Parse(first); + if (result.ok()) { + result = parser.Parse(rest); + if (result.ok()) { + result = parser.FinishParse(); + } + } + return result; + } + + void DoTest(StringPiece json, int split) { + util::Status result = RunTest(json, split); + if (!result.ok()) { + GOOGLE_LOG(WARNING) << result; + } + EXPECT_OK(result); + } + + void DoErrorTest(StringPiece json, int split, StringPiece error_prefix) { + util::Status result = RunTest(json, split); + EXPECT_EQ(util::error::INVALID_ARGUMENT, result.error_code()); + StringPiece error_message(result.error_message()); + EXPECT_EQ(error_prefix, error_message.substr(0, error_prefix.size())); + } + + + MockObjectWriter mock_; + ExpectingObjectWriter ow_; +}; + + +// Positive tests + +// - true, false, null +TEST_F(JsonStreamParserTest, SimpleTrue) { + StringPiece str = "true"; + for (int i = 0; i <= str.length(); ++i) { + ow_.RenderBool("", true); + DoTest(str, i); + } +} + +TEST_F(JsonStreamParserTest, SimpleFalse) { + StringPiece str = "false"; + for (int i = 0; i <= str.length(); ++i) { + ow_.RenderBool("", false); + DoTest(str, i); + } +} + +TEST_F(JsonStreamParserTest, SimpleNull) { + StringPiece str = "null"; + for (int i = 0; i <= str.length(); ++i) { + ow_.RenderNull(""); + DoTest(str, i); + } +} + +// - empty object and array. +TEST_F(JsonStreamParserTest, EmptyObject) { + StringPiece str = "{}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject("")->EndObject(); + DoTest(str, i); + } +} + +TEST_F(JsonStreamParserTest, EmptyList) { + StringPiece str = "[]"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartList("")->EndList(); + DoTest(str, i); + } +} + +// - negative and positive double and int, unsigned int +TEST_F(JsonStreamParserTest, SimpleDouble) { + StringPiece str = "42.5"; + for (int i = 0; i <= str.length(); ++i) { + ow_.RenderDouble("", 42.5); + DoTest(str, i); + } +} + +TEST_F(JsonStreamParserTest, ScientificDouble) { + StringPiece str = "1.2345e-10"; + for (int i = 0; i < str.length(); ++i) { + ow_.RenderDouble("", 1.2345e-10); + DoTest(str, i); + } +} + +TEST_F(JsonStreamParserTest, SimpleNegativeDouble) { + StringPiece str = "-1045.235"; + for (int i = 0; i <= str.length(); ++i) { + ow_.RenderDouble("", -1045.235); + DoTest(str, i); + } +} + +TEST_F(JsonStreamParserTest, SimpleInt) { + StringPiece str = "123456"; + for (int i = 0; i <= str.length(); ++i) { + ow_.RenderUint64("", 123456); + DoTest(str, i); + } +} + +TEST_F(JsonStreamParserTest, SimpleNegativeInt) { + StringPiece str = "-79497823553162765"; + for (int i = 0; i <= str.length(); ++i) { + ow_.RenderInt64("", -79497823553162765LL); + DoTest(str, i); + } +} + +TEST_F(JsonStreamParserTest, SimpleUnsignedInt) { + StringPiece str = "11779497823553162765"; + for (int i = 0; i <= str.length(); ++i) { + ow_.RenderUint64("", 11779497823553162765ULL); + DoTest(str, i); + } +} + +// - single and double quoted strings +TEST_F(JsonStreamParserTest, EmptyDoubleQuotedString) { + StringPiece str = "\"\""; + for (int i = 0; i <= str.length(); ++i) { + ow_.RenderString("", ""); + DoTest(str, i); + } +} + +TEST_F(JsonStreamParserTest, EmptySingleQuotedString) { + StringPiece str = "''"; + for (int i = 0; i <= str.length(); ++i) { + ow_.RenderString("", ""); + DoTest(str, i); + } +} + +TEST_F(JsonStreamParserTest, SimpleDoubleQuotedString) { + StringPiece str = "\"Some String\""; + for (int i = 0; i <= str.length(); ++i) { + ow_.RenderString("", "Some String"); + DoTest(str, i); + } +} + +TEST_F(JsonStreamParserTest, SimpleSingleQuotedString) { + StringPiece str = "'Another String'"; + for (int i = 0; i <= str.length(); ++i) { + ow_.RenderString("", "Another String"); + DoTest(str, i); + } +} + +// - string key, unquoted key, numeric key +TEST_F(JsonStreamParserTest, ObjectKeyTypes) { + StringPiece str = + "{'s': true, \"d\": false, key: null, snake_key: [], camelKey: {}}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject("") + ->RenderBool("s", true) + ->RenderBool("d", false) + ->RenderNull("key") + ->StartList("snake_key") + ->EndList() + ->StartObject("camelKey") + ->EndObject() + ->EndObject(); + DoTest(str, i); + } +} + +// - array containing array, object, values (true, false, null, num, string) +TEST_F(JsonStreamParserTest, ArrayValues) { + StringPiece str = + "[true, false, null, 'a string', \"another string\", [22, -127, 45.3, " + "-1056.4, 11779497823553162765], {'key': true}]"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartList("") + ->RenderBool("", true) + ->RenderBool("", false) + ->RenderNull("") + ->RenderString("", "a string") + ->RenderString("", "another string") + ->StartList("") + ->RenderUint64("", 22) + ->RenderInt64("", -127) + ->RenderDouble("", 45.3) + ->RenderDouble("", -1056.4) + ->RenderUint64("", 11779497823553162765ULL) + ->EndList() + ->StartObject("") + ->RenderBool("key", true) + ->EndObject() + ->EndList(); + DoTest(str, i); + } +} + +// - object containing array, object, value (true, false, null, num, string) +TEST_F(JsonStreamParserTest, ObjectValues) { + StringPiece str = + "{t: true, f: false, n: null, s: 'a string', d: \"another string\", pi: " + "22, ni: -127, pd: 45.3, nd: -1056.4, pl: 11779497823553162765, l: [[]], " + "o: {'key': true}}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject("") + ->RenderBool("t", true) + ->RenderBool("f", false) + ->RenderNull("n") + ->RenderString("s", "a string") + ->RenderString("d", "another string") + ->RenderUint64("pi", 22) + ->RenderInt64("ni", -127) + ->RenderDouble("pd", 45.3) + ->RenderDouble("nd", -1056.4) + ->RenderUint64("pl", 11779497823553162765ULL) + ->StartList("l") + ->StartList("") + ->EndList() + ->EndList() + ->StartObject("o") + ->RenderBool("key", true) + ->EndObject() + ->EndObject(); + DoTest(str, i); + } +} + +// - unicode handling in strings +TEST_F(JsonStreamParserTest, UnicodeEscaping) { + StringPiece str = "[\"\\u0639\\u0631\\u0628\\u0649\"]"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartList("")->RenderString("", "\u0639\u0631\u0628\u0649")->EndList(); + DoTest(str, i); + } +} + +// - ascii escaping (\b, \f, \n, \r, \t, \v) +TEST_F(JsonStreamParserTest, AsciiEscaping) { + StringPiece str = + "[\"\\b\", \"\\ning\", \"test\\f\", \"\\r\\t\", \"test\\\\\\ving\"]"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartList("") + ->RenderString("", "\b") + ->RenderString("", "\ning") + ->RenderString("", "test\f") + ->RenderString("", "\r\t") + ->RenderString("", "test\\\ving") + ->EndList(); + DoTest(str, i); + } +} + +// - trailing commas, we support a single trailing comma but no internal commas. +TEST_F(JsonStreamParserTest, TrailingCommas) { + StringPiece str = "[['a',true,], {b: null,},]"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartList("") + ->StartList("") + ->RenderString("", "a") + ->RenderBool("", true) + ->EndList() + ->StartObject("") + ->RenderNull("b") + ->EndObject() + ->EndList(); + DoTest(str, i); + } +} + +// Negative tests + +// illegal literals +TEST_F(JsonStreamParserTest, ExtraTextAfterTrue) { + StringPiece str = "truee"; + for (int i = 0; i <= str.length(); ++i) { + ow_.RenderBool("", true); + DoErrorTest(str, i, "Parsing terminated before end of input."); + } +} + +TEST_F(JsonStreamParserTest, InvalidNumberDashOnly) { + StringPiece str = "-"; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Unable to parse number."); + } +} + +TEST_F(JsonStreamParserTest, InvalidNumberDashName) { + StringPiece str = "-foo"; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Unable to parse number."); + } +} + +TEST_F(JsonStreamParserTest, InvalidLiteralInArray) { + StringPiece str = "[nule]"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartList(""); + DoErrorTest(str, i, "Unexpected token."); + } +} + +TEST_F(JsonStreamParserTest, InvalidLiteralInObject) { + StringPiece str = "{123false}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject(""); + DoErrorTest(str, i, "Expected an object key or }."); + } +} + +// mismatched quotes failure on strings +TEST_F(JsonStreamParserTest, MismatchedSingleQuotedLiteral) { + StringPiece str = "'Some str\""; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Closing quote expected in string."); + } +} + +TEST_F(JsonStreamParserTest, MismatchedDoubleQuotedLiteral) { + StringPiece str = "\"Another string that ends poorly!'"; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Closing quote expected in string."); + } +} + +// unterminated strings +TEST_F(JsonStreamParserTest, UnterminatedLiteralString) { + StringPiece str = "\"Forgot the rest of i"; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Closing quote expected in string."); + } +} + +TEST_F(JsonStreamParserTest, UnterminatedStringEscape) { + StringPiece str = "\"Forgot the rest of \\"; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Closing quote expected in string."); + } +} + +TEST_F(JsonStreamParserTest, UnterminatedStringInArray) { + StringPiece str = "[\"Forgot to close the string]"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartList(""); + DoErrorTest(str, i, "Closing quote expected in string."); + } +} + +TEST_F(JsonStreamParserTest, UnterminatedStringInObject) { + StringPiece str = "{f: \"Forgot to close the string}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject(""); + DoErrorTest(str, i, "Closing quote expected in string."); + } +} + +TEST_F(JsonStreamParserTest, UnterminatedObject) { + StringPiece str = "{"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject(""); + DoErrorTest(str, i, "Unexpected end of string."); + } +} + + +// mismatched object and array closing +TEST_F(JsonStreamParserTest, MismatchedCloseObject) { + StringPiece str = "{'key': true]"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject("")->RenderBool("key", true); + DoErrorTest(str, i, "Expected , or } after key:value pair."); + } +} + +TEST_F(JsonStreamParserTest, MismatchedCloseArray) { + StringPiece str = "[true, null}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartList("")->RenderBool("", true)->RenderNull(""); + DoErrorTest(str, i, "Expected , or ] after array value."); + } +} + +// Invalid object keys. +TEST_F(JsonStreamParserTest, InvalidNumericObjectKey) { + StringPiece str = "{42: true}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject(""); + DoErrorTest(str, i, "Expected an object key or }."); + } +} + +TEST_F(JsonStreamParserTest, InvalidLiteralObjectInObject) { + StringPiece str = "{{bob: true}}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject(""); + DoErrorTest(str, i, "Expected an object key or }."); + } +} + +TEST_F(JsonStreamParserTest, InvalidLiteralArrayInObject) { + StringPiece str = "{[null]}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject(""); + DoErrorTest(str, i, "Expected an object key or }."); + } +} + +TEST_F(JsonStreamParserTest, InvalidLiteralValueInObject) { + StringPiece str = "{false}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject(""); + DoErrorTest(str, i, "Expected an object key or }."); + } +} + +TEST_F(JsonStreamParserTest, MissingColonAfterStringInObject) { + StringPiece str = "{\"key\"}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject(""); + DoErrorTest(str, i, "Expected : between key:value pair."); + } +} + +TEST_F(JsonStreamParserTest, MissingColonAfterKeyInObject) { + StringPiece str = "{key}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject(""); + DoErrorTest(str, i, "Expected : between key:value pair."); + } +} + +TEST_F(JsonStreamParserTest, EndOfTextAfterKeyInObject) { + StringPiece str = "{key"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject(""); + DoErrorTest(str, i, "Unexpected end of string."); + } +} + +TEST_F(JsonStreamParserTest, MissingValueAfterColonInObject) { + StringPiece str = "{key:}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject(""); + DoErrorTest(str, i, "Unexpected token."); + } +} + +TEST_F(JsonStreamParserTest, MissingCommaBetweenObjectEntries) { + StringPiece str = "{key:20 'hello': true}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject("")->RenderUint64("key", 20); + DoErrorTest(str, i, "Expected , or } after key:value pair."); + } +} + +TEST_F(JsonStreamParserTest, InvalidLiteralAsObjectKey) { + StringPiece str = "{false: 20}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject(""); + DoErrorTest(str, i, "Expected an object key or }."); + } +} + +TEST_F(JsonStreamParserTest, ExtraCharactersAfterObject) { + StringPiece str = "{}}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject("")->EndObject(); + DoErrorTest(str, i, "Parsing terminated before end of input."); + } +} + +// numbers too large +TEST_F(JsonStreamParserTest, PositiveNumberTooBig) { + StringPiece str = "[18446744073709551616]"; // 2^64 + for (int i = 0; i <= str.length(); ++i) { + ow_.StartList(""); + DoErrorTest(str, i, "Unable to parse number."); + } +} + +TEST_F(JsonStreamParserTest, NegativeNumberTooBig) { + StringPiece str = "[-18446744073709551616]"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartList(""); + DoErrorTest(str, i, "Unable to parse number."); + } +} + +/* +TODO(sven): Fail parsing when parsing a double that is too large. + +TEST_F(JsonStreamParserTest, DoubleTooBig) { + StringPiece str = "[184464073709551232321616.45]"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartList(""); + DoErrorTest(str, i, "Unable to parse number"); + } +} +*/ + +// invalid unicode sequence. +TEST_F(JsonStreamParserTest, UnicodeEscapeCutOff) { + StringPiece str = "\"\\u12"; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Illegal hex string."); + } +} + +TEST_F(JsonStreamParserTest, UnicodeEscapeInvalidCharacters) { + StringPiece str = "\"\\u12$4hello"; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Invalid escape sequence."); + } +} + +// Extra commas with an object or array. +TEST_F(JsonStreamParserTest, ExtraCommaInObject) { + StringPiece str = "{'k1': true,,'k2': false}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject("")->RenderBool("k1", true); + DoErrorTest(str, i, "Expected an object key or }."); + } +} + +TEST_F(JsonStreamParserTest, ExtraCommaInArray) { + StringPiece str = "[true,,false}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartList("")->RenderBool("", true); + DoErrorTest(str, i, "Unexpected token."); + } +} + +// Extra text beyond end of value. +TEST_F(JsonStreamParserTest, ExtraTextAfterLiteral) { + StringPiece str = "'hello', 'world'"; + for (int i = 0; i <= str.length(); ++i) { + ow_.RenderString("", "hello"); + DoErrorTest(str, i, "Parsing terminated before end of input."); + } +} + +TEST_F(JsonStreamParserTest, ExtraTextAfterObject) { + StringPiece str = "{'key': true} 'oops'"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject("")->RenderBool("key", true)->EndObject(); + DoErrorTest(str, i, "Parsing terminated before end of input."); + } +} + +TEST_F(JsonStreamParserTest, ExtraTextAfterArray) { + StringPiece str = "[null] 'oops'"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartList("")->RenderNull("")->EndList(); + DoErrorTest(str, i, "Parsing terminated before end of input."); + } +} + +// Random unknown text in the value. +TEST_F(JsonStreamParserTest, UnknownCharactersAsValue) { + StringPiece str = "*"; + for (int i = 0; i <= str.length(); ++i) { + DoErrorTest(str, i, "Expected a value."); + } +} + +TEST_F(JsonStreamParserTest, UnknownCharactersInArray) { + StringPiece str = "[*]"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartList(""); + DoErrorTest(str, i, "Expected a value or ] within an array."); + } +} + +TEST_F(JsonStreamParserTest, UnknownCharactersInObject) { + StringPiece str = "{'key': *}"; + for (int i = 0; i <= str.length(); ++i) { + ow_.StartObject(""); + DoErrorTest(str, i, "Expected a value."); + } +} + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/util/internal/location_tracker.h b/src/google/protobuf/util/internal/location_tracker.h new file mode 100644 index 00000000..0864b057 --- /dev/null +++ b/src/google/protobuf/util/internal/location_tracker.h @@ -0,0 +1,65 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_LOCATION_TRACKER_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_LOCATION_TRACKER_H__ + +#include + +#include + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +// LocationTrackerInterface is an interface for classes that track +// the location information for the purpose of error reporting. +class LIBPROTOBUF_EXPORT LocationTrackerInterface { + public: + virtual ~LocationTrackerInterface() {} + + // Returns the object location as human readable string. + virtual string ToString() const = 0; + + protected: + LocationTrackerInterface() {} + + private: + // Please do not add any data members to this class. + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(LocationTrackerInterface); +}; + +} // namespace converter +} // namespace util +} // namespace protobuf + +} // namespace google +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_LOCATION_TRACKER_H__ diff --git a/src/google/protobuf/util/internal/mock_error_listener.h b/src/google/protobuf/util/internal/mock_error_listener.h new file mode 100644 index 00000000..591c35db --- /dev/null +++ b/src/google/protobuf/util/internal/mock_error_listener.h @@ -0,0 +1,63 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_MOCK_ERROR_LISTENER_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_MOCK_ERROR_LISTENER_H__ + +#include +#include +#include +#include + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +class MockErrorListener : public ErrorListener { + public: + MockErrorListener() {} + virtual ~MockErrorListener() {} + + MOCK_METHOD3(InvalidName, void(const LocationTrackerInterface& loc, + StringPiece unknown_name, + StringPiece message)); + MOCK_METHOD3(InvalidValue, void(const LocationTrackerInterface& loc, + StringPiece type_name, StringPiece value)); + MOCK_METHOD2(MissingField, void(const LocationTrackerInterface& loc, + StringPiece missing_name)); +}; + +} // namespace converter +} // namespace util +} // namespace protobuf + +} // namespace google +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_MOCK_ERROR_LISTENER_H__ diff --git a/src/google/protobuf/util/internal/object_location_tracker.h b/src/google/protobuf/util/internal/object_location_tracker.h new file mode 100644 index 00000000..8586cecc --- /dev/null +++ b/src/google/protobuf/util/internal/object_location_tracker.h @@ -0,0 +1,64 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_OBJECT_LOCATION_TRACKER_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_OBJECT_LOCATION_TRACKER_H__ + +#include + +#include +#include + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +// An empty concrete implementation of LocationTrackerInterface. +class ObjectLocationTracker : public LocationTrackerInterface { + public: + // Creates an empty location tracker. + ObjectLocationTracker() {} + + virtual ~ObjectLocationTracker() {} + + // Returns empty because nothing is tracked. + virtual string ToString() const { return ""; } + + private: + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(ObjectLocationTracker); +}; + +} // namespace converter +} // namespace util +} // namespace protobuf + +} // namespace google +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_OBJECT_LOCATION_TRACKER_H__ diff --git a/src/google/protobuf/util/internal/object_source.h b/src/google/protobuf/util/internal/object_source.h new file mode 100644 index 00000000..2c31cfb0 --- /dev/null +++ b/src/google/protobuf/util/internal/object_source.h @@ -0,0 +1,79 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_OBJECT_SOURCE_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_OBJECT_SOURCE_H__ + +#include +#include +#include + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +class ObjectWriter; + +// An ObjectSource is anything that can write to an ObjectWriter. +// Implementation of this interface typically provide constructors or +// factory methods to create an instance based on some source data, for +// example, a character stream, or protobuf. +// +// Derived classes could be thread-unsafe. +class LIBPROTOBUF_EXPORT ObjectSource { + public: + virtual ~ObjectSource() {} + + // Writes to the ObjectWriter + virtual util::Status WriteTo(ObjectWriter* ow) const { + return NamedWriteTo("", ow); + } + + // Writes to the ObjectWriter with a custom name for the message. + // This is useful when you chain ObjectSource together by embedding one + // within another. + virtual util::Status NamedWriteTo(StringPiece name, + ObjectWriter* ow) const = 0; + + protected: + ObjectSource() {} + + private: + // Do not add any data members to this class. + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(ObjectSource); +}; + +} // namespace converter +} // namespace util +} // namespace protobuf + +} // namespace google +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_OBJECT_SOURCE_H__ diff --git a/src/google/protobuf/util/internal/object_writer.cc b/src/google/protobuf/util/internal/object_writer.cc new file mode 100644 index 00000000..57cc08a1 --- /dev/null +++ b/src/google/protobuf/util/internal/object_writer.cc @@ -0,0 +1,92 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +// static +void ObjectWriter::RenderDataPieceTo(const DataPiece& data, StringPiece name, + ObjectWriter* ow) { + switch (data.type()) { + case DataPiece::TYPE_INT32: { + ow->RenderInt32(name, data.ToInt32().ValueOrDie()); + break; + } + case DataPiece::TYPE_INT64: { + ow->RenderInt64(name, data.ToInt64().ValueOrDie()); + break; + } + case DataPiece::TYPE_UINT32: { + ow->RenderUint32(name, data.ToUint32().ValueOrDie()); + break; + } + case DataPiece::TYPE_UINT64: { + ow->RenderUint64(name, data.ToUint64().ValueOrDie()); + break; + } + case DataPiece::TYPE_DOUBLE: { + ow->RenderDouble(name, data.ToDouble().ValueOrDie()); + break; + } + case DataPiece::TYPE_FLOAT: { + ow->RenderFloat(name, data.ToFloat().ValueOrDie()); + break; + } + case DataPiece::TYPE_BOOL: { + ow->RenderBool(name, data.ToBool().ValueOrDie()); + break; + } + case DataPiece::TYPE_STRING: { + ow->RenderString(name, data.ToString().ValueOrDie()); + break; + } + case DataPiece::TYPE_BYTES: { + ow->RenderBytes(name, data.ToBytes().ValueOrDie()); + break; + } + case DataPiece::TYPE_NULL: { + ow->RenderNull(name); + break; + } + default: + break; + } +} + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/util/internal/object_writer.h b/src/google/protobuf/util/internal/object_writer.h new file mode 100644 index 00000000..20bd3627 --- /dev/null +++ b/src/google/protobuf/util/internal/object_writer.h @@ -0,0 +1,126 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_OBJECT_WRITER_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_OBJECT_WRITER_H__ + +#include +#include + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +class DataPiece; + +// An ObjectWriter is an interface for writing a stream of events +// representing objects and collections. Implementation of this +// interface can be used to write an object stream to an in-memory +// structure, protobufs, JSON, XML, or any other output format +// desired. The ObjectSource interface is typically used as the +// source of an object stream. +// +// See JsonObjectWriter for a sample implementation of ObjectWriter +// and its use. +// +// Derived classes could be thread-unsafe. +// +// TODO(xinb): seems like a prime candidate to apply the RAII paradigm +// and get rid the need to call EndXXX(). +class LIBPROTOBUF_EXPORT ObjectWriter { + public: + virtual ~ObjectWriter() {} + + // Starts an object. If the name is empty, the object will not be named. + virtual ObjectWriter* StartObject(StringPiece name) = 0; + + // Ends an object. + virtual ObjectWriter* EndObject() = 0; + + // Starts a list. If the name is empty, the list will not be named. + virtual ObjectWriter* StartList(StringPiece name) = 0; + + // Ends a list. + virtual ObjectWriter* EndList() = 0; + + // Renders a boolean value. + virtual ObjectWriter* RenderBool(StringPiece name, bool value) = 0; + + // Renders an 32-bit integer value. + virtual ObjectWriter* RenderInt32(StringPiece name, int32 value) = 0; + + // Renders an 32-bit unsigned integer value. + virtual ObjectWriter* RenderUint32(StringPiece name, uint32 value) = 0; + + // Renders a 64-bit integer value. + virtual ObjectWriter* RenderInt64(StringPiece name, int64 value) = 0; + + // Renders an 64-bit unsigned integer value. + virtual ObjectWriter* RenderUint64(StringPiece name, uint64 value) = 0; + + // Renders a double value. + virtual ObjectWriter* RenderDouble(StringPiece name, double value) = 0; + + // Renders a float value. + virtual ObjectWriter* RenderFloat(StringPiece name, float value) = 0; + + // Renders a StringPiece value. This is for rendering strings. + virtual ObjectWriter* RenderString(StringPiece name, StringPiece value) = 0; + + // Renders a bytes value. + virtual ObjectWriter* RenderBytes(StringPiece name, StringPiece value) = 0; + + // Renders a Null value. + virtual ObjectWriter* RenderNull(StringPiece name) = 0; + + // Disables case normalization. Any RenderTYPE call after calling this + // function will output the name field as-is. No normalization is attempted on + // it. This setting is reset immediately after the next RenderTYPE is called. + virtual ObjectWriter* DisableCaseNormalizationForNextKey() { return this; } + + // Renders a DataPiece object to a ObjectWriter. + static void RenderDataPieceTo(const DataPiece& data, StringPiece name, + ObjectWriter* ow); + + protected: + ObjectWriter() {} + + private: + // Do not add any data members to this class. + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(ObjectWriter); +}; + +} // namespace converter +} // namespace util +} // namespace protobuf + +} // namespace google +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_OBJECT_WRITER_H__ diff --git a/src/google/protobuf/util/internal/protostream_objectsource.cc b/src/google/protobuf/util/internal/protostream_objectsource.cc new file mode 100644 index 00000000..53a0e47a --- /dev/null +++ b/src/google/protobuf/util/internal/protostream_objectsource.cc @@ -0,0 +1,1051 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace google { +namespace protobuf { +namespace util { +using util::Status; +using util::StatusOr; +namespace error { +using util::error::Code; +using util::error::INTERNAL; +} +namespace converter { + +using google::protobuf::Descriptor; +using google::protobuf::EnumValueDescriptor; +using google::protobuf::FieldDescriptor; +using google::protobuf::internal::WireFormat; +using google::protobuf::internal::WireFormatLite; +using util::Status; +using util::StatusOr; + +namespace { +// Finds a field with the given number. NULL if none found. +const google::protobuf::Field* FindFieldByNumber( + const google::protobuf::Type& type, int number); + +// Returns true if the field is packable. +bool IsPackable(const google::protobuf::Field& field); + +// Finds an enum value with the given number. NULL if none found. +const google::protobuf::EnumValue* FindEnumValueByNumber( + const google::protobuf::Enum& tech_enum, int number); + +// Utility function to format nanos. +const string FormatNanos(uint32 nanos); +} // namespace + + +ProtoStreamObjectSource::ProtoStreamObjectSource( + google::protobuf::io::CodedInputStream* stream, TypeResolver* type_resolver, + const google::protobuf::Type& type) + : stream_(stream), + typeinfo_(TypeInfo::NewTypeInfo(type_resolver)), + own_typeinfo_(true), + type_(type) { + GOOGLE_LOG_IF(DFATAL, stream == NULL) << "Input stream is NULL."; +} + +ProtoStreamObjectSource::ProtoStreamObjectSource( + google::protobuf::io::CodedInputStream* stream, TypeInfo* typeinfo, + const google::protobuf::Type& type) + : stream_(stream), typeinfo_(typeinfo), own_typeinfo_(false), type_(type) { + GOOGLE_LOG_IF(DFATAL, stream == NULL) << "Input stream is NULL."; +} + +ProtoStreamObjectSource::~ProtoStreamObjectSource() { + if (own_typeinfo_) { + delete typeinfo_; + } +} + +Status ProtoStreamObjectSource::NamedWriteTo(StringPiece name, + ObjectWriter* ow) const { + return WriteMessage(type_, name, 0, true, ow); +} + +const google::protobuf::Field* ProtoStreamObjectSource::FindAndVerifyField( + const google::protobuf::Type& type, uint32 tag) const { + // Lookup the new field in the type by tag number. + const google::protobuf::Field* field = FindFieldByNumber(type, tag >> 3); + // Verify if the field corresponds to the wire type in tag. + // If there is any discrepancy, mark the field as not found. + if (field != NULL) { + WireFormatLite::WireType expected_type = + WireFormatLite::WireTypeForFieldType( + static_cast(field->kind())); + WireFormatLite::WireType actual_type = WireFormatLite::GetTagWireType(tag); + if (actual_type != expected_type && + (!IsPackable(*field) || + actual_type != WireFormatLite::WIRETYPE_LENGTH_DELIMITED)) { + field = NULL; + } + } + return field; +} + +Status ProtoStreamObjectSource::WriteMessage(const google::protobuf::Type& type, + StringPiece name, + const uint32 end_tag, + bool include_start_and_end, + ObjectWriter* ow) const { + const TypeRenderer* type_renderer = FindTypeRenderer(type.name()); + if (type_renderer != NULL) { + return (*type_renderer)(this, type, name, ow); + } + + const google::protobuf::Field* field = NULL; + string field_name; + // last_tag set to dummy value that is different from tag. + uint32 tag = stream_->ReadTag(), last_tag = tag + 1; + + if (include_start_and_end) { + ow->StartObject(name); + } + while (tag != end_tag) { + if (tag != last_tag) { // Update field only if tag is changed. + last_tag = tag; + field = FindAndVerifyField(type, tag); + if (field != NULL) { + field_name = field->name(); + } + } + if (field == NULL) { + // If we didn't find a field, skip this unknown tag. + // TODO(wpoon): Check return boolean value. + WireFormat::SkipField(stream_, tag, NULL); + tag = stream_->ReadTag(); + continue; + } + + if (field->cardinality() == + google::protobuf::Field_Cardinality_CARDINALITY_REPEATED) { + if (IsMap(*field)) { + ow->StartObject(field_name); + ASSIGN_OR_RETURN(tag, RenderMap(field, field_name, tag, ow)); + ow->EndObject(); + } else { + ASSIGN_OR_RETURN(tag, RenderList(field, field_name, tag, ow)); + } + } else { + // Render the field. + RETURN_IF_ERROR(RenderField(field, field_name, ow)); + tag = stream_->ReadTag(); + } + } + if (include_start_and_end) { + ow->EndObject(); + } + return Status::OK; +} + +StatusOr ProtoStreamObjectSource::RenderList( + const google::protobuf::Field* field, StringPiece name, uint32 list_tag, + ObjectWriter* ow) const { + uint32 tag_to_return = 0; + ow->StartList(name); + if (IsPackable(*field) && + list_tag == + WireFormatLite::MakeTag(field->number(), + WireFormatLite::WIRETYPE_LENGTH_DELIMITED)) { + RETURN_IF_ERROR(RenderPacked(field, ow)); + // Since packed fields have a single tag, read another tag from stream to + // return. + tag_to_return = stream_->ReadTag(); + } else { + do { + RETURN_IF_ERROR(RenderField(field, "", ow)); + } while ((tag_to_return = stream_->ReadTag()) == list_tag); + } + ow->EndList(); + return tag_to_return; +} + +StatusOr ProtoStreamObjectSource::RenderMap( + const google::protobuf::Field* field, StringPiece name, uint32 list_tag, + ObjectWriter* ow) const { + const google::protobuf::Type* field_type = + typeinfo_->GetType(field->type_url()); + uint32 tag_to_return = 0; + if (IsPackable(*field) && + list_tag == + WireFormatLite::MakeTag(field->number(), + WireFormatLite::WIRETYPE_LENGTH_DELIMITED)) { + RETURN_IF_ERROR(RenderPackedMapEntry(field_type, ow)); + tag_to_return = stream_->ReadTag(); + } else { + do { + RETURN_IF_ERROR(RenderMapEntry(field_type, ow)); + } while ((tag_to_return = stream_->ReadTag()) == list_tag); + } + return tag_to_return; +} + +Status ProtoStreamObjectSource::RenderMapEntry( + const google::protobuf::Type* type, ObjectWriter* ow) const { + uint32 buffer32; + stream_->ReadVarint32(&buffer32); // message length + int old_limit = stream_->PushLimit(buffer32); + string map_key; + for (uint32 tag = stream_->ReadTag(); tag != 0; tag = stream_->ReadTag()) { + const google::protobuf::Field* field = FindAndVerifyField(*type, tag); + if (field == NULL) { + WireFormat::SkipField(stream_, tag, NULL); + continue; + } + // Map field numbers are key = 1 and value = 2 + if (field->number() == 1) { + map_key = ReadFieldValueAsString(*field); + } else if (field->number() == 2) { + if (map_key.empty()) { + return Status(util::error::INTERNAL, "Map key must be non-empty"); + } + // Disable case normalization for map keys as they are just data. We + // retain them intact. + ow->DisableCaseNormalizationForNextKey(); + RETURN_IF_ERROR(RenderField(field, map_key, ow)); + } + } + stream_->PopLimit(old_limit); + + return Status::OK; +} + +Status ProtoStreamObjectSource::RenderPacked( + const google::protobuf::Field* field, ObjectWriter* ow) const { + uint32 length; + stream_->ReadVarint32(&length); + int old_limit = stream_->PushLimit(length); + while (stream_->BytesUntilLimit() > 0) { + RETURN_IF_ERROR(RenderField(field, StringPiece(), ow)); + } + stream_->PopLimit(old_limit); + return Status::OK; +} + +Status ProtoStreamObjectSource::RenderPackedMapEntry( + const google::protobuf::Type* type, ObjectWriter* ow) const { + uint32 length; + stream_->ReadVarint32(&length); + int old_limit = stream_->PushLimit(length); + while (stream_->BytesUntilLimit() > 0) { + RETURN_IF_ERROR(RenderMapEntry(type, ow)); + } + stream_->PopLimit(old_limit); + return Status::OK; +} + +Status ProtoStreamObjectSource::RenderTimestamp( + const ProtoStreamObjectSource* os, const google::protobuf::Type& type, + StringPiece field_name, ObjectWriter* ow) { + pair p = os->ReadSecondsAndNanos(type); + int64 seconds = p.first; + int32 nanos = p.second; + if (seconds > kMaxSeconds || seconds < kMinSeconds) { + return Status( + util::error::INTERNAL, + StrCat("Timestamp seconds exceeds limit for field: ", field_name)); + } + + if (nanos < 0 || nanos >= kNanosPerSecond) { + return Status( + util::error::INTERNAL, + StrCat("Timestamp nanos exceeds limit for field: ", field_name)); + } + + ow->RenderString(field_name, + ::google::protobuf::internal::FormatTime(seconds, nanos)); + + return Status::OK; +} + +Status ProtoStreamObjectSource::RenderDuration( + const ProtoStreamObjectSource* os, const google::protobuf::Type& type, + StringPiece field_name, ObjectWriter* ow) { + pair p = os->ReadSecondsAndNanos(type); + int64 seconds = p.first; + int32 nanos = p.second; + if (seconds > kMaxSeconds || seconds < kMinSeconds) { + return Status( + util::error::INTERNAL, + StrCat("Duration seconds exceeds limit for field: ", field_name)); + } + + if (nanos <= -kNanosPerSecond || nanos >= kNanosPerSecond) { + return Status( + util::error::INTERNAL, + StrCat("Duration nanos exceeds limit for field: ", field_name)); + } + + string sign = ""; + if (seconds < 0) { + if (nanos > 0) { + return Status(util::error::INTERNAL, + StrCat( + "Duration nanos is non-negative, but seconds is " + "negative for field: ", + field_name)); + } + sign = "-"; + seconds = -seconds; + nanos = -nanos; + } else if (seconds == 0 && nanos < 0) { + sign = "-"; + nanos = -nanos; + } + string formatted_duration = StringPrintf("%s%lld%ss", sign.c_str(), seconds, + FormatNanos(nanos).c_str()); + ow->RenderString(field_name, formatted_duration); + return Status::OK; +} + +Status ProtoStreamObjectSource::RenderDouble(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece field_name, + ObjectWriter* ow) { + uint32 tag = os->stream_->ReadTag(); + uint64 buffer64 = 0; // default value of Double wrapper value + if (tag != 0) { + os->stream_->ReadLittleEndian64(&buffer64); + os->stream_->ReadTag(); + } + ow->RenderDouble(field_name, bit_cast(buffer64)); + return Status::OK; +} + +Status ProtoStreamObjectSource::RenderFloat(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece field_name, + ObjectWriter* ow) { + uint32 tag = os->stream_->ReadTag(); + uint32 buffer32 = 0; // default value of Float wrapper value + if (tag != 0) { + os->stream_->ReadLittleEndian32(&buffer32); + os->stream_->ReadTag(); + } + ow->RenderFloat(field_name, bit_cast(buffer32)); + return Status::OK; +} + +Status ProtoStreamObjectSource::RenderInt64(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece field_name, + ObjectWriter* ow) { + uint32 tag = os->stream_->ReadTag(); + uint64 buffer64 = 0; // default value of Int64 wrapper value + if (tag != 0) { + os->stream_->ReadVarint64(&buffer64); + os->stream_->ReadTag(); + } + ow->RenderInt64(field_name, bit_cast(buffer64)); + return Status::OK; +} + +Status ProtoStreamObjectSource::RenderUInt64(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece field_name, + ObjectWriter* ow) { + uint32 tag = os->stream_->ReadTag(); + uint64 buffer64 = 0; // default value of UInt64 wrapper value + if (tag != 0) { + os->stream_->ReadVarint64(&buffer64); + os->stream_->ReadTag(); + } + ow->RenderUint64(field_name, bit_cast(buffer64)); + return Status::OK; +} + +Status ProtoStreamObjectSource::RenderInt32(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece field_name, + ObjectWriter* ow) { + uint32 tag = os->stream_->ReadTag(); + uint32 buffer32 = 0; // default value of Int32 wrapper value + if (tag != 0) { + os->stream_->ReadVarint32(&buffer32); + os->stream_->ReadTag(); + } + ow->RenderInt32(field_name, bit_cast(buffer32)); + return Status::OK; +} + +Status ProtoStreamObjectSource::RenderUInt32(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece field_name, + ObjectWriter* ow) { + uint32 tag = os->stream_->ReadTag(); + uint32 buffer32 = 0; // default value of UInt32 wrapper value + if (tag != 0) { + os->stream_->ReadVarint32(&buffer32); + os->stream_->ReadTag(); + } + ow->RenderUint32(field_name, bit_cast(buffer32)); + return Status::OK; +} + +Status ProtoStreamObjectSource::RenderBool(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece field_name, + ObjectWriter* ow) { + uint32 tag = os->stream_->ReadTag(); + uint64 buffer64 = 0; // results in 'false' value as default, which is the + // default value of Bool wrapper + if (tag != 0) { + os->stream_->ReadVarint64(&buffer64); + os->stream_->ReadTag(); + } + ow->RenderBool(field_name, buffer64 != 0); + return Status::OK; +} + +Status ProtoStreamObjectSource::RenderString(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece field_name, + ObjectWriter* ow) { + uint32 tag = os->stream_->ReadTag(); + uint32 buffer32; + string str; // default value of empty for String wrapper + if (tag != 0) { + os->stream_->ReadVarint32(&buffer32); // string size. + os->stream_->ReadString(&str, buffer32); + os->stream_->ReadTag(); + } + ow->RenderString(field_name, str); + return Status::OK; +} + +Status ProtoStreamObjectSource::RenderBytes(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece field_name, + ObjectWriter* ow) { + uint32 tag = os->stream_->ReadTag(); + uint32 buffer32; + string str; + if (tag != 0) { + os->stream_->ReadVarint32(&buffer32); + os->stream_->ReadString(&str, buffer32); + os->stream_->ReadTag(); + } + ow->RenderBytes(field_name, str); + return Status::OK; +} + +Status ProtoStreamObjectSource::RenderStruct(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece field_name, + ObjectWriter* ow) { + const google::protobuf::Field* field = NULL; + uint32 tag = os->stream_->ReadTag(); + ow->StartObject(field_name); + while (tag != 0) { + field = os->FindAndVerifyField(type, tag); + // google.protobuf.Struct has only one field that is a map. Hence we use + // RenderMap to render that field. + if (os->IsMap(*field)) { + ASSIGN_OR_RETURN(tag, os->RenderMap(field, field_name, tag, ow)); + } + } + ow->EndObject(); + return Status::OK; +} + +Status ProtoStreamObjectSource::RenderStructValue( + const ProtoStreamObjectSource* os, const google::protobuf::Type& type, + StringPiece field_name, ObjectWriter* ow) { + const google::protobuf::Field* field = NULL; + for (uint32 tag = os->stream_->ReadTag(); tag != 0; + tag = os->stream_->ReadTag()) { + field = os->FindAndVerifyField(type, tag); + if (field == NULL) { + WireFormat::SkipField(os->stream_, tag, NULL); + continue; + } + RETURN_IF_ERROR(os->RenderField(field, field_name, ow)); + } + return Status::OK; +} + +// TODO(skarvaje): Avoid code duplication of for loops and SkipField logic. +Status ProtoStreamObjectSource::RenderStructListValue( + const ProtoStreamObjectSource* os, const google::protobuf::Type& type, + StringPiece field_name, ObjectWriter* ow) { + uint32 tag = os->stream_->ReadTag(); + + // Render empty list when we find empty ListValue message. + if (tag == 0) { + ow->StartList(field_name); + ow->EndList(); + return Status::OK; + } + + while (tag != 0) { + const google::protobuf::Field* field = os->FindAndVerifyField(type, tag); + if (field == NULL) { + WireFormat::SkipField(os->stream_, tag, NULL); + tag = os->stream_->ReadTag(); + continue; + } + ASSIGN_OR_RETURN(tag, os->RenderList(field, field_name, tag, ow)); + } + return Status::OK; +} + +Status ProtoStreamObjectSource::RenderAny(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece field_name, + ObjectWriter* ow) { + // An Any is of the form { string type_url = 1; bytes value = 2; } + uint32 tag; + string type_url; + string value; + + // First read out the type_url and value from the proto stream + for (tag = os->stream_->ReadTag(); tag != 0; tag = os->stream_->ReadTag()) { + const google::protobuf::Field* field = os->FindAndVerifyField(type, tag); + if (field == NULL) { + WireFormat::SkipField(os->stream_, tag, NULL); + continue; + } + // 'type_url' has field number of 1 and 'value' has field number 2 + // //google/protobuf/any.proto + if (field->number() == 1) { + // read type_url + uint32 type_url_size; + os->stream_->ReadVarint32(&type_url_size); + os->stream_->ReadString(&type_url, type_url_size); + } else if (field->number() == 2) { + // read value + uint32 value_size; + os->stream_->ReadVarint32(&value_size); + os->stream_->ReadString(&value, value_size); + } + } + + // If there is no value, we don't lookup the type, we just output it (if + // present). If both type and value are empty we output an empty object. + if (value.empty()) { + ow->StartObject(field_name); + if (!type_url.empty()) { + ow->RenderString("@type", type_url); + } + ow->EndObject(); + return util::Status::OK; + } + + // If there is a value but no type, we cannot render it, so report an error. + if (type_url.empty()) { + // TODO(sven): Add an external message once those are ready. + return util::Status(util::error::INTERNAL, + "Invalid Any, the type_url is missing."); + } + + util::StatusOr resolved_type = + os->typeinfo_->ResolveTypeUrl(type_url); + + if (!resolved_type.ok()) { + // Convert into an internal error, since this means the backend gave us + // an invalid response (missing or invalid type information). + return util::Status(util::error::INTERNAL, + resolved_type.status().error_message()); + } + // nested_type cannot be null at this time. + const google::protobuf::Type* nested_type = resolved_type.ValueOrDie(); + + // We know the type so we can render it. Recursively parse the nested stream + // using a nested ProtoStreamObjectSource using our nested type information. + google::protobuf::io::ArrayInputStream zero_copy_stream(value.data(), value.size()); + google::protobuf::io::CodedInputStream in_stream(&zero_copy_stream); + ProtoStreamObjectSource nested_os(&in_stream, os->typeinfo_, *nested_type); + + // We manually call start and end object here so we can inject the @type. + ow->StartObject(field_name); + ow->RenderString("@type", type_url); + util::Status result = + nested_os.WriteMessage(nested_os.type_, "value", 0, false, ow); + ow->EndObject(); + return result; +} + +Status ProtoStreamObjectSource::RenderFieldMask( + const ProtoStreamObjectSource* os, const google::protobuf::Type& type, + StringPiece field_name, ObjectWriter* ow) { + string combined; + uint32 buffer32; + uint32 paths_field_tag = 0; + for (uint32 tag = os->stream_->ReadTag(); tag != 0; + tag = os->stream_->ReadTag()) { + if (paths_field_tag == 0) { + const google::protobuf::Field* field = os->FindAndVerifyField(type, tag); + if (field != NULL && field->number() == 1 && + field->name() == "paths") { + paths_field_tag = tag; + } + } + if (paths_field_tag != tag) { + return util::Status(util::error::INTERNAL, + "Invalid FieldMask, unexpected field."); + } + string str; + os->stream_->ReadVarint32(&buffer32); // string size. + os->stream_->ReadString(&str, buffer32); + if (!combined.empty()) { + combined.append(","); + } + combined.append(ConvertFieldMaskPath(str, &ToCamelCase)); + } + ow->RenderString(field_name, combined); + return Status::OK; +} + +hash_map* +ProtoStreamObjectSource::CreateRendererMap() { + hash_map* result = + new hash_map(); + (*result)["google.protobuf.Timestamp"] = + &ProtoStreamObjectSource::RenderTimestamp; + (*result)["google.protobuf.Duration"] = + &ProtoStreamObjectSource::RenderDuration; + (*result)["google.protobuf.DoubleValue"] = + &ProtoStreamObjectSource::RenderDouble; + (*result)["google.protobuf.FloatValue"] = + &ProtoStreamObjectSource::RenderFloat; + (*result)["google.protobuf.Int64Value"] = + &ProtoStreamObjectSource::RenderInt64; + (*result)["google.protobuf.UInt64Value"] = + &ProtoStreamObjectSource::RenderUInt64; + (*result)["google.protobuf.Int32Value"] = + &ProtoStreamObjectSource::RenderInt32; + (*result)["google.protobuf.UInt32Value"] = + &ProtoStreamObjectSource::RenderUInt32; + (*result)["google.protobuf.BoolValue"] = &ProtoStreamObjectSource::RenderBool; + (*result)["google.protobuf.StringValue"] = + &ProtoStreamObjectSource::RenderString; + (*result)["google.protobuf.BytesValue"] = + &ProtoStreamObjectSource::RenderBytes; + (*result)["google.protobuf.Any"] = &ProtoStreamObjectSource::RenderAny; + (*result)["google.protobuf.Struct"] = &ProtoStreamObjectSource::RenderStruct; + (*result)["google.protobuf.Value"] = + &ProtoStreamObjectSource::RenderStructValue; + (*result)["google.protobuf.ListValue"] = + &ProtoStreamObjectSource::RenderStructListValue; + (*result)["google.protobuf.FieldMask"] = + &ProtoStreamObjectSource::RenderFieldMask; + return result; +} + +// static +ProtoStreamObjectSource::TypeRenderer* +ProtoStreamObjectSource::FindTypeRenderer(const string& type_url) { + static hash_map* renderers = CreateRendererMap(); + return FindOrNull(*renderers, type_url); +} + +Status ProtoStreamObjectSource::RenderField( + const google::protobuf::Field* field, StringPiece field_name, + ObjectWriter* ow) const { + switch (field->kind()) { + case google::protobuf::Field_Kind_TYPE_BOOL: { + uint64 buffer64; + stream_->ReadVarint64(&buffer64); + ow->RenderBool(field_name, buffer64 != 0); + break; + } + case google::protobuf::Field_Kind_TYPE_INT32: { + uint32 buffer32; + stream_->ReadVarint32(&buffer32); + ow->RenderInt32(field_name, bit_cast(buffer32)); + break; + } + case google::protobuf::Field_Kind_TYPE_INT64: { + uint64 buffer64; + stream_->ReadVarint64(&buffer64); + ow->RenderInt64(field_name, bit_cast(buffer64)); + break; + } + case google::protobuf::Field_Kind_TYPE_UINT32: { + uint32 buffer32; + stream_->ReadVarint32(&buffer32); + ow->RenderUint32(field_name, bit_cast(buffer32)); + break; + } + case google::protobuf::Field_Kind_TYPE_UINT64: { + uint64 buffer64; + stream_->ReadVarint64(&buffer64); + ow->RenderUint64(field_name, bit_cast(buffer64)); + break; + } + case google::protobuf::Field_Kind_TYPE_SINT32: { + uint32 buffer32; + stream_->ReadVarint32(&buffer32); + ow->RenderInt32(field_name, WireFormatLite::ZigZagDecode32(buffer32)); + break; + } + case google::protobuf::Field_Kind_TYPE_SINT64: { + uint64 buffer64; + stream_->ReadVarint64(&buffer64); + ow->RenderInt64(field_name, WireFormatLite::ZigZagDecode64(buffer64)); + break; + } + case google::protobuf::Field_Kind_TYPE_SFIXED32: { + uint32 buffer32; + stream_->ReadLittleEndian32(&buffer32); + ow->RenderInt32(field_name, bit_cast(buffer32)); + break; + } + case google::protobuf::Field_Kind_TYPE_SFIXED64: { + uint64 buffer64; + stream_->ReadLittleEndian64(&buffer64); + ow->RenderInt64(field_name, bit_cast(buffer64)); + break; + } + case google::protobuf::Field_Kind_TYPE_FIXED32: { + uint32 buffer32; + stream_->ReadLittleEndian32(&buffer32); + ow->RenderUint32(field_name, bit_cast(buffer32)); + break; + } + case google::protobuf::Field_Kind_TYPE_FIXED64: { + uint64 buffer64; + stream_->ReadLittleEndian64(&buffer64); + ow->RenderUint64(field_name, bit_cast(buffer64)); + break; + } + case google::protobuf::Field_Kind_TYPE_FLOAT: { + uint32 buffer32; + stream_->ReadLittleEndian32(&buffer32); + ow->RenderFloat(field_name, bit_cast(buffer32)); + break; + } + case google::protobuf::Field_Kind_TYPE_DOUBLE: { + uint64 buffer64; + stream_->ReadLittleEndian64(&buffer64); + ow->RenderDouble(field_name, bit_cast(buffer64)); + break; + } + case google::protobuf::Field_Kind_TYPE_ENUM: { + uint32 buffer32; + stream_->ReadVarint32(&buffer32); + + // If the field represents an explicit NULL value, render null. + if (field->type_url() == kStructNullValueTypeUrl) { + ow->RenderNull(field_name); + break; + } + + // Get the nested enum type for this field. + // TODO(skarvaje): Avoid string manipulation. Find ways to speed this + // up. + const google::protobuf::Enum* en = typeinfo_->GetEnum(field->type_url()); + // Lookup the name of the enum, and render that. Skips unknown enums. + if (en != NULL) { + const google::protobuf::EnumValue* enum_value = + FindEnumValueByNumber(*en, buffer32); + if (enum_value != NULL) { + ow->RenderString(field_name, enum_value->name()); + } + } else { + GOOGLE_LOG(INFO) << "Unkown enum skipped: " << field->type_url(); + } + break; + } + case google::protobuf::Field_Kind_TYPE_STRING: { + uint32 buffer32; + string str; + stream_->ReadVarint32(&buffer32); // string size. + stream_->ReadString(&str, buffer32); + ow->RenderString(field_name, str); + break; + } + case google::protobuf::Field_Kind_TYPE_BYTES: { + uint32 buffer32; + stream_->ReadVarint32(&buffer32); // bytes size. + string value; + stream_->ReadString(&value, buffer32); + ow->RenderBytes(field_name, value); + break; + } + case google::protobuf::Field_Kind_TYPE_MESSAGE: { + uint32 buffer32; + stream_->ReadVarint32(&buffer32); // message length + int old_limit = stream_->PushLimit(buffer32); + // Get the nested message type for this field. + const google::protobuf::Type* type = + typeinfo_->GetType(field->type_url()); + if (type == NULL) { + return Status(util::error::INTERNAL, + StrCat("Invalid configuration. Could not find the type: ", + field->type_url())); + } + RETURN_IF_ERROR(WriteMessage(*type, field_name, 0, true, ow)); + if (!stream_->ConsumedEntireMessage()) { + return Status(util::error::INVALID_ARGUMENT, + "Nested protocol message not parsed in its entirety."); + } + stream_->PopLimit(old_limit); + break; + } + default: + break; + } + return Status::OK; +} + +// TODO(skarvaje): Fix this to avoid code duplication. +const string ProtoStreamObjectSource::ReadFieldValueAsString( + const google::protobuf::Field& field) const { + string result; + switch (field.kind()) { + case google::protobuf::Field_Kind_TYPE_BOOL: { + uint64 buffer64; + stream_->ReadVarint64(&buffer64); + result = buffer64 != 0 ? "true" : "false"; + break; + } + case google::protobuf::Field_Kind_TYPE_INT32: { + uint32 buffer32; + stream_->ReadVarint32(&buffer32); + result = SimpleItoa(bit_cast(buffer32)); + break; + } + case google::protobuf::Field_Kind_TYPE_INT64: { + uint64 buffer64; + stream_->ReadVarint64(&buffer64); + result = SimpleItoa(bit_cast(buffer64)); + break; + } + case google::protobuf::Field_Kind_TYPE_UINT32: { + uint32 buffer32; + stream_->ReadVarint32(&buffer32); + result = SimpleItoa(bit_cast(buffer32)); + break; + } + case google::protobuf::Field_Kind_TYPE_UINT64: { + uint64 buffer64; + stream_->ReadVarint64(&buffer64); + result = SimpleItoa(bit_cast(buffer64)); + break; + } + case google::protobuf::Field_Kind_TYPE_SINT32: { + uint32 buffer32; + stream_->ReadVarint32(&buffer32); + result = SimpleItoa(WireFormatLite::ZigZagDecode32(buffer32)); + break; + } + case google::protobuf::Field_Kind_TYPE_SINT64: { + uint64 buffer64; + stream_->ReadVarint64(&buffer64); + result = SimpleItoa(WireFormatLite::ZigZagDecode64(buffer64)); + break; + } + case google::protobuf::Field_Kind_TYPE_SFIXED32: { + uint32 buffer32; + stream_->ReadLittleEndian32(&buffer32); + result = SimpleItoa(bit_cast(buffer32)); + break; + } + case google::protobuf::Field_Kind_TYPE_SFIXED64: { + uint64 buffer64; + stream_->ReadLittleEndian64(&buffer64); + result = SimpleItoa(bit_cast(buffer64)); + break; + } + case google::protobuf::Field_Kind_TYPE_FIXED32: { + uint32 buffer32; + stream_->ReadLittleEndian32(&buffer32); + result = SimpleItoa(bit_cast(buffer32)); + break; + } + case google::protobuf::Field_Kind_TYPE_FIXED64: { + uint64 buffer64; + stream_->ReadLittleEndian64(&buffer64); + result = SimpleItoa(bit_cast(buffer64)); + break; + } + case google::protobuf::Field_Kind_TYPE_FLOAT: { + uint32 buffer32; + stream_->ReadLittleEndian32(&buffer32); + result = SimpleFtoa(bit_cast(buffer32)); + break; + } + case google::protobuf::Field_Kind_TYPE_DOUBLE: { + uint64 buffer64; + stream_->ReadLittleEndian64(&buffer64); + result = SimpleDtoa(bit_cast(buffer64)); + break; + } + case google::protobuf::Field_Kind_TYPE_ENUM: { + uint32 buffer32; + stream_->ReadVarint32(&buffer32); + // Get the nested enum type for this field. + // TODO(skarvaje): Avoid string manipulation. Find ways to speed this + // up. + const google::protobuf::Enum* en = typeinfo_->GetEnum(field.type_url()); + // Lookup the name of the enum, and render that. Skips unknown enums. + if (en != NULL) { + const google::protobuf::EnumValue* enum_value = + FindEnumValueByNumber(*en, buffer32); + if (enum_value != NULL) { + result = enum_value->name(); + } + } + break; + } + case google::protobuf::Field_Kind_TYPE_STRING: { + uint32 buffer32; + stream_->ReadVarint32(&buffer32); // string size. + stream_->ReadString(&result, buffer32); + break; + } + case google::protobuf::Field_Kind_TYPE_BYTES: { + uint32 buffer32; + stream_->ReadVarint32(&buffer32); // bytes size. + stream_->ReadString(&result, buffer32); + break; + } + default: + break; + } + return result; +} + +// Field is a map if it is a repeated message and it has an option "map_type". +// TODO(skarvaje): Consider pre-computing the IsMap() into Field directly. +bool ProtoStreamObjectSource::IsMap( + const google::protobuf::Field& field) const { + const google::protobuf::Type* field_type = + typeinfo_->GetType(field.type_url()); + + // TODO(xiaofeng): Unify option names. + return field.kind() == google::protobuf::Field_Kind_TYPE_MESSAGE && + (GetBoolOptionOrDefault(field_type->options(), + "google.protobuf.MessageOptions.map_entry", false) || + GetBoolOptionOrDefault(field_type->options(), "map_entry", false)); +} + +std::pair ProtoStreamObjectSource::ReadSecondsAndNanos( + const google::protobuf::Type& type) const { + uint64 seconds = 0; + uint32 nanos = 0; + uint32 tag = 0; + int64 signed_seconds = 0; + int64 signed_nanos = 0; + + for (tag = stream_->ReadTag(); tag != 0; tag = stream_->ReadTag()) { + const google::protobuf::Field* field = FindAndVerifyField(type, tag); + if (field == NULL) { + WireFormat::SkipField(stream_, tag, NULL); + continue; + } + // 'seconds' has field number of 1 and 'nanos' has field number 2 + // //google/protobuf/timestamp.proto & duration.proto + if (field->number() == 1) { + // read seconds + stream_->ReadVarint64(&seconds); + signed_seconds = bit_cast(seconds); + } else if (field->number() == 2) { + // read nanos + stream_->ReadVarint32(&nanos); + signed_nanos = bit_cast(nanos); + } + } + return std::pair(signed_seconds, signed_nanos); +} + +namespace { +// TODO(skarvaje): Speed this up by not doing a linear scan. +const google::protobuf::Field* FindFieldByNumber( + const google::protobuf::Type& type, int number) { + for (int i = 0; i < type.fields_size(); ++i) { + if (type.fields(i).number() == number) { + return &type.fields(i); + } + } + return NULL; +} + +// TODO(skarvaje): Replace FieldDescriptor by implementing IsTypePackable() +// using tech Field. +bool IsPackable(const google::protobuf::Field& field) { + return field.cardinality() == + google::protobuf::Field_Cardinality_CARDINALITY_REPEATED && + google::protobuf::FieldDescriptor::IsTypePackable( + static_cast(field.kind())); +} + +// TODO(skarvaje): Speed this up by not doing a linear scan. +const google::protobuf::EnumValue* FindEnumValueByNumber( + const google::protobuf::Enum& tech_enum, int number) { + for (int i = 0; i < tech_enum.enumvalue_size(); ++i) { + const google::protobuf::EnumValue& ev = tech_enum.enumvalue(i); + if (ev.number() == number) { + return &ev; + } + } + return NULL; +} + +// TODO(skarvaje): Look into optimizing this by not doing computation on +// double. +const string FormatNanos(uint32 nanos) { + const char* format = + (nanos % 1000 != 0) ? "%.9f" : (nanos % 1000000 != 0) ? "%.6f" : "%.3f"; + string formatted = + StringPrintf(format, static_cast(nanos) / kNanosPerSecond); + // remove the leading 0 before decimal. + return formatted.substr(1); +} +} // namespace + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/util/internal/protostream_objectsource.h b/src/google/protobuf/util/internal/protostream_objectsource.h new file mode 100644 index 00000000..4a4e6bbf --- /dev/null +++ b/src/google/protobuf/util/internal/protostream_objectsource.h @@ -0,0 +1,245 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_PROTOSTREAM_OBJECTSOURCE_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_PROTOSTREAM_OBJECTSOURCE_H__ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +namespace google { +namespace protobuf { +class Field; +class Type; +} // namespace protobuf + + +namespace protobuf { +namespace util { +namespace converter { + +class TypeInfo; + +// An ObjectSource that can parse a stream of bytes as a protocol buffer. +// This implementation uses a tech Type for tag lookup. +// +// Sample usage: (suppose input is: string proto) +// ArrayInputStream arr_stream(proto.data(), proto.size()); +// CodedInputStream in_stream(&arr_stream); +// ProtoStreamObjectSource os(&in_stream, /*ServiceTypeInfo*/ typeinfo, +// ); +// +// Status status = os.WriteTo(); +class LIBPROTOBUF_EXPORT ProtoStreamObjectSource : public ObjectSource { + public: + ProtoStreamObjectSource(google::protobuf::io::CodedInputStream* stream, + TypeResolver* type_resolver, + const google::protobuf::Type& type); + + virtual ~ProtoStreamObjectSource(); + + virtual util::Status NamedWriteTo(StringPiece name, ObjectWriter* ow) const; + + protected: + // Writes a proto2 Message to the ObjectWriter. When the given end_tag is + // found this method will complete, allowing it to be used for parsing both + // nested messages (end with 0) and nested groups (end with group end tag). + // The include_start_and_end parameter allows this method to be called when + // already inside of an object, and skip calling StartObject and EndObject. + virtual util::Status WriteMessage(const google::protobuf::Type& descriptor, + StringPiece name, const uint32 end_tag, + bool include_start_and_end, + ObjectWriter* ow) const; + + private: + ProtoStreamObjectSource(google::protobuf::io::CodedInputStream* stream, + TypeInfo* typeinfo, + const google::protobuf::Type& type); + // Function that renders a well known type with a modified behavior. + typedef util::Status (*TypeRenderer)(const ProtoStreamObjectSource*, + const google::protobuf::Type&, + StringPiece, ObjectWriter*); + + // Looks up a field and verify its consistency with wire type in tag. + const google::protobuf::Field* FindAndVerifyField( + const google::protobuf::Type& type, uint32 tag) const; + + // TODO(skarvaje): Mark these methods as non-const as they modify internal + // state (stream_). + // + // Renders a repeating field (packed or unpacked). + // Returns the next tag after reading all sequential repeating elements. The + // caller should use this tag before reading more tags from the stream. + util::StatusOr RenderList(const google::protobuf::Field* field, + StringPiece name, uint32 list_tag, + ObjectWriter* ow) const; + // Renders a NWP map. + // Returns the next tag after reading all map entries. The caller should use + // this tag before reading more tags from the stream. + util::StatusOr RenderMap(const google::protobuf::Field* field, + StringPiece name, uint32 list_tag, + ObjectWriter* ow) const; + + // Renders an entry in a map, advancing stream pointers appropriately. + util::Status RenderMapEntry(const google::protobuf::Type* type, + ObjectWriter* ow) const; + + // Renders a packed repeating field. A packed field is stored as: + // {tag length item1 item2 item3} instead of the less efficient + // {tag item1 tag item2 tag item3}. + util::Status RenderPacked(const google::protobuf::Field* field, + ObjectWriter* ow) const; + + // Equivalent of RenderPacked, but for map entries. + util::Status RenderPackedMapEntry(const google::protobuf::Type* type, + ObjectWriter* ow) const; + + // Renders a google.protobuf.Timestamp value to ObjectWriter + static util::Status RenderTimestamp(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, ObjectWriter* ow); + + // Renders a google.protobuf.Duration value to ObjectWriter + static util::Status RenderDuration(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, ObjectWriter* ow); + + // Following RenderTYPE functions render well known types in + // google/protobuf/wrappers.proto corresponding to TYPE. + static util::Status RenderDouble(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, ObjectWriter* ow); + static util::Status RenderFloat(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, ObjectWriter* ow); + static util::Status RenderInt64(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, ObjectWriter* ow); + static util::Status RenderUInt64(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, ObjectWriter* ow); + static util::Status RenderInt32(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, ObjectWriter* ow); + static util::Status RenderUInt32(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, ObjectWriter* ow); + static util::Status RenderBool(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, ObjectWriter* ow); + static util::Status RenderString(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, ObjectWriter* ow); + static util::Status RenderBytes(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, ObjectWriter* ow); + + // Renders a google.protobuf.Struct to ObjectWriter. + static util::Status RenderStruct(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, ObjectWriter* ow); + + // Helper to render google.protobuf.Struct's Value fields to ObjectWriter. + static util::Status RenderStructValue(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, ObjectWriter* ow); + + // Helper to render google.protobuf.Struct's ListValue fields to ObjectWriter. + static util::Status RenderStructListValue( + const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, ObjectWriter* ow); + + // Render the "Any" type. + static util::Status RenderAny(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, ObjectWriter* ow); + + // Render the "FieldMask" type. + static util::Status RenderFieldMask(const ProtoStreamObjectSource* os, + const google::protobuf::Type& type, + StringPiece name, ObjectWriter* ow); + + static hash_map* CreateRendererMap(); + static TypeRenderer* FindTypeRenderer(const string& type_url); + + // Renders a field value to the ObjectWriter. + util::Status RenderField(const google::protobuf::Field* field, + StringPiece field_name, ObjectWriter* ow) const; + + // Reads field value according to Field spec in 'field' and returns the read + // value as string. This only works for primitive datatypes (no message + // types). + const string ReadFieldValueAsString( + const google::protobuf::Field& field) const; + + // Utility function to detect proto maps. The 'field' MUST be repeated. + bool IsMap(const google::protobuf::Field& field) const; + + // Utility to read int64 and int32 values from a message type in stream_. + // Used for reading google.protobuf.Timestamp and Duration messages. + std::pair ReadSecondsAndNanos( + const google::protobuf::Type& type) const; + + // Input stream to read from. Ownership rests with the caller. + google::protobuf::io::CodedInputStream* stream_; + + // Type information for all the types used in the descriptor. Used to find + // google::protobuf::Type of nested messages/enums. + TypeInfo* typeinfo_; + // Whether this class owns the typeinfo_ object. If true the typeinfo_ object + // should be deleted in the destructor. + bool own_typeinfo_; + + // google::protobuf::Type of the message source. + const google::protobuf::Type& type_; + + GOOGLE_DISALLOW_IMPLICIT_CONSTRUCTORS(ProtoStreamObjectSource); +}; + +} // namespace converter +} // namespace util +} // namespace protobuf + +} // namespace google +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_PROTOSTREAM_OBJECTSOURCE_H__ diff --git a/src/google/protobuf/util/internal/protostream_objectsource_test.cc b/src/google/protobuf/util/internal/protostream_objectsource_test.cc new file mode 100644 index 00000000..630393f2 --- /dev/null +++ b/src/google/protobuf/util/internal/protostream_objectsource_test.cc @@ -0,0 +1,824 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include +#ifndef _SHARED_PTR_H +#include +#endif +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +using google::protobuf::Descriptor; +using google::protobuf::DescriptorPool; +using google::protobuf::FileDescriptorProto; +using google::protobuf::Message; +using google::protobuf::io::ArrayInputStream; +using google::protobuf::io::CodedInputStream; +using util::Status; +using google::protobuf::testing::Author; +using google::protobuf::testing::BadAuthor; +using google::protobuf::testing::BadNestedBook; +using google::protobuf::testing::Book; +using google::protobuf::testing::Book_Label; +using google::protobuf::testing::NestedBook; +using google::protobuf::testing::PackedPrimitive; +using google::protobuf::testing::Primitive; +using google::protobuf::testing::more_author; +using google::protobuf::testing::maps::MapOut; +using google::protobuf::testing::anys::AnyOut; +using google::protobuf::testing::anys::AnyM; +using google::protobuf::testing::FieldMaskTest; +using google::protobuf::testing::NestedFieldMask; +using google::protobuf::testing::structs::StructType; +using ::testing::_; + + +namespace { +string GetTypeUrl(const Descriptor* descriptor) { + return string(kTypeServiceBaseUrl) + "/" + descriptor->full_name(); +} +} // namespace + +class ProtostreamObjectSourceTest + : public ::testing::TestWithParam { + protected: + ProtostreamObjectSourceTest() : helper_(GetParam()), mock_(), ow_(&mock_) { + helper_.ResetTypeInfo(Book::descriptor()); + } + + virtual ~ProtostreamObjectSourceTest() {} + + void DoTest(const Message& msg, const Descriptor* descriptor) { + Status status = ExecuteTest(msg, descriptor); + EXPECT_EQ(Status::OK, status); + } + + Status ExecuteTest(const Message& msg, const Descriptor* descriptor) { + ostringstream oss; + msg.SerializePartialToOstream(&oss); + string proto = oss.str(); + ArrayInputStream arr_stream(proto.data(), proto.size()); + CodedInputStream in_stream(&arr_stream); + + google::protobuf::scoped_ptr os( + helper_.NewProtoSource(&in_stream, GetTypeUrl(descriptor))); + return os->WriteTo(&mock_); + } + + void PrepareExpectingObjectWriterForRepeatedPrimitive() { + ow_.StartObject("") + ->StartList("rep_fix32") + ->RenderUint32("", bit_cast(3201)) + ->RenderUint32("", bit_cast(0)) + ->RenderUint32("", bit_cast(3202)) + ->EndList() + ->StartList("rep_u32") + ->RenderUint32("", bit_cast(3203)) + ->RenderUint32("", bit_cast(0)) + ->EndList() + ->StartList("rep_i32") + ->RenderInt32("", 0) + ->RenderInt32("", 3204) + ->RenderInt32("", 3205) + ->EndList() + ->StartList("rep_sf32") + ->RenderInt32("", 3206) + ->RenderInt32("", 0) + ->EndList() + ->StartList("rep_s32") + ->RenderInt32("", 0) + ->RenderInt32("", 3207) + ->RenderInt32("", 3208) + ->EndList() + ->StartList("rep_fix64") + ->RenderUint64("", bit_cast(6401L)) + ->RenderUint64("", bit_cast(0L)) + ->EndList() + ->StartList("rep_u64") + ->RenderUint64("", bit_cast(0L)) + ->RenderUint64("", bit_cast(6402L)) + ->RenderUint64("", bit_cast(6403L)) + ->EndList() + ->StartList("rep_i64") + ->RenderInt64("", 6404L) + ->RenderInt64("", 0L) + ->EndList() + ->StartList("rep_sf64") + ->RenderInt64("", 0L) + ->RenderInt64("", 6405L) + ->RenderInt64("", 6406L) + ->EndList() + ->StartList("rep_s64") + ->RenderInt64("", 6407L) + ->RenderInt64("", 0L) + ->EndList() + ->StartList("rep_float") + ->RenderFloat("", 0.0f) + ->RenderFloat("", 32.1f) + ->RenderFloat("", 32.2f) + ->EndList() + ->StartList("rep_double") + ->RenderDouble("", 64.1L) + ->RenderDouble("", 0.0L) + ->EndList() + ->StartList("rep_bool") + ->RenderBool("", true) + ->RenderBool("", false) + ->EndList() + ->EndObject(); + } + + Primitive PrepareRepeatedPrimitive() { + Primitive primitive; + primitive.add_rep_fix32(3201); + primitive.add_rep_fix32(0); + primitive.add_rep_fix32(3202); + primitive.add_rep_u32(3203); + primitive.add_rep_u32(0); + primitive.add_rep_i32(0); + primitive.add_rep_i32(3204); + primitive.add_rep_i32(3205); + primitive.add_rep_sf32(3206); + primitive.add_rep_sf32(0); + primitive.add_rep_s32(0); + primitive.add_rep_s32(3207); + primitive.add_rep_s32(3208); + primitive.add_rep_fix64(6401L); + primitive.add_rep_fix64(0L); + primitive.add_rep_u64(0L); + primitive.add_rep_u64(6402L); + primitive.add_rep_u64(6403L); + primitive.add_rep_i64(6404L); + primitive.add_rep_i64(0L); + primitive.add_rep_sf64(0L); + primitive.add_rep_sf64(6405L); + primitive.add_rep_sf64(6406L); + primitive.add_rep_s64(6407L); + primitive.add_rep_s64(0L); + primitive.add_rep_float(0.0f); + primitive.add_rep_float(32.1f); + primitive.add_rep_float(32.2f); + primitive.add_rep_double(64.1L); + primitive.add_rep_double(0.0); + primitive.add_rep_bool(true); + primitive.add_rep_bool(false); + + PrepareExpectingObjectWriterForRepeatedPrimitive(); + return primitive; + } + + PackedPrimitive PreparePackedPrimitive() { + PackedPrimitive primitive; + primitive.add_rep_fix32(3201); + primitive.add_rep_fix32(0); + primitive.add_rep_fix32(3202); + primitive.add_rep_u32(3203); + primitive.add_rep_u32(0); + primitive.add_rep_i32(0); + primitive.add_rep_i32(3204); + primitive.add_rep_i32(3205); + primitive.add_rep_sf32(3206); + primitive.add_rep_sf32(0); + primitive.add_rep_s32(0); + primitive.add_rep_s32(3207); + primitive.add_rep_s32(3208); + primitive.add_rep_fix64(6401L); + primitive.add_rep_fix64(0L); + primitive.add_rep_u64(0L); + primitive.add_rep_u64(6402L); + primitive.add_rep_u64(6403L); + primitive.add_rep_i64(6404L); + primitive.add_rep_i64(0L); + primitive.add_rep_sf64(0L); + primitive.add_rep_sf64(6405L); + primitive.add_rep_sf64(6406L); + primitive.add_rep_s64(6407L); + primitive.add_rep_s64(0L); + primitive.add_rep_float(0.0f); + primitive.add_rep_float(32.1f); + primitive.add_rep_float(32.2f); + primitive.add_rep_double(64.1L); + primitive.add_rep_double(0.0); + primitive.add_rep_bool(true); + primitive.add_rep_bool(false); + + PrepareExpectingObjectWriterForRepeatedPrimitive(); + return primitive; + } + + testing::TypeInfoTestHelper helper_; + + ::testing::NiceMock mock_; + ExpectingObjectWriter ow_; +}; + +INSTANTIATE_TEST_CASE_P(DifferentTypeInfoSourceTest, + ProtostreamObjectSourceTest, + ::testing::Values( + testing::USE_TYPE_RESOLVER)); + +TEST_P(ProtostreamObjectSourceTest, EmptyMessage) { + Book empty; + ow_.StartObject("")->EndObject(); + DoTest(empty, Book::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, Primitives) { + Primitive primitive; + primitive.set_fix32(3201); + primitive.set_u32(3202); + primitive.set_i32(3203); + primitive.set_sf32(3204); + primitive.set_s32(3205); + primitive.set_fix64(6401L); + primitive.set_u64(6402L); + primitive.set_i64(6403L); + primitive.set_sf64(6404L); + primitive.set_s64(6405L); + primitive.set_str("String Value"); + primitive.set_bytes("Some Bytes"); + primitive.set_float_(32.1f); + primitive.set_double_(64.1L); + primitive.set_bool_(true); + + ow_.StartObject("") + ->RenderUint32("fix32", bit_cast(3201)) + ->RenderUint32("u32", bit_cast(3202)) + ->RenderInt32("i32", 3203) + ->RenderInt32("sf32", 3204) + ->RenderInt32("s32", 3205) + ->RenderUint64("fix64", bit_cast(6401L)) + ->RenderUint64("u64", bit_cast(6402L)) + ->RenderInt64("i64", 6403L) + ->RenderInt64("sf64", 6404L) + ->RenderInt64("s64", 6405L) + ->RenderString("str", "String Value") + ->RenderBytes("bytes", "Some Bytes") + ->RenderFloat("float", 32.1f) + ->RenderDouble("double", 64.1L) + ->RenderBool("bool", true) + ->EndObject(); + DoTest(primitive, Primitive::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, RepeatingPrimitives) { + Primitive primitive = PrepareRepeatedPrimitive(); + primitive.add_rep_str("String One"); + primitive.add_rep_str("String Two"); + primitive.add_rep_bytes("Some Bytes"); + + ow_.StartList("rep_str") + ->RenderString("", "String One") + ->RenderString("", "String Two") + ->EndList() + ->StartList("rep_bytes") + ->RenderBytes("", "Some Bytes") + ->EndList(); + DoTest(primitive, Primitive::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, NestedMessage) { + Author* author = new Author(); + author->set_id(101L); + author->set_name("Tolstoy"); + Book book; + book.set_title("My Book"); + book.set_allocated_author(author); + + ow_.StartObject("") + ->RenderString("title", "My Book") + ->StartObject("author") + ->RenderUint64("id", bit_cast(101L)) + ->RenderString("name", "Tolstoy") + ->EndObject() + ->EndObject(); + DoTest(book, Book::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, RepeatingField) { + Author author; + author.set_alive(false); + author.set_name("john"); + author.add_pseudonym("phil"); + author.add_pseudonym("bob"); + + ow_.StartObject("") + ->RenderBool("alive", false) + ->RenderString("name", "john") + ->StartList("pseudonym") + ->RenderString("", "phil") + ->RenderString("", "bob") + ->EndList() + ->EndObject(); + DoTest(author, Author::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, PackedRepeatingFields) { + DoTest(PreparePackedPrimitive(), PackedPrimitive::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, NonPackedPackableFieldsActuallyPacked) { + // Protostream is packed, but parse with non-packed Primitive. + DoTest(PreparePackedPrimitive(), Primitive::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, PackedPackableFieldNotActuallyPacked) { + // Protostream is not packed, but parse with PackedPrimitive. + DoTest(PrepareRepeatedPrimitive(), PackedPrimitive::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, BadAuthor) { + Author author; + author.set_alive(false); + author.set_name("john"); + author.set_id(1234L); + author.add_pseudonym("phil"); + author.add_pseudonym("bob"); + + ow_.StartObject("") + ->StartList("alive") + ->RenderBool("", false) + ->EndList() + ->StartList("name") + ->RenderUint64("", static_cast('j')) + ->RenderUint64("", static_cast('o')) + ->RenderUint64("", static_cast('h')) + ->RenderUint64("", static_cast('n')) + ->EndList() + ->RenderString("pseudonym", "phil") + ->RenderString("pseudonym", "bob") + ->EndObject(); + // Protostream created with Author, but parsed with BadAuthor. + DoTest(author, BadAuthor::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, NestedBookToBadNestedBook) { + Book* book = new Book(); + book->set_length(250); + book->set_published(2014L); + NestedBook nested; + nested.set_allocated_book(book); + + ow_.StartObject("") + ->StartList("book") + ->RenderUint32("", 24) // tag for field length (3 << 3) + ->RenderUint32("", 250) + ->RenderUint32("", 32) // tag for field published (4 << 3) + ->RenderUint32("", 2014) + ->EndList() + ->EndObject(); + // Protostream created with NestedBook, but parsed with BadNestedBook. + DoTest(nested, BadNestedBook::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, BadNestedBookToNestedBook) { + BadNestedBook nested; + nested.add_book(1); + nested.add_book(2); + nested.add_book(3); + nested.add_book(4); + nested.add_book(5); + nested.add_book(6); + nested.add_book(7); + + ow_.StartObject("")->StartObject("book")->EndObject()->EndObject(); + // Protostream created with BadNestedBook, but parsed with NestedBook. + DoTest(nested, NestedBook::descriptor()); +} + +TEST_P(ProtostreamObjectSourceTest, + LongRepeatedListDoesNotBreakIntoMultipleJsonLists) { + Book book; + + int repeat = 10000; + for (int i = 0; i < repeat; ++i) { + Book_Label* label = book.add_labels(); + label->set_key(StrCat("i", i)); + label->set_value(StrCat("v", i)); + } + + // Make sure StartList and EndList are called exactly once (see b/18227499 for + // problems when this doesn't happen) + EXPECT_CALL(mock_, StartList(_)).Times(1); + EXPECT_CALL(mock_, EndList()).Times(1); + + DoTest(book, Book::descriptor()); +} + +class ProtostreamObjectSourceMapsTest : public ProtostreamObjectSourceTest { + protected: + ProtostreamObjectSourceMapsTest() { + helper_.ResetTypeInfo(MapOut::descriptor()); + } +}; + +INSTANTIATE_TEST_CASE_P(DifferentTypeInfoSourceTest, + ProtostreamObjectSourceMapsTest, + ::testing::Values( + testing::USE_TYPE_RESOLVER)); + +// Tests JSON map. +// +// This is the example expected output. +// { +// "map1": { +// "key1": { +// "foo": "foovalue" +// }, +// "key2": { +// "foo": "barvalue" +// } +// }, +// "map2": { +// "nestedself": { +// "map1": { +// "nested_key1": { +// "foo": "nested_foo" +// } +// }, +// "bar": "nested_bar_string" +// } +// }, +// "map3": { +// "111": "one one one" +// }, +// "bar": "top bar" +// } +TEST_P(ProtostreamObjectSourceMapsTest, MapsTest) { + MapOut out; + (*out.mutable_map1())["key1"].set_foo("foovalue"); + (*out.mutable_map1())["key2"].set_foo("barvalue"); + + MapOut* nested_value = &(*out.mutable_map2())["nestedself"]; + (*nested_value->mutable_map1())["nested_key1"].set_foo("nested_foo"); + nested_value->set_bar("nested_bar_string"); + + (*out.mutable_map3())[111] = "one one one"; + + out.set_bar("top bar"); + + ow_.StartObject("") + ->StartObject("map1") + ->StartObject("key1") + ->RenderString("foo", "foovalue") + ->EndObject() + ->StartObject("key2") + ->RenderString("foo", "barvalue") + ->EndObject() + ->StartObject("map2") + ->StartObject("nestedself") + ->StartObject("map1") + ->StartObject("nested_key1") + ->RenderString("foo", "nested_foo") + ->EndObject() + ->EndObject() + ->RenderString("bar", "nested_bar_string") + ->EndObject() + ->EndObject() + ->StartObject("map3") + ->RenderString("111", "one one one") + ->EndObject() + ->EndObject() + ->RenderString("bar", "top bar") + ->EndObject(); + + DoTest(out, MapOut::descriptor()); +} + +class ProtostreamObjectSourceAnysTest : public ProtostreamObjectSourceTest { + protected: + ProtostreamObjectSourceAnysTest() { + helper_.ResetTypeInfo(AnyOut::descriptor(), + google::protobuf::Any::descriptor()); + } +}; + +INSTANTIATE_TEST_CASE_P(DifferentTypeInfoSourceTest, + ProtostreamObjectSourceAnysTest, + ::testing::Values( + testing::USE_TYPE_RESOLVER)); + +// Tests JSON any support. +// +// This is the example expected output. +// { +// "any": { +// "@type": "type.googleapis.com/google.protobuf.testing.anys.AnyM" +// "foo": "foovalue" +// } +// } +TEST_P(ProtostreamObjectSourceAnysTest, BasicAny) { + AnyOut out; + ::google::protobuf::Any* any = out.mutable_any(); + + AnyM m; + m.set_foo("foovalue"); + any->PackFrom(m); + + ow_.StartObject("") + ->StartObject("any") + ->RenderString("@type", + "type.googleapis.com/google.protobuf.testing.anys.AnyM") + ->RenderString("foo", "foovalue") + ->EndObject() + ->EndObject(); + + DoTest(out, AnyOut::descriptor()); +} + +TEST_P(ProtostreamObjectSourceAnysTest, RecursiveAny) { + AnyOut out; + ::google::protobuf::Any* any = out.mutable_any(); + any->set_type_url("type.googleapis.com/google.protobuf.Any"); + + ::google::protobuf::Any nested_any; + nested_any.set_type_url( + "type.googleapis.com/google.protobuf.testing.anys.AnyM"); + + AnyM m; + m.set_foo("foovalue"); + nested_any.set_value(m.SerializeAsString()); + + any->set_value(nested_any.SerializeAsString()); + + ow_.StartObject("") + ->StartObject("any") + ->RenderString("@type", "type.googleapis.com/google.protobuf.Any") + ->StartObject("value") + ->RenderString("@type", + "type.googleapis.com/google.protobuf.testing.anys.AnyM") + ->RenderString("foo", "foovalue") + ->EndObject() + ->EndObject() + ->EndObject(); + + DoTest(out, AnyOut::descriptor()); +} + +TEST_P(ProtostreamObjectSourceAnysTest, DoubleRecursiveAny) { + AnyOut out; + ::google::protobuf::Any* any = out.mutable_any(); + any->set_type_url("type.googleapis.com/google.protobuf.Any"); + + ::google::protobuf::Any nested_any; + nested_any.set_type_url("type.googleapis.com/google.protobuf.Any"); + + ::google::protobuf::Any second_nested_any; + second_nested_any.set_type_url( + "type.googleapis.com/google.protobuf.testing.anys.AnyM"); + + AnyM m; + m.set_foo("foovalue"); + second_nested_any.set_value(m.SerializeAsString()); + nested_any.set_value(second_nested_any.SerializeAsString()); + any->set_value(nested_any.SerializeAsString()); + + ow_.StartObject("") + ->StartObject("any") + ->RenderString("@type", "type.googleapis.com/google.protobuf.Any") + ->StartObject("value") + ->RenderString("@type", "type.googleapis.com/google.protobuf.Any") + ->StartObject("value") + ->RenderString("@type", + "type.googleapis.com/google.protobuf.testing.anys.AnyM") + ->RenderString("foo", "foovalue") + ->EndObject() + ->EndObject() + ->EndObject() + ->EndObject(); + + DoTest(out, AnyOut::descriptor()); +} + +TEST_P(ProtostreamObjectSourceAnysTest, EmptyAnyOutputsEmptyObject) { + AnyOut out; + out.mutable_any(); + + ow_.StartObject("")->StartObject("any")->EndObject()->EndObject(); + + DoTest(out, AnyOut::descriptor()); +} + +TEST_P(ProtostreamObjectSourceAnysTest, EmptyWithTypeAndNoValueOutputsType) { + AnyOut out; + out.mutable_any()->set_type_url("foo.googleapis.com/my.Type"); + + ow_.StartObject("") + ->StartObject("any") + ->RenderString("@type", "foo.googleapis.com/my.Type") + ->EndObject() + ->EndObject(); + + DoTest(out, AnyOut::descriptor()); +} + +TEST_P(ProtostreamObjectSourceAnysTest, MissingTypeUrlError) { + AnyOut out; + ::google::protobuf::Any* any = out.mutable_any(); + + AnyM m; + m.set_foo("foovalue"); + any->set_value(m.SerializeAsString()); + + // We start the "AnyOut" part and then fail when we hit the Any object. + ow_.StartObject(""); + + Status status = ExecuteTest(out, AnyOut::descriptor()); + EXPECT_EQ(util::error::INTERNAL, status.error_code()); +} + +TEST_P(ProtostreamObjectSourceAnysTest, UnknownTypeServiceError) { + AnyOut out; + ::google::protobuf::Any* any = out.mutable_any(); + any->set_type_url("foo.googleapis.com/my.own.Type"); + + AnyM m; + m.set_foo("foovalue"); + any->set_value(m.SerializeAsString()); + + // We start the "AnyOut" part and then fail when we hit the Any object. + ow_.StartObject(""); + + Status status = ExecuteTest(out, AnyOut::descriptor()); + EXPECT_EQ(util::error::INTERNAL, status.error_code()); +} + +TEST_P(ProtostreamObjectSourceAnysTest, UnknownTypeError) { + AnyOut out; + ::google::protobuf::Any* any = out.mutable_any(); + any->set_type_url("type.googleapis.com/unknown.Type"); + + AnyM m; + m.set_foo("foovalue"); + any->set_value(m.SerializeAsString()); + + // We start the "AnyOut" part and then fail when we hit the Any object. + ow_.StartObject(""); + + Status status = ExecuteTest(out, AnyOut::descriptor()); + EXPECT_EQ(util::error::INTERNAL, status.error_code()); +} + +class ProtostreamObjectSourceStructTest : public ProtostreamObjectSourceTest { + protected: + ProtostreamObjectSourceStructTest() { + helper_.ResetTypeInfo(StructType::descriptor(), + google::protobuf::Struct::descriptor()); + } +}; + +INSTANTIATE_TEST_CASE_P(DifferentTypeInfoSourceTest, + ProtostreamObjectSourceStructTest, + ::testing::Values( + testing::USE_TYPE_RESOLVER)); + +// Tests struct +// +// "object": { +// "k1": 123, +// "k2": true +// } +TEST_P(ProtostreamObjectSourceStructTest, StructRenderSuccess) { + StructType out; + google::protobuf::Struct* s = out.mutable_object(); + s->mutable_fields()->operator[]("k1").set_number_value(123); + s->mutable_fields()->operator[]("k2").set_bool_value(true); + + ow_.StartObject("") + ->StartObject("object") + ->RenderDouble("k1", 123) + ->RenderBool("k2", true) + ->EndObject() + ->EndObject(); + + DoTest(out, StructType::descriptor()); +} + +TEST_P(ProtostreamObjectSourceStructTest, MissingValueSkipsField) { + StructType out; + google::protobuf::Struct* s = out.mutable_object(); + s->mutable_fields()->operator[]("k1"); + + ow_.StartObject("")->StartObject("object")->EndObject()->EndObject(); + + DoTest(out, StructType::descriptor()); +} + +class ProtostreamObjectSourceFieldMaskTest + : public ProtostreamObjectSourceTest { + protected: + ProtostreamObjectSourceFieldMaskTest() { + helper_.ResetTypeInfo(FieldMaskTest::descriptor(), + google::protobuf::FieldMask::descriptor()); + } +}; + +INSTANTIATE_TEST_CASE_P(DifferentTypeInfoSourceTest, + ProtostreamObjectSourceFieldMaskTest, + ::testing::Values( + testing::USE_TYPE_RESOLVER)); + +TEST_P(ProtostreamObjectSourceFieldMaskTest, FieldMaskRenderSuccess) { + FieldMaskTest out; + out.set_id("1"); + out.mutable_single_mask()->add_paths("path1"); + out.mutable_single_mask()->add_paths("snake_case_path2"); + ::google::protobuf::FieldMask* mask = out.add_repeated_mask(); + mask->add_paths("path3"); + mask = out.add_repeated_mask(); + mask->add_paths("snake_case_path4"); + mask->add_paths("path5"); + NestedFieldMask* nested = out.add_nested_mask(); + nested->set_data("data"); + nested->mutable_single_mask()->add_paths("nested.path1"); + nested->mutable_single_mask()->add_paths("nested_field.snake_case_path2"); + mask = nested->add_repeated_mask(); + mask->add_paths("nested_field.path3"); + mask->add_paths("nested.snake_case_path4"); + mask = nested->add_repeated_mask(); + mask->add_paths("nested.path5"); + mask = nested->add_repeated_mask(); + mask->add_paths( + "snake_case.map_field[\"map_key_should_be_ignored\"].nested_snake_case." + "map_field[\"map_key_sho\\\"uld_be_ignored\"]"); + + ow_.StartObject("") + ->RenderString("id", "1") + ->RenderString("single_mask", "path1,snakeCasePath2") + ->StartList("repeated_mask") + ->RenderString("", "path3") + ->RenderString("", "snakeCasePath4,path5") + ->EndList() + ->StartList("nested_mask") + ->StartObject("") + ->RenderString("data", "data") + ->RenderString("single_mask", "nested.path1,nestedField.snakeCasePath2") + ->StartList("repeated_mask") + ->RenderString("", "nestedField.path3,nested.snakeCasePath4") + ->RenderString("", "nested.path5") + ->RenderString("", + "snakeCase.mapField[\"map_key_should_be_ignored\"]." + "nestedSnakeCase.mapField[\"map_key_sho\\\"uld_be_" + "ignored\"]") + ->EndList() + ->EndObject() + ->EndList() + ->EndObject(); + + DoTest(out, FieldMaskTest::descriptor()); +} + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/util/internal/protostream_objectwriter.cc b/src/google/protobuf/util/internal/protostream_objectwriter.cc new file mode 100644 index 00000000..f9ddbf32 --- /dev/null +++ b/src/google/protobuf/util/internal/protostream_objectwriter.cc @@ -0,0 +1,1557 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +using google::protobuf::internal::WireFormatLite; +using google::protobuf::io::CodedOutputStream; +using util::error::INVALID_ARGUMENT; +using util::Status; +using util::StatusOr; + + +ProtoStreamObjectWriter::ProtoStreamObjectWriter( + TypeResolver* type_resolver, const google::protobuf::Type& type, + strings::ByteSink* output, ErrorListener* listener) + : master_type_(type), + typeinfo_(TypeInfo::NewTypeInfo(type_resolver)), + own_typeinfo_(true), + done_(false), + element_(NULL), + size_insert_(), + output_(output), + buffer_(), + adapter_(&buffer_), + stream_(new CodedOutputStream(&adapter_)), + listener_(listener), + invalid_depth_(0), + tracker_(new ObjectLocationTracker()) {} + +ProtoStreamObjectWriter::ProtoStreamObjectWriter( + TypeInfo* typeinfo, const google::protobuf::Type& type, + strings::ByteSink* output, ErrorListener* listener) + : master_type_(type), + typeinfo_(typeinfo), + own_typeinfo_(false), + done_(false), + element_(NULL), + size_insert_(), + output_(output), + buffer_(), + adapter_(&buffer_), + stream_(new CodedOutputStream(&adapter_)), + listener_(listener), + invalid_depth_(0), + tracker_(new ObjectLocationTracker()) {} + +ProtoStreamObjectWriter::~ProtoStreamObjectWriter() { + // Cleanup explicitly in order to avoid destructor stack overflow when input + // is deeply nested. + while (element_ != NULL) { + element_.reset(element_->pop()); + } + if (own_typeinfo_) { + delete typeinfo_; + } +} + +namespace { + +// Writes an INT32 field, including tag to the stream. +inline Status WriteInt32(int field_number, const DataPiece& data, + CodedOutputStream* stream) { + StatusOr i32 = data.ToInt32(); + if (i32.ok()) { + WireFormatLite::WriteInt32(field_number, i32.ValueOrDie(), stream); + } + return i32.status(); +} + +// writes an SFIXED32 field, including tag, to the stream. +inline Status WriteSFixed32(int field_number, const DataPiece& data, + CodedOutputStream* stream) { + StatusOr i32 = data.ToInt32(); + if (i32.ok()) { + WireFormatLite::WriteSFixed32(field_number, i32.ValueOrDie(), stream); + } + return i32.status(); +} + +// Writes an SINT32 field, including tag, to the stream. +inline Status WriteSInt32(int field_number, const DataPiece& data, + CodedOutputStream* stream) { + StatusOr i32 = data.ToInt32(); + if (i32.ok()) { + WireFormatLite::WriteSInt32(field_number, i32.ValueOrDie(), stream); + } + return i32.status(); +} + +// Writes a FIXED32 field, including tag, to the stream. +inline Status WriteFixed32(int field_number, const DataPiece& data, + CodedOutputStream* stream) { + StatusOr u32 = data.ToUint32(); + if (u32.ok()) { + WireFormatLite::WriteFixed32(field_number, u32.ValueOrDie(), stream); + } + return u32.status(); +} + +// Writes a UINT32 field, including tag, to the stream. +inline Status WriteUInt32(int field_number, const DataPiece& data, + CodedOutputStream* stream) { + StatusOr u32 = data.ToUint32(); + if (u32.ok()) { + WireFormatLite::WriteUInt32(field_number, u32.ValueOrDie(), stream); + } + return u32.status(); +} + +// Writes an INT64 field, including tag, to the stream. +inline Status WriteInt64(int field_number, const DataPiece& data, + CodedOutputStream* stream) { + StatusOr i64 = data.ToInt64(); + if (i64.ok()) { + WireFormatLite::WriteInt64(field_number, i64.ValueOrDie(), stream); + } + return i64.status(); +} + +// Writes an SFIXED64 field, including tag, to the stream. +inline Status WriteSFixed64(int field_number, const DataPiece& data, + CodedOutputStream* stream) { + StatusOr i64 = data.ToInt64(); + if (i64.ok()) { + WireFormatLite::WriteSFixed64(field_number, i64.ValueOrDie(), stream); + } + return i64.status(); +} + +// Writes an SINT64 field, including tag, to the stream. +inline Status WriteSInt64(int field_number, const DataPiece& data, + CodedOutputStream* stream) { + StatusOr i64 = data.ToInt64(); + if (i64.ok()) { + WireFormatLite::WriteSInt64(field_number, i64.ValueOrDie(), stream); + } + return i64.status(); +} + +// Writes a FIXED64 field, including tag, to the stream. +inline Status WriteFixed64(int field_number, const DataPiece& data, + CodedOutputStream* stream) { + StatusOr u64 = data.ToUint64(); + if (u64.ok()) { + WireFormatLite::WriteFixed64(field_number, u64.ValueOrDie(), stream); + } + return u64.status(); +} + +// Writes a UINT64 field, including tag, to the stream. +inline Status WriteUInt64(int field_number, const DataPiece& data, + CodedOutputStream* stream) { + StatusOr u64 = data.ToUint64(); + if (u64.ok()) { + WireFormatLite::WriteUInt64(field_number, u64.ValueOrDie(), stream); + } + return u64.status(); +} + +// Writes a DOUBLE field, including tag, to the stream. +inline Status WriteDouble(int field_number, const DataPiece& data, + CodedOutputStream* stream) { + StatusOr d = data.ToDouble(); + if (d.ok()) { + WireFormatLite::WriteDouble(field_number, d.ValueOrDie(), stream); + } + return d.status(); +} + +// Writes a FLOAT field, including tag, to the stream. +inline Status WriteFloat(int field_number, const DataPiece& data, + CodedOutputStream* stream) { + StatusOr f = data.ToFloat(); + if (f.ok()) { + WireFormatLite::WriteFloat(field_number, f.ValueOrDie(), stream); + } + return f.status(); +} + +// Writes a BOOL field, including tag, to the stream. +inline Status WriteBool(int field_number, const DataPiece& data, + CodedOutputStream* stream) { + StatusOr b = data.ToBool(); + if (b.ok()) { + WireFormatLite::WriteBool(field_number, b.ValueOrDie(), stream); + } + return b.status(); +} + +// Writes a BYTES field, including tag, to the stream. +inline Status WriteBytes(int field_number, const DataPiece& data, + CodedOutputStream* stream) { + StatusOr c = data.ToBytes(); + if (c.ok()) { + WireFormatLite::WriteBytes(field_number, c.ValueOrDie(), stream); + } + return c.status(); +} + +// Writes a STRING field, including tag, to the stream. +inline Status WriteString(int field_number, const DataPiece& data, + CodedOutputStream* stream) { + StatusOr s = data.ToString(); + if (s.ok()) { + WireFormatLite::WriteString(field_number, s.ValueOrDie(), stream); + } + return s.status(); +} + +// Writes an ENUM field, including tag, to the stream. +inline Status WriteEnum(int field_number, const DataPiece& data, + const google::protobuf::Enum* enum_type, + CodedOutputStream* stream) { + StatusOr e = data.ToEnum(enum_type); + if (e.ok()) { + WireFormatLite::WriteEnum(field_number, e.ValueOrDie(), stream); + } + return e.status(); +} + +// Given a google::protobuf::Type, returns the set of all required fields. +std::set GetRequiredFields( + const google::protobuf::Type& type) { + std::set required; + for (int i = 0; i < type.fields_size(); i++) { + const google::protobuf::Field& field = type.fields(i); + if (field.cardinality() == + google::protobuf::Field_Cardinality_CARDINALITY_REQUIRED) { + required.insert(&field); + } + } + return required; +} + +// Utility method to split a string representation of Timestamp or Duration and +// return the parts. +void SplitSecondsAndNanos(StringPiece input, StringPiece* seconds, + StringPiece* nanos) { + size_t idx = input.rfind('.'); + if (idx != string::npos) { + *seconds = input.substr(0, idx); + *nanos = input.substr(idx + 1); + } else { + *seconds = input; + *nanos = StringPiece(); + } +} + +} // namespace + +ProtoStreamObjectWriter::AnyWriter::AnyWriter(ProtoStreamObjectWriter* parent) + : parent_(parent), + ow_(), + invalid_(false), + data_(), + output_(&data_), + depth_(0), + has_injected_value_message_(false) {} + +ProtoStreamObjectWriter::AnyWriter::~AnyWriter() {} + +void ProtoStreamObjectWriter::AnyWriter::StartObject(StringPiece name) { + ++depth_; + // If an object writer is absent, that means we have not called StartAny() + // before reaching here. This is an invalid state. StartAny() gets called + // whenever we see an "@type" being rendered (see AnyWriter::RenderDataPiece). + if (ow_ == NULL) { + // Make sure we are not already in an invalid state. This avoids making + // multiple unnecessary InvalidValue calls. + if (!invalid_) { + parent_->InvalidValue("Any", + StrCat("Missing or invalid @type for any field in ", + parent_->master_type_.name())); + invalid_ = true; + } + } else if (!has_injected_value_message_ || depth_ != 1 || name != "value") { + // We don't propagate to ow_ StartObject("value") calls for nested Anys or + // Struct at depth 1 as they are nested one level deep with an injected + // "value" field. + ow_->StartObject(name); + } +} + +bool ProtoStreamObjectWriter::AnyWriter::EndObject() { + --depth_; + // As long as depth_ >= 0, we know we haven't reached the end of Any. + // Propagate these EndObject() calls to the contained ow_. If we are in a + // nested Any or Struct type, ignore the second to last EndObject call (depth_ + // == -1) + if (ow_ != NULL && (!has_injected_value_message_ || depth_ >= 0)) { + ow_->EndObject(); + } + // A negative depth_ implies that we have reached the end of Any + // object. Now we write out its contents. + if (depth_ < 0) { + WriteAny(); + return false; + } + return true; +} + +void ProtoStreamObjectWriter::AnyWriter::StartList(StringPiece name) { + ++depth_; + // We expect ow_ to be present as this call only makes sense inside an Any. + if (ow_ == NULL) { + if (!invalid_) { + parent_->InvalidValue("Any", + StrCat("Missing or invalid @type for any field in ", + parent_->master_type_.name())); + invalid_ = true; + } + } else { + ow_->StartList(name); + } +} + +void ProtoStreamObjectWriter::AnyWriter::EndList() { + --depth_; + if (depth_ < 0) { + GOOGLE_LOG(DFATAL) << "Mismatched EndList found, should not be possible"; + depth_ = 0; + } + // We don't write an error on the close, only on the open + if (ow_ != NULL) { + ow_->EndList(); + } +} + +void ProtoStreamObjectWriter::AnyWriter::RenderDataPiece( + StringPiece name, const DataPiece& value) { + // Start an Any only at depth_ 0. Other RenderDataPiece calls with "@type" + // should go to the contained ow_ as they indicate nested Anys. + if (depth_ == 0 && ow_ == NULL && name == "@type") { + StartAny(value); + } else if (ow_ == NULL) { + if (!invalid_) { + parent_->InvalidValue("Any", + StrCat("Missing or invalid @type for any field in ", + parent_->master_type_.name())); + invalid_ = true; + } + } else { + // Check to see if the data needs to be rendered with well-known-type + // renderer. + const TypeRenderer* type_renderer = + FindTypeRenderer(GetFullTypeWithUrl(ow_->master_type_.name())); + if (type_renderer) { + // TODO(rikka): Don't just ignore the util::Status object! + (*type_renderer)(ow_.get(), value); + } else { + ow_->RenderDataPiece(name, value); + } + } +} + +void ProtoStreamObjectWriter::AnyWriter::StartAny(const DataPiece& value) { + // Figure out the type url. This is a copy-paste from WriteString but we also + // need the value, so we can't just call through to that. + if (value.type() == DataPiece::TYPE_STRING) { + type_url_ = value.str().ToString(); + } else { + StatusOr s = value.ToString(); + if (!s.ok()) { + parent_->InvalidValue("String", s.status().error_message()); + invalid_ = true; + return; + } + type_url_ = s.ValueOrDie(); + } + // Resolve the type url, and report an error if we failed to resolve it. + StatusOr resolved_type = + parent_->typeinfo_->ResolveTypeUrl(type_url_); + if (!resolved_type.ok()) { + parent_->InvalidValue("Any", resolved_type.status().error_message()); + invalid_ = true; + return; + } + // At this point, type is never null. + const google::protobuf::Type* type = resolved_type.ValueOrDie(); + + // If this is the case of an Any in an Any or Struct in an Any, we need to + // expect a StartObject call with "value" while we're at depth_ 0, which we + // should ignore (not propagate to our nested object writer). We also need to + // ignore the second-to-last EndObject call, and not propagate that either. + if (type->name() == kAnyType || type->name() == kStructType) { + has_injected_value_message_ = true; + } + + // Create our object writer and initialize it with the first StartObject + // call. + ow_.reset(new ProtoStreamObjectWriter(parent_->typeinfo_, *type, &output_, + parent_->listener_)); + ow_->StartObject(""); +} + +void ProtoStreamObjectWriter::AnyWriter::WriteAny() { + if (ow_ == NULL) { + // If we had no object writer, we never got any content, so just return + // immediately, which is equivalent to writing an empty Any. + return; + } + // Render the type_url and value fields directly to the stream. + // type_url has tag 1 and value has tag 2. + WireFormatLite::WriteString(1, type_url_, parent_->stream_.get()); + if (!data_.empty()) { + WireFormatLite::WriteBytes(2, data_, parent_->stream_.get()); + } +} + +ProtoStreamObjectWriter::ProtoElement::ProtoElement( + TypeInfo* typeinfo, const google::protobuf::Type& type, + ProtoStreamObjectWriter* enclosing) + : BaseElement(NULL), + ow_(enclosing), + any_(), + field_(NULL), + typeinfo_(typeinfo), + type_(type), + required_fields_(GetRequiredFields(type)), + is_repeated_type_(false), + size_index_(-1), + array_index_(-1), + element_type_(GetElementType(type_)) { + if (element_type_ == ANY) { + any_.reset(new AnyWriter(ow_)); + } +} + +ProtoStreamObjectWriter::ProtoElement::ProtoElement( + ProtoStreamObjectWriter::ProtoElement* parent, + const google::protobuf::Field* field, const google::protobuf::Type& type, + ElementType element_type) + : BaseElement(parent), + ow_(this->parent()->ow_), + any_(), + field_(field), + typeinfo_(this->parent()->typeinfo_), + type_(type), + is_repeated_type_(element_type == ProtoElement::LIST || + element_type == ProtoElement::STRUCT_LIST || + element_type == ProtoElement::MAP || + element_type == ProtoElement::STRUCT_MAP), + size_index_(!is_repeated_type_ && + field->kind() == + google::protobuf::Field_Kind_TYPE_MESSAGE + ? ow_->size_insert_.size() + : -1), + array_index_(is_repeated_type_ ? 0 : -1), + element_type_(element_type) { + if (!is_repeated_type_) { + if (field->cardinality() == + google::protobuf::Field_Cardinality_CARDINALITY_REPEATED) { + // Update array_index_ if it is an explicit list. + if (this->parent()->array_index_ >= 0) this->parent()->array_index_++; + } else { + this->parent()->RegisterField(field); + } + if (field->kind() == google::protobuf::Field_Kind_TYPE_MESSAGE) { + required_fields_ = GetRequiredFields(type_); + int start_pos = ow_->stream_->ByteCount(); + // length of serialized message is the final buffer position minus + // starting buffer position, plus length adjustments for size fields + // of any nested messages. We start with -start_pos here, so we only + // need to add the final buffer position to it at the end. + SizeInfo info = {start_pos, -start_pos}; + ow_->size_insert_.push_back(info); + } + } + if (element_type == ANY) { + any_.reset(new AnyWriter(ow_)); + } +} + +ProtoStreamObjectWriter::ProtoElement* +ProtoStreamObjectWriter::ProtoElement::pop() { + // Calls the registered error listener for any required field(s) not yet + // seen. + for (set::iterator it = + required_fields_.begin(); + it != required_fields_.end(); ++it) { + ow_->MissingField((*it)->name()); + } + // Computes the total number of proto bytes used by a message, also adjusts + // the size of all parent messages by the length of this size field. + // If size_index_ < 0, this is not a message, so no size field is added. + if (size_index_ >= 0) { + // Add the final buffer position to compute the total length of this + // serialized message. The stored value (before this addition) already + // contains the total length of the size fields of all nested messages + // minus the initial buffer position. + ow_->size_insert_[size_index_].size += ow_->stream_->ByteCount(); + // Calculate the length required to serialize the size field of the + // message, and propagate this additional size information upward to + // all enclosing messages. + int size = ow_->size_insert_[size_index_].size; + int length = CodedOutputStream::VarintSize32(size); + for (ProtoElement* e = parent(); e != NULL; e = e->parent()) { + // Only nested messages have size field, lists do not have size field. + if (e->size_index_ >= 0) { + ow_->size_insert_[e->size_index_].size += length; + } + } + } + return BaseElement::pop(); +} + +void ProtoStreamObjectWriter::ProtoElement::RegisterField( + const google::protobuf::Field* field) { + if (!required_fields_.empty() && + field->cardinality() == + google::protobuf::Field_Cardinality_CARDINALITY_REQUIRED) { + required_fields_.erase(field); + } +} + +string ProtoStreamObjectWriter::ProtoElement::ToString() const { + if (parent() == NULL) return ""; + string loc = parent()->ToString(); + if (field_->cardinality() != + google::protobuf::Field_Cardinality_CARDINALITY_REPEATED || + parent()->field_ != field_) { + string name = field_->name(); + int i = 0; + while (i < name.size() && (ascii_isalnum(name[i]) || name[i] == '_')) ++i; + if (i > 0 && i == name.size()) { // safe field name + if (loc.empty()) { + loc = name; + } else { + StrAppend(&loc, ".", name); + } + } else { + StrAppend(&loc, "[\"", CEscape(name), "\"]"); + } + } + if (field_->cardinality() == + google::protobuf::Field_Cardinality_CARDINALITY_REPEATED && + array_index_ > 0) { + StrAppend(&loc, "[", array_index_ - 1, "]"); + } + return loc.empty() ? "." : loc; +} + +inline void ProtoStreamObjectWriter::InvalidName(StringPiece unknown_name, + StringPiece message) { + listener_->InvalidName(location(), ToSnakeCase(unknown_name), message); +} + +inline void ProtoStreamObjectWriter::InvalidValue(StringPiece type_name, + StringPiece value) { + listener_->InvalidValue(location(), type_name, value); +} + +inline void ProtoStreamObjectWriter::MissingField(StringPiece missing_name) { + listener_->MissingField(location(), missing_name); +} + +ProtoStreamObjectWriter* ProtoStreamObjectWriter::StartObject( + StringPiece name) { + // Starting the root message. Create the root ProtoElement and return. + if (element_ == NULL) { + if (!name.empty()) { + InvalidName(name, "Root element should not be named."); + } + element_.reset(new ProtoElement(typeinfo_, master_type_, this)); + + // If master type is a special type that needs extra values to be written to + // stream, we write those values. + if (master_type_.name() == kStructType) { + StartStruct(NULL); + } else if (master_type_.name() == kStructValueType) { + // We got a StartObject call with google.protobuf.Value field. This means + // we are starting an object within google.protobuf.Value type. The only + // object within that type is a struct type. So start a struct. + const google::protobuf::Field* field = StartStructValueInStruct(NULL); + StartStruct(field); + } + return this; + } + + const google::protobuf::Field* field = NULL; + if (element_ != NULL && element_->IsAny()) { + element_->any()->StartObject(name); + return this; + } else if (element_ != NULL && + (element_->IsMap() || element_->IsStructMap())) { + field = StartMapEntry(name); + if (element_->IsStructMapEntry()) { + // If the top element is a map entry, this means we are starting another + // struct within a struct. + field = StartStructValueInStruct(field); + } + } else if (element_ != NULL && element_->IsStructList()) { + // If the top element is a list, then we are starting a list field within a + // struct. + field = Lookup(name); + field = StartStructValueInStruct(field); + } else { + field = BeginNamed(name, false); + } + if (field == NULL) { + return this; + } + + const google::protobuf::Type* type = LookupType(field); + if (type == NULL) { + ++invalid_depth_; + InvalidName(name, + StrCat("Missing descriptor for field: ", field->type_url())); + return this; + } + + if (field->type_url() == GetFullTypeWithUrl(kStructType)) { + // Start a struct object. + StartStruct(field); + } else if (field->type_url() == GetFullTypeWithUrl(kStructValueType)) { + // We got a StartObject call with google.protobuf.Value field. This means we + // are starting an object within google.protobuf.Value type. The only object + // within that type is a struct type. So start a struct. + field = StartStructValueInStruct(field); + StartStruct(field); + } else if (field->type_url() == GetFullTypeWithUrl(kAnyType)) { + // Begin an Any. We can't do the real work till we get the @type field. + WriteTag(*field); + element_.reset( + new ProtoElement(element_.release(), field, *type, ProtoElement::ANY)); + } else if (IsMap(*field)) { + // Begin a map. + // A map is triggered by a StartObject() call if the current field has a map + // type. Map values are written to proto in a manner detailed in comments + // above StartMapEntry() function. + element_.reset( + new ProtoElement(element_.release(), field, *type, ProtoElement::MAP)); + } else { + WriteTag(*field); + element_.reset(new ProtoElement(element_.release(), field, *type, + ProtoElement::MESSAGE)); + } + return this; +} + +// Proto3 maps are represented on the wire as a message with +// "key" and a "value". +// +// For example, the syntax: +// map map_field = N; +// +// is represented as: +// message MapFieldEntry { +// option map_entry = true; // marks the map construct in the descriptor +// +// key_type key = 1; +// value_type value = 2; +// } +// repeated MapFieldEntry map_field = N; +// +// See go/proto3-maps for more information. +const google::protobuf::Field* ProtoStreamObjectWriter::StartMapEntry( + StringPiece name) { + // top of stack is already a map field + const google::protobuf::Field* field = element_->field(); + const google::protobuf::Type& type = element_->type(); + // If we come from a regular map, use MAP_ENTRY or if we come from a struct, + // use STRUCT_MAP_ENTRY. These values are used later in StartObject/StartList + // or RenderDataPiece for making appropriate decisions. + ProtoElement::ElementType element_type = element_->IsStructMap() + ? ProtoElement::STRUCT_MAP_ENTRY + : ProtoElement::MAP_ENTRY; + WriteTag(*field); + element_.reset( + new ProtoElement(element_.release(), field, type, element_type)); + RenderDataPiece("key", DataPiece(name)); + return BeginNamed("value", false); +} + +// Starts a google.protobuf.Struct. +// 'field' represents a field in a message of type google.protobuf.Struct. A +// struct contains a map with name 'fields'. This function starts this map as +// well. +// When 'field' is NULL, it means that the top level message is of struct +// type. +void ProtoStreamObjectWriter::StartStruct( + const google::protobuf::Field* field) { + const google::protobuf::Type* type = NULL; + if (field) { + type = LookupType(field); + WriteTag(*field); + element_.reset(new ProtoElement(element_.release(), field, *type, + ProtoElement::STRUCT)); + } + const google::protobuf::Field* struct_field = BeginNamed("fields", false); + + if (!struct_field) { + // It is a programmatic error if this happens. Log an error. + GOOGLE_LOG(ERROR) << "Invalid internal state. Cannot find 'fields' within " + << (field ? field->type_url() : "google.protobuf.Struct"); + return; + } + + type = LookupType(struct_field); + element_.reset(new ProtoElement(element_.release(), struct_field, *type, + ProtoElement::STRUCT_MAP)); +} + +// Starts a "struct_value" within struct.proto's google.protobuf.Value type. +// 'field' should be of the type google.protobuf.Value. +// Returns the field identifying "struct_value" within the given field. +// +// If field is NULL, then we are starting struct_value at the top-level, in +// this case skip writing any tag information for the passed field. +const google::protobuf::Field* +ProtoStreamObjectWriter::StartStructValueInStruct( + const google::protobuf::Field* field) { + if (field) { + const google::protobuf::Type* type = LookupType(field); + WriteTag(*field); + element_.reset(new ProtoElement(element_.release(), field, *type, + ProtoElement::STRUCT_VALUE)); + } + return BeginNamed("struct_value", false); +} + +// Starts a "list_value" within struct.proto's google.protobuf.Value type. +// 'field' should be of the type google.protobuf.Value. +// Returns the field identifying "list_value" within the given field. +// +// If field is NULL, then we are starting list_value at the top-level, in +// this case skip writing any tag information for the passed field. +const google::protobuf::Field* ProtoStreamObjectWriter::StartListValueInStruct( + const google::protobuf::Field* field) { + if (field) { + const google::protobuf::Type* type = LookupType(field); + WriteTag(*field); + element_.reset(new ProtoElement(element_.release(), field, *type, + ProtoElement::STRUCT_VALUE)); + } + const google::protobuf::Field* list_value = BeginNamed("list_value", false); + + if (!list_value) { + // It is a programmatic error if this happens. Log an error. + GOOGLE_LOG(ERROR) << "Invalid internal state. Cannot find 'list_value' within " + << (field ? field->type_url() : "google.protobuf.Value"); + return field; + } + + return StartRepeatedValuesInListValue(list_value); +} + +// Starts the repeated "values" field in struct.proto's +// google.protobuf.ListValue type. 'field' should be of type +// google.protobuf.ListValue. +// +// If field is NULL, then we are starting ListValue at the top-level, in +// this case skip writing any tag information for the passed field. +const google::protobuf::Field* +ProtoStreamObjectWriter::StartRepeatedValuesInListValue( + const google::protobuf::Field* field) { + if (field) { + const google::protobuf::Type* type = LookupType(field); + WriteTag(*field); + element_.reset(new ProtoElement(element_.release(), field, *type, + ProtoElement::STRUCT_LIST_VALUE)); + } + return BeginNamed("values", true); +} + +void ProtoStreamObjectWriter::SkipElements() { + if (element_ == NULL) return; + + ProtoElement::ElementType element_type = element_->element_type(); + while (element_type == ProtoElement::STRUCT || + element_type == ProtoElement::STRUCT_LIST_VALUE || + element_type == ProtoElement::STRUCT_VALUE || + element_type == ProtoElement::STRUCT_MAP_ENTRY || + element_type == ProtoElement::MAP_ENTRY) { + element_.reset(element_->pop()); + element_type = + element_ != NULL ? element_->element_type() : ProtoElement::MESSAGE; + } +} + +ProtoStreamObjectWriter* ProtoStreamObjectWriter::EndObject() { + if (invalid_depth_ > 0) { + --invalid_depth_; + return this; + } + if (element_ != NULL && element_->IsAny()) { + if (element_->any()->EndObject()) { + return this; + } + } + if (element_ != NULL) { + element_.reset(element_->pop()); + } + + // Skip sentinel elements added to keep track of new proto3 types - map, + // struct. + SkipElements(); + + // If ending the root element, + // then serialize the full message with calculated sizes. + if (element_ == NULL) { + WriteRootMessage(); + } + return this; +} + +ProtoStreamObjectWriter* ProtoStreamObjectWriter::StartList(StringPiece name) { + const google::protobuf::Field* field = NULL; + // Since we cannot have a top-level repeated item in protobuf, the only way + // element_ can be null when here is when we start a top-level list + // google.protobuf.ListValue. + if (element_ == NULL) { + if (!name.empty()) { + InvalidName(name, "Root element should not be named."); + } + element_.reset(new ProtoElement(typeinfo_, master_type_, this)); + + // If master type is a special type that needs extra values to be written to + // stream, we write those values. + if (master_type_.name() == kStructValueType) { + // We got a StartList with google.protobuf.Value master type. This means + // we have to start the "list_value" within google.protobuf.Value. + field = StartListValueInStruct(NULL); + } else if (master_type_.name() == kStructListValueType) { + // We got a StartList with google.protobuf.ListValue master type. This + // means we have to start the "values" within google.protobuf.ListValue. + field = StartRepeatedValuesInListValue(NULL); + } + + // field is NULL when master_type_ is anything other than + // google.protobuf.Value or google.protobuf.ListValue. + if (field) { + const google::protobuf::Type* type = LookupType(field); + element_.reset(new ProtoElement(element_.release(), field, *type, + ProtoElement::STRUCT_LIST)); + } + return this; + } + + if (element_->IsAny()) { + element_->any()->StartList(name); + return this; + } + // The type of element we push to stack. + ProtoElement::ElementType element_type = ProtoElement::LIST; + + // Check if we need to start a map. This can heppen when there is either a map + // or a struct type within a list. + if (element_->IsMap() || element_->IsStructMap()) { + field = StartMapEntry(name); + if (field == NULL) return this; + + if (element_->IsStructMapEntry()) { + // If the top element is a map entry, this means we are starting a list + // within a struct or a map. + // An example sequence of calls would be + // StartObject -> StartList + field = StartListValueInStruct(field); + if (field == NULL) return this; + } + + element_type = ProtoElement::STRUCT_LIST; + } else if (element_->IsStructList()) { + // If the top element is a STRUCT_LIST, this means we are starting a list + // within the current list (inside a struct). + // An example call sequence would be + // StartObject -> StartList -> StartList + // with StartObject starting a struct. + + // Lookup the last list type in element stack as we are adding an element of + // the same type. + field = Lookup(name); + if (field == NULL) return this; + + field = StartListValueInStruct(field); + if (field == NULL) return this; + + element_type = ProtoElement::STRUCT_LIST; + } else { + // Lookup field corresponding to 'name'. If it is a google.protobuf.Value + // or google.protobuf.ListValue type, then StartList is a valid call, start + // this list. + // We cannot use Lookup() here as it will produce InvalidName() error if the + // field is not found. We do not want to error here as it would cause us to + // report errors twice, once here and again later in BeginNamed() call. + // Also we ignore if the field is not found here as it is caught later. + field = typeinfo_->FindField(&element_->type(), name); + + // It is an error to try to bind to map, which behind the scenes is a list. + if (field && IsMap(*field)) { + // Push field to stack for error location tracking & reporting. + element_.reset(new ProtoElement(element_.release(), field, + *LookupType(field), + ProtoElement::MESSAGE)); + InvalidValue("Map", "Cannot bind a list to map."); + ++invalid_depth_; + element_->pop(); + return this; + } + + if (field && field->type_url() == GetFullTypeWithUrl(kStructValueType)) { + // There are 2 cases possible: + // a. g.p.Value is repeated + // b. g.p.Value is not repeated + // + // For case (a), the StartList should bind to the repeated g.p.Value. + // For case (b), the StartList should bind to g.p.ListValue within the + // g.p.Value. + // + // This means, for case (a), we treat it just like any other repeated + // message, except we would apply an appropriate element_type so future + // Start or Render calls are routed appropriately. + if (field->cardinality() != + google::protobuf::Field_Cardinality_CARDINALITY_REPEATED) { + field = StartListValueInStruct(field); + } + element_type = ProtoElement::STRUCT_LIST; + } else if (field && + field->type_url() == GetFullTypeWithUrl(kStructListValueType)) { + // We got a StartList with google.protobuf.ListValue master type. This + // means we have to start the "values" within google.protobuf.ListValue. + field = StartRepeatedValuesInListValue(field); + } else { + // If no special types are to be bound, fall back to normal processing of + // StartList. + field = BeginNamed(name, true); + } + if (field == NULL) return this; + } + + const google::protobuf::Type* type = LookupType(field); + if (type == NULL) { + ++invalid_depth_; + InvalidName(name, + StrCat("Missing descriptor for field: ", field->type_url())); + return this; + } + + element_.reset( + new ProtoElement(element_.release(), field, *type, element_type)); + return this; +} + +ProtoStreamObjectWriter* ProtoStreamObjectWriter::EndList() { + if (invalid_depth_ > 0) { + --invalid_depth_; + } else if (element_ != NULL) { + if (element_->IsAny()) { + element_->any()->EndList(); + } else { + element_.reset(element_->pop()); + // Skip sentinel elements added to keep track of new proto3 types - map, + // struct. + SkipElements(); + } + } + + // When element_ is NULL, we have reached the root message type. Write out + // the bytes. + if (element_ == NULL) { + WriteRootMessage(); + } + return this; +} + +Status ProtoStreamObjectWriter::RenderStructValue(ProtoStreamObjectWriter* ow, + const DataPiece& data) { + string struct_field_name; + switch (data.type()) { + // Our JSON parser parses numbers as either int64, uint64, or double. + case DataPiece::TYPE_INT64: + case DataPiece::TYPE_UINT64: + case DataPiece::TYPE_DOUBLE: { + struct_field_name = "number_value"; + break; + } + case DataPiece::TYPE_STRING: { + struct_field_name = "string_value"; + break; + } + case DataPiece::TYPE_BOOL: { + struct_field_name = "bool_value"; + break; + } + case DataPiece::TYPE_NULL: { + struct_field_name = "null_value"; + break; + } + default: { + return Status(INVALID_ARGUMENT, + "Invalid struct data type. Only number, string, boolean or " + "null values are supported."); + } + } + ow->RenderDataPiece(struct_field_name, data); + return Status::OK; +} + +Status ProtoStreamObjectWriter::RenderTimestamp(ProtoStreamObjectWriter* ow, + const DataPiece& data) { + if (data.type() != DataPiece::TYPE_STRING) { + return Status(INVALID_ARGUMENT, + StrCat("Invalid data type for timestamp, value is ", + data.ValueAsStringOrDefault(""))); + } + + StringPiece value(data.str()); + + int64 seconds; + int32 nanos; + if (!::google::protobuf::internal::ParseTime(value.ToString(), &seconds, + &nanos)) { + return Status(INVALID_ARGUMENT, StrCat("Invalid time format: ", value)); + } + + + ow->RenderDataPiece("seconds", DataPiece(seconds)); + ow->RenderDataPiece("nanos", DataPiece(nanos)); + return Status::OK; +} + +static inline util::Status RenderOneFieldPath(ProtoStreamObjectWriter* ow, + StringPiece path) { + ow->RenderDataPiece("paths", + DataPiece(ConvertFieldMaskPath(path, &ToSnakeCase))); + return Status::OK; +} + +Status ProtoStreamObjectWriter::RenderFieldMask(ProtoStreamObjectWriter* ow, + const DataPiece& data) { + if (data.type() != DataPiece::TYPE_STRING) { + return Status(INVALID_ARGUMENT, + StrCat("Invalid data type for field mask, value is ", + data.ValueAsStringOrDefault(""))); + } + + // TODO(tsun): figure out how to do proto descriptor based snake case + // conversions as much as possible. Because ToSnakeCase sometimes returns the + // wrong value. + google::protobuf::scoped_ptr > callback( + NewPermanentCallback(&RenderOneFieldPath, ow)); + return DecodeCompactFieldMaskPaths(data.str(), callback.get()); +} + +Status ProtoStreamObjectWriter::RenderDuration(ProtoStreamObjectWriter* ow, + const DataPiece& data) { + if (data.type() != DataPiece::TYPE_STRING) { + return Status(INVALID_ARGUMENT, + StrCat("Invalid data type for duration, value is ", + data.ValueAsStringOrDefault(""))); + } + + StringPiece value(data.str()); + + if (!value.ends_with("s")) { + return Status(INVALID_ARGUMENT, + "Illegal duration format; duration must end with 's'"); + } + value = value.substr(0, value.size() - 1); + int sign = 1; + if (value.starts_with("-")) { + sign = -1; + value = value.substr(1); + } + + StringPiece s_secs, s_nanos; + SplitSecondsAndNanos(value, &s_secs, &s_nanos); + uint64 unsigned_seconds; + if (!safe_strtou64(s_secs, &unsigned_seconds)) { + return Status(INVALID_ARGUMENT, + "Invalid duration format, failed to parse seconds"); + } + + double d_nanos = 0; + if (!safe_strtod("0." + s_nanos.ToString(), &d_nanos)) { + return Status(INVALID_ARGUMENT, + "Invalid duration format, failed to parse nanos seconds"); + } + + int32 nanos = sign * static_cast(d_nanos * kNanosPerSecond); + int64 seconds = sign * unsigned_seconds; + + if (seconds > kMaxSeconds || seconds < kMinSeconds || + nanos <= -kNanosPerSecond || nanos >= kNanosPerSecond) { + return Status(INVALID_ARGUMENT, "Duration value exceeds limits"); + } + + ow->RenderDataPiece("seconds", DataPiece(seconds)); + ow->RenderDataPiece("nanos", DataPiece(nanos)); + return Status::OK; +} + +Status ProtoStreamObjectWriter::RenderWrapperType(ProtoStreamObjectWriter* ow, + const DataPiece& data) { + ow->RenderDataPiece("value", data); + return Status::OK; +} + +ProtoStreamObjectWriter* ProtoStreamObjectWriter::RenderDataPiece( + StringPiece name, const DataPiece& data) { + Status status; + if (invalid_depth_ > 0) return this; + if (element_ != NULL && element_->IsAny()) { + element_->any()->RenderDataPiece(name, data); + return this; + } + + const google::protobuf::Field* field = NULL; + string type_url; + bool is_map_entry = false; + if (element_ == NULL) { + type_url = GetFullTypeWithUrl(master_type_.name()); + } else { + if (element_->IsMap() || element_->IsStructMap()) { + is_map_entry = true; + field = StartMapEntry(name); + } else { + field = Lookup(name); + } + if (field == NULL) { + return this; + } + type_url = field->type_url(); + } + + // Check if there are any well known type renderers available for type_url. + const TypeRenderer* type_renderer = FindTypeRenderer(type_url); + if (type_renderer != NULL) { + // Push the current element to stack so lookups in type_renderer will + // find the fields. We do an EndObject soon after, which pops this. This is + // safe because all well-known types are messages. + if (element_ == NULL) { + element_.reset(new ProtoElement(typeinfo_, master_type_, this)); + } else { + if (field) { + WriteTag(*field); + const google::protobuf::Type* type = LookupType(field); + element_.reset(new ProtoElement(element_.release(), field, *type, + ProtoElement::MESSAGE)); + } + } + status = (*type_renderer)(this, data); + if (!status.ok()) { + InvalidValue(type_url, + StrCat("Field '", name, "', ", status.error_message())); + } + EndObject(); + return this; + } else if (element_ == NULL) { // no message type found at root + element_.reset(new ProtoElement(typeinfo_, master_type_, this)); + InvalidName(name, "Root element must be a message."); + return this; + } + + if (field == NULL) { + return this; + } + const google::protobuf::Type* type = LookupType(field); + if (type == NULL) { + InvalidName(name, + StrCat("Missing descriptor for field: ", field->type_url())); + return this; + } + + // Whether we should pop at the end. Set to true if the data field is a + // message type, which can happen in case of struct values. + bool should_pop = false; + + RenderSimpleDataPiece(*field, *type, data); + + if (should_pop && element_ != NULL) { + element_.reset(element_->pop()); + } + + if (is_map_entry) { + // Ending map is the same as ending an object. + EndObject(); + } + return this; +} + +void ProtoStreamObjectWriter::RenderSimpleDataPiece( + const google::protobuf::Field& field, const google::protobuf::Type& type, + const DataPiece& data) { + // If we are rendering explicit null values and the backend proto field is not + // of the google.protobuf.NullType type, we do nothing. + if (data.type() == DataPiece::TYPE_NULL && + field.type_url() != kStructNullValueTypeUrl) { + return; + } + + // Pushing a ProtoElement and then pop it off at the end for 2 purposes: + // error location reporting and required field accounting. + element_.reset(new ProtoElement(element_.release(), &field, type, + ProtoElement::MESSAGE)); + + // Make sure that field represents a simple data type. + if (field.kind() == google::protobuf::Field_Kind_TYPE_UNKNOWN || + field.kind() == google::protobuf::Field_Kind_TYPE_MESSAGE) { + InvalidValue(field.type_url().empty() + ? google::protobuf::Field_Kind_Name(field.kind()) + : field.type_url(), + data.ValueAsStringOrDefault("")); + return; + } + + Status status; + switch (field.kind()) { + case google::protobuf::Field_Kind_TYPE_INT32: { + status = WriteInt32(field.number(), data, stream_.get()); + break; + } + case google::protobuf::Field_Kind_TYPE_SFIXED32: { + status = WriteSFixed32(field.number(), data, stream_.get()); + break; + } + case google::protobuf::Field_Kind_TYPE_SINT32: { + status = WriteSInt32(field.number(), data, stream_.get()); + break; + } + case google::protobuf::Field_Kind_TYPE_FIXED32: { + status = WriteFixed32(field.number(), data, stream_.get()); + break; + } + case google::protobuf::Field_Kind_TYPE_UINT32: { + status = WriteUInt32(field.number(), data, stream_.get()); + break; + } + case google::protobuf::Field_Kind_TYPE_INT64: { + status = WriteInt64(field.number(), data, stream_.get()); + break; + } + case google::protobuf::Field_Kind_TYPE_SFIXED64: { + status = WriteSFixed64(field.number(), data, stream_.get()); + break; + } + case google::protobuf::Field_Kind_TYPE_SINT64: { + status = WriteSInt64(field.number(), data, stream_.get()); + break; + } + case google::protobuf::Field_Kind_TYPE_FIXED64: { + status = WriteFixed64(field.number(), data, stream_.get()); + break; + } + case google::protobuf::Field_Kind_TYPE_UINT64: { + status = WriteUInt64(field.number(), data, stream_.get()); + break; + } + case google::protobuf::Field_Kind_TYPE_DOUBLE: { + status = WriteDouble(field.number(), data, stream_.get()); + break; + } + case google::protobuf::Field_Kind_TYPE_FLOAT: { + status = WriteFloat(field.number(), data, stream_.get()); + break; + } + case google::protobuf::Field_Kind_TYPE_BOOL: { + status = WriteBool(field.number(), data, stream_.get()); + break; + } + case google::protobuf::Field_Kind_TYPE_BYTES: { + status = WriteBytes(field.number(), data, stream_.get()); + break; + } + case google::protobuf::Field_Kind_TYPE_STRING: { + status = WriteString(field.number(), data, stream_.get()); + break; + } + case google::protobuf::Field_Kind_TYPE_ENUM: { + status = WriteEnum(field.number(), data, + typeinfo_->GetEnum(field.type_url()), stream_.get()); + break; + } + default: // TYPE_GROUP or TYPE_MESSAGE + status = Status(INVALID_ARGUMENT, data.ToString().ValueOrDie()); + } + if (!status.ok()) { + InvalidValue(google::protobuf::Field_Kind_Name(field.kind()), + status.error_message()); + } + element_.reset(element_->pop()); +} + +// Map of functions that are responsible for rendering well known type +// represented by the key. +hash_map* +ProtoStreamObjectWriter::CreateRendererMap() { + google::protobuf::scoped_ptr > + result(new hash_map()); + (*result)["type.googleapis.com/google.protobuf.Timestamp"] = + &ProtoStreamObjectWriter::RenderTimestamp; + (*result)["type.googleapis.com/google.protobuf.Duration"] = + &ProtoStreamObjectWriter::RenderDuration; + (*result)["type.googleapis.com/google.protobuf.FieldMask"] = + &ProtoStreamObjectWriter::RenderFieldMask; + (*result)["type.googleapis.com/google.protobuf.Double"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*result)["type.googleapis.com/google.protobuf.Float"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*result)["type.googleapis.com/google.protobuf.Int64"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*result)["type.googleapis.com/google.protobuf.UInt64"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*result)["type.googleapis.com/google.protobuf.Int32"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*result)["type.googleapis.com/google.protobuf.UInt32"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*result)["type.googleapis.com/google.protobuf.Bool"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*result)["type.googleapis.com/google.protobuf.String"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*result)["type.googleapis.com/google.protobuf.Bytes"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*result)["type.googleapis.com/google.protobuf.DoubleValue"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*result)["type.googleapis.com/google.protobuf.FloatValue"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*result)["type.googleapis.com/google.protobuf.Int64Value"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*result)["type.googleapis.com/google.protobuf.UInt64Value"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*result)["type.googleapis.com/google.protobuf.Int32Value"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*result)["type.googleapis.com/google.protobuf.UInt32Value"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*result)["type.googleapis.com/google.protobuf.BoolValue"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*result)["type.googleapis.com/google.protobuf.StringValue"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*result)["type.googleapis.com/google.protobuf.BytesValue"] = + &ProtoStreamObjectWriter::RenderWrapperType; + (*result)["type.googleapis.com/google.protobuf.Value"] = + &ProtoStreamObjectWriter::RenderStructValue; + return result.release(); +} + +ProtoStreamObjectWriter::TypeRenderer* +ProtoStreamObjectWriter::FindTypeRenderer(const string& type_url) { + static hash_map* renderers = CreateRendererMap(); + return FindOrNull(*renderers, type_url); +} + +ProtoStreamObjectWriter::ProtoElement::ElementType +ProtoStreamObjectWriter::GetElementType(const google::protobuf::Type& type) { + if (type.name() == kAnyType) { + return ProtoElement::ANY; + } else if (type.name() == kStructType) { + return ProtoElement::STRUCT; + } else if (type.name() == kStructValueType) { + return ProtoElement::STRUCT_VALUE; + } else if (type.name() == kStructListValueType) { + return ProtoElement::STRUCT_LIST_VALUE; + } else { + return ProtoElement::MESSAGE; + } +} + +const google::protobuf::Field* ProtoStreamObjectWriter::BeginNamed( + StringPiece name, bool is_list) { + if (invalid_depth_ > 0) { + ++invalid_depth_; + return NULL; + } + const google::protobuf::Field* field = Lookup(name); + if (field == NULL) { + ++invalid_depth_; + // InvalidName() already called in Lookup(). + return NULL; + } + if (is_list && + field->cardinality() != + google::protobuf::Field_Cardinality_CARDINALITY_REPEATED) { + ++invalid_depth_; + InvalidName(name, "Proto field is not repeating, cannot start list."); + return NULL; + } + return field; +} + +const google::protobuf::Field* ProtoStreamObjectWriter::Lookup( + StringPiece unnormalized_name) { + ProtoElement* e = element(); + if (e == NULL) { + InvalidName(unnormalized_name, "Root element must be a message."); + return NULL; + } + if (unnormalized_name.empty()) { + // Objects in repeated field inherit the same field descriptor. + if (e->field() == NULL) { + InvalidName(unnormalized_name, "Proto fields must have a name."); + } else if (e->field()->cardinality() != + google::protobuf::Field_Cardinality_CARDINALITY_REPEATED) { + InvalidName(unnormalized_name, "Proto fields must have a name."); + return NULL; + } + return e->field(); + } + const google::protobuf::Field* field = + typeinfo_->FindField(&e->type(), unnormalized_name); + if (field == NULL) InvalidName(unnormalized_name, "Cannot find field."); + return field; +} + +const google::protobuf::Type* ProtoStreamObjectWriter::LookupType( + const google::protobuf::Field* field) { + return (field->kind() == google::protobuf::Field_Kind_TYPE_MESSAGE + ? typeinfo_->GetType(field->type_url()) + : &element_->type()); +} + +// Looks up the oneof struct field based on the data type. +StatusOr +ProtoStreamObjectWriter::LookupStructField(DataPiece::Type type) { + const google::protobuf::Field* field = NULL; + switch (type) { + // Our JSON parser parses numbers as either int64, uint64, or double. + case DataPiece::TYPE_INT64: + case DataPiece::TYPE_UINT64: + case DataPiece::TYPE_DOUBLE: { + field = Lookup("number_value"); + break; + } + case DataPiece::TYPE_STRING: { + field = Lookup("string_value"); + break; + } + case DataPiece::TYPE_BOOL: { + field = Lookup("bool_value"); + break; + } + case DataPiece::TYPE_NULL: { + field = Lookup("null_value"); + break; + } + default: { return Status(INVALID_ARGUMENT, "Invalid struct data type"); } + } + if (field == NULL) { + return Status(INVALID_ARGUMENT, "Could not lookup struct field"); + } + return field; +} + +void ProtoStreamObjectWriter::WriteRootMessage() { + GOOGLE_DCHECK(!done_); + int curr_pos = 0; + // Calls the destructor of CodedOutputStream to remove any uninitialized + // memory from the Cord before we read it. + stream_.reset(NULL); + const void* data; + int length; + google::protobuf::io::ArrayInputStream input_stream(buffer_.data(), buffer_.size()); + while (input_stream.Next(&data, &length)) { + if (length == 0) continue; + int num_bytes = length; + // Write up to where we need to insert the size field. + // The number of bytes we may write is the smaller of: + // - the current fragment size + // - the distance to the next position where a size field needs to be + // inserted. + if (!size_insert_.empty() && + size_insert_.front().pos - curr_pos < num_bytes) { + num_bytes = size_insert_.front().pos - curr_pos; + } + output_->Append(static_cast(data), num_bytes); + if (num_bytes < length) { + input_stream.BackUp(length - num_bytes); + } + curr_pos += num_bytes; + // Insert the size field. + // size_insert_.front(): the next pair to be written. + // size_insert_.front().pos: position of the size field. + // size_insert_.front().size: the size (integer) to be inserted. + if (!size_insert_.empty() && curr_pos == size_insert_.front().pos) { + // Varint32 occupies at most 10 bytes. + uint8 insert_buffer[10]; + uint8* insert_buffer_pos = CodedOutputStream::WriteVarint32ToArray( + size_insert_.front().size, insert_buffer); + output_->Append(reinterpret_cast(insert_buffer), + insert_buffer_pos - insert_buffer); + size_insert_.pop_front(); + } + } + output_->Flush(); + stream_.reset(new CodedOutputStream(&adapter_)); + done_ = true; +} + +bool ProtoStreamObjectWriter::IsMap(const google::protobuf::Field& field) { + if (field.type_url().empty() || + field.kind() != google::protobuf::Field_Kind_TYPE_MESSAGE || + field.cardinality() != + google::protobuf::Field_Cardinality_CARDINALITY_REPEATED) { + return false; + } + const google::protobuf::Type* field_type = + typeinfo_->GetType(field.type_url()); + + return GetBoolOptionOrDefault(field_type->options(), + "google.protobuf.MessageOptions.map_entry", false); +} + +void ProtoStreamObjectWriter::WriteTag(const google::protobuf::Field& field) { + WireFormatLite::WireType wire_type = WireFormatLite::WireTypeForFieldType( + static_cast(field.kind())); + stream_->WriteTag(WireFormatLite::MakeTag(field.number(), wire_type)); +} + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/util/internal/protostream_objectwriter.h b/src/google/protobuf/util/internal/protostream_objectwriter.h new file mode 100644 index 00000000..f17278b4 --- /dev/null +++ b/src/google/protobuf/util/internal/protostream_objectwriter.h @@ -0,0 +1,455 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_PROTOSTREAM_OBJECTWRITER_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_PROTOSTREAM_OBJECTWRITER_H__ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace google { +namespace protobuf { +namespace io { +class CodedOutputStream; +} // namespace io +} // namespace protobuf + + +namespace protobuf { +class Type; +class Field; +} // namespace protobuf + + +namespace protobuf { +namespace util { +namespace converter { + +class ObjectLocationTracker; + +// An ObjectWriter that can write protobuf bytes directly from writer events. +// +// It also supports streaming. +class LIBPROTOBUF_EXPORT ProtoStreamObjectWriter : public StructuredObjectWriter { + public: + // Constructor. Does not take ownership of any parameter passed in. + ProtoStreamObjectWriter(TypeResolver* type_resolver, + const google::protobuf::Type& type, + strings::ByteSink* output, ErrorListener* listener); + virtual ~ProtoStreamObjectWriter(); + + // ObjectWriter methods. + virtual ProtoStreamObjectWriter* StartObject(StringPiece name); + virtual ProtoStreamObjectWriter* EndObject(); + virtual ProtoStreamObjectWriter* StartList(StringPiece name); + virtual ProtoStreamObjectWriter* EndList(); + virtual ProtoStreamObjectWriter* RenderBool(StringPiece name, + const bool value) { + return RenderDataPiece(name, DataPiece(value)); + } + virtual ProtoStreamObjectWriter* RenderInt32(StringPiece name, + const int32 value) { + return RenderDataPiece(name, DataPiece(value)); + } + virtual ProtoStreamObjectWriter* RenderUint32(StringPiece name, + const uint32 value) { + return RenderDataPiece(name, DataPiece(value)); + } + virtual ProtoStreamObjectWriter* RenderInt64(StringPiece name, + const int64 value) { + return RenderDataPiece(name, DataPiece(value)); + } + virtual ProtoStreamObjectWriter* RenderUint64(StringPiece name, + const uint64 value) { + return RenderDataPiece(name, DataPiece(value)); + } + virtual ProtoStreamObjectWriter* RenderDouble(StringPiece name, + const double value) { + return RenderDataPiece(name, DataPiece(value)); + } + virtual ProtoStreamObjectWriter* RenderFloat(StringPiece name, + const float value) { + return RenderDataPiece(name, DataPiece(value)); + } + virtual ProtoStreamObjectWriter* RenderString(StringPiece name, + StringPiece value) { + return RenderDataPiece(name, DataPiece(value)); + } + virtual ProtoStreamObjectWriter* RenderBytes(StringPiece name, + StringPiece value) { + return RenderDataPiece(name, DataPiece(value, false)); + } + virtual ProtoStreamObjectWriter* RenderNull(StringPiece name) { + return RenderDataPiece(name, DataPiece::NullData()); + } + + // Renders a DataPiece 'value' into a field whose wire type is determined + // from the given field 'name'. + ProtoStreamObjectWriter* RenderDataPiece(StringPiece name, + const DataPiece& value); + + // Returns the location tracker to use for tracking locations for errors. + const LocationTrackerInterface& location() { + return element_ != NULL ? *element_ : *tracker_; + } + + // When true, we finished writing to output a complete message. + bool done() const { return done_; } + + private: + // Function that renders a well known type with modified behavior. + typedef util::Status (*TypeRenderer)(ProtoStreamObjectWriter*, + const DataPiece&); + + // Handles writing Anys out using nested object writers and the like. + class LIBPROTOBUF_EXPORT AnyWriter { + public: + explicit AnyWriter(ProtoStreamObjectWriter* parent); + ~AnyWriter(); + + // Passes a StartObject call through to the Any writer. + void StartObject(StringPiece name); + + // Passes an EndObject call through to the Any. Returns true if the any + // handled the EndObject call, false if the Any is now all done and is no + // longer needed. + bool EndObject(); + + // Passes a StartList call through to the Any writer. + void StartList(StringPiece name); + + // Passes an EndList call through to the Any writer. + void EndList(); + + // Renders a data piece on the any. + void RenderDataPiece(StringPiece name, const DataPiece& value); + + private: + // Handles starting up the any once we have a type. + void StartAny(const DataPiece& value); + + // Writes the Any out to the parent writer in its serialized form. + void WriteAny(); + + // The parent of this writer, needed for various bits such as type info and + // the listeners. + ProtoStreamObjectWriter* parent_; + + // The nested object writer, used to write events. + google::protobuf::scoped_ptr ow_; + + // The type_url_ that this Any represents. + string type_url_; + + // Whether this any is invalid. This allows us to only report an invalid + // Any message a single time rather than every time we get a nested field. + bool invalid_; + + // The output data and wrapping ByteSink. + string data_; + strings::StringByteSink output_; + + // The depth within the Any, so we can track when we're done. + int depth_; + + // True if the message type contained in Any has a special "value" message + // injected. This is true for well-known message types like Any or Struct. + bool has_injected_value_message_; + }; + + class LIBPROTOBUF_EXPORT ProtoElement : public BaseElement, public LocationTrackerInterface { + public: + // Indicates the type of element. Special types like LIST, MAP, MAP_ENTRY, + // STRUCT etc. are used to deduce other information based on their position + // on the stack of elements. + enum ElementType { + MESSAGE, // Simple message + LIST, // List/repeated element + MAP, // Proto3 map type + MAP_ENTRY, // Proto3 map message type, with 'key' and 'value' fields + ANY, // Proto3 Any type + STRUCT, // Proto3 struct type + STRUCT_VALUE, // Struct's Value message type + STRUCT_LIST, // List type indicator within a struct + STRUCT_LIST_VALUE, // Struct Value's ListValue message type + STRUCT_MAP, // Struct within a struct type + STRUCT_MAP_ENTRY // Struct map's entry type with 'key' and 'value' + // fields + }; + + // Constructor for the root element. No parent nor field. + ProtoElement(TypeInfo* typeinfo, const google::protobuf::Type& type, + ProtoStreamObjectWriter* enclosing); + + // Constructor for a field of an element. + ProtoElement(ProtoElement* parent, const google::protobuf::Field* field, + const google::protobuf::Type& type, ElementType element_type); + + virtual ~ProtoElement() {} + + // Called just before the destructor for clean up: + // - reports any missing required fields + // - computes the space needed by the size field, and augment the + // length of all parent messages by this additional space. + // - releases and returns the parent pointer. + ProtoElement* pop(); + + // Accessors + const google::protobuf::Field* field() const { return field_; } + const google::protobuf::Type& type() const { return type_; } + + // These functions return true if the element type is corresponding to the + // type in function name. + bool IsMap() { return element_type_ == MAP; } + bool IsStructMap() { return element_type_ == STRUCT_MAP; } + bool IsStructMapEntry() { return element_type_ == STRUCT_MAP_ENTRY; } + bool IsStructList() { return element_type_ == STRUCT_LIST; } + bool IsAny() { return element_type_ == ANY; } + + ElementType element_type() { return element_type_; } + + void RegisterField(const google::protobuf::Field* field); + virtual string ToString() const; + + AnyWriter* any() const { return any_.get(); } + + virtual ProtoElement* parent() const { + return static_cast(BaseElement::parent()); + } + + private: + // Used for access to variables of the enclosing instance of + // ProtoStreamObjectWriter. + ProtoStreamObjectWriter* ow_; + + // A writer for Any objects, handles all Any-related nonsense. + google::protobuf::scoped_ptr any_; + + // Describes the element as a field in the parent message. + // field_ is NULL if and only if this element is the root element. + const google::protobuf::Field* field_; + + // TypeInfo to lookup types. + TypeInfo* typeinfo_; + + // Additional variables if this element is a message: + // (Root element is always a message). + // descriptor_ : describes allowed fields in the message. + // required_fields_: set of required fields. + // is_repeated_type_ : true if the element is of type list or map. + // size_index_ : index into ProtoStreamObjectWriter::size_insert_ + // for later insertion of serialized message length. + const google::protobuf::Type& type_; + std::set required_fields_; + const bool is_repeated_type_; + const int size_index_; + + // Tracks position in repeated fields, needed for LocationTrackerInterface. + int array_index_; + + // The type of this element, see enum for permissible types. + ElementType element_type_; + + GOOGLE_DISALLOW_IMPLICIT_CONSTRUCTORS(ProtoElement); + }; + + // Container for inserting 'size' information at the 'pos' position. + struct SizeInfo { + const int pos; + int size; + }; + + ProtoStreamObjectWriter(TypeInfo* typeinfo, + const google::protobuf::Type& type, + strings::ByteSink* output, ErrorListener* listener); + + ProtoElement* element() { return element_.get(); } + + // Helper methods for calling ErrorListener. See error_listener.h. + void InvalidName(StringPiece unknown_name, StringPiece message); + void InvalidValue(StringPiece type_name, StringPiece value); + void MissingField(StringPiece missing_name); + + // Common code for BeginObject() and BeginList() that does invalid_depth_ + // bookkeeping associated with name lookup. + const google::protobuf::Field* BeginNamed(StringPiece name, bool is_list); + + // Lookup the field in the current element. Looks in the base descriptor + // and in any extension. This will report an error if the field cannot be + // found or if multiple matching extensions are found. + const google::protobuf::Field* Lookup(StringPiece name); + + // Lookup the field type in the type descriptor. Returns NULL if the type + // is not known. + const google::protobuf::Type* LookupType( + const google::protobuf::Field* field); + + // Looks up the oneof struct Value field depending on the type. + // On failure to find, it returns an appropriate error. + util::StatusOr LookupStructField( + DataPiece::Type type); + + // Starts an entry in map. This will be called after placing map element at + // the top of the stack. Uses this information to write map entries. + const google::protobuf::Field* StartMapEntry(StringPiece name); + + // Starts a google.protobuf.Struct. + // 'field' is of type google.protobuf.Struct. + // If field is NULL, it indicates that the top-level message is a struct + // type. + void StartStruct(const google::protobuf::Field* field); + + // Starts another struct within a struct. + // 'field' is of type google.protobuf.Value (see struct.proto). + const google::protobuf::Field* StartStructValueInStruct( + const google::protobuf::Field* field); + + // Starts a list within a struct. + // 'field' is of type google.protobuf.ListValue (see struct.proto). + const google::protobuf::Field* StartListValueInStruct( + const google::protobuf::Field* field); + + // Starts the repeated "values" field in struct.proto's + // google.protobuf.ListValue type. 'field' should be of type + // google.protobuf.ListValue. + const google::protobuf::Field* StartRepeatedValuesInListValue( + const google::protobuf::Field* field); + + // Pops sentinel elements off the stack. + void SkipElements(); + + // Write serialized output to the final output ByteSink, inserting all + // the size information for nested messages that are missing from the + // intermediate Cord buffer. + void WriteRootMessage(); + + // Returns true if the field is a map. + bool IsMap(const google::protobuf::Field& field); + + // Returns true if the field is an any. + bool IsAny(const google::protobuf::Field& field); + + // Helper method to write proto tags based on the given field. + void WriteTag(const google::protobuf::Field& field); + + // Helper function to render primitive data types in DataPiece. + void RenderSimpleDataPiece(const google::protobuf::Field& field, + const google::protobuf::Type& type, + const DataPiece& data); + + // Renders google.protobuf.Value in struct.proto. It picks the right oneof + // type based on value's type. + static util::Status RenderStructValue(ProtoStreamObjectWriter* ow, + const DataPiece& value); + + // Renders google.protobuf.Timestamp value. + static util::Status RenderTimestamp(ProtoStreamObjectWriter* ow, + const DataPiece& value); + + // Renders google.protobuf.FieldMask value. + static util::Status RenderFieldMask(ProtoStreamObjectWriter* ow, + const DataPiece& value); + + // Renders google.protobuf.Duration value. + static util::Status RenderDuration(ProtoStreamObjectWriter* ow, + const DataPiece& value); + + // Renders wrapper message types for primitive types in + // google/protobuf/wrappers.proto. + static util::Status RenderWrapperType(ProtoStreamObjectWriter* ow, + const DataPiece& value); + + // Helper functions to create the map and find functions responsible for + // rendering well known types, keyed by type URL. + static hash_map* CreateRendererMap(); + static TypeRenderer* FindTypeRenderer(const string& type_url); + + // Returns the ProtoElement::ElementType for the given Type. + static ProtoElement::ElementType GetElementType( + const google::protobuf::Type& type); + + // Variables for describing the structure of the input tree: + // master_type_: descriptor for the whole protobuf message. + // typeinfo_ : the TypeInfo object to lookup types. + const google::protobuf::Type& master_type_; + TypeInfo* typeinfo_; + // Whether we own the typeinfo_ object. + bool own_typeinfo_; + + // Indicates whether we finished writing root message completely. + bool done_; + + // Variable for internal state processing: + // element_ : the current element. + // size_insert_: sizes of nested messages. + // pos - position to insert the size field. + // size - size value to be inserted. + google::protobuf::scoped_ptr element_; + std::deque size_insert_; + + // Variables for output generation: + // output_ : pointer to an external ByteSink for final user-visible output. + // buffer_ : buffer holding partial message before being ready for output_. + // adapter_ : internal adapter between CodedOutputStream and Cord buffer_. + // stream_ : wrapper for writing tags and other encodings in wire format. + strings::ByteSink* output_; + string buffer_; + google::protobuf::io::StringOutputStream adapter_; + google::protobuf::scoped_ptr stream_; + + // Variables for error tracking and reporting: + // listener_ : a place to report any errors found. + // invalid_depth_: number of enclosing invalid nested messages. + // tracker_ : the root location tracker interface. + ErrorListener* listener_; + int invalid_depth_; + google::protobuf::scoped_ptr tracker_; + + GOOGLE_DISALLOW_IMPLICIT_CONSTRUCTORS(ProtoStreamObjectWriter); +}; + +} // namespace converter +} // namespace util +} // namespace protobuf + +} // namespace google +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_PROTOSTREAM_OBJECTWRITER_H__ diff --git a/src/google/protobuf/util/internal/protostream_objectwriter_test.cc b/src/google/protobuf/util/internal/protostream_objectwriter_test.cc new file mode 100644 index 00000000..bd4f29f5 --- /dev/null +++ b/src/google/protobuf/util/internal/protostream_objectwriter_test.cc @@ -0,0 +1,1513 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include // For size_t + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +using google::protobuf::testing::Author; +using google::protobuf::testing::Book; +using google::protobuf::testing::Book_Data; +using google::protobuf::testing::Primitive; +using google::protobuf::testing::Publisher; +using google::protobuf::Descriptor; +using google::protobuf::DescriptorPool; +using google::protobuf::DynamicMessageFactory; +using google::protobuf::FileDescriptorProto; +using google::protobuf::Message; +using google::protobuf::io::ArrayInputStream; +using strings::GrowingArrayByteSink; +using ::testing::_; +using ::testing::Args; +using google::protobuf::testing::anys::AnyM; +using google::protobuf::testing::anys::AnyOut; +using google::protobuf::testing::FieldMaskTest; +using google::protobuf::testing::maps::MapIn; +using google::protobuf::testing::structs::StructType; +using google::protobuf::testing::timestampduration::TimestampDuration; + + +namespace { +string GetTypeUrl(const Descriptor* descriptor) { + return string(kTypeServiceBaseUrl) + "/" + descriptor->full_name(); +} +} // namespace + +class BaseProtoStreamObjectWriterTest + : public ::testing::TestWithParam { + protected: + BaseProtoStreamObjectWriterTest() + : helper_(GetParam()), + listener_(), + output_(new GrowingArrayByteSink(1000)), + ow_() {} + + explicit BaseProtoStreamObjectWriterTest(const Descriptor* descriptor) + : helper_(GetParam()), + listener_(), + output_(new GrowingArrayByteSink(1000)), + ow_() { + vector descriptors; + descriptors.push_back(descriptor); + ResetTypeInfo(descriptors); + } + + explicit BaseProtoStreamObjectWriterTest( + vector descriptors) + : helper_(GetParam()), + listener_(), + output_(new GrowingArrayByteSink(1000)), + ow_() { + ResetTypeInfo(descriptors); + } + + void ResetTypeInfo(vector descriptors) { + GOOGLE_CHECK(!descriptors.empty()) << "Must have at least one descriptor!"; + helper_.ResetTypeInfo(descriptors); + ow_.reset(helper_.NewProtoWriter(GetTypeUrl(descriptors[0]), output_.get(), + &listener_)); + } + + virtual ~BaseProtoStreamObjectWriterTest() {} + + void CheckOutput(const Message& expected, int expected_length) { + size_t nbytes; + google::protobuf::scoped_array buffer(output_->GetBuffer(&nbytes)); + if (expected_length >= 0) { + EXPECT_EQ(expected_length, nbytes); + } + string str(buffer.get(), nbytes); + + std::stringbuf str_buf(str, std::ios_base::in); + std::istream istream(&str_buf); + google::protobuf::scoped_ptr message(expected.New()); + message->ParsePartialFromIstream(&istream); + + EXPECT_EQ(expected.DebugString(), message->DebugString()); + } + + void CheckOutput(const Message& expected) { CheckOutput(expected, -1); } + + const google::protobuf::Type* GetType(const Descriptor* descriptor) { + return helper_.GetTypeInfo()->GetType(GetTypeUrl(descriptor)); + } + + testing::TypeInfoTestHelper helper_; + MockErrorListener listener_; + google::protobuf::scoped_ptr output_; + google::protobuf::scoped_ptr ow_; +}; + +MATCHER_P(HasObjectLocation, expected, + "Verifies the expected object location") { + string actual = std::tr1::get<0>(arg).ToString(); + if (actual.compare(expected) == 0) return true; + *result_listener << "actual location is: " << actual; + return false; +} + +class ProtoStreamObjectWriterTest : public BaseProtoStreamObjectWriterTest { + protected: + ProtoStreamObjectWriterTest() + : BaseProtoStreamObjectWriterTest(Book::descriptor()) {} + + virtual ~ProtoStreamObjectWriterTest() {} +}; + +INSTANTIATE_TEST_CASE_P(DifferentTypeInfoSourceTest, + ProtoStreamObjectWriterTest, + ::testing::Values( + testing::USE_TYPE_RESOLVER)); + +TEST_P(ProtoStreamObjectWriterTest, EmptyObject) { + Book empty; + ow_->StartObject("")->EndObject(); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, SimpleObject) { + string content("My content"); + + Book book; + book.set_title("My Title"); + book.set_length(222); + book.set_content(content); + + ow_->StartObject("") + ->RenderString("title", "My Title") + ->RenderInt32("length", 222) + ->RenderBytes("content", content) + ->EndObject(); + CheckOutput(book); +} + +TEST_P(ProtoStreamObjectWriterTest, SimpleMessage) { + Book book; + book.set_title("Some Book"); + book.set_length(102); + Publisher* publisher = book.mutable_publisher(); + publisher->set_name("My Publisher"); + Author* robert = book.mutable_author(); + robert->set_alive(true); + robert->set_name("robert"); + robert->add_pseudonym("bob"); + robert->add_pseudonym("bobby"); + robert->add_friend_()->set_name("john"); + + ow_->StartObject("") + ->RenderString("title", "Some Book") + ->RenderInt32("length", 102) + ->StartObject("publisher") + ->RenderString("name", "My Publisher") + ->EndObject() + ->StartObject("author") + ->RenderBool("alive", true) + ->RenderString("name", "robert") + ->StartList("pseudonym") + ->RenderString("", "bob") + ->RenderString("", "bobby") + ->EndList() + ->StartList("friend") + ->StartObject("") + ->RenderString("name", "john") + ->EndObject() + ->EndList() + ->EndObject() + ->EndObject(); + CheckOutput(book); +} + +TEST_P(ProtoStreamObjectWriterTest, PrimitiveFromStringConversion) { + Primitive full; + full.set_fix32(101); + full.set_u32(102); + full.set_i32(-103); + full.set_sf32(-104); + full.set_s32(-105); + full.set_fix64(40000000001L); + full.set_u64(40000000002L); + full.set_i64(-40000000003L); + full.set_sf64(-40000000004L); + full.set_s64(-40000000005L); + full.set_str("string1"); + full.set_bytes("Some Bytes"); + full.set_float_(3.14f); + full.set_double_(-4.05L); + full.set_bool_(true); + full.add_rep_fix32(201); + full.add_rep_u32(202); + full.add_rep_i32(-203); + full.add_rep_sf32(-204); + full.add_rep_s32(-205); + full.add_rep_fix64(80000000001L); + full.add_rep_u64(80000000002L); + full.add_rep_i64(-80000000003L); + full.add_rep_sf64(-80000000004L); + full.add_rep_s64(-80000000005L); + full.add_rep_str("string2"); + full.add_rep_bytes("More Bytes"); + full.add_rep_float(6.14f); + full.add_rep_double(-8.05L); + full.add_rep_bool(false); + + ow_.reset(helper_.NewProtoWriter(GetTypeUrl(Primitive::descriptor()), + output_.get(), &listener_)); + + ow_->StartObject("") + ->RenderString("fix32", "101") + ->RenderString("u32", "102") + ->RenderString("i32", "-103") + ->RenderString("sf32", "-104") + ->RenderString("s32", "-105") + ->RenderString("fix64", "40000000001") + ->RenderString("u64", "40000000002") + ->RenderString("i64", "-40000000003") + ->RenderString("sf64", "-40000000004") + ->RenderString("s64", "-40000000005") + ->RenderString("str", "string1") + ->RenderString("bytes", "U29tZSBCeXRlcw==") // "Some Bytes" + ->RenderString("float", "3.14") + ->RenderString("double", "-4.05") + ->RenderString("bool", "true") + ->StartList("rep_fix32") + ->RenderString("", "201") + ->EndList() + ->StartList("rep_u32") + ->RenderString("", "202") + ->EndList() + ->StartList("rep_i32") + ->RenderString("", "-203") + ->EndList() + ->StartList("rep_sf32") + ->RenderString("", "-204") + ->EndList() + ->StartList("rep_s32") + ->RenderString("", "-205") + ->EndList() + ->StartList("rep_fix64") + ->RenderString("", "80000000001") + ->EndList() + ->StartList("rep_u64") + ->RenderString("", "80000000002") + ->EndList() + ->StartList("rep_i64") + ->RenderString("", "-80000000003") + ->EndList() + ->StartList("rep_sf64") + ->RenderString("", "-80000000004") + ->EndList() + ->StartList("rep_s64") + ->RenderString("", "-80000000005") + ->EndList() + ->StartList("rep_str") + ->RenderString("", "string2") + ->EndList() + ->StartList("rep_bytes") + ->RenderString("", "TW9yZSBCeXRlcw==") // "More Bytes" + ->EndList() + ->StartList("rep_float") + ->RenderString("", "6.14") + ->EndList() + ->StartList("rep_double") + ->RenderString("", "-8.05") + ->EndList() + ->StartList("rep_bool") + ->RenderString("", "false") + ->EndList() + ->EndObject(); + CheckOutput(full); +} + +TEST_P(ProtoStreamObjectWriterTest, InfinityInputTest) { + Primitive full; + full.set_double_(std::numeric_limits::infinity()); + full.set_float_(std::numeric_limits::infinity()); + full.set_str("-Infinity"); + + ow_.reset(helper_.NewProtoWriter(GetTypeUrl(Primitive::descriptor()), + output_.get(), &listener_)); + + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_INT32"), + StringPiece("\"Infinity\""))) + .With(Args<0>(HasObjectLocation("i32"))); + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_UINT32"), + StringPiece("\"Infinity\""))) + .With(Args<0>(HasObjectLocation("u32"))); + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_SFIXED64"), + StringPiece("\"-Infinity\""))) + .With(Args<0>(HasObjectLocation("sf64"))); + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_BOOL"), + StringPiece("\"Infinity\""))) + .With(Args<0>(HasObjectLocation("bool"))); + + ow_->StartObject("") + ->RenderString("double", "Infinity") + ->RenderString("float", "Infinity") + ->RenderString("i32", "Infinity") + ->RenderString("u32", "Infinity") + ->RenderString("sf64", "-Infinity") + ->RenderString("str", "-Infinity") + ->RenderString("bool", "Infinity") + ->EndObject(); + CheckOutput(full); +} + +TEST_P(ProtoStreamObjectWriterTest, NaNInputTest) { + Primitive full; + full.set_double_(std::numeric_limits::quiet_NaN()); + full.set_float_(std::numeric_limits::quiet_NaN()); + full.set_str("NaN"); + + ow_.reset(helper_.NewProtoWriter(GetTypeUrl(Primitive::descriptor()), + output_.get(), &listener_)); + + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_INT32"), + StringPiece("\"NaN\""))) + .With(Args<0>(HasObjectLocation("i32"))); + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_UINT32"), + StringPiece("\"NaN\""))) + .With(Args<0>(HasObjectLocation("u32"))); + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_SFIXED64"), + StringPiece("\"NaN\""))) + .With(Args<0>(HasObjectLocation("sf64"))); + EXPECT_CALL(listener_, + InvalidValue(_, StringPiece("TYPE_BOOL"), StringPiece("\"NaN\""))) + .With(Args<0>(HasObjectLocation("bool"))); + + ow_->StartObject("") + ->RenderString("double", "NaN") + ->RenderString("float", "NaN") + ->RenderString("i32", "NaN") + ->RenderString("u32", "NaN") + ->RenderString("sf64", "NaN") + ->RenderString("str", "NaN") + ->RenderString("bool", "NaN") + ->EndObject(); + + CheckOutput(full); +} + +TEST_P(ProtoStreamObjectWriterTest, ImplicitPrimitiveList) { + Book expected; + Author* author = expected.mutable_author(); + author->set_name("The Author"); + author->add_pseudonym("first"); + author->add_pseudonym("second"); + + ow_->StartObject("") + ->StartObject("author") + ->RenderString("name", "The Author") + ->RenderString("pseudonym", "first") + ->RenderString("pseudonym", "second") + ->EndObject() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, + LastWriteWinsOnNonRepeatedPrimitiveFieldWithDuplicates) { + Book expected; + Author* author = expected.mutable_author(); + author->set_name("second"); + + ow_->StartObject("") + ->StartObject("author") + ->RenderString("name", "first") + ->RenderString("name", "second") + ->EndObject() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, ExplicitPrimitiveList) { + Book expected; + Author* author = expected.mutable_author(); + author->set_name("The Author"); + author->add_pseudonym("first"); + author->add_pseudonym("second"); + + ow_->StartObject("") + ->StartObject("author") + ->RenderString("name", "The Author") + ->StartList("pseudonym") + ->RenderString("", "first") + ->RenderString("", "second") + ->EndList() + ->EndObject() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, NonRepeatedExplicitPrimitiveList) { + Book expected; + expected.set_allocated_author(new Author()); + + EXPECT_CALL( + listener_, + InvalidName( + _, StringPiece("name"), + StringPiece("Proto field is not repeating, cannot start list."))) + .With(Args<0>(HasObjectLocation("author"))); + ow_->StartObject("") + ->StartObject("author") + ->StartList("name") + ->RenderString("", "first") + ->RenderString("", "second") + ->EndList() + ->EndObject() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, ImplicitMessageList) { + Book expected; + Author* outer = expected.mutable_author(); + outer->set_name("outer"); + outer->set_alive(true); + Author* first = outer->add_friend_(); + first->set_name("first"); + Author* second = outer->add_friend_(); + second->set_name("second"); + + ow_->StartObject("") + ->StartObject("author") + ->RenderString("name", "outer") + ->RenderBool("alive", true) + ->StartObject("friend") + ->RenderString("name", "first") + ->EndObject() + ->StartObject("friend") + ->RenderString("name", "second") + ->EndObject() + ->EndObject() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, + LastWriteWinsOnNonRepeatedMessageFieldWithDuplicates) { + Book expected; + Author* author = expected.mutable_author(); + author->set_name("The Author"); + Publisher* publisher = expected.mutable_publisher(); + publisher->set_name("second"); + + ow_->StartObject("") + ->StartObject("author") + ->RenderString("name", "The Author") + ->EndObject() + ->StartObject("publisher") + ->RenderString("name", "first") + ->EndObject() + ->StartObject("publisher") + ->RenderString("name", "second") + ->EndObject() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, ExplicitMessageList) { + Book expected; + Author* outer = expected.mutable_author(); + outer->set_name("outer"); + outer->set_alive(true); + Author* first = outer->add_friend_(); + first->set_name("first"); + Author* second = outer->add_friend_(); + second->set_name("second"); + + ow_->StartObject("") + ->StartObject("author") + ->RenderString("name", "outer") + ->RenderBool("alive", true) + ->StartList("friend") + ->StartObject("") + ->RenderString("name", "first") + ->EndObject() + ->StartObject("") + ->RenderString("name", "second") + ->EndObject() + ->EndList() + ->EndObject() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, NonRepeatedExplicitMessageList) { + Book expected; + Author* author = expected.mutable_author(); + author->set_name("The Author"); + + EXPECT_CALL( + listener_, + InvalidName( + _, StringPiece("publisher"), + StringPiece("Proto field is not repeating, cannot start list."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->StartObject("") + ->StartObject("author") + ->RenderString("name", "The Author") + ->EndObject() + ->StartList("publisher") + ->StartObject("") + ->RenderString("name", "first") + ->EndObject() + ->StartObject("") + ->RenderString("name", "second") + ->EndObject() + ->EndList() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, UnknownFieldAtRoot) { + Book empty; + + EXPECT_CALL(listener_, InvalidName(_, StringPiece("unknown"), + StringPiece("Cannot find field."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->StartObject("")->RenderString("unknown", "Nope!")->EndObject(); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, UnknownFieldAtAuthorFriend) { + Book expected; + Author* paul = expected.mutable_author(); + paul->set_name("Paul"); + Author* mark = paul->add_friend_(); + mark->set_name("Mark"); + Author* john = paul->add_friend_(); + john->set_name("John"); + Author* luke = paul->add_friend_(); + luke->set_name("Luke"); + + EXPECT_CALL(listener_, InvalidName(_, StringPiece("address"), + StringPiece("Cannot find field."))) + .With(Args<0>(HasObjectLocation("author.friend[1]"))); + ow_->StartObject("") + ->StartObject("author") + ->RenderString("name", "Paul") + ->StartList("friend") + ->StartObject("") + ->RenderString("name", "Mark") + ->EndObject() + ->StartObject("") + ->RenderString("name", "John") + ->RenderString("address", "Patmos") + ->EndObject() + ->StartObject("") + ->RenderString("name", "Luke") + ->EndObject() + ->EndList() + ->EndObject() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, UnknownObjectAtRoot) { + Book empty; + + EXPECT_CALL(listener_, InvalidName(_, StringPiece("unknown"), + StringPiece("Cannot find field."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->StartObject("")->StartObject("unknown")->EndObject()->EndObject(); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, UnknownObjectAtAuthor) { + Book expected; + Author* author = expected.mutable_author(); + author->set_name("William"); + author->add_pseudonym("Bill"); + + EXPECT_CALL(listener_, InvalidName(_, StringPiece("wife"), + StringPiece("Cannot find field."))) + .With(Args<0>(HasObjectLocation("author"))); + ow_->StartObject("") + ->StartObject("author") + ->RenderString("name", "William") + ->StartObject("wife") + ->RenderString("name", "Hilary") + ->EndObject() + ->RenderString("pseudonym", "Bill") + ->EndObject() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, UnknownListAtRoot) { + Book empty; + + EXPECT_CALL(listener_, InvalidName(_, StringPiece("unknown"), + StringPiece("Cannot find field."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->StartObject("")->StartList("unknown")->EndList()->EndObject(); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, UnknownListAtPublisher) { + Book expected; + expected.set_title("Brainwashing"); + Publisher* publisher = expected.mutable_publisher(); + publisher->set_name("propaganda"); + + EXPECT_CALL(listener_, InvalidName(_, StringPiece("alliance"), + StringPiece("Cannot find field."))) + .With(Args<0>(HasObjectLocation("publisher"))); + ow_->StartObject("") + ->StartObject("publisher") + ->RenderString("name", "propaganda") + ->StartList("alliance") + ->EndList() + ->EndObject() + ->RenderString("title", "Brainwashing") + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, MissingRequiredField) { + Book expected; + expected.set_title("My Title"); + expected.set_allocated_publisher(new Publisher()); + + EXPECT_CALL(listener_, MissingField(_, StringPiece("name"))) + .With(Args<0>(HasObjectLocation("publisher"))); + ow_->StartObject("") + ->StartObject("publisher") + ->EndObject() + ->RenderString("title", "My Title") + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, InvalidFieldValueAtRoot) { + Book empty; + + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_UINT32"), + StringPiece("\"garbage\""))) + .With(Args<0>(HasObjectLocation("length"))); + ow_->StartObject("")->RenderString("length", "garbage")->EndObject(); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, MultipleInvalidFieldValues) { + Book expected; + expected.set_title("My Title"); + + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_UINT32"), + StringPiece("\"-400\""))) + .With(Args<0>(HasObjectLocation("length"))); + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_INT64"), + StringPiece("\"3.14\""))) + .With(Args<0>(HasObjectLocation("published"))); + ow_->StartObject("") + ->RenderString("length", "-400") + ->RenderString("published", "3.14") + ->RenderString("title", "My Title") + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, UnnamedFieldAtRoot) { + Book empty; + + EXPECT_CALL(listener_, + InvalidName(_, StringPiece(""), + StringPiece("Proto fields must have a name."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->StartObject("")->RenderFloat("", 3.14)->EndObject(); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, UnnamedFieldAtAuthor) { + Book expected; + expected.set_title("noname"); + expected.set_allocated_author(new Author()); + + EXPECT_CALL(listener_, + InvalidName(_, StringPiece(""), + StringPiece("Proto fields must have a name."))) + .With(Args<0>(HasObjectLocation("author"))); + ow_->StartObject("") + ->StartObject("author") + ->RenderInt32("", 123) + ->EndObject() + ->RenderString("title", "noname") + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, UnnamedListAtRoot) { + Book expected; + expected.set_title("noname"); + + EXPECT_CALL(listener_, + InvalidName(_, StringPiece(""), + StringPiece("Proto fields must have a name."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->StartObject("") + ->StartList("") + ->EndList() + ->RenderString("title", "noname") + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, RootNamedObject) { + Book expected; + expected.set_title("Annie"); + + EXPECT_CALL(listener_, + InvalidName(_, StringPiece("oops"), + StringPiece("Root element should not be named."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->StartObject("oops")->RenderString("title", "Annie")->EndObject(); + CheckOutput(expected, 7); +} + +TEST_P(ProtoStreamObjectWriterTest, RootNamedList) { + Book empty; + + EXPECT_CALL(listener_, + InvalidName(_, StringPiece("oops"), + StringPiece("Root element should not be named."))) + .With(Args<0>(HasObjectLocation(""))); + EXPECT_CALL(listener_, + InvalidName(_, StringPiece(""), + StringPiece("Proto fields must have a name."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->StartList("oops")->RenderString("", "item")->EndList(); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, RootUnnamedField) { + Book empty; + + EXPECT_CALL(listener_, + InvalidName(_, StringPiece(""), + StringPiece("Root element must be a message."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->RenderBool("", true); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, RootNamedField) { + Book empty; + + EXPECT_CALL(listener_, + InvalidName(_, StringPiece("oops"), + StringPiece("Root element must be a message."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->RenderBool("oops", true); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, NullValue) { + Book empty; + + ow_->RenderNull(""); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, NullValueForMessageField) { + Book empty; + + ow_->RenderNull("author"); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, NullValueForPrimitiveField) { + Book empty; + + ow_->RenderNull("length"); + CheckOutput(empty, 0); +} + +class ProtoStreamObjectWriterTimestampDurationTest + : public BaseProtoStreamObjectWriterTest { + protected: + ProtoStreamObjectWriterTimestampDurationTest() { + vector descriptors; + descriptors.push_back(TimestampDuration::descriptor()); + descriptors.push_back(google::protobuf::Timestamp::descriptor()); + descriptors.push_back(google::protobuf::Duration::descriptor()); + ResetTypeInfo(descriptors); + } +}; + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidTimestampError1) { + TimestampDuration timestamp; + + EXPECT_CALL( + listener_, + InvalidValue(_, + StringPiece("type.googleapis.com/google.protobuf.Timestamp"), + StringPiece( + "Field 'ts', Illegal timestamp format; timestamps " + "must end with 'Z'"))); + + ow_->StartObject("")->RenderString("ts", "")->EndObject(); + CheckOutput(timestamp); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidTimestampError2) { + TimestampDuration timestamp; + + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.Timestamp"), + StringPiece( + "Field 'ts', Invalid time format: Failed to parse input"))); + + ow_->StartObject("")->RenderString("ts", "Z")->EndObject(); + CheckOutput(timestamp); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidTimestampError3) { + TimestampDuration timestamp; + + EXPECT_CALL( + listener_, + InvalidValue(_, + StringPiece("type.googleapis.com/google.protobuf.Timestamp"), + StringPiece( + "Field 'ts', Invalid time format, failed to parse nano " + "seconds"))); + + ow_->StartObject("") + ->RenderString("ts", "1970-01-01T00:00:00.ABZ") + ->EndObject(); + CheckOutput(timestamp); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidTimestampError4) { + TimestampDuration timestamp; + + EXPECT_CALL( + listener_, + InvalidValue(_, + StringPiece("type.googleapis.com/google.protobuf.Timestamp"), + StringPiece("Field 'ts', Timestamp value exceeds limits"))); + + ow_->StartObject("") + ->RenderString("ts", "-8032-10-18T00:00:00.000Z") + ->EndObject(); + CheckOutput(timestamp); +} + +// TODO(skarvaje): Write a test for nanos that exceed limit. Currently, it is +// not possible to construct a test case where nanos exceed limit because of +// floating point arithmetic. + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidDurationError1) { + TimestampDuration duration; + + EXPECT_CALL( + listener_, + InvalidValue(_, + StringPiece("type.googleapis.com/google.protobuf.Duration"), + StringPiece( + "Field 'dur', Illegal duration format; duration must " + "end with 's'"))); + + ow_->StartObject("")->RenderString("dur", "")->EndObject(); + CheckOutput(duration); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidDurationError2) { + TimestampDuration duration; + + EXPECT_CALL( + listener_, + InvalidValue(_, + StringPiece("type.googleapis.com/google.protobuf.Duration"), + StringPiece( + "Field 'dur', Invalid duration format, failed to parse " + "seconds"))); + + ow_->StartObject("")->RenderString("dur", "s")->EndObject(); + CheckOutput(duration); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidDurationError3) { + TimestampDuration duration; + + EXPECT_CALL( + listener_, + InvalidValue(_, + StringPiece("type.googleapis.com/google.protobuf.Duration"), + StringPiece( + "Field 'dur', Invalid duration format, failed to " + "parse nanos seconds"))); + + ow_->StartObject("")->RenderString("dur", "123.DEFs")->EndObject(); + CheckOutput(duration); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidDurationError4) { + TimestampDuration duration; + + EXPECT_CALL( + listener_, + InvalidValue(_, + StringPiece("type.googleapis.com/google.protobuf.Duration"), + StringPiece("Field 'dur', Duration value exceeds limits"))); + + ow_->StartObject("")->RenderString("dur", "315576000002s")->EndObject(); + CheckOutput(duration); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, + MismatchedTimestampTypeInput) { + TimestampDuration timestamp; + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.Timestamp"), + StringPiece( + "Field 'ts', Invalid data type for timestamp, value is null"))) + .With(Args<0>(HasObjectLocation("ts"))); + ow_->StartObject("")->RenderNull("ts")->EndObject(); + CheckOutput(timestamp); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, + MismatchedDurationTypeInput) { + TimestampDuration duration; + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.Duration"), + StringPiece( + "Field 'dur', Invalid data type for duration, value is null"))) + .With(Args<0>(HasObjectLocation("dur"))); + ow_->StartObject("")->RenderNull("dur")->EndObject(); + CheckOutput(duration); +} + +class ProtoStreamObjectWriterStructTest + : public BaseProtoStreamObjectWriterTest { + protected: + ProtoStreamObjectWriterStructTest() { + vector descriptors; + descriptors.push_back(StructType::descriptor()); + descriptors.push_back(google::protobuf::Struct::descriptor()); + ResetTypeInfo(descriptors); + } +}; + +// TODO(skarvaje): Write tests for failure cases. +TEST_P(ProtoStreamObjectWriterStructTest, StructRenderSuccess) { + StructType struct_type; + google::protobuf::Struct* s = struct_type.mutable_object(); + s->mutable_fields()->operator[]("k1").set_number_value(123); + s->mutable_fields()->operator[]("k2").set_bool_value(true); + + ow_->StartObject("") + ->StartObject("object") + ->RenderDouble("k1", 123) + ->RenderBool("k2", true) + ->EndObject() + ->EndObject(); + CheckOutput(struct_type); +} + +TEST_P(ProtoStreamObjectWriterStructTest, StructNullInputSuccess) { + StructType struct_type; + EXPECT_CALL(listener_, + InvalidName(_, StringPiece(""), + StringPiece("Proto fields must have a name."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->StartObject("")->RenderNull("")->EndObject(); + CheckOutput(struct_type); +} + +TEST_P(ProtoStreamObjectWriterStructTest, StructInvalidInputFailure) { + StructType struct_type; + EXPECT_CALL( + listener_, + InvalidValue(_, StringPiece("type.googleapis.com/google.protobuf.Struct"), + StringPiece("true"))) + .With(Args<0>(HasObjectLocation("object"))); + + ow_->StartObject("")->RenderBool("object", true)->EndObject(); + CheckOutput(struct_type); +} + +class ProtoStreamObjectWriterMapTest : public BaseProtoStreamObjectWriterTest { + protected: + ProtoStreamObjectWriterMapTest() + : BaseProtoStreamObjectWriterTest(MapIn::descriptor()) {} +}; + +TEST_P(ProtoStreamObjectWriterMapTest, MapShouldNotAcceptList) { + MapIn mm; + EXPECT_CALL(listener_, + InvalidValue(_, StringPiece("Map"), + StringPiece("Cannot bind a list to map."))) + .With(Args<0>(HasObjectLocation("map_input"))); + ow_->StartObject("") + ->StartList("map_input") + ->RenderString("a", "b") + ->EndList() + ->EndObject(); + CheckOutput(mm); +} + +class ProtoStreamObjectWriterAnyTest : public BaseProtoStreamObjectWriterTest { + protected: + ProtoStreamObjectWriterAnyTest() { + vector descriptors; + descriptors.push_back(AnyOut::descriptor()); + descriptors.push_back(google::protobuf::DoubleValue::descriptor()); + descriptors.push_back(google::protobuf::Any::descriptor()); + ResetTypeInfo(descriptors); + } +}; + +TEST_P(ProtoStreamObjectWriterAnyTest, AnyRenderSuccess) { + AnyOut any; + google::protobuf::Any* any_type = any.mutable_any(); + any_type->set_type_url("type.googleapis.com/google.protobuf.DoubleValue"); + google::protobuf::DoubleValue d; + d.set_value(40.2); + any_type->set_value(d.SerializeAsString()); + + ow_->StartObject("") + ->StartObject("any") + ->RenderString("@type", "type.googleapis.com/google.protobuf.DoubleValue") + ->RenderDouble("value", 40.2) + ->EndObject() + ->EndObject(); + CheckOutput(any); +} + +TEST_P(ProtoStreamObjectWriterAnyTest, RecursiveAny) { + AnyOut out; + ::google::protobuf::Any* any = out.mutable_any(); + any->set_type_url("type.googleapis.com/google.protobuf.Any"); + + ::google::protobuf::Any nested_any; + nested_any.set_type_url( + "type.googleapis.com/google.protobuf.testing.anys.AnyM"); + + AnyM m; + m.set_foo("foovalue"); + nested_any.set_value(m.SerializeAsString()); + + any->set_value(nested_any.SerializeAsString()); + + ow_->StartObject("") + ->StartObject("any") + ->RenderString("@type", "type.googleapis.com/google.protobuf.Any") + ->StartObject("value") + ->RenderString("@type", + "type.googleapis.com/google.protobuf.testing.anys.AnyM") + ->RenderString("foo", "foovalue") + ->EndObject() + ->EndObject() + ->EndObject(); + + CheckOutput(out, 115); +} + +TEST_P(ProtoStreamObjectWriterAnyTest, DoubleRecursiveAny) { + AnyOut out; + ::google::protobuf::Any* any = out.mutable_any(); + any->set_type_url("type.googleapis.com/google.protobuf.Any"); + + ::google::protobuf::Any nested_any; + nested_any.set_type_url("type.googleapis.com/google.protobuf.Any"); + + ::google::protobuf::Any second_nested_any; + second_nested_any.set_type_url( + "type.googleapis.com/google.protobuf.testing.anys.AnyM"); + + AnyM m; + m.set_foo("foovalue"); + second_nested_any.set_value(m.SerializeAsString()); + + nested_any.set_value(second_nested_any.SerializeAsString()); + any->set_value(nested_any.SerializeAsString()); + + ow_->StartObject("") + ->StartObject("any") + ->RenderString("@type", "type.googleapis.com/google.protobuf.Any") + ->StartObject("value") + ->RenderString("@type", "type.googleapis.com/google.protobuf.Any") + ->StartObject("value") + ->RenderString("@type", + "type.googleapis.com/google.protobuf.testing.anys.AnyM") + ->RenderString("foo", "foovalue") + ->EndObject() + ->EndObject() + ->EndObject() + ->EndObject(); + + CheckOutput(out, 159); +} + +TEST_P(ProtoStreamObjectWriterAnyTest, EmptyAnyFromEmptyObject) { + AnyOut out; + out.mutable_any(); + + ow_->StartObject("")->StartObject("any")->EndObject()->EndObject(); + + CheckOutput(out, 2); +} + +TEST_P(ProtoStreamObjectWriterAnyTest, AnyWithoutTypeUrlFails1) { + AnyOut any; + + EXPECT_CALL(listener_, + InvalidValue(_, StringPiece("Any"), + StringPiece( + "Missing or invalid @type for any field in " + "google.protobuf.testing.anys.AnyOut"))); + + ow_->StartObject("") + ->StartObject("any") + ->StartObject("another") + ->EndObject() + ->EndObject() + ->EndObject(); + CheckOutput(any); +} + +TEST_P(ProtoStreamObjectWriterAnyTest, AnyWithoutTypeUrlFails2) { + AnyOut any; + + EXPECT_CALL(listener_, + InvalidValue(_, StringPiece("Any"), + StringPiece( + "Missing or invalid @type for any field in " + "google.protobuf.testing.anys.AnyOut"))); + + ow_->StartObject("") + ->StartObject("any") + ->StartList("another") + ->EndObject() + ->EndObject() + ->EndObject(); + CheckOutput(any); +} + +TEST_P(ProtoStreamObjectWriterAnyTest, AnyWithoutTypeUrlFails3) { + AnyOut any; + + EXPECT_CALL(listener_, + InvalidValue(_, StringPiece("Any"), + StringPiece( + "Missing or invalid @type for any field in " + "google.protobuf.testing.anys.AnyOut"))); + + ow_->StartObject("") + ->StartObject("any") + ->RenderString("value", "somevalue") + ->EndObject() + ->EndObject(); + CheckOutput(any); +} + +TEST_P(ProtoStreamObjectWriterAnyTest, AnyWithInvalidTypeUrlFails) { + AnyOut any; + + EXPECT_CALL( + listener_, + InvalidValue(_, StringPiece("Any"), + StringPiece( + "Invalid type URL, type URLs must be of the form " + "'type.googleapis.com/', got: " + "type.other.com/some.Type"))); + + ow_->StartObject("") + ->StartObject("any") + ->RenderString("@type", "type.other.com/some.Type") + ->RenderDouble("value", 40.2) + ->EndObject() + ->EndObject(); + CheckOutput(any); +} + +TEST_P(ProtoStreamObjectWriterAnyTest, AnyWithUnknownTypeFails) { + AnyOut any; + + EXPECT_CALL( + listener_, + InvalidValue(_, StringPiece("Any"), + StringPiece("Invalid type URL, unknown type: some.Type"))); + ow_->StartObject("") + ->StartObject("any") + ->RenderString("@type", "type.googleapis.com/some.Type") + ->RenderDouble("value", 40.2) + ->EndObject() + ->EndObject(); + CheckOutput(any); +} + +TEST_P(ProtoStreamObjectWriterAnyTest, AnyNullInputFails) { + AnyOut any; + + ow_->StartObject("")->RenderNull("any")->EndObject(); + CheckOutput(any); +} + +class ProtoStreamObjectWriterFieldMaskTest + : public BaseProtoStreamObjectWriterTest { + protected: + ProtoStreamObjectWriterFieldMaskTest() { + vector descriptors; + descriptors.push_back(FieldMaskTest::descriptor()); + descriptors.push_back(google::protobuf::FieldMask::descriptor()); + ResetTypeInfo(descriptors); + } +}; + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, SimpleFieldMaskTest) { + FieldMaskTest expected; + expected.set_id("1"); + expected.mutable_single_mask()->add_paths("path1"); + + ow_->StartObject(""); + ow_->RenderString("id", "1"); + ow_->RenderString("single_mask", "path1"); + ow_->EndObject(); + + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, MutipleMasksInCompactForm) { + FieldMaskTest expected; + expected.set_id("1"); + expected.mutable_single_mask()->add_paths("camel_case1"); + expected.mutable_single_mask()->add_paths("camel_case2"); + expected.mutable_single_mask()->add_paths("camel_case3"); + + ow_->StartObject(""); + ow_->RenderString("id", "1"); + ow_->RenderString("single_mask", "camelCase1,camelCase2,camelCase3"); + ow_->EndObject(); + + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, RepeatedFieldMaskTest) { + FieldMaskTest expected; + expected.set_id("1"); + google::protobuf::FieldMask* mask = expected.add_repeated_mask(); + mask->add_paths("field1"); + mask->add_paths("field2"); + expected.add_repeated_mask()->add_paths("field3"); + + ow_->StartObject(""); + ow_->RenderString("id", "1"); + ow_->StartList("repeated_mask"); + ow_->RenderString("", "field1,field2"); + ow_->RenderString("", "field3"); + ow_->EndList(); + ow_->EndObject(); + + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, EmptyFieldMaskTest) { + FieldMaskTest expected; + expected.set_id("1"); + + ow_->StartObject(""); + ow_->RenderString("id", "1"); + ow_->RenderString("single_mask", ""); + ow_->EndObject(); + + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, MaskUsingApiaryStyleShouldWork) { + FieldMaskTest expected; + expected.set_id("1"); + + ow_->StartObject(""); + ow_->RenderString("id", "1"); + // Case1 + ow_->RenderString("single_mask", + "outerField(camelCase1,camelCase2,camelCase3)"); + expected.mutable_single_mask()->add_paths("outer_field.camel_case1"); + expected.mutable_single_mask()->add_paths("outer_field.camel_case2"); + expected.mutable_single_mask()->add_paths("outer_field.camel_case3"); + + ow_->StartList("repeated_mask"); + + ow_->RenderString("", "a(field1,field2)"); + google::protobuf::FieldMask* mask = expected.add_repeated_mask(); + mask->add_paths("a.field1"); + mask->add_paths("a.field2"); + + ow_->RenderString("", "a(field3)"); + mask = expected.add_repeated_mask(); + mask->add_paths("a.field3"); + + ow_->RenderString("", "a()"); + expected.add_repeated_mask(); + + ow_->RenderString("", "a(,)"); + expected.add_repeated_mask(); + + ow_->RenderString("", "a(field1(field2(field3)))"); + mask = expected.add_repeated_mask(); + mask->add_paths("a.field1.field2.field3"); + + ow_->RenderString("", "a(field1(field2(field3,field4),field5),field6)"); + mask = expected.add_repeated_mask(); + mask->add_paths("a.field1.field2.field3"); + mask->add_paths("a.field1.field2.field4"); + mask->add_paths("a.field1.field5"); + mask->add_paths("a.field6"); + + ow_->RenderString("", "a(id,field1(id,field2(field3,field4),field5),field6)"); + mask = expected.add_repeated_mask(); + mask->add_paths("a.id"); + mask->add_paths("a.field1.id"); + mask->add_paths("a.field1.field2.field3"); + mask->add_paths("a.field1.field2.field4"); + mask->add_paths("a.field1.field5"); + mask->add_paths("a.field6"); + + ow_->RenderString("", "a(((field3,field4)))"); + mask = expected.add_repeated_mask(); + mask->add_paths("a.field3"); + mask->add_paths("a.field4"); + + ow_->EndList(); + ow_->EndObject(); + + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, MoreCloseThanOpenParentheses) { + EXPECT_CALL( + listener_, + InvalidValue(_, + StringPiece("type.googleapis.com/google.protobuf.FieldMask"), + StringPiece( + "Field 'single_mask', Invalid FieldMask 'a(b,c))'. " + "Cannot find matching '(' for all ')'."))); + + ow_->StartObject(""); + ow_->RenderString("id", "1"); + ow_->RenderString("single_mask", "a(b,c))"); + ow_->EndObject(); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, MoreOpenThanCloseParentheses) { + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.FieldMask"), + StringPiece( + "Field 'single_mask', Invalid FieldMask 'a(((b,c)'. Cannot " + "find matching ')' for all '('."))); + + ow_->StartObject(""); + ow_->RenderString("id", "1"); + ow_->RenderString("single_mask", "a(((b,c)"); + ow_->EndObject(); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, PathWithMapKeyShouldWork) { + FieldMaskTest expected; + expected.mutable_single_mask()->add_paths("path.to.map[\"key1\"]"); + expected.mutable_single_mask()->add_paths( + "path.to.map[\"e\\\"[]][scape\\\"\"]"); + expected.mutable_single_mask()->add_paths("path.to.map[\"key2\"]"); + + ow_->StartObject(""); + ow_->RenderString("single_mask", + "path.to.map[\"key1\"],path.to.map[\"e\\\"[]][scape\\\"\"]," + "path.to.map[\"key2\"]"); + ow_->EndObject(); + + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, + MapKeyMustBeAtTheEndOfAPathSegment) { + EXPECT_CALL( + listener_, + InvalidValue(_, + StringPiece("type.googleapis.com/google.protobuf.FieldMask"), + StringPiece( + "Field 'single_mask', Invalid FieldMask " + "'path.to.map[\"key1\"]a,path.to.map[\"key2\"]'. " + "Map keys should be at the end of a path segment."))); + + ow_->StartObject(""); + ow_->RenderString("single_mask", + "path.to.map[\"key1\"]a,path.to.map[\"key2\"]"); + ow_->EndObject(); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, MapKeyMustEnd) { + EXPECT_CALL( + listener_, + InvalidValue(_, + StringPiece("type.googleapis.com/google.protobuf.FieldMask"), + StringPiece( + "Field 'single_mask', Invalid FieldMask " + "'path.to.map[\"key1\"'. Map keys should be " + "represented as [\"some_key\"]."))); + + ow_->StartObject(""); + ow_->RenderString("single_mask", "path.to.map[\"key1\""); + ow_->EndObject(); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, MapKeyMustBeEscapedCorrectly) { + EXPECT_CALL( + listener_, + InvalidValue(_, + StringPiece("type.googleapis.com/google.protobuf.FieldMask"), + StringPiece( + "Field 'single_mask', Invalid FieldMask " + "'path.to.map[\"ke\"y1\"]'. Map keys should be " + "represented as [\"some_key\"]."))); + + ow_->StartObject(""); + ow_->RenderString("single_mask", "path.to.map[\"ke\"y1\"]"); + ow_->EndObject(); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, MapKeyCanContainAnyChars) { + FieldMaskTest expected; + expected.mutable_single_mask()->add_paths( + "path.to.map[\"(),[],\\\"'!@#$%^&*123_|War孙天涌,./?><\\\\\"]"); + expected.mutable_single_mask()->add_paths("path.to.map[\"key2\"]"); + + ow_->StartObject(""); + ow_->RenderString( + "single_mask", + "path.to.map[\"(),[],\\\"'!@#$%^&*123_|War孙天涌,./?><\\\\\"]," + "path.to.map[\"key2\"]"); + ow_->EndObject(); + + CheckOutput(expected); +} + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/util/internal/snake2camel_objectwriter.h b/src/google/protobuf/util/internal/snake2camel_objectwriter.h new file mode 100644 index 00000000..1a32bc56 --- /dev/null +++ b/src/google/protobuf/util/internal/snake2camel_objectwriter.h @@ -0,0 +1,187 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_SNAKE2CAMEL_OBJECTWRITER_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_SNAKE2CAMEL_OBJECTWRITER_H__ + +#include + +#include +#include +#include +#include +#include + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +// Snake2CamelObjectWriter is an ObjectWriter than translates each field name +// from snake_case to camelCase. Typical usage is: +// ProtoStreamObjectSource psos(...); +// JsonObjectWriter jow(...); +// Snake2CamelObjectWriter snake_to_camel(&jow); +// psos.writeTo(&snake_to_camel); +class Snake2CamelObjectWriter : public ObjectWriter { + public: + explicit Snake2CamelObjectWriter(ObjectWriter* ow) + : ow_(ow), normalize_case_(true) {} + virtual ~Snake2CamelObjectWriter() {} + + // ObjectWriter methods. + virtual Snake2CamelObjectWriter* StartObject(StringPiece name) { + ow_->StartObject(ShouldNormalizeCase(name) + ? StringPiece(StringPiece(ToCamelCase(name))) + : name); + return this; + } + + virtual Snake2CamelObjectWriter* EndObject() { + ow_->EndObject(); + return this; + } + + virtual Snake2CamelObjectWriter* StartList(StringPiece name) { + ow_->StartList(ShouldNormalizeCase(name) ? StringPiece(ToCamelCase(name)) + : name); + return this; + } + + virtual Snake2CamelObjectWriter* EndList() { + ow_->EndList(); + return this; + } + + virtual Snake2CamelObjectWriter* RenderBool(StringPiece name, bool value) { + ow_->RenderBool( + ShouldNormalizeCase(name) ? StringPiece(ToCamelCase(name)) : name, + value); + return this; + } + + virtual Snake2CamelObjectWriter* RenderInt32(StringPiece name, int32 value) { + ow_->RenderInt32( + ShouldNormalizeCase(name) ? StringPiece(ToCamelCase(name)) : name, + value); + return this; + } + + virtual Snake2CamelObjectWriter* RenderUint32(StringPiece name, + uint32 value) { + ow_->RenderUint32( + ShouldNormalizeCase(name) ? StringPiece(ToCamelCase(name)) : name, + value); + return this; + } + + virtual Snake2CamelObjectWriter* RenderInt64(StringPiece name, int64 value) { + ow_->RenderInt64( + ShouldNormalizeCase(name) ? StringPiece(ToCamelCase(name)) : name, + value); + return this; + } + + virtual Snake2CamelObjectWriter* RenderUint64(StringPiece name, + uint64 value) { + ow_->RenderUint64( + ShouldNormalizeCase(name) ? StringPiece(ToCamelCase(name)) : name, + value); + return this; + } + + virtual Snake2CamelObjectWriter* RenderDouble(StringPiece name, + double value) { + ow_->RenderDouble( + ShouldNormalizeCase(name) ? StringPiece(ToCamelCase(name)) : name, + value); + return this; + } + + virtual Snake2CamelObjectWriter* RenderFloat(StringPiece name, float value) { + ow_->RenderFloat( + ShouldNormalizeCase(name) ? StringPiece(ToCamelCase(name)) : name, + value); + return this; + } + + virtual Snake2CamelObjectWriter* RenderString(StringPiece name, + StringPiece value) { + ow_->RenderString( + ShouldNormalizeCase(name) ? StringPiece(ToCamelCase(name)) : name, + value); + return this; + } + + virtual Snake2CamelObjectWriter* RenderBytes(StringPiece name, + StringPiece value) { + ow_->RenderBytes( + ShouldNormalizeCase(name) ? StringPiece(ToCamelCase(name)) : name, + value); + return this; + } + + virtual Snake2CamelObjectWriter* RenderNull(StringPiece name) { + ow_->RenderNull(ShouldNormalizeCase(name) ? StringPiece(ToCamelCase(name)) + : name); + return this; + } + + virtual Snake2CamelObjectWriter* DisableCaseNormalizationForNextKey() { + normalize_case_ = false; + return this; + } + + private: + ObjectWriter* ow_; + bool normalize_case_; + + bool ShouldNormalizeCase(StringPiece name) { + if (normalize_case_) { + return !IsCamel(name); + } else { + normalize_case_ = true; + return false; + } + } + + bool IsCamel(StringPiece name) { + return name.empty() || (ascii_islower(name[0]) && !name.contains("_")); + } + + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(Snake2CamelObjectWriter); +}; + +} // namespace converter +} // namespace util +} // namespace protobuf + +} // namespace google +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_SNAKE2CAMEL_OBJECTWRITER_H__ diff --git a/src/google/protobuf/util/internal/snake2camel_objectwriter_test.cc b/src/google/protobuf/util/internal/snake2camel_objectwriter_test.cc new file mode 100644 index 00000000..67388c3b --- /dev/null +++ b/src/google/protobuf/util/internal/snake2camel_objectwriter_test.cc @@ -0,0 +1,311 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +class Snake2CamelObjectWriterTest : public ::testing::Test { + protected: + Snake2CamelObjectWriterTest() : mock_(), expects_(&mock_), testing_(&mock_) {} + virtual ~Snake2CamelObjectWriterTest() {} + + MockObjectWriter mock_; + ExpectingObjectWriter expects_; + Snake2CamelObjectWriter testing_; +}; + +TEST_F(Snake2CamelObjectWriterTest, Empty) { + // Set expectation + expects_.StartObject("")->EndObject(); + + // Actual testing + testing_.StartObject("")->EndObject(); +} + +TEST_F(Snake2CamelObjectWriterTest, UnderscoresOnly) { + // Set expectation + expects_.StartObject("") + ->RenderInt32("", 1) + ->RenderInt32("", 2) + ->RenderInt32("", 3) + ->RenderInt32("", 4) + ->RenderInt32("", 5) + ->EndObject(); + + // Actual testing + testing_.StartObject("") + ->RenderInt32("_", 1) + ->RenderInt32("__", 2) + ->RenderInt32("___", 3) + ->RenderInt32("____", 4) + ->RenderInt32("_____", 5) + ->EndObject(); +} + +TEST_F(Snake2CamelObjectWriterTest, LowercaseOnly) { + // Set expectation + expects_.StartObject("") + ->RenderString("key", "value") + ->RenderString("abracadabra", "magic") + ->EndObject(); + + // Actual testing + testing_.StartObject("") + ->RenderString("key", "value") + ->RenderString("abracadabra", "magic") + ->EndObject(); +} + +TEST_F(Snake2CamelObjectWriterTest, UppercaseOnly) { + // Set expectation + expects_.StartObject("") + ->RenderString("key", "VALUE") + ->RenderString("abracadabra", "MAGIC") + ->EndObject(); + + // Actual testing + testing_.StartObject("") + ->RenderString("KEY", "VALUE") + ->RenderString("ABRACADABRA", "MAGIC") + ->EndObject(); +} + +TEST_F(Snake2CamelObjectWriterTest, CamelCase) { + // Set expectation + expects_.StartObject("") + ->RenderString("camelCase", "camelCase") + ->RenderString("theQuickBrownFoxJumpsOverTheLazyDog", + "theQuickBrownFoxJumpsOverTheLazyDog") + ->EndObject(); + + // Actual testing + testing_.StartObject("") + ->RenderString("camelCase", "camelCase") + ->RenderString("theQuickBrownFoxJumpsOverTheLazyDog", + "theQuickBrownFoxJumpsOverTheLazyDog") + ->EndObject(); +} + +TEST_F(Snake2CamelObjectWriterTest, FirstCapCamelCase) { + // Sets expectation + expects_.StartObject("camel") + ->RenderString("camelCase", "CamelCase") + ->RenderString("theQuickBrownFoxJumpsOverTheLazyDog", + "TheQuickBrownFoxJumpsOverTheLazyDog") + ->EndObject(); + + // Actual testing + testing_.StartObject("Camel") + ->RenderString("CamelCase", "CamelCase") + ->RenderString("TheQuickBrownFoxJumpsOverTheLazyDog", + "TheQuickBrownFoxJumpsOverTheLazyDog") + ->EndObject(); +} + +TEST_F(Snake2CamelObjectWriterTest, LastCapCamelCase) { + // Sets expectation + expects_.StartObject("lastCapCamelCasE")->EndObject(); + + // Actual testing + testing_.StartObject("lastCapCamelCasE")->EndObject(); +} + +TEST_F(Snake2CamelObjectWriterTest, MixedCapCamelCase) { + // Sets expectation + expects_.StartObject("googleIsTheBest") + ->RenderFloat("iLoveGOOGLE", 1.61803f) + ->RenderFloat("goGoogleGO", 2.71828f) + ->RenderFloat("gBikeISCool", 3.14159f) + ->EndObject(); + + // Actual testing + testing_.StartObject("GOOGLEIsTheBest") + ->RenderFloat("ILoveGOOGLE", 1.61803f) + ->RenderFloat("GOGoogleGO", 2.71828f) + ->RenderFloat("GBikeISCool", 3.14159f) + ->EndObject(); +} + +TEST_F(Snake2CamelObjectWriterTest, MixedCase) { + // Sets expectation + expects_.StartObject("snakeCaseCamelCase") + ->RenderBool("camelCaseSnakeCase", false) + ->RenderBool("mixedCamelAndUnderScores", false) + ->RenderBool("goGOOGLEGo", true) + ->EndObject(); + + // Actual testing + testing_.StartObject("snake_case_camelCase") + ->RenderBool("camelCase_snake_case", false) + ->RenderBool("MixedCamel_And_UnderScores", false) + ->RenderBool("Go_GOOGLEGo", true) + ->EndObject(); +} + +TEST_F(Snake2CamelObjectWriterTest, SnakeCase) { + // Sets expectation + expects_.StartObject("") + ->RenderString("snakeCase", "snake_case") + ->RenderString("theQuickBrownFoxJumpsOverTheLazyDog", + "the_quick_brown_fox_jumps_over_the_lazy_dog") + ->EndObject(); + + // Actual testing + testing_.StartObject("") + ->RenderString("snake_case", "snake_case") + ->RenderString("the_quick_brown_fox_jumps_over_the_lazy_dog", + "the_quick_brown_fox_jumps_over_the_lazy_dog") + ->EndObject(); +} + +TEST_F(Snake2CamelObjectWriterTest, FirstCapSnakeCase) { + // Sets expectation + expects_.StartObject("firstCapSnakeCase") + ->RenderBool("helloWorld", true) + ->EndObject(); + + // Actual testing + testing_.StartObject("First_Cap_Snake_Case") + ->RenderBool("Hello_World", true) + ->EndObject(); +} + +TEST_F(Snake2CamelObjectWriterTest, AllCapSnakeCase) { + // Sets expectation + expects_.StartObject("allCAPSNAKECASE") + ->RenderDouble("nyseGOOGL", 600.0L) + ->RenderDouble("aBCDE", 1.0L) + ->RenderDouble("klMNOP", 2.0L) + ->RenderDouble("abcIJKPQRXYZ", 3.0L) + ->EndObject(); + + // Actual testing + testing_.StartObject("ALL_CAP_SNAKE_CASE") + ->RenderDouble("NYSE_GOOGL", 600.0L) + ->RenderDouble("A_B_C_D_E", 1.0L) + ->RenderDouble("KL_MN_OP", 2.0L) + ->RenderDouble("ABC_IJK_PQR_XYZ", 3.0L) + ->EndObject(); +} + +TEST_F(Snake2CamelObjectWriterTest, RepeatedUnderScoreSnakeCase) { + // Sets expectation + expects_.StartObject("") + ->RenderInt32("doubleUnderscoreSnakeCase", 2) + ->RenderInt32("tripleUnderscoreFirstCap", 3) + ->RenderInt32("quadrupleUNDERSCOREALLCAP", 4) + ->EndObject(); + + // Actual testing + testing_.StartObject("") + ->RenderInt32("double__underscore__snake__case", 2) + ->RenderInt32("Triple___Underscore___First___Cap", 3) + ->RenderInt32("QUADRUPLE____UNDERSCORE____ALL____CAP", 4) + ->EndObject(); +} + +TEST_F(Snake2CamelObjectWriterTest, LeadingUnderscoreSnakeCase) { + // Sets expectation + expects_.StartObject("leadingUnderscoreSnakeCase") + ->RenderUint32("leadingDoubleUnderscore", 2) + ->RenderUint32("leadingTripleUnderscoreFirstCap", 3) + ->RenderUint32("leadingQUADRUPLEUNDERSCOREALLCAP", 4) + ->EndObject(); + + // Actual testing + testing_.StartObject("_leading_underscore_snake_case") + ->RenderUint32("__leading_double_underscore", 2) + ->RenderUint32("___Leading_Triple_Underscore_First_Cap", 3) + ->RenderUint32("____LEADING_QUADRUPLE_UNDERSCORE_ALL_CAP", 4) + ->EndObject(); +} + +TEST_F(Snake2CamelObjectWriterTest, TrailingUnderscoreSnakeCase) { + // Sets expectation + expects_.StartObject("trailingUnderscoreSnakeCase") + ->RenderInt64("trailingDoubleUnderscore", 2L) + ->RenderInt64("trailingTripleUnderscoreFirstCap", 3L) + ->RenderInt64("trailingQUADRUPLEUNDERSCOREALLCAP", 4L) + ->EndObject(); + + // Actual testing + testing_.StartObject("trailing_underscore_snake_case") + ->RenderInt64("trailing_double_underscore__", 2L) + ->RenderInt64("Trailing_Triple_Underscore_First_Cap___", 3L) + ->RenderInt64("TRAILING_QUADRUPLE_UNDERSCORE_ALL_CAP____", 4L) + ->EndObject(); +} + +TEST_F(Snake2CamelObjectWriterTest, EnclosingUnderscoreSnakeCase) { + // Sets expectation + expects_.StartObject("enclosingUnderscoreSnakeCase") + ->RenderUint64("enclosingDoubleUnderscore", 2L) + ->RenderUint64("enclosingTripleUnderscoreFirstCap", 3L) + ->RenderUint64("enclosingQUADRUPLEUNDERSCOREALLCAP", 4L) + ->EndObject(); + + // Actual testing + testing_.StartObject("_enclosing_underscore_snake_case_") + ->RenderUint64("__enclosing_double_underscore__", 2L) + ->RenderUint64("___Enclosing_Triple_Underscore_First_Cap___", 3L) + ->RenderUint64("____ENCLOSING_QUADRUPLE_UNDERSCORE_ALL_CAP____", 4L) + ->EndObject(); +} + +TEST_F(Snake2CamelObjectWriterTest, DisableCaseNormalizationOnlyDisablesFirst) { + // Sets expectation + expects_.StartObject("") + ->RenderString("snakeCase", "snake_case") + ->RenderString( + "the_quick_brown_fox_jumps_over_the_lazy_dog", // case retained + "the_quick_brown_fox_jumps_over_the_lazy_dog") + ->RenderBool("theSlowFox", true) // disable case not in effect + ->EndObject(); + + // Actual testing + testing_.StartObject("") + ->RenderString("snake_case", "snake_case") + ->DisableCaseNormalizationForNextKey() + ->RenderString("the_quick_brown_fox_jumps_over_the_lazy_dog", + "the_quick_brown_fox_jumps_over_the_lazy_dog") + ->RenderBool("the_slow_fox", true) + ->EndObject(); +} + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/util/internal/structured_objectwriter.h b/src/google/protobuf/util/internal/structured_objectwriter.h new file mode 100644 index 00000000..3f065d6b --- /dev/null +++ b/src/google/protobuf/util/internal/structured_objectwriter.h @@ -0,0 +1,118 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_STRUCTURED_OBJECTWRITER_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_STRUCTURED_OBJECTWRITER_H__ + +#include +#ifndef _SHARED_PTR_H +#include +#endif + +#include +#include +#include + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +// An StructuredObjectWriter is an ObjectWriter for writing +// tree-structured data in a stream of events representing objects +// and collections. Implementation of this interface can be used to +// write an object stream to an in-memory structure, protobufs, +// JSON, XML, or any other output format desired. The ObjectSource +// interface is typically used as the source of an object stream. +// +// See JsonObjectWriter for a sample implementation of +// StructuredObjectWriter and its use. +// +// Derived classes could be thread-unsafe. +class LIBPROTOBUF_EXPORT StructuredObjectWriter : public ObjectWriter { + public: + virtual ~StructuredObjectWriter() {} + + protected: + // A base element class for subclasses to extend, makes tracking state easier. + // + // StructuredObjectWriter behaves as a visitor. BaseElement represents a node + // in the input tree. Implementation of StructuredObjectWriter should also + // extend BaseElement to keep track of the location in the input tree. + class LIBPROTOBUF_EXPORT BaseElement { + public: + // Takes ownership of the parent Element. + explicit BaseElement(BaseElement* parent) + : parent_(parent), level_(parent == NULL ? 0 : parent->level() + 1) {} + virtual ~BaseElement() {} + + // Releases ownership of the parent and returns a pointer to it. + template + ElementType* pop() { + return down_cast(parent_.release()); + } + + // Returns true if this element is the root. + bool is_root() const { return parent_ == NULL; } + + // Returns the number of hops from this element to the root element. + int level() const { return level_; } + + protected: + // Returns pointer to parent element without releasing ownership. + virtual BaseElement* parent() const { return parent_.get(); } + + private: + // Pointer to the parent Element. + google::protobuf::scoped_ptr parent_; + + // Number of hops to the root Element. + // The root Element has NULL parent_ and a level_ of 0. + const int level_; + + GOOGLE_DISALLOW_IMPLICIT_CONSTRUCTORS(BaseElement); + }; + + StructuredObjectWriter() {} + + // Returns the current element. Used for indentation and name overrides. + virtual BaseElement* element() = 0; + + private: + // Do not add any data members to this class. + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(StructuredObjectWriter); +}; + +} // namespace converter +} // namespace util +} // namespace protobuf + +} // namespace google +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_STRUCTURED_OBJECTWRITER_H__ diff --git a/src/google/protobuf/util/internal/testdata/anys.proto b/src/google/protobuf/util/internal/testdata/anys.proto new file mode 100644 index 00000000..18c59cbb --- /dev/null +++ b/src/google/protobuf/util/internal/testdata/anys.proto @@ -0,0 +1,53 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Proto to test Proto3 Any serialization. +syntax = "proto3"; + +package google.protobuf.testing.anys; +option java_package = "com.google.protobuf.testing.anys"; + +import "google/protobuf/any.proto"; + +message AnyIn { + string something = 1; +} + +message AnyOut { + google.protobuf.Any any = 1; +} + +message AnyM { + string foo = 1; +} + +service TestService { + rpc Call(AnyIn) returns (AnyOut); +} diff --git a/src/google/protobuf/util/internal/testdata/books.proto b/src/google/protobuf/util/internal/testdata/books.proto new file mode 100644 index 00000000..6e2f109b --- /dev/null +++ b/src/google/protobuf/util/internal/testdata/books.proto @@ -0,0 +1,171 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: sven@google.com (Sven Mawson) +// +// Sample protos for testing. +syntax = "proto2"; + +package google.protobuf.testing; + +// A book +message Book { + optional string title = 1; + optional Author author = 2; + optional uint32 length = 3; + optional int64 published = 4; + optional bytes content = 5; + + optional group Data = 6 { + optional uint32 year = 7; + optional string copyright = 8; + } + + message Label { + optional string key = 1; + optional string value = 2; + } + + optional Publisher publisher = 9; + repeated Label labels = 10; + + extensions 200 to 499; +} + +// A publisher of a book, tests required fields. +message Publisher { + required string name = 1; +} + +// An author of a book +message Author { + optional uint64 id = 1; + optional string name = 2; + repeated string pseudonym = 3; + optional bool alive = 4; + repeated Author friend = 5; +} + +// For testing resiliency of our protostream parser. +// Field numbers of Author are reused for something else. +message BadAuthor { + optional string id = 1; // non-length-delimited to length-delimited. + repeated uint64 name = 2; // string to repeated (both length-delimited). + optional string pseudonym = 3; // Repeated to optional. + repeated bool alive = 4 [packed=true]; // Optional to repeated. +} + +// All primitive types +message Primitive { + // 32 bit numbers: + optional fixed32 fix32 = 1; + optional uint32 u32 = 2; + optional int32 i32 = 3; + optional sfixed32 sf32 = 4; + optional sint32 s32 = 5; + + // 64 bit numbers: + optional fixed64 fix64 = 6; + optional uint64 u64 = 7; + optional int64 i64 = 8; + optional sfixed64 sf64 = 9; + optional sint64 s64 = 10; + + // The other stuff. + optional string str = 11; + optional bytes bytes = 12; + optional float float = 13; + optional double double = 14; + optional bool bool = 15; + + // repeated 32 bit numbers: + repeated fixed32 rep_fix32 = 16; + repeated uint32 rep_u32 = 17; + repeated int32 rep_i32 = 18; + repeated sfixed32 rep_sf32 = 19; + repeated sint32 rep_s32 = 20; + + // repeated 64 bit numbers: + repeated fixed64 rep_fix64 = 21; + repeated uint64 rep_u64 = 22; + repeated int64 rep_i64 = 23; + repeated sfixed64 rep_sf64 = 24; + repeated sint64 rep_s64 = 25; + + // repeated other stuff: + repeated string rep_str = 26; + repeated bytes rep_bytes = 27; + repeated float rep_float = 28; + repeated double rep_double = 29; + repeated bool rep_bool = 30; +} + +// Test packed versions of all repeated primitives. +// The field numbers should match their non-packed version in Primitive message. +message PackedPrimitive { + // repeated 32 bit numbers: + repeated fixed32 rep_fix32 = 16 [packed=true]; + repeated uint32 rep_u32 = 17 [packed=true]; + repeated int32 rep_i32 = 18 [packed=true]; + repeated sfixed32 rep_sf32 = 19 [packed=true]; + repeated sint32 rep_s32 = 20 [packed=true]; + + // repeated 64 bit numbers: + repeated fixed64 rep_fix64 = 21 [packed=true]; + repeated uint64 rep_u64 = 22 [packed=true]; + repeated int64 rep_i64 = 23 [packed=true]; + repeated sfixed64 rep_sf64 = 24 [packed=true]; + repeated sint64 rep_s64 = 25 [packed=true]; + + // repeated other stuff: + repeated float rep_float = 28 [packed=true]; + repeated double rep_double = 29 [packed=true]; + repeated bool rep_bool = 30 [packed=true]; +} + +// Test extensions. +extend Book { + repeated Author more_author = 201; +} + +// Test nested extensions. +message NestedBook { + extend Book { + optional NestedBook another_book = 301; + } + // Recurse + optional Book book = 1; +} + +// For testing resiliency of our protostream parser. +// Field number of NestedBook is reused for something else. +message BadNestedBook { + repeated uint32 book = 1 [packed=true]; // Packed to optional message. +} diff --git a/src/google/protobuf/util/internal/testdata/default_value.proto b/src/google/protobuf/util/internal/testdata/default_value.proto new file mode 100644 index 00000000..ecfc8119 --- /dev/null +++ b/src/google/protobuf/util/internal/testdata/default_value.proto @@ -0,0 +1,162 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf.testing; + +import "google/protobuf/any.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/wrappers.proto"; + +message DefaultValueTestCases { + DoubleMessage empty_double = 1; + DoubleMessage double_with_default_value = 2; + DoubleMessage double_with_nondefault_value = 3; + DoubleMessage repeated_double = 4; + DoubleMessage nested_message = 5; + DoubleMessage repeated_nested_message = 6; + StructMessage empty_struct = 201; + StructMessage empty_struct2 = 202; + StructMessage struct_with_null_value = 203; + StructMessage struct_with_values = 204; + StructMessage struct_with_nested_struct = 205; + StructMessage struct_with_nested_list = 206; + StructMessage struct_with_list_of_nulls = 207; + StructMessage struct_with_list_of_lists = 208; + StructMessage struct_with_list_of_structs = 209; + google.protobuf.Struct top_level_struct = 210; + ValueMessage value_wrapper_simple = 212; + ValueMessage value_wrapper_with_struct = 213; + ValueMessage value_wrapper_with_list = 214; + ListValueMessage list_value_wrapper = 215; + google.protobuf.Value top_level_value_simple = 216; + google.protobuf.Value top_level_value_with_struct = 217; + google.protobuf.Value top_level_value_with_list = 218; + google.protobuf.ListValue top_level_listvalue = 219; + AnyMessage empty_any = 301; + AnyMessage type_only_any = 302; + AnyMessage recursive_any = 303; + AnyMessage any_with_message_value = 304; + AnyMessage any_with_nested_message = 305; + AnyMessage any_with_message_containing_map = 306; + AnyMessage any_with_message_containing_struct = 307; + google.protobuf.Any top_level_any = 308; + StringtoIntMap empty_map = 401; + StringtoIntMap string_to_int = 402; + IntToStringMap int_to_string = 403; + MixedMap mixed1 = 404; + MixedMap2 mixed2 = 405; + MessageMap map_of_objects = 406; + DoubleValueMessage double_value = 501; + DoubleValueMessage double_value_default = 502; +} + +message DoubleMessage { + double double_value = 1; + repeated double repeated_double = 2; + DoubleMessage nested_message = 3; + repeated DoubleMessage repeated_nested_message = 4; + google.protobuf.DoubleValue double_wrapper = 100; +} + +message StructMessage { + google.protobuf.Struct struct = 1; +} + +message ValueMessage { + google.protobuf.Value value = 1; +} + +message ListValueMessage { + google.protobuf.ListValue shopping_list = 1; +} +message RequestMessage { + string content = 1; +} + +// A test service. +service DefaultValueTestService { + // A test method. + rpc Call(RequestMessage) returns (DefaultValueTestCases); +} + +message AnyMessage { + google.protobuf.Any any = 1; + AnyData data = 2; +} + +message AnyData { + int32 attr = 1; + string str = 2; + repeated string msgs = 3; + AnyData nested_data = 4; + map map_data = 7; + google.protobuf.Struct struct_data = 8; + repeated AnyData repeated_data = 9; +} + +message StringtoIntMap { + map map = 1; +} + +message IntToStringMap { + map map = 1; +} + +message MixedMap { + string msg = 1; + map map = 2; + int32 int_value = 3; +} + +message MixedMap2 { + enum E { + E0 = 0; + E1 = 1; + E2 = 2; + E3 = 3; + } + map map = 1; + E ee = 2; + string msg = 4; +} + +message MessageMap { + message M { + int32 inner_int = 1; + string inner_text = 2; + } + map map = 1; +} + +message DoubleValueMessage { + google.protobuf.DoubleValue double = 1; +} diff --git a/src/google/protobuf/util/internal/testdata/default_value_test.proto b/src/google/protobuf/util/internal/testdata/default_value_test.proto new file mode 100644 index 00000000..21b85e6d --- /dev/null +++ b/src/google/protobuf/util/internal/testdata/default_value_test.proto @@ -0,0 +1,46 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf.testing; + +message DefaultValueTest { + double double_value = 1; + repeated double repeated_double = 2; + float float_value = 3; + int64 int64_value = 5; + uint64 uint64_value = 7; + int32 int32_value = 9; + uint32 uint32_value = 11; + bool bool_value = 13; + string string_value = 15; + bytes bytes_value = 17 [ctype = CORD]; +} diff --git a/src/google/protobuf/util/internal/testdata/field_mask.proto b/src/google/protobuf/util/internal/testdata/field_mask.proto new file mode 100644 index 00000000..e8b2bc5f --- /dev/null +++ b/src/google/protobuf/util/internal/testdata/field_mask.proto @@ -0,0 +1,71 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf.testing; + +import "google/protobuf/field_mask.proto"; + +message NestedFieldMask { + string data = 1; + google.protobuf.FieldMask single_mask = 2; + repeated google.protobuf.FieldMask repeated_mask = 3; +} + +message FieldMaskTest { + string id = 1; + google.protobuf.FieldMask single_mask = 2; + repeated google.protobuf.FieldMask repeated_mask = 3; + repeated NestedFieldMask nested_mask = 4; +} + +message FieldMaskTestCases { + FieldMaskWrapper single_mask = 1; + FieldMaskWrapper multiple_mask = 2; + FieldMaskWrapper snake_camel = 3; + FieldMaskWrapper empty_field = 4; + FieldMaskWrapper apiary_format1 = 5; + FieldMaskWrapper apiary_format2 = 6; + FieldMaskWrapper apiary_format3 = 7; + FieldMaskWrapper map_key1 = 8; + FieldMaskWrapper map_key2 = 9; + FieldMaskWrapper map_key3 = 10; + FieldMaskWrapper map_key4 = 11; + FieldMaskWrapper map_key5 = 12; +} + +message FieldMaskWrapper { + google.protobuf.FieldMask mask = 1; +} + +service FieldMaskTestService { + rpc Call(FieldMaskTestCases) returns (FieldMaskTestCases); +} diff --git a/src/google/protobuf/util/internal/testdata/maps.proto b/src/google/protobuf/util/internal/testdata/maps.proto new file mode 100644 index 00000000..7fb42a26 --- /dev/null +++ b/src/google/protobuf/util/internal/testdata/maps.proto @@ -0,0 +1,57 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Proto to test proto3 maps. +syntax = "proto3"; + +package google.protobuf.testing.maps; +option java_package = "com.google.protobuf.testing.maps"; + +message MapIn { + string other = 1; + repeated string things = 2; + map map_input = 3; +} + +message MapOut { + map map1 = 1; + map map2 = 2; + map map3 = 3; + string bar = 4; +} + +message MapM { + string foo = 1; +} + +service TestService { + rpc Call1(MapIn) returns (MapOut); + rpc Call2(MapIn) returns (MapOut); +} diff --git a/src/google/protobuf/util/internal/testdata/struct.proto b/src/google/protobuf/util/internal/testdata/struct.proto new file mode 100644 index 00000000..c15aba0d --- /dev/null +++ b/src/google/protobuf/util/internal/testdata/struct.proto @@ -0,0 +1,45 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Proto to test proto3 struct. +syntax = "proto3"; + +package google.protobuf.testing.structs; +option java_package = "com.google.protobuf.testing.structs"; + +import "google/protobuf/struct.proto"; + +message StructType { + google.protobuf.Struct object = 1; +} + +service TestService { + rpc Call(StructType) returns (StructType); +} diff --git a/src/google/protobuf/util/internal/testdata/timestamp_duration.proto b/src/google/protobuf/util/internal/testdata/timestamp_duration.proto new file mode 100644 index 00000000..56351f16 --- /dev/null +++ b/src/google/protobuf/util/internal/testdata/timestamp_duration.proto @@ -0,0 +1,47 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Proto to test proto3 Timestamp and Duration. +syntax = "proto3"; + +package google.protobuf.testing.timestampduration; +option java_package = "com.google.protobuf.testing.timestampduration"; + +import "google/protobuf/timestamp.proto"; +import "google/protobuf/duration.proto"; + +message TimestampDuration { + google.protobuf.Timestamp ts = 1; + google.protobuf.Duration dur = 2; +} + +service TestService { + rpc Call(TimestampDuration) returns (TimestampDuration); +} diff --git a/src/google/protobuf/util/internal/testdata/wrappers.proto b/src/google/protobuf/util/internal/testdata/wrappers.proto new file mode 100644 index 00000000..eabc99f2 --- /dev/null +++ b/src/google/protobuf/util/internal/testdata/wrappers.proto @@ -0,0 +1,100 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf.testing; + +import "google/protobuf/wrappers.proto"; + +// Top-level test cases proto used by MarshallingTest. See description +// at the top of the class MarshallingTest for details on how to write +// test cases. +message WrappersTestCases { + DoubleWrapper double_wrapper = 1; + FloatWrapper float_wrapper = 2; + Int64Wrapper int64_wrapper = 3; + UInt64Wrapper uint64_wrapper = 4; + Int32Wrapper int32_wrapper = 5; + UInt32Wrapper uint32_wrapper = 6; + BoolWrapper bool_wrapper = 7; + StringWrapper string_wrapper = 8; + BytesWrapper bytes_wrapper = 9; + + DoubleWrapper double_wrapper_default = 10; + FloatWrapper float_wrapper_default = 11; + Int64Wrapper int64_wrapper_default = 12; + UInt64Wrapper uint64_wrapper_default = 13; + Int32Wrapper int32_wrapper_default = 14; + UInt32Wrapper uint32_wrapper_default = 15; + BoolWrapper bool_wrapper_default = 16; + StringWrapper string_wrapper_default = 17; + BytesWrapper bytes_wrapper_default = 18; +} + +message DoubleWrapper { + google.protobuf.DoubleValue double = 1; +} + +message FloatWrapper { + google.protobuf.FloatValue float = 1; +} + +message Int64Wrapper { + google.protobuf.Int64Value int64 = 1; +} + +message UInt64Wrapper { + google.protobuf.UInt64Value uint64 = 1; +} + +message Int32Wrapper { + google.protobuf.Int32Value int32 = 1; +} + +message UInt32Wrapper { + google.protobuf.UInt32Value uint32 = 1; +} + +message BoolWrapper { + google.protobuf.BoolValue bool = 1; +} + +message StringWrapper { + google.protobuf.StringValue string = 1; +} + +message BytesWrapper { + google.protobuf.BytesValue bytes = 1; +} + +service WrappersTestService { + rpc Call(WrappersTestCases) returns (WrappersTestCases); +} diff --git a/src/google/protobuf/util/internal/type_info.cc b/src/google/protobuf/util/internal/type_info.cc new file mode 100644 index 00000000..6392e18c --- /dev/null +++ b/src/google/protobuf/util/internal/type_info.cc @@ -0,0 +1,171 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +namespace { + +// A TypeInfo that looks up information provided by a TypeResolver. +class TypeInfoForTypeResolver : public TypeInfo { + public: + explicit TypeInfoForTypeResolver(TypeResolver* type_resolver) + : type_resolver_(type_resolver) {} + + virtual ~TypeInfoForTypeResolver() { + DeleteCachedTypes(&cached_types_); + DeleteCachedTypes(&cached_enums_); + } + + virtual util::StatusOr ResolveTypeUrl( + StringPiece type_url) { + map::iterator it = cached_types_.find(type_url); + if (it != cached_types_.end()) { + return it->second; + } + // Stores the string value so it can be referenced using StringPiece in the + // cached_types_ map. + const string& string_type_url = + *string_storage_.insert(type_url.ToString()).first; + google::protobuf::scoped_ptr type(new google::protobuf::Type()); + util::Status status = + type_resolver_->ResolveMessageType(string_type_url, type.get()); + StatusOrType result = + status.ok() ? StatusOrType(type.release()) : StatusOrType(status); + cached_types_[string_type_url] = result; + return result; + } + + virtual const google::protobuf::Type* GetType(StringPiece type_url) { + StatusOrType result = ResolveTypeUrl(type_url); + return result.ok() ? result.ValueOrDie() : NULL; + } + + virtual const google::protobuf::Enum* GetEnum(StringPiece type_url) { + map::iterator it = cached_enums_.find(type_url); + if (it != cached_enums_.end()) { + return it->second.ok() ? it->second.ValueOrDie() : NULL; + } + // Stores the string value so it can be referenced using StringPiece in the + // cached_enums_ map. + const string& string_type_url = + *string_storage_.insert(type_url.ToString()).first; + google::protobuf::scoped_ptr enum_type( + new google::protobuf::Enum()); + util::Status status = + type_resolver_->ResolveEnumType(string_type_url, enum_type.get()); + StatusOrEnum result = + status.ok() ? StatusOrEnum(enum_type.release()) : StatusOrEnum(status); + cached_enums_[string_type_url] = result; + return result.ok() ? result.ValueOrDie() : NULL; + } + + virtual const google::protobuf::Field* FindField( + const google::protobuf::Type* type, StringPiece camel_case_name) { + if (indexed_types_.find(type) == indexed_types_.end()) { + PopulateNameLookupTable(type); + indexed_types_.insert(type); + } + StringPiece name = + FindWithDefault(camel_case_name_table_, camel_case_name, StringPiece()); + if (name.empty()) { + // Didn't find a mapping. Use whatever provided. + name = camel_case_name; + } + return FindFieldInTypeOrNull(type, name); + } + + private: + typedef util::StatusOr StatusOrType; + typedef util::StatusOr StatusOrEnum; + + template + static void DeleteCachedTypes(map* cached_types) { + for (typename map::iterator it = cached_types->begin(); + it != cached_types->end(); ++it) { + if (it->second.ok()) { + delete it->second.ValueOrDie(); + } + } + } + + void PopulateNameLookupTable(const google::protobuf::Type* type) { + for (int i = 0; i < type->fields_size(); ++i) { + const google::protobuf::Field& field = type->fields(i); + StringPiece name = field.name(); + StringPiece camel_case_name = + *string_storage_.insert(ToCamelCase(name)).first; + const StringPiece* existing = InsertOrReturnExisting( + &camel_case_name_table_, camel_case_name, name); + if (existing && *existing != name) { + GOOGLE_LOG(WARNING) << "Field '" << name << "' and '" << *existing + << "' map to the same camel case name '" << camel_case_name + << "'."; + } + } + } + + TypeResolver* type_resolver_; + + // Stores string values that will be referenced by StringPieces in + // cached_types_, cached_enums_ and camel_case_name_table_. + set string_storage_; + + map cached_types_; + map cached_enums_; + + set indexed_types_; + map camel_case_name_table_; +}; +} // namespace + +TypeInfo* TypeInfo::NewTypeInfo(TypeResolver* type_resolver) { + return new TypeInfoForTypeResolver(type_resolver); +} + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/util/internal/type_info.h b/src/google/protobuf/util/internal/type_info.h new file mode 100644 index 00000000..04ed78df --- /dev/null +++ b/src/google/protobuf/util/internal/type_info.h @@ -0,0 +1,87 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_TYPE_INFO_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_TYPE_INFO_H__ + +#include +#include +#include +#include +#include +#include + +namespace google { +namespace protobuf { +namespace util { +namespace converter { +// Internal helper class for type resolving. Note that this class is not +// thread-safe and should only be accessed in one thread. +class TypeInfo { + public: + TypeInfo() {} + virtual ~TypeInfo() {} + + // Resolves a type url into a Type. If the type url is invalid, returns + // INVALID_ARGUMENT error status. If the type url is valid but the + // corresponding type cannot be found, returns a NOT_FOUND error status. + // + // This TypeInfo class retains the ownership of the returned pointer. + virtual util::StatusOr ResolveTypeUrl( + StringPiece type_url) = 0; + + // Resolves a type url into a Type. Like ResolveTypeUrl() but returns + // NULL if the type url is invalid or the type cannot be found. + // + // This TypeInfo class retains the ownership of the returned pointer. + virtual const google::protobuf::Type* GetType(StringPiece type_url) = 0; + + // Resolves a type url for an enum. Returns NULL if the type url is + // invalid or the type cannot be found. + // + // This TypeInfo class retains the ownership of the returned pointer. + virtual const google::protobuf::Enum* GetEnum(StringPiece type_url) = 0; + + // Looks up a field in the specified type given a CamelCase name. + virtual const google::protobuf::Field* FindField( + const google::protobuf::Type* type, StringPiece camel_case_name) = 0; + + static TypeInfo* NewTypeInfo(TypeResolver* type_resolver); + + private: + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(TypeInfo); +}; + +} // namespace converter +} // namespace util +} // namespace protobuf + +} // namespace google +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_TYPE_INFO_H__ diff --git a/src/google/protobuf/util/internal/type_info_test_helper.cc b/src/google/protobuf/util/internal/type_info_test_helper.cc new file mode 100644 index 00000000..f7aea857 --- /dev/null +++ b/src/google/protobuf/util/internal/type_info_test_helper.cc @@ -0,0 +1,130 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include +#ifndef _SHARED_PTR_H +#include +#endif +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace google { +namespace protobuf { +namespace util { +namespace converter { +namespace testing { + + +void TypeInfoTestHelper::ResetTypeInfo( + const vector& descriptors) { + switch (type_) { + case USE_TYPE_RESOLVER: { + const DescriptorPool* pool = descriptors[0]->file()->pool(); + for (int i = 1; i < descriptors.size(); ++i) { + GOOGLE_CHECK(pool == descriptors[i]->file()->pool()) + << "Descriptors from different pools are not supported."; + } + type_resolver_.reset( + NewTypeResolverForDescriptorPool(kTypeServiceBaseUrl, pool)); + typeinfo_.reset(TypeInfo::NewTypeInfo(type_resolver_.get())); + return; + } + } + GOOGLE_LOG(FATAL) << "Can not reach here."; +} + +void TypeInfoTestHelper::ResetTypeInfo(const Descriptor* descriptor) { + vector descriptors; + descriptors.push_back(descriptor); + ResetTypeInfo(descriptors); +} + +void TypeInfoTestHelper::ResetTypeInfo(const Descriptor* descriptor1, + const Descriptor* descriptor2) { + vector descriptors; + descriptors.push_back(descriptor1); + descriptors.push_back(descriptor2); + ResetTypeInfo(descriptors); +} + +TypeInfo* TypeInfoTestHelper::GetTypeInfo() { return typeinfo_.get(); } + +ProtoStreamObjectSource* TypeInfoTestHelper::NewProtoSource( + io::CodedInputStream* coded_input, const string& type_url) { + const google::protobuf::Type* type = typeinfo_->GetType(type_url); + switch (type_) { + case USE_TYPE_RESOLVER: { + return new ProtoStreamObjectSource(coded_input, type_resolver_.get(), + *type); + } + } + GOOGLE_LOG(FATAL) << "Can not reach here."; +} + +ProtoStreamObjectWriter* TypeInfoTestHelper::NewProtoWriter( + const string& type_url, strings::ByteSink* output, + ErrorListener* listener) { + const google::protobuf::Type* type = typeinfo_->GetType(type_url); + switch (type_) { + case USE_TYPE_RESOLVER: { + return new ProtoStreamObjectWriter(type_resolver_.get(), *type, output, + listener); + } + } + GOOGLE_LOG(FATAL) << "Can not reach here."; +} + +DefaultValueObjectWriter* TypeInfoTestHelper::NewDefaultValueWriter( + const string& type_url, ObjectWriter* writer) { + const google::protobuf::Type* type = typeinfo_->GetType(type_url); + switch (type_) { + case USE_TYPE_RESOLVER: { + return new DefaultValueObjectWriter(type_resolver_.get(), *type, writer); + } + } + GOOGLE_LOG(FATAL) << "Can not reach here."; +} + +} // namespace testing +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/util/internal/type_info_test_helper.h b/src/google/protobuf/util/internal/type_info_test_helper.h new file mode 100644 index 00000000..6916a73b --- /dev/null +++ b/src/google/protobuf/util/internal/type_info_test_helper.h @@ -0,0 +1,98 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_TYPE_INFO_TEST_HELPER_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_TYPE_INFO_TEST_HELPER_H__ + +#include +#ifndef _SHARED_PTR_H +#include +#endif +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace google { +namespace protobuf { +namespace util { +namespace converter { +namespace testing { + +enum TypeInfoSource { + USE_TYPE_RESOLVER, +}; + +// In the unit-tests we want to test two scenarios: one with type info from +// ServiceTypeInfo, the other with type info from TypeResolver. This class +// wraps the detail of where the type info is from and provides the same +// interface so the same unit-test code can test both scenarios. +class TypeInfoTestHelper { + public: + explicit TypeInfoTestHelper(TypeInfoSource type) : type_(type) {} + + // Creates a TypeInfo object for the given set of descriptors. + void ResetTypeInfo(const vector& descriptors); + + // Convinent overloads. + void ResetTypeInfo(const Descriptor* descriptor); + void ResetTypeInfo(const Descriptor* descriptor1, + const Descriptor* descriptor2); + + // Returns the TypeInfo created after ResetTypeInfo. + TypeInfo* GetTypeInfo(); + + ProtoStreamObjectSource* NewProtoSource(io::CodedInputStream* coded_input, + const string& type_url); + + ProtoStreamObjectWriter* NewProtoWriter(const string& type_url, + strings::ByteSink* output, + ErrorListener* listener); + + DefaultValueObjectWriter* NewDefaultValueWriter(const string& type_url, + ObjectWriter* writer); + + private: + TypeInfoSource type_; + google::protobuf::scoped_ptr typeinfo_; + google::protobuf::scoped_ptr type_resolver_; +}; +} // namespace testing +} // namespace converter +} // namespace util +} // namespace protobuf + +} // namespace google +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_TYPE_INFO_TEST_HELPER_H__ diff --git a/src/google/protobuf/util/internal/utility.cc b/src/google/protobuf/util/internal/utility.cc new file mode 100644 index 00000000..b6ec19b6 --- /dev/null +++ b/src/google/protobuf/util/internal/utility.cc @@ -0,0 +1,332 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +namespace { +const StringPiece SkipWhiteSpace(StringPiece str) { + StringPiece::size_type i; + for (i = 0; i < str.size() && isspace(str[i]); ++i) {} + GOOGLE_DCHECK(i == str.size() || !isspace(str[i])); + return StringPiece(str, i); +} +} // namespace + +bool GetBoolOptionOrDefault( + const google::protobuf::RepeatedPtrField& options, + const string& option_name, bool default_value) { + const google::protobuf::Option* opt = FindOptionOrNull(options, option_name); + if (opt == NULL) { + return default_value; + } + return GetBoolFromAny(opt->value()); +} + +int64 GetInt64OptionOrDefault( + const google::protobuf::RepeatedPtrField& options, + const string& option_name, int64 default_value) { + const google::protobuf::Option* opt = FindOptionOrNull(options, option_name); + if (opt == NULL) { + return default_value; + } + return GetInt64FromAny(opt->value()); +} + +double GetDoubleOptionOrDefault( + const google::protobuf::RepeatedPtrField& options, + const string& option_name, double default_value) { + const google::protobuf::Option* opt = FindOptionOrNull(options, option_name); + if (opt == NULL) { + return default_value; + } + return GetDoubleFromAny(opt->value()); +} + +string GetStringOptionOrDefault( + const google::protobuf::RepeatedPtrField& options, + const string& option_name, const string& default_value) { + const google::protobuf::Option* opt = FindOptionOrNull(options, option_name); + if (opt == NULL) { + return default_value; + } + return GetStringFromAny(opt->value()); +} + +template +void ParseFromAny(const string& data, T* result) { + result->ParseFromString(data); +} + +// Returns a boolean value contained in Any type. +// TODO(skarvaje): Add type checking & error messages here. +bool GetBoolFromAny(const google::protobuf::Any& any) { + google::protobuf::BoolValue b; + ParseFromAny(any.value(), &b); + return b.value(); +} + +int64 GetInt64FromAny(const google::protobuf::Any& any) { + google::protobuf::Int64Value i; + ParseFromAny(any.value(), &i); + return i.value(); +} + +double GetDoubleFromAny(const google::protobuf::Any& any) { + google::protobuf::DoubleValue i; + ParseFromAny(any.value(), &i); + return i.value(); +} + +string GetStringFromAny(const google::protobuf::Any& any) { + google::protobuf::StringValue s; + ParseFromAny(any.value(), &s); + return s.value(); +} + +const StringPiece GetTypeWithoutUrl(StringPiece type_url) { + size_t idx = type_url.rfind('/'); + return type_url.substr(idx + 1); +} + +const string GetFullTypeWithUrl(StringPiece simple_type) { + return StrCat(kTypeServiceBaseUrl, "/", simple_type); +} + +const google::protobuf::Option* FindOptionOrNull( + const google::protobuf::RepeatedPtrField& options, + const string& option_name) { + for (int i = 0; i < options.size(); ++i) { + const google::protobuf::Option& opt = options.Get(i); + if (opt.name() == option_name) { + return &opt; + } + } + return NULL; +} + +const google::protobuf::Field* FindFieldInTypeOrNull( + const google::protobuf::Type* type, StringPiece field_name) { + if (type != NULL) { + for (int i = 0; i < type->fields_size(); ++i) { + const google::protobuf::Field& field = type->fields(i); + if (field.name() == field_name) { + return &field; + } + } + } + return NULL; +} + +const google::protobuf::EnumValue* FindEnumValueByNameOrNull( + const google::protobuf::Enum* enum_type, StringPiece enum_name) { + if (enum_type != NULL) { + for (int i = 0; i < enum_type->enumvalue_size(); ++i) { + const google::protobuf::EnumValue& enum_value = enum_type->enumvalue(i); + if (enum_value.name() == enum_name) { + return &enum_value; + } + } + } + return NULL; +} + +const google::protobuf::EnumValue* FindEnumValueByNumberOrNull( + const google::protobuf::Enum* enum_type, int32 value) { + if (enum_type != NULL) { + for (int i = 0; i < enum_type->enumvalue_size(); ++i) { + const google::protobuf::EnumValue& enum_value = enum_type->enumvalue(i); + if (enum_value.number() == value) { + return &enum_value; + } + } + } + return NULL; +} + +string ToCamelCase(const StringPiece input) { + bool capitalize_next = false; + bool was_cap = true; + bool is_cap = false; + bool first_word = true; + string result; + result.reserve(input.size()); + + for (size_t i = 0; i < input.size(); ++i, was_cap = is_cap) { + is_cap = ascii_isupper(input[i]); + if (input[i] == '_') { + capitalize_next = true; + if (!result.empty()) first_word = false; + continue; + } else if (first_word) { + // Consider when the current character B is capitalized, + // first word ends when: + // 1) following a lowercase: "...aB..." + // 2) followed by a lowercase: "...ABc..." + if (!result.empty() && is_cap && + (!was_cap || (i + 1 < input.size() && ascii_islower(input[i + 1])))) { + first_word = false; + } else { + result.push_back(ascii_tolower(input[i])); + continue; + } + } else if (capitalize_next) { + capitalize_next = false; + if (ascii_islower(input[i])) { + result.push_back(ascii_toupper(input[i])); + continue; + } + } + result.push_back(input[i]); + } + return result; +} + +string ToSnakeCase(StringPiece input) { + bool was_not_underscore = false; // Initialize to false for case 1 (below) + bool was_not_cap = false; + string result; + result.reserve(input.size() << 1); + + for (size_t i = 0; i < input.size(); ++i) { + if (ascii_isupper(input[i])) { + // Consider when the current character B is capitalized: + // 1) At beginning of input: "B..." => "b..." + // (e.g. "Biscuit" => "biscuit") + // 2) Following a lowercase: "...aB..." => "...a_b..." + // (e.g. "gBike" => "g_bike") + // 3) At the end of input: "...AB" => "...ab" + // (e.g. "GoogleLAB" => "google_lab") + // 4) Followed by a lowercase: "...ABc..." => "...a_bc..." + // (e.g. "GBike" => "g_bike") + if (was_not_underscore && // case 1 out + (was_not_cap || // case 2 in, case 3 out + (i + 1 < input.size() && // case 3 out + ascii_islower(input[i + 1])))) { // case 4 in + // We add an underscore for case 2 and case 4. + result.push_back('_'); + } + result.push_back(ascii_tolower(input[i])); + was_not_underscore = true; + was_not_cap = false; + } else { + result.push_back(input[i]); + was_not_underscore = input[i] != '_'; + was_not_cap = true; + } + } + return result; +} + +set* well_known_types_ = NULL; +GOOGLE_PROTOBUF_DECLARE_ONCE(well_known_types_init_); +const char* well_known_types_name_array_[] = { + "google.protobuf.Timestamp", "google.protobuf.Duration", + "google.protobuf.DoubleValue", "google.protobuf.FloatValue", + "google.protobuf.Int64Value", "google.protobuf.UInt64Value", + "google.protobuf.Int32Value", "google.protobuf.UInt32Value", + "google.protobuf.BoolValue", "google.protobuf.StringValue", + "google.protobuf.BytesValue", "google.protobuf.FieldMask"}; + +void DeleteWellKnownTypes() { delete well_known_types_; } + +void InitWellKnownTypes() { + well_known_types_ = new set; + for (int i = 0; i < GOOGLE_ARRAYSIZE(well_known_types_name_array_); ++i) { + well_known_types_->insert(well_known_types_name_array_[i]); + } + google::protobuf::internal::OnShutdown(&DeleteWellKnownTypes); +} + +bool IsWellKnownType(const string& type_name) { + InitWellKnownTypes(); + return ContainsKey(*well_known_types_, type_name); +} + +bool IsValidBoolString(const string& bool_string) { + return bool_string == "true" || bool_string == "false" || + bool_string == "1" || bool_string == "0"; +} + +bool IsMap(const google::protobuf::Field& field, + const google::protobuf::Type& type) { + return (field.cardinality() == + google::protobuf::Field_Cardinality_CARDINALITY_REPEATED && + GetBoolOptionOrDefault(type.options(), + "google.protobuf.MessageOptions.map_entry", false)); +} + +string DoubleAsString(double value) { + if (value == std::numeric_limits::infinity()) return "Infinity"; + if (value == -std::numeric_limits::infinity()) return "-Infinity"; + if (isnan(value)) return "NaN"; + + return SimpleDtoa(value); +} + +string FloatAsString(float value) { + if (isfinite(value)) return SimpleFtoa(value); + return DoubleAsString(value); +} + +bool SafeStrToFloat(StringPiece str, float *value) { + double double_value; + if (!safe_strtod(str, &double_value)) { + return false; + } + *value = static_cast(double_value); + + if ((*value == numeric_limits::infinity()) || + (*value == -numeric_limits::infinity())) { + return false; + } + return true; +} + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/util/internal/utility.h b/src/google/protobuf/util/internal/utility.h new file mode 100644 index 00000000..56ae1a25 --- /dev/null +++ b/src/google/protobuf/util/internal/utility.h @@ -0,0 +1,187 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef GOOGLE_PROTOBUF_UTIL_CONVERTER_UTILITY_H__ +#define GOOGLE_PROTOBUF_UTIL_CONVERTER_UTILITY_H__ + +#include +#ifndef _SHARED_PTR_H +#include +#endif +#include +#include + +#include +#include +#include +#include +#include +#include +#include + + +namespace google { +namespace protobuf { +class Method; +class Any; +class Bool; +class Option; +class Field; +class Type; +class Enum; +class EnumValue; +} // namespace protobuf + + +namespace protobuf { +namespace util { +namespace converter { +// Finds the tech option identified by option_name. Parses the boolean value and +// returns it. +// When the option with the given name is not found, default_value is returned. +bool GetBoolOptionOrDefault( + const google::protobuf::RepeatedPtrField& options, + const string& option_name, bool default_value); + +// Returns int64 option value. If the option isn't found, returns the +// default_value. +int64 GetInt64OptionOrDefault( + const google::protobuf::RepeatedPtrField& options, + const string& option_name, int64 default_value); + +// Returns double option value. If the option isn't found, returns the +// default_value. +double GetDoubleOptionOrDefault( + const google::protobuf::RepeatedPtrField& options, + const string& option_name, double default_value); + +// Returns string option value. If the option isn't found, returns the +// default_value. +string GetStringOptionOrDefault( + const google::protobuf::RepeatedPtrField& options, + const string& option_name, const string& default_value); + +// Returns a boolean value contained in Any type. +// TODO(skarvaje): Make these utilities dealing with Any types more generic, +// add more error checking and move to a more public/sharable location so others +// can use. +bool GetBoolFromAny(const google::protobuf::Any& any); + +// Returns int64 value contained in Any type. +int64 GetInt64FromAny(const google::protobuf::Any& any); + +// Returns double value contained in Any type. +double GetDoubleFromAny(const google::protobuf::Any& any); + +// Returns string value contained in Any type. +string GetStringFromAny(const google::protobuf::Any& any); + +// Returns the type string without the url prefix. e.g.: If the passed type is +// 'type.googleapis.com/tech.type.Bool', the returned value is 'tech.type.Bool'. +const StringPiece GetTypeWithoutUrl(StringPiece type_url); + +// Returns the simple_type with the base type url (kTypeServiceBaseUrl) +// prefixed. +// +// E.g: +// GetFullTypeWithUrl("google.protobuf.Timestamp") returns the string +// "type.googleapis.com/google.protobuf.Timestamp". +const string GetFullTypeWithUrl(StringPiece simple_type); + +// Finds and returns option identified by name and option_name within the +// provided map. Returns NULL if none found. +const google::protobuf::Option* FindOptionOrNull( + const google::protobuf::RepeatedPtrField& options, + const string& option_name); + +// Finds and returns the field identified by field_name in the passed tech Type +// object. Returns NULL if none found. +const google::protobuf::Field* FindFieldInTypeOrNull( + const google::protobuf::Type* type, StringPiece field_name); + +// Finds and returns the EnumValue identified by enum_name in the passed tech +// Enum object. Returns NULL if none found. +const google::protobuf::EnumValue* FindEnumValueByNameOrNull( + const google::protobuf::Enum* enum_type, StringPiece enum_name); + +// Finds and returns the EnumValue identified by value in the passed tech +// Enum object. Returns NULL if none found. +const google::protobuf::EnumValue* FindEnumValueByNumberOrNull( + const google::protobuf::Enum* enum_type, int32 value); + +// Converts input to camel-case and returns it. +// Tests are in wrappers/translator/snake2camel_objectwriter_test.cc +// TODO(skarvaje): Isolate tests for this function and put them in +// utility_test.cc +string ToCamelCase(const StringPiece input); + +// Converts input to snake_case and returns it. +string ToSnakeCase(StringPiece input); + +// Returns true if type_name represents a well-known type. +bool IsWellKnownType(const string& type_name); + +// Returns true if 'bool_string' represents a valid boolean value. Only "true", +// "false", "0" and "1" are allowed. +bool IsValidBoolString(const string& bool_string); + +// Returns true if "field" is a protobuf map field based on its type. +bool IsMap(const google::protobuf::Field& field, + const google::protobuf::Type& type); + +// Infinity/NaN-aware conversion to string. +string DoubleAsString(double value); +string FloatAsString(float value); + +// Convert from int32, int64, uint32, uint64, double or float to string. +template +string ValueAsString(T value) { + return SimpleItoa(value); +} + +template <> +inline string ValueAsString(float value) { + return FloatAsString(value); +} + +template <> +inline string ValueAsString(double value) { + return DoubleAsString(value); +} + +// Converts a string to float. Unlike safe_strtof, conversion will fail if the +// value fits into double but not float (e.g., DBL_MAX). +bool SafeStrToFloat(StringPiece str, float* value); +} // namespace converter +} // namespace util +} // namespace protobuf + +} // namespace google +#endif // GOOGLE_PROTOBUF_UTIL_CONVERTER_UTILITY_H__ diff --git a/src/google/protobuf/util/json_format_proto3.proto b/src/google/protobuf/util/json_format_proto3.proto new file mode 100644 index 00000000..7a282868 --- /dev/null +++ b/src/google/protobuf/util/json_format_proto3.proto @@ -0,0 +1,157 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package proto3; + + +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/any.proto"; +import "google/protobuf/field_mask.proto"; + +enum EnumType { + FOO = 0; + BAR = 1; +} + +message MessageType { + int32 value = 1; +} + +message TestMessage { + bool bool_value = 1; + int32 int32_value = 2; + int64 int64_value = 3; + uint32 uint32_value = 4; + uint64 uint64_value = 5; + float float_value = 6; + double double_value = 7; + string string_value = 8; + bytes bytes_value = 9; + EnumType enum_value = 10; + MessageType message_value = 11; + + repeated bool repeated_bool_value = 21; + repeated int32 repeated_int32_value = 22; + repeated int64 repeated_int64_value = 23; + repeated uint32 repeated_uint32_value = 24; + repeated uint64 repeated_uint64_value = 25; + repeated float repeated_float_value = 26; + repeated double repeated_double_value = 27; + repeated string repeated_string_value = 28; + repeated bytes repeated_bytes_value = 29; + repeated EnumType repeated_enum_value = 30; + repeated MessageType repeated_message_value = 31; +} + +message TestOneof { + // In JSON format oneof fields behave mostly the same as optional + // fields except that: + // 1. Oneof fields have field presence information and will be + // printed if it's set no matter whether it's the default value. + // 2. Multiple oneof fields in the same oneof cannot appear at the + // same time in the input. + oneof oneof_value { + int32 oneof_int32_value = 1; + string oneof_string_value = 2; + bytes oneof_bytes_value = 3; + EnumType oneof_enum_value = 4; + MessageType oneof_message_value = 5; + } +} + +message TestMap { + map bool_map = 1; + map int32_map = 2; + map int64_map = 3; + map uint32_map = 4; + map uint64_map = 5; + map string_map = 6; +} + +message TestWrapper { + google.protobuf.BoolValue bool_value = 1; + google.protobuf.Int32Value int32_value = 2; + google.protobuf.Int64Value int64_value = 3; + google.protobuf.UInt32Value uint32_value = 4; + google.protobuf.UInt64Value uint64_value = 5; + google.protobuf.FloatValue float_value = 6; + google.protobuf.DoubleValue double_value = 7; + google.protobuf.StringValue string_value = 8; + google.protobuf.BytesValue bytes_value = 9; + + repeated google.protobuf.BoolValue repeated_bool_value = 11; + repeated google.protobuf.Int32Value repeated_int32_value = 12; + repeated google.protobuf.Int64Value repeated_int64_value = 13; + repeated google.protobuf.UInt32Value repeated_uint32_value = 14; + repeated google.protobuf.UInt64Value repeated_uint64_value = 15; + repeated google.protobuf.FloatValue repeated_float_value = 16; + repeated google.protobuf.DoubleValue repeated_double_value = 17; + repeated google.protobuf.StringValue repeated_string_value = 18; + repeated google.protobuf.BytesValue repeated_bytes_value = 19; +} + +message TestTimestamp { + google.protobuf.Timestamp value = 1; + repeated google.protobuf.Timestamp repeated_value = 2; +} + +message TestDuration { + google.protobuf.Duration value = 1; + repeated google.protobuf.Duration repeated_value = 2; +} + +message TestFieldMask { + google.protobuf.FieldMask value = 1; +} + +message TestStruct { + google.protobuf.Struct value = 1; + repeated google.protobuf.Struct repeated_value = 2; +} + +message TestAny { + google.protobuf.Any value = 1; + repeated google.protobuf.Any repeated_value = 2; +} + +message TestValue { + google.protobuf.Value value = 1; + repeated google.protobuf.Value repeated_value = 2; +} + +message TestListValue { + google.protobuf.ListValue value = 1; + repeated google.protobuf.ListValue repeated_value = 2; +} diff --git a/src/google/protobuf/util/json_util.cc b/src/google/protobuf/util/json_util.cc new file mode 100644 index 00000000..6cd40fd5 --- /dev/null +++ b/src/google/protobuf/util/json_util.cc @@ -0,0 +1,142 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace google { +namespace protobuf { +namespace util { + +namespace internal { +void ZeroCopyStreamByteSink::Append(const char* bytes, size_t len) { + while (len > 0) { + void* buffer; + int length; + if (!stream_->Next(&buffer, &length)) { + // There isn't a way for ByteSink to report errors. + return; + } + if (len < length) { + memcpy(buffer, bytes, len); + stream_->BackUp(length - len); + break; + } else { + memcpy(buffer, bytes, length); + bytes += length; + len -= length; + } + } +} +} // namespace internal + +util::Status BinaryToJsonStream(TypeResolver* resolver, + const string& type_url, + io::ZeroCopyInputStream* binary_input, + io::ZeroCopyOutputStream* json_output, + const JsonOptions& options) { + io::CodedInputStream in_stream(binary_input); + google::protobuf::Type type; + RETURN_IF_ERROR(resolver->ResolveMessageType(type_url, &type)); + converter::ProtoStreamObjectSource proto_source(&in_stream, resolver, type); + io::CodedOutputStream out_stream(json_output); + converter::JsonObjectWriter json_writer(options.add_whitespace ? " " : "", + &out_stream); + converter::Snake2CamelObjectWriter snake2camel_writer(&json_writer); + if (options.always_print_primitive_fields) { + converter::DefaultValueObjectWriter default_value_writer( + resolver, type, &snake2camel_writer); + return proto_source.WriteTo(&default_value_writer); + } else { + return proto_source.WriteTo(&snake2camel_writer); + } +} + +util::Status BinaryToJsonString(TypeResolver* resolver, + const string& type_url, + const string& binary_input, + string* json_output, + const JsonOptions& options) { + io::ArrayInputStream input_stream(binary_input.data(), binary_input.size()); + io::StringOutputStream output_stream(json_output); + return BinaryToJsonStream(resolver, type_url, &input_stream, &output_stream, + options); +} + +util::Status JsonToBinaryStream(TypeResolver* resolver, + const string& type_url, + io::ZeroCopyInputStream* json_input, + io::ZeroCopyOutputStream* binary_output) { + google::protobuf::Type type; + RETURN_IF_ERROR(resolver->ResolveMessageType(type_url, &type)); + internal::ZeroCopyStreamByteSink sink(binary_output); + converter::NoopErrorListener listener; + converter::ProtoStreamObjectWriter proto_writer(resolver, type, &sink, + &listener); + + converter::JsonStreamParser parser(&proto_writer); + const void* buffer; + int length; + while (json_input->Next(&buffer, &length)) { + if (length == 0) continue; + RETURN_IF_ERROR( + parser.Parse(StringPiece(static_cast(buffer), length))); + } + RETURN_IF_ERROR(parser.FinishParse()); + + return util::Status::OK; +} + +util::Status JsonToBinaryString(TypeResolver* resolver, + const string& type_url, + const string& json_input, + string* binary_output) { + io::ArrayInputStream input_stream(json_input.data(), json_input.size()); + io::StringOutputStream output_stream(binary_output); + return JsonToBinaryStream(resolver, type_url, &input_stream, &output_stream); +} + +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/util/json_util.h b/src/google/protobuf/util/json_util.h new file mode 100644 index 00000000..86594487 --- /dev/null +++ b/src/google/protobuf/util/json_util.h @@ -0,0 +1,132 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Utility functions to convert between protobuf binary format and proto3 JSON +// format. +#ifndef GOOGLE_PROTOBUF_UTIL_JSON_UTIL_H__ +#define GOOGLE_PROTOBUF_UTIL_JSON_UTIL_H__ + +#include +#include + +namespace google { +namespace protobuf { +namespace io { +class ZeroCopyInputStream; +class ZeroCopyOutputStream; +} // namespace io +namespace util { + +struct JsonOptions { + // Whether to add spaces, line breaks and indentation to make the JSON output + // easy to read. + bool add_whitespace; + // Whether to always print primitive fields. By default primitive fields with + // default values will be omitted in JSON joutput. For example, an int32 field + // set to 0 will be omitted. Set this flag to true will override the default + // behavior and print primitive fields regardless of their values. + bool always_print_primitive_fields; + + JsonOptions() : add_whitespace(false), + always_print_primitive_fields(false) { + } +}; + +// Converts protobuf binary data to JSON. +// The conversion will fail if: +// 1. TypeResolver fails to resolve a type. +// 2. input is not valid protobuf wire format, or conflicts with the type +// information returned by TypeResolver. +// Note that unknown fields will be discarded silently. +util::Status BinaryToJsonStream(TypeResolver* resolver, + const string& type_url, + io::ZeroCopyInputStream* binary_input, + io::ZeroCopyOutputStream* json_output, + const JsonOptions& options); + +inline util::Status BinaryToJsonStream( + TypeResolver* resolver, const string& type_url, + io::ZeroCopyInputStream* binary_input, + io::ZeroCopyOutputStream* json_output) { + return BinaryToJsonStream(resolver, type_url, binary_input, json_output, + JsonOptions()); +} + +util::Status BinaryToJsonString(TypeResolver* resolver, + const string& type_url, + const string& binary_input, + string* json_output, + const JsonOptions& options); + +inline util::Status BinaryToJsonString(TypeResolver* resolver, + const string& type_url, + const string& binary_input, + string* json_output) { + return BinaryToJsonString(resolver, type_url, binary_input, json_output, + JsonOptions()); +} + +// Converts JSON data to protobuf binary format. +// The conversion will fail if: +// 1. TypeResolver fails to resolve a type. +// 2. input is not valid JSON format, or conflicts with the type +// information returned by TypeResolver. +// 3. input has unknown fields. +util::Status JsonToBinaryStream(TypeResolver* resolver, + const string& type_url, + io::ZeroCopyInputStream* json_input, + io::ZeroCopyOutputStream* binary_output); + +util::Status JsonToBinaryString(TypeResolver* resolver, + const string& type_url, + const string& json_input, + string* binary_output); + +namespace internal { +// Internal helper class. Put in the header so we can write unit-tests for it. +class LIBPROTOBUF_EXPORT ZeroCopyStreamByteSink : public strings::ByteSink { + public: + explicit ZeroCopyStreamByteSink(io::ZeroCopyOutputStream* stream) + : stream_(stream) {} + + virtual void Append(const char* bytes, size_t len); + + private: + io::ZeroCopyOutputStream* stream_; + + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(ZeroCopyStreamByteSink); +}; +} // namespace internal + +} // namespace util +} // namespace protobuf + +} // namespace google +#endif // GOOGLE_PROTOBUF_UTIL_JSON_UTIL_H__ diff --git a/src/google/protobuf/util/json_util_test.cc b/src/google/protobuf/util/json_util_test.cc new file mode 100644 index 00000000..8399b408 --- /dev/null +++ b/src/google/protobuf/util/json_util_test.cc @@ -0,0 +1,277 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include +#include + +#include +#include +#include +#include +#include + +namespace google { +namespace protobuf { +namespace util { +namespace { + +using proto3::FOO; +using proto3::BAR; +using proto3::TestMessage; + +static const char kTypeUrlPrefix[] = "type.googleapis.com"; + +static string GetTypeUrl(const Descriptor* message) { + return string(kTypeUrlPrefix) + "/" + message->full_name(); +} + +// As functions defined in json_util.h are just thin wrappers around the +// JSON conversion code in //net/proto2/util/converter, in this test we +// only cover some very basic cases to make sure the wrappers have forwarded +// parameters to the underlying implementation correctly. More detailed +// tests are contained in the //net/proto2/util/converter directory. +class JsonUtilTest : public testing::Test { + protected: + JsonUtilTest() { + resolver_.reset(NewTypeResolverForDescriptorPool( + kTypeUrlPrefix, DescriptorPool::generated_pool())); + } + + string ToJson(const Message& message, const JsonOptions& options) { + string result; + GOOGLE_CHECK_OK(BinaryToJsonString(resolver_.get(), + GetTypeUrl(message.GetDescriptor()), + message.SerializeAsString(), &result, options)); + return result; + } + + bool FromJson(const string& json, Message* message) { + string binary; + GOOGLE_CHECK_OK(JsonToBinaryString( + resolver_.get(), GetTypeUrl(message->GetDescriptor()), json, &binary)); + return message->ParseFromString(binary); + } + + google::protobuf::scoped_ptr resolver_; +}; + +TEST_F(JsonUtilTest, TestWhitespaces) { + TestMessage m; + m.mutable_message_value(); + + JsonOptions options; + EXPECT_EQ("{\"messageValue\":{}}", ToJson(m, options)); + options.add_whitespace = true; + EXPECT_EQ( + "{\n" + " \"messageValue\": {}\n" + "}\n", + ToJson(m, options)); +} + +TEST_F(JsonUtilTest, TestDefaultValues) { + TestMessage m; + JsonOptions options; + EXPECT_EQ("{}", ToJson(m, options)); + options.always_print_primitive_fields = true; + EXPECT_EQ( + "{\"boolValue\":false," + "\"int32Value\":0," + "\"int64Value\":\"0\"," + "\"uint32Value\":0," + "\"uint64Value\":\"0\"," + "\"floatValue\":0," + "\"doubleValue\":0," + "\"stringValue\":\"\"," + "\"bytesValue\":\"\"," + // TODO(xiaofeng): The default enum value should be FOO. I believe + // this is a bug in DefaultValueObjectWriter. + "\"enumValue\":null" + "}", + ToJson(m, options)); +} + +TEST_F(JsonUtilTest, ParseMessage) { + // Some random message but good enough to verify that the parsing warpper + // functions are working properly. + string input = + "{\n" + " \"int32Value\": 1024,\n" + " \"repeatedInt32Value\": [1, 2],\n" + " \"messageValue\": {\n" + " \"value\": 2048\n" + " },\n" + " \"repeatedMessageValue\": [\n" + " {\"value\": 40}, {\"value\": 96}\n" + " ]\n" + "}\n"; + TestMessage m; + ASSERT_TRUE(FromJson(input, &m)); + EXPECT_EQ(1024, m.int32_value()); + ASSERT_EQ(2, m.repeated_int32_value_size()); + EXPECT_EQ(1, m.repeated_int32_value(0)); + EXPECT_EQ(2, m.repeated_int32_value(1)); + EXPECT_EQ(2048, m.message_value().value()); + ASSERT_EQ(2, m.repeated_message_value_size()); + EXPECT_EQ(40, m.repeated_message_value(0).value()); + EXPECT_EQ(96, m.repeated_message_value(1).value()); +} + +typedef pair Segment; +// A ZeroCopyOutputStream that writes to multiple buffers. +class SegmentedZeroCopyOutputStream : public io::ZeroCopyOutputStream { + public: + explicit SegmentedZeroCopyOutputStream(list segments) + : segments_(segments), last_segment_(NULL, 0), byte_count_(0) {} + + virtual bool Next(void** buffer, int* length) { + if (segments_.empty()) { + return false; + } + last_segment_ = segments_.front(); + segments_.pop_front(); + *buffer = last_segment_.first; + *length = last_segment_.second; + byte_count_ += *length; + return true; + } + + virtual void BackUp(int length) { + GOOGLE_CHECK(length <= last_segment_.second); + segments_.push_front( + Segment(last_segment_.first + last_segment_.second - length, length)); + last_segment_ = Segment(last_segment_.first, last_segment_.second - length); + byte_count_ -= length; + } + + virtual int64 ByteCount() const { return byte_count_; } + + private: + list segments_; + Segment last_segment_; + int64 byte_count_; +}; + +// This test splits the output buffer and also the input data into multiple +// segments and checks that the implementation of ZeroCopyStreamByteSink +// handles all possible cases correctly. +TEST(ZeroCopyStreamByteSinkTest, TestAllInputOutputPatterns) { + static const int kOutputBufferLength = 10; + // An exhaustive test takes too long, skip some combinations to make the test + // run faster. + static const int kSkippedPatternCount = 7; + + char buffer[kOutputBufferLength]; + for (int split_pattern = 0; split_pattern < (1 << (kOutputBufferLength - 1)); + split_pattern += kSkippedPatternCount) { + // Split the buffer into small segments according to the split_pattern. + list segments; + int segment_start = 0; + for (int i = 0; i < kOutputBufferLength - 1; ++i) { + if (split_pattern & (1 << i)) { + segments.push_back( + Segment(buffer + segment_start, i - segment_start + 1)); + segment_start = i + 1; + } + } + segments.push_back( + Segment(buffer + segment_start, kOutputBufferLength - segment_start)); + + // Write exactly 10 bytes through the ByteSink. + string input_data = "0123456789"; + for (int input_pattern = 0; input_pattern < (1 << (input_data.size() - 1)); + input_pattern += kSkippedPatternCount) { + memset(buffer, 0, sizeof(buffer)); + { + SegmentedZeroCopyOutputStream output_stream(segments); + internal::ZeroCopyStreamByteSink byte_sink(&output_stream); + int start = 0; + for (int j = 0; j < input_data.length() - 1; ++j) { + if (input_pattern & (1 << j)) { + byte_sink.Append(&input_data[start], j - start + 1); + start = j + 1; + } + } + byte_sink.Append(&input_data[start], input_data.length() - start); + } + EXPECT_EQ(input_data, string(buffer, input_data.length())); + } + + // Write only 9 bytes through the ByteSink. + input_data = "012345678"; + for (int input_pattern = 0; input_pattern < (1 << (input_data.size() - 1)); + input_pattern += kSkippedPatternCount) { + memset(buffer, 0, sizeof(buffer)); + { + SegmentedZeroCopyOutputStream output_stream(segments); + internal::ZeroCopyStreamByteSink byte_sink(&output_stream); + int start = 0; + for (int j = 0; j < input_data.length() - 1; ++j) { + if (input_pattern & (1 << j)) { + byte_sink.Append(&input_data[start], j - start + 1); + start = j + 1; + } + } + byte_sink.Append(&input_data[start], input_data.length() - start); + } + EXPECT_EQ(input_data, string(buffer, input_data.length())); + EXPECT_EQ(0, buffer[input_data.length()]); + } + + // Write 11 bytes through the ByteSink. The extra byte will just + // be ignored. + input_data = "0123456789A"; + for (int input_pattern = 0; input_pattern < (1 << (input_data.size() - 1)); + input_pattern += kSkippedPatternCount) { + memset(buffer, 0, sizeof(buffer)); + { + SegmentedZeroCopyOutputStream output_stream(segments); + internal::ZeroCopyStreamByteSink byte_sink(&output_stream); + int start = 0; + for (int j = 0; j < input_data.length() - 1; ++j) { + if (input_pattern & (1 << j)) { + byte_sink.Append(&input_data[start], j - start + 1); + start = j + 1; + } + } + byte_sink.Append(&input_data[start], input_data.length() - start); + } + EXPECT_EQ(input_data.substr(0, kOutputBufferLength), + string(buffer, kOutputBufferLength)); + } + } +} + +} // namespace +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/util/message_differencer.cc b/src/google/protobuf/util/message_differencer.cc new file mode 100644 index 00000000..50485633 --- /dev/null +++ b/src/google/protobuf/util/message_differencer.cc @@ -0,0 +1,1629 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: jschorr@google.com (Joseph Schorr) +// Based on original Protocol Buffers design by +// Sanjay Ghemawat, Jeff Dean, and others. +// +// This file defines static methods and classes for comparing Protocol +// Messages (see //google/protobuf/util/message_differencer.h for more +// information). + +#include + +#include +#include +#ifndef _SHARED_PTR_H +#include +#endif +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace google { +namespace protobuf { + +namespace util { + +// When comparing a repeated field as map, MultipleFieldMapKeyComparator can +// be used to specify multiple fields as key for key comparison. +// Two elements of a repeated field will be regarded as having the same key +// iff they have the same value for every specified key field. +// Note that you can also specify only one field as key. +class MessageDifferencer::MultipleFieldsMapKeyComparator + : public MessageDifferencer::MapKeyComparator { + public: + MultipleFieldsMapKeyComparator( + MessageDifferencer* message_differencer, + const vector >& key_field_paths) + : message_differencer_(message_differencer), + key_field_paths_(key_field_paths) { + GOOGLE_CHECK(!key_field_paths_.empty()); + for (int i = 0; i < key_field_paths_.size(); ++i) { + GOOGLE_CHECK(!key_field_paths_[i].empty()); + } + } + MultipleFieldsMapKeyComparator( + MessageDifferencer* message_differencer, + const FieldDescriptor* key) + : message_differencer_(message_differencer) { + vector key_field_path; + key_field_path.push_back(key); + key_field_paths_.push_back(key_field_path); + } + bool IsMatch( + const Message& message1, + const Message& message2, + const vector& parent_fields) const override { + for (int i = 0; i < key_field_paths_.size(); ++i) { + if (!IsMatchInternal(message1, message2, parent_fields, + key_field_paths_[i], 0)) { + return false; + } + } + return true; + } + private: + bool IsMatchInternal( + const Message& message1, + const Message& message2, + const vector& parent_fields, + const vector& key_field_path, + int path_index) const { + const FieldDescriptor* field = key_field_path[path_index]; + vector current_parent_fields(parent_fields); + if (path_index == key_field_path.size() - 1) { + if (field->is_repeated()) { + if (!message_differencer_->CompareRepeatedField( + message1, message2, field, ¤t_parent_fields)) { + return false; + } + } else { + if (!message_differencer_->CompareFieldValueUsingParentFields( + message1, message2, field, -1, -1, ¤t_parent_fields)) { + return false; + } + } + return true; + } else { + const Reflection* reflection1 = message1.GetReflection(); + const Reflection* reflection2 = message2.GetReflection(); + bool has_field1 = reflection1->HasField(message1, field); + bool has_field2 = reflection2->HasField(message2, field); + if (!has_field1 && !has_field2) { + return true; + } + if (has_field1 != has_field2) { + return false; + } + SpecificField specific_field; + specific_field.field = field; + current_parent_fields.push_back(specific_field); + return IsMatchInternal( + reflection1->GetMessage(message1, field), + reflection2->GetMessage(message2, field), + current_parent_fields, + key_field_path, + path_index + 1); + } + } + MessageDifferencer* message_differencer_; + vector > key_field_paths_; + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(MultipleFieldsMapKeyComparator); +}; + +bool MessageDifferencer::Equals(const Message& message1, + const Message& message2) { + MessageDifferencer differencer; + + return differencer.Compare(message1, message2); +} + +bool MessageDifferencer::Equivalent(const Message& message1, + const Message& message2) { + MessageDifferencer differencer; + differencer.set_message_field_comparison(MessageDifferencer::EQUIVALENT); + + return differencer.Compare(message1, message2); +} + +bool MessageDifferencer::ApproximatelyEquals(const Message& message1, + const Message& message2) { + MessageDifferencer differencer; + differencer.set_float_comparison( + MessageDifferencer::APPROXIMATE); + + return differencer.Compare(message1, message2); +} + +bool MessageDifferencer::ApproximatelyEquivalent(const Message& message1, + const Message& message2) { + MessageDifferencer differencer; + differencer.set_message_field_comparison(MessageDifferencer::EQUIVALENT); + differencer.set_float_comparison(MessageDifferencer::APPROXIMATE); + + return differencer.Compare(message1, message2); +} + +// =========================================================================== + +MessageDifferencer::MessageDifferencer() + : reporter_(NULL), + field_comparator_(NULL), + message_field_comparison_(EQUAL), + scope_(FULL), + repeated_field_comparison_(AS_LIST), + report_matches_(false), + output_string_(NULL) { } + +MessageDifferencer::~MessageDifferencer() { + for (int i = 0; i < owned_key_comparators_.size(); ++i) { + delete owned_key_comparators_[i]; + } + for (int i = 0; i < ignore_criteria_.size(); ++i) { + delete ignore_criteria_[i]; + } +} + +void MessageDifferencer::set_field_comparator(FieldComparator* comparator) { + GOOGLE_CHECK(comparator) << "Field comparator can't be NULL."; + field_comparator_ = comparator; +} + +void MessageDifferencer::set_message_field_comparison( + MessageFieldComparison comparison) { + message_field_comparison_ = comparison; +} + +void MessageDifferencer::set_scope(Scope scope) { + scope_ = scope; +} + +MessageDifferencer::Scope MessageDifferencer::scope() { + return scope_; +} + +void MessageDifferencer::set_float_comparison(FloatComparison comparison) { + default_field_comparator_.set_float_comparison( + comparison == EXACT ? + DefaultFieldComparator::EXACT : DefaultFieldComparator::APPROXIMATE); +} + +void MessageDifferencer::set_repeated_field_comparison( + RepeatedFieldComparison comparison) { + repeated_field_comparison_ = comparison; +} + +void MessageDifferencer::TreatAsSet(const FieldDescriptor* field) { + GOOGLE_CHECK(field->is_repeated()) << "Field must be repeated: " + << field->full_name(); + const MapKeyComparator* key_comparator = GetMapKeyComparator(field); + GOOGLE_CHECK(key_comparator == NULL) + << "Cannot treat this repeated field as both Map and Set for" + << " comparison. Field name is: " << field->full_name(); + set_fields_.insert(field); +} + +void MessageDifferencer::TreatAsMap(const FieldDescriptor* field, + const FieldDescriptor* key) { + GOOGLE_CHECK(field->is_repeated()) << "Field must be repeated: " + << field->full_name(); + GOOGLE_CHECK_EQ(FieldDescriptor::CPPTYPE_MESSAGE, field->cpp_type()) + << "Field has to be message type. Field name is: " + << field->full_name(); + GOOGLE_CHECK(key->containing_type() == field->message_type()) + << key->full_name() + << " must be a direct subfield within the repeated field " + << field->full_name() << ", not " << key->containing_type()->full_name(); + GOOGLE_CHECK(set_fields_.find(field) == set_fields_.end()) + << "Cannot treat this repeated field as both Map and Set for " + << "comparison."; + MapKeyComparator* key_comparator = + new MultipleFieldsMapKeyComparator(this, key); + owned_key_comparators_.push_back(key_comparator); + map_field_key_comparator_[field] = key_comparator; +} + +void MessageDifferencer::TreatAsMapWithMultipleFieldsAsKey( + const FieldDescriptor* field, + const vector& key_fields) { + vector > key_field_paths; + for (int i = 0; i < key_fields.size(); ++i) { + vector key_field_path; + key_field_path.push_back(key_fields[i]); + key_field_paths.push_back(key_field_path); + } + TreatAsMapWithMultipleFieldPathsAsKey(field, key_field_paths); +} + +void MessageDifferencer::TreatAsMapWithMultipleFieldPathsAsKey( + const FieldDescriptor* field, + const vector >& key_field_paths) { + GOOGLE_CHECK(field->is_repeated()) << "Field must be repeated: " + << field->full_name(); + GOOGLE_CHECK_EQ(FieldDescriptor::CPPTYPE_MESSAGE, field->cpp_type()) + << "Field has to be message type. Field name is: " + << field->full_name(); + for (int i = 0; i < key_field_paths.size(); ++i) { + const vector& key_field_path = key_field_paths[i]; + for (int j = 0; j < key_field_path.size(); ++j) { + const FieldDescriptor* parent_field = + j == 0 ? field : key_field_path[j - 1]; + const FieldDescriptor* child_field = key_field_path[j]; + GOOGLE_CHECK(child_field->containing_type() == parent_field->message_type()) + << child_field->full_name() + << " must be a direct subfield within the field: " + << parent_field->full_name(); + if (j != 0) { + GOOGLE_CHECK_EQ(FieldDescriptor::CPPTYPE_MESSAGE, parent_field->cpp_type()) + << parent_field->full_name() << " has to be of type message."; + GOOGLE_CHECK(!parent_field->is_repeated()) + << parent_field->full_name() << " cannot be a repeated field."; + } + } + } + GOOGLE_CHECK(set_fields_.find(field) == set_fields_.end()) + << "Cannot treat this repeated field as both Map and Set for " + << "comparison."; + MapKeyComparator* key_comparator = + new MultipleFieldsMapKeyComparator(this, key_field_paths); + owned_key_comparators_.push_back(key_comparator); + map_field_key_comparator_[field] = key_comparator; +} + +void MessageDifferencer::TreatAsMapUsingKeyComparator( + const FieldDescriptor* field, + const MapKeyComparator* key_comparator) { + GOOGLE_CHECK(field->is_repeated()) << "Field must be repeated: " + << field->full_name(); + GOOGLE_CHECK_EQ(FieldDescriptor::CPPTYPE_MESSAGE, field->cpp_type()) + << "Field has to be message type. Field name is: " + << field->full_name(); + GOOGLE_CHECK(set_fields_.find(field) == set_fields_.end()) + << "Cannot treat this repeated field as both Map and Set for " + << "comparison."; + map_field_key_comparator_[field] = key_comparator; +} + +void MessageDifferencer::AddIgnoreCriteria(IgnoreCriteria* ignore_criteria) { + ignore_criteria_.push_back(ignore_criteria); +} + +void MessageDifferencer::IgnoreField(const FieldDescriptor* field) { + ignored_fields_.insert(field); +} + +void MessageDifferencer::SetFractionAndMargin(const FieldDescriptor* field, + double fraction, double margin) { + default_field_comparator_.SetFractionAndMargin(field, fraction, margin); +} + +void MessageDifferencer::ReportDifferencesToString(string* output) { + GOOGLE_DCHECK(output) << "Specified output string was NULL"; + + output_string_ = output; + output_string_->clear(); +} + +void MessageDifferencer::ReportDifferencesTo(Reporter* reporter) { + // If an output string is set, clear it to prevent + // it superceding the specified reporter. + if (output_string_) { + output_string_ = NULL; + } + + reporter_ = reporter; +} + +bool MessageDifferencer::FieldBefore(const FieldDescriptor* field1, + const FieldDescriptor* field2) { + // Handle sentinel values (i.e. make sure NULLs are always ordered + // at the end of the list). + if (field1 == NULL) { + return false; + } + + if (field2 == NULL) { + return true; + } + + // Always order fields by their tag number + return (field1->number() < field2->number()); +} + +bool MessageDifferencer::Compare(const Message& message1, + const Message& message2) { + vector parent_fields; + + bool result = false; + + // Setup the internal reporter if need be. + if (output_string_) { + io::StringOutputStream output_stream(output_string_); + StreamReporter reporter(&output_stream); + reporter_ = &reporter; + result = Compare(message1, message2, &parent_fields); + reporter_ = NULL; + } else { + result = Compare(message1, message2, &parent_fields); + } + + return result; +} + +bool MessageDifferencer::CompareWithFields( + const Message& message1, + const Message& message2, + const vector& message1_fields_arg, + const vector& message2_fields_arg) { + if (message1.GetDescriptor() != message2.GetDescriptor()) { + GOOGLE_LOG(DFATAL) << "Comparison between two messages with different " + << "descriptors."; + return false; + } + + vector parent_fields; + + bool result = false; + + vector message1_fields(message1_fields_arg); + vector message2_fields(message2_fields_arg); + + std::sort(message1_fields.begin(), message1_fields.end(), FieldBefore); + std::sort(message2_fields.begin(), message2_fields.end(), FieldBefore); + // Append NULL sentinel values. + message1_fields.push_back(NULL); + message2_fields.push_back(NULL); + + // Setup the internal reporter if need be. + if (output_string_) { + io::StringOutputStream output_stream(output_string_); + StreamReporter reporter(&output_stream); + reporter_ = &reporter; + result = CompareRequestedFieldsUsingSettings( + message1, message2, message1_fields, message2_fields, &parent_fields); + reporter_ = NULL; + } else { + result = CompareRequestedFieldsUsingSettings( + message1, message2, message1_fields, message2_fields, &parent_fields); + } + + return result; +} + +bool MessageDifferencer::Compare( + const Message& message1, + const Message& message2, + vector* parent_fields) { + const Descriptor* descriptor1 = message1.GetDescriptor(); + const Descriptor* descriptor2 = message2.GetDescriptor(); + if (descriptor1 != descriptor2) { + GOOGLE_LOG(DFATAL) << "Comparison between two messages with different " + << "descriptors."; + return false; + } + // Expand google.protobuf.Any payload if possible. + if (descriptor1->full_name() == internal::kAnyFullTypeName) { + google::protobuf::scoped_ptr data1; + google::protobuf::scoped_ptr data2; + if (UnpackAny(message1, &data1) && UnpackAny(message2, &data2)) { + return Compare(*data1, *data2, parent_fields); + } + } + const Reflection* reflection1 = message1.GetReflection(); + const Reflection* reflection2 = message2.GetReflection(); + + // Retrieve all the set fields, including extensions. + vector message1_fields; + vector message2_fields; + + 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 + // each list are different. + message1_fields.push_back(NULL); + message2_fields.push_back(NULL); + + bool unknown_compare_result = true; + // Ignore unknown fields in EQUIVALENT mode + if (message_field_comparison_ != EQUIVALENT) { + const google::protobuf::UnknownFieldSet* unknown_field_set1 = + &reflection1->GetUnknownFields(message1); + const google::protobuf::UnknownFieldSet* unknown_field_set2 = + &reflection2->GetUnknownFields(message2); + if (!CompareUnknownFields(message1, message2, + *unknown_field_set1, *unknown_field_set2, + parent_fields)) { + if (reporter_ == NULL) { + return false; + }; + unknown_compare_result = false; + } + } + + return CompareRequestedFieldsUsingSettings( + message1, message2, + message1_fields, message2_fields, + parent_fields) && unknown_compare_result; +} + +bool MessageDifferencer::CompareRequestedFieldsUsingSettings( + const Message& message1, + const Message& message2, + const vector& message1_fields, + const vector& message2_fields, + vector* parent_fields) { + if (scope_ == FULL) { + if (message_field_comparison_ == EQUIVALENT) { + // We need to merge the field lists of both messages (i.e. + // we are merely checking for a difference in field values, + // rather than the addition or deletion of fields). + vector fields_union; + CombineFields(message1_fields, FULL, message2_fields, FULL, + &fields_union); + return CompareWithFieldsInternal(message1, message2, fields_union, + fields_union, parent_fields); + } else { + // Simple equality comparison, use the unaltered field lists. + return CompareWithFieldsInternal(message1, message2, message1_fields, + message2_fields, parent_fields); + } + } else { + if (message_field_comparison_ == EQUIVALENT) { + // We use the list of fields for message1 for both messages when + // comparing. This way, extra fields in message2 are ignored, + // and missing fields in message2 use their default value. + return CompareWithFieldsInternal(message1, message2, message1_fields, + message1_fields, parent_fields); + } else { + // We need to consider the full list of fields for message1 + // but only the intersection for message2. This way, any fields + // only present in message2 will be ignored, but any fields only + // present in message1 will be marked as a difference. + vector fields_intersection; + CombineFields(message1_fields, PARTIAL, message2_fields, PARTIAL, + &fields_intersection); + return CompareWithFieldsInternal(message1, message2, message1_fields, + fields_intersection, parent_fields); + } + } +} + +void MessageDifferencer::CombineFields( + const vector& fields1, + Scope fields1_scope, + const vector& fields2, + Scope fields2_scope, + vector* combined_fields) { + + int index1 = 0; + int index2 = 0; + + while (index1 < fields1.size() && index2 < fields2.size()) { + const FieldDescriptor* field1 = fields1[index1]; + const FieldDescriptor* field2 = fields2[index2]; + + if (FieldBefore(field1, field2)) { + if (fields1_scope == FULL) { + combined_fields->push_back(fields1[index1]); + } + ++index1; + } else if (FieldBefore(field2, field1)) { + if (fields2_scope == FULL) { + combined_fields->push_back(fields2[index2]); + } + ++index2; + } else { + combined_fields->push_back(fields1[index1]); + ++index1; + ++index2; + } + } +} + +bool MessageDifferencer::CompareWithFieldsInternal( + const Message& message1, + const Message& message2, + const vector& message1_fields, + const vector& message2_fields, + vector* parent_fields) { + bool isDifferent = false; + int field_index1 = 0; + int field_index2 = 0; + + const Reflection* reflection1 = message1.GetReflection(); + const Reflection* reflection2 = message2.GetReflection(); + + while (true) { + const FieldDescriptor* field1 = message1_fields[field_index1]; + const FieldDescriptor* field2 = message2_fields[field_index2]; + + // Once we have reached sentinel values, we are done the comparison. + if (field1 == NULL && field2 == NULL) { + break; + } + + // Check for differences in the field itself. + if (FieldBefore(field1, field2)) { + // Field 1 is not in the field list for message 2. + if (IsIgnored(message1, message2, field1, *parent_fields)) { + // We are ignoring field1. Report the ignore and move on to + // the next field in message1_fields. + if (reporter_ != NULL) { + SpecificField specific_field; + specific_field.field = field1; + + parent_fields->push_back(specific_field); + reporter_->ReportIgnored(message1, message2, *parent_fields); + parent_fields->pop_back(); + } + ++field_index1; + continue; + } + + if (reporter_ != NULL) { + int count = field1->is_repeated() ? + reflection1->FieldSize(message1, field1) : 1; + + for (int i = 0; i < count; ++i) { + SpecificField specific_field; + specific_field.field = field1; + specific_field.index = field1->is_repeated() ? i : -1; + + parent_fields->push_back(specific_field); + reporter_->ReportDeleted(message1, message2, *parent_fields); + parent_fields->pop_back(); + } + + isDifferent = true; + } else { + return false; + } + + ++field_index1; + continue; + } else if (FieldBefore(field2, field1)) { + // Field 2 is not in the field list for message 1. + if (IsIgnored(message1, message2, field2, *parent_fields)) { + // We are ignoring field2. Report the ignore and move on to + // the next field in message2_fields. + if (reporter_ != NULL) { + SpecificField specific_field; + specific_field.field = field2; + + parent_fields->push_back(specific_field); + reporter_->ReportIgnored(message1, message2, *parent_fields); + parent_fields->pop_back(); + } + ++field_index2; + continue; + } + + if (reporter_ != NULL) { + int count = field2->is_repeated() ? + reflection2->FieldSize(message2, field2) : 1; + + for (int i = 0; i < count; ++i) { + SpecificField specific_field; + specific_field.field = field2; + specific_field.index = field2->is_repeated() ? i : -1; + specific_field.new_index = specific_field.index; + + parent_fields->push_back(specific_field); + reporter_->ReportAdded(message1, message2, *parent_fields); + parent_fields->pop_back(); + } + + isDifferent = true; + } else { + return false; + } + + ++field_index2; + continue; + } + + // By this point, field1 and field2 are guarenteed to point to the same + // field, so we can now compare the values. + if (IsIgnored(message1, message2, field1, *parent_fields)) { + // Ignore this field. Report and move on. + if (reporter_ != NULL) { + SpecificField specific_field; + specific_field.field = field1; + + parent_fields->push_back(specific_field); + reporter_->ReportIgnored(message1, message2, *parent_fields); + parent_fields->pop_back(); + } + + ++field_index1; + ++field_index2; + continue; + } + + bool fieldDifferent = false; + if (field1->is_repeated()) { + fieldDifferent = !CompareRepeatedField(message1, message2, field1, + parent_fields); + if (fieldDifferent) { + if (reporter_ == NULL) return false; + isDifferent = true; + } + } else { + fieldDifferent = !CompareFieldValueUsingParentFields( + message1, message2, field1, -1, -1, parent_fields); + + // If we have found differences, either report them or terminate if + // no reporter is present. + if (fieldDifferent && reporter_ == NULL) { + return false; + } + + if (reporter_ != NULL) { + SpecificField specific_field; + specific_field.field = field1; + parent_fields->push_back(specific_field); + if (fieldDifferent) { + reporter_->ReportModified(message1, message2, *parent_fields); + isDifferent = true; + } else if (report_matches_) { + reporter_->ReportMatched(message1, message2, *parent_fields); + } + parent_fields->pop_back(); + } + } + // Increment the field indicies. + ++field_index1; + ++field_index2; + } + + return !isDifferent; +} + +bool MessageDifferencer::IsMatch(const FieldDescriptor* repeated_field, + const MapKeyComparator* key_comparator, + const Message* message1, + const Message* message2, + const vector& parent_fields, + int index1, int index2) { + vector current_parent_fields(parent_fields); + if (repeated_field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE) { + return CompareFieldValueUsingParentFields( + *message1, *message2, repeated_field, index1, index2, + ¤t_parent_fields); + } + // Back up the Reporter and output_string_. They will be reset in the + // following code. + Reporter* backup_reporter = reporter_; + string* output_string = output_string_; + reporter_ = NULL; + output_string_ = NULL; + bool match; + + if (key_comparator == NULL) { + match = CompareFieldValueUsingParentFields( + *message1, *message2, repeated_field, index1, index2, + ¤t_parent_fields); + } else { + const Reflection* reflection1 = message1->GetReflection(); + const Reflection* reflection2 = message2->GetReflection(); + const Message& m1 = + reflection1->GetRepeatedMessage(*message1, repeated_field, index1); + const Message& m2 = + reflection2->GetRepeatedMessage(*message2, repeated_field, index2); + SpecificField specific_field; + specific_field.field = repeated_field; + current_parent_fields.push_back(specific_field); + match = key_comparator->IsMatch(m1, m2, current_parent_fields); + } + + reporter_ = backup_reporter; + output_string_ = output_string; + return match; +} + +bool MessageDifferencer::CompareRepeatedField( + const Message& message1, + const Message& message2, + const FieldDescriptor* repeated_field, + vector* parent_fields) { + // the input FieldDescriptor is guaranteed to be repeated field. + const Reflection* reflection1 = message1.GetReflection(); + const Reflection* reflection2 = message2.GetReflection(); + const int count1 = reflection1->FieldSize(message1, repeated_field); + const int count2 = reflection2->FieldSize(message2, repeated_field); + const bool treated_as_subset = IsTreatedAsSubset(repeated_field); + + // If the field is not treated as subset and no detailed reports is needed, + // we do a quick check on the number of the elements to avoid unnecessary + // comparison. + if (count1 != count2 && reporter_ == NULL && !treated_as_subset) { + return false; + } + // A match can never be found if message1 has more items than message2. + if (count1 > count2 && reporter_ == NULL) { + return false; + } + + // These two list are used for store the index of the correspondent + // element in peer repeated field. + vector match_list1; + vector match_list2; + + // Try to match indices of the repeated fields. Return false if match fails + // and there's no detailed report needed. + if (!MatchRepeatedFieldIndices(message1, message2, repeated_field, + *parent_fields, &match_list1, &match_list2) && + reporter_ == NULL) { + return false; + } + + bool fieldDifferent = false; + SpecificField specific_field; + specific_field.field = repeated_field; + + // At this point, we have already matched pairs of fields (with the reporting + // to be done later). Now to check if the paired elements are different. + for (int i = 0; i < count1; i++) { + if (match_list1[i] == -1) continue; + specific_field.index = i; + specific_field.new_index = match_list1[i]; + + const bool result = CompareFieldValueUsingParentFields( + message1, message2, repeated_field, i, specific_field.new_index, + parent_fields); + + // If we have found differences, either report them or terminate if + // no reporter is present. Note that ReportModified, ReportMoved, and + // ReportMatched are all mutually exclusive. + if (!result) { + if (reporter_ == NULL) return false; + parent_fields->push_back(specific_field); + reporter_->ReportModified(message1, message2, *parent_fields); + parent_fields->pop_back(); + fieldDifferent = true; + } else if (reporter_ != NULL && + specific_field.index != specific_field.new_index) { + parent_fields->push_back(specific_field); + reporter_->ReportMoved(message1, message2, *parent_fields); + parent_fields->pop_back(); + } else if (report_matches_ && reporter_ != NULL) { + parent_fields->push_back(specific_field); + reporter_->ReportMatched(message1, message2, *parent_fields); + parent_fields->pop_back(); + } + } + + // Report any remaining additions or deletions. + for (int i = 0; i < count2; ++i) { + if (match_list2[i] != -1) continue; + if (!treated_as_subset) { + fieldDifferent = true; + } + + if (reporter_ == NULL) continue; + specific_field.index = i; + specific_field.new_index = i; + parent_fields->push_back(specific_field); + reporter_->ReportAdded(message1, message2, *parent_fields); + parent_fields->pop_back(); + } + + for (int i = 0; i < count1; ++i) { + if (match_list1[i] != -1) continue; + specific_field.index = i; + parent_fields->push_back(specific_field); + reporter_->ReportDeleted(message1, message2, *parent_fields); + parent_fields->pop_back(); + fieldDifferent = true; + } + return !fieldDifferent; +} + +bool MessageDifferencer::CompareFieldValue(const Message& message1, + const Message& message2, + const FieldDescriptor* field, + int index1, + int index2) { + return CompareFieldValueUsingParentFields(message1, message2, field, index1, + index2, NULL); +} + +bool MessageDifferencer::CompareFieldValueUsingParentFields( + const Message& message1, const Message& message2, + const FieldDescriptor* field, int index1, int index2, + vector* parent_fields) { + FieldContext field_context(parent_fields); + FieldComparator::ComparisonResult result = GetFieldComparisonResult( + message1, message2, field, index1, index2, &field_context); + + if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE && + result == FieldComparator::RECURSE) { + // Get the nested messages and compare them using one of the Compare + // methods. + const Reflection* reflection1 = message1.GetReflection(); + const Reflection* reflection2 = message2.GetReflection(); + const Message& m1 = field->is_repeated() ? + reflection1->GetRepeatedMessage(message1, field, index1) : + reflection1->GetMessage(message1, field); + const Message& m2 = field->is_repeated() ? + reflection2->GetRepeatedMessage(message2, field, index2) : + reflection2->GetMessage(message2, field); + + // parent_fields is used in calls to Reporter methods. + if (parent_fields != NULL) { + // Append currently compared field to the end of parent_fields. + SpecificField specific_field; + specific_field.field = field; + specific_field.index = index1; + specific_field.new_index = index2; + parent_fields->push_back(specific_field); + const bool compare_result = Compare(m1, m2, parent_fields); + parent_fields->pop_back(); + return compare_result; + } else { + // Recreates parent_fields as if m1 and m2 had no parents. + return Compare(m1, m2); + } + } else { + return (result == FieldComparator::SAME); + } +} + +bool MessageDifferencer::CheckPathChanged( + const vector& field_path) { + for (int i = 0; i < field_path.size(); ++i) { + if (field_path[i].index != field_path[i].new_index) return true; + } + return false; +} + +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 true; + return (set_fields_.find(field) != set_fields_.end()); +} + +bool MessageDifferencer::IsTreatedAsSubset(const FieldDescriptor* field) { + return scope_ == PARTIAL && + (IsTreatedAsSet(field) || GetMapKeyComparator(field) != NULL); +} + +bool MessageDifferencer::IsIgnored( + const Message& message1, + const Message& message2, + const FieldDescriptor* field, + const vector& parent_fields) { + if (ignored_fields_.find(field) != ignored_fields_.end()) { + return true; + } + for (int i = 0; i < ignore_criteria_.size(); ++i) { + if (ignore_criteria_[i]->IsIgnored(message1, message2, field, + parent_fields)) { + return true; + } + } + return false; +} + +const MessageDifferencer::MapKeyComparator* MessageDifferencer + ::GetMapKeyComparator(const FieldDescriptor* field) { + if (!field->is_repeated()) return NULL; + if (map_field_key_comparator_.find(field) != + map_field_key_comparator_.end()) { + return map_field_key_comparator_[field]; + } + return NULL; +} + +namespace { + +typedef pair IndexUnknownFieldPair; + +struct UnknownFieldOrdering { + inline bool operator()(const IndexUnknownFieldPair& a, + const IndexUnknownFieldPair& b) const { + if (a.second->number() < b.second->number()) return true; + if (a.second->number() > b.second->number()) return false; + return a.second->type() < b.second->type(); + } +}; + +} // namespace + +bool MessageDifferencer::UnpackAny(const Message& any, + google::protobuf::scoped_ptr* data) { + const Reflection* reflection = any.GetReflection(); + const FieldDescriptor* type_url_field; + const FieldDescriptor* value_field; + if (!internal::GetAnyFieldDescriptors(any, &type_url_field, &value_field)) { + return false; + } + const string& type_url = reflection->GetString(any, type_url_field); + string full_type_name; + if (!internal::ParseAnyTypeUrl(type_url, &full_type_name)) { + return false; + } + + const google::protobuf::Descriptor* desc = + any.GetDescriptor()->file()->pool()->FindMessageTypeByName( + full_type_name); + if (desc == NULL) { + GOOGLE_LOG(ERROR) << "Proto type '" << full_type_name << "' not found"; + return false; + } + + if (dynamic_message_factory_ == NULL) { + dynamic_message_factory_.reset(new DynamicMessageFactory()); + } + data->reset(dynamic_message_factory_->GetPrototype(desc)->New()); + string serialized_value = reflection->GetString(any, value_field); + if (!(*data)->ParseFromString(serialized_value)) { + GOOGLE_LOG(ERROR) << "Failed to parse value for " << full_type_name; + return false; + } + return true; +} + +bool MessageDifferencer::CompareUnknownFields( + const Message& message1, const Message& message2, + const google::protobuf::UnknownFieldSet& unknown_field_set1, + const google::protobuf::UnknownFieldSet& unknown_field_set2, + vector* parent_field) { + // Ignore unknown fields in EQUIVALENT mode. + if (message_field_comparison_ == EQUIVALENT) return true; + + if (unknown_field_set1.empty() && unknown_field_set2.empty()) { + return true; + } + + bool is_different = false; + + // We first sort the unknown fields by field number and type (in other words, + // in tag order), making sure to preserve ordering of values with the same + // tag. This allows us to report only meaningful differences between the + // two sets -- that is, differing values for the same tag. We use + // IndexUnknownFieldPairs to keep track of the field's original index for + // reporting purposes. + vector fields1; // unknown_field_set1, sorted + vector fields2; // unknown_field_set2, sorted + fields1.reserve(unknown_field_set1.field_count()); + fields2.reserve(unknown_field_set2.field_count()); + + for (int i = 0; i < unknown_field_set1.field_count(); i++) { + fields1.push_back(std::make_pair(i, &unknown_field_set1.field(i))); + } + for (int i = 0; i < unknown_field_set2.field_count(); i++) { + fields2.push_back(std::make_pair(i, &unknown_field_set2.field(i))); + } + + UnknownFieldOrdering is_before; + std::stable_sort(fields1.begin(), fields1.end(), is_before); + std::stable_sort(fields2.begin(), fields2.end(), is_before); + + // In order to fill in SpecificField::index, we have to keep track of how + // many values we've seen with the same field number and type. + // current_repeated points at the first field in this range, and + // current_repeated_start{1,2} are the indexes of the first field in the + // range within fields1 and fields2. + const UnknownField* current_repeated = NULL; + int current_repeated_start1 = 0; + int current_repeated_start2 = 0; + + // Now that we have two sorted lists, we can detect fields which appear only + // in one list or the other by traversing them simultaneously. + int index1 = 0; + int index2 = 0; + while (index1 < fields1.size() || index2 < fields2.size()) { + enum { ADDITION, DELETION, MODIFICATION, COMPARE_GROUPS, + NO_CHANGE } change_type; + + // focus_field is the field we're currently reporting on. (In the case + // of a modification, it's the field on the left side.) + const UnknownField* focus_field; + bool match = false; + + if (index2 == fields2.size() || + (index1 < fields1.size() && + is_before(fields1[index1], fields2[index2]))) { + // fields1[index1] is not present in fields2. + change_type = DELETION; + focus_field = fields1[index1].second; + } else if (index1 == fields1.size() || + is_before(fields2[index2], fields1[index1])) { + // fields2[index2] is not present in fields1. + if (scope_ == PARTIAL) { + // Ignore. + ++index2; + continue; + } + change_type = ADDITION; + focus_field = fields2[index2].second; + } else { + // Field type and number are the same. See if the values differ. + change_type = MODIFICATION; + focus_field = fields1[index1].second; + + switch (focus_field->type()) { + case UnknownField::TYPE_VARINT: + match = fields1[index1].second->varint() == + fields2[index2].second->varint(); + break; + case UnknownField::TYPE_FIXED32: + match = fields1[index1].second->fixed32() == + fields2[index2].second->fixed32(); + break; + case UnknownField::TYPE_FIXED64: + match = fields1[index1].second->fixed64() == + fields2[index2].second->fixed64(); + break; + case UnknownField::TYPE_LENGTH_DELIMITED: + match = fields1[index1].second->length_delimited() == + fields2[index2].second->length_delimited(); + break; + case UnknownField::TYPE_GROUP: + // We must deal with this later, after building the SpecificField. + change_type = COMPARE_GROUPS; + break; + } + if (match && change_type != COMPARE_GROUPS) { + change_type = NO_CHANGE; + } + } + + if (current_repeated == NULL || + focus_field->number() != current_repeated->number() || + focus_field->type() != current_repeated->type()) { + // We've started a new repeated field. + current_repeated = focus_field; + current_repeated_start1 = index1; + current_repeated_start2 = index2; + } + + if (change_type == NO_CHANGE && reporter_ == NULL) { + // Fields were already compared and matched and we have no reporter. + ++index1; + ++index2; + continue; + } + + if (change_type == ADDITION || change_type == DELETION || + change_type == MODIFICATION) { + if (reporter_ == NULL) { + // We found a difference and we have no reproter. + return false; + } + is_different = true; + } + + // Build the SpecificField. This is slightly complicated. + SpecificField specific_field; + specific_field.unknown_field_number = focus_field->number(); + specific_field.unknown_field_type = focus_field->type(); + + specific_field.unknown_field_set1 = &unknown_field_set1; + specific_field.unknown_field_set2 = &unknown_field_set2; + + if (change_type != ADDITION) { + specific_field.unknown_field_index1 = fields1[index1].first; + } + if (change_type != DELETION) { + specific_field.unknown_field_index2 = fields2[index2].first; + } + + // Calculate the field index. + if (change_type == ADDITION) { + specific_field.index = index2 - current_repeated_start2; + specific_field.new_index = index2 - current_repeated_start2; + } else { + specific_field.index = index1 - current_repeated_start1; + specific_field.new_index = index2 - current_repeated_start2; + } + + parent_field->push_back(specific_field); + + switch (change_type) { + case ADDITION: + reporter_->ReportAdded(message1, message2, *parent_field); + ++index2; + break; + case DELETION: + reporter_->ReportDeleted(message1, message2, *parent_field); + ++index1; + break; + case MODIFICATION: + reporter_->ReportModified(message1, message2, *parent_field); + ++index1; + ++index2; + break; + case COMPARE_GROUPS: + if (!CompareUnknownFields(message1, message2, + fields1[index1].second->group(), + fields2[index2].second->group(), + parent_field)) { + if (reporter_ == NULL) return false; + is_different = true; + reporter_->ReportModified(message1, message2, *parent_field); + } + ++index1; + ++index2; + break; + case NO_CHANGE: + ++index1; + ++index2; + if (report_matches_) { + reporter_->ReportMatched(message1, message2, *parent_field); + } + } + + parent_field->pop_back(); + } + + return !is_different; +} + +namespace { + +// Find maximum bipartite matching using the argumenting path algorithm. +class MaximumMatcher { + public: + typedef ResultCallback2 NodeMatchCallback; + // MaximumMatcher takes ownership of the passed in callback and uses it to + // determine whether a node on the left side of the bipartial graph matches + // a node on the right side. count1 is the number of nodes on the left side + // of the graph and count2 to is the number of nodes on the right side. + // Every node is referred to using 0-based indices. + // If a maximum match is found, the result will be stored in match_list1 and + // match_list2. match_list1[i] == j means the i-th node on the left side is + // matched to the j-th node on the right side and match_list2[x] == y means + // the x-th node on the right side is matched to y-th node on the left side. + // match_list1[i] == -1 means the node is not matched. Same with match_list2. + MaximumMatcher(int count1, int count2, NodeMatchCallback* callback, + vector* match_list1, vector* match_list2); + // Find a maximum match and return the number of matched node pairs. + // If early_return is true, this method will return 0 immediately when it + // finds that not all nodes on the left side can be matched. + int FindMaximumMatch(bool early_return); + private: + // Determines whether the node on the left side of the bipartial graph + // matches the one on the right side. + bool Match(int left, int right); + // Find an argumenting path starting from the node v on the left side. If a + // path can be found, update match_list2_ to reflect the path and return + // true. + bool FindArgumentPathDFS(int v, vector* visited); + + int count1_; + int count2_; + google::protobuf::scoped_ptr match_callback_; + map, bool> cached_match_results_; + vector* match_list1_; + vector* match_list2_; + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(MaximumMatcher); +}; + +MaximumMatcher::MaximumMatcher(int count1, int count2, + NodeMatchCallback* callback, + vector* match_list1, + vector* match_list2) + : count1_(count1), count2_(count2), match_callback_(callback), + match_list1_(match_list1), match_list2_(match_list2) { + match_list1_->assign(count1, -1); + match_list2_->assign(count2, -1); +} + +int MaximumMatcher::FindMaximumMatch(bool early_return) { + int result = 0; + for (int i = 0; i < count1_; ++i) { + vector visited(count1_); + if (FindArgumentPathDFS(i, &visited)) { + ++result; + } else if (early_return) { + return 0; + } + } + // Backfill match_list1_ as we only filled match_list2_ when finding + // argumenting pathes. + for (int i = 0; i < count2_; ++i) { + if ((*match_list2_)[i] != -1) { + (*match_list1_)[(*match_list2_)[i]] = i; + } + } + return result; +} + +bool MaximumMatcher::Match(int left, int right) { + pair p(left, right); + map, bool>::iterator it = cached_match_results_.find(p); + if (it != cached_match_results_.end()) { + return it->second; + } + cached_match_results_[p] = match_callback_->Run(left, right); + return cached_match_results_[p]; +} + +bool MaximumMatcher::FindArgumentPathDFS(int v, vector* visited) { + (*visited)[v] = true; + // We try to match those un-matched nodes on the right side first. This is + // the step that the navie greedy matching algorithm uses. In the best cases + // where the greedy algorithm can find a maximum matching, we will always + // find a match in this step and the performance will be identical to the + // greedy algorithm. + for (int i = 0; i < count2_; ++i) { + int matched = (*match_list2_)[i]; + if (matched == -1 && Match(v, i)) { + (*match_list2_)[i] = v; + return true; + } + } + // Then we try those already matched nodes and see if we can find an + // alternaive match for the node matched to them. + // The greedy algorithm will stop before this and fail to produce the + // correct result. + for (int i = 0; i < count2_; ++i) { + int matched = (*match_list2_)[i]; + if (matched != -1 && Match(v, i)) { + if (!(*visited)[matched] && FindArgumentPathDFS(matched, visited)) { + (*match_list2_)[i] = v; + return true; + } + } + } + return false; +} + +} // namespace + +bool MessageDifferencer::MatchRepeatedFieldIndices( + const Message& message1, + const Message& message2, + const FieldDescriptor* repeated_field, + const vector& parent_fields, + vector* match_list1, + vector* match_list2) { + const int count1 = + message1.GetReflection()->FieldSize(message1, repeated_field); + const int count2 = + message2.GetReflection()->FieldSize(message2, repeated_field); + const MapKeyComparator* key_comparator = GetMapKeyComparator(repeated_field); + + match_list1->assign(count1, -1); + match_list2->assign(count2, -1); + + SpecificField specific_field; + specific_field.field = repeated_field; + + bool success = true; + // Find potential match if this is a special repeated field. + if (key_comparator != NULL || IsTreatedAsSet(repeated_field)) { + if (scope_ == PARTIAL) { + // When partial matching is enabled, Compare(a, b) && Compare(a, c) + // doesn't neccessarily imply Compare(b, c). Therefore a naive greedy + // algorithm will fail to find a maximum matching. + // Here we use the argumenting path algorithm. + MaximumMatcher::NodeMatchCallback* callback = NewPermanentCallback( + this, &MessageDifferencer::IsMatch, repeated_field, key_comparator, + &message1, &message2, parent_fields); + MaximumMatcher matcher(count1, count2, callback, match_list1, + match_list2); + // If diff info is not needed, we should end the matching process as + // soon as possible if not all items can be matched. + bool early_return = (reporter_ == NULL); + int match_count = matcher.FindMaximumMatch(early_return); + if (match_count != count1 && reporter_ == NULL) return false; + success = success && (match_count == count1); + } else { + for (int i = 0; i < count1; ++i) { + // Indicates any matched elements for this repeated field. + bool match = false; + + specific_field.index = i; + specific_field.new_index = i; + + for (int j = 0; j < count2; j++) { + if (match_list2->at(j) != -1) continue; + specific_field.index = i; + specific_field.new_index = j; + + match = IsMatch(repeated_field, key_comparator, + &message1, &message2, parent_fields, i, j); + + if (match) { + match_list1->at(specific_field.index) = specific_field.new_index; + match_list2->at(specific_field.new_index) = specific_field.index; + break; + } + } + if (!match && reporter_ == NULL) return false; + success = success && match; + } + } + } else { + // If this field should be treated as list, just label the match_list. + for (int i = 0; i < count1 && i < count2; i++) { + match_list1->at(i) = i; + match_list2->at(i) = i; + } + } + + return success; +} + +FieldComparator::ComparisonResult MessageDifferencer::GetFieldComparisonResult( + const Message& message1, const Message& message2, + const FieldDescriptor* field, int index1, int index2, + const FieldContext* field_context) { + FieldComparator* comparator = field_comparator_ != NULL ? + field_comparator_ : &default_field_comparator_; + return comparator->Compare(message1, message2, field, + index1, index2, field_context); +} + +// =========================================================================== + +MessageDifferencer::Reporter::Reporter() { } +MessageDifferencer::Reporter::~Reporter() {} + +// =========================================================================== + +MessageDifferencer::MapKeyComparator::MapKeyComparator() {} +MessageDifferencer::MapKeyComparator::~MapKeyComparator() {} + +// =========================================================================== + +MessageDifferencer::IgnoreCriteria::IgnoreCriteria() {} +MessageDifferencer::IgnoreCriteria::~IgnoreCriteria() {} + +// =========================================================================== + +// Note that the printer's delimiter is not used, because if we are given a +// printer, we don't know its delimiter. +MessageDifferencer::StreamReporter::StreamReporter( + io::ZeroCopyOutputStream* output) : printer_(new io::Printer(output, '$')), + delete_printer_(true), + report_modified_aggregates_(false) { } + +MessageDifferencer::StreamReporter::StreamReporter( + io::Printer* printer) : printer_(printer), + delete_printer_(false), + report_modified_aggregates_(false) { } + +MessageDifferencer::StreamReporter::~StreamReporter() { + if (delete_printer_) delete printer_; +} + +void MessageDifferencer::StreamReporter::PrintPath( + const vector& field_path, bool left_side) { + for (int i = 0; i < field_path.size(); ++i) { + if (i > 0) { + printer_->Print("."); + } + + SpecificField specific_field = field_path[i]; + + if (specific_field.field != NULL) { + if (specific_field.field->is_extension()) { + printer_->Print("($name$)", "name", + specific_field.field->full_name()); + } else { + printer_->PrintRaw(specific_field.field->name()); + } + } else { + printer_->PrintRaw(SimpleItoa(specific_field.unknown_field_number)); + } + if (left_side && specific_field.index >= 0) { + printer_->Print("[$name$]", "name", SimpleItoa(specific_field.index)); + } + if (!left_side && specific_field.new_index >= 0) { + printer_->Print("[$name$]", "name", SimpleItoa(specific_field.new_index)); + } + } +} + +void MessageDifferencer:: +StreamReporter::PrintValue(const Message& message, + const vector& field_path, + bool left_side) { + const SpecificField& specific_field = field_path.back(); + const FieldDescriptor* field = specific_field.field; + if (field != NULL) { + string output; + int index = left_side ? specific_field.index : specific_field.new_index; + if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { + const Reflection* reflection = message.GetReflection(); + const Message& field_message = field->is_repeated() ? + reflection->GetRepeatedMessage(message, field, index) : + reflection->GetMessage(message, field); + output = field_message.ShortDebugString(); + if (output.empty()) { + printer_->Print("{ }"); + } else { + printer_->Print("{ $name$ }", "name", output); + } + } else { + TextFormat::PrintFieldValueToString(message, field, index, &output); + printer_->PrintRaw(output); + } + } else { + const UnknownFieldSet* unknown_fields = + (left_side ? + specific_field.unknown_field_set1 : + specific_field.unknown_field_set2); + const UnknownField* unknown_field = &unknown_fields->field( + left_side ? + specific_field.unknown_field_index1 : + specific_field.unknown_field_index2); + PrintUnknownFieldValue(unknown_field); + } +} + +void MessageDifferencer:: +StreamReporter::PrintUnknownFieldValue(const UnknownField* unknown_field) { + GOOGLE_CHECK(unknown_field != NULL) << " Cannot print NULL unknown_field."; + + string output; + switch (unknown_field->type()) { + case UnknownField::TYPE_VARINT: + output = SimpleItoa(unknown_field->varint()); + break; + case UnknownField::TYPE_FIXED32: + output = StrCat("0x", strings::Hex(unknown_field->fixed32(), + strings::ZERO_PAD_8)); + break; + case UnknownField::TYPE_FIXED64: + output = StrCat("0x", strings::Hex(unknown_field->fixed64(), + strings::ZERO_PAD_16)); + break; + case UnknownField::TYPE_LENGTH_DELIMITED: + output = StringPrintf("\"%s\"", + CEscape(unknown_field->length_delimited()).c_str()); + break; + case UnknownField::TYPE_GROUP: + // TODO(kenton): Print the contents of the group like we do for + // messages. Requires an equivalent of ShortDebugString() for + // UnknownFieldSet. + output = "{ ... }"; + break; + } + printer_->PrintRaw(output); +} + +void MessageDifferencer::StreamReporter::Print(const string& str) { + printer_->Print(str.c_str()); +} + +void MessageDifferencer::StreamReporter::ReportAdded( + const Message& message1, + const Message& message2, + const vector& field_path) { + printer_->Print("added: "); + PrintPath(field_path, false); + printer_->Print(": "); + PrintValue(message2, field_path, false); + printer_->Print("\n"); // Print for newlines. +} + +void MessageDifferencer::StreamReporter::ReportDeleted( + const Message& message1, + const Message& message2, + const vector& field_path) { + printer_->Print("deleted: "); + PrintPath(field_path, true); + printer_->Print(": "); + PrintValue(message1, field_path, true); + printer_->Print("\n"); // Print for newlines +} + +void MessageDifferencer::StreamReporter::ReportModified( + const Message& message1, + const Message& message2, + const vector& field_path) { + if (!report_modified_aggregates_ && field_path.back().field == NULL) { + if (field_path.back().unknown_field_type == UnknownField::TYPE_GROUP) { + // Any changes to the subfields have already been printed. + return; + } + } else if (!report_modified_aggregates_) { + if (field_path.back().field->cpp_type() == + FieldDescriptor::CPPTYPE_MESSAGE) { + // Any changes to the subfields have already been printed. + return; + } + } + + printer_->Print("modified: "); + PrintPath(field_path, true); + if (CheckPathChanged(field_path)) { + printer_->Print(" -> "); + PrintPath(field_path, false); + } + printer_->Print(": "); + PrintValue(message1, field_path, true); + printer_->Print(" -> "); + PrintValue(message2, field_path, false); + printer_->Print("\n"); // Print for newlines. +} + +void MessageDifferencer::StreamReporter::ReportMoved( + const Message& message1, + const Message& message2, + const vector& field_path) { + printer_->Print("moved: "); + PrintPath(field_path, true); + printer_->Print(" -> "); + PrintPath(field_path, false); + printer_->Print(" : "); + PrintValue(message1, field_path, true); + printer_->Print("\n"); // Print for newlines. +} + +void MessageDifferencer::StreamReporter::ReportMatched( + const Message& message1, + const Message& message2, + const vector& field_path) { + printer_->Print("matched: "); + PrintPath(field_path, true); + if (CheckPathChanged(field_path)) { + printer_->Print(" -> "); + PrintPath(field_path, false); + } + printer_->Print(" : "); + PrintValue(message1, field_path, true); + printer_->Print("\n"); // Print for newlines. +} + +void MessageDifferencer::StreamReporter::ReportIgnored( + const Message& message1, + const Message& message2, + const vector& field_path) { + printer_->Print("ignored: "); + PrintPath(field_path, true); + if (CheckPathChanged(field_path)) { + printer_->Print(" -> "); + PrintPath(field_path, false); + } + printer_->Print("\n"); // Print for newlines. +} + +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/util/message_differencer.h b/src/google/protobuf/util/message_differencer.h new file mode 100644 index 00000000..05548897 --- /dev/null +++ b/src/google/protobuf/util/message_differencer.h @@ -0,0 +1,817 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: jschorr@google.com (Joseph Schorr) +// Based on original Protocol Buffers design by +// Sanjay Ghemawat, Jeff Dean, and others. +// +// This file defines static methods and classes for comparing Protocol +// Messages. +// +// Aug. 2008: Added Unknown Fields Comparison for messages. +// Aug. 2009: Added different options to compare repeated fields. +// Apr. 2010: Moved field comparison to FieldComparator. + +#ifndef GOOGLE_PROTOBUF_UTIL_MESSAGE_DIFFERENCER_H__ +#define GOOGLE_PROTOBUF_UTIL_MESSAGE_DIFFERENCER_H__ + +#include +#include +#include +#include +#include // FieldDescriptor +#include // Message +#include +#include + +namespace google { +namespace protobuf { + +class DynamicMessageFactory; +class FieldDescriptor; + +namespace io { +class ZeroCopyOutputStream; +class Printer; +} + +namespace util { + +class FieldContext; // declared below MessageDifferencer + +// A basic differencer that can be used to determine +// the differences between two specified Protocol Messages. If any differences +// are found, the Compare method will return false, and any differencer reporter +// specified via ReportDifferencesTo will have its reporting methods called (see +// below for implementation of the report). Based off of the original +// ProtocolDifferencer implementation in //net/proto/protocol-differencer.h +// (Thanks Todd!). +// +// MessageDifferencer REQUIRES that compared messages be the same type, defined +// as messages that share the same descriptor. If not, the behavior of this +// class is undefined. +// +// People disagree on what MessageDifferencer should do when asked to compare +// messages with different descriptors. Some people think it should always +// return false. Others expect it to try to look for similar fields and +// compare them anyway -- especially if the descriptors happen to be identical. +// If we chose either of these behaviors, some set of people would find it +// surprising, and could end up writing code expecting the other behavior +// without realizing their error. Therefore, we forbid that usage. +// +// This class is implemented based on the proto2 reflection. The performance +// should be good enough for normal usages. However, for places where the +// performance is extremely sensitive, there are several alternatives: +// - Comparing serialized string +// Downside: false negatives (there are messages that are the same but their +// serialized strings are different). +// - Equals code generator by compiler plugin (net/proto2/contrib/equals_plugin) +// Downside: more generated code; maintenance overhead for the additional rule +// (must be in sync with the original proto_library). +// +// Note on handling of google.protobuf.Any: MessageDifferencer automatically +// unpacks Any::value into a Message and compares its individual fields. +// Messages encoded in a repeated Any cannot be compared using TreatAsMap. +// +// +// Note on thread-safety: MessageDifferencer is *not* thread-safe. You need to +// guard it with a lock to use the same MessageDifferencer instance from +// multiple threads. Note that it's fine to call static comparison methods +// (like MessageDifferencer::Equals) concurrently. +class LIBPROTOBUF_EXPORT MessageDifferencer { + public: + // Determines whether the supplied messages are equal. Equality is defined as + // all fields within the two messages being set to the same value. Primitive + // fields and strings are compared by value while embedded messages/groups + // are compared as if via a recursive call. Use IgnoreField() and Compare() + // if some fields should be ignored in the comparison. + // + // This method REQUIRES that the two messages have the same + // Descriptor (message1.GetDescriptor() == message2.GetDescriptor()). + static bool Equals(const Message& message1, const Message& message2); + + // Determines whether the supplied messages are equivalent. Equivalency is + // defined as all fields within the two messages having the same value. This + // differs from the Equals method above in that fields with default values + // are considered set to said value automatically. For details on how default + // values are defined for each field type, see http://shortn/_x2Gv6XFrWt. + // Also, Equivalent() ignores unknown fields. Use IgnoreField() and Compare() + // if some fields should be ignored in the comparison. + // + // This method REQUIRES that the two messages have the same + // Descriptor (message1.GetDescriptor() == message2.GetDescriptor()). + static bool Equivalent(const Message& message1, const Message& message2); + + // Determines whether the supplied messages are approximately equal. + // Approximate equality is defined as all fields within the two messages + // being approximately equal. Primitive (non-float) fields and strings are + // compared by value, floats are compared using MathUtil::AlmostEquals() and + // embedded messages/groups are compared as if via a recursive call. Use + // IgnoreField() and Compare() if some fields should be ignored in the + // comparison. + // + // This method REQUIRES that the two messages have the same + // Descriptor (message1.GetDescriptor() == message2.GetDescriptor()). + static bool ApproximatelyEquals(const Message& message1, + const Message& message2); + + // Determines whether the supplied messages are approximately equivalent. + // Approximate equivalency is defined as all fields within the two messages + // being approximately equivalent. As in + // MessageDifferencer::ApproximatelyEquals, primitive (non-float) fields and + // strings are compared by value, floats are compared using + // MathUtil::AlmostEquals() and embedded messages/groups are compared as if + // via a recursive call. However, fields with default values are considered + // set to said value, as per MessageDiffencer::Equivalent. Use IgnoreField() + // and Compare() if some fields should be ignored in the comparison. + // + // This method REQUIRES that the two messages have the same + // Descriptor (message1.GetDescriptor() == message2.GetDescriptor()). + static bool ApproximatelyEquivalent(const Message& message1, + const Message& message2); + + // Identifies an individual field in a message instance. Used for field_path, + // below. + struct SpecificField { + // For known fields, "field" is filled in and "unknown_field_number" is -1. + // For unknown fields, "field" is NULL, "unknown_field_number" is the field + // number, and "unknown_field_type" is its type. + const FieldDescriptor* field; + int unknown_field_number; + UnknownField::Type unknown_field_type; + + // If this a repeated field, "index" is the index within it. For unknown + // fields, this is the index of the field among all unknown fields of the + // same field number and type. + int index; + + // If "field" is a repeated field which is being treated as a map or + // a set (see TreatAsMap() and TreatAsSet(), below), new_index indicates + // the index the position to which the element has moved. This only + // applies to ReportMoved() and (in the case of TreatAsMap()) + // ReportModified(). In all other cases, "new_index" will have the same + // value as "index". + int new_index; + + // For unknown fields, these are the pointers to the UnknownFieldSet + // containing the unknown fields. In certain cases (e.g. proto1's + // MessageSet, or nested groups of unknown fields), these may differ from + // the messages' internal UnknownFieldSets. + const UnknownFieldSet* unknown_field_set1; + const UnknownFieldSet* unknown_field_set2; + + // For unknown fields, these are the index of the field within the + // UnknownFieldSets. One or the other will be -1 when + // reporting an addition or deletion. + int unknown_field_index1; + int unknown_field_index2; + + SpecificField() + : field(NULL), + unknown_field_number(-1), + index(-1), + new_index(-1), + unknown_field_set1(NULL), + unknown_field_set2(NULL), + unknown_field_index1(-1), + unknown_field_index2(-1) {} + }; + + // Abstract base class from which all MessageDifferencer + // reporters derive. The five Report* methods below will be called when + // a field has been added, deleted, modified, moved, or matched. The third + // argument is a vector of FieldDescriptor pointers which describes the chain + // of fields that was taken to find the current field. For example, for a + // field found in an embedded message, the vector will contain two + // FieldDescriptors. The first will be the field of the embedded message + // itself and the second will be the actual field in the embedded message + // that was added/deleted/modified. + class LIBPROTOBUF_EXPORT Reporter { + public: + Reporter(); + virtual ~Reporter(); + + // Reports that a field has been added into Message2. + virtual void ReportAdded( + const Message& message1, const Message& message2, + const vector& field_path) = 0; + + // Reports that a field has been deleted from Message1. + virtual void ReportDeleted( + const Message& message1, + const Message& message2, + const vector& field_path) = 0; + + // Reports that the value of a field has been modified. + virtual void ReportModified( + const Message& message1, + const Message& message2, + const vector& field_path) = 0; + + // Reports that a repeated field has been moved to another location. This + // only applies when using TreatAsSet or TreatAsMap() -- see below. Also + // note that for any given field, ReportModified and ReportMoved are + // mutually exclusive. If a field has been both moved and modified, then + // only ReportModified will be called. + virtual void ReportMoved( + const Message& message1, + const Message& message2, + const vector& field_path) { } + + // Reports that two fields match. Useful for doing side-by-side diffs. + // This function is mutually exclusive with ReportModified and ReportMoved. + // Note that you must call set_report_matches(true) before calling Compare + // to make use of this function. + virtual void ReportMatched( + const Message& message1, + const Message& message2, + const vector& field_path) { } + + // Reports that two fields would have been compared, but the + // comparison has been skipped because the field was marked as + // 'ignored' using IgnoreField(). This function is mutually + // exclusive with all the other Report() functions. + // + // The contract of ReportIgnored is slightly different than the + // other Report() functions, in that |field_path.back().index| is + // always equal to -1, even if the last field is repeated. This is + // because while the other Report() functions indicate where in a + // repeated field the action (Addition, Deletion, etc...) + // happened, when a repeated field is 'ignored', the differencer + // simply calls ReportIgnored on the repeated field as a whole and + // moves on without looking at its individual elements. + // + // Furthermore, ReportIgnored() does not indicate whether the + // fields were in fact equal or not, as Compare() does not inspect + // these fields at all. It is up to the Reporter to decide whether + // the fields are equal or not (perhaps with a second call to + // Compare()), if it cares. + virtual void ReportIgnored( + const Message& message1, + const Message& message2, + const vector& field_path) { } + + private: + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(Reporter); + }; + + // MapKeyComparator is used to determine if two elements have the same key + // when comparing elements of a repeated field as a map. + class LIBPROTOBUF_EXPORT MapKeyComparator { + public: + MapKeyComparator(); + virtual ~MapKeyComparator(); + + // The first IsMatch without parent_fields is only for backward + // compatibility. New users should override the second one instead. + // + // Deprecated. + // TODO(ykzhu): remove this function. + virtual bool IsMatch(const Message& message1, + const Message& message2) const { + GOOGLE_CHECK(false) << "This function shouldn't get called"; + return false; + } + virtual bool IsMatch(const Message& message1, + const Message& message2, + const vector& parent_fields) const { + return IsMatch(message1, message2); + } + + private: + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(MapKeyComparator); + }; + + // Abstract base class from which all IgnoreCriteria derive. + // By adding IgnoreCriteria more complex ignore logic can be implemented. + // IgnoreCriteria are registed with AddIgnoreCriteria. For each compared + // field IsIgnored is called on each added IgnoreCriteria until one returns + // true or all return false. + // IsIgnored is called for fields where at least one side has a value. + class LIBPROTOBUF_EXPORT IgnoreCriteria { + public: + IgnoreCriteria(); + virtual ~IgnoreCriteria(); + + // Returns true if the field should be ignored. + virtual bool IsIgnored( + const Message& message1, + const Message& message2, + const FieldDescriptor* field, + const vector& parent_fields) = 0; + }; + + // To add a Reporter, construct default here, then use ReportDifferencesTo or + // ReportDifferencesToString. + explicit MessageDifferencer(); + + ~MessageDifferencer(); + + enum MessageFieldComparison { + EQUAL, // Fields must be present in both messages + // for the messages to be considered the same. + EQUIVALENT, // Fields with default values are considered set + // for comparison purposes even if not explicitly + // set in the messages themselves. Unknown fields + // are ignored. + }; + + enum Scope { + FULL, // All fields of both messages are considered in the comparison. + PARTIAL // Only fields present in the first message are considered; fields + // set only in the second message will be skipped during + // comparison. + }; + + // DEPRECATED. Use FieldComparator::FloatComparison instead. + enum FloatComparison { + EXACT, // Floats and doubles are compared exactly. + APPROXIMATE // Floats and doubles are compared using the + // MathUtil::AlmostEquals method. + }; + + enum RepeatedFieldComparison { + AS_LIST, // Repeated fields are compared in order. Differing values at + // the same index are reported using ReportModified(). If the + // repeated fields have different numbers of elements, the + // unpaired elements are reported using ReportAdded() or + // ReportDeleted(). + AS_SET, // Treat all the repeated fields as sets by default. + // See TreatAsSet(), as below. + }; + + // The elements of the given repeated field will be treated as a set for + // diffing purposes, so different orderings of the same elements will be + // considered equal. Elements which are present on both sides of the + // comparison but which have changed position will be reported with + // ReportMoved(). Elements which only exist on one side or the other are + // reported with ReportAdded() and ReportDeleted() regardless of their + // positions. ReportModified() is never used for this repeated field. If + // the only differences between the compared messages is that some fields + // have been moved, then the comparison returns true. + // + // If the scope of comparison is set to PARTIAL, then in addition to what's + // above, extra values added to repeated fields of the second message will + // not cause the comparison to fail. + // + // Note that set comparison is currently O(k * n^2) (where n is the total + // number of elements, and k is the average size of each element). In theory + // it could be made O(n * k) with a more complex hashing implementation. Feel + // free to contribute one if the current implementation is too slow for you. + // If partial matching is also enabled, the time complexity will be O(k * n^2 + // + n^3) in which n^3 is the time complexity of the maximum matching + // algorithm. + // + // REQUIRES: field->is_repeated() + void TreatAsSet(const FieldDescriptor* field); + + // The elements of the given repeated field will be treated as a map for + // diffing purposes, with |key| being the map key. Thus, elements with the + // same key will be compared even if they do not appear at the same index. + // Differences are reported similarly to TreatAsSet(), except that + // ReportModified() is used to report elements with the same key but + // different values. Note that if an element is both moved and modified, + // only ReportModified() will be called. As with TreatAsSet, if the only + // differences between the compared messages is that some fields have been + // moved, then the comparison returns true. See TreatAsSet for notes on + // performance. + // + // REQUIRES: field->is_repeated() + // REQUIRES: field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE + // REQUIRES: key->containing_type() == field->message_type() + void TreatAsMap(const FieldDescriptor* field, const FieldDescriptor* key); + // Same as TreatAsMap except that this method will use multiple fields as + // the key in comparison. All specified fields in 'key_fields' should be + // present in the compared elements. Two elements will be treated as having + // the same key iff they have the same value for every specified field. There + // are two steps in the comparison process. The first one is key matching. + // Every element from one message will be compared to every element from + // the other message. Only fields in 'key_fields' are compared in this step + // to decide if two elements have the same key. The second step is value + // comparison. Those pairs of elements with the same key (with equal value + // for every field in 'key_fields') will be compared in this step. + // Time complexity of the first step is O(s * m * n ^ 2) where s is the + // average size of the fields specified in 'key_fields', m is the number of + // fields in 'key_fields' and n is the number of elements. If partial + // matching is enabled, an extra O(n^3) will be incured by the maximum + // matching algorithm. The second step is O(k * n) where k is the average + // size of each element. + void TreatAsMapWithMultipleFieldsAsKey( + const FieldDescriptor* field, + const vector& key_fields); + // Same as TreatAsMapWithMultipleFieldsAsKey, except that each of the field + // do not necessarily need to be a direct subfield. Each element in + // key_field_paths indicate a path from the message being compared, listing + // successive subfield to reach the key field. + // + // REQUIRES: + // for key_field_path in key_field_paths: + // key_field_path[0]->containing_type() == field->message_type() + // for i in [0, key_field_path.size() - 1): + // key_field_path[i+1]->containing_type() == + // key_field_path[i]->message_type() + // key_field_path[i]->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE + // !key_field_path[i]->is_repeated() + void TreatAsMapWithMultipleFieldPathsAsKey( + const FieldDescriptor* field, + const vector >& key_field_paths); + + // Uses a custom MapKeyComparator to determine if two elements have the same + // key when comparing a repeated field as a map. + // The caller is responsible to delete the key_comparator. + // This method varies from TreatAsMapWithMultipleFieldsAsKey only in the + // first key matching step. Rather than comparing some specified fields, it + // will invoke the IsMatch method of the given 'key_comparator' to decide if + // two elements have the same key. + void TreatAsMapUsingKeyComparator( + const FieldDescriptor* field, + const MapKeyComparator* key_comparator); + + // Add a custom ignore criteria that is evaluated in addition to the + // ignored fields added with IgnoreField. + // Takes ownership of ignore_criteria. + void AddIgnoreCriteria(IgnoreCriteria* ignore_criteria); + + // Indicates that any field with the given descriptor should be + // ignored for the purposes of comparing two messages. This applies + // to fields nested in the message structure as well as top level + // ones. When the MessageDifferencer encounters an ignored field, + // ReportIgnored is called on the reporter, if one is specified. + // + // The only place where the field's 'ignored' status is not applied is when + // it is being used as a key in a field passed to TreatAsMap or is one of + // the fields passed to TreatAsMapWithMultipleFieldsAsKey. + // In this case it is compared in key matching but after that it's ignored + // in value comparison. + void IgnoreField(const FieldDescriptor* field); + + // Sets the field comparator used to determine differences between protocol + // buffer fields. By default it's set to a DefaultFieldComparator instance. + // MessageDifferencer doesn't take ownership over the passed object. + // Note that this method must be called before Compare for the comparator to + // be used. + void set_field_comparator(FieldComparator* comparator); + + // DEPRECATED. Pass a DefaultFieldComparator instance instead. + // Sets the fraction and margin for the float comparison of a given field. + // Uses MathUtil::WithinFractionOrMargin to compare the values. + // NOTE: this method does nothing if differencer's field comparator has been + // set to a custom object. + // + // REQUIRES: field->cpp_type == FieldDescriptor::CPPTYPE_DOUBLE or + // field->cpp_type == FieldDescriptor::CPPTYPE_FLOAT + // REQUIRES: float_comparison_ == APPROXIMATE + void SetFractionAndMargin(const FieldDescriptor* field, double fraction, + double margin); + + // Sets the type of comparison (as defined in the MessageFieldComparison + // enumeration above) that is used by this differencer when determining how + // to compare fields in messages. + void set_message_field_comparison(MessageFieldComparison comparison); + + // Tells the differencer whether or not to report matches. This method must + // be called before Compare. The default for a new differencer is false. + void set_report_matches(bool report_matches) { + report_matches_ = report_matches; + } + + // 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. + void set_scope(Scope scope); + + // Returns the current scope used by this differencer. + Scope scope(); + + // DEPRECATED. Pass a DefaultFieldComparator instance instead. + // Sets the type of comparison (as defined in the FloatComparison enumeration + // above) that is used by this differencer when comparing float (and double) + // fields in messages. + // NOTE: this method does nothing if differencer's field comparator has been + // set to a custom object. + void set_float_comparison(FloatComparison comparison); + + // Sets the type of comparison for repeated field (as defined in the + // RepeatedFieldComparison enumeration above) that is used by this + // differencer when compare repeated fields in messages. + void set_repeated_field_comparison(RepeatedFieldComparison comparison); + + // Compares the two specified messages, returning true if they are the same, + // false otherwise. If this method returns false, any changes between the + // two messages will be reported if a Reporter was specified via + // ReportDifferencesTo (see also ReportDifferencesToString). + // + // This method REQUIRES that the two messages have the same + // Descriptor (message1.GetDescriptor() == message2.GetDescriptor()). + bool Compare(const Message& message1, const Message& message2); + + // Same as above, except comparing only the list of fields specified by the + // two vectors of FieldDescriptors. + bool CompareWithFields(const Message& message1, const Message& message2, + const vector& message1_fields, + const vector& message2_fields); + + // Automatically creates a reporter that will output the differences + // found (if any) to the specified output string pointer. Note that this + // method must be called before Compare. + void ReportDifferencesToString(string* output); + + // Tells the MessageDifferencer to report differences via the specified + // reporter. Note that this method must be called before Compare for + // the reporter to be used. It is the responsibility of the caller to delete + // this object. + // If the provided pointer equals NULL, the MessageDifferencer stops reporting + // differences to any previously set reporters or output strings. + void ReportDifferencesTo(Reporter* reporter); + + // An implementation of the MessageDifferencer Reporter that outputs + // any differences found in human-readable form to the supplied + // ZeroCopyOutputStream or Printer. If a printer is used, the delimiter + // *must* be '$'. + class LIBPROTOBUF_EXPORT StreamReporter : public Reporter { + public: + explicit StreamReporter(io::ZeroCopyOutputStream* output); + explicit StreamReporter(io::Printer* printer); // delimiter '$' + virtual ~StreamReporter(); + + // When set to true, the stream reporter will also output aggregates nodes + // (i.e. messages and groups) whose subfields have been modified. When + // false, will only report the individual subfields. Defaults to false. + void set_report_modified_aggregates(bool report) { + report_modified_aggregates_ = report; + } + + // The following are implementations of the methods described above. + virtual void ReportAdded(const Message& message1, const Message& message2, + const vector& field_path); + + virtual void ReportDeleted(const Message& message1, + const Message& message2, + const vector& field_path); + + virtual void ReportModified(const Message& message1, + const Message& message2, + const vector& field_path); + + virtual void ReportMoved(const Message& message1, + const Message& message2, + const vector& field_path); + + virtual void ReportMatched(const Message& message1, + const Message& message2, + const vector& field_path); + + virtual void ReportIgnored(const Message& message1, + const Message& message2, + const vector& field_path); + + protected: + // Prints the specified path of fields to the buffer. + virtual void PrintPath(const vector& field_path, + bool left_side); + + // Prints the value of fields to the buffer. left_side is true if the + // given message is from the left side of the comparison, false if it + // was the right. This is relevant only to decide whether to follow + // unknown_field_index1 or unknown_field_index2 when an unknown field + // is encountered in field_path. + virtual void PrintValue(const Message& message, + const vector& field_path, + bool left_side); + + // Prints the specified path of unknown fields to the buffer. + virtual void PrintUnknownFieldValue(const UnknownField* unknown_field); + + // Just print a string + void Print(const string& str); + + private: + io::Printer* printer_; + bool delete_printer_; + bool report_modified_aggregates_; + + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(StreamReporter); + }; + + private: + // A MapKeyComparator to be used in TreatAsMapUsingKeyComparator. + // Implementation of this class needs to do field value comparison which + // relies on some private methods of MessageDifferencer. That's why this + // class is declared as a nested class of MessageDifferencer. + class MultipleFieldsMapKeyComparator; + // Returns true if field1's number() is less than field2's. + static bool FieldBefore(const FieldDescriptor* field1, + const FieldDescriptor* field2); + + // Combine the two lists of fields into the combined_fields output vector. + // All fields present in both lists will always be included in the combined + // list. Fields only present in one of the lists will only appear in the + // combined list if the corresponding fields_scope option is set to FULL. + void CombineFields(const vector& fields1, + Scope fields1_scope, + const vector& fields2, + Scope fields2_scope, + vector* combined_fields); + + // Internal version of the Compare method which performs the actual + // comparison. The parent_fields vector is a vector containing field + // descriptors of all fields accessed to get to this comparison operation + // (i.e. if the current message is an embedded message, the parent_fields + // vector will contain the field that has this embedded message). + bool Compare(const Message& message1, const Message& message2, + vector* parent_fields); + + // Compares all the unknown fields in two messages. + bool CompareUnknownFields(const Message& message1, const Message& message2, + const google::protobuf::UnknownFieldSet&, + const google::protobuf::UnknownFieldSet&, + vector* parent_fields); + + // Compares the specified messages for the requested field lists. The field + // lists are modified depending on comparison settings, and then passed to + // CompareWithFieldsInternal. + bool CompareRequestedFieldsUsingSettings( + const Message& message1, const Message& message2, + const vector& message1_fields, + const vector& message2_fields, + vector* parent_fields); + + // Compares the specified messages with the specified field lists. + bool CompareWithFieldsInternal( + const Message& message1, const Message& message2, + const vector& message1_fields, + const vector& message2_fields, + vector* parent_fields); + + // Compares the repeated fields, and report the error. + bool CompareRepeatedField(const Message& message1, const Message& message2, + const FieldDescriptor* field, + vector* parent_fields); + + // Shorthand for CompareFieldValueUsingParentFields with NULL parent_fields. + bool CompareFieldValue(const Message& message1, + const Message& message2, + const FieldDescriptor* field, + int index1, + int index2); + + // Compares the specified field on the two messages, returning + // true if they are the same, false otherwise. For repeated fields, + // this method only compares the value in the specified index. This method + // uses Compare functions to recurse into submessages. + // The parent_fields vector is used in calls to a Reporter instance calls. + // It can be NULL, in which case the MessageDifferencer will create new + // list of parent messages if it needs to recursively compare the given field. + // To avoid confusing users you should not set it to NULL unless you modified + // Reporter to handle the change of parent_fields correctly. + bool CompareFieldValueUsingParentFields(const Message& message1, + const Message& message2, + const FieldDescriptor* field, + int index1, + int index2, + vector* parent_fields); + + // Compares the specified field on the two messages, returning comparison + // result, as returned by appropriate FieldComparator. + FieldComparator::ComparisonResult GetFieldComparisonResult( + const Message& message1, const Message& message2, + const FieldDescriptor* field, int index1, int index2, + const FieldContext* field_context); + + // Check if the two elements in the repeated field are match to each other. + // if the key_comprator is NULL, this function returns true when the two + // elements are equal. + bool IsMatch(const FieldDescriptor* repeated_field, + const MapKeyComparator* key_comparator, + const Message* message1, const Message* message2, + const vector& parent_fields, + int index1, int index2); + + // Returns true when this repeated field has been configured to be treated + // as a set. + bool IsTreatedAsSet(const FieldDescriptor* field); + + // Returns true when this repeated field is to be compared as a subset, ie. + // has been configured to be treated as a set or map and scope is set to + // PARTIAL. + bool IsTreatedAsSubset(const FieldDescriptor* field); + + // Returns true if this field is to be ignored when this + // MessageDifferencer compares messages. + bool IsIgnored( + const Message& message1, + const Message& message2, + const FieldDescriptor* field, + const vector& 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); + + // 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 + // messages, ie. if message1[0] matches message2[1], then match_list1[0] == 1 + // and match_list2[1] == 0. The unmatched indices are indicated by -1. + // This method returns false if the match failed. However, it doesn't mean + // that the comparison succeeds when this method returns true (you need to + // double-check in this case). + bool MatchRepeatedFieldIndices(const Message& message1, + const Message& message2, + const FieldDescriptor* repeated_field, + const vector& parent_fields, + vector* match_list1, + vector* match_list2); + + // If "any" is of type google.protobuf.Any, extract its payload using + // DynamicMessageFactory and store in "data". + bool UnpackAny(const Message& any, google::protobuf::scoped_ptr* data); + + // Checks if index is equal to new_index in all the specific fields. + static bool CheckPathChanged(const vector& parent_fields); + + // Defines a map between field descriptors and their MapKeyComparators. + // Used for repeated fields when they are configured as TreatAsMap. + typedef map FieldKeyComparatorMap; + + // Defines a set to store field descriptors. Used for repeated fields when + // they are configured as TreatAsSet. + typedef set FieldSet; + + Reporter* reporter_; + DefaultFieldComparator default_field_comparator_; + FieldComparator* field_comparator_; + MessageFieldComparison message_field_comparison_; + Scope scope_; + RepeatedFieldComparison repeated_field_comparison_; + + FieldSet set_fields_; + // Keeps track of MapKeyComparators that are created within + // MessageDifferencer. These MapKeyComparators should be deleted + // before MessageDifferencer is destroyed. + // When TreatAsMap or TreatAsMapWithMultipleFieldsAsKey is called, we don't + // store the supplied FieldDescriptors directly. Instead, a new + // MapKeyComparator is created for comparison purpose. + vector owned_key_comparators_; + FieldKeyComparatorMap map_field_key_comparator_; + vector ignore_criteria_; + + FieldSet ignored_fields_; + + bool compare_unknown_fields_; + bool report_matches_; + + string* output_string_; + + google::protobuf::scoped_ptr dynamic_message_factory_; + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(MessageDifferencer); +}; + +// This class provides extra information to the FieldComparator::Compare +// function. +class LIBPROTOBUF_EXPORT FieldContext { + public: + explicit FieldContext( + vector* parent_fields) + : parent_fields_(parent_fields) {} + + vector* parent_fields() const { + return parent_fields_; + } + + private: + vector* parent_fields_; +}; + +} +} + +} // namespace google +#endif // GOOGLE_PROTOBUF_UTIL_MESSAGE_DIFFERENCER_H__ diff --git a/src/google/protobuf/util/message_differencer_unittest.cc b/src/google/protobuf/util/message_differencer_unittest.cc new file mode 100755 index 00000000..bd19f695 --- /dev/null +++ b/src/google/protobuf/util/message_differencer_unittest.cc @@ -0,0 +1,3132 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: jschorr@google.com (Joseph Schorr) +// Based on original Protocol Buffers design by +// Sanjay Ghemawat, Jeff Dean, and others. +// +// TODO(ksroka): Move some of these tests to field_comparator_test.cc. + +#include +#include +#include + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace google { +namespace protobuf { + +namespace { + + +const FieldDescriptor* GetFieldDescriptor( + const Message& message, const string& field_name) { + vector field_path = + Split(field_name, ".", true); + const Descriptor* descriptor = message.GetDescriptor(); + const FieldDescriptor* field = NULL; + for (int i = 0; i < field_path.size(); i++) { + field = descriptor->FindFieldByName(field_path[i]); + descriptor = field->message_type(); + } + return field; +} + +void ExpectEqualsWithDifferencer(util::MessageDifferencer* differencer, + const Message& msg1, + const Message& msg2) { + differencer->set_scope(util::MessageDifferencer::FULL); + EXPECT_TRUE(differencer->Compare(msg1, msg2)); + + differencer->set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_TRUE(differencer->Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, BasicEqualityTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + // Compare + EXPECT_TRUE(util::MessageDifferencer::Equals(msg1, msg2)); +} + +TEST(MessageDifferencerTest, BasicInequalityTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + msg1.set_optional_int32(-1); + + // Compare + EXPECT_FALSE(util::MessageDifferencer::Equals(msg1, msg2)); +} + +TEST(MessageDifferencerTest, RepeatedFieldInequalityTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + msg1.add_repeated_int32(-1); + + // Compare + EXPECT_FALSE(util::MessageDifferencer::Equals(msg1, msg2)); +} + +TEST(MessageDifferencerTest, MapFieldEqualityTest) { + // Create the testing protos + unittest::TestMap msg1; + unittest::TestMap msg2; + + MapTestUtil::MapReflectionTester tester(unittest::TestMap::descriptor()); + tester.SetMapFieldsViaReflection(&msg1); + tester.SetMapFieldsViaReflection(&msg2); + tester.SwapMapsViaReflection(&msg1); + + // Compare + EXPECT_TRUE(util::MessageDifferencer::Equals(msg1, msg2)); +} + +TEST(MessageDifferencerTest, BasicPartialEqualityTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + // Compare + util::MessageDifferencer differencer; + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, PartialEqualityTestExtraField) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + msg1.clear_optional_int32(); + + // Compare + util::MessageDifferencer differencer; + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, PartialEqualityTestSkipRequiredField) { + // Create the testing protos + unittest::TestRequired msg1; + unittest::TestRequired msg2; + + msg1.set_a(401); + msg2.set_a(401); + msg2.set_b(402); + + // Compare + util::MessageDifferencer differencer; + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, BasicPartialInequalityTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + msg1.set_optional_int32(-1); + + // Compare + util::MessageDifferencer differencer; + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, PartialInequalityMissingFieldTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + msg2.clear_optional_int32(); + + // Compare + util::MessageDifferencer differencer; + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, RepeatedFieldPartialInequalityTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + msg1.add_repeated_int32(-1); + + // Compare + util::MessageDifferencer differencer; + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, BasicEquivalencyTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + // Compare + EXPECT_TRUE(util::MessageDifferencer::Equivalent(msg1, msg2)); +} + +TEST(MessageDifferencerTest, EquivalencyNotEqualTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + msg1.clear_optional_int32(); + msg2.set_optional_int32(0); + + // Compare + EXPECT_FALSE(util::MessageDifferencer::Equals(msg1, msg2)); + EXPECT_TRUE(util::MessageDifferencer::Equivalent(msg1, msg2)); +} + +TEST(MessageDifferencerTest, BasicInequivalencyTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + msg1.set_optional_int32(-1); + + // Compare + EXPECT_FALSE(util::MessageDifferencer::Equivalent(msg1, msg2)); +} + + +TEST(MessageDifferencerTest, BasicEquivalencyNonSetTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + // Compare + EXPECT_TRUE(util::MessageDifferencer::Equivalent(msg1, msg2)); +} + + +TEST(MessageDifferencerTest, BasicInequivalencyNonSetTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + msg1.set_optional_int32(-1); + + // Compare + EXPECT_FALSE(util::MessageDifferencer::Equivalent(msg1, msg2)); +} + + +TEST(MessageDifferencerTest, BasicPartialEquivalencyTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + // Compare + util::MessageDifferencer differencer; + differencer.set_message_field_comparison( + util::MessageDifferencer::EQUIVALENT); + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, PartialEquivalencyNotEqualTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + msg1.set_optional_int32(0); + msg2.clear_optional_int32(); + + // Compare + EXPECT_FALSE(util::MessageDifferencer::Equals(msg1, msg2)); + util::MessageDifferencer differencer; + differencer.set_message_field_comparison( + util::MessageDifferencer::EQUIVALENT); + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, PartialEquivalencyTestExtraField) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + msg1.clear_optional_int32(); + + // Compare + util::MessageDifferencer differencer; + differencer.set_message_field_comparison( + util::MessageDifferencer::EQUIVALENT); + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, PartialEquivalencyTestSkipRequiredField) { + // Create the testing protos + unittest::TestRequired msg1; + unittest::TestRequired msg2; + + msg1.set_a(401); + msg2.set_a(401); + msg2.set_b(402); + + // Compare + util::MessageDifferencer differencer; + differencer.set_message_field_comparison( + util::MessageDifferencer::EQUIVALENT); + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, BasicPartialInequivalencyTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + msg1.set_optional_int32(-1); + + // Compare + util::MessageDifferencer differencer; + differencer.set_message_field_comparison( + util::MessageDifferencer::EQUIVALENT); + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, BasicPartialEquivalencyNonSetTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + // Compare + util::MessageDifferencer differencer; + differencer.set_message_field_comparison( + util::MessageDifferencer::EQUIVALENT); + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, BasicPartialInequivalencyNonSetTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + msg1.set_optional_int32(-1); + + // Compare + util::MessageDifferencer differencer; + differencer.set_message_field_comparison( + util::MessageDifferencer::EQUIVALENT); + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, ApproximateEqualityTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + // Compare + EXPECT_TRUE(util::MessageDifferencer::ApproximatelyEquals(msg1, msg2)); +} + +TEST(MessageDifferencerTest, ApproximateModifiedEqualityTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + const float v1 = 2.300005f; + const float v2 = 2.300006f; + msg1.set_optional_float(v1); + msg2.set_optional_float(v2); + + // Compare + ASSERT_NE(v1, v2) << "Should not be the same: " << v1 << ", " << v2; + ASSERT_FLOAT_EQ(v1, v2) << "Should be approx. equal: " << v1 << ", " << v2; + EXPECT_FALSE(util::MessageDifferencer::Equals(msg1, msg2)); + EXPECT_TRUE(util::MessageDifferencer::ApproximatelyEquals(msg1, msg2)); +} + +TEST(MessageDifferencerTest, ApproximateEquivalencyTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + // Compare + EXPECT_TRUE(util::MessageDifferencer::ApproximatelyEquivalent(msg1, + msg2)); +} + +TEST(MessageDifferencerTest, ApproximateModifiedEquivalencyTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + // Modify the approximateness requirement + const float v1 = 2.300005f; + const float v2 = 2.300006f; + msg1.set_optional_float(v1); + msg2.set_optional_float(v2); + + // Compare + ASSERT_NE(v1, v2) << "Should not be the same: " << v1 << ", " << v2; + ASSERT_FLOAT_EQ(v1, v2) << "Should be approx. equal: " << v1 << ", " << v2; + EXPECT_FALSE(util::MessageDifferencer::Equals(msg1, msg2)); + EXPECT_TRUE(util::MessageDifferencer::ApproximatelyEquivalent(msg1, + msg2)); + + // Modify the equivalency requirement too + msg1.clear_optional_int32(); + msg2.set_optional_int32(0); + + // Compare. Now should only pass on ApproximatelyEquivalent + EXPECT_FALSE(util::MessageDifferencer::Equals(msg1, msg2)); + EXPECT_FALSE(util::MessageDifferencer::Equivalent(msg1, msg2)); + EXPECT_FALSE(util::MessageDifferencer::ApproximatelyEquals(msg1, msg2)); + EXPECT_TRUE(util::MessageDifferencer::ApproximatelyEquivalent(msg1, + msg2)); +} + +TEST(MessageDifferencerTest, ApproximateInequivalencyTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + // Should fail on equivalency + msg1.set_optional_int32(-1); + EXPECT_FALSE(util::MessageDifferencer::ApproximatelyEquivalent(msg1, + msg2)); + + // Make these fields the same again. + msg1.set_optional_int32(0); + msg2.set_optional_int32(0); + EXPECT_TRUE(util::MessageDifferencer::ApproximatelyEquivalent(msg1, + msg2)); + + // Should fail on approximate equality check + const float v1 = 2.3f; + const float v2 = 9.3f; + msg1.set_optional_float(v1); + msg2.set_optional_float(v2); + EXPECT_FALSE(util::MessageDifferencer::ApproximatelyEquivalent(msg1, + msg2)); +} + +TEST(MessageDifferencerTest, WithinFractionOrMarginFloatTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + // Should fail on approximate equality check + const float v1 = 100.0f; + const float v2 = 109.9f; + msg1.set_optional_float(v1); + msg2.set_optional_float(v2); + + // Compare + util::MessageDifferencer differencer; + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + const FieldDescriptor* fd = + msg1.GetDescriptor()->FindFieldByName("optional_float"); + + // Set float comparison to exact, margin and fraction value should not matter. + differencer.set_float_comparison(util::MessageDifferencer::EXACT); + // Set margin for float comparison. + differencer.SetFractionAndMargin(fd, 0.0, 10.0); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + + // Margin and fraction float comparison is activated when float comparison is + // set to approximate. + differencer.set_float_comparison(util::MessageDifferencer::APPROXIMATE); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + + // Test out float comparison with fraction. + differencer.SetFractionAndMargin(fd, 0.2, 0.0); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + + // Should fail since the fraction is smaller than error. + differencer.SetFractionAndMargin(fd, 0.01, 0.0); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + + // Should pass if either fraction or margin are satisfied. + differencer.SetFractionAndMargin(fd, 0.01, 10.0); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + + // Make sure that the margin and fraction only affects the field that it was + // set for. + msg1.set_default_float(v1); + msg2.set_default_float(v2); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + msg1.set_default_float(v1); + msg2.set_default_float(v1); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, WithinFractionOrMarginDoubleTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + // Should fail on approximate equality check + const double v1 = 100.0; + const double v2 = 109.9; + msg1.set_optional_double(v1); + msg2.set_optional_double(v2); + + // Compare + util::MessageDifferencer differencer; + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + + // Set comparison to exact, margin and fraction value should not matter. + differencer.set_float_comparison(util::MessageDifferencer::EXACT); + // Set margin for float comparison. + const FieldDescriptor* fd = + msg1.GetDescriptor()->FindFieldByName("optional_double"); + differencer.SetFractionAndMargin(fd, 0.0, 10.0); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + + // Margin and fraction comparison is activated when float comparison is + // set to approximate. + differencer.set_float_comparison(util::MessageDifferencer::APPROXIMATE); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + + // Test out comparison with fraction. + differencer.SetFractionAndMargin(fd, 0.2, 0.0); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + + // Should fail since the fraction is smaller than error. + differencer.SetFractionAndMargin(fd, 0.01, 0.0); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + + // Should pass if either fraction or margin are satisfied. + differencer.SetFractionAndMargin(fd, 0.01, 10.0); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + + // Make sure that the margin and fraction only affects the field that it was + // set for. + msg1.set_default_double(v1); + msg2.set_default_double(v2); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + msg1.set_default_double(v1); + msg2.set_default_double(v1); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, WithinDefaultFractionOrMarginDoubleTest) { + // Create the testing protos + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + // Should fail on approximate equality check + const double v1 = 100.0; + const double v2 = 109.9; + msg1.set_optional_double(v1); + msg2.set_optional_double(v2); + + util::MessageDifferencer differencer; + + // Compare + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + + // Set up a custom field comparitor, with a default fraction and margin for + // float and double comparison. + util::DefaultFieldComparator field_comparitor; + field_comparitor.SetDefaultFractionAndMargin(0.0, 10.0); + differencer.set_field_comparator(&field_comparitor); + + // Set comparison to exact, margin and fraction value should not matter. + field_comparitor.set_float_comparison(util::DefaultFieldComparator::EXACT); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + + // Margin and fraction comparison is activated when float comparison is + // set to approximate. + field_comparitor.set_float_comparison( + util::DefaultFieldComparator::APPROXIMATE); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + + // Test out comparison with fraction. + field_comparitor.SetDefaultFractionAndMargin(0.2, 0.0); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + + // Should fail since the fraction is smaller than error. + field_comparitor.SetDefaultFractionAndMargin(0.01, 0.0); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + + // Should pass if either fraction or margin are satisfied. + field_comparitor.SetDefaultFractionAndMargin(0.01, 10.0); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + + // Make sure that the default margin and fraction affects all fields + msg1.set_default_double(v1); + msg2.set_default_double(v2); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, BasicFieldOrderingsTest) { + // Create the testing protos + unittest::TestFieldOrderings msg1; + unittest::TestFieldOrderings msg2; + + TestUtil::SetAllFieldsAndExtensions(&msg1); + TestUtil::SetAllFieldsAndExtensions(&msg2); + + // Compare + EXPECT_TRUE(util::MessageDifferencer::Equals(msg1, msg2)); +} + + +TEST(MessageDifferencerTest, BasicFieldOrderingInequalityTest) { + // Create the testing protos + unittest::TestFieldOrderings msg1; + unittest::TestFieldOrderings msg2; + + TestUtil::SetAllFieldsAndExtensions(&msg1); + TestUtil::SetAllFieldsAndExtensions(&msg2); + + msg1.set_my_float(15.00); + msg2.set_my_float(16.00); + + // Compare + EXPECT_FALSE(util::MessageDifferencer::Equals(msg1, msg2)); +} + +TEST(MessageDifferencerTest, BasicExtensionTest) { + // Create the testing protos + unittest::TestAllExtensions msg1; + unittest::TestAllExtensions msg2; + + TestUtil::SetAllExtensions(&msg1); + TestUtil::SetAllExtensions(&msg2); + + // Compare + EXPECT_TRUE(util::MessageDifferencer::Equals(msg1, msg2)); +} + + +TEST(MessageDifferencerTest, BasicExtensionInequalityTest) { + // Create the testing protos + unittest::TestAllExtensions msg1; + unittest::TestAllExtensions msg2; + + TestUtil::SetAllExtensions(&msg1); + TestUtil::SetAllExtensions(&msg2); + + msg1.SetExtension(unittest::optional_int32_extension, 101); + msg2.SetExtension(unittest::optional_int32_extension, 102); + + // Compare + EXPECT_FALSE(util::MessageDifferencer::Equals(msg1, msg2)); +} + +TEST(MessageDifferencerTest, OneofTest) { + // Create the testing protos + unittest::TestOneof2 msg1; + unittest::TestOneof2 msg2; + + TestUtil::SetOneof1(&msg1); + TestUtil::SetOneof1(&msg2); + + // Compare + EXPECT_TRUE(util::MessageDifferencer::Equals(msg1, msg2)); +} + +TEST(MessageDifferencerTest, OneofInequalityTest) { + // Create the testing protos + unittest::TestOneof2 msg1; + unittest::TestOneof2 msg2; + + TestUtil::SetOneof1(&msg1); + TestUtil::SetOneof2(&msg2); + + // Compare + EXPECT_FALSE(util::MessageDifferencer::Equals(msg1, msg2)); +} + +TEST(MessageDifferencerTest, UnknownFieldPartialEqualTest) { + unittest::TestEmptyMessage empty1; + unittest::TestEmptyMessage empty2; + + UnknownFieldSet* unknown1 = empty1.mutable_unknown_fields(); + UnknownFieldSet* unknown2 = empty2.mutable_unknown_fields(); + + unknown1->AddVarint(243, 122); + unknown1->AddLengthDelimited(245, "abc"); + unknown1->AddGroup(246)->AddFixed32(248, 1); + unknown1->mutable_field(2)->mutable_group()->AddFixed32(248, 2); + + unknown2->AddVarint(243, 122); + unknown2->AddLengthDelimited(245, "abc"); + unknown2->AddGroup(246)->AddFixed32(248, 1); + unknown2->mutable_field(2)->mutable_group()->AddFixed32(248, 2); + + util::MessageDifferencer differencer; + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_TRUE(differencer.Compare(empty1, empty2)); +} + +TEST(MessageDifferencerTest, SpecifiedFieldsEqualityAllTest) { + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + vector fields1; + vector fields2; + msg1.GetReflection()->ListFields(msg1, &fields1); + msg2.GetReflection()->ListFields(msg2, &fields2); + + util::MessageDifferencer differencer; + EXPECT_TRUE(differencer.CompareWithFields(msg1, msg2, fields1, fields2)); +} + +TEST(MessageDifferencerTest, SpecifiedFieldsInequalityAllTest) { + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + + vector fields1; + vector fields2; + msg1.GetReflection()->ListFields(msg1, &fields1); + msg2.GetReflection()->ListFields(msg2, &fields2); + + util::MessageDifferencer differencer; + EXPECT_FALSE(differencer.CompareWithFields(msg1, msg2, fields1, fields2)); +} + +TEST(MessageDifferencerTest, SpecifiedFieldsEmptyListAlwaysSucceeds) { + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + + vector empty_fields; + + util::MessageDifferencer differencer; + EXPECT_TRUE(differencer.CompareWithFields(msg1, msg2, + empty_fields, empty_fields)); + + TestUtil::SetAllFields(&msg2); + EXPECT_TRUE(differencer.CompareWithFields(msg1, msg2, + empty_fields, empty_fields)); +} + +TEST(MessageDifferencerTest, SpecifiedFieldsCompareWithSelf) { + unittest::TestAllTypes msg1; + TestUtil::SetAllFields(&msg1); + + vector fields; + msg1.GetReflection()->ListFields(msg1, &fields); + + util::MessageDifferencer differencer; + EXPECT_TRUE(differencer.CompareWithFields(msg1, msg1, fields, fields)); + + { + // Compare with a subset of fields. + vector compare_fields; + for (int i = 0; i < fields.size(); ++i) { + if (i % 2 == 0) { + compare_fields.push_back(fields[i]); + } + } + EXPECT_TRUE(differencer.CompareWithFields(msg1, msg1, + compare_fields, compare_fields)); + } + { + // Specify a different set of fields to compare, even though we're using the + // same message. This should fail, since we are explicitly saying that the + // set of fields are different. + vector compare_fields1; + vector compare_fields2; + for (int i = 0; i < fields.size(); ++i) { + if (i % 2 == 0) { + compare_fields1.push_back(fields[i]); + } else { + compare_fields2.push_back(fields[i]); + } + } + EXPECT_FALSE(differencer.CompareWithFields( + msg1, msg1, compare_fields1, compare_fields2)); + } +} + +TEST(MessageDifferencerTest, SpecifiedFieldsEqualityAllShuffledTest) { + // This is a public function, so make sure there are no assumptions about the + // list of fields. Randomly shuffle them to make sure that they are properly + // ordered for comparison. + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + vector fields1; + vector fields2; + msg1.GetReflection()->ListFields(msg1, &fields1); + msg2.GetReflection()->ListFields(msg2, &fields2); + + std::random_shuffle(fields1.begin(), fields1.end()); + std::random_shuffle(fields2.begin(), fields2.end()); + + util::MessageDifferencer differencer; + EXPECT_TRUE(differencer.CompareWithFields(msg1, msg2, fields1, fields2)); +} + +TEST(MessageDifferencerTest, SpecifiedFieldsSubsetEqualityTest) { + // Specify a set of fields to compare. All the fields are equal. + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + vector fields1; + msg1.GetReflection()->ListFields(msg1, &fields1); + + vector compare_fields; + // Only compare the field descriptors with even indices. + for (int i = 0; i < fields1.size(); ++i) { + if (i % 2 == 0) { + compare_fields.push_back(fields1[i]); + } + } + + util::MessageDifferencer differencer; + EXPECT_TRUE(differencer.CompareWithFields(msg1, msg2, + compare_fields, compare_fields)); +} + +TEST(MessageDifferencerTest, + SpecifiedFieldsSubsetIgnoresOtherFieldDifferencesTest) { + // Specify a set of fields to compare, but clear all the other fields in one + // of the messages. This should fail a regular compare, but CompareWithFields + // should succeed. + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + vector fields1; + const Reflection* reflection = msg1.GetReflection(); + reflection->ListFields(msg1, &fields1); + + vector compare_fields; + // Only compare the field descriptors with even indices. + for (int i = 0; i < fields1.size(); ++i) { + if (i % 2 == 0) { + compare_fields.push_back(fields1[i]); + } else { + reflection->ClearField(&msg2, fields1[i]); + } + } + + util::MessageDifferencer differencer; + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + EXPECT_TRUE(differencer.CompareWithFields(msg1, msg2, + compare_fields, compare_fields)); +} + +TEST(MessageDifferencerTest, SpecifiedFieldsDetectsDifferencesTest) { + // Change all of the repeated fields in one of the messages, and use only + // those fields for comparison. + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + TestUtil::ModifyRepeatedFields(&msg2); + + vector fields1; + msg1.GetReflection()->ListFields(msg1, &fields1); + + vector compare_fields; + // Only compare the repeated field descriptors. + for (int i = 0; i < fields1.size(); ++i) { + if (fields1[i]->is_repeated()) { + compare_fields.push_back(fields1[i]); + } + } + + util::MessageDifferencer differencer; + EXPECT_FALSE(differencer.CompareWithFields(msg1, msg2, + compare_fields, compare_fields)); +} + +TEST(MessageDifferencerTest, SpecifiedFieldsEquivalenceAllTest) { + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + + TestUtil::SetAllFields(&msg1); + TestUtil::SetAllFields(&msg2); + + vector fields1; + vector fields2; + msg1.GetReflection()->ListFields(msg1, &fields1); + msg2.GetReflection()->ListFields(msg2, &fields2); + + util::MessageDifferencer differencer; + differencer.set_message_field_comparison( + util::MessageDifferencer::EQUIVALENT); + EXPECT_TRUE(differencer.CompareWithFields(msg1, msg2, fields1, fields2)); +} + +TEST(MessageDifferencerTest, + SpecifiedFieldsEquivalenceIgnoresOtherFieldDifferencesTest) { + unittest::TestAllTypes msg1; + unittest::TestAllTypes msg2; + const Descriptor* desc = msg1.GetDescriptor(); + + const FieldDescriptor* optional_int32_desc = + desc->FindFieldByName("optional_int32"); + const FieldDescriptor* optional_int64_desc = + desc->FindFieldByName("optional_int64"); + const FieldDescriptor* default_int64_desc = + desc->FindFieldByName("default_int64"); + ASSERT_TRUE(optional_int32_desc != NULL); + ASSERT_TRUE(optional_int64_desc != NULL); + ASSERT_TRUE(default_int64_desc != NULL); + msg1.set_optional_int32(0); + msg2.set_optional_int64(0); + msg1.set_default_int64(default_int64_desc->default_value_int64()); + + // Set a field to a non-default value so we know that field selection is + // actually doing something. + msg2.set_optional_uint64(23); + + vector fields1; + vector fields2; + fields1.push_back(optional_int32_desc); + fields1.push_back(default_int64_desc); + + fields2.push_back(optional_int64_desc); + + util::MessageDifferencer differencer; + EXPECT_FALSE(differencer.CompareWithFields(msg1, msg2, fields1, fields2)); + differencer.set_message_field_comparison( + util::MessageDifferencer::EQUIVALENT); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + EXPECT_TRUE(differencer.CompareWithFields(msg1, msg2, fields1, fields2)); +} + +TEST(MessageDifferencerTest, RepeatedFieldSetTest_SetOfSet) { + // Create the testing protos + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + + protobuf_unittest::TestDiffMessage::Item* item = msg1.add_item(); + item->add_ra(1); item->add_ra(2); item->add_ra(3); + item = msg1.add_item(); + item->add_ra(5); item->add_ra(6); + item = msg1.add_item(); + item->add_ra(1); item->add_ra(3); + item = msg1.add_item(); + item->add_ra(6); item->add_ra(7); item->add_ra(8); + + item = msg2.add_item(); + item->add_ra(6); item->add_ra(5); + item = msg2.add_item(); + item->add_ra(6); item->add_ra(8); item->add_ra(7); + item = msg2.add_item(); + item->add_ra(1); item->add_ra(3); + item = msg2.add_item(); + item->add_ra(3); item->add_ra(2); item->add_ra(1); + + // Compare + util::MessageDifferencer differencer; + differencer.set_repeated_field_comparison(util::MessageDifferencer::AS_SET); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, RepeatedFieldSetTest_Combination) { + // Create the testing protos + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + // Treat "item" as Map, with key = "a" + // Treat "item.ra" also as Set + // Treat "rv" as Set + // Treat "rw" as List + protobuf_unittest::TestDiffMessage::Item* item = msg1.add_item(); + item->set_a(3); + item->add_ra(1); item->add_ra(2); item->add_ra(3); + item = msg1.add_item(); + item->set_a(4); + item->add_ra(5); item->add_ra(6); + item = msg1.add_item(); + item->set_a(1); + item->add_ra(1); item->add_ra(3); + item = msg1.add_item(); + item->set_a(2); + item->add_ra(6); item->add_ra(7); item->add_ra(8); + + item = msg2.add_item(); + item->set_a(4); + item->add_ra(6); item->add_ra(5); + item = msg2.add_item(); + item->set_a(2); + item->add_ra(6); item->add_ra(8); item->add_ra(7); + item = msg2.add_item(); + item->set_a(1); + item->add_ra(1); item->add_ra(3); + item = msg2.add_item(); + item->set_a(3); + item->add_ra(3); item->add_ra(2); item->add_ra(1); + + msg1.add_rv(3); + msg1.add_rv(4); + msg1.add_rv(7); + msg1.add_rv(0); + msg2.add_rv(4); + msg2.add_rv(3); + msg2.add_rv(0); + msg2.add_rv(7); + + msg1.add_rw("nothing"); msg2.add_rw("nothing"); + msg1.add_rw("should"); msg2.add_rw("should"); + msg1.add_rw("change"); msg2.add_rw("change"); + + // Compare + util::MessageDifferencer differencer; + differencer.TreatAsMap(msg1.GetDescriptor()->FindFieldByName("item"), + item->GetDescriptor()->FindFieldByName("a")); + differencer.TreatAsSet(msg1.GetDescriptor()->FindFieldByName("rv")); + differencer.TreatAsSet(item->GetDescriptor()->FindFieldByName("ra")); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, RepeatedFieldMapTest_Partial) { + protobuf_unittest::TestDiffMessage msg1; + // message msg1 { + // item { a: 1; b: "11" } + // } + protobuf_unittest::TestDiffMessage::Item* item = msg1.add_item(); + item->set_a(1); + item->set_b("11"); + + protobuf_unittest::TestDiffMessage msg2; + // message msg2 { + // item { a: 2; b: "22" } + // item { a: 1; b: "11" } + // } + item = msg2.add_item(); + item->set_a(2); + item->set_b("22"); + item = msg2.add_item(); + item->set_a(1); + item->set_b("11"); + + // Compare + util::MessageDifferencer differencer; + differencer.TreatAsMap(GetFieldDescriptor(msg1, "item"), + GetFieldDescriptor(msg1, "item.a")); + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, RepeatedFieldSetTest_Duplicates) { + protobuf_unittest::TestDiffMessage a, b, c; + // message a: { + // rv: 0 + // rv: 1 + // rv: 0 + // } + a.add_rv(0); + a.add_rv(1); + a.add_rv(0); + // message b: { + // rv: 0 + // rv: 0 + // rv: 1 + // } + b.add_rv(0); + b.add_rv(0); + b.add_rv(1); + // message c: { + // rv: 0 + // rv: 1 + // } + c.add_rv(0); + c.add_rv(1); + util::MessageDifferencer differencer; + differencer.TreatAsSet(GetFieldDescriptor(a, "rv")); + EXPECT_TRUE(differencer.Compare(b, a)); + EXPECT_FALSE(differencer.Compare(c, a)); +} + +TEST(MessageDifferencerTest, RepeatedFieldSetTest_PartialSimple) { + protobuf_unittest::TestDiffMessage a, b, c; + // message a: { + // rm { c: 1 } + // rm { c: 0 } + // } + a.add_rm()->set_c(1); + a.add_rm()->set_c(0); + // message b: { + // rm { c: 1 } + // rm {} + // } + b.add_rm()->set_c(1); + b.add_rm(); + // message c: { + // rm {} + // rm { c: 1 } + // } + c.add_rm(); + c.add_rm()->set_c(1); + util::MessageDifferencer differencer; + differencer.set_scope(util::MessageDifferencer::PARTIAL); + differencer.TreatAsSet(GetFieldDescriptor(a, "rm")); + EXPECT_TRUE(differencer.Compare(b, a)); + EXPECT_TRUE(differencer.Compare(c, a)); +} + +TEST(MessageDifferencerTest, RepeatedFieldSetTest_Partial) { + protobuf_unittest::TestDiffMessage msg1, msg2; + // message msg1: { + // rm { a: 1 } + // rm { b: 2 } + // rm { c: 3 } + // } + msg1.add_rm()->set_a(1); + msg1.add_rm()->set_b(2); + msg1.add_rm()->set_c(3); + // message msg2: { + // rm { a: 1; c: 3 } + // rm { b: 2; c: 3 } + // rm { b: 2 } + // } + protobuf_unittest::TestField* field = msg2.add_rm(); + field->set_a(1); + field->set_c(3); + field = msg2.add_rm(); + field->set_b(2); + field->set_c(3); + field = msg2.add_rm(); + field->set_b(2); + + util::MessageDifferencer differencer; + differencer.set_scope(util::MessageDifferencer::PARTIAL); + differencer.TreatAsSet(GetFieldDescriptor(msg1, "rm")); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, RepeatedFieldMapTest_MultipleFieldsAsKey) { + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + // Treat "item" as Map, with key = ("a", "ra") + // Treat "item.ra" as Set + protobuf_unittest::TestDiffMessage::Item* item = msg1.add_item(); + // key => value: (1, {2, 3}) => "a" + item->set_a(1); + item->add_ra(2); + item->add_ra(3); + item->set_b("a"); + item = msg1.add_item(); + // key => value: (2, {1, 3}) => "b" + item->set_a(2); + item->add_ra(1); + item->add_ra(3); + item->set_b("b"); + item = msg1.add_item(); + // key => value: (1, {1, 3}) => "c" + item->set_a(1); + item->add_ra(1); + item->add_ra(3); + item->set_b("c"); + + item = msg2.add_item(); + // key => value: (1, {1, 3}) => "c" + item->set_a(1); + item->add_ra(3); + item->add_ra(1); + item->set_b("c"); + item = msg2.add_item(); + // key => value: (1, {2, 3}) => "a" + item->set_a(1); + item->add_ra(3); + item->add_ra(2); + item->set_b("a"); + item = msg2.add_item(); + // key => value: (2, {1, 3}) => "b" + item->set_a(2); + item->add_ra(3); + item->add_ra(1); + item->set_b("b"); + + // Compare + util::MessageDifferencer differencer; + differencer.TreatAsSet(GetFieldDescriptor(msg1, "item.ra")); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + vector key_fields; + key_fields.push_back(GetFieldDescriptor(msg1, "item.a")); + key_fields.push_back(GetFieldDescriptor(msg1, "item.ra")); + differencer.TreatAsMapWithMultipleFieldsAsKey( + GetFieldDescriptor(msg1, "item"), key_fields); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + + // Introduce some differences. + msg1.clear_item(); + msg2.clear_item(); + item = msg1.add_item(); + item->set_a(4); + item->add_ra(5); + item->add_ra(6); + item->set_b("hello"); + item = msg2.add_item(); + item->set_a(4); + item->add_ra(6); + item->add_ra(5); + item->set_b("world"); + string output; + differencer.ReportDifferencesToString(&output); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + EXPECT_EQ( + "moved: item[0].ra[0] -> item[0].ra[1] : 5\n" + "moved: item[0].ra[1] -> item[0].ra[0] : 6\n" + "modified: item[0].b: \"hello\" -> \"world\"\n", + output); +} + +TEST(MessageDifferencerTest, RepeatedFieldMapTest_MultipleFieldPathsAsKey) { + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + // Treat "item" as Map, with key = ("m.a", "m.rc") + // Treat "item.m.rc" as Set + protobuf_unittest::TestDiffMessage::Item* item = msg1.add_item(); + // key => value: (1, {2, 3}) => "a" + item->mutable_m()->set_a(1); + item->mutable_m()->add_rc(2); + item->mutable_m()->add_rc(3); + item->set_b("a"); + item = msg1.add_item(); + // key => value: (2, {1, 3}) => "b" + item->mutable_m()->set_a(2); + item->mutable_m()->add_rc(1); + item->mutable_m()->add_rc(3); + item->set_b("b"); + item = msg1.add_item(); + // key => value: (1, {1, 3}) => "c" + item->mutable_m()->set_a(1); + item->mutable_m()->add_rc(1); + item->mutable_m()->add_rc(3); + item->set_b("c"); + + item = msg2.add_item(); + // key => value: (1, {1, 3}) => "c" + item->mutable_m()->set_a(1); + item->mutable_m()->add_rc(3); + item->mutable_m()->add_rc(1); + item->set_b("c"); + item = msg2.add_item(); + // key => value: (1, {2, 3}) => "a" + item->mutable_m()->set_a(1); + item->mutable_m()->add_rc(3); + item->mutable_m()->add_rc(2); + item->set_b("a"); + item = msg2.add_item(); + // key => value: (2, {1, 3}) => "b" + item->mutable_m()->set_a(2); + item->mutable_m()->add_rc(3); + item->mutable_m()->add_rc(1); + item->set_b("b"); + + // Compare + util::MessageDifferencer differencer; + differencer.TreatAsSet(GetFieldDescriptor(msg1, "item.m.rc")); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + vector > key_field_paths; + vector key_field_path1; + key_field_path1.push_back(GetFieldDescriptor(msg1, "item.m")); + key_field_path1.push_back(GetFieldDescriptor(msg1, "item.m.a")); + vector key_field_path2; + key_field_path2.push_back(GetFieldDescriptor(msg1, "item.m")); + key_field_path2.push_back(GetFieldDescriptor(msg1, "item.m.rc")); + key_field_paths.push_back(key_field_path1); + key_field_paths.push_back(key_field_path2); + differencer.TreatAsMapWithMultipleFieldPathsAsKey( + GetFieldDescriptor(msg1, "item"), key_field_paths); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + + // Introduce some differences. + msg1.clear_item(); + msg2.clear_item(); + item = msg1.add_item(); + item->mutable_m()->set_a(4); + item->mutable_m()->add_rc(5); + item->mutable_m()->add_rc(6); + item->set_b("hello"); + item = msg2.add_item(); + item->mutable_m()->set_a(4); + item->mutable_m()->add_rc(6); + item->mutable_m()->add_rc(5); + item->set_b("world"); + string output; + differencer.ReportDifferencesToString(&output); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + EXPECT_EQ( + "modified: item[0].b: \"hello\" -> \"world\"\n" + "moved: item[0].m.rc[0] -> item[0].m.rc[1] : 5\n" + "moved: item[0].m.rc[1] -> item[0].m.rc[0] : 6\n", + output); +} + +TEST(MessageDifferencerTest, RepeatedFieldMapTest_IgnoredKeyFields) { + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + // Treat "item" as Map, with key = ("a", "ra") + protobuf_unittest::TestDiffMessage::Item* item = msg1.add_item(); + item->set_a(1); + item->add_ra(2); + item->set_b("hello"); + item = msg2.add_item(); + item->set_a(1); + item->add_ra(3); + item->set_b("world"); + // Compare + util::MessageDifferencer differencer; + vector key_fields; + key_fields.push_back(GetFieldDescriptor(msg1, "item.a")); + key_fields.push_back(GetFieldDescriptor(msg1, "item.ra")); + differencer.TreatAsMapWithMultipleFieldsAsKey( + GetFieldDescriptor(msg1, "item"), key_fields); + string output; + differencer.ReportDifferencesToString(&output); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + EXPECT_EQ( + "added: item[0]: { a: 1 ra: 3 b: \"world\" }\n" + "deleted: item[0]: { a: 1 ra: 2 b: \"hello\" }\n", + output); + // Ignored fields that are listed as parts of the key are still used + // in key comparison, but they're not used in value comparison. + differencer.IgnoreField(GetFieldDescriptor(msg1, "item.ra")); + output.clear(); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + EXPECT_EQ( + "added: item[0]: { a: 1 ra: 3 b: \"world\" }\n" + "deleted: item[0]: { a: 1 ra: 2 b: \"hello\" }\n", + output); + // Ignoring a field in the key is different from treating the left fields + // as key. That is: + // (key = ("a", "ra") && ignore "ra") != (key = ("a") && ignore "ra") + util::MessageDifferencer differencer2; + differencer2.TreatAsMap(GetFieldDescriptor(msg1, "item"), + GetFieldDescriptor(msg1, "item.a")); + differencer2.IgnoreField(GetFieldDescriptor(msg1, "item.ra")); + output.clear(); + differencer2.ReportDifferencesToString(&output); + EXPECT_FALSE(differencer2.Compare(msg1, msg2)); + EXPECT_EQ( + "ignored: item[0].ra\n" + "modified: item[0].b: \"hello\" -> \"world\"\n", + output); +} + +static const char* const kIgnoredFields[] = {"rm.b", "rm.m.b"}; + +class TestIgnorer : public util::MessageDifferencer::IgnoreCriteria { + public: + bool IsIgnored( + const Message& message1, const Message& message2, + const FieldDescriptor* field, + const vector& parent_fields) + override { + string name = ""; + for (int i = 0; i < parent_fields.size(); ++i) { + name += parent_fields[i].field->name() + "."; + } + name += field->name(); + for (int i = 0; i < GOOGLE_ARRAYSIZE(kIgnoredFields); ++i) { + if (name.compare(kIgnoredFields[i]) == 0) { + return true; + } + } + return false; + } +}; + +TEST(MessageDifferencerTest, TreatRepeatedFieldAsSetWithIgnoredFields) { + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + TextFormat::MergeFromString("rm { a: 11\n b: 12 }", &msg1); + TextFormat::MergeFromString("rm { a: 11\n b: 13 }", &msg2); + util::MessageDifferencer differ; + differ.TreatAsSet(GetFieldDescriptor(msg1, "rm")); + differ.AddIgnoreCriteria(new TestIgnorer); + EXPECT_TRUE(differ.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, TreatRepeatedFieldAsMapWithIgnoredKeyFields) { + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + TextFormat::MergeFromString("rm { a: 11\n m { a: 12\n b: 13\n } }", &msg1); + TextFormat::MergeFromString("rm { a: 11\n m { a: 12\n b: 14\n } }", &msg2); + util::MessageDifferencer differ; + differ.TreatAsMap(GetFieldDescriptor(msg1, "rm"), + GetFieldDescriptor(msg1, "rm.m")); + differ.AddIgnoreCriteria(new TestIgnorer); + EXPECT_TRUE(differ.Compare(msg1, msg2)); +} + +// Takes the product of all elements of item.ra as the key for key comparison. +class ValueProductMapKeyComparator + : public util::MessageDifferencer::MapKeyComparator { + public: + virtual bool IsMatch(const Message &message1, + const Message &message2) const { + const Reflection* reflection1 = message1.GetReflection(); + const Reflection* reflection2 = message2.GetReflection(); + // FieldDescriptor for item.ra + const FieldDescriptor* ra_field = + message1.GetDescriptor()->FindFieldByName("ra"); + // Get the product of all elements in item.ra + int result1 = 1, result2 = 1; + for (int i = 0; i < reflection1->FieldSize(message1, ra_field); ++i) { + result1 *= reflection1->GetRepeatedInt32(message1, ra_field, i); + } + for (int i = 0; i < reflection2->FieldSize(message2, ra_field); ++i) { + result2 *= reflection2->GetRepeatedInt32(message2, ra_field, i); + } + return result1 == result2; + } +}; + +TEST(MessageDifferencerTest, RepeatedFieldMapTest_CustomMapKeyComparator) { + 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->add_ra(6); + item->add_ra(35); + item->set_b("hello"); + item = msg2.add_item(); + item->add_ra(10); + item->add_ra(21); + item->set_b("hello"); + util::MessageDifferencer differencer; + ValueProductMapKeyComparator key_comparator; + differencer.TreatAsMapUsingKeyComparator( + GetFieldDescriptor(msg1, "item"), &key_comparator); + string output; + differencer.ReportDifferencesToString(&output); + // Though the above two messages have different values for item.ra, they + // are regarded as having the same key because 6 * 35 == 10 * 21. That's + // how the key comparator determines if the two have the same key. + // However, in value comparison, all fields of the message are taken into + // consideration, so they are different because their item.ra fields have + // different values using normal value comparison. + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + EXPECT_EQ( + "modified: item[0].ra[0]: 6 -> 10\n" + "modified: item[0].ra[1]: 35 -> 21\n", + output); + differencer.IgnoreField(GetFieldDescriptor(msg1, "item.ra")); + output.clear(); + // item.ra is ignored in value comparison, so the two messages equal. + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + EXPECT_EQ("ignored: item[0].ra\n", output); +} + +TEST(MessageDifferencerTest, RepeatedFieldSetTest_Subset) { + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + + msg1.add_rv(3); + msg1.add_rv(8); + msg1.add_rv(2); + msg2.add_rv(2); + msg2.add_rv(3); + msg2.add_rv(5); + msg2.add_rv(8); + + util::MessageDifferencer differencer; + + // Fail with only partial scope set. + differencer.set_scope(util::MessageDifferencer::PARTIAL); + differencer.set_repeated_field_comparison(util::MessageDifferencer::AS_LIST); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + + // Fail with only set-like comparison set. + differencer.set_scope(util::MessageDifferencer::FULL); + differencer.set_repeated_field_comparison(util::MessageDifferencer::AS_SET); + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + + // Succeed with scope and repeated field comparison set properly. + differencer.set_scope(util::MessageDifferencer::PARTIAL); + differencer.set_repeated_field_comparison(util::MessageDifferencer::AS_SET); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, IgnoreField_Single) { + protobuf_unittest::TestField msg1; + protobuf_unittest::TestField msg2; + + msg1.set_c(3); + msg1.add_rc(1); + + msg2.set_c(5); + msg2.add_rc(1); + + util::MessageDifferencer differencer; + differencer.IgnoreField(GetFieldDescriptor(msg1, "c")); + + ExpectEqualsWithDifferencer(&differencer, msg1, msg2); +} + +TEST(MessageDifferencerTest, IgnoreField_Repeated) { + protobuf_unittest::TestField msg1; + protobuf_unittest::TestField msg2; + + msg1.set_c(3); + msg1.add_rc(1); + msg1.add_rc(2); + + msg2.set_c(3); + msg2.add_rc(1); + msg2.add_rc(3); + + util::MessageDifferencer differencer; + differencer.IgnoreField(GetFieldDescriptor(msg1, "rc")); + + ExpectEqualsWithDifferencer(&differencer, msg1, msg2); +} + +TEST(MessageDifferencerTest, IgnoreField_Message) { + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + + protobuf_unittest::TestField* field; + + field = msg1.add_rm(); + field->set_c(3); + + field = msg2.add_rm(); + field->set_c(4); + + util::MessageDifferencer differencer; + differencer.IgnoreField(GetFieldDescriptor(msg1, "rm")); + + ExpectEqualsWithDifferencer(&differencer, msg1, msg2); +} + +TEST(MessageDifferencerTest, IgnoreField_Group) { + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + + protobuf_unittest::TestDiffMessage::Item* item; + + item = msg1.add_item(); + item->set_a(3); + + item = msg2.add_item(); + item->set_a(4); + + util::MessageDifferencer differencer; + differencer.IgnoreField(GetFieldDescriptor(msg1, "item")); + + ExpectEqualsWithDifferencer(&differencer, msg1, msg2); +} + +TEST(MessageDifferencerTest, IgnoreField_Missing) { + protobuf_unittest::TestField msg1; + protobuf_unittest::TestField msg2; + + msg1.set_c(3); + msg1.add_rc(1); + + msg2.add_rc(1); + + util::MessageDifferencer differencer; + differencer.IgnoreField(GetFieldDescriptor(msg1, "c")); + + ExpectEqualsWithDifferencer(&differencer, msg1, msg2); + ExpectEqualsWithDifferencer(&differencer, msg2, msg1); +} + +TEST(MessageDifferencerTest, IgnoreField_Multiple) { + protobuf_unittest::TestField msg1; + protobuf_unittest::TestField msg2; + + msg1.set_c(3); + msg1.add_rc(1); + msg1.add_rc(2); + + msg2.set_c(5); + msg2.add_rc(1); + msg2.add_rc(3); + + const FieldDescriptor* c = GetFieldDescriptor(msg1, "c"); + const FieldDescriptor* rc = GetFieldDescriptor(msg1, "rc"); + + { // Ignore c + util::MessageDifferencer differencer; + differencer.IgnoreField(c); + + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + } + { // Ignore rc + util::MessageDifferencer differencer; + differencer.IgnoreField(rc); + + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + } + { // Ignore both + util::MessageDifferencer differencer; + differencer.IgnoreField(c); + differencer.IgnoreField(rc); + + ExpectEqualsWithDifferencer(&differencer, msg1, msg2); + } +} + +TEST(MessageDifferencerTest, IgnoreField_NestedMessage) { + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + + protobuf_unittest::TestField* field; + + field = msg1.add_rm(); + field->set_c(3); + field->add_rc(1); + + field = msg2.add_rm(); + field->set_c(4); + field->add_rc(1); + + util::MessageDifferencer differencer; + differencer.IgnoreField(GetFieldDescriptor(msg1, "rm.c")); + + ExpectEqualsWithDifferencer(&differencer, msg1, msg2); +} + +TEST(MessageDifferencerTest, IgnoreField_NestedGroup) { + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + + protobuf_unittest::TestDiffMessage::Item* item; + + item = msg1.add_item(); + item->set_a(3); + item->set_b("foo"); + + item = msg2.add_item(); + item->set_a(4); + item->set_b("foo"); + + util::MessageDifferencer differencer; + differencer.IgnoreField(GetFieldDescriptor(msg1, "item.a")); + + ExpectEqualsWithDifferencer(&differencer, msg1, msg2); +} + +TEST(MessageDifferencerTest, IgnoreField_InsideSet) { + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + + protobuf_unittest::TestDiffMessage::Item* item; + + item = msg1.add_item(); + item->set_a(1); + item->set_b("foo"); + item->add_ra(1); + + item = msg1.add_item(); + item->set_a(2); + item->set_b("bar"); + item->add_ra(2); + + item = msg2.add_item(); + item->set_a(2); + item->set_b("bar"); + item->add_ra(2); + + item = msg2.add_item(); + item->set_a(1); + item->set_b("baz"); + item->add_ra(1); + + const FieldDescriptor* item_desc = GetFieldDescriptor(msg1, "item"); + const FieldDescriptor* b = GetFieldDescriptor(msg1, "item.b"); + + util::MessageDifferencer differencer; + differencer.IgnoreField(b); + differencer.TreatAsSet(item_desc); + + ExpectEqualsWithDifferencer(&differencer, msg1, msg2); +} + +TEST(MessageDifferencerTest, IgnoreField_InsideMap) { + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + + protobuf_unittest::TestDiffMessage::Item* item; + + item = msg1.add_item(); + item->set_a(1); + item->set_b("foo"); + item->add_ra(1); + + item = msg1.add_item(); + item->set_a(2); + item->set_b("bar"); + item->add_ra(2); + + item = msg2.add_item(); + item->set_a(2); + item->set_b("bar"); + item->add_ra(2); + + item = msg2.add_item(); + item->set_a(1); + item->set_b("baz"); + item->add_ra(1); + + const FieldDescriptor* item_desc = GetFieldDescriptor(msg1, "item"); + const FieldDescriptor* a = GetFieldDescriptor(msg1, "item.a"); + const FieldDescriptor* b = GetFieldDescriptor(msg1, "item.b"); + + util::MessageDifferencer differencer; + differencer.IgnoreField(b); + differencer.TreatAsMap(item_desc, a); + + ExpectEqualsWithDifferencer(&differencer, msg1, msg2); +} + +TEST(MessageDifferencerTest, IgnoreField_DoesNotIgnoreKey) { + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + + protobuf_unittest::TestDiffMessage::Item* item; + + item = msg1.add_item(); + item->set_a(1); + item->set_b("foo"); + item->add_ra(1); + + item = msg2.add_item(); + item->set_a(2); + item->set_b("foo"); + item->add_ra(1); + + const FieldDescriptor* item_desc = GetFieldDescriptor(msg1, "item"); + const FieldDescriptor* a = GetFieldDescriptor(msg1, "item.a"); + + util::MessageDifferencer differencer; + differencer.IgnoreField(a); + differencer.TreatAsMap(item_desc, a); + + EXPECT_FALSE(differencer.Compare(msg1, msg2)); +} + +TEST(MessageDifferencerTest, IgnoreField_TrumpsCompareWithFields) { + protobuf_unittest::TestField msg1; + protobuf_unittest::TestField msg2; + + msg1.set_c(3); + msg1.add_rc(1); + msg1.add_rc(2); + + msg2.set_c(3); + msg2.add_rc(1); + msg2.add_rc(3); + + const FieldDescriptor* c = GetFieldDescriptor(msg1, "c"); + const FieldDescriptor* rc = GetFieldDescriptor(msg1, "rc"); + + vector fields; + fields.push_back(c); + fields.push_back(rc); + + util::MessageDifferencer differencer; + differencer.IgnoreField(rc); + + differencer.set_scope(util::MessageDifferencer::FULL); + EXPECT_TRUE(differencer.CompareWithFields(msg1, msg2, fields, fields)); + + differencer.set_scope(util::MessageDifferencer::PARTIAL); + EXPECT_TRUE(differencer.CompareWithFields(msg1, msg2, fields, fields)); +} + + +// Test class to save a copy of the last field_context.parent_fields() vector +// passed to the comparison function. +class ParentSavingFieldComparator : public util::FieldComparator { + public: + ParentSavingFieldComparator() {} + + virtual ComparisonResult Compare( + const google::protobuf::Message& message_1, + const google::protobuf::Message& message_2, + const google::protobuf::FieldDescriptor* field, + int index_1, int index_2, + const google::protobuf::util::FieldContext* field_context) { + if (field_context) + parent_fields_ = *(field_context->parent_fields()); + if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { + return RECURSE; + } else { + return SAME; + } + } + + vector parent_fields() { + return parent_fields_; + } + + private: + vector parent_fields_; +}; + +// Tests if MessageDifferencer sends the parent fields in the FieldContext +// parameter. +TEST(MessageDifferencerTest, FieldContextParentFieldsTest) { + protobuf_unittest::TestDiffMessage msg1; + msg1.add_rm()->set_c(1); + protobuf_unittest::TestDiffMessage msg2; + msg2.add_rm()->set_c(1); + + ParentSavingFieldComparator field_comparator; + util::MessageDifferencer differencer; + differencer.set_field_comparator(&field_comparator); + differencer.Compare(msg1, msg2); + + // We want only one parent with the name "rm" + ASSERT_EQ(1, field_comparator.parent_fields().size()); + EXPECT_EQ("rm", field_comparator.parent_fields()[0].field->name()); +} + + +class ComparisonTest : public testing::Test { + protected: + ComparisonTest() : use_equivalency_(false), repeated_field_as_set_(false) { + // Setup the test. + TestUtil::SetAllFields(&proto1_); + TestUtil::SetAllFields(&proto2_); + + TestUtil::SetAllExtensions(&proto1ex_); + TestUtil::SetAllExtensions(&proto2ex_); + + TestUtil::SetAllFieldsAndExtensions(&orderings_proto1_); + TestUtil::SetAllFieldsAndExtensions(&orderings_proto2_); + + unknown1_ = empty1_.mutable_unknown_fields(); + unknown2_ = empty2_.mutable_unknown_fields(); + } + + ~ComparisonTest() { } + + void SetSpecialFieldOption(const Message& message, + util::MessageDifferencer* d) { + if (!ignored_field_.empty()) { + d->IgnoreField(GetFieldDescriptor(message, ignored_field_)); + } + + if (repeated_field_as_set_) { + d->set_repeated_field_comparison(util::MessageDifferencer::AS_SET); + } + + if (!set_field_.empty()) { + d->TreatAsSet(GetFieldDescriptor(message, set_field_)); + } + + if (!map_field_.empty() && !map_key_.empty()) { + d->TreatAsMap(GetFieldDescriptor(message, map_field_), + GetFieldDescriptor(message, map_field_ + "." + map_key_)); + } + } + + string Run(const Message& msg1, const Message& msg2) { + string output; + + // Setup the comparison. + util::MessageDifferencer differencer; + differencer.ReportDifferencesToString(&output); + + if (use_equivalency_) { + differencer.set_message_field_comparison( + util::MessageDifferencer::EQUIVALENT); + } + + SetSpecialFieldOption(msg1, &differencer); + + // Conduct the comparison. + EXPECT_FALSE(differencer.Compare(msg1, msg2)); + + return output; + } + + string Run() { + return Run(proto1_, proto2_); + } + + string RunOrder() { + return Run(orderings_proto1_, orderings_proto2_); + } + + string RunEx() { + return Run(proto1ex_, proto2ex_); + } + + string RunDiff() { + return Run(proto1diff_, proto2diff_); + } + + string RunUn() { + return Run(empty1_, empty2_); + } + + void use_equivalency() { + use_equivalency_ = true; + } + + void repeated_field_as_set() { + repeated_field_as_set_ = true; + } + + void field_as_set(const string& field) { + set_field_ = field; + } + + void field_as_map(const string& field, const string& key) { + map_field_ = field; + map_key_ = key; + } + + void ignore_field(const string& field) { + ignored_field_ = field; + } + + unittest::TestAllTypes proto1_; + unittest::TestAllTypes proto2_; + + unittest::TestFieldOrderings orderings_proto1_; + unittest::TestFieldOrderings orderings_proto2_; + + unittest::TestAllExtensions proto1ex_; + unittest::TestAllExtensions proto2ex_; + + unittest::TestDiffMessage proto1diff_; + unittest::TestDiffMessage proto2diff_; + + unittest::TestEmptyMessage empty1_; + unittest::TestEmptyMessage empty2_; + + UnknownFieldSet* unknown1_; + UnknownFieldSet* unknown2_; + + bool use_equivalency_; + bool repeated_field_as_set_; + + string set_field_; + string map_field_; + string map_key_; + string ignored_field_; +}; + +// Basic tests. +TEST_F(ComparisonTest, AdditionTest) { + proto1_.clear_optional_int32(); + + EXPECT_EQ("added: optional_int32: 101\n", + Run()); +} + +TEST_F(ComparisonTest, Addition_OrderTest) { + orderings_proto1_.clear_my_int(); + + EXPECT_EQ("added: my_int: 1\n", + RunOrder()); +} + +TEST_F(ComparisonTest, DeletionTest) { + proto2_.clear_optional_int32(); + + EXPECT_EQ("deleted: optional_int32: 101\n", + Run()); +} + +TEST_F(ComparisonTest, Deletion_OrderTest) { + orderings_proto2_.clear_my_string(); + + EXPECT_EQ("deleted: my_string: \"foo\"\n", + RunOrder()); +} + +TEST_F(ComparisonTest, RepeatedDeletionTest) { + proto2_.clear_repeated_int32(); + + EXPECT_EQ("deleted: repeated_int32[0]: 201\n" + "deleted: repeated_int32[1]: 301\n", + Run()); +} + +TEST_F(ComparisonTest, ModificationTest) { + proto1_.set_optional_int32(-1); + + EXPECT_EQ("modified: optional_int32: -1 -> 101\n", + Run()); +} + +// Basic equivalency tests. +TEST_F(ComparisonTest, EquivalencyAdditionTest) { + use_equivalency(); + + proto1_.clear_optional_int32(); + + EXPECT_EQ("modified: optional_int32: 0 -> 101\n", + Run()); +} + +TEST_F(ComparisonTest, EquivalencyDeletionTest) { + use_equivalency(); + + proto2_.clear_optional_int32(); + + EXPECT_EQ("modified: optional_int32: 101 -> 0\n", + Run()); +} + +// Group tests. +TEST_F(ComparisonTest, GroupAdditionTest) { + proto1_.mutable_optionalgroup()->clear_a(); + + EXPECT_EQ("added: optionalgroup.a: 117\n", + Run()); +} + +TEST_F(ComparisonTest, GroupDeletionTest) { + proto2_.mutable_optionalgroup()->clear_a(); + + EXPECT_EQ("deleted: optionalgroup.a: 117\n", + Run()); +} + +TEST_F(ComparisonTest, GroupModificationTest) { + proto1_.mutable_optionalgroup()->set_a(2); + + EXPECT_EQ("modified: optionalgroup.a: 2 -> 117\n", + Run()); +} + +TEST_F(ComparisonTest, GroupFullAdditionTest) { + proto1_.clear_optionalgroup(); + + // Note the difference in the output between this and GroupAdditionTest. + EXPECT_EQ("added: optionalgroup: { a: 117 }\n", + Run()); +} + +TEST_F(ComparisonTest, GroupFullDeletionTest) { + proto2_.clear_optionalgroup(); + + EXPECT_EQ("deleted: optionalgroup: { a: 117 }\n", + Run()); +} + +TEST_F(ComparisonTest, RepeatedSetOptionTest) { + repeated_field_as_set(); + + proto2_.clear_repeatedgroup(); + proto1_.clear_repeatedgroup(); + proto1_.add_repeatedgroup()->set_a(317); + proto2_.add_repeatedgroup()->set_a(909); + proto2_.add_repeatedgroup()->set_a(907); + proto1_.add_repeatedgroup()->set_a(904); + proto1_.add_repeatedgroup()->set_a(907); + proto1_.add_repeatedgroup()->set_a(909); + + EXPECT_EQ("moved: repeatedgroup[2] -> repeatedgroup[1] : { a: 907 }\n" + "moved: repeatedgroup[3] -> repeatedgroup[0] : { a: 909 }\n" + "deleted: repeatedgroup[0]: { a: 317 }\n" + "deleted: repeatedgroup[1]: { a: 904 }\n", + Run()); +} + +TEST_F(ComparisonTest, RepeatedSetOptionTest_Ex) { + repeated_field_as_set(); + + proto1ex_.ClearExtension(protobuf_unittest::repeated_nested_message_extension); + proto2ex_.ClearExtension(protobuf_unittest::repeated_nested_message_extension); + proto2ex_.AddExtension(protobuf_unittest::repeated_nested_message_extension) + ->set_bb(909); + proto2ex_.AddExtension(protobuf_unittest::repeated_nested_message_extension) + ->set_bb(907); + proto1ex_.AddExtension(protobuf_unittest::repeated_nested_message_extension) + ->set_bb(904); + proto1ex_.AddExtension(protobuf_unittest::repeated_nested_message_extension) + ->set_bb(907); + proto1ex_.AddExtension(protobuf_unittest::repeated_nested_message_extension) + ->set_bb(909); + + EXPECT_EQ("moved: (protobuf_unittest.repeated_nested_message_extension)[2] ->" + " (protobuf_unittest.repeated_nested_message_extension)[0] :" + " { bb: 909 }\n" + "deleted: (protobuf_unittest.repeated_nested_message_extension)[0]:" + " { bb: 904 }\n", + RunEx()); +} + +TEST_F(ComparisonTest, RepeatedMapFieldTest_Group) { + field_as_map("repeatedgroup", "a"); + proto1_.clear_repeatedgroup(); + proto2_.clear_repeatedgroup(); + + proto1_.add_repeatedgroup()->set_a(317); // deleted + proto1_.add_repeatedgroup()->set_a(904); // deleted + proto1_.add_repeatedgroup()->set_a(907); // moved from + proto1_.add_repeatedgroup()->set_a(909); // moved from + + proto2_.add_repeatedgroup()->set_a(909); // moved to + proto2_.add_repeatedgroup()->set_a(318); // added + proto2_.add_repeatedgroup()->set_a(907); // moved to + + EXPECT_EQ("moved: repeatedgroup[3] -> repeatedgroup[0] : { a: 909 }\n" + "added: repeatedgroup[1]: { a: 318 }\n" + "deleted: repeatedgroup[0]: { a: 317 }\n" + "deleted: repeatedgroup[1]: { a: 904 }\n", + Run()); +} + +TEST_F(ComparisonTest, RepeatedMapFieldTest_MessageKey) { + // Use m as key, but use b as value. + field_as_map("item", "m"); + + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + protobuf_unittest::TestDiffMessage::Item* item = msg1.add_item(); + + // The following code creates one deletion, one addition and two moved fields + // on the messages. + item->mutable_m()->set_c(0); + item->set_b("first"); + item = msg1.add_item(); + item->mutable_m()->set_c(2); + item->set_b("second"); + item = msg1.add_item(); item->set_b("null"); // empty key moved + item = msg1.add_item(); + item->mutable_m()->set_c(3); + item->set_b("third"); // deletion + item = msg1.add_item(); + item->mutable_m()->set_c(2); + item->set_b("second"); // duplicated key ( deletion ) + item = msg2.add_item(); + item->mutable_m()->set_c(2); + item->set_b("second"); // modification + item = msg2.add_item(); + item->mutable_m()->set_c(4); + item->set_b("fourth"); // addition + item = msg2.add_item(); + item->mutable_m()->set_c(0); + item->set_b("fist"); // move with change + item = msg2.add_item(); item->set_b("null"); + + EXPECT_EQ( + "modified: item[0].b -> item[2].b: \"first\" -> \"fist\"\n" + "moved: item[1] -> item[0] : { b: \"second\" m { c: 2 } }\n" + "moved: item[2] -> item[3] : { b: \"null\" }\n" + "added: item[1]: { b: \"fourth\" m { c: 4 } }\n" + "deleted: item[3]: { b: \"third\" m { c: 3 } }\n" + "deleted: item[4]: { b: \"second\" m { c: 2 } }\n", + Run(msg1, msg2)); +} + +TEST_F(ComparisonTest, RepeatedFieldSetTest_SetOfSet) { + repeated_field_as_set(); + // Create the testing protos + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + + protobuf_unittest::TestDiffMessage::Item* item = msg1.add_item(); + item->add_ra(1); item->add_ra(2); item->add_ra(3); + item = msg1.add_item(); + item->add_ra(5); item->add_ra(6); + item = msg1.add_item(); + item->add_ra(1); item->add_ra(3); + item = msg1.add_item(); + item->add_ra(6); item->add_ra(7); item->add_ra(8); + + item = msg2.add_item(); + item->add_ra(6); item->add_ra(5); + item = msg2.add_item(); + item->add_ra(6); item->add_ra(8); + item = msg2.add_item(); + item->add_ra(1); item->add_ra(3); + item = msg2.add_item(); + item->add_ra(3); item->add_ra(2); item->add_ra(1); + + // Compare + EXPECT_EQ("moved: item[0].ra[0] -> item[3].ra[2] : 1\n" + "moved: item[0].ra[2] -> item[3].ra[0] : 3\n" + "moved: item[0] -> item[3] : { ra: 1 ra: 2 ra: 3 }\n" + "moved: item[1].ra[0] -> item[0].ra[1] : 5\n" + "moved: item[1].ra[1] -> item[0].ra[0] : 6\n" + "moved: item[1] -> item[0] : { ra: 5 ra: 6 }\n" + "added: item[1]: { ra: 6 ra: 8 }\n" + "deleted: item[3]: { ra: 6 ra: 7 ra: 8 }\n", + Run(msg1, msg2)); +} + +TEST_F(ComparisonTest, RepeatedMapFieldTest_RepeatedKey) { + // used rb as a key, but b is the value. + repeated_field_as_set(); + field_as_map("item", "rb"); + + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + protobuf_unittest::TestDiffMessage::Item* item = msg1.add_item(); + item->add_rb("a"); + item->add_rb("b"); + item->set_b("first"); + + item = msg2.add_item(); + item->add_rb("c"); + item->set_b("second"); + + item = msg2.add_item(); + item->add_rb("b"); + item->add_rb("a"); + item->set_b("fist"); + + + EXPECT_EQ("modified: item[0].b -> item[1].b: \"first\" -> \"fist\"\n" + "moved: item[0].rb[0] -> item[1].rb[1] : \"a\"\n" + "moved: item[0].rb[1] -> item[1].rb[0] : \"b\"\n" + "added: item[0]: { b: \"second\" rb: \"c\" }\n", + Run(msg1, msg2)); +} + +TEST_F(ComparisonTest, RepeatedMapFieldTest_RepeatedMessageKey) { + field_as_map("item", "rm"); + + protobuf_unittest::TestDiffMessage msg1; + protobuf_unittest::TestDiffMessage msg2; + protobuf_unittest::TestDiffMessage::Item* item = msg1.add_item(); + protobuf_unittest::TestField* key = item->add_rm(); + key->set_c(2); key->add_rc(10); key->add_rc(10); + item = msg1.add_item(); key = item->add_rm(); + key->set_c(0); key->add_rc(1); key->add_rc(2); + key = item->add_rm(); + key->set_c(0); + item->add_rb("first"); + + item = msg2.add_item(); + item->CopyFrom(msg1.item(1)); + item->add_rb("second"); + + EXPECT_EQ("added: item[0].rb[1]: \"second\"\n" + "deleted: item[0]: { rm { c: 2 rc: 10 rc: 10 } }\n", + Run(msg1, msg2)); +} + +TEST_F(ComparisonTest, RepeatedSetOptionTest_Unknown) { + // Currently, as_set option doens't have affects on unknown field. + // If needed, this feature will be added by request. + repeated_field_as_set(); + unknown1_->AddGroup(245)->AddFixed32(248, 1); + unknown2_->AddGroup(245)->AddFixed32(248, 3); + unknown2_->AddGroup(245)->AddFixed32(248, 1); + + // We expect it behaves the same as normal comparison. + EXPECT_EQ("modified: 245[0].248[0]: 0x00000001 -> 0x00000003\n" + "added: 245[1]: { ... }\n", + RunUn()); +} + +TEST_F(ComparisonTest, Matching_Unknown) { + unknown1_->AddGroup(245)->AddFixed32(248, 1); + unknown2_->AddGroup(245)->AddFixed32(248, 1); + unknown1_->AddGroup(245)->AddFixed32(248, 3); + unknown2_->AddGroup(245)->AddFixed32(248, 3); + unknown2_->AddLengthDelimited(242, "cat"); + unknown2_->AddGroup(246)->AddFixed32(248, 4); + + // report_match is false so only added/modified fields are expected. + EXPECT_EQ("added: 242[0]: \"cat\"\n" + "added: 246[0]: { ... }\n", + RunUn()); +} + +TEST_F(ComparisonTest, RepeatedSetFieldTest) { + field_as_set("repeatedgroup"); + + proto1_.clear_repeatedgroup(); + proto2_.clear_repeatedgroup(); + proto2_.add_repeatedgroup()->set_a(909); + proto2_.add_repeatedgroup()->set_a(907); + proto1_.add_repeatedgroup()->set_a(317); + proto1_.add_repeatedgroup()->set_a(904); + proto1_.add_repeatedgroup()->set_a(907); + proto1_.add_repeatedgroup()->set_a(909); + + EXPECT_EQ("moved: repeatedgroup[2] -> repeatedgroup[1] : { a: 907 }\n" + "moved: repeatedgroup[3] -> repeatedgroup[0] : { a: 909 }\n" + "deleted: repeatedgroup[0]: { a: 317 }\n" + "deleted: repeatedgroup[1]: { a: 904 }\n", + Run()); +} + +// Embedded message tests. +TEST_F(ComparisonTest, EmbeddedAdditionTest) { + proto1_.mutable_optional_nested_message()->clear_bb(); + + EXPECT_EQ("added: optional_nested_message.bb: 118\n", + Run()); +} + +TEST_F(ComparisonTest, EmbeddedDeletionTest) { + proto2_.mutable_optional_nested_message()->clear_bb(); + + EXPECT_EQ("deleted: optional_nested_message.bb: 118\n", + Run()); +} + +TEST_F(ComparisonTest, EmbeddedModificationTest) { + proto1_.mutable_optional_nested_message()->set_bb(2); + + EXPECT_EQ("modified: optional_nested_message.bb: 2 -> 118\n", + Run()); +} + +TEST_F(ComparisonTest, EmbeddedFullAdditionTest) { + proto1_.clear_optional_nested_message(); + + EXPECT_EQ("added: optional_nested_message: { bb: 118 }\n", + Run()); +} + +TEST_F(ComparisonTest, EmbeddedPartialAdditionTest) { + proto1_.clear_optional_nested_message(); + proto2_.mutable_optional_nested_message()->clear_bb(); + + EXPECT_EQ("added: optional_nested_message: { }\n", + Run()); +} + +TEST_F(ComparisonTest, EmbeddedFullDeletionTest) { + proto2_.clear_optional_nested_message(); + + EXPECT_EQ("deleted: optional_nested_message: { bb: 118 }\n", + Run()); +} + +// Repeated element tests. +TEST_F(ComparisonTest, BasicRepeatedTest) { + proto1_.clear_repeated_int32(); + proto2_.clear_repeated_int32(); + + proto1_.add_repeated_int32(500); + proto1_.add_repeated_int32(501); + proto1_.add_repeated_int32(502); + proto1_.add_repeated_int32(503); + proto1_.add_repeated_int32(500); + + proto2_.add_repeated_int32(500); + proto2_.add_repeated_int32(509); + proto2_.add_repeated_int32(502); + proto2_.add_repeated_int32(504); + + EXPECT_EQ("modified: repeated_int32[1]: 501 -> 509\n" + "modified: repeated_int32[3]: 503 -> 504\n" + "deleted: repeated_int32[4]: 500\n", + Run()); +} + +TEST_F(ComparisonTest, BasicRepeatedTest_SetOption) { + repeated_field_as_set(); + proto1_.clear_repeated_int32(); + proto2_.clear_repeated_int32(); + + proto1_.add_repeated_int32(501); + proto1_.add_repeated_int32(502); + proto1_.add_repeated_int32(503); + proto1_.add_repeated_int32(500); + proto1_.add_repeated_int32(500); + + proto2_.add_repeated_int32(500); + proto2_.add_repeated_int32(509); + proto2_.add_repeated_int32(503); + proto2_.add_repeated_int32(502); + proto2_.add_repeated_int32(504); + + EXPECT_EQ("moved: repeated_int32[1] -> repeated_int32[3] : 502\n" + "moved: repeated_int32[3] -> repeated_int32[0] : 500\n" + "added: repeated_int32[1]: 509\n" + "added: repeated_int32[4]: 504\n" + "deleted: repeated_int32[0]: 501\n" + "deleted: repeated_int32[4]: 500\n", + Run()); +} + +TEST_F(ComparisonTest, BasicRepeatedTest_SetField) { + field_as_set("repeated_int32"); + proto1_.clear_repeated_int32(); + proto2_.clear_repeated_int32(); + + proto1_.add_repeated_int32(501); + proto1_.add_repeated_int32(502); + proto1_.add_repeated_int32(503); + proto1_.add_repeated_int32(500); + proto1_.add_repeated_int32(500); + + proto2_.add_repeated_int32(500); + proto2_.add_repeated_int32(509); + proto2_.add_repeated_int32(503); + proto2_.add_repeated_int32(502); + proto2_.add_repeated_int32(504); + + EXPECT_EQ("moved: repeated_int32[1] -> repeated_int32[3] : 502\n" + "moved: repeated_int32[3] -> repeated_int32[0] : 500\n" + "added: repeated_int32[1]: 509\n" + "added: repeated_int32[4]: 504\n" + "deleted: repeated_int32[0]: 501\n" + "deleted: repeated_int32[4]: 500\n", + Run()); +} + +// Multiple action tests. +TEST_F(ComparisonTest, AddDeleteTest) { + proto1_.clear_optional_int32(); + proto2_.clear_optional_int64(); + + EXPECT_EQ("added: optional_int32: 101\n" + "deleted: optional_int64: 102\n", + Run()); +} + +TEST_F(ComparisonTest, AddDelete_FieldOrderingTest) { + orderings_proto1_.ClearExtension(unittest::my_extension_string); + orderings_proto2_.clear_my_int(); + + EXPECT_EQ("deleted: my_int: 1\n" + "added: (protobuf_unittest.my_extension_string): \"bar\"\n", + RunOrder()); +} + +TEST_F(ComparisonTest, AllThreeTest) { + proto1_.clear_optional_int32(); + proto2_.clear_optional_float(); + proto2_.set_optional_string("hello world!"); + + EXPECT_EQ("added: optional_int32: 101\n" + "deleted: optional_float: 111\n" + "modified: optional_string: \"115\" -> \"hello world!\"\n", + Run()); +} + +TEST_F(ComparisonTest, SandwhichTest) { + proto1_.clear_optional_int64(); + proto1_.clear_optional_uint32(); + + proto2_.clear_optional_uint64(); + + EXPECT_EQ("added: optional_int64: 102\n" + "added: optional_uint32: 103\n" + "deleted: optional_uint64: 104\n", + Run()); +} + +TEST_F(ComparisonTest, IgnoredNoChangeTest) { + proto1diff_.set_v(3); + proto2diff_.set_v(3); + proto2diff_.set_w("foo"); + + ignore_field("v"); + + EXPECT_EQ("ignored: v\n" + "added: w: \"foo\"\n", + RunDiff()); +} + +TEST_F(ComparisonTest, IgnoredAddTest) { + proto2diff_.set_v(3); + proto2diff_.set_w("foo"); + + ignore_field("v"); + + EXPECT_EQ("ignored: v\n" + "added: w: \"foo\"\n", + RunDiff()); +} + +TEST_F(ComparisonTest, IgnoredDeleteTest) { + proto1diff_.set_v(3); + proto2diff_.set_w("foo"); + + ignore_field("v"); + + EXPECT_EQ("ignored: v\n" + "added: w: \"foo\"\n", + RunDiff()); +} + +TEST_F(ComparisonTest, IgnoredModifyTest) { + proto1diff_.set_v(3); + proto2diff_.set_v(4); + proto2diff_.set_w("foo"); + + ignore_field("v"); + + EXPECT_EQ("ignored: v\n" + "added: w: \"foo\"\n", + RunDiff()); +} + +TEST_F(ComparisonTest, IgnoredRepeatedAddTest) { + proto1diff_.add_rv(3); + proto1diff_.add_rv(4); + + proto2diff_.add_rv(3); + proto2diff_.add_rv(4); + proto2diff_.add_rv(5); + + proto2diff_.set_w("foo"); + + ignore_field("rv"); + + EXPECT_EQ("ignored: rv\n" + "added: w: \"foo\"\n", + RunDiff()); +} + +TEST_F(ComparisonTest, IgnoredRepeatedDeleteTest) { + proto1diff_.add_rv(3); + proto1diff_.add_rv(4); + proto1diff_.add_rv(5); + + proto2diff_.add_rv(3); + proto2diff_.add_rv(4); + + proto2diff_.set_w("foo"); + + ignore_field("rv"); + + EXPECT_EQ("ignored: rv\n" + "added: w: \"foo\"\n", + RunDiff()); +} + +TEST_F(ComparisonTest, IgnoredRepeatedModifyTest) { + proto1diff_.add_rv(3); + proto1diff_.add_rv(4); + + proto2diff_.add_rv(3); + proto2diff_.add_rv(5); + + proto2diff_.set_w("foo"); + + ignore_field("rv"); + + EXPECT_EQ("ignored: rv\n" + "added: w: \"foo\"\n", + RunDiff()); +} + +TEST_F(ComparisonTest, IgnoredWholeNestedMessage) { + proto1diff_.mutable_m()->set_c(3); + proto2diff_.mutable_m()->set_c(4); + + proto2diff_.set_w("foo"); + + ignore_field("m"); + + EXPECT_EQ("added: w: \"foo\"\n" + "ignored: m\n", + RunDiff()); +} + +TEST_F(ComparisonTest, IgnoredNestedField) { + proto1diff_.mutable_m()->set_c(3); + proto2diff_.mutable_m()->set_c(4); + + proto2diff_.set_w("foo"); + + ignore_field("m.c"); + + EXPECT_EQ("added: w: \"foo\"\n" + "ignored: m.c\n", + RunDiff()); +} + +TEST_F(ComparisonTest, IgnoredRepeatedNested) { + proto1diff_.add_rm()->set_c(0); + proto1diff_.add_rm()->set_c(1); + proto2diff_.add_rm()->set_c(2); + proto2diff_.add_rm()->set_c(3); + + proto2diff_.set_w("foo"); + + ignore_field("rm.c"); + + EXPECT_EQ("ignored: rm[0].c\n" + "ignored: rm[1].c\n" + "added: w: \"foo\"\n", + RunDiff()); +} + +TEST_F(ComparisonTest, IgnoredNestedRepeated) { + proto1diff_.mutable_m()->add_rc(23); + proto1diff_.mutable_m()->add_rc(24); + proto2diff_.mutable_m()->add_rc(25); + + proto2diff_.set_w("foo"); + + ignore_field("m.rc"); + + EXPECT_EQ("added: w: \"foo\"\n" + "ignored: m.rc\n", + RunDiff()); +} + +TEST_F(ComparisonTest, ExtensionTest) { + proto1ex_.SetExtension(unittest::optional_int32_extension, 401); + proto2ex_.SetExtension(unittest::optional_int32_extension, 402); + + proto1ex_.ClearExtension(unittest::optional_int64_extension); + proto2ex_.SetExtension(unittest::optional_int64_extension, 403); + + EXPECT_EQ( + "modified: (protobuf_unittest.optional_int32_extension): 401 -> 402\n" + "added: (protobuf_unittest.optional_int64_extension): 403\n", + RunEx()); +} + +TEST_F(ComparisonTest, MatchedUnknownFieldTagTest) { + unknown1_->AddVarint(240, 122); + unknown2_->AddVarint(240, 121); + unknown1_->AddFixed32(241, 1); + unknown2_->AddFixed64(241, 2); + unknown1_->AddLengthDelimited(242, "cat"); + unknown2_->AddLengthDelimited(242, "dog"); + + EXPECT_EQ( + "modified: 240[0]: 122 -> 121\n" + "deleted: 241[0]: 0x00000001\n" + "added: 241[0]: 0x0000000000000002\n" + "modified: 242[0]: \"cat\" -> \"dog\"\n", + RunUn()); +} + +TEST_F(ComparisonTest, UnmatchedUnknownFieldTagTest) { + unknown1_->AddFixed32(243, 1); + unknown2_->AddVarint(244, 2); + unknown2_->AddVarint(244, 4); + + EXPECT_EQ( + "deleted: 243[0]: 0x00000001\n" + "added: 244[0]: 2\n" + "added: 244[1]: 4\n", + RunUn()); +} + +TEST_F(ComparisonTest, DifferentSizedUnknownFieldTest) { + unknown1_->AddVarint(240, 1); + unknown1_->AddVarint(240, 3); + unknown1_->AddVarint(240, 4); + unknown2_->AddVarint(240, 2); + unknown2_->AddVarint(240, 3); + unknown2_->AddVarint(240, 2); + unknown2_->AddVarint(240, 5); + + EXPECT_EQ( + "modified: 240[0]: 1 -> 2\n" + "modified: 240[2]: 4 -> 2\n" + "added: 240[3]: 5\n", + RunUn()); +} + +TEST_F(ComparisonTest, UnknownFieldsAll) { + unknown1_->AddVarint(243, 122); + unknown1_->AddFixed64(244, 0x0172356); + unknown1_->AddFixed64(244, 0x098); + unknown1_->AddGroup(245)->AddFixed32(248, 1); + unknown1_->mutable_field(3)->mutable_group()->AddFixed32(248, 2); + unknown1_->AddGroup(249)->AddFixed64(250, 1); + + unknown2_->AddVarint(243, 121); + unknown2_->AddLengthDelimited(73882, "test 123"); + unknown2_->AddGroup(245)->AddFixed32(248, 3); + unknown2_->AddGroup(247); + + EXPECT_EQ( + "modified: 243[0]: 122 -> 121\n" + "deleted: 244[0]: 0x0000000000172356\n" + "deleted: 244[1]: 0x0000000000000098\n" + "modified: 245[0].248[0]: 0x00000001 -> 0x00000003\n" + "deleted: 245[0].248[1]: 0x00000002\n" + "added: 247[0]: { ... }\n" + "deleted: 249[0]: { ... }\n" + "added: 73882[0]: \"test 123\"\n", + RunUn()); +} + +TEST_F(ComparisonTest, EquivalentIgnoresUnknown) { + unittest::ForeignMessage message1, message2; + + message1.set_c(5); + message1.mutable_unknown_fields()->AddVarint(123, 456); + message2.set_c(5); + message2.mutable_unknown_fields()->AddVarint(321, 654); + + EXPECT_FALSE(util::MessageDifferencer::Equals(message1, message2)); + EXPECT_TRUE(util::MessageDifferencer::Equivalent(message1, message2)); +} + +class MatchingTest : public testing::Test { + public: + typedef util::MessageDifferencer MessageDifferencer; + + protected: + MatchingTest() { + } + + ~MatchingTest() { + } + + string RunWithResult(MessageDifferencer* differencer, + const Message& msg1, const Message& msg2, + bool result) { + string output; + io::StringOutputStream output_stream(&output); + MessageDifferencer::StreamReporter reporter(&output_stream); + reporter.set_report_modified_aggregates(true); + differencer->set_report_matches(true); + differencer->ReportDifferencesTo(&reporter); + if (result) { + EXPECT_TRUE(differencer->Compare(msg1, msg2)); + } else { + EXPECT_FALSE(differencer->Compare(msg1, msg2)); + } + return output; + } + + private: + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(MatchingTest); +}; + +TEST_F(MatchingTest, StreamReporterMatching) { + protobuf_unittest::TestField msg1, msg2; + msg1.set_c(72); + msg2.set_c(72); + msg1.add_rc(13); + msg2.add_rc(13); + msg1.add_rc(17); + msg2.add_rc(17); + string output; + MessageDifferencer differencer; + differencer.set_report_matches(true); + differencer.ReportDifferencesToString(&output); + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + EXPECT_EQ( + "matched: c : 72\n" + "matched: rc[0] : 13\n" + "matched: rc[1] : 17\n", + output); +} + +TEST_F(MatchingTest, DontReportMatchedWhenIgnoring) { + protobuf_unittest::TestField msg1, msg2; + msg1.set_c(72); + msg2.set_c(72); + msg1.add_rc(13); + msg2.add_rc(13); + msg1.add_rc(17); + msg2.add_rc(17); + string output; + MessageDifferencer differencer; + differencer.set_report_matches(true); + differencer.ReportDifferencesToString(&output); + + differencer.IgnoreField(msg1.GetDescriptor()->FindFieldByName("c")); + + EXPECT_TRUE(differencer.Compare(msg1, msg2)); + EXPECT_EQ( + "ignored: c\n" + "matched: rc[0] : 13\n" + "matched: rc[1] : 17\n", + output); +} + +TEST_F(MatchingTest, ReportMatchedForMovedFields) { + protobuf_unittest::TestDiffMessage msg1, msg2; + protobuf_unittest::TestDiffMessage::Item* item = msg1.add_item(); + item->set_a(53); + item->set_b("hello"); + item = msg2.add_item(); + item->set_a(27); + item = msg2.add_item(); + item->set_a(53); + item->set_b("hello"); + item = msg1.add_item(); + item->set_a(27); + MessageDifferencer differencer; + const FieldDescriptor* desc; + desc = msg1.GetDescriptor()->FindFieldByName("item"); + differencer.TreatAsSet(desc); + + EXPECT_EQ( + "matched: item[0].a -> item[1].a : 53\n" + "matched: item[0].b -> item[1].b : \"hello\"\n" + "moved: item[0] -> item[1] : { a: 53 b: \"hello\" }\n" + "matched: item[1].a -> item[0].a : 27\n" + "moved: item[1] -> item[0] : { a: 27 }\n", + RunWithResult(&differencer, msg1, msg2, true)); +} + +TEST_F(MatchingTest, MatchesAppearInPostTraversalOrderForMovedFields) { + protobuf_unittest::TestDiffMessage msg1, msg2; + protobuf_unittest::TestDiffMessage::Item* item; + protobuf_unittest::TestField* field; + + const FieldDescriptor* desc; + const FieldDescriptor* nested_desc; + const FieldDescriptor* double_nested_desc; + desc = msg1.GetDescriptor()->FindFieldByName("item"); + nested_desc = desc->message_type()->FindFieldByName("rm"); + double_nested_desc = nested_desc->message_type()->FindFieldByName("rc"); + MessageDifferencer differencer; + differencer.TreatAsSet(desc); + differencer.TreatAsSet(nested_desc); + differencer.TreatAsSet(double_nested_desc); + + item = msg1.add_item(); + field = item->add_rm(); + field->set_c(1); + field->add_rc(2); + field->add_rc(3); + field = item->add_rm(); + field->set_c(4); + field->add_rc(5); + field->add_rc(6); + field->add_rc(7); + item = msg2.add_item(); + field = item->add_rm(); + field->set_c(4); + field->add_rc(7); + field->add_rc(6); + field->add_rc(5); + field = item->add_rm(); + field->set_c(1); + field->add_rc(3); + field->add_rc(2); + item = msg1.add_item(); + field = item->add_rm(); + field->set_c(8); + field->add_rc(10); + field->add_rc(11); + field->add_rc(9); + item = msg2.add_item(); + field = item->add_rm(); + field->set_c(8); + field->add_rc(9); + field->add_rc(10); + field->add_rc(11); + + EXPECT_EQ( + "matched: item[0].rm[0].c -> item[0].rm[1].c : 1\n" + "moved: item[0].rm[0].rc[0] -> item[0].rm[1].rc[1] : 2\n" + "moved: item[0].rm[0].rc[1] -> item[0].rm[1].rc[0] : 3\n" + "moved: item[0].rm[0] -> item[0].rm[1] : { c: 1 rc: 2 rc: 3 }\n" + "matched: item[0].rm[1].c -> item[0].rm[0].c : 4\n" + "moved: item[0].rm[1].rc[0] -> item[0].rm[0].rc[2] : 5\n" + "matched: item[0].rm[1].rc[1] -> item[0].rm[0].rc[1] : 6\n" + "moved: item[0].rm[1].rc[2] -> item[0].rm[0].rc[0] : 7\n" + "moved: item[0].rm[1] -> item[0].rm[0] : { c: 4 rc: 5 rc: 6 rc: 7 }\n" + "matched: item[0] : { rm { c: 1 rc: 2 rc: 3 }" + " rm { c: 4 rc: 5 rc: 6 rc: 7 } }\n" + "matched: item[1].rm[0].c : 8\n" + "moved: item[1].rm[0].rc[0] -> item[1].rm[0].rc[1] : 10\n" + "moved: item[1].rm[0].rc[1] -> item[1].rm[0].rc[2] : 11\n" + "moved: item[1].rm[0].rc[2] -> item[1].rm[0].rc[0] : 9\n" + "matched: item[1].rm[0] : { c: 8 rc: 10 rc: 11 rc: 9 }\n" + "matched: item[1] : { rm { c: 8 rc: 10 rc: 11 rc: 9 } }\n", + RunWithResult(&differencer, msg1, msg2, true)); +} + +TEST_F(MatchingTest, MatchAndModifiedInterleaveProperly) { + protobuf_unittest::TestDiffMessage msg1, msg2; + protobuf_unittest::TestDiffMessage::Item* item; + protobuf_unittest::TestField* field; + + const FieldDescriptor* desc; + const FieldDescriptor* nested_key; + const FieldDescriptor* nested_desc; + const FieldDescriptor* double_nested_key; + const FieldDescriptor* double_nested_desc; + desc = msg1.GetDescriptor()->FindFieldByName("item"); + nested_key = desc->message_type()->FindFieldByName("a"); + nested_desc = desc->message_type()->FindFieldByName("rm"); + double_nested_key = nested_desc->message_type()->FindFieldByName("c"); + double_nested_desc = nested_desc->message_type()->FindFieldByName("rc"); + + MessageDifferencer differencer; + differencer.TreatAsMap(desc, nested_key); + differencer.TreatAsMap(nested_desc, double_nested_key); + differencer.TreatAsSet(double_nested_desc); + + item = msg1.add_item(); + item->set_a(1); + field = item->add_rm(); + field->set_c(2); + field->add_rc(3); + field->add_rc(4); + field = item->add_rm(); + field->set_c(5); + field->add_rc(6); + field->add_rc(7); + field->add_rc(8); + item = msg1.add_item(); + item->set_a(9); + field = item->add_rm(); + field->set_c(10); + field->add_rc(11); + field->add_rc(12); + field = item->add_rm(); + field->set_c(13); + + item = msg2.add_item(); + item->set_a(1); + field = item->add_rm(); + field->set_c(5); + field->add_rc(8); + field->add_rc(8); + field->add_rc(6); + field = item->add_rm(); + field->set_c(3); + field->add_rc(2); + field->add_rc(4); + item = msg2.add_item(); + item->set_a(9); + field = item->add_rm(); + field->set_c(10); + field->add_rc(12); + field->add_rc(11); + field = item->add_rm(); + field->set_c(13); + + EXPECT_EQ( + "matched: item[0].a : 1\n" + "matched: item[0].rm[1].c -> item[0].rm[0].c : 5\n" + "moved: item[0].rm[1].rc[0] -> item[0].rm[0].rc[2] : 6\n" + "moved: item[0].rm[1].rc[2] -> item[0].rm[0].rc[0] : 8\n" + "added: item[0].rm[0].rc[1]: 8\n" + "deleted: item[0].rm[1].rc[1]: 7\n" + "modified: item[0].rm[1] -> item[0].rm[0]: { c: 5 rc: 6 rc: 7 rc: 8 } ->" + " { c: 5 rc: 8 rc: 8 rc: 6 }\n" + "added: item[0].rm[1]: { c: 3 rc: 2 rc: 4 }\n" + "deleted: item[0].rm[0]: { c: 2 rc: 3 rc: 4 }\n" + "modified: item[0]: { a: 1 rm { c: 2 rc: 3 rc: 4 }" + " rm { c: 5 rc: 6 rc: 7 rc: 8 } } ->" + " { a: 1 rm { c: 5 rc: 8 rc: 8 rc: 6 }" + " rm { c: 3 rc: 2 rc: 4 } }\n" + "matched: item[1].a : 9\n" + "matched: item[1].rm[0].c : 10\n" + "moved: item[1].rm[0].rc[0] -> item[1].rm[0].rc[1] : 11\n" + "moved: item[1].rm[0].rc[1] -> item[1].rm[0].rc[0] : 12\n" + "matched: item[1].rm[0] : { c: 10 rc: 11 rc: 12 }\n" + "matched: item[1].rm[1].c : 13\n" + "matched: item[1].rm[1] : { c: 13 }\n" + "matched: item[1] : { a: 9 rm { c: 10 rc: 11 rc: 12 } rm { c: 13 } }\n", + RunWithResult(&differencer, msg1, msg2, false)); +} + +TEST_F(MatchingTest, MatchingWorksWithExtensions) { + protobuf_unittest::TestAllExtensions msg1, msg2; + protobuf_unittest::TestAllTypes::NestedMessage* nested; + using protobuf_unittest::repeated_nested_message_extension; + + const FileDescriptor* descriptor; + const FieldDescriptor* desc; + const FieldDescriptor* nested_key; + descriptor = msg1.GetDescriptor()->file(); + desc = descriptor->FindExtensionByName("repeated_nested_message_extension"); + ASSERT_FALSE(desc == NULL); + nested_key = desc->message_type()->FindFieldByName("bb"); + + MessageDifferencer differencer; + differencer.TreatAsMap(desc, nested_key); + + nested = msg1.AddExtension(repeated_nested_message_extension); + nested->set_bb(7); + nested = msg1.AddExtension(repeated_nested_message_extension); + nested->set_bb(13); + nested = msg1.AddExtension(repeated_nested_message_extension); + nested->set_bb(11); + nested = msg2.AddExtension(repeated_nested_message_extension); + nested->set_bb(11); + nested = msg2.AddExtension(repeated_nested_message_extension); + nested->set_bb(13); + nested = msg2.AddExtension(repeated_nested_message_extension); + nested->set_bb(7); + + EXPECT_EQ( + "matched: (protobuf_unittest.repeated_nested_message_extension)[0].bb ->" + " (protobuf_unittest.repeated_nested_message_extension)[2].bb : 7\n" + "moved: (protobuf_unittest.repeated_nested_message_extension)[0] ->" + " (protobuf_unittest.repeated_nested_message_extension)[2] :" + " { bb: 7 }\n" + "matched: (protobuf_unittest.repeated_nested_message_extension)[1].bb :" + " 13\n" + "matched: (protobuf_unittest.repeated_nested_message_extension)[1] :" + " { bb: 13 }\n" + "matched: (protobuf_unittest.repeated_nested_message_extension)[2].bb ->" + " (protobuf_unittest.repeated_nested_message_extension)[0].bb :" + " 11\n" + "moved: (protobuf_unittest.repeated_nested_message_extension)[2] ->" + " (protobuf_unittest.repeated_nested_message_extension)[0] :" + " { bb: 11 }\n", + RunWithResult(&differencer, msg1, msg2, true)); +} + +TEST(AnyTest, Simple) { + protobuf_unittest::TestField value1, value2; + value1.set_a(20); + value2.set_a(21); + + protobuf_unittest::TestAny m1, m2; + m1.mutable_any_value()->PackFrom(value1); + m2.mutable_any_value()->PackFrom(value2); + util::MessageDifferencer message_differencer; + string difference_string; + message_differencer.ReportDifferencesToString(&difference_string); + EXPECT_FALSE(message_differencer.Compare(m1, m2)); + EXPECT_EQ("modified: any_value.a: 20 -> 21\n", difference_string); +} + +TEST(Anytest, TreatAsSet) { + protobuf_unittest::TestField value1, value2; + value1.set_a(20); + value1.set_b(30); + value2.set_a(20); + value2.set_b(31); + + protobuf_unittest::TestAny m1, m2; + m1.add_repeated_any_value()->PackFrom(value1); + m1.add_repeated_any_value()->PackFrom(value2); + m2.add_repeated_any_value()->PackFrom(value2); + m2.add_repeated_any_value()->PackFrom(value1); + + util::MessageDifferencer message_differencer; + message_differencer.TreatAsSet(GetFieldDescriptor(m1, "repeated_any_value")); + EXPECT_TRUE(message_differencer.Compare(m1, m2)); +} + + +} // namespace +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/util/message_differencer_unittest.proto b/src/google/protobuf/util/message_differencer_unittest.proto new file mode 100644 index 00000000..698775f1 --- /dev/null +++ b/src/google/protobuf/util/message_differencer_unittest.proto @@ -0,0 +1,74 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: kenton@google.com (Kenton Varda) +// Based on original Protocol Buffers design by +// Sanjay Ghemawat, Jeff Dean, and others. +// +// This file contains messages for testing repeated field comparison + +syntax = "proto2"; +package protobuf_unittest; + +option optimize_for = SPEED; + +message TestField { + optional int32 a = 3; + optional int32 b = 4; + optional int32 c = 1; + repeated int32 rc = 2; + optional TestField m = 5; + + extend TestDiffMessage { + optional TestField tf = 100; + } +} + +message TestDiffMessage { + repeated group Item = 1 { + optional int32 a = 2; // Test basic repeated field comparison. + optional string b = 4; // Test basic repeated field comparison. + repeated int32 ra = 3; // Test SetOfSet Comparison. + repeated string rb = 5; // Test TreatAsMap when key is repeated + optional TestField m = 6; // Test TreatAsMap when key is a message + repeated TestField rm = 7; // Test TreatAsMap when key is a repeated + // message + } + + optional int32 v = 13 [deprecated = true]; + optional string w = 14; + optional TestField m = 15; + repeated int32 rv = 11; // Test for combinations + repeated string rw = 10; // Test for combinations + repeated TestField rm = 12 [deprecated = true]; // Test for combinations + + extensions 100 to 199; +} + diff --git a/src/google/protobuf/util/type_resolver.h b/src/google/protobuf/util/type_resolver.h new file mode 100644 index 00000000..77d4416a --- /dev/null +++ b/src/google/protobuf/util/type_resolver.h @@ -0,0 +1,75 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef GOOGLE_PROTOBUF_UTIL_TYPE_RESOLVER_H__ +#define GOOGLE_PROTOBUF_UTIL_TYPE_RESOLVER_H__ + +#include + +#include +#include + + +namespace google { +namespace protobuf { +class Type; +class Enum; +} // namespace protobuf + + +namespace protobuf { +class DescriptorPool; +namespace util { + +// Abstract interface for a type resovler. +// +// Implementations of this interface must be thread-safe. +class LIBPROTOBUF_EXPORT TypeResolver { + public: + TypeResolver() {} + virtual ~TypeResolver() {} + + // Resolves a type url for a message type. + virtual util::Status ResolveMessageType( + const string& type_url, google::protobuf::Type* message_type) = 0; + + // Resolves a type url for an enum type. + virtual util::Status ResolveEnumType(const string& type_url, + google::protobuf::Enum* enum_type) = 0; + + private: + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(TypeResolver); +}; + +} // namespace util +} // namespace protobuf + +} // namespace google +#endif // GOOGLE_PROTOBUF_UTIL_TYPE_RESOLVER_H__ diff --git a/src/google/protobuf/util/type_resolver_util.cc b/src/google/protobuf/util/type_resolver_util.cc new file mode 100644 index 00000000..053a4ed7 --- /dev/null +++ b/src/google/protobuf/util/type_resolver_util.cc @@ -0,0 +1,212 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include +#include +#include +#include +#include +#include + +namespace google { +namespace protobuf { +namespace util { +namespace { +using google::protobuf::BoolValue; +using google::protobuf::Enum; +using google::protobuf::EnumValue; +using google::protobuf::Field; +using google::protobuf::Option; +using google::protobuf::Type; + +using util::Status; +using util::error::INVALID_ARGUMENT; +using util::error::NOT_FOUND; + +bool SplitTypeUrl(const string& type_url, + string* url_prefix, + string* message_name) { + size_t pos = type_url.find_last_of("/"); + if (pos == string::npos) { + return false; + } + *url_prefix = type_url.substr(0, pos); + *message_name = type_url.substr(pos + 1); + return true; +} + + +class DescriptorPoolTypeResolver : public TypeResolver { + public: + DescriptorPoolTypeResolver(const string& url_prefix, + const DescriptorPool* pool) + : url_prefix_(url_prefix), pool_(pool) { + } + + Status ResolveMessageType(const string& type_url, Type* type) { + string url_prefix, message_name; + if (!SplitTypeUrl(type_url, &url_prefix, &message_name) || + url_prefix != url_prefix_) { + return Status(INVALID_ARGUMENT, "Failed to parse type url: " + type_url); + } + if (url_prefix != url_prefix_) { + return Status(INVALID_ARGUMENT, + "Cannot resolve types from URL: " + url_prefix); + } + const Descriptor* descriptor = pool_->FindMessageTypeByName(message_name); + if (descriptor == NULL) { + return Status(NOT_FOUND, "Cannot found the type: " + message_name); + } + ConvertDescriptor(descriptor, type); + return Status(); + } + + Status ResolveEnumType(const string& type_url, Enum* enum_type) { + string url_prefix, type_name; + if (!SplitTypeUrl(type_url, &url_prefix, &type_name) || + url_prefix != url_prefix_) { + return Status(INVALID_ARGUMENT, "Failed to parse type url: " + type_url); + } + if (url_prefix != url_prefix_) { + return Status(INVALID_ARGUMENT, + "Cannot resolve types from URL: " + url_prefix); + } + const EnumDescriptor* descriptor = pool_->FindEnumTypeByName(type_name); + if (descriptor == NULL) { + return Status(NOT_FOUND, "Cannot found the type: " + type_name); + } + ConvertEnumDescriptor(descriptor, enum_type); + return Status(); + } + + private: + void ConvertDescriptor(const Descriptor* descriptor, Type* type) { + type->Clear(); + type->set_name(descriptor->full_name()); + for (int i = 0; i < descriptor->field_count(); ++i) { + const FieldDescriptor* field = descriptor->field(i); + if (field->type() == FieldDescriptor::TYPE_GROUP) { + // Group fields cannot be represented with Type. We discard them. + continue; + } + ConvertFieldDescriptor(descriptor->field(i), type->add_fields()); + } + for (int i = 0; i < descriptor->oneof_decl_count(); ++i) { + type->add_oneofs(descriptor->oneof_decl(i)->name()); + } + type->mutable_source_context()->set_file_name(descriptor->file()->name()); + ConvertMessageOptions(descriptor->options(), type->mutable_options()); + } + + void ConvertMessageOptions(const MessageOptions& options, + RepeatedPtrField