From dce816593d4450a92167c62be44554571e7f6a96 Mon Sep 17 00:00:00 2001 From: Jisi Liu Date: Fri, 30 Jan 2015 16:14:15 -0800 Subject: Initial checkin for maps support in JavaNano. --- .../protobuf/compiler/javanano/javanano_helpers.h | 6 ++ .../compiler/javanano/javanano_map_field.cc | 85 ++++++++++++++++++++++ .../compiler/javanano/javanano_map_field.h | 71 ++++++++++++++++++ 3 files changed, 162 insertions(+) create mode 100644 src/google/protobuf/compiler/javanano/javanano_map_field.cc create mode 100644 src/google/protobuf/compiler/javanano/javanano_map_field.h (limited to 'src/google') diff --git a/src/google/protobuf/compiler/javanano/javanano_helpers.h b/src/google/protobuf/compiler/javanano/javanano_helpers.h index 29310743..5d3532b4 100644 --- a/src/google/protobuf/compiler/javanano/javanano_helpers.h +++ b/src/google/protobuf/compiler/javanano/javanano_helpers.h @@ -181,6 +181,12 @@ string GenerateDifferentBit(int bit_index); void SetBitOperationVariables(const string name, int bitIndex, map* variables); +inline bool IsMapEntry(const Descriptor* descriptor) { + // TODO(liujisi): Add an option to turn on maps for proto2 syntax as well. + return descriptor->options().map_entry() && + descriptor->file()->syntax() == FileDescriptor::SYNTAX_PROTO3; +} + } // namespace javanano } // namespace compiler } // namespace protobuf diff --git a/src/google/protobuf/compiler/javanano/javanano_map_field.cc b/src/google/protobuf/compiler/javanano/javanano_map_field.cc new file mode 100644 index 00000000..5dcc37f4 --- /dev/null +++ b/src/google/protobuf/compiler/javanano/javanano_map_field.cc @@ -0,0 +1,85 @@ +// 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. + +#include +#include +#include +#include +#include +#include + +namespace google { +namespace protobuf { +namespace compiler { +namespace javanano { + +namespace { + +void SetMapVariables(const Params& params, + const FieldDescriptor* descriptor, map* variables) { + (*variables)["name"] = + RenameJavaKeywords(UnderscoresToCamelCase(descriptor)); + (*variables)["capitalized_name"] = + RenameJavaKeywords(UnderscoresToCapitalizedCamelCase(descriptor)); + (*variables)["number"] = SimpleItoa(descriptor->number()); + (*variables)["type"] = "java.lang.Integer"; + (*variables)["default"] = "null"; + (*variables)["tag"] = SimpleItoa(internal::WireFormat::MakeTag(descriptor)); + (*variables)["tag_size"] = SimpleItoa( + internal::WireFormat::TagSize(descriptor->number(), descriptor->type())); + (*variables)["message_name"] = descriptor->containing_type()->name(); +} +} // namespace + +// =================================================================== +MapFieldGenerator::MapFieldGenerator(const FieldDescriptor* descriptor, + const Params& params) + : FieldGenerator(params), descriptor_(descriptor) { + SetMapVariables(params, descriptor, &variables_); +} + +MapFieldGenerator::~MapFieldGenerator() {} + +void MapFieldGenerator:: +GenerateMembers(io::Printer* printer, bool /* unused lazy_init */) const { + printer->Print(variables_, + "public $type$ $name$;\n"); +} + +void MapFieldGenerator:: +GenerateClearCode(io::Printer* printer) const { + printer->Print(variables_, + "$name$ = null;\n"); +} + +} // namespace javanano +} // namespace compiler +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/compiler/javanano/javanano_map_field.h b/src/google/protobuf/compiler/javanano/javanano_map_field.h new file mode 100644 index 00000000..4987945d --- /dev/null +++ b/src/google/protobuf/compiler/javanano/javanano_map_field.h @@ -0,0 +1,71 @@ +// 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. + +#ifndef GOOGLE_PROTOBUF_COMPILER_JAVANANO_MAP_FIELD_H__ +#define GOOGLE_PROTOBUF_COMPILER_JAVANANO_MAP_FIELD_H__ + +#include +#include +#include +#include + +namespace google { +namespace protobuf { +namespace compiler { +namespace javanano { + +class MapFieldGenerator : public FieldGenerator { + public: + explicit MapFieldGenerator( + const FieldDescriptor* descriptor, const Params& params); + ~MapFieldGenerator(); + + // implements FieldGenerator --------------------------------------- + void GenerateMembers(io::Printer* printer, bool lazy_init) const; + void GenerateClearCode(io::Printer* printer) const; + // void GenerateMergingCode(io::Printer* printer) const; + // void GenerateSerializationCode(io::Printer* printer) const; + // void GenerateSerializedSizeCode(io::Printer* printer) const; + // void GenerateEqualsCode(io::Printer* printer) const; + // void GenerateHashCodeCode(io::Printer* printer) const; + + private: + const FieldDescriptor* descriptor_; + map variables_; + vector canonical_values_; + + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(MapFieldGenerator); +}; + +} // namespace javanano +} // namespace compiler +} // namespace protobuf +} // namespace google +#endif // GOOGLE_PROTOBUF_COMPILER_JAVANANO_MAP_FIELD_H__ -- cgit v1.2.3 From 4a99897c0b31802f78df01bb8afa500184a4aca7 Mon Sep 17 00:00:00 2001 From: Jisi Liu Date: Mon, 2 Feb 2015 14:51:36 -0800 Subject: Use map generator for maps field; skip entry message. --- src/google/protobuf/compiler/javanano/javanano_field.cc | 7 ++++++- src/google/protobuf/compiler/javanano/javanano_message.cc | 3 +++ 2 files changed, 9 insertions(+), 1 deletion(-) (limited to 'src/google') diff --git a/src/google/protobuf/compiler/javanano/javanano_field.cc b/src/google/protobuf/compiler/javanano/javanano_field.cc index e3e4cefe..b9be90b1 100644 --- a/src/google/protobuf/compiler/javanano/javanano_field.cc +++ b/src/google/protobuf/compiler/javanano/javanano_field.cc @@ -36,6 +36,7 @@ #include #include #include +#include #include #include @@ -97,7 +98,11 @@ FieldGenerator* FieldGeneratorMap::MakeGenerator(const FieldDescriptor* field, if (field->is_repeated()) { switch (java_type) { case JAVATYPE_MESSAGE: - return new RepeatedMessageFieldGenerator(field, params); + if (IsMapEntry(field->message_type())) { + return new MapFieldGenerator(field, params); + } else { + return new RepeatedMessageFieldGenerator(field, params); + } case JAVATYPE_ENUM: return new RepeatedEnumFieldGenerator(field, params); default: diff --git a/src/google/protobuf/compiler/javanano/javanano_message.cc b/src/google/protobuf/compiler/javanano/javanano_message.cc index 7c52ca31..5454d9be 100644 --- a/src/google/protobuf/compiler/javanano/javanano_message.cc +++ b/src/google/protobuf/compiler/javanano/javanano_message.cc @@ -90,6 +90,7 @@ void MessageGenerator::GenerateStaticVariables(io::Printer* printer) { // Generate static members for all nested types. for (int i = 0; i < descriptor_->nested_type_count(); i++) { // TODO(kenton): Reuse MessageGenerator objects? + if (IsMapEntry(descriptor_->nested_type(i))) continue; MessageGenerator(descriptor_->nested_type(i), params_) .GenerateStaticVariables(printer); } @@ -100,6 +101,7 @@ void MessageGenerator::GenerateStaticVariableInitializers( // Generate static member initializers for all nested types. for (int i = 0; i < descriptor_->nested_type_count(); i++) { // TODO(kenton): Reuse MessageGenerator objects? + if (IsMapEntry(descriptor_->nested_type(i))) continue; MessageGenerator(descriptor_->nested_type(i), params_) .GenerateStaticVariableInitializers(printer); } @@ -159,6 +161,7 @@ void MessageGenerator::Generate(io::Printer* printer) { } for (int i = 0; i < descriptor_->nested_type_count(); i++) { + if (IsMapEntry(descriptor_->nested_type(i))) continue; MessageGenerator(descriptor_->nested_type(i), params_).Generate(printer); } -- cgit v1.2.3 From 4d64e65f0974f56310dacd3c6f6a7425ff8e9bf3 Mon Sep 17 00:00:00 2001 From: Jisi Liu Date: Mon, 2 Feb 2015 21:06:59 -0800 Subject: Parsing for Maps in JavaNano --- .../java/com/google/protobuf/nano/MapUtil.java | 97 +++++++++++++++++++++ .../compiler/javanano/javanano_map_field.cc | 98 +++++++++++++++++++--- .../compiler/javanano/javanano_map_field.h | 11 ++- 3 files changed, 190 insertions(+), 16 deletions(-) create mode 100644 javanano/src/main/java/com/google/protobuf/nano/MapUtil.java (limited to 'src/google') diff --git a/javanano/src/main/java/com/google/protobuf/nano/MapUtil.java b/javanano/src/main/java/com/google/protobuf/nano/MapUtil.java new file mode 100644 index 00000000..8e7647dd --- /dev/null +++ b/javanano/src/main/java/com/google/protobuf/nano/MapUtil.java @@ -0,0 +1,97 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2013 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. + +package com.google.protobuf.nano; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * Utility class for maps support. + */ +public final class MapUtil { + public static interface MapFactory { + Map forMap(Map oldMap); + } + public static void setMapFactory(MapFactory newMapFactory) { + mapFactory = newMapFactory; + } + + private static class DefaultMapFactory implements MapFactory { + public Map forMap(Map oldMap) { + if (oldMap == null) { + return new HashMap(); + } + return oldMap; + } + } + private static volatile MapFactory mapFactory = new DefaultMapFactory(); + + @SuppressWarnings("unchecked") + public static final Map mergeEntry( + Map target, CodedInputByteBufferNano input, + int keyType, int valueType, V value, + int keyTag, int valueTag) + throws IOException { + target = mapFactory.forMap(target); + final int length = input.readRawVarint32(); + final int oldLimit = input.pushLimit(length); + K key = null; + while (true) { + int tag = input.readTag(); + if (tag == 0) { + break; + } + if (tag == keyTag) { + key = (K) input.readData(keyType); + } else if (tag == valueTag) { + if (valueType == InternalNano.TYPE_MESSAGE) { + input.readMessage((MessageNano) value); + } else { + value = (V) input.readData(valueType); + } + } else { + if (!input.skipField(tag)) { + break; + } + } + } + input.checkLastTagWas(0); + input.popLimit(oldLimit); + + if (key != null) { + target.put(key, value); + } + return target; + } + + private MapUtil() {} +} diff --git a/src/google/protobuf/compiler/javanano/javanano_map_field.cc b/src/google/protobuf/compiler/javanano/javanano_map_field.cc index 5dcc37f4..4453cdfa 100644 --- a/src/google/protobuf/compiler/javanano/javanano_map_field.cc +++ b/src/google/protobuf/compiler/javanano/javanano_map_field.cc @@ -42,19 +42,69 @@ namespace javanano { namespace { +string TypeName(const Params& params, const FieldDescriptor* field, + bool boxed) { + JavaType java_type = GetJavaType(field); + switch (java_type) { + case JAVATYPE_MESSAGE: + return ClassName(params, field->message_type()); + case JAVATYPE_INT: + case JAVATYPE_LONG: + case JAVATYPE_FLOAT: + case JAVATYPE_DOUBLE: + case JAVATYPE_BOOLEAN: + case JAVATYPE_STRING: + case JAVATYPE_BYTES: + case JAVATYPE_ENUM: + if (boxed) { + return BoxedPrimitiveTypeName(java_type); + } else { + return PrimitiveTypeName(java_type); + } + // No default because we want the compiler to complain if any new JavaTypes + // are added.. + } + + GOOGLE_LOG(FATAL) << "should not reach here."; + return ""; +} + +const FieldDescriptor* KeyField(const FieldDescriptor* descriptor) { + GOOGLE_CHECK_EQ(FieldDescriptor::TYPE_MESSAGE, descriptor->type()); + const Descriptor* message = descriptor->message_type(); + GOOGLE_CHECK(message->options().map_entry()); + return message->FindFieldByName("key"); +} + +const FieldDescriptor* ValueField(const FieldDescriptor* descriptor) { + GOOGLE_CHECK_EQ(FieldDescriptor::TYPE_MESSAGE, descriptor->type()); + const Descriptor* message = descriptor->message_type(); + GOOGLE_CHECK(message->options().map_entry()); + return message->FindFieldByName("value"); +} + void SetMapVariables(const Params& params, const FieldDescriptor* descriptor, map* variables) { + const FieldDescriptor* key = KeyField(descriptor); + const FieldDescriptor* value = ValueField(descriptor); (*variables)["name"] = RenameJavaKeywords(UnderscoresToCamelCase(descriptor)); - (*variables)["capitalized_name"] = - RenameJavaKeywords(UnderscoresToCapitalizedCamelCase(descriptor)); - (*variables)["number"] = SimpleItoa(descriptor->number()); - (*variables)["type"] = "java.lang.Integer"; - (*variables)["default"] = "null"; - (*variables)["tag"] = SimpleItoa(internal::WireFormat::MakeTag(descriptor)); - (*variables)["tag_size"] = SimpleItoa( - internal::WireFormat::TagSize(descriptor->number(), descriptor->type())); - (*variables)["message_name"] = descriptor->containing_type()->name(); + (*variables)["key_type"] = TypeName(params, key, false); + (*variables)["boxed_key_type"] = TypeName(params,key, true); + (*variables)["key_desc_type"] = + "TYPE_" + ToUpper(FieldDescriptor::TypeName(key->type())); + (*variables)["key_tag"] = SimpleItoa(internal::WireFormat::MakeTag(key)); + (*variables)["value_type"] = TypeName(params, value, false); + (*variables)["boxed_value_type"] = TypeName(params, value, true); + (*variables)["value_desc_type"] = + "TYPE_" + ToUpper(FieldDescriptor::TypeName(value->type())); + (*variables)["value_tag"] = SimpleItoa(internal::WireFormat::MakeTag(value)); + (*variables)["type_parameters"] = + (*variables)["boxed_key_type"] + ", " + (*variables)["boxed_value_type"]; + (*variables)["value_default"] = + value->type() == FieldDescriptor::TYPE_MESSAGE + ? "new " + (*variables)["value_type"] + "()" + : "null"; } } // namespace @@ -70,7 +120,7 @@ MapFieldGenerator::~MapFieldGenerator() {} void MapFieldGenerator:: GenerateMembers(io::Printer* printer, bool /* unused lazy_init */) const { printer->Print(variables_, - "public $type$ $name$;\n"); + "public java.util.Map<$type_parameters$> $name$;\n"); } void MapFieldGenerator:: @@ -79,6 +129,34 @@ GenerateClearCode(io::Printer* printer) const { "$name$ = null;\n"); } +void MapFieldGenerator:: +GenerateMergingCode(io::Printer* printer) const { + printer->Print(variables_, + "$name$ = com.google.protobuf.nano.MapUtil.mergeEntry(\n" + " $name$, input,\n" + " com.google.protobuf.nano.InternalNano.$key_desc_type$,\n" + " com.google.protobuf.nano.InternalNano.$value_desc_type$,\n" + " $value_default$,\n" + " $key_tag$, $value_tag$);\n" + "\n"); +} + +void MapFieldGenerator:: +GenerateSerializationCode(io::Printer* printer) const { +} + +void MapFieldGenerator:: +GenerateSerializedSizeCode(io::Printer* printer) const { +} + +void MapFieldGenerator:: +GenerateEqualsCode(io::Printer* printer) const { +} + +void MapFieldGenerator:: +GenerateHashCodeCode(io::Printer* printer) const { +} + } // namespace javanano } // namespace compiler } // namespace protobuf diff --git a/src/google/protobuf/compiler/javanano/javanano_map_field.h b/src/google/protobuf/compiler/javanano/javanano_map_field.h index 4987945d..c01bde38 100644 --- a/src/google/protobuf/compiler/javanano/javanano_map_field.h +++ b/src/google/protobuf/compiler/javanano/javanano_map_field.h @@ -50,16 +50,15 @@ class MapFieldGenerator : public FieldGenerator { // implements FieldGenerator --------------------------------------- void GenerateMembers(io::Printer* printer, bool lazy_init) const; void GenerateClearCode(io::Printer* printer) const; - // void GenerateMergingCode(io::Printer* printer) const; - // void GenerateSerializationCode(io::Printer* printer) const; - // void GenerateSerializedSizeCode(io::Printer* printer) const; - // void GenerateEqualsCode(io::Printer* printer) const; - // void GenerateHashCodeCode(io::Printer* printer) const; + void GenerateMergingCode(io::Printer* printer) const; + void GenerateSerializationCode(io::Printer* printer) const; + void GenerateSerializedSizeCode(io::Printer* printer) const; + void GenerateEqualsCode(io::Printer* printer) const; + void GenerateHashCodeCode(io::Printer* printer) const; private: const FieldDescriptor* descriptor_; map variables_; - vector canonical_values_; GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(MapFieldGenerator); }; -- cgit v1.2.3 From d5839d2b4d45a6af02fe5852ace768ab3d40b5ff Mon Sep 17 00:00:00 2001 From: Jisi Liu Date: Tue, 3 Feb 2015 18:15:12 -0800 Subject: parsing and serialzation for maps in JavaNano. --- .../protobuf/nano/CodedOutputByteBufferNano.java | 124 +++++++++++++ .../java/com/google/protobuf/nano/MapUtil.java | 193 ++++++++++++++++++--- .../java/com/google/protobuf/nano/NanoTest.java | 29 +--- .../compiler/javanano/javanano_map_field.cc | 25 ++- 4 files changed, 316 insertions(+), 55 deletions(-) (limited to 'src/google') diff --git a/javanano/src/main/java/com/google/protobuf/nano/CodedOutputByteBufferNano.java b/javanano/src/main/java/com/google/protobuf/nano/CodedOutputByteBufferNano.java index 37982b57..2777f34c 100644 --- a/javanano/src/main/java/com/google/protobuf/nano/CodedOutputByteBufferNano.java +++ b/javanano/src/main/java/com/google/protobuf/nano/CodedOutputByteBufferNano.java @@ -876,4 +876,128 @@ public final class CodedOutputByteBufferNano { // Note: the right-shift must be arithmetic return (n << 1) ^ (n >> 63); } + + static int computeFieldSize(int number, int type, Object object) { + switch (type) { + case InternalNano.TYPE_BOOL: + return computeBoolSize(number, (Boolean) object); + case InternalNano.TYPE_BYTES: + return computeBytesSize(number, (byte[]) object); + case InternalNano.TYPE_STRING: + return computeStringSize(number, (String) object); + case InternalNano.TYPE_FLOAT: + return computeFloatSize(number, (Float) object); + case InternalNano.TYPE_DOUBLE: + return computeDoubleSize(number, (Double) object); + case InternalNano.TYPE_ENUM: + return computeEnumSize(number, (Integer) object); + case InternalNano.TYPE_FIXED32: + return computeFixed32Size(number, (Integer) object); + case InternalNano.TYPE_INT32: + return computeInt32Size(number, (Integer) object); + case InternalNano.TYPE_UINT32: + return computeUInt32Size(number, (Integer) object); + case InternalNano.TYPE_SINT32: + return computeSInt32Size(number, (Integer) object); + case InternalNano.TYPE_SFIXED32: + return computeSFixed32Size(number, (Integer) object); + case InternalNano.TYPE_INT64: + return computeInt64Size(number, (Long) object); + case InternalNano.TYPE_UINT64: + return computeUInt64Size(number, (Long) object); + case InternalNano.TYPE_SINT64: + return computeSInt64Size(number, (Long) object); + case InternalNano.TYPE_FIXED64: + return computeFixed64Size(number, (Long) object); + case InternalNano.TYPE_SFIXED64: + return computeSFixed64Size(number, (Long) object); + case InternalNano.TYPE_MESSAGE: + return computeMessageSize(number, (MessageNano) object); + case InternalNano.TYPE_GROUP: + return computeGroupSize(number, (MessageNano) object); + default: + throw new IllegalArgumentException("Unknown type: " + type); + } + } + + void writeField(int number, int type, Object value) + throws IOException { + switch (type) { + case InternalNano.TYPE_DOUBLE: + Double doubleValue = (Double) value; + writeDouble(number, doubleValue); + break; + case InternalNano.TYPE_FLOAT: + Float floatValue = (Float) value; + writeFloat(number, floatValue); + break; + case InternalNano.TYPE_INT64: + Long int64Value = (Long) value; + writeInt64(number, int64Value); + break; + case InternalNano.TYPE_UINT64: + Long uint64Value = (Long) value; + writeUInt64(number, uint64Value); + break; + case InternalNano.TYPE_INT32: + Integer int32Value = (Integer) value; + writeInt32(number, int32Value); + break; + case InternalNano.TYPE_FIXED64: + Long fixed64Value = (Long) value; + writeFixed64(number, fixed64Value); + break; + case InternalNano.TYPE_FIXED32: + Integer fixed32Value = (Integer) value; + writeFixed32(number, fixed32Value); + break; + case InternalNano.TYPE_BOOL: + Boolean boolValue = (Boolean) value; + writeBool(number, boolValue); + break; + case InternalNano.TYPE_STRING: + String stringValue = (String) value; + writeString(number, stringValue); + break; + case InternalNano.TYPE_BYTES: + byte[] bytesValue = (byte[]) value; + writeBytes(number, bytesValue); + break; + case InternalNano.TYPE_UINT32: + Integer uint32Value = (Integer) value; + writeUInt32(number, uint32Value); + break; + case InternalNano.TYPE_ENUM: + Integer enumValue = (Integer) value; + writeEnum(number, enumValue); + break; + case InternalNano.TYPE_SFIXED32: + Integer sfixed32Value = (Integer) value; + writeSFixed32(number, sfixed32Value); + break; + case InternalNano.TYPE_SFIXED64: + Long sfixed64Value = (Long) value; + writeSFixed64(number, sfixed64Value); + break; + case InternalNano.TYPE_SINT32: + Integer sint32Value = (Integer) value; + writeSInt32(number, sint32Value); + break; + case InternalNano.TYPE_SINT64: + Long sint64Value = (Long) value; + writeSInt64(number, sint64Value); + break; + case InternalNano.TYPE_MESSAGE: + MessageNano messageValue = (MessageNano) value; + writeMessage(number, messageValue); + break; + case InternalNano.TYPE_GROUP: + MessageNano groupValue = (MessageNano) value; + writeGroup(number, groupValue); + break; + default: + throw new IOException("Unknown type: " + type); + } + } + } diff --git a/javanano/src/main/java/com/google/protobuf/nano/MapUtil.java b/javanano/src/main/java/com/google/protobuf/nano/MapUtil.java index 8e7647dd..bc544081 100644 --- a/javanano/src/main/java/com/google/protobuf/nano/MapUtil.java +++ b/javanano/src/main/java/com/google/protobuf/nano/MapUtil.java @@ -33,6 +33,7 @@ package com.google.protobuf.nano; import java.io.IOException; import java.util.HashMap; import java.util.Map; +import java.util.Map.Entry; /** * Utility class for maps support. @@ -55,42 +56,178 @@ public final class MapUtil { } private static volatile MapFactory mapFactory = new DefaultMapFactory(); - @SuppressWarnings("unchecked") - public static final Map mergeEntry( - Map target, CodedInputByteBufferNano input, - int keyType, int valueType, V value, - int keyTag, int valueTag) - throws IOException { - target = mapFactory.forMap(target); - final int length = input.readRawVarint32(); - final int oldLimit = input.pushLimit(length); - K key = null; - while (true) { - int tag = input.readTag(); - if (tag == 0) { - break; + /** + * Internal utilities to implement maps for generated messages. + * Do NOT use it explicitly. + */ + public static class Internal { + private static final byte[] emptyBytes = new byte[0]; + private static Object primitiveDefaultValue(int type) { + switch (type) { + case InternalNano.TYPE_BOOL: + return Boolean.FALSE; + case InternalNano.TYPE_BYTES: + return emptyBytes; + case InternalNano.TYPE_STRING: + return ""; + case InternalNano.TYPE_FLOAT: + return Float.valueOf(0); + case InternalNano.TYPE_DOUBLE: + return Double.valueOf(0); + case InternalNano.TYPE_ENUM: + case InternalNano.TYPE_FIXED32: + case InternalNano.TYPE_INT32: + case InternalNano.TYPE_UINT32: + case InternalNano.TYPE_SINT32: + case InternalNano.TYPE_SFIXED32: + return Integer.valueOf(0); + case InternalNano.TYPE_INT64: + case InternalNano.TYPE_UINT64: + case InternalNano.TYPE_SINT64: + case InternalNano.TYPE_FIXED64: + case InternalNano.TYPE_SFIXED64: + return Long.valueOf(0L); + case InternalNano.TYPE_MESSAGE: + case InternalNano.TYPE_GROUP: + default: + throw new IllegalArgumentException( + "Type: " + type + " is not a primitive type."); } - if (tag == keyTag) { - key = (K) input.readData(keyType); - } else if (tag == valueTag) { - if (valueType == InternalNano.TYPE_MESSAGE) { - input.readMessage((MessageNano) value); + } + + /** + * Merges the map entry into the map field. Note this is only supposed to + * be called by generated messages. + * + * @param map the map field; may be null, in which case a map will be + * instantiated using the {@link MapUtil.MapFactory} + * @param input the input byte buffer + * @param keyType key type, as defined in InternalNano.TYPE_* + * @param valueType value type, as defined in InternalNano.TYPE_* + * @param valueClazz class of the value field if the valueType is + * TYPE_MESSAGE; otherwise the parameter is ignored and can be null. + * @param keyTag wire tag for the key + * @param valueTag wire tag for the value + * @return the map field + * @throws IOException + */ + @SuppressWarnings("unchecked") + public static final Map mergeEntry( + CodedInputByteBufferNano input, + Map map, + int keyType, + int valueType, + Class valueClazz, + int keyTag, + int valueTag) throws IOException { + map = mapFactory.forMap(map); + final int length = input.readRawVarint32(); + final int oldLimit = input.pushLimit(length); + byte[] payload = null; + K key = null; + V value = null; + while (true) { + int tag = input.readTag(); + if (tag == 0) { + break; + } + if (tag == keyTag) { + key = (K) input.readData(keyType); + } else if (tag == valueTag) { + if (valueType == InternalNano.TYPE_MESSAGE) { + payload = input.readBytes(); + } else { + value = (V) input.readData(valueType); + } } else { - value = (V) input.readData(valueType); + if (!input.skipField(tag)) { + break; + } } - } else { - if (!input.skipField(tag)) { - break; + } + input.checkLastTagWas(0); + input.popLimit(oldLimit); + + if (key == null) { + key = (K) primitiveDefaultValue(keyType); + } + + // Special case: merge the value when the value is a message. + if (valueType == InternalNano.TYPE_MESSAGE) { + MessageNano oldMessageValue = (MessageNano) map.get(key); + if (oldMessageValue != null) { + if (payload != null) { + MessageNano.mergeFrom(oldMessageValue, payload); + } + return map; + } + // Otherwise, create a new value message. + try { + value = valueClazz.newInstance(); + } catch (InstantiationException e) { + throw new IOException( + "Unable to create value message " + valueClazz.getName() + + " in maps."); + } catch (IllegalAccessException e) { + throw new IOException( + "Unable to create value message " + valueClazz.getName() + + " in maps."); + } + if (payload != null) { + MessageNano.mergeFrom((MessageNano) value, payload); } } + + if (value == null) { + value = (V) primitiveDefaultValue(valueType); + } + + map.put(key, value); + return map; + } + + public static void serializeMapField( + CodedOutputByteBufferNano output, + Map map, int number, int keyType, int valueType) + throws IOException { + for (Entry entry: map.entrySet()) { + K key = entry.getKey(); + V value = entry.getValue(); + if (key == null || value == null) { + throw new IllegalStateException( + "keys and values in maps cannot be null"); + } + int entrySize = + CodedOutputByteBufferNano.computeFieldSize(1, keyType, key) + + CodedOutputByteBufferNano.computeFieldSize(2, valueType, value); + output.writeTag(number, WireFormatNano.WIRETYPE_LENGTH_DELIMITED); + output.writeRawVarint32(entrySize); + output.writeField(1, keyType, key); + output.writeField(2, valueType, value); + } } - input.checkLastTagWas(0); - input.popLimit(oldLimit); - if (key != null) { - target.put(key, value); + public static int computeMapFieldSize( + Map map, int number, int keyType, int valueType) { + int size = 0; + int tagSize = CodedOutputByteBufferNano.computeTagSize(number); + for (Entry entry: map.entrySet()) { + K key = entry.getKey(); + V value = entry.getValue(); + if (key == null || value == null) { + throw new IllegalStateException( + "keys and values in maps cannot be null"); + } + int entrySize = + CodedOutputByteBufferNano.computeFieldSize(1, keyType, key) + + CodedOutputByteBufferNano.computeFieldSize(2, valueType, value); + size += tagSize + entrySize + + CodedOutputByteBufferNano.computeRawVarint32Size(entrySize); + } + return size; } - return target; + + private Internal() {} } private MapUtil() {} diff --git a/javanano/src/test/java/com/google/protobuf/nano/NanoTest.java b/javanano/src/test/java/com/google/protobuf/nano/NanoTest.java index 442f0b74..d9428432 100644 --- a/javanano/src/test/java/com/google/protobuf/nano/NanoTest.java +++ b/javanano/src/test/java/com/google/protobuf/nano/NanoTest.java @@ -30,31 +30,9 @@ package com.google.protobuf.nano; -import com.google.protobuf.nano.CodedInputByteBufferNano; -import com.google.protobuf.nano.EnumClassNanoMultiple; -import com.google.protobuf.nano.EnumClassNanos; -import com.google.protobuf.nano.EnumValidity; -import com.google.protobuf.nano.EnumValidityAccessors; -import com.google.protobuf.nano.FileScopeEnumMultiple; -import com.google.protobuf.nano.FileScopeEnumRefNano; -import com.google.protobuf.nano.InternalNano; -import com.google.protobuf.nano.InvalidProtocolBufferNanoException; -import com.google.protobuf.nano.MessageNano; -import com.google.protobuf.nano.MessageScopeEnumRefNano; -import com.google.protobuf.nano.MultipleImportingNonMultipleNano1; -import com.google.protobuf.nano.MultipleImportingNonMultipleNano2; -import com.google.protobuf.nano.MultipleNameClashNano; 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.NanoRepeatedPackables; -import com.google.protobuf.nano.PackedExtensions; -import com.google.protobuf.nano.RepeatedExtensions; -import com.google.protobuf.nano.SingularExtensions; -import com.google.protobuf.nano.TestRepeatedMergeNano; -import com.google.protobuf.nano.UnittestMultipleNano; import com.google.protobuf.nano.UnittestRecursiveNano.RecursiveMessageNano; import com.google.protobuf.nano.UnittestSimpleNano.SimpleMessageNano; import com.google.protobuf.nano.UnittestSingleNano.SingleMessageNano; @@ -3754,6 +3732,13 @@ public class NanoTest extends TestCase { assertTrue(Arrays.equals(new boolean[] {false, true, false, true}, nonPacked.bools)); } + public void testMapsSerializeAndParse() throws Exception { + // TODO(liujisi): Test basic serialization/parsing roundtrip. + // TODO(liujisi): Test null values in serialization. + // TODO(liujisi): Test merging message type values. + // TODO(liujisi): Test missing key/value in parsing. + } + private void assertRepeatedPackablesEqual( NanoRepeatedPackables.NonPacked nonPacked, NanoRepeatedPackables.Packed packed) { // Not using MessageNano.equals() -- that belongs to a separate test. diff --git a/src/google/protobuf/compiler/javanano/javanano_map_field.cc b/src/google/protobuf/compiler/javanano/javanano_map_field.cc index 4453cdfa..dead3685 100644 --- a/src/google/protobuf/compiler/javanano/javanano_map_field.cc +++ b/src/google/protobuf/compiler/javanano/javanano_map_field.cc @@ -89,6 +89,7 @@ void SetMapVariables(const Params& params, const FieldDescriptor* value = ValueField(descriptor); (*variables)["name"] = RenameJavaKeywords(UnderscoresToCamelCase(descriptor)); + (*variables)["number"] = SimpleItoa(descriptor->number()); (*variables)["key_type"] = TypeName(params, key, false); (*variables)["boxed_key_type"] = TypeName(params,key, true); (*variables)["key_desc_type"] = @@ -101,9 +102,9 @@ void SetMapVariables(const Params& params, (*variables)["value_tag"] = SimpleItoa(internal::WireFormat::MakeTag(value)); (*variables)["type_parameters"] = (*variables)["boxed_key_type"] + ", " + (*variables)["boxed_value_type"]; - (*variables)["value_default"] = + (*variables)["value_class"] = value->type() == FieldDescriptor::TYPE_MESSAGE - ? "new " + (*variables)["value_type"] + "()" + ? (*variables)["value_type"] + ".class" : "null"; } } // namespace @@ -132,21 +133,35 @@ GenerateClearCode(io::Printer* printer) const { void MapFieldGenerator:: GenerateMergingCode(io::Printer* printer) const { printer->Print(variables_, - "$name$ = com.google.protobuf.nano.MapUtil.mergeEntry(\n" - " $name$, input,\n" + "this.$name$ = com.google.protobuf.nano.MapUtil.Internal.mergeEntry(\n" + " input, this.$name$,\n" " com.google.protobuf.nano.InternalNano.$key_desc_type$,\n" " com.google.protobuf.nano.InternalNano.$value_desc_type$,\n" - " $value_default$,\n" + " $value_class$,\n" " $key_tag$, $value_tag$);\n" "\n"); } void MapFieldGenerator:: GenerateSerializationCode(io::Printer* printer) const { + printer->Print(variables_, + "if (this.$name$ != null) {\n" + " com.google.protobuf.nano.MapUtil.Internal.serializeMapField(\n" + " output, this.$name$, $number$,\n" + " com.google.protobuf.nano.InternalNano.$key_desc_type$,\n" + " com.google.protobuf.nano.InternalNano.$value_desc_type$);\n" + "}\n"); } void MapFieldGenerator:: GenerateSerializedSizeCode(io::Printer* printer) const { + printer->Print(variables_, + "if (this.$name$ != null) {\n" + " size += com.google.protobuf.nano.MapUtil.Internal.computeMapFieldSize(\n" + " this.$name$, $number$,\n" + " com.google.protobuf.nano.InternalNano.$key_desc_type$,\n" + " com.google.protobuf.nano.InternalNano.$value_desc_type$);\n" + "}\n"); } void MapFieldGenerator:: -- cgit v1.2.3 From 3c0355ef3768d45843aaf8437f8ecf63f3706e5d Mon Sep 17 00:00:00 2001 From: Jisi Liu Date: Wed, 4 Feb 2015 13:54:58 -0800 Subject: Moving internal code to InternalNano and rename MapUtil to MapFactories. --- .../com/google/protobuf/nano/InternalNano.java | 167 +++++++++++++++ .../com/google/protobuf/nano/MapFactories.java | 63 ++++++ .../java/com/google/protobuf/nano/MapUtil.java | 234 --------------------- .../compiler/javanano/javanano_map_field.cc | 6 +- 4 files changed, 233 insertions(+), 237 deletions(-) create mode 100644 javanano/src/main/java/com/google/protobuf/nano/MapFactories.java delete mode 100644 javanano/src/main/java/com/google/protobuf/nano/MapUtil.java (limited to 'src/google') diff --git a/javanano/src/main/java/com/google/protobuf/nano/InternalNano.java b/javanano/src/main/java/com/google/protobuf/nano/InternalNano.java index 6d30c0f8..0705b0c0 100644 --- a/javanano/src/main/java/com/google/protobuf/nano/InternalNano.java +++ b/javanano/src/main/java/com/google/protobuf/nano/InternalNano.java @@ -30,8 +30,11 @@ package com.google.protobuf.nano; +import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.Arrays; +import java.util.Map; +import java.util.Map.Entry; /** * The classes contained within are used internally by the Protocol Buffer @@ -349,5 +352,169 @@ public final class InternalNano { } return result; } + private static final byte[] emptyBytes = new byte[0]; + private static Object primitiveDefaultValue(int type) { + switch (type) { + case TYPE_BOOL: + return Boolean.FALSE; + case TYPE_BYTES: + return emptyBytes; + case TYPE_STRING: + return ""; + case TYPE_FLOAT: + return Float.valueOf(0); + case TYPE_DOUBLE: + return Double.valueOf(0); + case TYPE_ENUM: + case TYPE_FIXED32: + case TYPE_INT32: + case TYPE_UINT32: + case TYPE_SINT32: + case TYPE_SFIXED32: + return Integer.valueOf(0); + case TYPE_INT64: + case TYPE_UINT64: + case TYPE_SINT64: + case TYPE_FIXED64: + case TYPE_SFIXED64: + return Long.valueOf(0L); + case TYPE_MESSAGE: + case TYPE_GROUP: + default: + throw new IllegalArgumentException( + "Type: " + type + " is not a primitive type."); + } + } + + /** + * Merges the map entry into the map field. Note this is only supposed to + * be called by generated messages. + * + * @param map the map field; may be null, in which case a map will be + * instantiated using the {@link MapUtil.MapFactory} + * @param input the input byte buffer + * @param keyType key type, as defined in InternalNano.TYPE_* + * @param valueType value type, as defined in InternalNano.TYPE_* + * @param valueClazz class of the value field if the valueType is + * TYPE_MESSAGE; otherwise the parameter is ignored and can be null. + * @param keyTag wire tag for the key + * @param valueTag wire tag for the value + * @return the map field + * @throws IOException + */ + @SuppressWarnings("unchecked") + public static final Map mergeMapEntry( + CodedInputByteBufferNano input, + Map map, + int keyType, + int valueType, + Class valueClazz, + int keyTag, + int valueTag) throws IOException { + map = MapFactories.getMapFactory().forMap(map); + final int length = input.readRawVarint32(); + final int oldLimit = input.pushLimit(length); + byte[] payload = null; + K key = null; + V value = null; + while (true) { + int tag = input.readTag(); + if (tag == 0) { + break; + } + if (tag == keyTag) { + key = (K) input.readData(keyType); + } else if (tag == valueTag) { + if (valueType == TYPE_MESSAGE) { + payload = input.readBytes(); + } else { + value = (V) input.readData(valueType); + } + } else { + if (!input.skipField(tag)) { + break; + } + } + } + input.checkLastTagWas(0); + input.popLimit(oldLimit); + + if (key == null) { + key = (K) primitiveDefaultValue(keyType); + } + // Special case: merge the value when the value is a message. + if (valueType == TYPE_MESSAGE) { + MessageNano oldMessageValue = (MessageNano) map.get(key); + if (oldMessageValue != null) { + if (payload != null) { + MessageNano.mergeFrom(oldMessageValue, payload); + } + return map; + } + // Otherwise, create a new value message. + try { + value = valueClazz.newInstance(); + } catch (InstantiationException e) { + throw new IOException( + "Unable to create value message " + valueClazz.getName() + + " in maps."); + } catch (IllegalAccessException e) { + throw new IOException( + "Unable to create value message " + valueClazz.getName() + + " in maps."); + } + if (payload != null) { + MessageNano.mergeFrom((MessageNano) value, payload); + } + } + + if (value == null) { + value = (V) primitiveDefaultValue(valueType); + } + + map.put(key, value); + return map; + } + + public static void serializeMapField( + CodedOutputByteBufferNano output, + Map map, int number, int keyType, int valueType) + throws IOException { + for (Entry entry: map.entrySet()) { + K key = entry.getKey(); + V value = entry.getValue(); + if (key == null || value == null) { + throw new IllegalStateException( + "keys and values in maps cannot be null"); + } + int entrySize = + CodedOutputByteBufferNano.computeFieldSize(1, keyType, key) + + CodedOutputByteBufferNano.computeFieldSize(2, valueType, value); + output.writeTag(number, WireFormatNano.WIRETYPE_LENGTH_DELIMITED); + output.writeRawVarint32(entrySize); + output.writeField(1, keyType, key); + output.writeField(2, valueType, value); + } + } + + public static int computeMapFieldSize( + Map map, int number, int keyType, int valueType) { + int size = 0; + int tagSize = CodedOutputByteBufferNano.computeTagSize(number); + for (Entry entry: map.entrySet()) { + K key = entry.getKey(); + V value = entry.getValue(); + if (key == null || value == null) { + throw new IllegalStateException( + "keys and values in maps cannot be null"); + } + int entrySize = + CodedOutputByteBufferNano.computeFieldSize(1, keyType, key) + + CodedOutputByteBufferNano.computeFieldSize(2, valueType, value); + size += tagSize + entrySize + + CodedOutputByteBufferNano.computeRawVarint32Size(entrySize); + } + return size; + } } diff --git a/javanano/src/main/java/com/google/protobuf/nano/MapFactories.java b/javanano/src/main/java/com/google/protobuf/nano/MapFactories.java new file mode 100644 index 00000000..57d00b49 --- /dev/null +++ b/javanano/src/main/java/com/google/protobuf/nano/MapFactories.java @@ -0,0 +1,63 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2013 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. + +package com.google.protobuf.nano; + +import java.util.HashMap; +import java.util.Map; + +/** + * Utility class for maps support. + */ +public final class MapFactories { + public static interface MapFactory { + Map forMap(Map oldMap); + } + + public static void setMapFactory(MapFactory newMapFactory) { + mapFactory = newMapFactory; + } + + public static MapFactory getMapFactory() { + return mapFactory; + } + + private static class DefaultMapFactory implements MapFactory { + public Map forMap(Map oldMap) { + if (oldMap == null) { + return new HashMap(); + } + return oldMap; + } + } + private static volatile MapFactory mapFactory = new DefaultMapFactory(); + + private MapFactories() {} +} diff --git a/javanano/src/main/java/com/google/protobuf/nano/MapUtil.java b/javanano/src/main/java/com/google/protobuf/nano/MapUtil.java deleted file mode 100644 index bc544081..00000000 --- a/javanano/src/main/java/com/google/protobuf/nano/MapUtil.java +++ /dev/null @@ -1,234 +0,0 @@ -// Protocol Buffers - Google's data interchange format -// Copyright 2013 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. - -package com.google.protobuf.nano; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; - -/** - * Utility class for maps support. - */ -public final class MapUtil { - public static interface MapFactory { - Map forMap(Map oldMap); - } - public static void setMapFactory(MapFactory newMapFactory) { - mapFactory = newMapFactory; - } - - private static class DefaultMapFactory implements MapFactory { - public Map forMap(Map oldMap) { - if (oldMap == null) { - return new HashMap(); - } - return oldMap; - } - } - private static volatile MapFactory mapFactory = new DefaultMapFactory(); - - /** - * Internal utilities to implement maps for generated messages. - * Do NOT use it explicitly. - */ - public static class Internal { - private static final byte[] emptyBytes = new byte[0]; - private static Object primitiveDefaultValue(int type) { - switch (type) { - case InternalNano.TYPE_BOOL: - return Boolean.FALSE; - case InternalNano.TYPE_BYTES: - return emptyBytes; - case InternalNano.TYPE_STRING: - return ""; - case InternalNano.TYPE_FLOAT: - return Float.valueOf(0); - case InternalNano.TYPE_DOUBLE: - return Double.valueOf(0); - case InternalNano.TYPE_ENUM: - case InternalNano.TYPE_FIXED32: - case InternalNano.TYPE_INT32: - case InternalNano.TYPE_UINT32: - case InternalNano.TYPE_SINT32: - case InternalNano.TYPE_SFIXED32: - return Integer.valueOf(0); - case InternalNano.TYPE_INT64: - case InternalNano.TYPE_UINT64: - case InternalNano.TYPE_SINT64: - case InternalNano.TYPE_FIXED64: - case InternalNano.TYPE_SFIXED64: - return Long.valueOf(0L); - case InternalNano.TYPE_MESSAGE: - case InternalNano.TYPE_GROUP: - default: - throw new IllegalArgumentException( - "Type: " + type + " is not a primitive type."); - } - } - - /** - * Merges the map entry into the map field. Note this is only supposed to - * be called by generated messages. - * - * @param map the map field; may be null, in which case a map will be - * instantiated using the {@link MapUtil.MapFactory} - * @param input the input byte buffer - * @param keyType key type, as defined in InternalNano.TYPE_* - * @param valueType value type, as defined in InternalNano.TYPE_* - * @param valueClazz class of the value field if the valueType is - * TYPE_MESSAGE; otherwise the parameter is ignored and can be null. - * @param keyTag wire tag for the key - * @param valueTag wire tag for the value - * @return the map field - * @throws IOException - */ - @SuppressWarnings("unchecked") - public static final Map mergeEntry( - CodedInputByteBufferNano input, - Map map, - int keyType, - int valueType, - Class valueClazz, - int keyTag, - int valueTag) throws IOException { - map = mapFactory.forMap(map); - final int length = input.readRawVarint32(); - final int oldLimit = input.pushLimit(length); - byte[] payload = null; - K key = null; - V value = null; - while (true) { - int tag = input.readTag(); - if (tag == 0) { - break; - } - if (tag == keyTag) { - key = (K) input.readData(keyType); - } else if (tag == valueTag) { - if (valueType == InternalNano.TYPE_MESSAGE) { - payload = input.readBytes(); - } else { - value = (V) input.readData(valueType); - } - } else { - if (!input.skipField(tag)) { - break; - } - } - } - input.checkLastTagWas(0); - input.popLimit(oldLimit); - - if (key == null) { - key = (K) primitiveDefaultValue(keyType); - } - - // Special case: merge the value when the value is a message. - if (valueType == InternalNano.TYPE_MESSAGE) { - MessageNano oldMessageValue = (MessageNano) map.get(key); - if (oldMessageValue != null) { - if (payload != null) { - MessageNano.mergeFrom(oldMessageValue, payload); - } - return map; - } - // Otherwise, create a new value message. - try { - value = valueClazz.newInstance(); - } catch (InstantiationException e) { - throw new IOException( - "Unable to create value message " + valueClazz.getName() - + " in maps."); - } catch (IllegalAccessException e) { - throw new IOException( - "Unable to create value message " + valueClazz.getName() - + " in maps."); - } - if (payload != null) { - MessageNano.mergeFrom((MessageNano) value, payload); - } - } - - if (value == null) { - value = (V) primitiveDefaultValue(valueType); - } - - map.put(key, value); - return map; - } - - public static void serializeMapField( - CodedOutputByteBufferNano output, - Map map, int number, int keyType, int valueType) - throws IOException { - for (Entry entry: map.entrySet()) { - K key = entry.getKey(); - V value = entry.getValue(); - if (key == null || value == null) { - throw new IllegalStateException( - "keys and values in maps cannot be null"); - } - int entrySize = - CodedOutputByteBufferNano.computeFieldSize(1, keyType, key) + - CodedOutputByteBufferNano.computeFieldSize(2, valueType, value); - output.writeTag(number, WireFormatNano.WIRETYPE_LENGTH_DELIMITED); - output.writeRawVarint32(entrySize); - output.writeField(1, keyType, key); - output.writeField(2, valueType, value); - } - } - - public static int computeMapFieldSize( - Map map, int number, int keyType, int valueType) { - int size = 0; - int tagSize = CodedOutputByteBufferNano.computeTagSize(number); - for (Entry entry: map.entrySet()) { - K key = entry.getKey(); - V value = entry.getValue(); - if (key == null || value == null) { - throw new IllegalStateException( - "keys and values in maps cannot be null"); - } - int entrySize = - CodedOutputByteBufferNano.computeFieldSize(1, keyType, key) + - CodedOutputByteBufferNano.computeFieldSize(2, valueType, value); - size += tagSize + entrySize - + CodedOutputByteBufferNano.computeRawVarint32Size(entrySize); - } - return size; - } - - private Internal() {} - } - - private MapUtil() {} -} diff --git a/src/google/protobuf/compiler/javanano/javanano_map_field.cc b/src/google/protobuf/compiler/javanano/javanano_map_field.cc index dead3685..7b14db5d 100644 --- a/src/google/protobuf/compiler/javanano/javanano_map_field.cc +++ b/src/google/protobuf/compiler/javanano/javanano_map_field.cc @@ -133,7 +133,7 @@ GenerateClearCode(io::Printer* printer) const { void MapFieldGenerator:: GenerateMergingCode(io::Printer* printer) const { printer->Print(variables_, - "this.$name$ = com.google.protobuf.nano.MapUtil.Internal.mergeEntry(\n" + "this.$name$ = com.google.protobuf.nano.InternalNano.mergeMapEntry(\n" " input, this.$name$,\n" " com.google.protobuf.nano.InternalNano.$key_desc_type$,\n" " com.google.protobuf.nano.InternalNano.$value_desc_type$,\n" @@ -146,7 +146,7 @@ void MapFieldGenerator:: GenerateSerializationCode(io::Printer* printer) const { printer->Print(variables_, "if (this.$name$ != null) {\n" - " com.google.protobuf.nano.MapUtil.Internal.serializeMapField(\n" + " com.google.protobuf.nano.InternalNano.serializeMapField(\n" " output, this.$name$, $number$,\n" " com.google.protobuf.nano.InternalNano.$key_desc_type$,\n" " com.google.protobuf.nano.InternalNano.$value_desc_type$);\n" @@ -157,7 +157,7 @@ void MapFieldGenerator:: GenerateSerializedSizeCode(io::Printer* printer) const { printer->Print(variables_, "if (this.$name$ != null) {\n" - " size += com.google.protobuf.nano.MapUtil.Internal.computeMapFieldSize(\n" + " size += com.google.protobuf.nano.InternalNano.computeMapFieldSize(\n" " this.$name$, $number$,\n" " com.google.protobuf.nano.InternalNano.$key_desc_type$,\n" " com.google.protobuf.nano.InternalNano.$value_desc_type$);\n" -- cgit v1.2.3 From bd3573cb096cb8f0ec4bf29f0e11744a06a9e5a6 Mon Sep 17 00:00:00 2001 From: Jisi Liu Date: Thu, 5 Feb 2015 14:52:17 -0800 Subject: Fix the behavior when merging conflicting keys, the new value always override the existing one even for message types. --- .../com/google/protobuf/nano/InternalNano.java | 40 ++++------------------ .../java/com/google/protobuf/nano/NanoTest.java | 28 ++++++++++++++- .../java/com/google/protobuf/nano/map_test.proto | 1 + .../compiler/javanano/javanano_map_field.cc | 6 ++-- 4 files changed, 38 insertions(+), 37 deletions(-) (limited to 'src/google') diff --git a/javanano/src/main/java/com/google/protobuf/nano/InternalNano.java b/javanano/src/main/java/com/google/protobuf/nano/InternalNano.java index 0705b0c0..fc0a36cd 100644 --- a/javanano/src/main/java/com/google/protobuf/nano/InternalNano.java +++ b/javanano/src/main/java/com/google/protobuf/nano/InternalNano.java @@ -391,12 +391,12 @@ public final class InternalNano { * be called by generated messages. * * @param map the map field; may be null, in which case a map will be - * instantiated using the {@link MapUtil.MapFactory} + * instantiated using the {@link MapFactories.MapFactory} * @param input the input byte buffer * @param keyType key type, as defined in InternalNano.TYPE_* * @param valueType value type, as defined in InternalNano.TYPE_* - * @param valueClazz class of the value field if the valueType is - * TYPE_MESSAGE; otherwise the parameter is ignored and can be null. + * @param value an new instance of the value, if the value is a TYPE_MESSAGE; + * otherwise this parameter can be null and will be ignored. * @param keyTag wire tag for the key * @param valueTag wire tag for the value * @return the map field @@ -408,15 +408,13 @@ public final class InternalNano { Map map, int keyType, int valueType, - Class valueClazz, + V value, int keyTag, int valueTag) throws IOException { map = MapFactories.getMapFactory().forMap(map); final int length = input.readRawVarint32(); final int oldLimit = input.pushLimit(length); - byte[] payload = null; K key = null; - V value = null; while (true) { int tag = input.readTag(); if (tag == 0) { @@ -426,7 +424,7 @@ public final class InternalNano { key = (K) input.readData(keyType); } else if (tag == valueTag) { if (valueType == TYPE_MESSAGE) { - payload = input.readBytes(); + input.readMessage((MessageNano) value); } else { value = (V) input.readData(valueType); } @@ -440,36 +438,12 @@ public final class InternalNano { input.popLimit(oldLimit); if (key == null) { + // key can only be primitive types. key = (K) primitiveDefaultValue(keyType); } - // Special case: merge the value when the value is a message. - if (valueType == TYPE_MESSAGE) { - MessageNano oldMessageValue = (MessageNano) map.get(key); - if (oldMessageValue != null) { - if (payload != null) { - MessageNano.mergeFrom(oldMessageValue, payload); - } - return map; - } - // Otherwise, create a new value message. - try { - value = valueClazz.newInstance(); - } catch (InstantiationException e) { - throw new IOException( - "Unable to create value message " + valueClazz.getName() - + " in maps."); - } catch (IllegalAccessException e) { - throw new IOException( - "Unable to create value message " + valueClazz.getName() - + " in maps."); - } - if (payload != null) { - MessageNano.mergeFrom((MessageNano) value, payload); - } - } - if (value == null) { + // message type value = (V) primitiveDefaultValue(valueType); } diff --git a/javanano/src/test/java/com/google/protobuf/nano/NanoTest.java b/javanano/src/test/java/com/google/protobuf/nano/NanoTest.java index a7383cb4..bf8a391a 100644 --- a/javanano/src/test/java/com/google/protobuf/nano/NanoTest.java +++ b/javanano/src/test/java/com/google/protobuf/nano/NanoTest.java @@ -3742,7 +3742,6 @@ public class NanoTest extends TestCase { byte[] output = MessageNano.toByteArray(origin); TestMap parsed = new TestMap(); MessageNano.mergeFrom(parsed, output); - // TODO(liujisi): Test merging message type values. // TODO(liujisi): Test missing key/value in parsing. } @@ -3769,6 +3768,33 @@ public class NanoTest extends TestCase { } } + /** + * Tests that merging bytes containing conflicting keys with override the + * message value instead of merging the message value into the existing entry. + */ + public void testMapMergeOverrideMessageValues() throws Exception { + TestMap.MessageValue origValue = new TestMap.MessageValue(); + origValue.value = 1; + origValue.value2 = 2; + TestMap.MessageValue newValue = new TestMap.MessageValue(); + newValue.value = 3; + + TestMap origMessage = new TestMap(); + origMessage.int32ToMessageField = + new HashMap(); + origMessage.int32ToMessageField.put(1, origValue); + + TestMap newMessage = new TestMap(); + newMessage.int32ToMessageField = + new HashMap(); + newMessage.int32ToMessageField.put(1, newValue); + MessageNano.mergeFrom(origMessage, + MessageNano.toByteArray(newMessage)); + TestMap.MessageValue mergedValue = origMessage.int32ToMessageField.get(1); + assertEquals(3, mergedValue.value); + assertEquals(0, mergedValue.value2); + } + private static final Integer[] int32Values = new Integer[] { 0, 1, -1, Integer.MAX_VALUE, Integer.MIN_VALUE, }; diff --git a/javanano/src/test/java/com/google/protobuf/nano/map_test.proto b/javanano/src/test/java/com/google/protobuf/nano/map_test.proto index 5ea86717..f72833ad 100644 --- a/javanano/src/test/java/com/google/protobuf/nano/map_test.proto +++ b/javanano/src/test/java/com/google/protobuf/nano/map_test.proto @@ -38,6 +38,7 @@ option java_outer_classname = "MapTestProto"; message TestMap { message MessageValue { int32 value = 1; + int32 value2 = 2; } enum EnumValue { FOO = 0; diff --git a/src/google/protobuf/compiler/javanano/javanano_map_field.cc b/src/google/protobuf/compiler/javanano/javanano_map_field.cc index 7b14db5d..082573dd 100644 --- a/src/google/protobuf/compiler/javanano/javanano_map_field.cc +++ b/src/google/protobuf/compiler/javanano/javanano_map_field.cc @@ -102,9 +102,9 @@ void SetMapVariables(const Params& params, (*variables)["value_tag"] = SimpleItoa(internal::WireFormat::MakeTag(value)); (*variables)["type_parameters"] = (*variables)["boxed_key_type"] + ", " + (*variables)["boxed_value_type"]; - (*variables)["value_class"] = + (*variables)["value_default"] = value->type() == FieldDescriptor::TYPE_MESSAGE - ? (*variables)["value_type"] + ".class" + ? "new " + (*variables)["value_type"] + "()" : "null"; } } // namespace @@ -137,7 +137,7 @@ GenerateMergingCode(io::Printer* printer) const { " input, this.$name$,\n" " com.google.protobuf.nano.InternalNano.$key_desc_type$,\n" " com.google.protobuf.nano.InternalNano.$value_desc_type$,\n" - " $value_class$,\n" + " $value_default$,\n" " $key_tag$, $value_tag$);\n" "\n"); } -- cgit v1.2.3 From 1536e93349b0f8e703060e5efddb91169c93786d Mon Sep 17 00:00:00 2001 From: Jisi Liu Date: Fri, 6 Feb 2015 15:30:48 -0800 Subject: Implement Equals for nano. --- .../com/google/protobuf/nano/InternalNano.java | 40 +++++++ .../java/com/google/protobuf/nano/NanoTest.java | 123 ++++++++++++++++++++- .../compiler/javanano/javanano_map_field.cc | 5 + 3 files changed, 166 insertions(+), 2 deletions(-) (limited to 'src/google') diff --git a/javanano/src/main/java/com/google/protobuf/nano/InternalNano.java b/javanano/src/main/java/com/google/protobuf/nano/InternalNano.java index a9a459dd..044c30dd 100644 --- a/javanano/src/main/java/com/google/protobuf/nano/InternalNano.java +++ b/javanano/src/main/java/com/google/protobuf/nano/InternalNano.java @@ -491,4 +491,44 @@ public final class InternalNano { } return size; } + + /** + * Checks whether two {@link Map} are equal. We don't use the default equals + * method of {@link Map} because it compares by identity not by content for + * byte arrays. + */ + public static boolean equals(Map a, Map b) { + if (a == b) { + return true; + } + if (a == null) { + return b.size() == 0; + } + if (b == null) { + return a.size() == 0; + } + if (a.size() != b.size()) { + return false; + } + for (Entry entry : a.entrySet()) { + if (!b.containsKey(entry.getKey())) { + return false; + } + if (!equalsMapValue(entry.getValue(), b.get(entry.getKey()))) { + return false; + } + } + return true; + } + + private static boolean equalsMapValue(Object a, Object b) { + if (a == null || b == null) { + throw new IllegalStateException( + "keys and values in maps cannot be null"); + } + if (a instanceof byte[] && b instanceof byte[]) { + return Arrays.equals((byte[]) a, (byte[]) b); + } + return a.equals(b); + } } diff --git a/javanano/src/test/java/com/google/protobuf/nano/NanoTest.java b/javanano/src/test/java/com/google/protobuf/nano/NanoTest.java index 4159e662..aa279da2 100644 --- a/javanano/src/test/java/com/google/protobuf/nano/NanoTest.java +++ b/javanano/src/test/java/com/google/protobuf/nano/NanoTest.java @@ -31,6 +31,7 @@ package com.google.protobuf.nano; import com.google.protobuf.nano.MapTestProto.TestMap; +import com.google.protobuf.nano.MapTestProto.TestMap.MessageValue; import com.google.protobuf.nano.NanoAccessorsOuterClass.TestNanoAccessors; import com.google.protobuf.nano.NanoHasOuterClass.TestAllTypesNanoHas; import com.google.protobuf.nano.NanoOuterClass.TestAllTypesNano; @@ -47,6 +48,7 @@ import junit.framework.TestCase; import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.TreeMap; /** * Test nano runtime. @@ -3824,15 +3826,107 @@ public class NanoTest extends TestCase { assertEquals(0, messageValue.value2); } + public void testMapEquals() throws Exception { + TestMap a = new TestMap(); + TestMap b = new TestMap(); + + // empty and null map fields are equal. + assertTestMapEqual(a, b); + a.int32ToBytesField = new HashMap(); + assertTestMapEqual(a, b); + + a.int32ToInt32Field = new HashMap(); + b.int32ToInt32Field = new HashMap(); + setMap(a.int32ToInt32Field, deepCopy(int32Values), deepCopy(int32Values)); + setMap(b.int32ToInt32Field, deepCopy(int32Values), deepCopy(int32Values)); + assertTestMapEqual(a, b); + + a.int32ToMessageField = + new HashMap(); + b.int32ToMessageField = + new HashMap(); + setMap(a.int32ToMessageField, + deepCopy(int32Values), deepCopy(messageValues)); + setMap(b.int32ToMessageField, + deepCopy(int32Values), deepCopy(messageValues)); + assertTestMapEqual(a, b); + + a.stringToInt32Field = new HashMap(); + b.stringToInt32Field = new HashMap(); + setMap(a.stringToInt32Field, deepCopy(stringValues), deepCopy(int32Values)); + setMap(b.stringToInt32Field, deepCopy(stringValues), deepCopy(int32Values)); + assertTestMapEqual(a, b); + + a.int32ToBytesField = new HashMap(); + b.int32ToBytesField = new HashMap(); + setMap(a.int32ToBytesField, deepCopy(int32Values), deepCopy(bytesValues)); + setMap(b.int32ToBytesField, deepCopy(int32Values), deepCopy(bytesValues)); + assertTestMapEqual(a, b); + + // Make sure the map implementation does not matter. + a.int32ToStringField = new TreeMap(); + b.int32ToStringField = new HashMap(); + setMap(a.int32ToStringField, deepCopy(int32Values), deepCopy(stringValues)); + setMap(b.int32ToStringField, deepCopy(int32Values), deepCopy(stringValues)); + assertTestMapEqual(a, b); + + a.clear(); + b.clear(); + + // unequal cases: different value + a.int32ToInt32Field = new HashMap(); + b.int32ToInt32Field = new HashMap(); + a.int32ToInt32Field.put(1, 1); + b.int32ToInt32Field.put(1, 2); + assertTestMapUnequal(a, b); + // unequal case: additional entry + b.int32ToInt32Field.put(1, 1); + b.int32ToInt32Field.put(2, 1); + assertTestMapUnequal(a, b); + a.int32ToInt32Field.put(2, 1); + assertTestMapEqual(a, b); + + // unequal case: different message value. + a.int32ToMessageField = + new HashMap(); + b.int32ToMessageField = + new HashMap(); + MessageValue va = new MessageValue(); + va.value = 1; + MessageValue vb = new MessageValue(); + vb.value = 1; + a.int32ToMessageField.put(1, va); + b.int32ToMessageField.put(1, vb); + assertTestMapEqual(a, b); + vb.value = 2; + assertTestMapUnequal(a, b); + } + + private static void assertTestMapEqual(TestMap a, TestMap b) + throws Exception { + assertEquals(a.hashCode(), b.hashCode()); + assertTrue(a.equals(b)); + assertTrue(b.equals(a)); + } + + private static void assertTestMapUnequal(TestMap a, TestMap b) + throws Exception { + assertFalse(a.equals(b)); + assertFalse(b.equals(a)); + } + private static final Integer[] int32Values = new Integer[] { 0, 1, -1, Integer.MAX_VALUE, Integer.MIN_VALUE, }; + private static final Long[] int64Values = new Long[] { 0L, 1L, -1L, Long.MAX_VALUE, Long.MIN_VALUE, }; + private static final String[] stringValues = new String[] { "", "hello", "world", "foo", "bar", }; + private static final byte[][] bytesValues = new byte[][] { new byte[] {}, new byte[] {0}, @@ -3840,13 +3934,16 @@ public class NanoTest extends TestCase { new byte[] {127, -128}, new byte[] {'a', 'b', '0', '1'}, }; + private static final Boolean[] boolValues = new Boolean[] { false, true, }; + private static final Integer[] enumValues = new Integer[] { TestMap.FOO, TestMap.BAR, TestMap.BAZ, TestMap.QUX, Integer.MAX_VALUE /* unknown */, }; + private static final TestMap.MessageValue[] messageValues = new TestMap.MessageValue[] { newMapValueMessage(0), @@ -3855,15 +3952,37 @@ public class NanoTest extends TestCase { newMapValueMessage(Integer.MAX_VALUE), newMapValueMessage(Integer.MIN_VALUE), }; + private static TestMap.MessageValue newMapValueMessage(int value) { TestMap.MessageValue result = new TestMap.MessageValue(); result.value = value; return result; } + @SuppressWarnings("unchecked") + private static T[] deepCopy(T[] orig) throws Exception { + if (orig instanceof MessageValue[]) { + MessageValue[] result = new MessageValue[orig.length]; + for (int i = 0; i < orig.length; i++) { + result[i] = new MessageValue(); + MessageNano.mergeFrom( + result[i], MessageNano.toByteArray((MessageValue) orig[i])); + } + return (T[]) result; + } + if (orig instanceof byte[][]) { + byte[][] result = new byte[orig.length][]; + for (int i = 0; i < orig.length; i++) { + byte[] origBytes = (byte[]) orig[i]; + result[i] = Arrays.copyOf(origBytes, origBytes.length); + } + } + return Arrays.copyOf(orig, orig.length); + } + private void setMap(Map map, K[] keys, V[] values) { assert(keys.length == values.length); - for (int i = 0; i < keys.length; ++i) { + for (int i = 0; i < keys.length; i++) { map.put(keys[i], values[i]); } } @@ -3871,7 +3990,7 @@ public class NanoTest extends TestCase { private void assertMapSet( Map map, K[] keys, V[] values) throws Exception { assert(keys.length == values.length); - for (int i = 0; i < values.length; ++i) { + for (int i = 0; i < values.length; i++) { assertEquals(values[i], map.get(keys[i])); } assertEquals(keys.length, map.size()); diff --git a/src/google/protobuf/compiler/javanano/javanano_map_field.cc b/src/google/protobuf/compiler/javanano/javanano_map_field.cc index 082573dd..c816fb3d 100644 --- a/src/google/protobuf/compiler/javanano/javanano_map_field.cc +++ b/src/google/protobuf/compiler/javanano/javanano_map_field.cc @@ -166,6 +166,11 @@ GenerateSerializedSizeCode(io::Printer* printer) const { void MapFieldGenerator:: GenerateEqualsCode(io::Printer* printer) const { + printer->Print(variables_, + "if (!com.google.protobuf.nano.InternalNano.equals(\n" + " this.$name$, other.$name$)) {\n" + " return false;\n" + "}\n"); } void MapFieldGenerator:: -- cgit v1.2.3 From d9a6f27bc333587fad88b3bc1f53b370a016ccd0 Mon Sep 17 00:00:00 2001 From: Jisi Liu Date: Fri, 6 Feb 2015 15:55:04 -0800 Subject: implement hashCode() for JavaNano maps. --- .../java/com/google/protobuf/nano/InternalNano.java | 19 +++++++++++++++++++ .../protobuf/compiler/javanano/javanano_map_field.cc | 3 +++ 2 files changed, 22 insertions(+) (limited to 'src/google') diff --git a/javanano/src/main/java/com/google/protobuf/nano/InternalNano.java b/javanano/src/main/java/com/google/protobuf/nano/InternalNano.java index 044c30dd..f3144652 100644 --- a/javanano/src/main/java/com/google/protobuf/nano/InternalNano.java +++ b/javanano/src/main/java/com/google/protobuf/nano/InternalNano.java @@ -531,4 +531,23 @@ public final class InternalNano { } return a.equals(b); } + + public static int hashCode(Map map) { + if (map == null) { + return 0; + } + int result = 0; + for (Entry entry : map.entrySet()) { + result += hashCodeForMap(entry.getKey()) + ^ hashCodeForMap(entry.getValue()); + } + return result; + } + + private static int hashCodeForMap(Object o) { + if (o instanceof byte[]) { + return Arrays.hashCode((byte[]) o); + } + return o.hashCode(); + } } diff --git a/src/google/protobuf/compiler/javanano/javanano_map_field.cc b/src/google/protobuf/compiler/javanano/javanano_map_field.cc index c816fb3d..117c4f5c 100644 --- a/src/google/protobuf/compiler/javanano/javanano_map_field.cc +++ b/src/google/protobuf/compiler/javanano/javanano_map_field.cc @@ -175,6 +175,9 @@ GenerateEqualsCode(io::Printer* printer) const { void MapFieldGenerator:: GenerateHashCodeCode(io::Printer* printer) const { + printer->Print(variables_, + "result = 31 * result +\n" + " com.google.protobuf.nano.InternalNano.hashCode(this.$name$);\n"); } } // namespace javanano -- cgit v1.2.3 From 12d8a514c84ee8d60e5d8b8b84aa09dad15197f2 Mon Sep 17 00:00:00 2001 From: Jisi Liu Date: Sat, 7 Feb 2015 12:51:36 -0800 Subject: Get the map factory per mergeFrom() call for JavaNano. --- .../src/main/java/com/google/protobuf/nano/InternalNano.java | 5 ++++- src/google/protobuf/compiler/javanano/javanano_helpers.cc | 11 +++++++++++ src/google/protobuf/compiler/javanano/javanano_helpers.h | 2 ++ src/google/protobuf/compiler/javanano/javanano_map_field.cc | 2 +- src/google/protobuf/compiler/javanano/javanano_message.cc | 5 +++++ 5 files changed, 23 insertions(+), 2 deletions(-) (limited to 'src/google') diff --git a/javanano/src/main/java/com/google/protobuf/nano/InternalNano.java b/javanano/src/main/java/com/google/protobuf/nano/InternalNano.java index f3144652..c92ec417 100644 --- a/javanano/src/main/java/com/google/protobuf/nano/InternalNano.java +++ b/javanano/src/main/java/com/google/protobuf/nano/InternalNano.java @@ -30,6 +30,8 @@ package com.google.protobuf.nano; +import com.google.protobuf.nano.MapFactories.MapFactory; + import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.Arrays; @@ -406,12 +408,13 @@ public final class InternalNano { public static final Map mergeMapEntry( CodedInputByteBufferNano input, Map map, + MapFactory mapFactory, int keyType, int valueType, V value, int keyTag, int valueTag) throws IOException { - map = MapFactories.getMapFactory().forMap(map); + map = mapFactory.forMap(map); final int length = input.readRawVarint32(); final int oldLimit = input.pushLimit(length); K key = null; diff --git a/src/google/protobuf/compiler/javanano/javanano_helpers.cc b/src/google/protobuf/compiler/javanano/javanano_helpers.cc index 2149418a..7b23d9f4 100644 --- a/src/google/protobuf/compiler/javanano/javanano_helpers.cc +++ b/src/google/protobuf/compiler/javanano/javanano_helpers.cc @@ -560,6 +560,17 @@ void SetBitOperationVariables(const string name, (*variables)["different_" + name] = GenerateDifferentBit(bitIndex); } +bool HasMapField(const Descriptor* descriptor) { + for (int i = 0; i < descriptor->field_count(); ++i) { + const FieldDescriptor* field = descriptor->field(i); + if (field->type() == FieldDescriptor::TYPE_MESSAGE && + IsMapEntry(field->message_type())) { + return true; + } + } + return false; +} + } // namespace javanano } // namespace compiler } // namespace protobuf diff --git a/src/google/protobuf/compiler/javanano/javanano_helpers.h b/src/google/protobuf/compiler/javanano/javanano_helpers.h index 5d3532b4..b4fee649 100644 --- a/src/google/protobuf/compiler/javanano/javanano_helpers.h +++ b/src/google/protobuf/compiler/javanano/javanano_helpers.h @@ -187,6 +187,8 @@ inline bool IsMapEntry(const Descriptor* descriptor) { descriptor->file()->syntax() == FileDescriptor::SYNTAX_PROTO3; } +bool HasMapField(const Descriptor* descriptor); + } // namespace javanano } // namespace compiler } // namespace protobuf diff --git a/src/google/protobuf/compiler/javanano/javanano_map_field.cc b/src/google/protobuf/compiler/javanano/javanano_map_field.cc index 117c4f5c..83b2b0ce 100644 --- a/src/google/protobuf/compiler/javanano/javanano_map_field.cc +++ b/src/google/protobuf/compiler/javanano/javanano_map_field.cc @@ -134,7 +134,7 @@ void MapFieldGenerator:: GenerateMergingCode(io::Printer* printer) const { printer->Print(variables_, "this.$name$ = com.google.protobuf.nano.InternalNano.mergeMapEntry(\n" - " input, this.$name$,\n" + " input, this.$name$, mapFactory,\n" " com.google.protobuf.nano.InternalNano.$key_desc_type$,\n" " com.google.protobuf.nano.InternalNano.$value_desc_type$,\n" " $value_default$,\n" diff --git a/src/google/protobuf/compiler/javanano/javanano_message.cc b/src/google/protobuf/compiler/javanano/javanano_message.cc index 5454d9be..da7c656f 100644 --- a/src/google/protobuf/compiler/javanano/javanano_message.cc +++ b/src/google/protobuf/compiler/javanano/javanano_message.cc @@ -345,6 +345,11 @@ void MessageGenerator::GenerateMergeFromMethods(io::Printer* printer) { "classname", descriptor_->name()); printer->Indent(); + if (HasMapField(descriptor_)) { + printer->Print( + "com.google.protobuf.nano.MapFactories.MapFactory mapFactory =\n" + " com.google.protobuf.nano.MapFactories.getMapFactory();\n"); + } printer->Print( "while (true) {\n"); -- cgit v1.2.3