blob: 905329fe4409520560eadd2417e6868f0539fa28 [file] [log] [blame]
Feng Xiaoe841bac2015-12-11 17:09:20 -08001// Protocol Buffers - Google's data interchange format
2// Copyright 2008 Google Inc. All rights reserved.
3// https://developers.google.com/protocol-buffers/
4//
5// Redistribution and use in source and binary forms, with or without
6// modification, are permitted provided that the following conditions are
7// met:
8//
9// * Redistributions of source code must retain the above copyright
10// notice, this list of conditions and the following disclaimer.
11// * Redistributions in binary form must reproduce the above
12// copyright notice, this list of conditions and the following disclaimer
13// in the documentation and/or other materials provided with the
14// distribution.
15// * Neither the name of Google Inc. nor the names of its
16// contributors may be used to endorse or promote products derived from
17// this software without specific prior written permission.
18//
19// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31/**
32 * @fileoverview Definition of jspb.Message.
33 *
Joshua Haberman38d6de12020-09-28 11:54:54 -070034 * @suppress {missingRequire} TODO(b/152540451): this shouldn't be needed
Feng Xiaoe841bac2015-12-11 17:09:20 -080035 * @author mwr@google.com (Mark Rawling)
36 */
37
Josh Haberman7429b912016-07-18 15:58:58 -070038goog.provide('jspb.ExtensionFieldBinaryInfo');
Josh Habermane0e73772016-07-18 20:49:11 -070039goog.provide('jspb.ExtensionFieldInfo');
Feng Xiaoe841bac2015-12-11 17:09:20 -080040goog.provide('jspb.Message');
41
42goog.require('goog.array');
43goog.require('goog.asserts');
Jisi Liu3b3c8ab2016-03-30 11:39:59 -070044goog.require('goog.crypt.base64');
Yilun Chong0adb74c2019-01-08 15:06:30 -080045goog.require('jspb.BinaryReader');
Adam Cozzetted64a2d92016-06-29 15:23:27 -070046goog.require('jspb.Map');
Feng Xiaoe841bac2015-12-11 17:09:20 -080047
Feng Xiaoe841bac2015-12-11 17:09:20 -080048
49
50
51/**
52 * Stores information for a single extension field.
53 *
54 * For example, an extension field defined like so:
55 *
56 * extend BaseMessage {
57 * optional MyMessage my_field = 123;
58 * }
59 *
60 * will result in an ExtensionFieldInfo object with these properties:
61 *
62 * {
63 * fieldIndex: 123,
64 * fieldName: {my_field_renamed: 0},
65 * ctor: proto.example.MyMessage,
66 * toObjectFn: proto.example.MyMessage.toObject,
67 * isRepeated: 0
68 * }
69 *
70 * We include `toObjectFn` to allow the JSCompiler to perform dead-code removal
71 * on unused toObject() methods.
72 *
73 * If an extension field is primitive, ctor and toObjectFn will be null.
74 * isRepeated should be 0 or 1.
75 *
76 * binary{Reader,Writer}Fn and (if message type) binaryMessageSerializeFn are
77 * always provided. binaryReaderFn and binaryWriterFn are references to the
78 * appropriate methods on BinaryReader/BinaryWriter to read/write the value of
79 * this extension, and binaryMessageSerializeFn is a reference to the message
80 * class's .serializeBinary method, if available.
81 *
82 * @param {number} fieldNumber
83 * @param {Object} fieldName This has the extension field name as a property.
84 * @param {?function(new: jspb.Message, Array=)} ctor
85 * @param {?function((boolean|undefined),!jspb.Message):!Object} toObjectFn
86 * @param {number} isRepeated
Feng Xiaoe841bac2015-12-11 17:09:20 -080087 * @constructor
88 * @struct
89 * @template T
90 */
91jspb.ExtensionFieldInfo = function(fieldNumber, fieldName, ctor, toObjectFn,
Josh Haberman7429b912016-07-18 15:58:58 -070092 isRepeated) {
Feng Xiaoe841bac2015-12-11 17:09:20 -080093 /** @const */
94 this.fieldIndex = fieldNumber;
95 /** @const */
96 this.fieldName = fieldName;
97 /** @const */
98 this.ctor = ctor;
99 /** @const */
100 this.toObjectFn = toObjectFn;
101 /** @const */
Feng Xiaoe841bac2015-12-11 17:09:20 -0800102 this.isRepeated = isRepeated;
Feng Xiaoe841bac2015-12-11 17:09:20 -0800103};
104
Josh Haberman7429b912016-07-18 15:58:58 -0700105/**
106 * Stores binary-related information for a single extension field.
107 * @param {!jspb.ExtensionFieldInfo<T>} fieldInfo
Paul Yang7bff8392019-07-19 14:49:01 -0700108 * @param {function(this:jspb.BinaryReader,number,?,?)} binaryReaderFn
Feng Xiaod36c0c52017-03-29 14:32:48 -0700109 * @param {function(this:jspb.BinaryWriter,number,?)
110 * |function(this:jspb.BinaryWriter,number,?,?,?,?,?)} binaryWriterFn
Bo Yangcc8ca5b2016-09-19 13:45:07 -0700111 * @param {function(?,?)=} opt_binaryMessageSerializeFn
112 * @param {function(?,?)=} opt_binaryMessageDeserializeFn
113 * @param {boolean=} opt_isPacked
Josh Haberman7429b912016-07-18 15:58:58 -0700114 * @constructor
115 * @struct
116 * @template T
117 */
118jspb.ExtensionFieldBinaryInfo = function(fieldInfo, binaryReaderFn, binaryWriterFn,
Bo Yangcc8ca5b2016-09-19 13:45:07 -0700119 opt_binaryMessageSerializeFn, opt_binaryMessageDeserializeFn, opt_isPacked) {
Josh Haberman7429b912016-07-18 15:58:58 -0700120 /** @const */
121 this.fieldInfo = fieldInfo;
122 /** @const */
123 this.binaryReaderFn = binaryReaderFn;
124 /** @const */
125 this.binaryWriterFn = binaryWriterFn;
126 /** @const */
Bo Yangcc8ca5b2016-09-19 13:45:07 -0700127 this.binaryMessageSerializeFn = opt_binaryMessageSerializeFn;
Josh Haberman7429b912016-07-18 15:58:58 -0700128 /** @const */
Bo Yangcc8ca5b2016-09-19 13:45:07 -0700129 this.binaryMessageDeserializeFn = opt_binaryMessageDeserializeFn;
Josh Haberman7429b912016-07-18 15:58:58 -0700130 /** @const */
Bo Yangcc8ca5b2016-09-19 13:45:07 -0700131 this.isPacked = opt_isPacked;
Josh Haberman7429b912016-07-18 15:58:58 -0700132};
Feng Xiaoe841bac2015-12-11 17:09:20 -0800133
134/**
Jisi Liu3b3c8ab2016-03-30 11:39:59 -0700135 * @return {boolean} Does this field represent a sub Message?
136 */
137jspb.ExtensionFieldInfo.prototype.isMessageType = function() {
138 return !!this.ctor;
139};
140
141
142/**
Feng Xiaoe841bac2015-12-11 17:09:20 -0800143 * Base class for all JsPb messages.
Feng Xiaod36c0c52017-03-29 14:32:48 -0700144 *
145 * Several common methods (toObject, serializeBinary, in particular) are not
146 * defined on the prototype to encourage code patterns that minimize code bloat
147 * due to otherwise unused code on all protos contained in the project.
148 *
149 * If you want to call these methods on a generic message, either
150 * pass in your instance of method as a parameter:
151 * someFunction(instanceOfKnownProto,
152 * KnownProtoClass.prototype.serializeBinary);
153 * or use a lambda that knows the type:
154 * someFunction(()=>instanceOfKnownProto.serializeBinary());
155 * or, if you don't care about code size, just suppress the
156 * WARNING - Property serializeBinary never defined on jspb.Message
157 * and call it the intuitive way.
158 *
Feng Xiaoe841bac2015-12-11 17:09:20 -0800159 * @constructor
160 * @struct
161 */
162jspb.Message = function() {
163};
164
165
166/**
167 * @define {boolean} Whether to generate toObject methods for objects. Turn
168 * this off, if you do not want toObject to be ever used in your project.
169 * When turning off this flag, consider adding a conformance test that bans
170 * calling toObject. Enabling this will disable the JSCompiler's ability to
171 * dead code eliminate fields used in protocol buffers that are never used
172 * in an application.
173 */
Hao Nguyen176f7db2019-04-09 06:23:32 -0700174jspb.Message.GENERATE_TO_OBJECT =
175 goog.define('jspb.Message.GENERATE_TO_OBJECT', true);
Feng Xiaoe841bac2015-12-11 17:09:20 -0800176
177
178/**
179 * @define {boolean} Whether to generate fromObject methods for objects. Turn
180 * this off, if you do not want fromObject to be ever used in your project.
181 * When turning off this flag, consider adding a conformance test that bans
182 * calling fromObject. Enabling this might disable the JSCompiler's ability
183 * to dead code eliminate fields used in protocol buffers that are never
184 * used in an application.
Feng Xiaoe841bac2015-12-11 17:09:20 -0800185 * By default this is enabled for test code only.
186 */
Hao Nguyen176f7db2019-04-09 06:23:32 -0700187jspb.Message.GENERATE_FROM_OBJECT = goog.define(
188 'jspb.Message.GENERATE_FROM_OBJECT', !goog.DISALLOW_TEST_ONLY_CODE);
Feng Xiaoe841bac2015-12-11 17:09:20 -0800189
190
191/**
Paul Yang7f3e2372017-01-31 09:17:32 -0800192 * @define {boolean} Whether to generate toString methods for objects. Turn
Jisi Liu09354db2017-07-18 15:38:30 -0700193 * this off if you do not use toString in your project and want to trim it
194 * from the compiled JS.
Paul Yang7f3e2372017-01-31 09:17:32 -0800195 */
Hao Nguyen176f7db2019-04-09 06:23:32 -0700196jspb.Message.GENERATE_TO_STRING =
197 goog.define('jspb.Message.GENERATE_TO_STRING', true);
Paul Yang7f3e2372017-01-31 09:17:32 -0800198
199
200/**
201 * @define {boolean} Whether arrays passed to initialize() can be assumed to be
202 * local (e.g. not from another iframe) and thus safely classified with
203 * instanceof Array.
204 */
Hao Nguyen176f7db2019-04-09 06:23:32 -0700205jspb.Message.ASSUME_LOCAL_ARRAYS =
206 goog.define('jspb.Message.ASSUME_LOCAL_ARRAYS', false);
Paul Yang7f3e2372017-01-31 09:17:32 -0800207
208
Adam Cozzette92a7e772017-12-01 10:05:10 -0800209// TODO(jakubvrana): Turn this off by default.
210/**
211 * @define {boolean} Disabling the serialization of empty trailing fields
212 * reduces the size of serialized protos. The price is an extra iteration of
213 * the proto before serialization. This is enabled by default to be
214 * backwards compatible. Projects are advised to turn this flag always off.
215 */
Hao Nguyen176f7db2019-04-09 06:23:32 -0700216jspb.Message.SERIALIZE_EMPTY_TRAILING_FIELDS =
217 goog.define('jspb.Message.SERIALIZE_EMPTY_TRAILING_FIELDS', true);
Adam Cozzette92a7e772017-12-01 10:05:10 -0800218
219
Paul Yang7f3e2372017-01-31 09:17:32 -0800220/**
Josh Habermanf873d322016-06-08 12:38:15 -0700221 * Does this JavaScript environment support Uint8Aray typed arrays?
Jisi Liu3b3c8ab2016-03-30 11:39:59 -0700222 * @type {boolean}
223 * @private
224 */
225jspb.Message.SUPPORTS_UINT8ARRAY_ = (typeof Uint8Array == 'function');
226
227
228/**
Feng Xiaoe841bac2015-12-11 17:09:20 -0800229 * The internal data array.
230 * @type {!Array}
231 * @protected
232 */
233jspb.Message.prototype.array;
234
235
236/**
237 * Wrappers are the constructed instances of message-type fields. They are built
238 * on demand from the raw array data. Includes message fields, repeated message
239 * fields and extension message fields. Indexed by field number.
240 * @type {Object}
241 * @private
242 */
243jspb.Message.prototype.wrappers_;
244
245
246/**
247 * The object that contains extension fields, if any. This is an object that
248 * maps from a proto field number to the field's value.
249 * @type {Object}
250 * @private
251 */
252jspb.Message.prototype.extensionObject_;
253
254
255/**
256 * Non-extension fields with a field number at or above the pivot are
257 * stored in the extension object (in addition to all extension fields).
258 * @type {number}
259 * @private
260 */
261jspb.Message.prototype.pivot_;
262
263
264/**
265 * The JsPb message_id of this proto.
266 * @type {string|undefined} the message id or undefined if this message
267 * has no id.
268 * @private
269 */
270jspb.Message.prototype.messageId_;
271
272
273/**
Hao Nguyend0f91c82019-03-06 12:39:12 -0800274 * Repeated fields that have been converted to their proper type. This is used
275 * for numbers stored as strings (typically "NaN", "Infinity" and "-Infinity")
276 * and for booleans stored as numbers (0 or 1).
Jisi Liu3b3c8ab2016-03-30 11:39:59 -0700277 * @private {!Object<number,boolean>|undefined}
278 */
Hao Nguyend0f91c82019-03-06 12:39:12 -0800279jspb.Message.prototype.convertedPrimitiveFields_;
Jisi Liu3b3c8ab2016-03-30 11:39:59 -0700280
281/**
Adam Cozzette92a7e772017-12-01 10:05:10 -0800282 * Repeated fields numbers.
283 * @protected {?Array<number>|undefined}
284 */
285jspb.Message.prototype.repeatedFields;
286
287
Feng Xiaoe841bac2015-12-11 17:09:20 -0800288
289/**
290 * Returns the JsPb message_id of this proto.
291 * @return {string|undefined} the message id or undefined if this message
292 * has no id.
293 */
294jspb.Message.prototype.getJsPbMessageId = function() {
295 return this.messageId_;
296};
297
298
299/**
300 * An offset applied to lookups into this.array to account for the presence or
301 * absence of a messageId at position 0. For response messages, this will be 0.
302 * Otherwise, it will be -1 so that the first array position is not wasted.
303 * @type {number}
304 * @private
305 */
306jspb.Message.prototype.arrayIndexOffset_;
307
308
309/**
310 * Returns the index into msg.array at which the proto field with tag number
311 * fieldNumber will be located.
312 * @param {!jspb.Message} msg Message for which we're calculating an index.
313 * @param {number} fieldNumber The field number.
314 * @return {number} The index.
315 * @private
316 */
317jspb.Message.getIndex_ = function(msg, fieldNumber) {
318 return fieldNumber + msg.arrayIndexOffset_;
319};
320
Brian Wignalla104dff2020-01-08 13:18:20 -0500321// This is only here to ensure we are not back sliding on ES6 requirements for
Hao Nguyen176f7db2019-04-09 06:23:32 -0700322// protos in g3.
323jspb.Message.hiddenES6Property_ = class {};
324
Feng Xiaoe841bac2015-12-11 17:09:20 -0800325
326/**
Adam Cozzette92a7e772017-12-01 10:05:10 -0800327 * Returns the tag number based on the index in msg.array.
328 * @param {!jspb.Message} msg Message for which we're calculating an index.
329 * @param {number} index The tag number.
330 * @return {number} The field number.
331 * @private
332 */
333jspb.Message.getFieldNumber_ = function(msg, index) {
334 return index - msg.arrayIndexOffset_;
335};
336
337
338/**
Feng Xiaoe841bac2015-12-11 17:09:20 -0800339 * Initializes a JsPb Message.
340 * @param {!jspb.Message} msg The JsPb proto to modify.
341 * @param {Array|undefined} data An initial data array.
342 * @param {string|number} messageId For response messages, the message id or ''
343 * if no message id is specified. For non-response messages, 0.
344 * @param {number} suggestedPivot The field number at which to start putting
345 * fields into the extension object. This is only used if data does not
346 * contain an extension object already. -1 if no extension object is
347 * required for this message type.
348 * @param {Array<number>} repeatedFields The message's repeated fields.
349 * @param {Array<!Array<number>>=} opt_oneofFields The fields belonging to
350 * each of the message's oneof unions.
351 * @protected
352 */
353jspb.Message.initialize = function(
354 msg, data, messageId, suggestedPivot, repeatedFields, opt_oneofFields) {
Adam Cozzette0400cca2018-03-13 16:37:29 -0700355 msg.wrappers_ = null;
Feng Xiaoe841bac2015-12-11 17:09:20 -0800356 if (!data) {
357 data = messageId ? [messageId] : [];
358 }
359 msg.messageId_ = messageId ? String(messageId) : undefined;
360 // If the messageId is 0, this message is not a response message, so we shift
361 // array indices down by 1 so as not to waste the first position in the array,
362 // which would otherwise go unused.
363 msg.arrayIndexOffset_ = messageId === 0 ? -1 : 0;
364 msg.array = data;
Jisi Liu09354db2017-07-18 15:38:30 -0700365 jspb.Message.initPivotAndExtensionObject_(msg, suggestedPivot);
Hao Nguyend0f91c82019-03-06 12:39:12 -0800366 msg.convertedPrimitiveFields_ = {};
Jisi Liu3b3c8ab2016-03-30 11:39:59 -0700367
Adam Cozzette92a7e772017-12-01 10:05:10 -0800368 if (!jspb.Message.SERIALIZE_EMPTY_TRAILING_FIELDS) {
369 // TODO(jakubvrana): This is same for all instances, move to prototype.
Brian Wignalla104dff2020-01-08 13:18:20 -0500370 // TODO(jakubvrana): There are indexOf calls on this in serialization,
Adam Cozzette92a7e772017-12-01 10:05:10 -0800371 // consider switching to a set.
372 msg.repeatedFields = repeatedFields;
373 }
374
Feng Xiaoe841bac2015-12-11 17:09:20 -0800375 if (repeatedFields) {
376 for (var i = 0; i < repeatedFields.length; i++) {
377 var fieldNumber = repeatedFields[i];
378 if (fieldNumber < msg.pivot_) {
379 var index = jspb.Message.getIndex_(msg, fieldNumber);
Adam Cozzette0400cca2018-03-13 16:37:29 -0700380 msg.array[index] =
381 msg.array[index] || jspb.Message.EMPTY_LIST_SENTINEL_;
Feng Xiaoe841bac2015-12-11 17:09:20 -0800382 } else {
Jisi Liu09354db2017-07-18 15:38:30 -0700383 jspb.Message.maybeInitEmptyExtensionObject_(msg);
Adam Cozzette0400cca2018-03-13 16:37:29 -0700384 msg.extensionObject_[fieldNumber] = msg.extensionObject_[fieldNumber] ||
385 jspb.Message.EMPTY_LIST_SENTINEL_;
Feng Xiaoe841bac2015-12-11 17:09:20 -0800386 }
387 }
388 }
389
390 if (opt_oneofFields && opt_oneofFields.length) {
391 // Compute the oneof case for each union. This ensures only one value is
392 // set in the union.
Adam Cozzette92a7e772017-12-01 10:05:10 -0800393 for (var i = 0; i < opt_oneofFields.length; i++) {
394 jspb.Message.computeOneofCase(msg, opt_oneofFields[i]);
395 }
Feng Xiaoe841bac2015-12-11 17:09:20 -0800396 }
397};
398
399
400/**
401 * Used to mark empty repeated fields. Serializes to null when serialized
402 * to JSON.
403 * When reading a repeated field readers must check the return value against
404 * this value and return and replace it with a new empty array if it is
405 * present.
406 * @private @const {!Object}
407 */
408jspb.Message.EMPTY_LIST_SENTINEL_ = goog.DEBUG && Object.freeze ?
409 Object.freeze([]) :
410 [];
411
412
413/**
Paul Yang7f3e2372017-01-31 09:17:32 -0800414 * Returns true if the provided argument is an array.
415 * @param {*} o The object to classify as array or not.
416 * @return {boolean} True if the provided object is an array.
417 * @private
418 */
419jspb.Message.isArray_ = function(o) {
420 return jspb.Message.ASSUME_LOCAL_ARRAYS ? o instanceof Array :
Joshua Haberman183cdec2020-06-01 16:32:31 -0700421 Array.isArray(o);
Paul Yang7f3e2372017-01-31 09:17:32 -0800422};
423
Adam Cozzette0894e072018-11-09 11:28:22 -0800424/**
425 * Returns true if the provided argument is an extension object.
426 * @param {*} o The object to classify as array or not.
427 * @return {boolean} True if the provided object is an extension object.
428 * @private
429 */
430jspb.Message.isExtensionObject_ = function(o) {
431 // Normal fields are never objects, so we can be sure that if we find an
432 // object here, then it's the extension object. However, we must ensure that
433 // the object is not an array, since arrays are valid field values (bytes
434 // fields can also be array).
435 // NOTE(lukestebbing): We avoid looking at .length to avoid a JIT bug
436 // in Safari on iOS 8. See the description of CL/86511464 for details.
437 return (o !== null && typeof o == 'object' &&
438 !jspb.Message.isArray_(o) &&
439 !(jspb.Message.SUPPORTS_UINT8ARRAY_ && o instanceof Uint8Array));
440};
441
Paul Yang7f3e2372017-01-31 09:17:32 -0800442
443/**
Feng Xiaoe841bac2015-12-11 17:09:20 -0800444 * If the array contains an extension object in its last position, then the
Jisi Liu09354db2017-07-18 15:38:30 -0700445 * object is kept in place and its position is used as the pivot. If not,
446 * decides the pivot of the message based on suggestedPivot without
447 * materializing the extension object.
448 *
Feng Xiaoe841bac2015-12-11 17:09:20 -0800449 * @param {!jspb.Message} msg The JsPb proto to modify.
450 * @param {number} suggestedPivot See description for initialize().
451 * @private
452 */
Jisi Liu09354db2017-07-18 15:38:30 -0700453jspb.Message.initPivotAndExtensionObject_ = function(msg, suggestedPivot) {
Feng Xiao6bbe1972018-08-08 17:00:41 -0700454 // There are 3 variants that need to be dealt with which are the
455 // combination of whether there exists an extension object (EO) and
456 // whether there is a suggested pivot (SP).
457 //
458 // EO, ? : pivot is the index of the EO
459 // no-EO, no-SP: pivot is MAX_INT
460 // no-EO, SP : pivot is the max(lastindex + 1, SP)
461
462 var msgLength = msg.array.length;
463 var lastIndex = -1;
464 if (msgLength) {
465 lastIndex = msgLength - 1;
466 var obj = msg.array[lastIndex];
Adam Cozzette0894e072018-11-09 11:28:22 -0800467 if (jspb.Message.isExtensionObject_(obj)) {
Feng Xiao6bbe1972018-08-08 17:00:41 -0700468 msg.pivot_ = jspb.Message.getFieldNumber_(msg, lastIndex);
Feng Xiaoe841bac2015-12-11 17:09:20 -0800469 msg.extensionObject_ = obj;
470 return;
471 }
472 }
Jisi Liu09354db2017-07-18 15:38:30 -0700473
Feng Xiaoe841bac2015-12-11 17:09:20 -0800474 if (suggestedPivot > -1) {
Feng Xiao6bbe1972018-08-08 17:00:41 -0700475 // If a extension object is not present, set the pivot value as being
476 // after the last value in the array to avoid overwriting values, etc.
477 msg.pivot_ = Math.max(
478 suggestedPivot, jspb.Message.getFieldNumber_(msg, lastIndex + 1));
Jisi Liu09354db2017-07-18 15:38:30 -0700479 // Avoid changing the shape of the proto with an empty extension object by
480 // deferring the materialization of the extension object until the first
481 // time a field set into it (may be due to getting a repeated proto field
482 // from it, in which case a new empty array is set into it at first).
483 msg.extensionObject_ = null;
Feng Xiaoe841bac2015-12-11 17:09:20 -0800484 } else {
Jisi Liu09354db2017-07-18 15:38:30 -0700485 // suggestedPivot is -1, which means that we don't have an extension object
486 // at all, in which case all fields are stored in the array.
Feng Xiaoe841bac2015-12-11 17:09:20 -0800487 msg.pivot_ = Number.MAX_VALUE;
488 }
489};
490
491
492/**
493 * Creates an empty extensionObject_ if non exists.
494 * @param {!jspb.Message} msg The JsPb proto to modify.
495 * @private
496 */
497jspb.Message.maybeInitEmptyExtensionObject_ = function(msg) {
498 var pivotIndex = jspb.Message.getIndex_(msg, msg.pivot_);
499 if (!msg.array[pivotIndex]) {
500 msg.extensionObject_ = msg.array[pivotIndex] = {};
501 }
502};
503
504
505/**
506 * Converts a JsPb repeated message field into an object list.
507 * @param {!Array<T>} field The repeated message field to be
508 * converted.
509 * @param {?function(boolean=): Object|
510 * function((boolean|undefined),T): Object} toObjectFn The toObject
511 * function for this field. We need to pass this for effective dead code
512 * removal.
513 * @param {boolean=} opt_includeInstance Whether to include the JSPB instance
514 * for transitional soy proto support: http://goto/soy-param-migration
515 * @return {!Array<Object>} An array of converted message objects.
516 * @template T
517 */
518jspb.Message.toObjectList = function(field, toObjectFn, opt_includeInstance) {
519 // Not using goog.array.map in the generated code to keep it small.
520 // And not using it here to avoid a function call.
521 var result = [];
522 for (var i = 0; i < field.length; i++) {
Adam Cozzette0400cca2018-03-13 16:37:29 -0700523 result[i] = toObjectFn.call(field[i], opt_includeInstance, field[i]);
Feng Xiaoe841bac2015-12-11 17:09:20 -0800524 }
525 return result;
526};
527
528
529/**
530 * Adds a proto's extension data to a Soy rendering object.
531 * @param {!jspb.Message} proto The proto whose extensions to convert.
532 * @param {!Object} obj The Soy object to add converted extension data to.
533 * @param {!Object} extensions The proto class' registered extensions.
Jisi Liu3b3c8ab2016-03-30 11:39:59 -0700534 * @param {function(this:?, jspb.ExtensionFieldInfo) : *} getExtensionFn
535 * The proto class' getExtension function. Passed for effective dead code
536 * removal.
Feng Xiaoe841bac2015-12-11 17:09:20 -0800537 * @param {boolean=} opt_includeInstance Whether to include the JSPB instance
538 * for transitional soy proto support: http://goto/soy-param-migration
539 */
540jspb.Message.toObjectExtension = function(proto, obj, extensions,
541 getExtensionFn, opt_includeInstance) {
542 for (var fieldNumber in extensions) {
543 var fieldInfo = extensions[fieldNumber];
544 var value = getExtensionFn.call(proto, fieldInfo);
Jisi Liu09354db2017-07-18 15:38:30 -0700545 if (value != null) {
Feng Xiaoe841bac2015-12-11 17:09:20 -0800546 for (var name in fieldInfo.fieldName) {
547 if (fieldInfo.fieldName.hasOwnProperty(name)) {
548 break; // the compiled field name
549 }
550 }
551 if (!fieldInfo.toObjectFn) {
552 obj[name] = value;
553 } else {
554 if (fieldInfo.isRepeated) {
555 obj[name] = jspb.Message.toObjectList(
Adam Cozzette0400cca2018-03-13 16:37:29 -0700556 /** @type {!Array<!jspb.Message>} */ (value),
Feng Xiaoe841bac2015-12-11 17:09:20 -0800557 fieldInfo.toObjectFn, opt_includeInstance);
558 } else {
Adam Cozzette0400cca2018-03-13 16:37:29 -0700559 obj[name] = fieldInfo.toObjectFn(
560 opt_includeInstance, /** @type {!jspb.Message} */ (value));
Feng Xiaoe841bac2015-12-11 17:09:20 -0800561 }
562 }
563 }
564 }
565};
566
567
568/**
569 * Writes a proto's extension data to a binary-format output stream.
570 * @param {!jspb.Message} proto The proto whose extensions to convert.
571 * @param {*} writer The binary-format writer to write to.
572 * @param {!Object} extensions The proto class' registered extensions.
Feng Xiaod36c0c52017-03-29 14:32:48 -0700573 * @param {function(this:jspb.Message,!jspb.ExtensionFieldInfo) : *} getExtensionFn The proto
Feng Xiaoe841bac2015-12-11 17:09:20 -0800574 * class' getExtension function. Passed for effective dead code removal.
575 */
576jspb.Message.serializeBinaryExtensions = function(proto, writer, extensions,
577 getExtensionFn) {
578 for (var fieldNumber in extensions) {
Josh Haberman7429b912016-07-18 15:58:58 -0700579 var binaryFieldInfo = extensions[fieldNumber];
580 var fieldInfo = binaryFieldInfo.fieldInfo;
581
Feng Xiaoe841bac2015-12-11 17:09:20 -0800582 // The old codegen doesn't add the extra fields to ExtensionFieldInfo, so we
583 // need to gracefully error-out here rather than produce a null dereference
584 // below.
Josh Haberman7429b912016-07-18 15:58:58 -0700585 if (!binaryFieldInfo.binaryWriterFn) {
Feng Xiaoe841bac2015-12-11 17:09:20 -0800586 throw new Error('Message extension present that was generated ' +
587 'without binary serialization support');
588 }
589 var value = getExtensionFn.call(proto, fieldInfo);
Jisi Liu09354db2017-07-18 15:38:30 -0700590 if (value != null) {
Jisi Liu3b3c8ab2016-03-30 11:39:59 -0700591 if (fieldInfo.isMessageType()) {
Feng Xiaoe841bac2015-12-11 17:09:20 -0800592 // If the message type of the extension was generated without binary
593 // support, there may not be a binary message serializer function, and
594 // we can't know when we codegen the extending message that the extended
595 // message may require binary support, so we can *only* catch this error
596 // here, at runtime (and this decoupled codegen is the whole point of
597 // extensions!).
Josh Haberman7429b912016-07-18 15:58:58 -0700598 if (binaryFieldInfo.binaryMessageSerializeFn) {
599 binaryFieldInfo.binaryWriterFn.call(writer, fieldInfo.fieldIndex,
600 value, binaryFieldInfo.binaryMessageSerializeFn);
Feng Xiaoe841bac2015-12-11 17:09:20 -0800601 } else {
602 throw new Error('Message extension present holding submessage ' +
603 'without binary support enabled, and message is ' +
604 'being serialized to binary format');
605 }
606 } else {
Josh Haberman7429b912016-07-18 15:58:58 -0700607 binaryFieldInfo.binaryWriterFn.call(
608 writer, fieldInfo.fieldIndex, value);
Feng Xiaoe841bac2015-12-11 17:09:20 -0800609 }
610 }
611 }
612};
613
614
615/**
616 * Reads an extension field from the given reader and, if a valid extension,
617 * sets the extension value.
618 * @param {!jspb.Message} msg A jspb proto.
Yilun Chong0adb74c2019-01-08 15:06:30 -0800619 * @param {!jspb.BinaryReader} reader
Feng Xiaoe841bac2015-12-11 17:09:20 -0800620 * @param {!Object} extensions The extensions object.
Feng Xiaod36c0c52017-03-29 14:32:48 -0700621 * @param {function(this:jspb.Message,!jspb.ExtensionFieldInfo)} getExtensionFn
622 * @param {function(this:jspb.Message,!jspb.ExtensionFieldInfo, ?)} setExtensionFn
Feng Xiaoe841bac2015-12-11 17:09:20 -0800623 */
624jspb.Message.readBinaryExtension = function(msg, reader, extensions,
625 getExtensionFn, setExtensionFn) {
Josh Haberman7429b912016-07-18 15:58:58 -0700626 var binaryFieldInfo = extensions[reader.getFieldNumber()];
Josh Haberman7429b912016-07-18 15:58:58 -0700627 if (!binaryFieldInfo) {
Feng Xiaoe841bac2015-12-11 17:09:20 -0800628 reader.skipField();
629 return;
630 }
Adam Cozzette5a76e632016-11-17 16:48:38 -0800631 var fieldInfo = binaryFieldInfo.fieldInfo;
Josh Haberman7429b912016-07-18 15:58:58 -0700632 if (!binaryFieldInfo.binaryReaderFn) {
Feng Xiaoe841bac2015-12-11 17:09:20 -0800633 throw new Error('Deserializing extension whose generated code does not ' +
634 'support binary format');
635 }
636
637 var value;
Jisi Liu3b3c8ab2016-03-30 11:39:59 -0700638 if (fieldInfo.isMessageType()) {
Feng Xiaoe841bac2015-12-11 17:09:20 -0800639 value = new fieldInfo.ctor();
Josh Haberman7429b912016-07-18 15:58:58 -0700640 binaryFieldInfo.binaryReaderFn.call(
641 reader, value, binaryFieldInfo.binaryMessageDeserializeFn);
Feng Xiaoe841bac2015-12-11 17:09:20 -0800642 } else {
643 // All other types.
Josh Haberman7429b912016-07-18 15:58:58 -0700644 value = binaryFieldInfo.binaryReaderFn.call(reader);
Feng Xiaoe841bac2015-12-11 17:09:20 -0800645 }
646
Josh Haberman7429b912016-07-18 15:58:58 -0700647 if (fieldInfo.isRepeated && !binaryFieldInfo.isPacked) {
Feng Xiaoe841bac2015-12-11 17:09:20 -0800648 var currentList = getExtensionFn.call(msg, fieldInfo);
649 if (!currentList) {
650 setExtensionFn.call(msg, fieldInfo, [value]);
651 } else {
652 currentList.push(value);
653 }
654 } else {
655 setExtensionFn.call(msg, fieldInfo, value);
656 }
657};
658
659
660/**
661 * Gets the value of a non-extension field.
662 * @param {!jspb.Message} msg A jspb proto.
663 * @param {number} fieldNumber The field number.
664 * @return {string|number|boolean|Uint8Array|Array|null|undefined}
665 * The field's value.
666 * @protected
667 */
668jspb.Message.getField = function(msg, fieldNumber) {
669 if (fieldNumber < msg.pivot_) {
670 var index = jspb.Message.getIndex_(msg, fieldNumber);
671 var val = msg.array[index];
672 if (val === jspb.Message.EMPTY_LIST_SENTINEL_) {
673 return msg.array[index] = [];
674 }
675 return val;
676 } else {
Jisi Liu09354db2017-07-18 15:38:30 -0700677 if (!msg.extensionObject_) {
678 return undefined;
679 }
Feng Xiaoe841bac2015-12-11 17:09:20 -0800680 var val = msg.extensionObject_[fieldNumber];
681 if (val === jspb.Message.EMPTY_LIST_SENTINEL_) {
682 return msg.extensionObject_[fieldNumber] = [];
683 }
684 return val;
685 }
686};
687
688
689/**
Jisi Liu09354db2017-07-18 15:38:30 -0700690 * Gets the value of a non-extension repeated field.
691 * @param {!jspb.Message} msg A jspb proto.
692 * @param {number} fieldNumber The field number.
693 * @return {!Array}
694 * The field's value.
695 * @protected
696 */
697jspb.Message.getRepeatedField = function(msg, fieldNumber) {
Yilun Chongd8c25012019-02-22 18:13:33 +0800698 return /** @type {!Array} */ (jspb.Message.getField(msg, fieldNumber));
Jisi Liu09354db2017-07-18 15:38:30 -0700699};
700
701
702/**
Jisi Liu3b3c8ab2016-03-30 11:39:59 -0700703 * Gets the value of an optional float or double field.
704 * @param {!jspb.Message} msg A jspb proto.
705 * @param {number} fieldNumber The field number.
706 * @return {?number|undefined} The field's value.
707 * @protected
708 */
709jspb.Message.getOptionalFloatingPointField = function(msg, fieldNumber) {
710 var value = jspb.Message.getField(msg, fieldNumber);
711 // Converts "NaN", "Infinity" and "-Infinity" to their corresponding numbers.
712 return value == null ? value : +value;
713};
714
715
716/**
Hao Nguyend0f91c82019-03-06 12:39:12 -0800717 * Gets the value of an optional boolean field.
718 * @param {!jspb.Message} msg A jspb proto.
719 * @param {number} fieldNumber The field number.
720 * @return {?boolean|undefined} The field's value.
721 * @protected
722 */
723jspb.Message.getBooleanField = function(msg, fieldNumber) {
724 var value = jspb.Message.getField(msg, fieldNumber);
725 // TODO(b/122673075): always return null when the value is null-ish.
726 return value == null ? (value) : !!value;
727};
728
729
730/**
Jisi Liu3b3c8ab2016-03-30 11:39:59 -0700731 * Gets the value of a repeated float or double field.
732 * @param {!jspb.Message} msg A jspb proto.
733 * @param {number} fieldNumber The field number.
734 * @return {!Array<number>} The field's value.
735 * @protected
736 */
737jspb.Message.getRepeatedFloatingPointField = function(msg, fieldNumber) {
Jisi Liu09354db2017-07-18 15:38:30 -0700738 var values = jspb.Message.getRepeatedField(msg, fieldNumber);
Hao Nguyend0f91c82019-03-06 12:39:12 -0800739 if (!msg.convertedPrimitiveFields_) {
740 msg.convertedPrimitiveFields_ = {};
Jisi Liu3b3c8ab2016-03-30 11:39:59 -0700741 }
Hao Nguyend0f91c82019-03-06 12:39:12 -0800742 if (!msg.convertedPrimitiveFields_[fieldNumber]) {
Jisi Liu3b3c8ab2016-03-30 11:39:59 -0700743 for (var i = 0; i < values.length; i++) {
744 // Converts "NaN", "Infinity" and "-Infinity" to their corresponding
745 // numbers.
746 values[i] = +values[i];
747 }
Hao Nguyend0f91c82019-03-06 12:39:12 -0800748 msg.convertedPrimitiveFields_[fieldNumber] = true;
Jisi Liu3b3c8ab2016-03-30 11:39:59 -0700749 }
750 return /** @type {!Array<number>} */ (values);
751};
752
Hao Nguyend0f91c82019-03-06 12:39:12 -0800753/**
754 * Gets the value of a repeated boolean field.
755 * @param {!jspb.Message} msg A jspb proto.
756 * @param {number} fieldNumber The field number.
757 * @return {!Array<boolean>} The field's value.
758 * @protected
759 */
760jspb.Message.getRepeatedBooleanField = function(msg, fieldNumber) {
761 var values = jspb.Message.getRepeatedField(msg, fieldNumber);
762 if (!msg.convertedPrimitiveFields_) {
763 msg.convertedPrimitiveFields_ = {};
764 }
765 if (!msg.convertedPrimitiveFields_[fieldNumber]) {
766 for (var i = 0; i < values.length; i++) {
767 // Converts 0 and 1 to their corresponding booleans.
768 values[i] = !!values[i];
769 }
770 msg.convertedPrimitiveFields_[fieldNumber] = true;
771 }
772 return /** @type {!Array<boolean>} */ (values);
773};
774
Jisi Liu3b3c8ab2016-03-30 11:39:59 -0700775
776/**
777 * Coerce a 'bytes' field to a base 64 string.
778 * @param {string|Uint8Array|null} value
779 * @return {?string} The field's coerced value.
780 */
781jspb.Message.bytesAsB64 = function(value) {
Rafi Kamal58d44202019-11-11 17:06:56 -0800782 if (value == null || typeof value === 'string') {
Jisi Liu3b3c8ab2016-03-30 11:39:59 -0700783 return value;
784 }
785 if (jspb.Message.SUPPORTS_UINT8ARRAY_ && value instanceof Uint8Array) {
786 return goog.crypt.base64.encodeByteArray(value);
787 }
788 goog.asserts.fail('Cannot coerce to b64 string: ' + goog.typeOf(value));
789 return null;
790};
791
792
793/**
794 * Coerce a 'bytes' field to a Uint8Array byte buffer.
795 * Note that Uint8Array is not supported on IE versions before 10 nor on Opera
796 * Mini. @see http://caniuse.com/Uint8Array
797 * @param {string|Uint8Array|null} value
798 * @return {?Uint8Array} The field's coerced value.
799 */
800jspb.Message.bytesAsU8 = function(value) {
801 if (value == null || value instanceof Uint8Array) {
802 return value;
803 }
Rafi Kamal58d44202019-11-11 17:06:56 -0800804 if (typeof value === 'string') {
Jisi Liu3b3c8ab2016-03-30 11:39:59 -0700805 return goog.crypt.base64.decodeStringToUint8Array(value);
806 }
807 goog.asserts.fail('Cannot coerce to Uint8Array: ' + goog.typeOf(value));
808 return null;
809};
810
811
812/**
813 * Coerce a repeated 'bytes' field to an array of base 64 strings.
814 * Note: the returned array should be treated as immutable.
815 * @param {!Array<string>|!Array<!Uint8Array>} value
816 * @return {!Array<string?>} The field's coerced value.
817 */
818jspb.Message.bytesListAsB64 = function(value) {
819 jspb.Message.assertConsistentTypes_(value);
Rafi Kamal58d44202019-11-11 17:06:56 -0800820 if (!value.length || typeof value[0] === 'string') {
Jisi Liu3b3c8ab2016-03-30 11:39:59 -0700821 return /** @type {!Array<string>} */ (value);
822 }
823 return goog.array.map(value, jspb.Message.bytesAsB64);
824};
825
826
827/**
828 * Coerce a repeated 'bytes' field to an array of Uint8Array byte buffers.
829 * Note: the returned array should be treated as immutable.
830 * Note that Uint8Array is not supported on IE versions before 10 nor on Opera
831 * Mini. @see http://caniuse.com/Uint8Array
832 * @param {!Array<string>|!Array<!Uint8Array>} value
833 * @return {!Array<Uint8Array?>} The field's coerced value.
834 */
835jspb.Message.bytesListAsU8 = function(value) {
836 jspb.Message.assertConsistentTypes_(value);
837 if (!value.length || value[0] instanceof Uint8Array) {
838 return /** @type {!Array<!Uint8Array>} */ (value);
839 }
840 return goog.array.map(value, jspb.Message.bytesAsU8);
841};
842
843
844/**
845 * Asserts that all elements of an array are of the same type.
846 * @param {Array?} array The array to test.
847 * @private
848 */
849jspb.Message.assertConsistentTypes_ = function(array) {
850 if (goog.DEBUG && array && array.length > 1) {
851 var expected = goog.typeOf(array[0]);
852 goog.array.forEach(array, function(e) {
853 if (goog.typeOf(e) != expected) {
854 goog.asserts.fail('Inconsistent type in JSPB repeated field array. ' +
855 'Got ' + goog.typeOf(e) + ' expected ' + expected);
856 }
857 });
858 }
859};
860
861
862/**
Feng Xiaoe841bac2015-12-11 17:09:20 -0800863 * Gets the value of a non-extension primitive field, with proto3 (non-nullable
864 * primitives) semantics. Returns `defaultValue` if the field is not otherwise
865 * set.
866 * @template T
867 * @param {!jspb.Message} msg A jspb proto.
868 * @param {number} fieldNumber The field number.
869 * @param {T} defaultValue The default value.
870 * @return {T} The field's value.
871 * @protected
872 */
Bo Yangcc8ca5b2016-09-19 13:45:07 -0700873jspb.Message.getFieldWithDefault = function(msg, fieldNumber, defaultValue) {
Feng Xiaoe841bac2015-12-11 17:09:20 -0800874 var value = jspb.Message.getField(msg, fieldNumber);
875 if (value == null) {
876 return defaultValue;
877 } else {
878 return value;
879 }
880};
881
882
883/**
Hao Nguyend0f91c82019-03-06 12:39:12 -0800884 * Gets the value of a boolean field, with proto3 (non-nullable primitives)
885 * semantics. Returns `defaultValue` if the field is not otherwise set.
886 * @template T
887 * @param {!jspb.Message} msg A jspb proto.
888 * @param {number} fieldNumber The field number.
889 * @param {boolean} defaultValue The default value.
890 * @return {boolean} The field's value.
891 * @protected
892 */
893jspb.Message.getBooleanFieldWithDefault = function(
894 msg, fieldNumber, defaultValue) {
895 var value = jspb.Message.getBooleanField(msg, fieldNumber);
896 if (value == null) {
897 return defaultValue;
898 } else {
899 return value;
900 }
901};
902
903
904/**
905 * Gets the value of a floating point field, with proto3 (non-nullable
906 * primitives) semantics. Returns `defaultValue` if the field is not otherwise
907 * set.
908 * @template T
909 * @param {!jspb.Message} msg A jspb proto.
910 * @param {number} fieldNumber The field number.
911 * @param {number} defaultValue The default value.
912 * @return {number} The field's value.
913 * @protected
914 */
915jspb.Message.getFloatingPointFieldWithDefault = function(
916 msg, fieldNumber, defaultValue) {
917 var value = jspb.Message.getOptionalFloatingPointField(msg, fieldNumber);
918 if (value == null) {
919 return defaultValue;
920 } else {
921 return value;
922 }
923};
924
925
926/**
Josh Habermanf2eeaf72016-09-28 10:52:32 -0700927 * Alias for getFieldWithDefault used by older generated code.
928 * @template T
929 * @param {!jspb.Message} msg A jspb proto.
930 * @param {number} fieldNumber The field number.
931 * @param {T} defaultValue The default value.
932 * @return {T} The field's value.
933 * @protected
934 */
935jspb.Message.getFieldProto3 = jspb.Message.getFieldWithDefault;
936
937
938/**
Adam Cozzetted64a2d92016-06-29 15:23:27 -0700939 * Gets the value of a map field, lazily creating the map container if
940 * necessary.
941 *
942 * This should only be called from generated code, because it requires knowledge
943 * of serialization/parsing callbacks (which are required by the map at
944 * construction time, and the map may be constructed here).
945 *
Adam Cozzetted64a2d92016-06-29 15:23:27 -0700946 * @template K, V
947 * @param {!jspb.Message} msg
948 * @param {number} fieldNumber
949 * @param {boolean|undefined} noLazyCreate
950 * @param {?=} opt_valueCtor
Adam Cozzetted64a2d92016-06-29 15:23:27 -0700951 * @return {!jspb.Map<K, V>|undefined}
952 * @protected
953 */
954jspb.Message.getMapField = function(msg, fieldNumber, noLazyCreate,
Josh Haberman923eae82016-07-18 14:46:12 -0700955 opt_valueCtor) {
Adam Cozzetted64a2d92016-06-29 15:23:27 -0700956 if (!msg.wrappers_) {
957 msg.wrappers_ = {};
958 }
959 // If we already have a map in the map wrappers, return that.
960 if (fieldNumber in msg.wrappers_) {
961 return msg.wrappers_[fieldNumber];
Adam Cozzetted64a2d92016-06-29 15:23:27 -0700962 }
Hao Nguyen09cab822019-06-11 16:00:16 -0700963 var arr = jspb.Message.getField(msg, fieldNumber);
964 // Wrap the underlying elements array with a Map.
965 if (!arr) {
966 if (noLazyCreate) {
967 return undefined;
968 }
969 arr = [];
970 jspb.Message.setField(msg, fieldNumber, arr);
971 }
972 return msg.wrappers_[fieldNumber] =
973 new jspb.Map(
974 /** @type {!Array<!Array<!Object>>} */ (arr), opt_valueCtor);
Adam Cozzetted64a2d92016-06-29 15:23:27 -0700975};
976
977
978/**
Feng Xiaoe841bac2015-12-11 17:09:20 -0800979 * Sets the value of a non-extension field.
Paul Yang7bff8392019-07-19 14:49:01 -0700980 * @param {T} msg A jspb proto.
Feng Xiaoe841bac2015-12-11 17:09:20 -0800981 * @param {number} fieldNumber The field number.
982 * @param {string|number|boolean|Uint8Array|Array|undefined} value New value
Paul Yang7bff8392019-07-19 14:49:01 -0700983 * @return {T} return msg
984 * @template T
Feng Xiaoe841bac2015-12-11 17:09:20 -0800985 * @protected
986 */
987jspb.Message.setField = function(msg, fieldNumber, value) {
Paul Yang7bff8392019-07-19 14:49:01 -0700988 // TODO(b/35241823): replace this with a bounded generic when available
989 goog.asserts.assertInstanceof(msg, jspb.Message);
Feng Xiaoe841bac2015-12-11 17:09:20 -0800990 if (fieldNumber < msg.pivot_) {
991 msg.array[jspb.Message.getIndex_(msg, fieldNumber)] = value;
992 } else {
Jisi Liu09354db2017-07-18 15:38:30 -0700993 jspb.Message.maybeInitEmptyExtensionObject_(msg);
Feng Xiaoe841bac2015-12-11 17:09:20 -0800994 msg.extensionObject_[fieldNumber] = value;
995 }
Paul Yang7bff8392019-07-19 14:49:01 -0700996 return msg;
Feng Xiaoe841bac2015-12-11 17:09:20 -0800997};
998
999
1000/**
Adam Cozzette13fd0452017-09-12 10:32:01 -07001001 * Sets the value of a non-extension integer field of a proto3
Paul Yang7bff8392019-07-19 14:49:01 -07001002 * @param {T} msg A jspb proto.
Adam Cozzette13fd0452017-09-12 10:32:01 -07001003 * @param {number} fieldNumber The field number.
1004 * @param {number} value New value
Paul Yang7bff8392019-07-19 14:49:01 -07001005 * @return {T} return msg
1006 * @template T
Adam Cozzette13fd0452017-09-12 10:32:01 -07001007 * @protected
1008 */
1009jspb.Message.setProto3IntField = function(msg, fieldNumber, value) {
Paul Yang7bff8392019-07-19 14:49:01 -07001010 return jspb.Message.setFieldIgnoringDefault_(msg, fieldNumber, value, 0);
Adam Cozzette13fd0452017-09-12 10:32:01 -07001011};
1012
1013
1014/**
1015 * Sets the value of a non-extension floating point field of a proto3
Paul Yang7bff8392019-07-19 14:49:01 -07001016 * @param {T} msg A jspb proto.
Adam Cozzette13fd0452017-09-12 10:32:01 -07001017 * @param {number} fieldNumber The field number.
1018 * @param {number} value New value
Paul Yang7bff8392019-07-19 14:49:01 -07001019 * @return {T} return msg
1020 * @template T
Adam Cozzette13fd0452017-09-12 10:32:01 -07001021 * @protected
1022 */
1023jspb.Message.setProto3FloatField = function(msg, fieldNumber, value) {
Paul Yang7bff8392019-07-19 14:49:01 -07001024 return jspb.Message.setFieldIgnoringDefault_(msg, fieldNumber, value, 0.0);
Adam Cozzette13fd0452017-09-12 10:32:01 -07001025};
1026
1027
1028/**
1029 * Sets the value of a non-extension boolean field of a proto3
Paul Yang7bff8392019-07-19 14:49:01 -07001030 * @param {T} msg A jspb proto.
Adam Cozzette13fd0452017-09-12 10:32:01 -07001031 * @param {number} fieldNumber The field number.
1032 * @param {boolean} value New value
Paul Yang7bff8392019-07-19 14:49:01 -07001033 * @return {T} return msg
1034 * @template T
Adam Cozzette13fd0452017-09-12 10:32:01 -07001035 * @protected
1036 */
1037jspb.Message.setProto3BooleanField = function(msg, fieldNumber, value) {
Paul Yang7bff8392019-07-19 14:49:01 -07001038 return jspb.Message.setFieldIgnoringDefault_(msg, fieldNumber, value, false);
Adam Cozzette13fd0452017-09-12 10:32:01 -07001039};
1040
1041
1042/**
1043 * Sets the value of a non-extension String field of a proto3
Paul Yang7bff8392019-07-19 14:49:01 -07001044 * @param {T} msg A jspb proto.
Adam Cozzette13fd0452017-09-12 10:32:01 -07001045 * @param {number} fieldNumber The field number.
1046 * @param {string} value New value
Paul Yang7bff8392019-07-19 14:49:01 -07001047 * @return {T} return msg
1048 * @template T
Adam Cozzette13fd0452017-09-12 10:32:01 -07001049 * @protected
1050 */
1051jspb.Message.setProto3StringField = function(msg, fieldNumber, value) {
Paul Yang7bff8392019-07-19 14:49:01 -07001052 return jspb.Message.setFieldIgnoringDefault_(msg, fieldNumber, value, '');
Adam Cozzette13fd0452017-09-12 10:32:01 -07001053};
1054
1055
1056/**
1057 * Sets the value of a non-extension Bytes field of a proto3
Paul Yang7bff8392019-07-19 14:49:01 -07001058 * @param {T} msg A jspb proto.
Adam Cozzette13fd0452017-09-12 10:32:01 -07001059 * @param {number} fieldNumber The field number.
1060 * @param {!Uint8Array|string} value New value
Paul Yang7bff8392019-07-19 14:49:01 -07001061 * @return {T} return msg
1062 * @template T
Adam Cozzette13fd0452017-09-12 10:32:01 -07001063 * @protected
1064 */
1065jspb.Message.setProto3BytesField = function(msg, fieldNumber, value) {
Paul Yang7bff8392019-07-19 14:49:01 -07001066 return jspb.Message.setFieldIgnoringDefault_(msg, fieldNumber, value, '');
Adam Cozzette13fd0452017-09-12 10:32:01 -07001067};
1068
1069
1070/**
1071 * Sets the value of a non-extension enum field of a proto3
Paul Yang7bff8392019-07-19 14:49:01 -07001072 * @param {T} msg A jspb proto.
Adam Cozzette13fd0452017-09-12 10:32:01 -07001073 * @param {number} fieldNumber The field number.
1074 * @param {number} value New value
Paul Yang7bff8392019-07-19 14:49:01 -07001075 * @return {T} return msg
1076 * @template T
Adam Cozzette13fd0452017-09-12 10:32:01 -07001077 * @protected
1078 */
1079jspb.Message.setProto3EnumField = function(msg, fieldNumber, value) {
Paul Yang7bff8392019-07-19 14:49:01 -07001080 return jspb.Message.setFieldIgnoringDefault_(msg, fieldNumber, value, 0);
Adam Cozzette13fd0452017-09-12 10:32:01 -07001081};
1082
1083
Feng Xiao6bbe1972018-08-08 17:00:41 -07001084/**
1085 * Sets the value of a non-extension int field of a proto3 that has jstype set
1086 * to String.
Paul Yang7bff8392019-07-19 14:49:01 -07001087 * @param {T} msg A jspb proto.
Feng Xiao6bbe1972018-08-08 17:00:41 -07001088 * @param {number} fieldNumber The field number.
1089 * @param {string} value New value
Paul Yang7bff8392019-07-19 14:49:01 -07001090 * @return {T} return msg
1091 * @template T
Feng Xiao6bbe1972018-08-08 17:00:41 -07001092 * @protected
1093 */
1094jspb.Message.setProto3StringIntField = function(msg, fieldNumber, value) {
Paul Yang7bff8392019-07-19 14:49:01 -07001095 return jspb.Message.setFieldIgnoringDefault_(msg, fieldNumber, value, '0');
Feng Xiao6bbe1972018-08-08 17:00:41 -07001096};
Adam Cozzette13fd0452017-09-12 10:32:01 -07001097
1098/**
1099 * Sets the value of a non-extension primitive field, with proto3 (non-nullable
1100 * primitives) semantics of ignoring values that are equal to the type's
1101 * default.
Paul Yang7bff8392019-07-19 14:49:01 -07001102 * @param {T} msg A jspb proto.
Adam Cozzette13fd0452017-09-12 10:32:01 -07001103 * @param {number} fieldNumber The field number.
1104 * @param {!Uint8Array|string|number|boolean|undefined} value New value
1105 * @param {!Uint8Array|string|number|boolean} defaultValue The default value.
Paul Yang7bff8392019-07-19 14:49:01 -07001106 * @return {T} return msg
1107 * @template T
Adam Cozzette13fd0452017-09-12 10:32:01 -07001108 * @private
1109 */
1110jspb.Message.setFieldIgnoringDefault_ = function(
1111 msg, fieldNumber, value, defaultValue) {
Paul Yang7bff8392019-07-19 14:49:01 -07001112 // TODO(b/35241823): replace this with a bounded generic when available
1113 goog.asserts.assertInstanceof(msg, jspb.Message);
Feng Xiao6bbe1972018-08-08 17:00:41 -07001114 if (value !== defaultValue) {
Adam Cozzette13fd0452017-09-12 10:32:01 -07001115 jspb.Message.setField(msg, fieldNumber, value);
Artem Kustikov743a4322020-02-27 20:10:16 +03001116 } else if (fieldNumber < msg.pivot_) {
Adam Cozzette13fd0452017-09-12 10:32:01 -07001117 msg.array[jspb.Message.getIndex_(msg, fieldNumber)] = null;
Artem Kustikov743a4322020-02-27 20:10:16 +03001118 } else {
1119 jspb.Message.maybeInitEmptyExtensionObject_(msg);
1120 delete msg.extensionObject_[fieldNumber];
Adam Cozzette13fd0452017-09-12 10:32:01 -07001121 }
Paul Yang7bff8392019-07-19 14:49:01 -07001122 return msg;
Adam Cozzette13fd0452017-09-12 10:32:01 -07001123};
1124
1125
1126/**
Bo Yangcc8ca5b2016-09-19 13:45:07 -07001127 * Adds a value to a repeated, primitive field.
Paul Yang7bff8392019-07-19 14:49:01 -07001128 * @param {T} msg A jspb proto.
Bo Yangcc8ca5b2016-09-19 13:45:07 -07001129 * @param {number} fieldNumber The field number.
1130 * @param {string|number|boolean|!Uint8Array} value New value
1131 * @param {number=} opt_index Index where to put new value.
Paul Yang7bff8392019-07-19 14:49:01 -07001132 * @return {T} return msg
1133 * @template T
Bo Yangcc8ca5b2016-09-19 13:45:07 -07001134 * @protected
1135 */
1136jspb.Message.addToRepeatedField = function(msg, fieldNumber, value, opt_index) {
Paul Yang7bff8392019-07-19 14:49:01 -07001137 // TODO(b/35241823): replace this with a bounded generic when available
1138 goog.asserts.assertInstanceof(msg, jspb.Message);
Jisi Liu09354db2017-07-18 15:38:30 -07001139 var arr = jspb.Message.getRepeatedField(msg, fieldNumber);
Bo Yangcc8ca5b2016-09-19 13:45:07 -07001140 if (opt_index != undefined) {
1141 arr.splice(opt_index, 0, value);
1142 } else {
1143 arr.push(value);
1144 }
Paul Yang7bff8392019-07-19 14:49:01 -07001145 return msg;
Bo Yangcc8ca5b2016-09-19 13:45:07 -07001146};
1147
1148
1149/**
Feng Xiaoe841bac2015-12-11 17:09:20 -08001150 * Sets the value of a field in a oneof union and clears all other fields in
1151 * the union.
Paul Yang7bff8392019-07-19 14:49:01 -07001152 * @param {T} msg A jspb proto.
Feng Xiaoe841bac2015-12-11 17:09:20 -08001153 * @param {number} fieldNumber The field number.
1154 * @param {!Array<number>} oneof The fields belonging to the union.
1155 * @param {string|number|boolean|Uint8Array|Array|undefined} value New value
Paul Yang7bff8392019-07-19 14:49:01 -07001156 * @return {T} return msg
1157 * @template T
Feng Xiaoe841bac2015-12-11 17:09:20 -08001158 * @protected
1159 */
1160jspb.Message.setOneofField = function(msg, fieldNumber, oneof, value) {
Paul Yang7bff8392019-07-19 14:49:01 -07001161 // TODO(b/35241823): replace this with a bounded generic when available
1162 goog.asserts.assertInstanceof(msg, jspb.Message);
Feng Xiaoe841bac2015-12-11 17:09:20 -08001163 var currentCase = jspb.Message.computeOneofCase(msg, oneof);
1164 if (currentCase && currentCase !== fieldNumber && value !== undefined) {
1165 if (msg.wrappers_ && currentCase in msg.wrappers_) {
1166 msg.wrappers_[currentCase] = undefined;
1167 }
1168 jspb.Message.setField(msg, currentCase, undefined);
1169 }
Paul Yang7bff8392019-07-19 14:49:01 -07001170 return jspb.Message.setField(msg, fieldNumber, value);
Feng Xiaoe841bac2015-12-11 17:09:20 -08001171};
1172
1173
1174/**
1175 * Computes the selection in a oneof group for the given message, ensuring
1176 * only one field is set in the process.
1177 *
1178 * According to the protobuf language guide (
1179 * https://developers.google.com/protocol-buffers/docs/proto#oneof), "if the
1180 * parser encounters multiple members of the same oneof on the wire, only the
1181 * last member seen is used in the parsed message." Since JSPB serializes
1182 * messages to a JSON array, the "last member seen" will always be the field
1183 * with the greatest field number (directly corresponding to the greatest
1184 * array index).
1185 *
1186 * @param {!jspb.Message} msg A jspb proto.
1187 * @param {!Array<number>} oneof The field numbers belonging to the union.
1188 * @return {number} The field number currently set in the union, or 0 if none.
1189 * @protected
1190 */
1191jspb.Message.computeOneofCase = function(msg, oneof) {
1192 var oneofField;
1193 var oneofValue;
1194
Adam Cozzette92a7e772017-12-01 10:05:10 -08001195 for (var i = 0; i < oneof.length; i++) {
1196 var fieldNumber = oneof[i];
Feng Xiaoe841bac2015-12-11 17:09:20 -08001197 var value = jspb.Message.getField(msg, fieldNumber);
Adam Cozzette92a7e772017-12-01 10:05:10 -08001198 if (value != null) {
Feng Xiaoe841bac2015-12-11 17:09:20 -08001199 oneofField = fieldNumber;
1200 oneofValue = value;
1201 jspb.Message.setField(msg, fieldNumber, undefined);
1202 }
Adam Cozzette92a7e772017-12-01 10:05:10 -08001203 }
Feng Xiaoe841bac2015-12-11 17:09:20 -08001204
1205 if (oneofField) {
1206 // NB: We know the value is unique, so we can call jspb.Message.setField
1207 // directly instead of jpsb.Message.setOneofField. Also, setOneofField
1208 // calls this function.
1209 jspb.Message.setField(msg, oneofField, oneofValue);
1210 return oneofField;
1211 }
1212
1213 return 0;
1214};
1215
1216
1217/**
1218 * Gets and wraps a proto field on access.
1219 * @param {!jspb.Message} msg A jspb proto.
1220 * @param {function(new:jspb.Message, Array)} ctor Constructor for the field.
1221 * @param {number} fieldNumber The field number.
1222 * @param {number=} opt_required True (1) if this is a required field.
1223 * @return {jspb.Message} The field as a jspb proto.
1224 * @protected
1225 */
1226jspb.Message.getWrapperField = function(msg, ctor, fieldNumber, opt_required) {
1227 // TODO(mwr): Consider copying data and/or arrays.
1228 if (!msg.wrappers_) {
1229 msg.wrappers_ = {};
1230 }
1231 if (!msg.wrappers_[fieldNumber]) {
1232 var data = /** @type {Array} */ (jspb.Message.getField(msg, fieldNumber));
1233 if (opt_required || data) {
1234 // TODO(mwr): Remove existence test for always valid default protos.
1235 msg.wrappers_[fieldNumber] = new ctor(data);
1236 }
1237 }
1238 return /** @type {jspb.Message} */ (msg.wrappers_[fieldNumber]);
1239};
1240
1241
1242/**
1243 * Gets and wraps a repeated proto field on access.
1244 * @param {!jspb.Message} msg A jspb proto.
1245 * @param {function(new:jspb.Message, Array)} ctor Constructor for the field.
1246 * @param {number} fieldNumber The field number.
Feng Xiao6bbe1972018-08-08 17:00:41 -07001247 * @return {!Array<!jspb.Message>} The repeated field as an array of protos.
Feng Xiaoe841bac2015-12-11 17:09:20 -08001248 * @protected
1249 */
1250jspb.Message.getRepeatedWrapperField = function(msg, ctor, fieldNumber) {
Bo Yangcc8ca5b2016-09-19 13:45:07 -07001251 jspb.Message.wrapRepeatedField_(msg, ctor, fieldNumber);
1252 var val = msg.wrappers_[fieldNumber];
1253 if (val == jspb.Message.EMPTY_LIST_SENTINEL_) {
1254 val = msg.wrappers_[fieldNumber] = [];
1255 }
1256 return /** @type {!Array<!jspb.Message>} */ (val);
1257};
1258
1259
1260/**
1261 * Wraps underlying array into proto message representation if it wasn't done
1262 * before.
1263 * @param {!jspb.Message} msg A jspb proto.
1264 * @param {function(new:jspb.Message, ?Array)} ctor Constructor for the field.
1265 * @param {number} fieldNumber The field number.
1266 * @private
1267 */
1268jspb.Message.wrapRepeatedField_ = function(msg, ctor, fieldNumber) {
Feng Xiaoe841bac2015-12-11 17:09:20 -08001269 if (!msg.wrappers_) {
1270 msg.wrappers_ = {};
1271 }
1272 if (!msg.wrappers_[fieldNumber]) {
Jisi Liu09354db2017-07-18 15:38:30 -07001273 var data = jspb.Message.getRepeatedField(msg, fieldNumber);
Feng Xiaoe841bac2015-12-11 17:09:20 -08001274 for (var wrappers = [], i = 0; i < data.length; i++) {
1275 wrappers[i] = new ctor(data[i]);
1276 }
1277 msg.wrappers_[fieldNumber] = wrappers;
1278 }
Feng Xiaoe841bac2015-12-11 17:09:20 -08001279};
1280
1281
1282/**
1283 * Sets a proto field and syncs it to the backing array.
Paul Yang7bff8392019-07-19 14:49:01 -07001284 * @param {T} msg A jspb proto.
Feng Xiaoe841bac2015-12-11 17:09:20 -08001285 * @param {number} fieldNumber The field number.
Adam Cozzette5a76e632016-11-17 16:48:38 -08001286 * @param {?jspb.Message|?jspb.Map|undefined} value A new value for this proto
1287 * field.
Paul Yang7bff8392019-07-19 14:49:01 -07001288 * @return {T} the msg
1289 * @template T
Feng Xiaoe841bac2015-12-11 17:09:20 -08001290 * @protected
1291 */
1292jspb.Message.setWrapperField = function(msg, fieldNumber, value) {
Paul Yang7bff8392019-07-19 14:49:01 -07001293 // TODO(b/35241823): replace this with a bounded generic when available
1294 goog.asserts.assertInstanceof(msg, jspb.Message);
Feng Xiaoe841bac2015-12-11 17:09:20 -08001295 if (!msg.wrappers_) {
1296 msg.wrappers_ = {};
1297 }
1298 var data = value ? value.toArray() : value;
1299 msg.wrappers_[fieldNumber] = value;
Paul Yang7bff8392019-07-19 14:49:01 -07001300 return jspb.Message.setField(msg, fieldNumber, data);
Feng Xiaoe841bac2015-12-11 17:09:20 -08001301};
1302
1303
Paul Yang7bff8392019-07-19 14:49:01 -07001304
Feng Xiaoe841bac2015-12-11 17:09:20 -08001305/**
1306 * Sets a proto field in a oneof union and syncs it to the backing array.
Paul Yang7bff8392019-07-19 14:49:01 -07001307 * @param {T} msg A jspb proto.
Feng Xiaoe841bac2015-12-11 17:09:20 -08001308 * @param {number} fieldNumber The field number.
1309 * @param {!Array<number>} oneof The fields belonging to the union.
1310 * @param {jspb.Message|undefined} value A new value for this proto field.
Paul Yang7bff8392019-07-19 14:49:01 -07001311 * @return {T} the msg
1312 * @template T
Feng Xiaoe841bac2015-12-11 17:09:20 -08001313 * @protected
1314 */
1315jspb.Message.setOneofWrapperField = function(msg, fieldNumber, oneof, value) {
Paul Yang7bff8392019-07-19 14:49:01 -07001316 // TODO(b/35241823): replace this with a bounded generic when available
1317 goog.asserts.assertInstanceof(msg, jspb.Message);
Feng Xiaoe841bac2015-12-11 17:09:20 -08001318 if (!msg.wrappers_) {
1319 msg.wrappers_ = {};
1320 }
1321 var data = value ? value.toArray() : value;
1322 msg.wrappers_[fieldNumber] = value;
Paul Yang7bff8392019-07-19 14:49:01 -07001323 return jspb.Message.setOneofField(msg, fieldNumber, oneof, data);
Feng Xiaoe841bac2015-12-11 17:09:20 -08001324};
1325
1326
1327/**
1328 * Sets a repeated proto field and syncs it to the backing array.
Paul Yang7bff8392019-07-19 14:49:01 -07001329 * @param {T} msg A jspb proto.
Feng Xiaoe841bac2015-12-11 17:09:20 -08001330 * @param {number} fieldNumber The field number.
1331 * @param {Array<!jspb.Message>|undefined} value An array of protos.
Paul Yang7bff8392019-07-19 14:49:01 -07001332 * @return {T} the msg
1333 * @template T
Feng Xiaoe841bac2015-12-11 17:09:20 -08001334 * @protected
1335 */
1336jspb.Message.setRepeatedWrapperField = function(msg, fieldNumber, value) {
Paul Yang7bff8392019-07-19 14:49:01 -07001337 // TODO(b/35241823): replace this with a bounded generic when available
1338 goog.asserts.assertInstanceof(msg, jspb.Message);
Feng Xiaoe841bac2015-12-11 17:09:20 -08001339 if (!msg.wrappers_) {
1340 msg.wrappers_ = {};
1341 }
1342 value = value || [];
1343 for (var data = [], i = 0; i < value.length; i++) {
1344 data[i] = value[i].toArray();
1345 }
1346 msg.wrappers_[fieldNumber] = value;
Paul Yang7bff8392019-07-19 14:49:01 -07001347 return jspb.Message.setField(msg, fieldNumber, data);
Feng Xiaoe841bac2015-12-11 17:09:20 -08001348};
1349
1350
1351/**
Bo Yangcc8ca5b2016-09-19 13:45:07 -07001352 * Add a message to a repeated proto field.
1353 * @param {!jspb.Message} msg A jspb proto.
1354 * @param {number} fieldNumber The field number.
1355 * @param {T_CHILD|undefined} value Proto that will be added to the
1356 * repeated field.
1357 * @param {function(new:T_CHILD, ?Array=)} ctor The constructor of the
1358 * message type.
1359 * @param {number|undefined} index Index at which to insert the value.
1360 * @return {T_CHILD_NOT_UNDEFINED} proto that was inserted to the repeated field
1361 * @template MessageType
1362 * Use go/closure-ttl to declare a non-undefined version of T_CHILD. Replace the
1363 * undefined in blah|undefined with none. This is necessary because the compiler
1364 * will infer T_CHILD to be |undefined.
1365 * @template T_CHILD
1366 * @template T_CHILD_NOT_UNDEFINED :=
1367 * cond(isUnknown(T_CHILD), unknown(),
1368 * mapunion(T_CHILD, (X) =>
1369 * cond(eq(X, 'undefined'), none(), X)))
1370 * =:
1371 * @protected
1372 */
1373jspb.Message.addToRepeatedWrapperField = function(
1374 msg, fieldNumber, value, ctor, index) {
1375 jspb.Message.wrapRepeatedField_(msg, ctor, fieldNumber);
1376 var wrapperArray = msg.wrappers_[fieldNumber];
1377 if (!wrapperArray) {
1378 wrapperArray = msg.wrappers_[fieldNumber] = [];
1379 }
1380 var insertedValue = value ? value : new ctor();
Jisi Liu09354db2017-07-18 15:38:30 -07001381 var array = jspb.Message.getRepeatedField(msg, fieldNumber);
Bo Yangcc8ca5b2016-09-19 13:45:07 -07001382 if (index != undefined) {
1383 wrapperArray.splice(index, 0, insertedValue);
1384 array.splice(index, 0, insertedValue.toArray());
1385 } else {
1386 wrapperArray.push(insertedValue);
1387 array.push(insertedValue.toArray());
1388 }
1389 return insertedValue;
1390};
1391
1392
1393/**
Feng Xiaoe841bac2015-12-11 17:09:20 -08001394 * Converts a JsPb repeated message field into a map. The map will contain
1395 * protos unless an optional toObject function is given, in which case it will
1396 * contain objects suitable for Soy rendering.
1397 * @param {!Array<T>} field The repeated message field to be
1398 * converted.
1399 * @param {function() : string?} mapKeyGetterFn The function to get the key of
1400 * the map.
1401 * @param {?function(boolean=): Object|
1402 * function((boolean|undefined),T): Object} opt_toObjectFn The
1403 * toObject function for this field. We need to pass this for effective
1404 * dead code removal.
1405 * @param {boolean=} opt_includeInstance Whether to include the JSPB instance
1406 * for transitional soy proto support: http://goto/soy-param-migration
Adam Cozzette92a7e772017-12-01 10:05:10 -08001407 * @return {!Object<string, Object>} A map of proto or Soy objects.
Feng Xiaoe841bac2015-12-11 17:09:20 -08001408 * @template T
1409 */
1410jspb.Message.toMap = function(
1411 field, mapKeyGetterFn, opt_toObjectFn, opt_includeInstance) {
1412 var result = {};
1413 for (var i = 0; i < field.length; i++) {
1414 result[mapKeyGetterFn.call(field[i])] = opt_toObjectFn ?
1415 opt_toObjectFn.call(field[i], opt_includeInstance,
1416 /** @type {!jspb.Message} */ (field[i])) : field[i];
1417 }
1418 return result;
1419};
1420
1421
1422/**
Adam Cozzetted64a2d92016-06-29 15:23:27 -07001423 * Syncs all map fields' contents back to their underlying arrays.
1424 * @private
1425 */
1426jspb.Message.prototype.syncMapFields_ = function() {
1427 // This iterates over submessage, map, and repeated fields, which is intended.
1428 // Submessages can contain maps which also need to be synced.
1429 //
1430 // There is a lot of opportunity for optimization here. For example we could
1431 // statically determine that some messages have no submessages with maps and
1432 // optimize this method away for those just by generating one extra static
1433 // boolean per message type.
1434 if (this.wrappers_) {
1435 for (var fieldNumber in this.wrappers_) {
1436 var val = this.wrappers_[fieldNumber];
Joshua Haberman183cdec2020-06-01 16:32:31 -07001437 if (Array.isArray(val)) {
Adam Cozzetted64a2d92016-06-29 15:23:27 -07001438 for (var i = 0; i < val.length; i++) {
1439 if (val[i]) {
1440 val[i].toArray();
1441 }
1442 }
1443 } else {
1444 // Works for submessages and maps.
1445 if (val) {
1446 val.toArray();
1447 }
1448 }
1449 }
1450 }
1451};
1452
1453
1454/**
Feng Xiaoe841bac2015-12-11 17:09:20 -08001455 * Returns the internal array of this proto.
1456 * <p>Note: If you use this array to construct a second proto, the content
1457 * would then be partially shared between the two protos.
1458 * @return {!Array} The proto represented as an array.
1459 */
1460jspb.Message.prototype.toArray = function() {
Adam Cozzetted64a2d92016-06-29 15:23:27 -07001461 this.syncMapFields_();
Feng Xiaoe841bac2015-12-11 17:09:20 -08001462 return this.array;
1463};
1464
1465
1466
Paul Yang7f3e2372017-01-31 09:17:32 -08001467if (jspb.Message.GENERATE_TO_STRING) {
Feng Xiaoe841bac2015-12-11 17:09:20 -08001468
1469/**
1470 * Creates a string representation of the internal data array of this proto.
1471 * <p>NOTE: This string is *not* suitable for use in server requests.
1472 * @return {string} A string representation of this proto.
1473 * @override
1474 */
1475jspb.Message.prototype.toString = function() {
Adam Cozzetted64a2d92016-06-29 15:23:27 -07001476 this.syncMapFields_();
Feng Xiaoe841bac2015-12-11 17:09:20 -08001477 return this.array.toString();
1478};
1479
Paul Yang7f3e2372017-01-31 09:17:32 -08001480}
Feng Xiaoe841bac2015-12-11 17:09:20 -08001481
1482/**
1483 * Gets the value of the extension field from the extended object.
Adam Cozzette92a7e772017-12-01 10:05:10 -08001484 * @param {jspb.ExtensionFieldInfo<T>} fieldInfo Specifies the field to get.
Feng Xiaoe841bac2015-12-11 17:09:20 -08001485 * @return {T} The value of the field.
1486 * @template T
1487 */
1488jspb.Message.prototype.getExtension = function(fieldInfo) {
1489 if (!this.extensionObject_) {
1490 return undefined;
1491 }
1492 if (!this.wrappers_) {
1493 this.wrappers_ = {};
1494 }
1495 var fieldNumber = fieldInfo.fieldIndex;
1496 if (fieldInfo.isRepeated) {
Jisi Liu3b3c8ab2016-03-30 11:39:59 -07001497 if (fieldInfo.isMessageType()) {
Feng Xiaoe841bac2015-12-11 17:09:20 -08001498 if (!this.wrappers_[fieldNumber]) {
1499 this.wrappers_[fieldNumber] =
1500 goog.array.map(this.extensionObject_[fieldNumber] || [],
1501 function(arr) {
1502 return new fieldInfo.ctor(arr);
1503 });
1504 }
1505 return this.wrappers_[fieldNumber];
1506 } else {
1507 return this.extensionObject_[fieldNumber];
1508 }
1509 } else {
Jisi Liu3b3c8ab2016-03-30 11:39:59 -07001510 if (fieldInfo.isMessageType()) {
Feng Xiaoe841bac2015-12-11 17:09:20 -08001511 if (!this.wrappers_[fieldNumber] && this.extensionObject_[fieldNumber]) {
1512 this.wrappers_[fieldNumber] = new fieldInfo.ctor(
1513 /** @type {Array|undefined} */ (
1514 this.extensionObject_[fieldNumber]));
1515 }
1516 return this.wrappers_[fieldNumber];
1517 } else {
1518 return this.extensionObject_[fieldNumber];
1519 }
1520 }
1521};
1522
1523
1524/**
1525 * Sets the value of the extension field in the extended object.
1526 * @param {jspb.ExtensionFieldInfo} fieldInfo Specifies the field to set.
Jisi Liu3b3c8ab2016-03-30 11:39:59 -07001527 * @param {jspb.Message|string|Uint8Array|number|boolean|Array?} value The value
1528 * to set.
Bo Yangcc8ca5b2016-09-19 13:45:07 -07001529 * @return {THIS} For chaining
1530 * @this {THIS}
1531 * @template THIS
Feng Xiaoe841bac2015-12-11 17:09:20 -08001532 */
1533jspb.Message.prototype.setExtension = function(fieldInfo, value) {
Bo Yangcc8ca5b2016-09-19 13:45:07 -07001534 // Cast self, since the inferred THIS is unknown inside the function body.
1535 // https://github.com/google/closure-compiler/issues/1411#issuecomment-232442220
1536 var self = /** @type {!jspb.Message} */ (this);
1537 if (!self.wrappers_) {
1538 self.wrappers_ = {};
Feng Xiaoe841bac2015-12-11 17:09:20 -08001539 }
Bo Yangcc8ca5b2016-09-19 13:45:07 -07001540 jspb.Message.maybeInitEmptyExtensionObject_(self);
Feng Xiaoe841bac2015-12-11 17:09:20 -08001541 var fieldNumber = fieldInfo.fieldIndex;
1542 if (fieldInfo.isRepeated) {
1543 value = value || [];
Jisi Liu3b3c8ab2016-03-30 11:39:59 -07001544 if (fieldInfo.isMessageType()) {
Bo Yangcc8ca5b2016-09-19 13:45:07 -07001545 self.wrappers_[fieldNumber] = value;
1546 self.extensionObject_[fieldNumber] = goog.array.map(
Adam Cozzette0400cca2018-03-13 16:37:29 -07001547 /** @type {!Array<!jspb.Message>} */ (value), function(msg) {
Feng Xiaoe841bac2015-12-11 17:09:20 -08001548 return msg.toArray();
1549 });
1550 } else {
Bo Yangcc8ca5b2016-09-19 13:45:07 -07001551 self.extensionObject_[fieldNumber] = value;
Feng Xiaoe841bac2015-12-11 17:09:20 -08001552 }
1553 } else {
Jisi Liu3b3c8ab2016-03-30 11:39:59 -07001554 if (fieldInfo.isMessageType()) {
Bo Yangcc8ca5b2016-09-19 13:45:07 -07001555 self.wrappers_[fieldNumber] = value;
Adam Cozzette0400cca2018-03-13 16:37:29 -07001556 self.extensionObject_[fieldNumber] =
1557 value ? /** @type {!jspb.Message} */ (value).toArray() : value;
Feng Xiaoe841bac2015-12-11 17:09:20 -08001558 } else {
Bo Yangcc8ca5b2016-09-19 13:45:07 -07001559 self.extensionObject_[fieldNumber] = value;
Feng Xiaoe841bac2015-12-11 17:09:20 -08001560 }
1561 }
Bo Yangcc8ca5b2016-09-19 13:45:07 -07001562 return self;
Feng Xiaoe841bac2015-12-11 17:09:20 -08001563};
1564
1565
1566/**
1567 * Creates a difference object between two messages.
1568 *
1569 * The result will contain the top-level fields of m2 that differ from those of
1570 * m1 at any level of nesting. No data is cloned, the result object will
1571 * share its top-level elements with m2 (but not with m1).
1572 *
1573 * Note that repeated fields should not have null/undefined elements, but if
1574 * they do, this operation will treat repeated fields of different length as
1575 * the same if the only difference between them is due to trailing
1576 * null/undefined values.
1577 *
1578 * @param {!jspb.Message} m1 The first message object.
1579 * @param {!jspb.Message} m2 The second message object.
1580 * @return {!jspb.Message} The difference returned as a proto message.
1581 * Note that the returned message may be missing required fields. This is
1582 * currently tolerated in Js, but would cause an error if you tried to
1583 * send such a proto to the server. You can access the raw difference
1584 * array with result.toArray().
1585 * @throws {Error} If the messages are responses with different types.
1586 */
1587jspb.Message.difference = function(m1, m2) {
1588 if (!(m1 instanceof m2.constructor)) {
1589 throw new Error('Messages have different types.');
1590 }
1591 var arr1 = m1.toArray();
1592 var arr2 = m2.toArray();
1593 var res = [];
1594 var start = 0;
1595 var length = arr1.length > arr2.length ? arr1.length : arr2.length;
1596 if (m1.getJsPbMessageId()) {
1597 res[0] = m1.getJsPbMessageId();
1598 start = 1;
1599 }
1600 for (var i = start; i < length; i++) {
1601 if (!jspb.Message.compareFields(arr1[i], arr2[i])) {
1602 res[i] = arr2[i];
1603 }
1604 }
1605 return new m1.constructor(res);
1606};
1607
1608
1609/**
1610 * Tests whether two messages are equal.
1611 * @param {jspb.Message|undefined} m1 The first message object.
1612 * @param {jspb.Message|undefined} m2 The second message object.
1613 * @return {boolean} true if both messages are null/undefined, or if both are
1614 * of the same type and have the same field values.
1615 */
1616jspb.Message.equals = function(m1, m2) {
1617 return m1 == m2 || (!!(m1 && m2) && (m1 instanceof m2.constructor) &&
1618 jspb.Message.compareFields(m1.toArray(), m2.toArray()));
1619};
1620
1621
1622/**
Jisi Liu3b3c8ab2016-03-30 11:39:59 -07001623 * Compares two message extension fields recursively.
1624 * @param {!Object} extension1 The first field.
1625 * @param {!Object} extension2 The second field.
1626 * @return {boolean} true if the extensions are null/undefined, or otherwise
1627 * equal.
1628 */
1629jspb.Message.compareExtensions = function(extension1, extension2) {
1630 extension1 = extension1 || {};
1631 extension2 = extension2 || {};
1632
1633 var keys = {};
1634 for (var name in extension1) {
1635 keys[name] = 0;
1636 }
1637 for (var name in extension2) {
1638 keys[name] = 0;
1639 }
1640 for (name in keys) {
1641 if (!jspb.Message.compareFields(extension1[name], extension2[name])) {
1642 return false;
1643 }
1644 }
1645 return true;
1646};
1647
1648
1649/**
Feng Xiaoe841bac2015-12-11 17:09:20 -08001650 * Compares two message fields recursively.
1651 * @param {*} field1 The first field.
1652 * @param {*} field2 The second field.
1653 * @return {boolean} true if the fields are null/undefined, or otherwise equal.
1654 */
1655jspb.Message.compareFields = function(field1, field2) {
Jisi Liu3b3c8ab2016-03-30 11:39:59 -07001656 // If the fields are trivially equal, they're equal.
1657 if (field1 == field2) return true;
1658
Jisi Liu3b3c8ab2016-03-30 11:39:59 -07001659 if (!goog.isObject(field1) || !goog.isObject(field2)) {
Adam Cozzette0400cca2018-03-13 16:37:29 -07001660 // NaN != NaN so we cover this case.
Rafi Kamal58d44202019-11-11 17:06:56 -08001661 if ((typeof field1 === 'number' && isNaN(field1)) ||
1662 (typeof field2 === 'number' && isNaN(field2))) {
Adam Cozzette0400cca2018-03-13 16:37:29 -07001663 // One of the fields might be a string 'NaN'.
1664 return String(field1) == String(field2);
1665 }
1666 // If the fields aren't trivially equal and one of them isn't an object,
1667 // they can't possibly be equal.
Jisi Liu3b3c8ab2016-03-30 11:39:59 -07001668 return false;
1669 }
1670
1671 // We have two objects. If they're different types, they're not equal.
1672 field1 = /** @type {!Object} */(field1);
1673 field2 = /** @type {!Object} */(field2);
1674 if (field1.constructor != field2.constructor) return false;
1675
1676 // If both are Uint8Arrays, compare them element-by-element.
1677 if (jspb.Message.SUPPORTS_UINT8ARRAY_ && field1.constructor === Uint8Array) {
1678 var bytes1 = /** @type {!Uint8Array} */(field1);
1679 var bytes2 = /** @type {!Uint8Array} */(field2);
1680 if (bytes1.length != bytes2.length) return false;
1681 for (var i = 0; i < bytes1.length; i++) {
1682 if (bytes1[i] != bytes2[i]) return false;
Feng Xiaoe841bac2015-12-11 17:09:20 -08001683 }
Jisi Liu3b3c8ab2016-03-30 11:39:59 -07001684 return true;
1685 }
1686
1687 // If they're both Arrays, compare them element by element except for the
1688 // optional extension objects at the end, which we compare separately.
1689 if (field1.constructor === Array) {
Adam Cozzette0400cca2018-03-13 16:37:29 -07001690 var typedField1 = /** @type {!Array<?>} */ (field1);
1691 var typedField2 = /** @type {!Array<?>} */ (field2);
Jisi Liu3b3c8ab2016-03-30 11:39:59 -07001692 var extension1 = undefined;
1693 var extension2 = undefined;
1694
Adam Cozzette0400cca2018-03-13 16:37:29 -07001695 var length = Math.max(typedField1.length, typedField2.length);
Jisi Liu3b3c8ab2016-03-30 11:39:59 -07001696 for (var i = 0; i < length; i++) {
Adam Cozzette0400cca2018-03-13 16:37:29 -07001697 var val1 = typedField1[i];
1698 var val2 = typedField2[i];
Jisi Liu3b3c8ab2016-03-30 11:39:59 -07001699
1700 if (val1 && (val1.constructor == Object)) {
1701 goog.asserts.assert(extension1 === undefined);
Adam Cozzette0400cca2018-03-13 16:37:29 -07001702 goog.asserts.assert(i === typedField1.length - 1);
Jisi Liu3b3c8ab2016-03-30 11:39:59 -07001703 extension1 = val1;
Feng Xiaoe841bac2015-12-11 17:09:20 -08001704 val1 = undefined;
1705 }
Jisi Liu3b3c8ab2016-03-30 11:39:59 -07001706
1707 if (val2 && (val2.constructor == Object)) {
1708 goog.asserts.assert(extension2 === undefined);
Adam Cozzette0400cca2018-03-13 16:37:29 -07001709 goog.asserts.assert(i === typedField2.length - 1);
Jisi Liu3b3c8ab2016-03-30 11:39:59 -07001710 extension2 = val2;
Feng Xiaoe841bac2015-12-11 17:09:20 -08001711 val2 = undefined;
1712 }
Jisi Liu3b3c8ab2016-03-30 11:39:59 -07001713
Feng Xiaoe841bac2015-12-11 17:09:20 -08001714 if (!jspb.Message.compareFields(val1, val2)) {
1715 return false;
1716 }
1717 }
Jisi Liu3b3c8ab2016-03-30 11:39:59 -07001718
1719 if (extension1 || extension2) {
1720 extension1 = extension1 || {};
1721 extension2 = extension2 || {};
1722 return jspb.Message.compareExtensions(extension1, extension2);
Feng Xiaoe841bac2015-12-11 17:09:20 -08001723 }
Jisi Liu3b3c8ab2016-03-30 11:39:59 -07001724
Feng Xiaoe841bac2015-12-11 17:09:20 -08001725 return true;
1726 }
Jisi Liu3b3c8ab2016-03-30 11:39:59 -07001727
1728 // If they're both plain Objects (i.e. extensions), compare them as
1729 // extensions.
1730 if (field1.constructor === Object) {
1731 return jspb.Message.compareExtensions(field1, field2);
1732 }
1733
1734 throw new Error('Invalid type in JSPB array');
Feng Xiaoe841bac2015-12-11 17:09:20 -08001735};
1736
1737
1738/**
Bo Yangcc8ca5b2016-09-19 13:45:07 -07001739 * Templated, type-safe cloneMessage definition.
1740 * @return {THIS}
1741 * @this {THIS}
1742 * @template THIS
1743 */
1744jspb.Message.prototype.cloneMessage = function() {
1745 return jspb.Message.cloneMessage(/** @type {!jspb.Message} */ (this));
1746};
1747
1748/**
1749 * Alias clone to cloneMessage. goog.object.unsafeClone uses clone to
1750 * efficiently copy objects. Without this alias, copying jspb messages comes
1751 * with a large performance penalty.
1752 * @return {THIS}
1753 * @this {THIS}
1754 * @template THIS
1755 */
1756jspb.Message.prototype.clone = function() {
1757 return jspb.Message.cloneMessage(/** @type {!jspb.Message} */ (this));
1758};
1759
1760/**
1761 * Static clone function. NOTE: A type-safe method called "cloneMessage"
1762 * exists
Feng Xiaoe841bac2015-12-11 17:09:20 -08001763 * on each generated JsPb class. Do not call this function directly.
1764 * @param {!jspb.Message} msg A message to clone.
1765 * @return {!jspb.Message} A deep clone of the given message.
1766 */
1767jspb.Message.clone = function(msg) {
1768 // Although we could include the wrappers, we leave them out here.
1769 return jspb.Message.cloneMessage(msg);
1770};
1771
1772
1773/**
1774 * @param {!jspb.Message} msg A message to clone.
1775 * @return {!jspb.Message} A deep clone of the given message.
1776 * @protected
1777 */
1778jspb.Message.cloneMessage = function(msg) {
1779 // Although we could include the wrappers, we leave them out here.
1780 return new msg.constructor(jspb.Message.clone_(msg.toArray()));
1781};
1782
1783
1784/**
1785 * Takes 2 messages of the same type and copies the contents of the first
1786 * message into the second. After this the 2 messages will equals in terms of
1787 * value semantics but share no state. All data in the destination message will
1788 * be overridden.
1789 *
1790 * @param {MESSAGE} fromMessage Message that will be copied into toMessage.
1791 * @param {MESSAGE} toMessage Message which will receive a copy of fromMessage
1792 * as its contents.
1793 * @template MESSAGE
1794 */
1795jspb.Message.copyInto = function(fromMessage, toMessage) {
1796 goog.asserts.assertInstanceof(fromMessage, jspb.Message);
1797 goog.asserts.assertInstanceof(toMessage, jspb.Message);
1798 goog.asserts.assert(fromMessage.constructor == toMessage.constructor,
1799 'Copy source and target message should have the same type.');
1800 var copyOfFrom = jspb.Message.clone(fromMessage);
1801
1802 var to = toMessage.toArray();
1803 var from = copyOfFrom.toArray();
1804
1805 // Empty destination in case it has more values at the end of the array.
1806 to.length = 0;
1807 // and then copy everything from the new to the existing message.
1808 for (var i = 0; i < from.length; i++) {
1809 to[i] = from[i];
1810 }
1811
1812 // This is either null or empty for a fresh copy.
1813 toMessage.wrappers_ = copyOfFrom.wrappers_;
1814 // Just a reference into the shared array.
1815 toMessage.extensionObject_ = copyOfFrom.extensionObject_;
1816};
1817
1818
1819/**
1820 * Helper for cloning an internal JsPb object.
1821 * @param {!Object} obj A JsPb object, eg, a field, to be cloned.
1822 * @return {!Object} A clone of the input object.
1823 * @private
1824 */
1825jspb.Message.clone_ = function(obj) {
1826 var o;
Joshua Haberman183cdec2020-06-01 16:32:31 -07001827 if (Array.isArray(obj)) {
Feng Xiaoe841bac2015-12-11 17:09:20 -08001828 // Allocate array of correct size.
1829 var clonedArray = new Array(obj.length);
1830 // Use array iteration where possible because it is faster than for-in.
1831 for (var i = 0; i < obj.length; i++) {
Adam Cozzette0400cca2018-03-13 16:37:29 -07001832 o = obj[i];
1833 if (o != null) {
1834 // NOTE:redundant null check existing for NTI compatibility.
1835 // see b/70515949
1836 clonedArray[i] = (typeof o == 'object') ?
1837 jspb.Message.clone_(goog.asserts.assert(o)) :
1838 o;
Feng Xiaoe841bac2015-12-11 17:09:20 -08001839 }
1840 }
1841 return clonedArray;
1842 }
Adam Cozzetted64a2d92016-06-29 15:23:27 -07001843 if (jspb.Message.SUPPORTS_UINT8ARRAY_ && obj instanceof Uint8Array) {
1844 return new Uint8Array(obj);
1845 }
Feng Xiaoe841bac2015-12-11 17:09:20 -08001846 var clone = {};
1847 for (var key in obj) {
Adam Cozzette0400cca2018-03-13 16:37:29 -07001848 o = obj[key];
1849 if (o != null) {
1850 // NOTE:redundant null check existing for NTI compatibility.
1851 // see b/70515949
1852 clone[key] = (typeof o == 'object') ?
1853 jspb.Message.clone_(goog.asserts.assert(o)) :
1854 o;
Feng Xiaoe841bac2015-12-11 17:09:20 -08001855 }
1856 }
1857 return clone;
1858};
1859
1860
1861/**
1862 * Registers a JsPb message type id with its constructor.
1863 * @param {string} id The id for this type of message.
1864 * @param {Function} constructor The message constructor.
1865 */
1866jspb.Message.registerMessageType = function(id, constructor) {
Brian Wignalla104dff2020-01-08 13:18:20 -05001867 // This is needed so we can later access messageId directly on the constructor,
Feng Xiaoe841bac2015-12-11 17:09:20 -08001868 // otherwise it is not available due to 'property collapsing' by the compiler.
Adam Cozzette0400cca2018-03-13 16:37:29 -07001869 /**
1870 * @suppress {strictMissingProperties} messageId is not defined on Function
1871 */
Feng Xiaoe841bac2015-12-11 17:09:20 -08001872 constructor.messageId = id;
1873};
Feng Xiaoe841bac2015-12-11 17:09:20 -08001874/**
1875 * The extensions registered on MessageSet. This is a map of extension
1876 * field number to field info object. This should be considered as a
1877 * private API.
1878 *
1879 * This is similar to [jspb class name].extensions object for
1880 * non-MessageSet. We special case MessageSet so that we do not need
1881 * to goog.require MessageSet from classes that extends MessageSet.
1882 *
Adam Cozzette92a7e772017-12-01 10:05:10 -08001883 * @type {!Object<number, jspb.ExtensionFieldInfo>}
Feng Xiaoe841bac2015-12-11 17:09:20 -08001884 */
1885jspb.Message.messageSetExtensions = {};
Adam Cozzette92a7e772017-12-01 10:05:10 -08001886
1887/**
1888 * @type {!Object<number, jspb.ExtensionFieldBinaryInfo>}
1889 */
Bo Yangcc8ca5b2016-09-19 13:45:07 -07001890jspb.Message.messageSetExtensionsBinary = {};