diff --git a/java/README.txt b/java/README.txt index e2e698e168..adc1972cc5 100644 --- a/java/README.txt +++ b/java/README.txt @@ -487,34 +487,67 @@ java_nano_generate_has={true,false} (default: false) many cases reading the default works and determining whether the field was received over the wire is irrelevant. -optional_field_style={default,accessors} (default: default) - Defines the style of the generated code for _optional_ fields only. +optional_field_style={default,accessors,reftypes} (default: default) + Defines the style of the generated code for fields. + + * default * + In the default style, optional fields translate into public mutable Java fields, and the serialization process is as discussed in the - "IMPORTANT" section above. When set to 'accessors', each optional - field is encapsulated behind 4 accessors, namely get(), - set(), has() and clear() methods, - with the standard semantics. The hazzer's return value determines - whether a field is serialized, so this style is useful when you need - to serialize a field with the default value, or check if a field has - been explicitly set to its default value from the wire. - - Required fields are still translated to one public mutable Java - field each, and repeated fields are still translated to arrays. No - accessors are generated for them. - - optional_field_style=accessors cannot be used together with - java_nano_generate_has=true. If you need the 'has' flag for any - required field (you have no reason to), you can only use - java_nano_generate_has=true. + "IMPORTANT" section above. - IMPORTANT: When using the 'accessor' style, ProGuard should always + * accessors * + + When set to 'accessors', each optional field is encapsulated behind + 4 accessors, namely get(), set(), has() + and clear() methods, with the standard semantics. The hazzer's + return value determines whether a field is serialized, so this style is + useful when you need to serialize a field with the default value, or check + if a field has been explicitly set to its default value from the wire. + + In the 'accessors' style, required fields are still translated to one + public mutable Java field each, and repeated fields are still translated + to arrays. No accessors are generated for them. + + IMPORTANT: When using the 'accessors' style, ProGuard should always be enabled with optimization (don't use -dontoptimize) and allowing access modification (use -allowaccessmodification). This removes the unused accessors and maybe inline the rest at the call sites, reducing the final code size. TODO(maxtroy): find ProGuard config that would work the best. + * reftypes * + + When set to 'reftypes', each proto field is generated as a public Java + field. For primitive types, these fields use the Java reference types + such as java.lang.Integer instead of primitive types such as int. + + In the 'reftypes' style, fields are initialized to null (or empty + arrays for repeated fields), and their default values are not available. + They are serialized over the wire based on equality to null. + + The 'reftypes' mode has some additional cost due to autoboxing and usage + of reference types. In practice, many boxed types are cached, and so don't + result in object creation. However, references do take slightly more memory + than primitives. + + The 'reftypes' mode is useful when you want to be able to serialize fields + with default values, or check if a field has been explicitly set to the + default over the wire without paying the extra method cost of the + 'accessors' mode. + + Note that if you attempt to write null to a required field in the reftypes + mode, serialization of the proto will cause a NullPointerException. This is + an intentional indicator that you must set required fields. + + + NOTE + optional_field_style=accessors or reftypes cannot be used together with + java_nano_generate_has=true. If you need the 'has' flag for any + required field (you have no reason to), you can only use + java_nano_generate_has=true. + + To use nano protobufs: - Link with the generated jar file diff --git a/java/src/test/java/com/google/protobuf/NanoTest.java b/java/src/test/java/com/google/protobuf/NanoTest.java index ca0bcda488..4f2ac3f830 100644 --- a/java/src/test/java/com/google/protobuf/NanoTest.java +++ b/java/src/test/java/com/google/protobuf/NanoTest.java @@ -48,6 +48,7 @@ import com.google.protobuf.nano.NanoAccessorsOuterClass.TestNanoAccessors; 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.NanoReferenceTypes; import com.google.protobuf.nano.UnittestImportNano; import com.google.protobuf.nano.UnittestMultipleNano; import com.google.protobuf.nano.UnittestRecursiveNano.RecursiveMessageNano; @@ -2624,6 +2625,67 @@ public class NanoTest extends TestCase { assertEquals(123, msg.synchronized_); } + public void testReferenceTypesForPrimitives() throws Exception { + NanoReferenceTypes.TestAllTypesNano message = new NanoReferenceTypes.TestAllTypesNano(); + + // Base check - when nothing is set, we serialize nothing. + assertHasWireData(message, false); + + message.defaultBool = true; + assertHasWireData(message, true); + + message.defaultBool = false; + assertHasWireData(message, true); + + message.defaultBool = null; + assertHasWireData(message, false); + + message.defaultInt32 = 5; + assertHasWireData(message, true); + + message.defaultInt32 = null; + assertHasWireData(message, false); + + message.defaultInt64 = 123456L; + assertHasWireData(message, true); + + message.defaultInt64 = null; + assertHasWireData(message, false); + + message.defaultFloat = 1f; + assertHasWireData(message, true); + + message.defaultFloat = null; + assertHasWireData(message, false); + + message.defaultDouble = 2.1; + assertHasWireData(message, true); + + message.defaultDouble = null; + assertHasWireData(message, false); + + message.defaultString = "hello"; + assertHasWireData(message, true); + + message.defaultString = null; + assertHasWireData(message, false); + + message.defaultBytes = new byte[] { 1, 2, 3 }; + assertHasWireData(message, true); + + message.defaultBytes = null; + assertHasWireData(message, false); + } + + private void assertHasWireData(MessageNano message, boolean expected) { + int wireLength = MessageNano.toByteArray(message).length; + if (expected) { + assertFalse(wireLength == 0); + } else { + assertEquals(0, wireLength); + } + } + private List list(T first, T... remaining) { List list = new ArrayList(); list.add(first); diff --git a/src/google/protobuf/compiler/javanano/javanano_enum_field.cc b/src/google/protobuf/compiler/javanano/javanano_enum_field.cc index b30b9fbf71..acb8dc2d5d 100644 --- a/src/google/protobuf/compiler/javanano/javanano_enum_field.cc +++ b/src/google/protobuf/compiler/javanano/javanano_enum_field.cc @@ -58,8 +58,16 @@ void SetEnumVariables(const Params& params, (*variables)["capitalized_name"] = RenameJavaKeywords(UnderscoresToCapitalizedCamelCase(descriptor)); (*variables)["number"] = SimpleItoa(descriptor->number()); - (*variables)["type"] = "int"; - (*variables)["default"] = DefaultValue(params, descriptor); + if (params.use_reference_types_for_primitives() + && !descriptor->is_repeated()) { + (*variables)["type"] = "java.lang.Integer"; + (*variables)["default"] = "null"; + } else { + (*variables)["type"] = "int"; + (*variables)["default"] = DefaultValue(params, descriptor); + } + (*variables)["repeated_default"] = + "com.google.protobuf.nano.WireFormatNano.EMPTY_INT_ARRAY"; (*variables)["tag"] = SimpleItoa(internal::WireFormat::MakeTag(descriptor)); (*variables)["tag_size"] = SimpleItoa( internal::WireFormat::TagSize(descriptor->number(), descriptor->type())); @@ -81,7 +89,7 @@ EnumFieldGenerator::~EnumFieldGenerator() {} void EnumFieldGenerator:: GenerateMembers(io::Printer* printer) const { printer->Print(variables_, - "public int $name$ = $default$;\n"); + "public $type$ $name$ = $default$;\n"); if (params_.generate_has()) { printer->Print(variables_, @@ -233,7 +241,7 @@ RepeatedEnumFieldGenerator::~RepeatedEnumFieldGenerator() {} void RepeatedEnumFieldGenerator:: GenerateMembers(io::Printer* printer) const { printer->Print(variables_, - "public int[] $name$ = com.google.protobuf.nano.WireFormatNano.EMPTY_INT_ARRAY;\n"); + "public $type$[] $name$ = $repeated_default$;\n"); if (descriptor_->options().packed()) { printer->Print(variables_, "private int $name$MemoizedSerializedSize;\n"); @@ -243,7 +251,7 @@ GenerateMembers(io::Printer* printer) const { void RepeatedEnumFieldGenerator:: GenerateClearCode(io::Printer* printer) const { printer->Print(variables_, - "$name$ = com.google.protobuf.nano.WireFormatNano.EMPTY_INT_ARRAY;\n"); + "$name$ = $repeated_default$;\n"); } void RepeatedEnumFieldGenerator:: diff --git a/src/google/protobuf/compiler/javanano/javanano_generator.cc b/src/google/protobuf/compiler/javanano/javanano_generator.cc index 8ba3f507fe..48c3a21514 100644 --- a/src/google/protobuf/compiler/javanano/javanano_generator.cc +++ b/src/google/protobuf/compiler/javanano/javanano_generator.cc @@ -124,15 +124,21 @@ bool JavaNanoGenerator::Generate(const FileDescriptor* file, params.set_java_enum_style(options[i].second == "java"); } else if (options[i].first == "optional_field_style") { params.set_optional_field_accessors(options[i].second == "accessors"); + params.set_use_reference_types_for_primitives(options[i].second == "reftypes"); } else { *error = "Ignore unknown javanano generator option: " + options[i].first; } } // Check illegal parameter combinations - if (params.generate_has() && params.optional_field_accessors()) { + // Note: the enum-like optional_field_style generator param ensures + // that we can never have illegal combinations of field styles + // (e.g. reftypes and accessors can't be on at the same time). + if (params.generate_has() + && (params.optional_field_accessors() + || params.use_reference_types_for_primitives())) { error->assign("java_nano_generate_has=true cannot be used in conjunction" - " with optional_field_style=accessors"); + " with optional_field_style=accessors or optional_field_style=reftypes"); return false; } diff --git a/src/google/protobuf/compiler/javanano/javanano_helpers.cc b/src/google/protobuf/compiler/javanano/javanano_helpers.cc index c4241653f0..ed6dbd753c 100644 --- a/src/google/protobuf/compiler/javanano/javanano_helpers.cc +++ b/src/google/protobuf/compiler/javanano/javanano_helpers.cc @@ -357,6 +357,10 @@ string DefaultValue(const Params& params, const FieldDescriptor* field) { return EmptyArrayName(params, field); } + if (params.use_reference_types_for_primitives()) { + return "null"; + } + // Switch on cpp_type since we need to know which default_value_* method // of FieldDescriptor to call. switch (field->cpp_type()) { diff --git a/src/google/protobuf/compiler/javanano/javanano_params.h b/src/google/protobuf/compiler/javanano/javanano_params.h index 5be5ff96d3..6e5379c144 100644 --- a/src/google/protobuf/compiler/javanano/javanano_params.h +++ b/src/google/protobuf/compiler/javanano/javanano_params.h @@ -60,6 +60,7 @@ class Params { bool generate_has_; bool java_enum_style_; bool optional_field_accessors_; + bool use_reference_types_for_primitives_; public: Params(const string & base_name) : @@ -69,7 +70,8 @@ class Params { store_unknown_fields_(false), generate_has_(false), java_enum_style_(false), - optional_field_accessors_(false) { + optional_field_accessors_(false), + use_reference_types_for_primitives_(false) { } const string& base_name() const { @@ -177,6 +179,13 @@ class Params { bool optional_field_accessors() const { return optional_field_accessors_; } + + void set_use_reference_types_for_primitives(bool value) { + use_reference_types_for_primitives_ = value; + } + bool use_reference_types_for_primitives() const { + return use_reference_types_for_primitives_; + } }; } // 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 fe6a48f8ed..d71b30b20b 100644 --- a/src/google/protobuf/compiler/javanano/javanano_primitive_field.cc +++ b/src/google/protobuf/compiler/javanano/javanano_primitive_field.cc @@ -245,7 +245,12 @@ void SetPrimitiveVariables(const FieldDescriptor* descriptor, const Params param (*variables)["capitalized_name"] = RenameJavaKeywords(UnderscoresToCapitalizedCamelCase(descriptor)); (*variables)["number"] = SimpleItoa(descriptor->number()); - (*variables)["type"] = PrimitiveTypeName(GetJavaType(descriptor)); + if (params.use_reference_types_for_primitives() + && !descriptor->is_repeated()) { + (*variables)["type"] = BoxedPrimitiveTypeName(GetJavaType(descriptor)); + } else { + (*variables)["type"] = PrimitiveTypeName(GetJavaType(descriptor)); + } (*variables)["default"] = DefaultValue(params, descriptor); (*variables)["default_constant"] = FieldDefaultConstantName(descriptor); // For C++-string types (string and bytes), we might need to have @@ -254,7 +259,8 @@ void SetPrimitiveVariables(const FieldDescriptor* descriptor, const Params param // once into a "private static final" field and re-use that from // then on. if (descriptor->cpp_type() == FieldDescriptor::CPPTYPE_STRING && - !descriptor->default_value_string().empty()) { + !descriptor->default_value_string().empty() && + !params.use_reference_types_for_primitives()) { string default_value; if (descriptor->type() == FieldDescriptor::TYPE_BYTES) { default_value = strings::Substitute( @@ -307,6 +313,7 @@ GenerateMembers(io::Printer* printer) const { printer->Print(variables_, "private static final $type$ $default_constant$ = $default_constant_value$;\n"); } + printer->Print(variables_, "public $type$ $name$ = $default_copy_if_needed$;\n"); @@ -340,6 +347,13 @@ GenerateMergingCode(io::Printer* printer) const { void PrimitiveFieldGenerator:: GenerateSerializationConditional(io::Printer* printer) const { + if (params_.use_reference_types_for_primitives()) { + // For reference type mode, serialize based on equality + // to null. + printer->Print(variables_, + "if (this.$name$ != null) {\n"); + return; + } if (params_.generate_has()) { printer->Print(variables_, "if (has$capitalized_name$ || "); diff --git a/src/google/protobuf/unittest_reference_types_nano.proto b/src/google/protobuf/unittest_reference_types_nano.proto new file mode 100644 index 0000000000..2b246150cf --- /dev/null +++ b/src/google/protobuf/unittest_reference_types_nano.proto @@ -0,0 +1,116 @@ +package protobuf_unittest; + +option java_package = "com.google.protobuf.nano"; +option java_outer_classname = "NanoReferenceTypes"; + +message TestAllTypesNano { + + enum NestedEnum { + FOO = 1; + BAR = 2; + BAZ = 3; + } + + message NestedMessage { + optional int32 foo = 1; + } + + // Singular + optional int32 optional_int32 = 1; + optional int64 optional_int64 = 2; + optional uint32 optional_uint32 = 3; + optional uint64 optional_uint64 = 4; + optional sint32 optional_sint32 = 5; + optional sint64 optional_sint64 = 6; + optional fixed32 optional_fixed32 = 7; + optional fixed64 optional_fixed64 = 8; + optional sfixed32 optional_sfixed32 = 9; + optional sfixed64 optional_sfixed64 = 10; + optional float optional_float = 11; + optional double optional_double = 12; + optional bool optional_bool = 13; + optional string optional_string = 14; + optional bytes optional_bytes = 15; + + optional group OptionalGroup = 16 { + optional int32 a = 17; + } + + optional NestedMessage optional_nested_message = 18; + + optional NestedEnum optional_nested_enum = 21; + + optional string optional_string_piece = 24 [ctype=STRING_PIECE]; + optional string optional_cord = 25 [ctype=CORD]; + + // Repeated + repeated int32 repeated_int32 = 31; + repeated int64 repeated_int64 = 32; + repeated uint32 repeated_uint32 = 33; + repeated uint64 repeated_uint64 = 34; + repeated sint32 repeated_sint32 = 35; + repeated sint64 repeated_sint64 = 36; + repeated fixed32 repeated_fixed32 = 37; + repeated fixed64 repeated_fixed64 = 38; + repeated sfixed32 repeated_sfixed32 = 39; + repeated sfixed64 repeated_sfixed64 = 40; + repeated float repeated_float = 41; + repeated double repeated_double = 42; + repeated bool repeated_bool = 43; + repeated string repeated_string = 44; + repeated bytes repeated_bytes = 45; + + repeated group RepeatedGroup = 46 { + optional int32 a = 47; + } + + repeated NestedMessage repeated_nested_message = 48; + + repeated NestedEnum repeated_nested_enum = 51; + + repeated string repeated_string_piece = 54 [ctype=STRING_PIECE]; + repeated string repeated_cord = 55 [ctype=CORD]; + + // Repeated packed + repeated int32 repeated_packed_int32 = 87 [packed=true]; + repeated sfixed64 repeated_packed_sfixed64 = 88 [packed=true]; + + repeated NestedEnum repeated_packed_nested_enum = 89 [packed=true]; + + // Singular with defaults + optional int32 default_int32 = 61 [default = 41 ]; + optional int64 default_int64 = 62 [default = 42 ]; + optional uint32 default_uint32 = 63 [default = 43 ]; + optional uint64 default_uint64 = 64 [default = 44 ]; + optional sint32 default_sint32 = 65 [default = -45 ]; + optional sint64 default_sint64 = 66 [default = 46 ]; + optional fixed32 default_fixed32 = 67 [default = 47 ]; + optional fixed64 default_fixed64 = 68 [default = 48 ]; + optional sfixed32 default_sfixed32 = 69 [default = 49 ]; + optional sfixed64 default_sfixed64 = 70 [default = -50 ]; + optional float default_float = 71 [default = 51.5 ]; + optional double default_double = 72 [default = 52e3 ]; + optional bool default_bool = 73 [default = true ]; + optional string default_string = 74 [default = "hello"]; + optional bytes default_bytes = 75 [default = "world"]; + + + optional float default_float_inf = 97 [default = inf]; + optional float default_float_neg_inf = 98 [default = -inf]; + optional float default_float_nan = 99 [default = nan]; + optional double default_double_inf = 100 [default = inf]; + optional double default_double_neg_inf = 101 [default = -inf]; + optional double default_double_nan = 102 [default = nan]; + +} + +message ForeignMessageNano { + optional int32 c = 1; +} + +enum ForeignEnumNano { + FOREIGN_NANO_FOO = 4; + FOREIGN_NANO_BAR = 5; + FOREIGN_NANO_BAZ = 6; +} +