From cc8ca5b6a5478b40546d4206392eb1471454460d Mon Sep 17 00:00:00 2001 From: Bo Yang Date: Mon, 19 Sep 2016 13:45:07 -0700 Subject: Integrate internal changes --- js/binary/proto_test.js | 2 +- js/debug.js | 3 +- js/debug_test.js | 2 +- js/map.js | 22 +-- js/maps_test.js | 359 ++++++++++++++++++++++++++++++++++++++++++++++++ js/message.js | 154 +++++++++++++++++---- js/message_test.js | 55 ++------ js/proto3_test.js | 2 +- 8 files changed, 514 insertions(+), 85 deletions(-) create mode 100755 js/maps_test.js (limited to 'js') diff --git a/js/binary/proto_test.js b/js/binary/proto_test.js index 14d0f42e..26e1d30f 100644 --- a/js/binary/proto_test.js +++ b/js/binary/proto_test.js @@ -496,7 +496,7 @@ describe('protoBinaryTest', function() { msg.setRepeatedBytesList([BYTES_B64, BYTES_B64]); assertGetters(); - msg.setRepeatedBytesList(null); + msg.setRepeatedBytesList([]); assertEquals(0, msg.getRepeatedBytesList().length); assertEquals(0, msg.getRepeatedBytesList_asB64().length); assertEquals(0, msg.getRepeatedBytesList_asU8().length); diff --git a/js/debug.js b/js/debug.js index 3c1ada02..46b24853 100644 --- a/js/debug.js +++ b/js/debug.js @@ -95,8 +95,7 @@ jspb.debug.dump_ = function(thing) { if (match && name != 'getExtension' && name != 'getJsPbMessageId') { var has = 'has' + match[1]; - if (!thing[has] || thing[has]()) - { + if (!thing[has] || thing[has]()) { var val = thing[name](); object[jspb.debug.formatFieldName_(match[1])] = jspb.debug.dump_(val); } diff --git a/js/debug_test.js b/js/debug_test.js index 01cbf891..702cc76e 100644 --- a/js/debug_test.js +++ b/js/debug_test.js @@ -65,7 +65,7 @@ describe('debugTest', function() { 'aBoolean': true }, jspb.debug.dump(message)); - message.setAString(undefined); + message.clearAString(); assertObjectEquals({ $name: 'proto.jspb.test.Simple1', diff --git a/js/map.js b/js/map.js index e6406a60..0f8401c4 100644 --- a/js/map.js +++ b/js/map.js @@ -103,7 +103,10 @@ jspb.Map.prototype.toArray = function() { var m = this.map_; for (var p in m) { if (Object.prototype.hasOwnProperty.call(m, p)) { - m[p].valueWrapper.toArray(); + var valueWrapper = /** @type {?jspb.Message} */ (m[p].valueWrapper); + if (valueWrapper) { + valueWrapper.toArray(); + } } } } @@ -343,13 +346,13 @@ jspb.Map.prototype.has = function(key) { * number. * @param {number} fieldNumber * @param {!jspb.BinaryWriter} writer - * @param {function(this:jspb.BinaryWriter,number,K)=} keyWriterFn + * @param {!function(this:jspb.BinaryWriter,number,K)} keyWriterFn * The method on BinaryWriter that writes type K to the stream. - * @param {function(this:jspb.BinaryWriter,number,V)| - * function(this:jspb.BinaryReader,V,?)=} valueWriterFn + * @param {!function(this:jspb.BinaryWriter,number,V)| + * function(this:jspb.BinaryReader,V,?)} valueWriterFn * The method on BinaryWriter that writes type V to the stream. May be * writeMessage, in which case the second callback arg form is used. - * @param {?function(V,!jspb.BinaryWriter)=} opt_valueWriterCallback + * @param {function(V,!jspb.BinaryWriter)=} opt_valueWriterCallback * The BinaryWriter serialization callback for type V, if V is a message * type. */ @@ -376,14 +379,15 @@ jspb.Map.prototype.serializeBinary = function( * Read one key/value message from the given BinaryReader. Compatible as the * `reader` callback parameter to jspb.BinaryReader.readMessage, to be called * when a key/value pair submessage is encountered. + * @template K, V * @param {!jspb.Map} map * @param {!jspb.BinaryReader} reader - * @param {function(this:jspb.BinaryReader):K=} keyReaderFn + * @param {!function(this:jspb.BinaryReader):K} keyReaderFn * The method on BinaryReader that reads type K from the stream. * - * @param {function(this:jspb.BinaryReader):V| - * function(this:jspb.BinaryReader,V, - * function(V,!jspb.BinaryReader))=} valueReaderFn + * @param {!function(this:jspb.BinaryReader):V| + * function(this:jspb.BinaryReader,V, + * function(V,!jspb.BinaryReader))} valueReaderFn * The method on BinaryReader that reads type V from the stream. May be * readMessage, in which case the second callback arg form is used. * diff --git a/js/maps_test.js b/js/maps_test.js new file mode 100755 index 00000000..3ffb510b --- /dev/null +++ b/js/maps_test.js @@ -0,0 +1,359 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 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. + +goog.require('goog.testing.asserts'); +goog.require('goog.userAgent'); +goog.require('proto.jspb.test.MapValueEnum'); +goog.require('proto.jspb.test.MapValueMessage'); +goog.require('proto.jspb.test.MapValueMessageNoBinary'); +goog.require('proto.jspb.test.TestMapFields'); +goog.require('proto.jspb.test.TestMapFieldsNoBinary'); + +/** + * Helper: check that the given map has exactly this set of (sorted) entries. + * @param {!jspb.Map} map + * @param {!Array>} entries + */ +function checkMapEquals(map, entries) { + var arr = map.toArray(); + assertEquals(arr.length, entries.length); + for (var i = 0; i < arr.length; i++) { + assertElementsEquals(arr[i], entries[i]); + } +} + +/** + * Converts an ES6 iterator to an array. + * @template T + * @param {!Iterator} iter an iterator + * @return {!Array} + */ +function toArray(iter) { + var arr = []; + while (true) { + var val = iter.next(); + if (val.done) { + break; + } + arr.push(val.value); + } + return arr; +} + + +/** + * Helper: generate test methods for this TestMapFields class. + * @param {?} msgInfo + * @param {?} submessageCtor + * @param {!string} suffix + */ +function makeTests(msgInfo, submessageCtor, suffix) { + /** + * Helper: fill all maps on a TestMapFields. + * @param {?} msg + */ + var fillMapFields = function(msg) { + msg.getMapStringStringMap().set('asdf', 'jkl;').set('key 2', 'hello world'); + msg.getMapStringInt32Map().set('a', 1).set('b', -2); + msg.getMapStringInt64Map().set('c', 0x100000000).set('d', 0x200000000); + msg.getMapStringBoolMap().set('e', true).set('f', false); + msg.getMapStringDoubleMap().set('g', 3.14159).set('h', 2.71828); + msg.getMapStringEnumMap() + .set('i', proto.jspb.test.MapValueEnum.MAP_VALUE_BAR) + .set('j', proto.jspb.test.MapValueEnum.MAP_VALUE_BAZ); + msg.getMapStringMsgMap() + .set('k', new submessageCtor()) + .set('l', new submessageCtor()); + msg.getMapStringMsgMap().get('k').setFoo(42); + msg.getMapStringMsgMap().get('l').setFoo(84); + msg.getMapInt32StringMap().set(-1, 'a').set(42, 'b'); + msg.getMapInt64StringMap().set(0x123456789abc, 'c').set(0xcba987654321, 'd'); + msg.getMapBoolStringMap().set(false, 'e').set(true, 'f'); + }; + + /** + * Helper: check all maps on a TestMapFields. + * @param {?} msg + */ + var checkMapFields = function(msg) { + checkMapEquals(msg.getMapStringStringMap(), [ + ['asdf', 'jkl;'], + ['key 2', 'hello world'] + ]); + checkMapEquals(msg.getMapStringInt32Map(), [ + ['a', 1], + ['b', -2] + ]); + checkMapEquals(msg.getMapStringInt64Map(), [ + ['c', 0x100000000], + ['d', 0x200000000] + ]); + checkMapEquals(msg.getMapStringBoolMap(), [ + ['e', true], + ['f', false] + ]); + checkMapEquals(msg.getMapStringDoubleMap(), [ + ['g', 3.14159], + ['h', 2.71828] + ]); + checkMapEquals(msg.getMapStringEnumMap(), [ + ['i', proto.jspb.test.MapValueEnum.MAP_VALUE_BAR], + ['j', proto.jspb.test.MapValueEnum.MAP_VALUE_BAZ] + ]); + checkMapEquals(msg.getMapInt32StringMap(), [ + [-1, 'a'], + [42, 'b'] + ]); + checkMapEquals(msg.getMapInt64StringMap(), [ + [0x123456789abc, 'c'], + [0xcba987654321, 'd'] + ]); + checkMapEquals(msg.getMapBoolStringMap(), [ + [false, 'e'], + [true, 'f'] + ]); + + assertEquals(msg.getMapStringMsgMap().getLength(), 2); + assertEquals(msg.getMapStringMsgMap().get('k').getFoo(), 42); + assertEquals(msg.getMapStringMsgMap().get('l').getFoo(), 84); + + var entries = toArray(msg.getMapStringMsgMap().entries()); + assertEquals(entries.length, 2); + entries.forEach(function(entry) { + var key = entry[0]; + var val = entry[1]; + assert(val === msg.getMapStringMsgMap().get(key)); + }); + + msg.getMapStringMsgMap().forEach(function(val, key) { + assert(val === msg.getMapStringMsgMap().get(key)); + }); + }; + + it('testMapStringStringField' + suffix, function() { + var msg = new msgInfo.constructor(); + assertEquals(msg.getMapStringStringMap().getLength(), 0); + assertEquals(msg.getMapStringInt32Map().getLength(), 0); + assertEquals(msg.getMapStringInt64Map().getLength(), 0); + assertEquals(msg.getMapStringBoolMap().getLength(), 0); + assertEquals(msg.getMapStringDoubleMap().getLength(), 0); + assertEquals(msg.getMapStringEnumMap().getLength(), 0); + assertEquals(msg.getMapStringMsgMap().getLength(), 0); + + // Re-create to clear out any internally-cached wrappers, etc. + msg = new msgInfo.constructor(); + var m = msg.getMapStringStringMap(); + assertEquals(m.has('asdf'), false); + assertEquals(m.get('asdf'), undefined); + m.set('asdf', 'hello world'); + assertEquals(m.has('asdf'), true); + assertEquals(m.get('asdf'), 'hello world'); + m.set('jkl;', 'key 2'); + assertEquals(m.has('jkl;'), true); + assertEquals(m.get('jkl;'), 'key 2'); + assertEquals(m.getLength(), 2); + var it = m.entries(); + assertElementsEquals(it.next().value, ['asdf', 'hello world']); + assertElementsEquals(it.next().value, ['jkl;', 'key 2']); + assertEquals(it.next().done, true); + checkMapEquals(m, [ + ['asdf', 'hello world'], + ['jkl;', 'key 2'] + ]); + m.del('jkl;'); + assertEquals(m.has('jkl;'), false); + assertEquals(m.get('jkl;'), undefined); + assertEquals(m.getLength(), 1); + it = m.keys(); + assertEquals(it.next().value, 'asdf'); + assertEquals(it.next().done, true); + it = m.values(); + assertEquals(it.next().value, 'hello world'); + assertEquals(it.next().done, true); + + var count = 0; + m.forEach(function(value, key, map) { + assertEquals(map, m); + assertEquals(key, 'asdf'); + assertEquals(value, 'hello world'); + count++; + }); + assertEquals(count, 1); + + m.clear(); + assertEquals(m.getLength(), 0); + }); + + + /** + * Tests operations on maps with all key and value types. + */ + it('testAllMapTypes' + suffix, function() { + var msg = new msgInfo.constructor(); + fillMapFields(msg); + checkMapFields(msg); + }); + + + if (msgInfo.deserializeBinary) { + /** + * Tests serialization and deserialization in binary format. + */ + it('testBinaryFormat' + suffix, function() { + if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(10)) { + // IE8/9 currently doesn't support binary format because they lack + // TypedArray. + return; + } + + // Check that the format is correct. + var msg = new msgInfo.constructor(); + msg.getMapStringStringMap().set('A', 'a'); + var serialized = msg.serializeBinary(); + var expectedSerialized = [ + 0x0a, 0x6, // field 1 (map_string_string), delimited, length 6 + 0x0a, 0x1, // field 1 in submessage (key), delimited, length 1 + 0x41, // ASCII 'A' + 0x12, 0x1, // field 2 in submessage (value), delimited, length 1 + 0x61 // ASCII 'a' + ]; + assertEquals(serialized.length, expectedSerialized.length); + for (var i = 0; i < serialized.length; i++) { + assertEquals(serialized[i], expectedSerialized[i]); + } + + // Check that all map fields successfully round-trip. + msg = new msgInfo.constructor(); + fillMapFields(msg); + serialized = msg.serializeBinary(); + var decoded = msgInfo.deserializeBinary(serialized); + checkMapFields(decoded); + }); + } + + + /** + * Tests serialization and deserialization in JSPB format. + */ + it('testJSPBFormat' + suffix, function() { + var msg = new msgInfo.constructor(); + fillMapFields(msg); + var serialized = msg.serialize(); + var decoded = msgInfo.deserialize(serialized); + checkMapFields(decoded); + }); + + /** + * Tests serialization and deserialization in JSPB format, when there is + * a submessage that also contains map entries. This tests recursive + * sync. + */ + it('testJSPBFormatNested' + suffix, function() { + var submsg = new msgInfo.constructor(); + var mapValue = new msgInfo.constructor(); + var msg = new msgInfo.constructor(); + + msg.getMapStringTestmapfieldsMap().set('test', mapValue); + msg.setTestMapFields(submsg); + + fillMapFields(submsg); + fillMapFields(msg); + fillMapFields(mapValue); + + var serialized = msg.serialize(); + + var decoded = msgInfo.deserialize(serialized); + checkMapFields(decoded); + + var decodedSubmsg = decoded.getTestMapFields(); + assertNotNull(decodedSubmsg); + checkMapFields(decodedSubmsg); + + var decodedMapValue = decoded.getMapStringTestmapfieldsMap().get('test'); + assertNotNull(decodedMapValue); + checkMapFields(decodedMapValue); + }); + + /** + * Tests toObject()/fromObject(). + */ + it('testToFromObject' + suffix, function() { + var msg = new msgInfo.constructor(); + fillMapFields(msg); + var obj = msg.toObject(); + var decoded = msgInfo.fromObject(obj); + checkMapFields(decoded); + obj = msgInfo.deserialize(msg.serialize()).toObject(); + decoded = msgInfo.fromObject(obj); + checkMapFields(decoded); + }); + + + /** + * Exercises the lazy map<->underlying array sync. + */ + it('testLazyMapSync' + suffix, function() { + // Start with a JSPB array containing a few map entries. + var entries = [ + ['a', 'entry 1'], + ['c', 'entry 2'], + ['b', 'entry 3'] + ]; + var msg = new msgInfo.constructor([entries]); + assertEquals(entries.length, 3); + assertEquals(entries[0][0], 'a'); + assertEquals(entries[1][0], 'c'); + assertEquals(entries[2][0], 'b'); + msg.getMapStringStringMap().del('a'); + assertEquals(entries.length, 3); // not yet sync'd + msg.toArray(); // force a sync + assertEquals(entries.length, 2); + assertEquals(entries[0][0], 'b'); // now in sorted order + assertEquals(entries[1][0], 'c'); + + var a = msg.toArray(); + assertEquals(a[0], entries); // retains original reference + }); +} + +describe('mapsTest', function() { + makeTests({ + constructor: proto.jspb.test.TestMapFields, + fromObject: proto.jspb.test.TestMapFields.fromObject, + deserialize: proto.jspb.test.TestMapFields.deserialize, + deserializeBinary: proto.jspb.test.TestMapFields.deserializeBinary + }, proto.jspb.test.MapValueMessage, "_Binary"); + makeTests({ + constructor: proto.jspb.test.TestMapFieldsNoBinary, + fromObject: proto.jspb.test.TestMapFieldsNoBinary.fromObject, + deserialize: proto.jspb.test.TestMapFieldsNoBinary.deserialize, + deserializeBinary: null + }, proto.jspb.test.MapValueMessageNoBinary, "_NoBinary"); +}); diff --git a/js/message.js b/js/message.js index 631ebe69..b150722a 100644 --- a/js/message.js +++ b/js/message.js @@ -106,17 +106,17 @@ jspb.ExtensionFieldInfo = function(fieldNumber, fieldName, ctor, toObjectFn, /** * Stores binary-related information for a single extension field. * @param {!jspb.ExtensionFieldInfo} fieldInfo - * @param {?function(number,?)=} binaryReaderFn - * @param {?function(number,?)|function(number,?,?,?,?,?)=} binaryWriterFn - * @param {?function(?,?)=} opt_binaryMessageSerializeFn - * @param {?function(?,?)=} opt_binaryMessageDeserializeFn - * @param {?boolean=} opt_isPacked + * @param {!function(number,?)} binaryReaderFn + * @param {!function(number,?)|function(number,?,?,?,?,?)} binaryWriterFn + * @param {function(?,?)=} opt_binaryMessageSerializeFn + * @param {function(?,?)=} opt_binaryMessageDeserializeFn + * @param {boolean=} opt_isPacked * @constructor * @struct * @template T */ jspb.ExtensionFieldBinaryInfo = function(fieldInfo, binaryReaderFn, binaryWriterFn, - binaryMessageSerializeFn, binaryMessageDeserializeFn, isPacked) { + opt_binaryMessageSerializeFn, opt_binaryMessageDeserializeFn, opt_isPacked) { /** @const */ this.fieldInfo = fieldInfo; /** @const */ @@ -124,11 +124,11 @@ jspb.ExtensionFieldBinaryInfo = function(fieldInfo, binaryReaderFn, binaryWriter /** @const */ this.binaryWriterFn = binaryWriterFn; /** @const */ - this.binaryMessageSerializeFn = binaryMessageSerializeFn; + this.binaryMessageSerializeFn = opt_binaryMessageSerializeFn; /** @const */ - this.binaryMessageDeserializeFn = binaryMessageDeserializeFn; + this.binaryMessageDeserializeFn = opt_binaryMessageDeserializeFn; /** @const */ - this.isPacked = isPacked; + this.isPacked = opt_isPacked; }; /** @@ -744,7 +744,7 @@ jspb.Message.assertConsistentTypes_ = function(array) { * @return {T} The field's value. * @protected */ -jspb.Message.getFieldProto3 = function(msg, fieldNumber, defaultValue) { +jspb.Message.getFieldWithDefault = function(msg, fieldNumber, defaultValue) { var value = jspb.Message.getField(msg, fieldNumber); if (value == null) { return defaultValue; @@ -810,6 +810,24 @@ jspb.Message.setField = function(msg, fieldNumber, value) { }; +/** + * Adds a value to a repeated, primitive field. + * @param {!jspb.Message} msg A jspb proto. + * @param {number} fieldNumber The field number. + * @param {string|number|boolean|!Uint8Array} value New value + * @param {number=} opt_index Index where to put new value. + * @protected + */ +jspb.Message.addToRepeatedField = function(msg, fieldNumber, value, opt_index) { + var arr = jspb.Message.getField(msg, fieldNumber); + if (opt_index != undefined) { + arr.splice(opt_index, 0, value); + } else { + arr.push(value); + } +}; + + /** * Sets the value of a field in a oneof union and clears all other fields in * the union. @@ -907,6 +925,24 @@ jspb.Message.getWrapperField = function(msg, ctor, fieldNumber, opt_required) { * @protected */ jspb.Message.getRepeatedWrapperField = function(msg, ctor, fieldNumber) { + jspb.Message.wrapRepeatedField_(msg, ctor, fieldNumber); + var val = msg.wrappers_[fieldNumber]; + if (val == jspb.Message.EMPTY_LIST_SENTINEL_) { + val = msg.wrappers_[fieldNumber] = []; + } + return /** @type {!Array} */ (val); +}; + + +/** + * Wraps underlying array into proto message representation if it wasn't done + * before. + * @param {!jspb.Message} msg A jspb proto. + * @param {function(new:jspb.Message, ?Array)} ctor Constructor for the field. + * @param {number} fieldNumber The field number. + * @private + */ +jspb.Message.wrapRepeatedField_ = function(msg, ctor, fieldNumber) { if (!msg.wrappers_) { msg.wrappers_ = {}; } @@ -917,11 +953,6 @@ jspb.Message.getRepeatedWrapperField = function(msg, ctor, fieldNumber) { } msg.wrappers_[fieldNumber] = wrappers; } - var val = msg.wrappers_[fieldNumber]; - if (val == jspb.Message.EMPTY_LIST_SENTINEL_) { - val = msg.wrappers_[fieldNumber] = []; - } - return /** @type {Array} */ (val); }; @@ -980,6 +1011,48 @@ jspb.Message.setRepeatedWrapperField = function(msg, fieldNumber, value) { }; +/** + * Add a message to a repeated proto field. + * @param {!jspb.Message} msg A jspb proto. + * @param {number} fieldNumber The field number. + * @param {T_CHILD|undefined} value Proto that will be added to the + * repeated field. + * @param {function(new:T_CHILD, ?Array=)} ctor The constructor of the + * message type. + * @param {number|undefined} index Index at which to insert the value. + * @return {T_CHILD_NOT_UNDEFINED} proto that was inserted to the repeated field + * @template MessageType + * Use go/closure-ttl to declare a non-undefined version of T_CHILD. Replace the + * undefined in blah|undefined with none. This is necessary because the compiler + * will infer T_CHILD to be |undefined. + * @template T_CHILD + * @template T_CHILD_NOT_UNDEFINED := + * cond(isUnknown(T_CHILD), unknown(), + * mapunion(T_CHILD, (X) => + * cond(eq(X, 'undefined'), none(), X))) + * =: + * @protected + */ +jspb.Message.addToRepeatedWrapperField = function( + msg, fieldNumber, value, ctor, index) { + jspb.Message.wrapRepeatedField_(msg, ctor, fieldNumber); + var wrapperArray = msg.wrappers_[fieldNumber]; + if (!wrapperArray) { + wrapperArray = msg.wrappers_[fieldNumber] = []; + } + var insertedValue = value ? value : new ctor(); + var array = jspb.Message.getField(msg, fieldNumber); + if (index != undefined) { + wrapperArray.splice(index, 0, insertedValue); + array.splice(index, 0, insertedValue.toArray()); + } else { + wrapperArray.push(insertedValue); + array.push(insertedValue.toArray()); + } + return insertedValue; +}; + + /** * Converts a JsPb repeated message field into a map. The map will contain * protos unless an optional toObject function is given, in which case it will @@ -1114,32 +1187,39 @@ jspb.Message.prototype.getExtension = function(fieldInfo) { * @param {jspb.ExtensionFieldInfo} fieldInfo Specifies the field to set. * @param {jspb.Message|string|Uint8Array|number|boolean|Array?} value The value * to set. + * @return {THIS} For chaining + * @this {THIS} + * @template THIS */ jspb.Message.prototype.setExtension = function(fieldInfo, value) { - if (!this.wrappers_) { - this.wrappers_ = {}; + // Cast self, since the inferred THIS is unknown inside the function body. + // https://github.com/google/closure-compiler/issues/1411#issuecomment-232442220 + var self = /** @type {!jspb.Message} */ (this); + if (!self.wrappers_) { + self.wrappers_ = {}; } - jspb.Message.maybeInitEmptyExtensionObject_(this); + jspb.Message.maybeInitEmptyExtensionObject_(self); var fieldNumber = fieldInfo.fieldIndex; if (fieldInfo.isRepeated) { value = value || []; if (fieldInfo.isMessageType()) { - this.wrappers_[fieldNumber] = value; - this.extensionObject_[fieldNumber] = goog.array.map( + self.wrappers_[fieldNumber] = value; + self.extensionObject_[fieldNumber] = goog.array.map( /** @type {Array} */ (value), function(msg) { return msg.toArray(); }); } else { - this.extensionObject_[fieldNumber] = value; + self.extensionObject_[fieldNumber] = value; } } else { if (fieldInfo.isMessageType()) { - this.wrappers_[fieldNumber] = value; - this.extensionObject_[fieldNumber] = value ? value.toArray() : value; + self.wrappers_[fieldNumber] = value; + self.extensionObject_[fieldNumber] = value ? value.toArray() : value; } else { - this.extensionObject_[fieldNumber] = value; + self.extensionObject_[fieldNumber] = value; } } + return self; }; @@ -1308,7 +1388,30 @@ jspb.Message.compareFields = function(field1, field2) { /** - * Static clone function. NOTE: A type-safe method called "cloneMessage" exists + * Templated, type-safe cloneMessage definition. + * @return {THIS} + * @this {THIS} + * @template THIS + */ +jspb.Message.prototype.cloneMessage = function() { + return jspb.Message.cloneMessage(/** @type {!jspb.Message} */ (this)); +}; + +/** + * Alias clone to cloneMessage. goog.object.unsafeClone uses clone to + * efficiently copy objects. Without this alias, copying jspb messages comes + * with a large performance penalty. + * @return {THIS} + * @this {THIS} + * @template THIS + */ +jspb.Message.prototype.clone = function() { + return jspb.Message.cloneMessage(/** @type {!jspb.Message} */ (this)); +}; + +/** + * Static clone function. NOTE: A type-safe method called "cloneMessage" + * exists * on each generated JsPb class. Do not call this function directly. * @param {!jspb.Message} msg A message to clone. * @return {!jspb.Message} A deep clone of the given message. @@ -1429,3 +1532,4 @@ jspb.Message.registry_ = {}; * @type {!Object.} */ jspb.Message.messageSetExtensions = {}; +jspb.Message.messageSetExtensionsBinary = {}; diff --git a/js/message_test.js b/js/message_test.js index b7791431..97c594c8 100644 --- a/js/message_test.js +++ b/js/message_test.js @@ -269,7 +269,7 @@ describe('Message test suite', function() { assertFalse(response.hasEnumField()); }); - it('testMessageRegistration', function() { + it('testMessageRegistration', /** @suppress {visibility} */ function() { // goog.require(SomeResponse) will include its library, which will in // turn add SomeResponse to the message registry. assertEquals(jspb.Message.registry_['res'], proto.jspb.test.SomeResponse); @@ -297,47 +297,9 @@ describe('Message test suite', function() { var expected = [,,, [], []]; expected[0] = expected[1] = expected[2] = undefined; assertObjectEquals(expected, foo.toArray()); - - // Test set(null). We could deprecated this in favor of clear(), but - // it's also convenient to have. - data = ['str', true, [11], [[22], [33]], ['s1', 's2']]; - foo = new proto.jspb.test.OptionalFields(data); - foo.setAString(null); - foo.setABool(null); - foo.setANestedMessage(null); - foo.setARepeatedMessageList(null); - foo.setARepeatedStringList(null); - assertEquals('', foo.getAString()); - assertEquals(false, foo.getABool()); - assertNull(foo.getANestedMessage()); - assertFalse(foo.hasAString()); - assertFalse(foo.hasABool()); - assertObjectEquals([], foo.getARepeatedMessageList()); - assertObjectEquals([], foo.getARepeatedStringList()); - assertObjectEquals([null, null, null, [], []], foo.toArray()); - - // Test set(undefined). Again, not something we really need, and not - // supported directly by our typing, but it should 'do the right thing'. - data = ['str', true, [11], [[22], [33]], ['s1', 's2']]; - foo = new proto.jspb.test.OptionalFields(data); - foo.setAString(undefined); - foo.setABool(undefined); - foo.setANestedMessage(undefined); - foo.setARepeatedMessageList(undefined); - foo.setARepeatedStringList(undefined); - assertEquals('', foo.getAString()); - assertEquals(false, foo.getABool()); - assertUndefined(foo.getANestedMessage()); - assertFalse(foo.hasAString()); - assertFalse(foo.hasABool()); - assertObjectEquals([], foo.getARepeatedMessageList()); - assertObjectEquals([], foo.getARepeatedStringList()); - expected = [,,, [], []]; - expected[0] = expected[1] = expected[2] = undefined; - assertObjectEquals(expected, foo.toArray()); }); - it('testDifferenceRawObject', function() { + it('testDifferenceRawObject', /** @suppress {visibility} */ function() { var p1 = new proto.jspb.test.HasExtensions(['hi', 'diff', {}]); var p2 = new proto.jspb.test.HasExtensions(['hi', 'what', {1000: 'unique'}]); @@ -477,7 +439,7 @@ describe('Message test suite', function() { var extension = new proto.jspb.test.CloneExtension(); extension.setExt('e1'); original.setExtension(proto.jspb.test.IsExtension.extField, extension); - var clone = original.cloneMessage(); + var clone = original.clone(); assertArrayEquals(['v1',, ['x1', ['y1', 'z1']],, [['x2', ['y2', 'z2']], ['x3', ['y3', 'z3']]], bytes1,, { 100: [, 'e1'] }], clone.toArray()); @@ -712,11 +674,12 @@ describe('Message test suite', function() { assertArrayEquals([1, 2, 3, {1: 'hi'}], msg.toArray()); }); - it('testExtendedMessageEnsureObject', function() { - var data = new proto.jspb.test.HasExtensions(['str1', - {'a_key': 'an_object'}]); - assertEquals('an_object', data.extensionObject_['a_key']); - }); + it('testExtendedMessageEnsureObject', + /** @suppress {visibility} */ function() { + var data = + new proto.jspb.test.HasExtensions(['str1', {'a_key': 'an_object'}]); + assertEquals('an_object', data.extensionObject_['a_key']); + }); it('testToObject_hasExtensionField', function() { var data = new proto.jspb.test.HasExtensions(['str1', {100: ['ext1']}]); diff --git a/js/proto3_test.js b/js/proto3_test.js index fab0fd44..3c929eff 100644 --- a/js/proto3_test.js +++ b/js/proto3_test.js @@ -294,7 +294,7 @@ describe('proto3Test', function() { msg.setOptionalForeignEnum(proto.jspb.test.Proto3Enum.PROTO3_BAR); msg.setOptionalForeignEnum(proto.jspb.test.Proto3Enum.PROTO3_FOO); msg.setOneofUint32(32); - msg.setOneofUint32(null); + msg.clearOneofUint32(); var serialized = msg.serializeBinary(); -- cgit v1.2.3