Ver código fonte

Merge "Add an option to inspect "has" state upon parse."

Ulas Kirazci 12 anos atrás
pai
commit
064b60c659

+ 16 - 0
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<fieldname>
+  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:
 

+ 88 - 0
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

+ 24 - 2
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");

+ 2 - 0
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;
     }

+ 9 - 0
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.

+ 10 - 1
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

+ 36 - 28
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"

+ 2 - 0
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<string, string> variables_;
 

+ 78 - 0
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;
+}