blob: cef9aefd32365ad14cef1186660bbb0f49d3fee9 [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 *
34 * @author mwr@google.com (Mark Rawling)
35 */
36
37goog.provide('jspb.ExtensionFieldInfo');
38goog.provide('jspb.Message');
39
40goog.require('goog.array');
41goog.require('goog.asserts');
42goog.require('goog.json');
43goog.require('goog.object');
44
45// Not needed in compilation units that have no protos with xids.
46goog.forwardDeclare('xid.String');
47
48
49
50/**
51 * Stores information for a single extension field.
52 *
53 * For example, an extension field defined like so:
54 *
55 * extend BaseMessage {
56 * optional MyMessage my_field = 123;
57 * }
58 *
59 * will result in an ExtensionFieldInfo object with these properties:
60 *
61 * {
62 * fieldIndex: 123,
63 * fieldName: {my_field_renamed: 0},
64 * ctor: proto.example.MyMessage,
65 * toObjectFn: proto.example.MyMessage.toObject,
66 * isRepeated: 0
67 * }
68 *
69 * We include `toObjectFn` to allow the JSCompiler to perform dead-code removal
70 * on unused toObject() methods.
71 *
72 * If an extension field is primitive, ctor and toObjectFn will be null.
73 * isRepeated should be 0 or 1.
74 *
75 * binary{Reader,Writer}Fn and (if message type) binaryMessageSerializeFn are
76 * always provided. binaryReaderFn and binaryWriterFn are references to the
77 * appropriate methods on BinaryReader/BinaryWriter to read/write the value of
78 * this extension, and binaryMessageSerializeFn is a reference to the message
79 * class's .serializeBinary method, if available.
80 *
81 * @param {number} fieldNumber
82 * @param {Object} fieldName This has the extension field name as a property.
83 * @param {?function(new: jspb.Message, Array=)} ctor
84 * @param {?function((boolean|undefined),!jspb.Message):!Object} toObjectFn
85 * @param {number} isRepeated
86 * @param {?function(number,?)=} opt_binaryReaderFn
87 * @param {?function(number,?)|function(number,?,?,?,?,?)=} opt_binaryWriterFn
88 * @param {?function(?,?)=} opt_binaryMessageSerializeFn
89 * @param {?function(?,?)=} opt_binaryMessageDeserializeFn
90 * @param {?boolean=} opt_isPacked
91 * @constructor
92 * @struct
93 * @template T
94 */
95jspb.ExtensionFieldInfo = function(fieldNumber, fieldName, ctor, toObjectFn,
96 isRepeated, opt_binaryReaderFn, opt_binaryWriterFn,
97 opt_binaryMessageSerializeFn, opt_binaryMessageDeserializeFn,
98 opt_isPacked) {
99 /** @const */
100 this.fieldIndex = fieldNumber;
101 /** @const */
102 this.fieldName = fieldName;
103 /** @const */
104 this.ctor = ctor;
105 /** @const */
106 this.toObjectFn = toObjectFn;
107 /** @const */
108 this.binaryReaderFn = opt_binaryReaderFn;
109 /** @const */
110 this.binaryWriterFn = opt_binaryWriterFn;
111 /** @const */
112 this.binaryMessageSerializeFn = opt_binaryMessageSerializeFn;
113 /** @const */
114 this.binaryMessageDeserializeFn = opt_binaryMessageDeserializeFn;
115 /** @const */
116 this.isRepeated = isRepeated;
117 /** @const */
118 this.isPacked = opt_isPacked;
119};
120
121
122/**
123 * Base class for all JsPb messages.
124 * @constructor
125 * @struct
126 */
127jspb.Message = function() {
128};
129
130
131/**
132 * @define {boolean} Whether to generate toObject methods for objects. Turn
133 * this off, if you do not want toObject to be ever used in your project.
134 * When turning off this flag, consider adding a conformance test that bans
135 * calling toObject. Enabling this will disable the JSCompiler's ability to
136 * dead code eliminate fields used in protocol buffers that are never used
137 * in an application.
138 */
139goog.define('jspb.Message.GENERATE_TO_OBJECT', true);
140
141
142/**
143 * @define {boolean} Whether to generate fromObject methods for objects. Turn
144 * this off, if you do not want fromObject to be ever used in your project.
145 * When turning off this flag, consider adding a conformance test that bans
146 * calling fromObject. Enabling this might disable the JSCompiler's ability
147 * to dead code eliminate fields used in protocol buffers that are never
148 * used in an application.
149 * NOTE: By default no protos actually have a fromObject method. You need to
150 * add the jspb.generate_from_object options to the proto definition to
151 * activate the feature.
152 * By default this is enabled for test code only.
153 */
154goog.define('jspb.Message.GENERATE_FROM_OBJECT', !goog.DISALLOW_TEST_ONLY_CODE);
155
156
157/**
158 * @define {boolean} Turning on this flag does NOT change the behavior of JSPB
159 * and only affects private internal state. It may, however, break some
160 * tests that use naive deeply-equals algorithms, because using a proto
161 * mutates its internal state.
162 * Projects are advised to turn this flag always on.
163 */
164goog.define('jspb.Message.MINIMIZE_MEMORY_ALLOCATIONS', COMPILED);
165// TODO(b/19419436) Turn this on by default.
166
167
168/**
169 * The internal data array.
170 * @type {!Array}
171 * @protected
172 */
173jspb.Message.prototype.array;
174
175
176/**
177 * Wrappers are the constructed instances of message-type fields. They are built
178 * on demand from the raw array data. Includes message fields, repeated message
179 * fields and extension message fields. Indexed by field number.
180 * @type {Object}
181 * @private
182 */
183jspb.Message.prototype.wrappers_;
184
185
186/**
187 * The object that contains extension fields, if any. This is an object that
188 * maps from a proto field number to the field's value.
189 * @type {Object}
190 * @private
191 */
192jspb.Message.prototype.extensionObject_;
193
194
195/**
196 * Non-extension fields with a field number at or above the pivot are
197 * stored in the extension object (in addition to all extension fields).
198 * @type {number}
199 * @private
200 */
201jspb.Message.prototype.pivot_;
202
203
204/**
205 * The JsPb message_id of this proto.
206 * @type {string|undefined} the message id or undefined if this message
207 * has no id.
208 * @private
209 */
210jspb.Message.prototype.messageId_;
211
212
213/**
214 * The xid of this proto type (The same for all instances of a proto). Provides
215 * a way to identify a proto by stable obfuscated name.
216 * @see {xid}.
217 * Available if {@link jspb.generate_xid} is added as a Message option to
218 * a protocol buffer.
219 * @const {!xid.String|undefined} The xid or undefined if message is
220 * annotated to generate the xid.
221 */
222jspb.Message.prototype.messageXid;
223
224
225
226/**
227 * Returns the JsPb message_id of this proto.
228 * @return {string|undefined} the message id or undefined if this message
229 * has no id.
230 */
231jspb.Message.prototype.getJsPbMessageId = function() {
232 return this.messageId_;
233};
234
235
236/**
237 * An offset applied to lookups into this.array to account for the presence or
238 * absence of a messageId at position 0. For response messages, this will be 0.
239 * Otherwise, it will be -1 so that the first array position is not wasted.
240 * @type {number}
241 * @private
242 */
243jspb.Message.prototype.arrayIndexOffset_;
244
245
246/**
247 * Returns the index into msg.array at which the proto field with tag number
248 * fieldNumber will be located.
249 * @param {!jspb.Message} msg Message for which we're calculating an index.
250 * @param {number} fieldNumber The field number.
251 * @return {number} The index.
252 * @private
253 */
254jspb.Message.getIndex_ = function(msg, fieldNumber) {
255 return fieldNumber + msg.arrayIndexOffset_;
256};
257
258
259/**
260 * Initializes a JsPb Message.
261 * @param {!jspb.Message} msg The JsPb proto to modify.
262 * @param {Array|undefined} data An initial data array.
263 * @param {string|number} messageId For response messages, the message id or ''
264 * if no message id is specified. For non-response messages, 0.
265 * @param {number} suggestedPivot The field number at which to start putting
266 * fields into the extension object. This is only used if data does not
267 * contain an extension object already. -1 if no extension object is
268 * required for this message type.
269 * @param {Array<number>} repeatedFields The message's repeated fields.
270 * @param {Array<!Array<number>>=} opt_oneofFields The fields belonging to
271 * each of the message's oneof unions.
272 * @protected
273 */
274jspb.Message.initialize = function(
275 msg, data, messageId, suggestedPivot, repeatedFields, opt_oneofFields) {
276 msg.wrappers_ = jspb.Message.MINIMIZE_MEMORY_ALLOCATIONS ? null : {};
277 if (!data) {
278 data = messageId ? [messageId] : [];
279 }
280 msg.messageId_ = messageId ? String(messageId) : undefined;
281 // If the messageId is 0, this message is not a response message, so we shift
282 // array indices down by 1 so as not to waste the first position in the array,
283 // which would otherwise go unused.
284 msg.arrayIndexOffset_ = messageId === 0 ? -1 : 0;
285 msg.array = data;
286 jspb.Message.materializeExtensionObject_(msg, suggestedPivot);
287 if (repeatedFields) {
288 for (var i = 0; i < repeatedFields.length; i++) {
289 var fieldNumber = repeatedFields[i];
290 if (fieldNumber < msg.pivot_) {
291 var index = jspb.Message.getIndex_(msg, fieldNumber);
292 msg.array[index] = msg.array[index] ||
293 (jspb.Message.MINIMIZE_MEMORY_ALLOCATIONS ?
294 jspb.Message.EMPTY_LIST_SENTINEL_ :
295 []);
296 } else {
297 msg.extensionObject_[fieldNumber] =
298 msg.extensionObject_[fieldNumber] ||
299 (jspb.Message.MINIMIZE_MEMORY_ALLOCATIONS ?
300 jspb.Message.EMPTY_LIST_SENTINEL_ :
301 []);
302 }
303 }
304 }
305
306 if (opt_oneofFields && opt_oneofFields.length) {
307 // Compute the oneof case for each union. This ensures only one value is
308 // set in the union.
309 goog.array.forEach(
310 opt_oneofFields, goog.partial(jspb.Message.computeOneofCase, msg));
311 }
312};
313
314
315/**
316 * Used to mark empty repeated fields. Serializes to null when serialized
317 * to JSON.
318 * When reading a repeated field readers must check the return value against
319 * this value and return and replace it with a new empty array if it is
320 * present.
321 * @private @const {!Object}
322 */
323jspb.Message.EMPTY_LIST_SENTINEL_ = goog.DEBUG && Object.freeze ?
324 Object.freeze([]) :
325 [];
326
327
328/**
329 * Ensures that the array contains an extension object if necessary.
330 * If the array contains an extension object in its last position, then the
331 * object is kept in place and its position is used as the pivot. If not, then
332 * create an extension object using suggestedPivot. If suggestedPivot is -1,
333 * we don't have an extension object at all, in which case all fields are stored
334 * in the array.
335 * @param {!jspb.Message} msg The JsPb proto to modify.
336 * @param {number} suggestedPivot See description for initialize().
337 * @private
338 */
339jspb.Message.materializeExtensionObject_ = function(msg, suggestedPivot) {
340 if (msg.array.length) {
341 var foundIndex = msg.array.length - 1;
342 var obj = msg.array[foundIndex];
343 // Normal fields are never objects, so we can be sure that if we find an
344 // object here, then it's the extension object. However, we must ensure that
345 // the object is not an array, since arrays are valid field values.
346 // NOTE(lukestebbing): We avoid looking at .length to avoid a JIT bug
347 // in Safari on iOS 8. See the description of CL/86511464 for details.
348 if (obj && typeof obj == 'object' && !goog.isArray(obj)) {
349 msg.pivot_ = foundIndex - msg.arrayIndexOffset_;
350 msg.extensionObject_ = obj;
351 return;
352 }
353 }
354 // This complexity exists because we keep all extension fields in the
355 // extensionObject_ regardless of proto field number. Changing this would
356 // simplify the code here, but it would require changing the serialization
357 // format from the server, which is not backwards compatible.
358 // TODO(jshneier): Should we just treat extension fields the same as
359 // non-extension fields, and select whether they appear in the object or in
360 // the array purely based on tag number? This would allow simplifying all the
361 // get/setExtension logic, but it would require the breaking change described
362 // above.
363 if (suggestedPivot > -1) {
364 msg.pivot_ = suggestedPivot;
365 var pivotIndex = jspb.Message.getIndex_(msg, suggestedPivot);
366 if (!jspb.Message.MINIMIZE_MEMORY_ALLOCATIONS) {
367 msg.extensionObject_ = msg.array[pivotIndex] = {};
368 } else {
369 // Initialize to null to avoid changing the shape of the proto when it
370 // gets eventually set.
371 msg.extensionObject_ = null;
372 }
373 } else {
374 msg.pivot_ = Number.MAX_VALUE;
375 }
376};
377
378
379/**
380 * Creates an empty extensionObject_ if non exists.
381 * @param {!jspb.Message} msg The JsPb proto to modify.
382 * @private
383 */
384jspb.Message.maybeInitEmptyExtensionObject_ = function(msg) {
385 var pivotIndex = jspb.Message.getIndex_(msg, msg.pivot_);
386 if (!msg.array[pivotIndex]) {
387 msg.extensionObject_ = msg.array[pivotIndex] = {};
388 }
389};
390
391
392/**
393 * Converts a JsPb repeated message field into an object list.
394 * @param {!Array<T>} field The repeated message field to be
395 * converted.
396 * @param {?function(boolean=): Object|
397 * function((boolean|undefined),T): Object} toObjectFn The toObject
398 * function for this field. We need to pass this for effective dead code
399 * removal.
400 * @param {boolean=} opt_includeInstance Whether to include the JSPB instance
401 * for transitional soy proto support: http://goto/soy-param-migration
402 * @return {!Array<Object>} An array of converted message objects.
403 * @template T
404 */
405jspb.Message.toObjectList = function(field, toObjectFn, opt_includeInstance) {
406 // Not using goog.array.map in the generated code to keep it small.
407 // And not using it here to avoid a function call.
408 var result = [];
409 for (var i = 0; i < field.length; i++) {
410 result[i] = toObjectFn.call(field[i], opt_includeInstance,
411 /** @type {!jspb.Message} */ (field[i]));
412 }
413 return result;
414};
415
416
417/**
418 * Adds a proto's extension data to a Soy rendering object.
419 * @param {!jspb.Message} proto The proto whose extensions to convert.
420 * @param {!Object} obj The Soy object to add converted extension data to.
421 * @param {!Object} extensions The proto class' registered extensions.
422 * @param {function(jspb.ExtensionFieldInfo) : *} getExtensionFn The proto
423 * class' getExtension function. Passed for effective dead code removal.
424 * @param {boolean=} opt_includeInstance Whether to include the JSPB instance
425 * for transitional soy proto support: http://goto/soy-param-migration
426 */
427jspb.Message.toObjectExtension = function(proto, obj, extensions,
428 getExtensionFn, opt_includeInstance) {
429 for (var fieldNumber in extensions) {
430 var fieldInfo = extensions[fieldNumber];
431 var value = getExtensionFn.call(proto, fieldInfo);
432 if (value) {
433 for (var name in fieldInfo.fieldName) {
434 if (fieldInfo.fieldName.hasOwnProperty(name)) {
435 break; // the compiled field name
436 }
437 }
438 if (!fieldInfo.toObjectFn) {
439 obj[name] = value;
440 } else {
441 if (fieldInfo.isRepeated) {
442 obj[name] = jspb.Message.toObjectList(
443 /** @type {!Array<jspb.Message>} */ (value),
444 fieldInfo.toObjectFn, opt_includeInstance);
445 } else {
446 obj[name] = fieldInfo.toObjectFn(opt_includeInstance, value);
447 }
448 }
449 }
450 }
451};
452
453
454/**
455 * Writes a proto's extension data to a binary-format output stream.
456 * @param {!jspb.Message} proto The proto whose extensions to convert.
457 * @param {*} writer The binary-format writer to write to.
458 * @param {!Object} extensions The proto class' registered extensions.
459 * @param {function(jspb.ExtensionFieldInfo) : *} getExtensionFn The proto
460 * class' getExtension function. Passed for effective dead code removal.
461 */
462jspb.Message.serializeBinaryExtensions = function(proto, writer, extensions,
463 getExtensionFn) {
464 for (var fieldNumber in extensions) {
465 var fieldInfo = extensions[fieldNumber];
466 // The old codegen doesn't add the extra fields to ExtensionFieldInfo, so we
467 // need to gracefully error-out here rather than produce a null dereference
468 // below.
469 if (!fieldInfo.binaryWriterFn) {
470 throw new Error('Message extension present that was generated ' +
471 'without binary serialization support');
472 }
473 var value = getExtensionFn.call(proto, fieldInfo);
474 if (value) {
475 if (fieldInfo.ctor) { // is this a message type?
476 // If the message type of the extension was generated without binary
477 // support, there may not be a binary message serializer function, and
478 // we can't know when we codegen the extending message that the extended
479 // message may require binary support, so we can *only* catch this error
480 // here, at runtime (and this decoupled codegen is the whole point of
481 // extensions!).
482 if (fieldInfo.binaryMessageSerializeFn) {
483 fieldInfo.binaryWriterFn.call(writer, fieldInfo.fieldIndex,
484 value, fieldInfo.binaryMessageSerializeFn);
485 } else {
486 throw new Error('Message extension present holding submessage ' +
487 'without binary support enabled, and message is ' +
488 'being serialized to binary format');
489 }
490 } else {
491 fieldInfo.binaryWriterFn.call(writer, fieldInfo.fieldIndex, value);
492 }
493 }
494 }
495};
496
497
498/**
499 * Reads an extension field from the given reader and, if a valid extension,
500 * sets the extension value.
501 * @param {!jspb.Message} msg A jspb proto.
502 * @param {{skipField:function(),getFieldNumber:function():number}} reader
503 * @param {!Object} extensions The extensions object.
504 * @param {function(jspb.ExtensionFieldInfo)} getExtensionFn
505 * @param {function(jspb.ExtensionFieldInfo, ?)} setExtensionFn
506 */
507jspb.Message.readBinaryExtension = function(msg, reader, extensions,
508 getExtensionFn, setExtensionFn) {
509 var fieldInfo = extensions[reader.getFieldNumber()];
510 if (!fieldInfo) {
511 reader.skipField();
512 return;
513 }
514 if (!fieldInfo.binaryReaderFn) {
515 throw new Error('Deserializing extension whose generated code does not ' +
516 'support binary format');
517 }
518
519 var value;
520 if (fieldInfo.ctor) {
521 // Message type.
522 value = new fieldInfo.ctor();
523 fieldInfo.binaryReaderFn.call(
524 reader, value, fieldInfo.binaryMessageDeserializeFn);
525 } else {
526 // All other types.
527 value = fieldInfo.binaryReaderFn.call(reader);
528 }
529
530 if (fieldInfo.isRepeated && !fieldInfo.isPacked) {
531 var currentList = getExtensionFn.call(msg, fieldInfo);
532 if (!currentList) {
533 setExtensionFn.call(msg, fieldInfo, [value]);
534 } else {
535 currentList.push(value);
536 }
537 } else {
538 setExtensionFn.call(msg, fieldInfo, value);
539 }
540};
541
542
543/**
544 * Gets the value of a non-extension field.
545 * @param {!jspb.Message} msg A jspb proto.
546 * @param {number} fieldNumber The field number.
547 * @return {string|number|boolean|Uint8Array|Array|null|undefined}
548 * The field's value.
549 * @protected
550 */
551jspb.Message.getField = function(msg, fieldNumber) {
552 if (fieldNumber < msg.pivot_) {
553 var index = jspb.Message.getIndex_(msg, fieldNumber);
554 var val = msg.array[index];
555 if (val === jspb.Message.EMPTY_LIST_SENTINEL_) {
556 return msg.array[index] = [];
557 }
558 return val;
559 } else {
560 var val = msg.extensionObject_[fieldNumber];
561 if (val === jspb.Message.EMPTY_LIST_SENTINEL_) {
562 return msg.extensionObject_[fieldNumber] = [];
563 }
564 return val;
565 }
566};
567
568
569/**
570 * Gets the value of a non-extension primitive field, with proto3 (non-nullable
571 * primitives) semantics. Returns `defaultValue` if the field is not otherwise
572 * set.
573 * @template T
574 * @param {!jspb.Message} msg A jspb proto.
575 * @param {number} fieldNumber The field number.
576 * @param {T} defaultValue The default value.
577 * @return {T} The field's value.
578 * @protected
579 */
580jspb.Message.getFieldProto3 = function(msg, fieldNumber, defaultValue) {
581 var value = jspb.Message.getField(msg, fieldNumber);
582 if (value == null) {
583 return defaultValue;
584 } else {
585 return value;
586 }
587};
588
589
590/**
591 * Sets the value of a non-extension field.
592 * @param {!jspb.Message} msg A jspb proto.
593 * @param {number} fieldNumber The field number.
594 * @param {string|number|boolean|Uint8Array|Array|undefined} value New value
595 * @protected
596 */
597jspb.Message.setField = function(msg, fieldNumber, value) {
598 if (fieldNumber < msg.pivot_) {
599 msg.array[jspb.Message.getIndex_(msg, fieldNumber)] = value;
600 } else {
601 msg.extensionObject_[fieldNumber] = value;
602 }
603};
604
605
606/**
607 * Sets the value of a field in a oneof union and clears all other fields in
608 * the union.
609 * @param {!jspb.Message} msg A jspb proto.
610 * @param {number} fieldNumber The field number.
611 * @param {!Array<number>} oneof The fields belonging to the union.
612 * @param {string|number|boolean|Uint8Array|Array|undefined} value New value
613 * @protected
614 */
615jspb.Message.setOneofField = function(msg, fieldNumber, oneof, value) {
616 var currentCase = jspb.Message.computeOneofCase(msg, oneof);
617 if (currentCase && currentCase !== fieldNumber && value !== undefined) {
618 if (msg.wrappers_ && currentCase in msg.wrappers_) {
619 msg.wrappers_[currentCase] = undefined;
620 }
621 jspb.Message.setField(msg, currentCase, undefined);
622 }
623 jspb.Message.setField(msg, fieldNumber, value);
624};
625
626
627/**
628 * Computes the selection in a oneof group for the given message, ensuring
629 * only one field is set in the process.
630 *
631 * According to the protobuf language guide (
632 * https://developers.google.com/protocol-buffers/docs/proto#oneof), "if the
633 * parser encounters multiple members of the same oneof on the wire, only the
634 * last member seen is used in the parsed message." Since JSPB serializes
635 * messages to a JSON array, the "last member seen" will always be the field
636 * with the greatest field number (directly corresponding to the greatest
637 * array index).
638 *
639 * @param {!jspb.Message} msg A jspb proto.
640 * @param {!Array<number>} oneof The field numbers belonging to the union.
641 * @return {number} The field number currently set in the union, or 0 if none.
642 * @protected
643 */
644jspb.Message.computeOneofCase = function(msg, oneof) {
645 var oneofField;
646 var oneofValue;
647
648 goog.array.forEach(oneof, function(fieldNumber) {
649 var value = jspb.Message.getField(msg, fieldNumber);
650 if (goog.isDefAndNotNull(value)) {
651 oneofField = fieldNumber;
652 oneofValue = value;
653 jspb.Message.setField(msg, fieldNumber, undefined);
654 }
655 });
656
657 if (oneofField) {
658 // NB: We know the value is unique, so we can call jspb.Message.setField
659 // directly instead of jpsb.Message.setOneofField. Also, setOneofField
660 // calls this function.
661 jspb.Message.setField(msg, oneofField, oneofValue);
662 return oneofField;
663 }
664
665 return 0;
666};
667
668
669/**
670 * Gets and wraps a proto field on access.
671 * @param {!jspb.Message} msg A jspb proto.
672 * @param {function(new:jspb.Message, Array)} ctor Constructor for the field.
673 * @param {number} fieldNumber The field number.
674 * @param {number=} opt_required True (1) if this is a required field.
675 * @return {jspb.Message} The field as a jspb proto.
676 * @protected
677 */
678jspb.Message.getWrapperField = function(msg, ctor, fieldNumber, opt_required) {
679 // TODO(mwr): Consider copying data and/or arrays.
680 if (!msg.wrappers_) {
681 msg.wrappers_ = {};
682 }
683 if (!msg.wrappers_[fieldNumber]) {
684 var data = /** @type {Array} */ (jspb.Message.getField(msg, fieldNumber));
685 if (opt_required || data) {
686 // TODO(mwr): Remove existence test for always valid default protos.
687 msg.wrappers_[fieldNumber] = new ctor(data);
688 }
689 }
690 return /** @type {jspb.Message} */ (msg.wrappers_[fieldNumber]);
691};
692
693
694/**
695 * Gets and wraps a repeated proto field on access.
696 * @param {!jspb.Message} msg A jspb proto.
697 * @param {function(new:jspb.Message, Array)} ctor Constructor for the field.
698 * @param {number} fieldNumber The field number.
699 * @return {Array<!jspb.Message>} The repeated field as an array of protos.
700 * @protected
701 */
702jspb.Message.getRepeatedWrapperField = function(msg, ctor, fieldNumber) {
703 if (!msg.wrappers_) {
704 msg.wrappers_ = {};
705 }
706 if (!msg.wrappers_[fieldNumber]) {
707 var data = jspb.Message.getField(msg, fieldNumber);
708 for (var wrappers = [], i = 0; i < data.length; i++) {
709 wrappers[i] = new ctor(data[i]);
710 }
711 msg.wrappers_[fieldNumber] = wrappers;
712 }
713 var val = msg.wrappers_[fieldNumber];
714 if (val == jspb.Message.EMPTY_LIST_SENTINEL_) {
715 val = msg.wrappers_[fieldNumber] = [];
716 }
717 return /** @type {Array<!jspb.Message>} */ (val);
718};
719
720
721/**
722 * Sets a proto field and syncs it to the backing array.
723 * @param {!jspb.Message} msg A jspb proto.
724 * @param {number} fieldNumber The field number.
725 * @param {jspb.Message|undefined} value A new value for this proto field.
726 * @protected
727 */
728jspb.Message.setWrapperField = function(msg, fieldNumber, value) {
729 if (!msg.wrappers_) {
730 msg.wrappers_ = {};
731 }
732 var data = value ? value.toArray() : value;
733 msg.wrappers_[fieldNumber] = value;
734 jspb.Message.setField(msg, fieldNumber, data);
735};
736
737
738/**
739 * Sets a proto field in a oneof union and syncs it to the backing array.
740 * @param {!jspb.Message} msg A jspb proto.
741 * @param {number} fieldNumber The field number.
742 * @param {!Array<number>} oneof The fields belonging to the union.
743 * @param {jspb.Message|undefined} value A new value for this proto field.
744 * @protected
745 */
746jspb.Message.setOneofWrapperField = function(msg, fieldNumber, oneof, value) {
747 if (!msg.wrappers_) {
748 msg.wrappers_ = {};
749 }
750 var data = value ? value.toArray() : value;
751 msg.wrappers_[fieldNumber] = value;
752 jspb.Message.setOneofField(msg, fieldNumber, oneof, data);
753};
754
755
756/**
757 * Sets a repeated proto field and syncs it to the backing array.
758 * @param {!jspb.Message} msg A jspb proto.
759 * @param {number} fieldNumber The field number.
760 * @param {Array<!jspb.Message>|undefined} value An array of protos.
761 * @protected
762 */
763jspb.Message.setRepeatedWrapperField = function(msg, fieldNumber, value) {
764 if (!msg.wrappers_) {
765 msg.wrappers_ = {};
766 }
767 value = value || [];
768 for (var data = [], i = 0; i < value.length; i++) {
769 data[i] = value[i].toArray();
770 }
771 msg.wrappers_[fieldNumber] = value;
772 jspb.Message.setField(msg, fieldNumber, data);
773};
774
775
776/**
777 * Converts a JsPb repeated message field into a map. The map will contain
778 * protos unless an optional toObject function is given, in which case it will
779 * contain objects suitable for Soy rendering.
780 * @param {!Array<T>} field The repeated message field to be
781 * converted.
782 * @param {function() : string?} mapKeyGetterFn The function to get the key of
783 * the map.
784 * @param {?function(boolean=): Object|
785 * function((boolean|undefined),T): Object} opt_toObjectFn The
786 * toObject function for this field. We need to pass this for effective
787 * dead code removal.
788 * @param {boolean=} opt_includeInstance Whether to include the JSPB instance
789 * for transitional soy proto support: http://goto/soy-param-migration
790 * @return {!Object.<string, Object>} A map of proto or Soy objects.
791 * @template T
792 */
793jspb.Message.toMap = function(
794 field, mapKeyGetterFn, opt_toObjectFn, opt_includeInstance) {
795 var result = {};
796 for (var i = 0; i < field.length; i++) {
797 result[mapKeyGetterFn.call(field[i])] = opt_toObjectFn ?
798 opt_toObjectFn.call(field[i], opt_includeInstance,
799 /** @type {!jspb.Message} */ (field[i])) : field[i];
800 }
801 return result;
802};
803
804
805/**
806 * Returns the internal array of this proto.
807 * <p>Note: If you use this array to construct a second proto, the content
808 * would then be partially shared between the two protos.
809 * @return {!Array} The proto represented as an array.
810 */
811jspb.Message.prototype.toArray = function() {
812 return this.array;
813};
814
815
816
817
818/**
819 * Creates a string representation of the internal data array of this proto.
820 * <p>NOTE: This string is *not* suitable for use in server requests.
821 * @return {string} A string representation of this proto.
822 * @override
823 */
824jspb.Message.prototype.toString = function() {
825 return this.array.toString();
826};
827
828
829/**
830 * Gets the value of the extension field from the extended object.
831 * @param {jspb.ExtensionFieldInfo.<T>} fieldInfo Specifies the field to get.
832 * @return {T} The value of the field.
833 * @template T
834 */
835jspb.Message.prototype.getExtension = function(fieldInfo) {
836 if (!this.extensionObject_) {
837 return undefined;
838 }
839 if (!this.wrappers_) {
840 this.wrappers_ = {};
841 }
842 var fieldNumber = fieldInfo.fieldIndex;
843 if (fieldInfo.isRepeated) {
844 if (fieldInfo.ctor) {
845 if (!this.wrappers_[fieldNumber]) {
846 this.wrappers_[fieldNumber] =
847 goog.array.map(this.extensionObject_[fieldNumber] || [],
848 function(arr) {
849 return new fieldInfo.ctor(arr);
850 });
851 }
852 return this.wrappers_[fieldNumber];
853 } else {
854 return this.extensionObject_[fieldNumber];
855 }
856 } else {
857 if (fieldInfo.ctor) {
858 if (!this.wrappers_[fieldNumber] && this.extensionObject_[fieldNumber]) {
859 this.wrappers_[fieldNumber] = new fieldInfo.ctor(
860 /** @type {Array|undefined} */ (
861 this.extensionObject_[fieldNumber]));
862 }
863 return this.wrappers_[fieldNumber];
864 } else {
865 return this.extensionObject_[fieldNumber];
866 }
867 }
868};
869
870
871/**
872 * Sets the value of the extension field in the extended object.
873 * @param {jspb.ExtensionFieldInfo} fieldInfo Specifies the field to set.
874 * @param {jspb.Message|string|number|boolean|Array} value The value to set.
875 */
876jspb.Message.prototype.setExtension = function(fieldInfo, value) {
877 if (!this.wrappers_) {
878 this.wrappers_ = {};
879 }
880 jspb.Message.maybeInitEmptyExtensionObject_(this);
881 var fieldNumber = fieldInfo.fieldIndex;
882 if (fieldInfo.isRepeated) {
883 value = value || [];
884 if (fieldInfo.ctor) {
885 this.wrappers_[fieldNumber] = value;
886 this.extensionObject_[fieldNumber] = goog.array.map(
887 /** @type {Array<jspb.Message>} */ (value), function(msg) {
888 return msg.toArray();
889 });
890 } else {
891 this.extensionObject_[fieldNumber] = value;
892 }
893 } else {
894 if (fieldInfo.ctor) {
895 this.wrappers_[fieldNumber] = value;
896 this.extensionObject_[fieldNumber] = value ? value.toArray() : value;
897 } else {
898 this.extensionObject_[fieldNumber] = value;
899 }
900 }
901};
902
903
904/**
905 * Creates a difference object between two messages.
906 *
907 * The result will contain the top-level fields of m2 that differ from those of
908 * m1 at any level of nesting. No data is cloned, the result object will
909 * share its top-level elements with m2 (but not with m1).
910 *
911 * Note that repeated fields should not have null/undefined elements, but if
912 * they do, this operation will treat repeated fields of different length as
913 * the same if the only difference between them is due to trailing
914 * null/undefined values.
915 *
916 * @param {!jspb.Message} m1 The first message object.
917 * @param {!jspb.Message} m2 The second message object.
918 * @return {!jspb.Message} The difference returned as a proto message.
919 * Note that the returned message may be missing required fields. This is
920 * currently tolerated in Js, but would cause an error if you tried to
921 * send such a proto to the server. You can access the raw difference
922 * array with result.toArray().
923 * @throws {Error} If the messages are responses with different types.
924 */
925jspb.Message.difference = function(m1, m2) {
926 if (!(m1 instanceof m2.constructor)) {
927 throw new Error('Messages have different types.');
928 }
929 var arr1 = m1.toArray();
930 var arr2 = m2.toArray();
931 var res = [];
932 var start = 0;
933 var length = arr1.length > arr2.length ? arr1.length : arr2.length;
934 if (m1.getJsPbMessageId()) {
935 res[0] = m1.getJsPbMessageId();
936 start = 1;
937 }
938 for (var i = start; i < length; i++) {
939 if (!jspb.Message.compareFields(arr1[i], arr2[i])) {
940 res[i] = arr2[i];
941 }
942 }
943 return new m1.constructor(res);
944};
945
946
947/**
948 * Tests whether two messages are equal.
949 * @param {jspb.Message|undefined} m1 The first message object.
950 * @param {jspb.Message|undefined} m2 The second message object.
951 * @return {boolean} true if both messages are null/undefined, or if both are
952 * of the same type and have the same field values.
953 */
954jspb.Message.equals = function(m1, m2) {
955 return m1 == m2 || (!!(m1 && m2) && (m1 instanceof m2.constructor) &&
956 jspb.Message.compareFields(m1.toArray(), m2.toArray()));
957};
958
959
960/**
961 * Compares two message fields recursively.
962 * @param {*} field1 The first field.
963 * @param {*} field2 The second field.
964 * @return {boolean} true if the fields are null/undefined, or otherwise equal.
965 */
966jspb.Message.compareFields = function(field1, field2) {
967 if (goog.isObject(field1) && goog.isObject(field2)) {
968 var keys = {}, name, extensionObject1, extensionObject2;
969 for (name in field1) {
970 field1.hasOwnProperty(name) && (keys[name] = 0);
971 }
972 for (name in field2) {
973 field2.hasOwnProperty(name) && (keys[name] = 0);
974 }
975 for (name in keys) {
976 var val1 = field1[name], val2 = field2[name];
977 if (goog.isObject(val1) && !goog.isArray(val1)) {
978 if (extensionObject1 !== undefined) {
979 throw new Error('invalid jspb state');
980 }
981 extensionObject1 = goog.object.isEmpty(val1) ? undefined : val1;
982 val1 = undefined;
983 }
984 if (goog.isObject(val2) && !goog.isArray(val2)) {
985 if (extensionObject2 !== undefined) {
986 throw new Error('invalid jspb state');
987 }
988 extensionObject2 = goog.object.isEmpty(val2) ? undefined : val2;
989 val2 = undefined;
990 }
991 if (!jspb.Message.compareFields(val1, val2)) {
992 return false;
993 }
994 }
995 if (extensionObject1 || extensionObject2) {
996 return jspb.Message.compareFields(extensionObject1, extensionObject2);
997 }
998 return true;
999 }
1000 // Primitive fields, null and undefined compare as equal.
1001 // This also forces booleans and 0/1 to compare as equal to ensure
1002 // compatibility with the jspb serializer.
1003 return field1 == field2;
1004};
1005
1006
1007/**
1008 * Static clone function. NOTE: A type-safe method called "cloneMessage" exists
1009 * on each generated JsPb class. Do not call this function directly.
1010 * @param {!jspb.Message} msg A message to clone.
1011 * @return {!jspb.Message} A deep clone of the given message.
1012 */
1013jspb.Message.clone = function(msg) {
1014 // Although we could include the wrappers, we leave them out here.
1015 return jspb.Message.cloneMessage(msg);
1016};
1017
1018
1019/**
1020 * @param {!jspb.Message} msg A message to clone.
1021 * @return {!jspb.Message} A deep clone of the given message.
1022 * @protected
1023 */
1024jspb.Message.cloneMessage = function(msg) {
1025 // Although we could include the wrappers, we leave them out here.
1026 return new msg.constructor(jspb.Message.clone_(msg.toArray()));
1027};
1028
1029
1030/**
1031 * Takes 2 messages of the same type and copies the contents of the first
1032 * message into the second. After this the 2 messages will equals in terms of
1033 * value semantics but share no state. All data in the destination message will
1034 * be overridden.
1035 *
1036 * @param {MESSAGE} fromMessage Message that will be copied into toMessage.
1037 * @param {MESSAGE} toMessage Message which will receive a copy of fromMessage
1038 * as its contents.
1039 * @template MESSAGE
1040 */
1041jspb.Message.copyInto = function(fromMessage, toMessage) {
1042 goog.asserts.assertInstanceof(fromMessage, jspb.Message);
1043 goog.asserts.assertInstanceof(toMessage, jspb.Message);
1044 goog.asserts.assert(fromMessage.constructor == toMessage.constructor,
1045 'Copy source and target message should have the same type.');
1046 var copyOfFrom = jspb.Message.clone(fromMessage);
1047
1048 var to = toMessage.toArray();
1049 var from = copyOfFrom.toArray();
1050
1051 // Empty destination in case it has more values at the end of the array.
1052 to.length = 0;
1053 // and then copy everything from the new to the existing message.
1054 for (var i = 0; i < from.length; i++) {
1055 to[i] = from[i];
1056 }
1057
1058 // This is either null or empty for a fresh copy.
1059 toMessage.wrappers_ = copyOfFrom.wrappers_;
1060 // Just a reference into the shared array.
1061 toMessage.extensionObject_ = copyOfFrom.extensionObject_;
1062};
1063
1064
1065/**
1066 * Helper for cloning an internal JsPb object.
1067 * @param {!Object} obj A JsPb object, eg, a field, to be cloned.
1068 * @return {!Object} A clone of the input object.
1069 * @private
1070 */
1071jspb.Message.clone_ = function(obj) {
1072 var o;
1073 if (goog.isArray(obj)) {
1074 // Allocate array of correct size.
1075 var clonedArray = new Array(obj.length);
1076 // Use array iteration where possible because it is faster than for-in.
1077 for (var i = 0; i < obj.length; i++) {
1078 if ((o = obj[i]) != null) {
1079 clonedArray[i] = typeof o == 'object' ? jspb.Message.clone_(o) : o;
1080 }
1081 }
1082 return clonedArray;
1083 }
1084 var clone = {};
1085 for (var key in obj) {
1086 if ((o = obj[key]) != null) {
1087 clone[key] = typeof o == 'object' ? jspb.Message.clone_(o) : o;
1088 }
1089 }
1090 return clone;
1091};
1092
1093
1094/**
1095 * Registers a JsPb message type id with its constructor.
1096 * @param {string} id The id for this type of message.
1097 * @param {Function} constructor The message constructor.
1098 */
1099jspb.Message.registerMessageType = function(id, constructor) {
1100 jspb.Message.registry_[id] = constructor;
1101 // This is needed so we can later access messageId directly on the contructor,
1102 // otherwise it is not available due to 'property collapsing' by the compiler.
1103 constructor.messageId = id;
1104};
1105
1106
1107/**
1108 * The registry of message ids to message constructors.
1109 * @private
1110 */
1111jspb.Message.registry_ = {};
1112
1113
1114/**
1115 * The extensions registered on MessageSet. This is a map of extension
1116 * field number to field info object. This should be considered as a
1117 * private API.
1118 *
1119 * This is similar to [jspb class name].extensions object for
1120 * non-MessageSet. We special case MessageSet so that we do not need
1121 * to goog.require MessageSet from classes that extends MessageSet.
1122 *
1123 * @type {!Object.<number, jspb.ExtensionFieldInfo>}
1124 */
1125jspb.Message.messageSetExtensions = {};