From e83bbbbe4213e9b7bcc0529c305b821da8d7a136 Mon Sep 17 00:00:00 2001 From: Ulas Kirazci Date: Mon, 1 Apr 2013 11:29:43 -0700 Subject: Add an option to inspect "has" state upon parse. If has is set, also always serialize. Change-Id: I2c8450f7ab9e837d722123dd1042991c0258ede3 --- java/README.txt | 16 ++++ .../test/java/com/google/protobuf/NanoTest.java | 88 ++++++++++++++++++++++ .../compiler/javanano/javanano_enum_field.cc | 26 ++++++- .../compiler/javanano/javanano_generator.cc | 2 + .../protobuf/compiler/javanano/javanano_message.cc | 9 +++ .../protobuf/compiler/javanano/javanano_params.h | 11 ++- .../compiler/javanano/javanano_primitive_field.cc | 64 +++++++++------- .../compiler/javanano/javanano_primitive_field.h | 2 + src/google/protobuf/unittest_has_nano.proto | 78 +++++++++++++++++++ 9 files changed, 265 insertions(+), 31 deletions(-) create mode 100644 src/google/protobuf/unittest_has_nano.proto diff --git a/java/README.txt b/java/README.txt index dac2e3c9..58ccb88e 100644 --- a/java/README.txt +++ b/java/README.txt @@ -301,6 +301,22 @@ message's constructor or clear() function is called, the default value penalty. This is not a problem if the field has no default or is an empty default. +Nano Generator options + +java_nano_generate_has: + If true, generates a public boolean variable has + accompanying the optional or required field (not present for + repeated fields, groups or messages). It is set to false initially + and upon clear(). If parseFrom(...) reads the field from the wire, + it is set to true. This is a way for clients to inspect the "has" + value upon parse. If it is set to true, writeTo(...) will ALWAYS + output that field (even if field value is equal to its + default). + + IMPORTANT: This option costs an extra 4 bytes per primitive field in + the message. Think carefully about whether you really need this. In + many cases reading the default works and determining whether the + field was received over the wire is irrelevant. To use nano protobufs: diff --git a/java/src/test/java/com/google/protobuf/NanoTest.java b/java/src/test/java/com/google/protobuf/NanoTest.java index 0ea80d40..80f091bf 100644 --- a/java/src/test/java/com/google/protobuf/NanoTest.java +++ b/java/src/test/java/com/google/protobuf/NanoTest.java @@ -37,6 +37,7 @@ import com.google.protobuf.nano.InternalNano; import com.google.protobuf.nano.MessageNano; import com.google.protobuf.nano.MultipleImportingNonMultipleNano1; import com.google.protobuf.nano.MultipleImportingNonMultipleNano2; +import com.google.protobuf.nano.NanoHasOuterClass.TestAllTypesNanoHas; import com.google.protobuf.nano.NanoOuterClass; import com.google.protobuf.nano.NanoOuterClass.TestAllTypesNano; import com.google.protobuf.nano.RecursiveMessageNano; @@ -2095,6 +2096,93 @@ public class NanoTest extends TestCase { } } + public void testNanoWithHasParseFrom() throws Exception { + TestAllTypesNanoHas msg = null; + // Test false on creation, after clear and upon empty parse. + for (int i = 0; i < 3; i++) { + if (i == 0) { + msg = new TestAllTypesNanoHas(); + } else if (i == 1) { + msg.clear(); + } else if (i == 2) { + msg = TestAllTypesNanoHas.parseFrom(new byte[0]); + } + assertFalse(msg.hasOptionalInt32); + assertFalse(msg.hasOptionalString); + assertFalse(msg.hasOptionalBytes); + assertFalse(msg.hasOptionalNestedEnum); + assertFalse(msg.hasDefaultInt32); + assertFalse(msg.hasDefaultString); + assertFalse(msg.hasDefaultBytes); + assertFalse(msg.hasDefaultFloatNan); + assertFalse(msg.hasDefaultNestedEnum); + assertFalse(msg.hasId); + msg.optionalInt32 = 123; + msg.optionalNestedMessage = new TestAllTypesNanoHas.NestedMessage(); + msg.optionalNestedMessage.bb = 2; + msg.optionalNestedEnum = TestAllTypesNano.BAZ; + } + + byte [] result = MessageNano.toByteArray(msg); + int msgSerializedSize = msg.getSerializedSize(); + //System.out.printf("mss=%d result.length=%d\n", msgSerializedSize, result.length); + assertTrue(msgSerializedSize == 13); + assertEquals(result.length, msgSerializedSize); + + // Has fields true upon parse. + TestAllTypesNanoHas newMsg = TestAllTypesNanoHas.parseFrom(result); + assertEquals(123, newMsg.optionalInt32); + assertTrue(newMsg.hasOptionalInt32); + assertEquals(2, newMsg.optionalNestedMessage.bb); + assertTrue(newMsg.optionalNestedMessage.hasBb); + assertEquals(TestAllTypesNanoHas.BAZ, newMsg.optionalNestedEnum); + assertTrue(newMsg.hasOptionalNestedEnum); + } + + public void testNanoWithHasSerialize() throws Exception { + TestAllTypesNanoHas msg = new TestAllTypesNanoHas(); + msg.hasOptionalInt32 = true; + msg.hasOptionalString = true; + msg.hasOptionalBytes = true; + msg.optionalNestedMessage = new TestAllTypesNanoHas.NestedMessage(); + msg.optionalNestedMessage.hasBb = true; + msg.hasOptionalNestedEnum = true; + msg.hasDefaultInt32 = true; + msg.hasDefaultString = true; + msg.hasDefaultBytes = true; + msg.hasDefaultFloatNan = true; + msg.hasDefaultNestedEnum = true; + + byte [] result = MessageNano.toByteArray(msg); + int msgSerializedSize = msg.getSerializedSize(); + assertEquals(result.length, msgSerializedSize); + + // Now deserialize and find that all fields are set and equal to their defaults. + TestAllTypesNanoHas newMsg = TestAllTypesNanoHas.parseFrom(result); + assertTrue(newMsg.hasOptionalInt32); + assertTrue(newMsg.hasOptionalString); + assertTrue(newMsg.hasOptionalBytes); + assertTrue(newMsg.optionalNestedMessage.hasBb); + assertTrue(newMsg.hasOptionalNestedEnum); + assertTrue(newMsg.hasDefaultInt32); + assertTrue(newMsg.hasDefaultString); + assertTrue(newMsg.hasDefaultBytes); + assertTrue(newMsg.hasDefaultFloatNan); + assertTrue(newMsg.hasDefaultNestedEnum); + assertTrue(newMsg.hasId); + assertEquals(0, newMsg.optionalInt32); + assertEquals(0, newMsg.optionalString.length()); + assertEquals(0, newMsg.optionalBytes.length); + assertEquals(0, newMsg.optionalNestedMessage.bb); + assertEquals(TestAllTypesNanoHas.FOO, newMsg.optionalNestedEnum); + assertEquals(41, newMsg.defaultInt32); + assertEquals("hello", newMsg.defaultString); + assertEquals("world", new String(newMsg.defaultBytes, "UTF-8")); + assertEquals(TestAllTypesNanoHas.BAR, newMsg.defaultNestedEnum); + assertEquals(Float.NaN, newMsg.defaultFloatNan); + assertEquals(0, newMsg.id); + } + /** * Tests that fields with a default value of NaN are not serialized when * set to NaN. This is a special case as NaN != NaN, so normal equality diff --git a/src/google/protobuf/compiler/javanano/javanano_enum_field.cc b/src/google/protobuf/compiler/javanano/javanano_enum_field.cc index 215f3419..9c4acb92 100644 --- a/src/google/protobuf/compiler/javanano/javanano_enum_field.cc +++ b/src/google/protobuf/compiler/javanano/javanano_enum_field.cc @@ -82,12 +82,22 @@ void EnumFieldGenerator:: GenerateMembers(io::Printer* printer) const { printer->Print(variables_, "public int $name$ = $default$;\n"); + + if (params_.generate_has()) { + printer->Print(variables_, + "public boolean has$capitalized_name$ = false;\n"); + } } void EnumFieldGenerator:: GenerateParsingCode(io::Printer* printer) const { printer->Print(variables_, " this.$name$ = input.readInt32();\n"); + + if (params_.generate_has()) { + printer->Print(variables_, + " has$capitalized_name$ = true;\n"); + } } void EnumFieldGenerator:: @@ -96,8 +106,14 @@ GenerateSerializationCode(io::Printer* printer) const { printer->Print(variables_, "output.writeInt32($number$, this.$name$);\n"); } else { + if (params_.generate_has()) { + printer->Print(variables_, + "if (this.$name$ != $default$ || has$capitalized_name$) {\n"); + } else { + printer->Print(variables_, + "if (this.$name$ != $default$) {\n"); + } printer->Print(variables_, - "if (this.$name$ != $default$) {\n" " output.writeInt32($number$, this.$name$);\n" "}\n"); } @@ -110,8 +126,14 @@ GenerateSerializedSizeCode(io::Printer* printer) const { "size += com.google.protobuf.nano.CodedOutputByteBufferNano\n" " .computeInt32Size($number$, this.$name$);\n"); } else { + if (params_.generate_has()) { + printer->Print(variables_, + "if (this.$name$ != $default$ || has$capitalized_name$) {\n"); + } else { + printer->Print(variables_, + "if (this.$name$ != $default$) {\n"); + } printer->Print(variables_, - "if (this.$name$ != $default$) {\n" " size += com.google.protobuf.nano.CodedOutputByteBufferNano\n" " .computeInt32Size($number$, this.$name$);\n" "}\n"); diff --git a/src/google/protobuf/compiler/javanano/javanano_generator.cc b/src/google/protobuf/compiler/javanano/javanano_generator.cc index 4b6b6a59..76e7263d 100644 --- a/src/google/protobuf/compiler/javanano/javanano_generator.cc +++ b/src/google/protobuf/compiler/javanano/javanano_generator.cc @@ -118,6 +118,8 @@ bool JavaNanoGenerator::Generate(const FileDescriptor* file, params.set_store_unknown_fields(options[i].second == "true"); } else if (options[i].first == "java_multiple_files") { params.set_override_java_multiple_files(options[i].second == "true"); + } else if (options[i].first == "java_nano_generate_has") { + params.set_generate_has(options[i].second == "true"); } else { *error = "Ignore unknown javanano generator option: " + options[i].first; } diff --git a/src/google/protobuf/compiler/javanano/javanano_message.cc b/src/google/protobuf/compiler/javanano/javanano_message.cc index b5983a2a..de569441 100644 --- a/src/google/protobuf/compiler/javanano/javanano_message.cc +++ b/src/google/protobuf/compiler/javanano/javanano_message.cc @@ -406,6 +406,15 @@ void MessageGenerator::GenerateClear(io::Printer* printer) { "name", RenameJavaKeywords(UnderscoresToCamelCase(field)), "default", DefaultValue(params_, field)); } + + if (params_.generate_has() && + field->label() != FieldDescriptor::LABEL_REPEATED && + field->type() != FieldDescriptor::TYPE_GROUP && + field->type() != FieldDescriptor::TYPE_MESSAGE) { + printer->Print( + "has$capitalized_name$ = false;\n", + "capitalized_name", UnderscoresToCapitalizedCamelCase(field)); + } } // Clear unknown fields. diff --git a/src/google/protobuf/compiler/javanano/javanano_params.h b/src/google/protobuf/compiler/javanano/javanano_params.h index 5c0e6f04..51686da5 100644 --- a/src/google/protobuf/compiler/javanano/javanano_params.h +++ b/src/google/protobuf/compiler/javanano/javanano_params.h @@ -57,13 +57,15 @@ class Params { NameMap java_packages_; NameMap java_outer_classnames_; NameSet java_multiple_files_; + bool generate_has_; public: Params(const string & base_name) : empty_(""), base_name_(base_name), override_java_multiple_files_(JAVANANO_MUL_UNSET), - store_unknown_fields_(false) { + store_unknown_fields_(false), + generate_has_(false) { } const string& base_name() const { @@ -151,6 +153,13 @@ class Params { return store_unknown_fields_; } + void set_generate_has(bool value) { + generate_has_ = value; + } + bool generate_has() const { + return generate_has_; + } + }; } // namespace javanano diff --git a/src/google/protobuf/compiler/javanano/javanano_primitive_field.cc b/src/google/protobuf/compiler/javanano/javanano_primitive_field.cc index 987a1037..20ea6b08 100644 --- a/src/google/protobuf/compiler/javanano/javanano_primitive_field.cc +++ b/src/google/protobuf/compiler/javanano/javanano_primitive_field.cc @@ -321,12 +321,46 @@ GenerateMembers(io::Printer* printer) const { printer->Print(variables_, "public $type$ $name$ = $default$;\n"); } + + if (params_.generate_has()) { + printer->Print(variables_, + "public boolean has$capitalized_name$ = false;\n"); + } } void PrimitiveFieldGenerator:: GenerateParsingCode(io::Printer* printer) const { printer->Print(variables_, "this.$name$ = input.read$capitalized_type$();\n"); + + if (params_.generate_has()) { + printer->Print(variables_, + "has$capitalized_name$ = true;\n"); + } +} + +void PrimitiveFieldGenerator:: +GenerateSerializationConditional(io::Printer* printer) const { + if (params_.generate_has()) { + printer->Print(variables_, + "if (has$capitalized_name$ || "); + } else { + printer->Print(variables_, + "if ("); + } + if (IsArrayType(GetJavaType(descriptor_))) { + printer->Print(variables_, + "!java.util.Arrays.equals(this.$name$, $default$)) {\n"); + } else if (IsReferenceType(GetJavaType(descriptor_))) { + printer->Print(variables_, + "!this.$name$.equals($default$)) {\n"); + } else if (IsDefaultNaN(descriptor_)) { + printer->Print(variables_, + "!$capitalized_type$.isNaN(this.$name$)) {\n"); + } else { + printer->Print(variables_, + "this.$name$ != $default$) {\n"); + } } void PrimitiveFieldGenerator:: @@ -335,20 +369,7 @@ GenerateSerializationCode(io::Printer* printer) const { printer->Print(variables_, "output.write$capitalized_type$($number$, this.$name$);\n"); } else { - if (IsArrayType(GetJavaType(descriptor_))) { - printer->Print(variables_, - "if (!java.util.Arrays.equals(this.$name$, $default$)) {\n"); - } else if (IsReferenceType(GetJavaType(descriptor_))) { - printer->Print(variables_, - "if (!this.$name$.equals($default$)) {\n"); - } else if (IsDefaultNaN(descriptor_)) { - printer->Print(variables_, - "if (!$capitalized_type$.isNaN(this.$name$)) {\n"); - } else { - printer->Print(variables_, - "if (this.$name$ != $default$) {\n"); - } - + GenerateSerializationConditional(printer); printer->Print(variables_, " output.write$capitalized_type$($number$, this.$name$);\n" "}\n"); @@ -362,20 +383,7 @@ GenerateSerializedSizeCode(io::Printer* printer) const { "size += com.google.protobuf.nano.CodedOutputByteBufferNano\n" " .compute$capitalized_type$Size($number$, this.$name$);\n"); } else { - if (IsArrayType(GetJavaType(descriptor_))) { - printer->Print(variables_, - "if (!java.util.Arrays.equals(this.$name$, $default$)) {\n"); - } else if (IsReferenceType(GetJavaType(descriptor_))) { - printer->Print(variables_, - "if (!this.$name$.equals($default$)) {\n"); - } else if (IsDefaultNaN(descriptor_)) { - printer->Print(variables_, - "if (!$capitalized_type$.isNaN(this.$name$)) {\n"); - } else { - printer->Print(variables_, - "if (this.$name$ != $default$) {\n"); - } - + GenerateSerializationConditional(printer); printer->Print(variables_, " size += com.google.protobuf.nano.CodedOutputByteBufferNano\n" " .compute$capitalized_type$Size($number$, this.$name$);\n" diff --git a/src/google/protobuf/compiler/javanano/javanano_primitive_field.h b/src/google/protobuf/compiler/javanano/javanano_primitive_field.h index 9b1bb465..aa6355b3 100644 --- a/src/google/protobuf/compiler/javanano/javanano_primitive_field.h +++ b/src/google/protobuf/compiler/javanano/javanano_primitive_field.h @@ -58,6 +58,8 @@ class PrimitiveFieldGenerator : public FieldGenerator { string GetBoxedType() const; private: + void GenerateSerializationConditional(io::Printer* printer) const; + const FieldDescriptor* descriptor_; map variables_; diff --git a/src/google/protobuf/unittest_has_nano.proto b/src/google/protobuf/unittest_has_nano.proto new file mode 100644 index 00000000..3a1e5b5d --- /dev/null +++ b/src/google/protobuf/unittest_has_nano.proto @@ -0,0 +1,78 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// http://code.google.com/p/protobuf/ +// +// 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: ulas@google.com (Ulas Kirazci) + +package protobuf_unittest; + +option java_package = "com.google.protobuf.nano"; +option java_outer_classname = "NanoHasOuterClass"; + +message TestAllTypesNanoHas { + + message NestedMessage { + optional int32 bb = 1; + } + + enum NestedEnum { + FOO = 1; + BAR = 2; + BAZ = 3; + } + + // Singular + optional int32 optional_int32 = 1; + optional string optional_string = 14; + optional bytes optional_bytes = 15; + + optional NestedMessage optional_nested_message = 18; + + optional NestedEnum optional_nested_enum = 21; + + // Repeated + repeated int32 repeated_int32 = 31; + repeated string repeated_string = 44; + repeated bytes repeated_bytes = 45; + + repeated NestedMessage repeated_nested_message = 48; + + repeated NestedEnum repeated_nested_enum = 51; + + // Singular with defaults + optional int32 default_int32 = 61 [default = 41 ]; + optional string default_string = 74 [default = "hello"]; + optional bytes default_bytes = 75 [default = "world"]; + + optional float default_float_nan = 99 [default = nan]; + + optional NestedEnum default_nested_enum = 81 [default = BAR]; + + required int32 id = 86; +} -- cgit v1.2.3