From bcf45045f229edab8c2589c1f561e7b7495a763b Mon Sep 17 00:00:00 2001 From: Brian Duff Date: Wed, 1 Oct 2014 13:33:40 -0700 Subject: Add clone() method support for nano. Upstreamed from Another Place (cr/57247854). Change-Id: I2aaf59544c0f5ae21a51891d8a5eeda1dc722c90 --- java/pom.xml | 6 +-- .../protobuf/nano/ExtendableMessageNano.java | 7 +++ .../java/com/google/protobuf/nano/FieldArray.java | 17 ++++++- .../java/com/google/protobuf/nano/FieldData.java | 52 +++++++++++++++++++++- .../com/google/protobuf/nano/InternalNano.java | 8 ++++ .../java/com/google/protobuf/nano/MessageNano.java | 8 ++++ .../com/google/protobuf/nano/UnknownFieldData.java | 4 ++ .../test/java/com/google/protobuf/NanoTest.java | 20 +++++++++ .../compiler/javanano/javanano_enum_field.cc | 8 ++++ .../compiler/javanano/javanano_enum_field.h | 1 + .../protobuf/compiler/javanano/javanano_field.h | 1 + .../compiler/javanano/javanano_generator.cc | 2 + .../protobuf/compiler/javanano/javanano_message.cc | 44 ++++++++++++++++-- .../protobuf/compiler/javanano/javanano_message.h | 1 + .../compiler/javanano/javanano_message_field.cc | 21 +++++++++ .../compiler/javanano/javanano_message_field.h | 2 + .../protobuf/compiler/javanano/javanano_params.h | 11 ++++- .../compiler/javanano/javanano_primitive_field.cc | 8 ++++ .../compiler/javanano/javanano_primitive_field.h | 1 + src/google/protobuf/unittest_extension_nano.proto | 1 + 20 files changed, 213 insertions(+), 10 deletions(-) diff --git a/java/pom.xml b/java/pom.xml index d263bec..a673f2d 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -152,19 +152,19 @@ - + - + - + diff --git a/java/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java b/java/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java index 46cd86f..8244164 100644 --- a/java/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java +++ b/java/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java @@ -184,4 +184,11 @@ public abstract class ExtendableMessageNano> return (unknownFieldData == null || unknownFieldData.isEmpty() ? 0 : unknownFieldData.hashCode()); } + + @Override + public M clone() throws CloneNotSupportedException { + M cloned = (M) super.clone(); + InternalNano.cloneUnknownFieldData(this, cloned); + return cloned; + } } diff --git a/java/src/main/java/com/google/protobuf/nano/FieldArray.java b/java/src/main/java/com/google/protobuf/nano/FieldArray.java index ab923a4..473c161 100644 --- a/java/src/main/java/com/google/protobuf/nano/FieldArray.java +++ b/java/src/main/java/com/google/protobuf/nano/FieldArray.java @@ -37,7 +37,7 @@ package com.google.protobuf.nano; * * Based on {@link android.support.v4.util.SpareArrayCompat}. */ -class FieldArray { +class FieldArray implements Cloneable { private static final FieldData DELETED = new FieldData(); private boolean mGarbage = false; @@ -270,4 +270,19 @@ class FieldArray { } return true; } + + @Override + public final FieldArray clone() { + // Trigger GC so we compact and don't copy DELETED elements. + int size = size(); + FieldArray clone = new FieldArray(size); + System.arraycopy(mFieldNumbers, 0, clone.mFieldNumbers, 0, size); + for (int i = 0; i < size; i++) { + if (mData[i] != null) { + clone.mData[i] = mData[i].clone(); + } + } + clone.mSize = size; + return clone; + } } diff --git a/java/src/main/java/com/google/protobuf/nano/FieldData.java b/java/src/main/java/com/google/protobuf/nano/FieldData.java index e5b69aa..20a5142 100644 --- a/java/src/main/java/com/google/protobuf/nano/FieldData.java +++ b/java/src/main/java/com/google/protobuf/nano/FieldData.java @@ -39,7 +39,7 @@ import java.util.List; * Stores unknown fields. These might be extensions or fields that the generated API doesn't * know about yet. */ -class FieldData { +class FieldData implements Cloneable { private Extension cachedExtension; private Object value; /** The serialised values for this object. Will be cleared if getValue is called */ @@ -187,4 +187,54 @@ class FieldData { return result; } + @Override + public final FieldData clone() { + FieldData clone = new FieldData(); + try { + clone.cachedExtension = cachedExtension; + if (unknownFieldData == null) { + clone.unknownFieldData = null; + } else { + clone.unknownFieldData.addAll(unknownFieldData); + } + + // Whether we need to deep clone value depends on its type. Primitive reference types + // (e.g. Integer, Long etc.) are ok, since they're immutable. We need to clone arrays + // and messages. + if (value == null) { + // No cloning required. + } else if (value instanceof MessageNano) { + clone.value = ((MessageNano) value).clone(); + } else if (value instanceof byte[]) { + clone.value = ((byte[]) value).clone(); + } else if (value instanceof byte[][]) { + byte[][] valueArray = (byte[][]) value; + byte[][] cloneArray = new byte[valueArray.length][]; + clone.value = cloneArray; + for (int i = 0; i < valueArray.length; i++) { + cloneArray[i] = valueArray[i].clone(); + } + } else if (value instanceof boolean[]) { + clone.value = ((boolean[]) value).clone(); + } else if (value instanceof int[]) { + clone.value = ((int[]) value).clone(); + } else if (value instanceof long[]) { + clone.value = ((long[]) value).clone(); + } else if (value instanceof float[]) { + clone.value = ((float[]) value).clone(); + } else if (value instanceof double[]) { + clone.value = ((double[]) value).clone(); + } else if (value instanceof MessageNano[]) { + MessageNano[] valueArray = (MessageNano[]) value; + MessageNano[] cloneArray = new MessageNano[valueArray.length]; + clone.value = cloneArray; + for (int i = 0; i < valueArray.length; i++) { + cloneArray[i] = valueArray[i].clone(); + } + } + return clone; + } catch (CloneNotSupportedException e) { + throw new AssertionError(e); + } + } } diff --git a/java/src/main/java/com/google/protobuf/nano/InternalNano.java b/java/src/main/java/com/google/protobuf/nano/InternalNano.java index 90ca11d..c4adfa5 100644 --- a/java/src/main/java/com/google/protobuf/nano/InternalNano.java +++ b/java/src/main/java/com/google/protobuf/nano/InternalNano.java @@ -330,4 +330,12 @@ public final class InternalNano { return result; } + // This avoids having to make FieldArray public. + public static void cloneUnknownFieldData(ExtendableMessageNano original, + ExtendableMessageNano cloned) { + if (original.unknownFieldData != null) { + cloned.unknownFieldData = (FieldArray) original.unknownFieldData.clone(); + } + } + } diff --git a/java/src/main/java/com/google/protobuf/nano/MessageNano.java b/java/src/main/java/com/google/protobuf/nano/MessageNano.java index 164f317..ea91b21 100644 --- a/java/src/main/java/com/google/protobuf/nano/MessageNano.java +++ b/java/src/main/java/com/google/protobuf/nano/MessageNano.java @@ -187,4 +187,12 @@ public abstract class MessageNano { public String toString() { return MessageNanoPrinter.print(this); } + + /** + * Provides support for cloning. This only works if you specify the generate_clone method. + */ + @Override + public MessageNano clone() throws CloneNotSupportedException { + return (MessageNano) super.clone(); + } } diff --git a/java/src/main/java/com/google/protobuf/nano/UnknownFieldData.java b/java/src/main/java/com/google/protobuf/nano/UnknownFieldData.java index 2032e1a..bf34bed 100644 --- a/java/src/main/java/com/google/protobuf/nano/UnknownFieldData.java +++ b/java/src/main/java/com/google/protobuf/nano/UnknownFieldData.java @@ -42,6 +42,10 @@ import java.util.Arrays; final class UnknownFieldData { final int tag; + /** + * Important: this should be treated as immutable, even though it's possible + * to change the array values. + */ final byte[] bytes; UnknownFieldData(int tag, byte[] bytes) { diff --git a/java/src/test/java/com/google/protobuf/NanoTest.java b/java/src/test/java/com/google/protobuf/NanoTest.java index aa555c8..8d364c1 100644 --- a/java/src/test/java/com/google/protobuf/NanoTest.java +++ b/java/src/test/java/com/google/protobuf/NanoTest.java @@ -2999,6 +2999,10 @@ public class NanoTest extends TestCase { assertTrue(Arrays.equals(floats, message.getExtension(RepeatedExtensions.repeatedFloat))); assertTrue(Arrays.equals(doubles, message.getExtension(RepeatedExtensions.repeatedDouble))); assertTrue(Arrays.equals(enums, message.getExtension(RepeatedExtensions.repeatedEnum))); + + // Clone the message and ensure it's still equal. + Extensions.ExtendableMessage clone = message.clone(); + assertEquals(clone, message); } public void testNullExtensions() throws Exception { @@ -3808,6 +3812,22 @@ public class NanoTest extends TestCase { assertTrue(Arrays.equals(nonPacked.enums, packed.enums)); } + public void testClone() throws Exception { + // A simple message. + AnotherMessage anotherMessage = new AnotherMessage(); + anotherMessage.string = "Hello"; + anotherMessage.value = true; + anotherMessage.integers = new int[] { 1, 2, 3 }; + + AnotherMessage clone = anotherMessage.clone(); + assertEquals(clone, anotherMessage); + + // Verify it was a deep clone - changes to the clone shouldn't affect the + // original. + clone.integers[1] = 100; + assertFalse(clone.equals(anotherMessage)); + } + private void assertHasWireData(MessageNano message, boolean expected) { byte[] bytes = MessageNano.toByteArray(message); int wireLength = bytes.length; diff --git a/src/google/protobuf/compiler/javanano/javanano_enum_field.cc b/src/google/protobuf/compiler/javanano/javanano_enum_field.cc index 8a59d32..99b316b 100644 --- a/src/google/protobuf/compiler/javanano/javanano_enum_field.cc +++ b/src/google/protobuf/compiler/javanano/javanano_enum_field.cc @@ -499,6 +499,14 @@ GenerateSerializedSizeCode(io::Printer* printer) const { } void RepeatedEnumFieldGenerator:: +GenerateFixClonedCode(io::Printer* printer) const { + printer->Print(variables_, + "if (this.$name$ != null && this.$name$.length > 0) {\n" + " cloned.$name$ = this.$name$.clone();\n" + "}\n"); +} + +void RepeatedEnumFieldGenerator:: GenerateEqualsCode(io::Printer* printer) const { printer->Print(variables_, "if (!com.google.protobuf.nano.InternalNano.equals(\n" diff --git a/src/google/protobuf/compiler/javanano/javanano_enum_field.h b/src/google/protobuf/compiler/javanano/javanano_enum_field.h index 55bf635..8622463 100644 --- a/src/google/protobuf/compiler/javanano/javanano_enum_field.h +++ b/src/google/protobuf/compiler/javanano/javanano_enum_field.h @@ -106,6 +106,7 @@ class RepeatedEnumFieldGenerator : public FieldGenerator { void GenerateSerializedSizeCode(io::Printer* printer) const; void GenerateEqualsCode(io::Printer* printer) const; void GenerateHashCodeCode(io::Printer* printer) const; + void GenerateFixClonedCode(io::Printer* printer) const; private: void GenerateRepeatedDataSizeCode(io::Printer* printer) const; diff --git a/src/google/protobuf/compiler/javanano/javanano_field.h b/src/google/protobuf/compiler/javanano/javanano_field.h index 61fc621..e015259 100644 --- a/src/google/protobuf/compiler/javanano/javanano_field.h +++ b/src/google/protobuf/compiler/javanano/javanano_field.h @@ -82,6 +82,7 @@ class FieldGenerator { virtual void GenerateSerializedSizeCode(io::Printer* printer) const = 0; virtual void GenerateEqualsCode(io::Printer* printer) const = 0; virtual void GenerateHashCodeCode(io::Printer* printer) const = 0; + virtual void GenerateFixClonedCode(io::Printer* printer) const {} protected: const Params& params_; diff --git a/src/google/protobuf/compiler/javanano/javanano_generator.cc b/src/google/protobuf/compiler/javanano/javanano_generator.cc index b1e75e3..9ca06cd 100644 --- a/src/google/protobuf/compiler/javanano/javanano_generator.cc +++ b/src/google/protobuf/compiler/javanano/javanano_generator.cc @@ -152,6 +152,8 @@ bool JavaNanoGenerator::Generate(const FileDescriptor* file, params.set_ignore_services(option_value == "true"); } else if (option_name == "parcelable_messages") { params.set_parcelable_messages(option_value == "true"); + } else if (option_name == "generate_clone") { + params.set_generate_clone(option_value == "true"); } else { *error = "Ignore unknown javanano generator option: " + option_name; } diff --git a/src/google/protobuf/compiler/javanano/javanano_message.cc b/src/google/protobuf/compiler/javanano/javanano_message.cc index 7c52ca3..03c7e18 100644 --- a/src/google/protobuf/compiler/javanano/javanano_message.cc +++ b/src/google/protobuf/compiler/javanano/javanano_message.cc @@ -134,18 +134,23 @@ void MessageGenerator::Generate(io::Printer* printer) { } if (params_.store_unknown_fields() && params_.parcelable_messages()) { printer->Print( - " com.google.protobuf.nano.android.ParcelableExtendableMessageNano<$classname$> {\n", + " com.google.protobuf.nano.android.ParcelableExtendableMessageNano<$classname$>", "classname", descriptor_->name()); } else if (params_.store_unknown_fields()) { printer->Print( - " com.google.protobuf.nano.ExtendableMessageNano<$classname$> {\n", + " com.google.protobuf.nano.ExtendableMessageNano<$classname$>", "classname", descriptor_->name()); } else if (params_.parcelable_messages()) { printer->Print( - " com.google.protobuf.nano.android.ParcelableMessageNano {\n"); + " com.google.protobuf.nano.android.ParcelableMessageNano"); } else { printer->Print( - " com.google.protobuf.nano.MessageNano {\n"); + " com.google.protobuf.nano.MessageNano"); + } + if (params_.generate_clone()) { + printer->Print(" implements java.lang.Cloneable {\n"); + } else { + printer->Print(" {\n"); } printer->Indent(); @@ -265,6 +270,10 @@ void MessageGenerator::Generate(io::Printer* printer) { GenerateClear(printer); + if (params_.generate_clone()) { + GenerateClone(printer); + } + if (params_.generate_equals()) { GenerateEquals(printer); GenerateHashCode(printer); @@ -478,6 +487,33 @@ void MessageGenerator::GenerateClear(io::Printer* printer) { "}\n"); } +void MessageGenerator::GenerateClone(io::Printer* printer) { + printer->Print( + "@Override\n" + "public $classname$ clone() {\n", + "classname", descriptor_->name()); + printer->Indent(); + + printer->Print( + "$classname$ cloned;\n" + "try {\n" + " cloned = ($classname$) super.clone();\n" + "} catch (java.lang.CloneNotSupportedException e) {\n" + " throw new java.lang.AssertionError(e);\n" + "}\n", + "classname", descriptor_->name()); + + for (int i = 0; i < descriptor_->field_count(); i++) { + field_generators_.get(descriptor_->field(i)).GenerateFixClonedCode(printer); + } + + printer->Outdent(); + printer->Print( + " return cloned;\n" + "}\n" + "\n"); +} + void MessageGenerator::GenerateEquals(io::Printer* printer) { // Don't override if there are no fields. We could generate an // equals method that compares types, but often empty messages diff --git a/src/google/protobuf/compiler/javanano/javanano_message.h b/src/google/protobuf/compiler/javanano/javanano_message.h index f87f84f..870f3bb 100644 --- a/src/google/protobuf/compiler/javanano/javanano_message.h +++ b/src/google/protobuf/compiler/javanano/javanano_message.h @@ -79,6 +79,7 @@ class MessageGenerator { void GenerateClear(io::Printer* printer); void GenerateEquals(io::Printer* printer); void GenerateHashCode(io::Printer* printer); + void GenerateClone(io::Printer* printer); const Params& params_; const Descriptor* descriptor_; diff --git a/src/google/protobuf/compiler/javanano/javanano_message_field.cc b/src/google/protobuf/compiler/javanano/javanano_message_field.cc index a46081d..4752644 100644 --- a/src/google/protobuf/compiler/javanano/javanano_message_field.cc +++ b/src/google/protobuf/compiler/javanano/javanano_message_field.cc @@ -127,6 +127,14 @@ GenerateSerializedSizeCode(io::Printer* printer) const { } void MessageFieldGenerator:: +GenerateFixClonedCode(io::Printer* printer) const { + printer->Print(variables_, + "if (this.$name$ != null) {\n" + " cloned.$name$ = this.$name$.clone();\n" + "}\n"); +} + +void MessageFieldGenerator:: GenerateEqualsCode(io::Printer* printer) const { printer->Print(variables_, "if (this.$name$ == null) { \n" @@ -238,6 +246,19 @@ GenerateSerializedSizeCode(io::Printer* printer) const { } void RepeatedMessageFieldGenerator:: +GenerateFixClonedCode(io::Printer* printer) const { + printer->Print(variables_, + "if (this.$name$ != null && this.$name$.length > 0) {\n" + " cloned.$name$ = new $type$[this.$name$.length];\n" + " for (int i = 0; i < this.$name$.length; i++) {\n" + " if (this.$name$[i] != null) {\n" + " cloned.$name$[i] = this.$name$[i].clone();\n" + " }\n" + " }\n" + "}\n"); +} + +void RepeatedMessageFieldGenerator:: GenerateEqualsCode(io::Printer* printer) const { printer->Print(variables_, "if (!com.google.protobuf.nano.InternalNano.equals(\n" diff --git a/src/google/protobuf/compiler/javanano/javanano_message_field.h b/src/google/protobuf/compiler/javanano/javanano_message_field.h index e94a37b..1edff25 100644 --- a/src/google/protobuf/compiler/javanano/javanano_message_field.h +++ b/src/google/protobuf/compiler/javanano/javanano_message_field.h @@ -58,6 +58,7 @@ class MessageFieldGenerator : public FieldGenerator { void GenerateSerializedSizeCode(io::Printer* printer) const; void GenerateEqualsCode(io::Printer* printer) const; void GenerateHashCodeCode(io::Printer* printer) const; + void GenerateFixClonedCode(io::Printer* printer) const; private: const FieldDescriptor* descriptor_; @@ -80,6 +81,7 @@ class RepeatedMessageFieldGenerator : public FieldGenerator { void GenerateSerializedSizeCode(io::Printer* printer) const; void GenerateEqualsCode(io::Printer* printer) const; void GenerateHashCodeCode(io::Printer* printer) const; + void GenerateFixClonedCode(io::Printer* printer) const; private: const FieldDescriptor* descriptor_; diff --git a/src/google/protobuf/compiler/javanano/javanano_params.h b/src/google/protobuf/compiler/javanano/javanano_params.h index 4691f36..77bc717 100644 --- a/src/google/protobuf/compiler/javanano/javanano_params.h +++ b/src/google/protobuf/compiler/javanano/javanano_params.h @@ -66,6 +66,7 @@ class Params { bool parcelable_messages_; bool reftypes_primitive_enums_; bool generate_clear_; + bool generate_clone_; public: Params(const string & base_name) : @@ -81,7 +82,8 @@ class Params { ignore_services_(false), parcelable_messages_(false), reftypes_primitive_enums_(false), - generate_clear_(true) { + generate_clear_(true), + generate_clone_(false) { } const string& base_name() const { @@ -231,6 +233,13 @@ class Params { bool generate_clear() const { return generate_clear_; } + + void set_generate_clone(bool value) { + generate_clone_ = value; + } + bool generate_clone() const { + return generate_clone_; + } }; } // 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 a3bc3a8..bda52c6 100644 --- a/src/google/protobuf/compiler/javanano/javanano_primitive_field.cc +++ b/src/google/protobuf/compiler/javanano/javanano_primitive_field.cc @@ -385,6 +385,14 @@ GenerateSerializedSizeCode(io::Printer* printer) const { } } +void RepeatedPrimitiveFieldGenerator:: +GenerateFixClonedCode(io::Printer* printer) const { + printer->Print(variables_, + "if (this.$name$ != null && this.$name$.length > 0) {\n" + " cloned.$name$ = this.$name$.clone();\n" + "}\n"); +} + void PrimitiveFieldGenerator:: GenerateEqualsCode(io::Printer* printer) const { // We define equality as serialized form equality. If generate_has(), diff --git a/src/google/protobuf/compiler/javanano/javanano_primitive_field.h b/src/google/protobuf/compiler/javanano/javanano_primitive_field.h index c04a19b..5ace0de 100644 --- a/src/google/protobuf/compiler/javanano/javanano_primitive_field.h +++ b/src/google/protobuf/compiler/javanano/javanano_primitive_field.h @@ -108,6 +108,7 @@ class RepeatedPrimitiveFieldGenerator : public FieldGenerator { void GenerateSerializedSizeCode(io::Printer* printer) const; void GenerateEqualsCode(io::Printer* printer) const; void GenerateHashCodeCode(io::Printer* printer) const; + void GenerateFixClonedCode(io::Printer* printer) const; private: void GenerateRepeatedDataSizeCode(io::Printer* printer) const; diff --git a/src/google/protobuf/unittest_extension_nano.proto b/src/google/protobuf/unittest_extension_nano.proto index d1c5766..ca56b3d 100644 --- a/src/google/protobuf/unittest_extension_nano.proto +++ b/src/google/protobuf/unittest_extension_nano.proto @@ -16,6 +16,7 @@ enum AnEnum { message AnotherMessage { optional string string = 1; optional bool value = 2; + repeated int32 integers = 3; } message ContainerMessage { -- cgit v1.1