aboutsummaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
Diffstat (limited to 'js')
-rw-r--r--js/binary/constants.js4
-rw-r--r--js/binary/decoder_test.js3
-rw-r--r--js/binary/reader_test.js25
-rw-r--r--js/binary/writer.js36
-rw-r--r--js/map.js482
-rw-r--r--js/message.js97
-rw-r--r--js/message_test.js33
-rw-r--r--js/package.json16
-rw-r--r--js/test.proto5
-rw-r--r--js/testbinary.proto27
10 files changed, 689 insertions, 39 deletions
diff --git a/js/binary/constants.js b/js/binary/constants.js
index 836216bf..ef5fecdd 100644
--- a/js/binary/constants.js
+++ b/js/binary/constants.js
@@ -141,8 +141,8 @@ jspb.ReaderFunction;
/**
* A writer function serializes a message to a BinaryWriter.
- * @typedef {!function(!jspb.Message, !jspb.BinaryWriter):void |
- * !function(!jspb.ConstBinaryMessage, !jspb.BinaryWriter):void}
+ * @typedef {function((!jspb.Message|!jspb.ConstBinaryMessage),
+ * !jspb.BinaryWriter):void}
*/
jspb.WriterFunction;
diff --git a/js/binary/decoder_test.js b/js/binary/decoder_test.js
index d045e912..ac312648 100644
--- a/js/binary/decoder_test.js
+++ b/js/binary/decoder_test.js
@@ -147,9 +147,8 @@ function doTestSignedValue(readValue,
describe('binaryDecoderTest', function() {
/**
* Tests the decoder instance cache.
- * @suppress {visibility}
*/
- it('testInstanceCache', function() {
+ it('testInstanceCache', /** @suppress {visibility} */ function() {
// Empty the instance caches.
jspb.BinaryDecoder.instanceCache_ = [];
diff --git a/js/binary/reader_test.js b/js/binary/reader_test.js
index db674cf8..95711385 100644
--- a/js/binary/reader_test.js
+++ b/js/binary/reader_test.js
@@ -52,9 +52,8 @@ goog.require('jspb.BinaryWriter');
describe('binaryReaderTest', function() {
/**
* Tests the reader instance cache.
- * @suppress {visibility}
*/
- it('testInstanceCaches', function() {
+ it('testInstanceCaches', /** @suppress {visibility} */ function() {
var writer = new jspb.BinaryWriter();
var dummyMessage = /** @type {!jspb.BinaryMessage} */({});
writer.writeMessage(1, dummyMessage, goog.nullFunction);
@@ -131,9 +130,8 @@ describe('binaryReaderTest', function() {
/**
* Verifies that misuse of the reader class triggers assertions.
- * @suppress {checkTypes|visibility}
*/
- it('testReadErrors', function() {
+ it('testReadErrors', /** @suppress {checkTypes|visibility} */ function() {
// Calling readMessage on a non-delimited field should trigger an
// assertion.
var reader = jspb.BinaryReader.alloc([8, 1]);
@@ -200,7 +198,7 @@ describe('binaryReaderTest', function() {
* @private
* @suppress {missingProperties}
*/
- function doTestUnsignedField_(readField,
+ var doTestUnsignedField_ = function(readField,
writeField, epsilon, upperLimit, filter) {
assertNotNull(readField);
assertNotNull(writeField);
@@ -252,7 +250,7 @@ describe('binaryReaderTest', function() {
* @private
* @suppress {missingProperties}
*/
- function doTestSignedField_(readField,
+ var doTestSignedField_ = function(readField,
writeField, epsilon, lowerLimit, upperLimit, filter) {
var writer = new jspb.BinaryWriter();
@@ -321,12 +319,12 @@ describe('binaryReaderTest', function() {
* Tests fields that use varint encoding.
*/
it('testVarintFields', function() {
- assertNotNull(jspb.BinaryReader.prototype.readUint32);
- assertNotNull(jspb.BinaryReader.prototype.writeUint32);
- assertNotNull(jspb.BinaryReader.prototype.readUint64);
- assertNotNull(jspb.BinaryReader.prototype.writeUint64);
- assertNotNull(jspb.BinaryReader.prototype.readBool);
- assertNotNull(jspb.BinaryReader.prototype.writeBool);
+ assertNotUndefined(jspb.BinaryReader.prototype.readUint32);
+ assertNotUndefined(jspb.BinaryWriter.prototype.writeUint32);
+ assertNotUndefined(jspb.BinaryReader.prototype.readUint64);
+ assertNotUndefined(jspb.BinaryWriter.prototype.writeUint64);
+ assertNotUndefined(jspb.BinaryReader.prototype.readBool);
+ assertNotUndefined(jspb.BinaryWriter.prototype.writeBool);
doTestUnsignedField_(
jspb.BinaryReader.prototype.readUint32,
jspb.BinaryWriter.prototype.writeUint32,
@@ -369,8 +367,7 @@ describe('binaryReaderTest', function() {
var bytesCount = (hexString.length + 1) / 3;
var bytes = new Uint8Array(bytesCount);
for (var i = 0; i < bytesCount; i++) {
- byte = parseInt(hexString.substring(i * 3, i * 3 + 2), 16);
- bytes[i] = byte;
+ bytes[i] = parseInt(hexString.substring(i * 3, i * 3 + 2), 16);
}
var reader = jspb.BinaryReader.alloc(bytes);
reader.nextField();
diff --git a/js/binary/writer.js b/js/binary/writer.js
index be4478ee..3eb2f1bd 100644
--- a/js/binary/writer.js
+++ b/js/binary/writer.js
@@ -717,11 +717,19 @@ jspb.BinaryWriter.prototype.writeBytes = function(field, value) {
/**
* Writes a message to the buffer.
- * @template MessageType
* @param {number} field The field number.
* @param {?MessageType} value The message to write.
- * @param {!jspb.WriterFunction} writerCallback Will be invoked with the value
- * to write and the writer to write it with.
+ * @param {function(MessageTypeNonNull, !jspb.BinaryWriter)} writerCallback
+ * Will be invoked with the value to write and the writer to write it with.
+ * @template MessageType
+ * Use go/closure-ttl to declare a non-nullable version of MessageType. Replace
+ * the null in blah|null with none. This is necessary because the compiler will
+ * infer MessageType to be nullable if the value parameter is nullable.
+ * @template MessageTypeNonNull :=
+ * cond(isUnknown(MessageType), unknown(),
+ * mapunion(MessageType, (X) =>
+ * cond(eq(X, 'null'), none(), X)))
+ * =:
*/
jspb.BinaryWriter.prototype.writeMessage = function(
field, value, writerCallback) {
@@ -735,12 +743,20 @@ jspb.BinaryWriter.prototype.writeMessage = function(
/**
* Writes a group message to the buffer.
*
- * @template MessageType
* @param {number} field The field number.
* @param {?MessageType} value The message to write, wrapped with START_GROUP /
* END_GROUP tags. Will be a no-op if 'value' is null.
- * @param {!jspb.WriterFunction} writerCallback Will be invoked with the value
- * to write and the writer to write it with.
+ * @param {function(MessageTypeNonNull, !jspb.BinaryWriter)} writerCallback
+ * Will be invoked with the value to write and the writer to write it with.
+ * @template MessageType
+ * Use go/closure-ttl to declare a non-nullable version of MessageType. Replace
+ * the null in blah|null with none. This is necessary because the compiler will
+ * infer MessageType to be nullable if the value parameter is nullable.
+ * @template MessageTypeNonNull :=
+ * cond(isUnknown(MessageType), unknown(),
+ * mapunion(MessageType, (X) =>
+ * cond(eq(X, 'null'), none(), X)))
+ * =:
*/
jspb.BinaryWriter.prototype.writeGroup = function(
field, value, writerCallback) {
@@ -1122,8 +1138,8 @@ jspb.BinaryWriter.prototype.writeRepeatedBytes = function(field, value) {
* @param {number} field The field number.
* @param {?Array.<MessageType>} value The array of messages to
* write.
- * @param {!jspb.WriterFunction} writerCallback Will be invoked with the value
- * to write and the writer to write it with.
+ * @param {function(MessageType, !jspb.BinaryWriter)} writerCallback
+ * Will be invoked with the value to write and the writer to write it with.
*/
jspb.BinaryWriter.prototype.writeRepeatedMessage = function(
field, value, writerCallback) {
@@ -1142,8 +1158,8 @@ jspb.BinaryWriter.prototype.writeRepeatedMessage = function(
* @param {number} field The field number.
* @param {?Array.<MessageType>} value The array of messages to
* write.
- * @param {!jspb.WriterFunction} writerCallback Will be invoked with the value
- * to write and the writer to write it with.
+ * @param {function(MessageType, !jspb.BinaryWriter)} writerCallback
+ * Will be invoked with the value to write and the writer to write it with.
*/
jspb.BinaryWriter.prototype.writeRepeatedGroup = function(
field, value, writerCallback) {
diff --git a/js/map.js b/js/map.js
new file mode 100644
index 00000000..821765ec
--- /dev/null
+++ b/js/map.js
@@ -0,0 +1,482 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+goog.provide('jspb.Map');
+
+goog.require('goog.asserts');
+
+goog.forwardDeclare('jspb.BinaryReader');
+goog.forwardDeclare('jspb.BinaryWriter');
+
+
+
+/**
+ * Constructs a new Map. A Map is a container that is used to implement map
+ * fields on message objects. It closely follows the ES6 Map API; however,
+ * it is distinct because we do not want to depend on external polyfills or
+ * on ES6 itself.
+ *
+ * This constructor should only be called from generated message code. It is not
+ * intended for general use by library consumers. The callback function
+ * arguments are references to methods in `BinaryReader` and `BinaryWriter`, as
+ * well as constructors and reader/writer methods in submessage types if
+ * appropriate, that are used for binary serialization and parsing.
+ *
+ * @template K, V
+ *
+ * @param {!Array<!Array<!Object>>} arr
+ *
+ * @param {function(this:jspb.BinaryWriter,number,K)=} opt_keyWriterFn
+ * The method on BinaryWriter that writes type K to the stream.
+ *
+ * @param {function(this:jspb.BinaryReader):K=} opt_keyReaderFn
+ * The method on BinaryReader that reads type K from the stream.
+ *
+ * @param {function(this:jspb.BinaryWriter,number,V)|
+ * function(this:jspb.BinaryReader,V,?)=} opt_valueWriterFn
+ * The method on BinaryWriter that writes type V to the stream. May be
+ * writeMessage, in which case the second callback arg form is used.
+ *
+ * @param {function(this:jspb.BinaryReader):V|
+ * function(this:jspb.BinaryReader,V,
+ * function(V,!jspb.BinaryReader))=} opt_valueReaderFn
+ * The method on BinaryReader that reads type V from the stream. May be
+ * readMessage, in which case the second callback arg form is used.
+ *
+ * @param {?function(new:V)|function(new:V,?)=} opt_valueCtor
+ * The constructor for type V, if type V is a message type.
+ *
+ * @param {?function(V,!jspb.BinaryWriter)=} opt_valueWriterCallback
+ * The BinaryWriter serialization callback for type V, if V is a message
+ * type.
+ *
+ * @param {?function(V,!jspb.BinaryReader)=} opt_valueReaderCallback
+ * The BinaryReader parsing callback for type V, if V is a message type.
+ *
+ * @constructor
+ * @struct
+ */
+jspb.Map = function(
+ arr, opt_keyWriterFn, opt_keyReaderFn, opt_valueWriterFn, opt_valueReaderFn,
+ opt_valueCtor, opt_valueWriterCallback, opt_valueReaderCallback) {
+
+ /** @const @private */
+ this.arr_ = arr;
+ /** @const @private */
+ this.keyWriterFn_ = opt_keyWriterFn;
+ /** @const @private */
+ this.keyReaderFn_ = opt_keyReaderFn;
+ /** @const @private */
+ this.valueWriterFn_ = opt_valueWriterFn;
+ /** @const @private */
+ this.valueReaderFn_ = opt_valueReaderFn;
+ /** @const @private */
+ this.valueCtor_ = opt_valueCtor;
+ /** @const @private */
+ this.valueWriterCallback_ = opt_valueWriterCallback;
+ /** @const @private */
+ this.valueReaderCallback_ = opt_valueReaderCallback;
+
+ /** @type {!Object<string, !jspb.Map.Entry_<K,V>>} @private */
+ this.map_ = {};
+
+ /**
+ * Is `this.arr_ updated with respect to `this.map_`?
+ * @type {boolean}
+ */
+ this.arrClean = true;
+
+ if (this.arr_.length > 0) {
+ this.loadFromArray_();
+ }
+};
+
+
+/**
+ * Load initial content from underlying array.
+ * @private
+ */
+jspb.Map.prototype.loadFromArray_ = function() {
+ for (var i = 0; i < this.arr_.length; i++) {
+ var record = this.arr_[i];
+ var key = record[0];
+ var value = record[1];
+ this.map_[key.toString()] = new jspb.Map.Entry_(key, value);
+ }
+ this.arrClean = true;
+};
+
+
+/**
+ * Synchronize content to underlying array, if needed, and return it.
+ * @return {!Array<!Array<!Object>>}
+ */
+jspb.Map.prototype.toArray = function() {
+ if (this.arrClean) {
+ if (this.valueCtor_) {
+ // We need to recursively sync maps in submessages to their arrays.
+ var m = this.map_;
+ for (var p in m) {
+ if (Object.prototype.hasOwnProperty.call(m, p)) {
+ m[p].valueWrapper.toArray();
+ }
+ }
+ }
+ } else {
+ // Delete all elements.
+ this.arr_.length = 0;
+ var strKeys = this.stringKeys_();
+ // Output keys in deterministic (sorted) order.
+ strKeys.sort();
+ for (var i = 0; i < strKeys.length; i++) {
+ var entry = this.map_[strKeys[i]];
+ var valueWrapper = /** @type {!Object} */ (entry.valueWrapper);
+ if (valueWrapper) {
+ valueWrapper.toArray();
+ }
+ this.arr_.push([entry.key, entry.value]);
+ }
+ this.arrClean = true;
+ }
+ return this.arr_;
+};
+
+
+/**
+ * Helper: return an iterator over an array.
+ * @template T
+ * @param {!Array<T>} arr the array
+ * @return {!Iterator<T>} an iterator
+ * @private
+ */
+jspb.Map.arrayIterator_ = function(arr) {
+ var idx = 0;
+ return /** @type {!Iterator} */ ({
+ next: function() {
+ if (idx < arr.length) {
+ return { done: false, value: arr[idx++] };
+ } else {
+ return { done: true };
+ }
+ }
+ });
+};
+
+
+/**
+ * Returns the map's length (number of key/value pairs).
+ * @return {number}
+ */
+jspb.Map.prototype.getLength = function() {
+ return this.stringKeys_().length;
+};
+
+
+/**
+ * Clears the map.
+ */
+jspb.Map.prototype.clear = function() {
+ this.map_ = {};
+ this.arrClean = false;
+};
+
+
+/**
+ * Deletes a particular key from the map.
+ * N.B.: differs in name from ES6 Map's `delete` because IE8 does not support
+ * reserved words as property names.
+ * @this {jspb.Map}
+ * @param {K} key
+ * @return {boolean} Whether any entry with this key was deleted.
+ */
+jspb.Map.prototype.del = function(key) {
+ var keyValue = key.toString();
+ var hadKey = this.map_.hasOwnProperty(keyValue);
+ delete this.map_[keyValue];
+ this.arrClean = false;
+ return hadKey;
+};
+
+
+/**
+ * Returns an array of [key, value] pairs in the map.
+ *
+ * This is redundant compared to the plain entries() method, but we provide this
+ * to help out Angular 1.x users. Still evaluating whether this is the best
+ * option.
+ *
+ * @return {!Array<K|V>}
+ */
+jspb.Map.prototype.getEntryList = function() {
+ var entries = [];
+ var strKeys = this.stringKeys_();
+ strKeys.sort();
+ for (var i = 0; i < strKeys.length; i++) {
+ var entry = this.map_[strKeys[i]];
+ entries.push([entry.key, entry.value]);
+ }
+ return entries;
+};
+
+
+/**
+ * Returns an iterator over [key, value] pairs in the map.
+ * Closure compiler sadly doesn't support tuples, ie. Iterator<[K,V]>.
+ * @return {!Iterator<!Array<K|V>>}
+ * The iterator
+ */
+jspb.Map.prototype.entries = function() {
+ var entries = [];
+ var strKeys = this.stringKeys_();
+ strKeys.sort();
+ for (var i = 0; i < strKeys.length; i++) {
+ var entry = this.map_[strKeys[i]];
+ entries.push([entry.key, this.wrapEntry_(entry)]);
+ }
+ return jspb.Map.arrayIterator_(entries);
+};
+
+
+/**
+ * Returns an iterator over keys in the map.
+ * @return {!Iterator<K>} The iterator
+ */
+jspb.Map.prototype.keys = function() {
+ var keys = [];
+ var strKeys = this.stringKeys_();
+ strKeys.sort();
+ for (var i = 0; i < strKeys.length; i++) {
+ var entry = this.map_[strKeys[i]];
+ keys.push(entry.key);
+ }
+ return jspb.Map.arrayIterator_(keys);
+};
+
+
+/**
+ * Returns an iterator over values in the map.
+ * @return {!Iterator<V>} The iterator
+ */
+jspb.Map.prototype.values = function() {
+ var values = [];
+ var strKeys = this.stringKeys_();
+ strKeys.sort();
+ for (var i = 0; i < strKeys.length; i++) {
+ var entry = this.map_[strKeys[i]];
+ values.push(this.wrapEntry_(entry));
+ }
+ return jspb.Map.arrayIterator_(values);
+};
+
+
+/**
+ * Iterates over entries in the map, calling a function on each.
+ * @template T
+ * @param {function(this:T, V, K, ?jspb.Map<K, V>)} cb
+ * @param {T=} opt_thisArg
+ */
+jspb.Map.prototype.forEach = function(cb, opt_thisArg) {
+ var strKeys = this.stringKeys_();
+ strKeys.sort();
+ for (var i = 0; i < strKeys.length; i++) {
+ var entry = this.map_[strKeys[i]];
+ cb.call(opt_thisArg, this.wrapEntry_(entry), entry.key, this);
+ }
+};
+
+
+/**
+ * Sets a key in the map to the given value.
+ * @param {K} key The key
+ * @param {V} value The value
+ * @return {!jspb.Map<K,V>}
+ */
+jspb.Map.prototype.set = function(key, value) {
+ var entry = new jspb.Map.Entry_(key);
+ if (this.valueCtor_) {
+ entry.valueWrapper = value;
+ // .toArray() on a message returns a reference to the underlying array
+ // rather than a copy.
+ entry.value = value.toArray();
+ } else {
+ entry.value = value;
+ }
+ this.map_[key.toString()] = entry;
+ this.arrClean = false;
+ return this;
+};
+
+
+/**
+ * Helper: lazily construct a wrapper around an entry, if needed, and return the
+ * user-visible type.
+ * @param {!jspb.Map.Entry_<K,V>} entry
+ * @return {V}
+ * @private
+ */
+jspb.Map.prototype.wrapEntry_ = function(entry) {
+ if (this.valueCtor_) {
+ if (!entry.valueWrapper) {
+ entry.valueWrapper = new this.valueCtor_(entry.value);
+ }
+ return /** @type {V} */ (entry.valueWrapper);
+ } else {
+ return entry.value;
+ }
+};
+
+
+/**
+ * Gets the value corresponding to a key in the map.
+ * @param {K} key
+ * @return {V|undefined} The value, or `undefined` if key not present
+ */
+jspb.Map.prototype.get = function(key) {
+ var keyValue = key.toString();
+ var entry = this.map_[keyValue];
+ if (entry) {
+ return this.wrapEntry_(entry);
+ } else {
+ return undefined;
+ }
+};
+
+
+/**
+ * Determines whether the given key is present in the map.
+ * @param {K} key
+ * @return {boolean} `true` if the key is present
+ */
+jspb.Map.prototype.has = function(key) {
+ var keyValue = key.toString();
+ return (keyValue in this.map_);
+};
+
+
+/**
+ * Write this Map field in wire format to a BinaryWriter, using the given field
+ * number.
+ * @param {number} fieldNumber
+ * @param {!jspb.BinaryWriter} writer
+ */
+jspb.Map.prototype.serializeBinary = function(fieldNumber, writer) {
+ var strKeys = this.stringKeys_();
+ strKeys.sort();
+ for (var i = 0; i < strKeys.length; i++) {
+ var entry = this.map_[strKeys[i]];
+ writer.beginSubMessage(fieldNumber);
+ this.keyWriterFn_.call(writer, 1, entry.key);
+ if (this.valueCtor_) {
+ this.valueWriterFn_.call(writer, 2, this.wrapEntry_(entry),
+ this.valueWriterCallback_);
+ } else {
+ this.valueWriterFn_.call(writer, 2, entry.value);
+ }
+ writer.endSubMessage();
+ }
+};
+
+
+/**
+ * Read one key/value message from the given BinaryReader. Compatible as the
+ * `reader` callback parameter to jspb.BinaryReader.readMessage, to be called
+ * when a key/value pair submessage is encountered.
+ * @param {!jspb.Map} map
+ * @param {!jspb.BinaryReader} reader
+ */
+jspb.Map.deserializeBinary = function(map, reader) {
+ var key = undefined;
+ var value = undefined;
+
+ while (reader.nextField()) {
+ if (reader.isEndGroup()) {
+ break;
+ }
+ var field = reader.getFieldNumber();
+ if (field == 1) {
+ // Key.
+ key = map.keyReaderFn_.call(reader);
+ } else if (field == 2) {
+ // Value.
+ if (map.valueCtor_) {
+ value = new map.valueCtor_();
+ map.valueReaderFn_.call(reader, value, map.valueReaderCallback_);
+ } else {
+ value = map.valueReaderFn_.call(reader);
+ }
+ }
+ }
+
+ goog.asserts.assert(key != undefined);
+ goog.asserts.assert(value != undefined);
+ map.set(key, value);
+};
+
+
+/**
+ * Helper: compute the list of all stringified keys in the underlying Object
+ * map.
+ * @return {!Array<string>}
+ * @private
+ */
+jspb.Map.prototype.stringKeys_ = function() {
+ var m = this.map_;
+ var ret = [];
+ for (var p in m) {
+ if (Object.prototype.hasOwnProperty.call(m, p)) {
+ ret.push(p);
+ }
+ }
+ return ret;
+};
+
+
+
+/**
+ * @param {!K} key The entry's key.
+ * @param {V=} opt_value The entry's value wrapper.
+ * @constructor
+ * @struct
+ * @template K, V
+ * @private
+ */
+jspb.Map.Entry_ = function(key, opt_value) {
+ /** @const {K} */
+ this.key = key;
+
+ // The JSPB-serializable value. For primitive types this will be of type V.
+ // For message types it will be an array.
+ /** @type {V} */
+ this.value = opt_value;
+
+ // Only used for submessage values.
+ /** @type {V} */
+ this.valueWrapper = undefined;
+};
diff --git a/js/message.js b/js/message.js
index 813e6b8c..3863bac0 100644
--- a/js/message.js
+++ b/js/message.js
@@ -41,6 +41,7 @@ goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.crypt.base64');
goog.require('goog.json');
+goog.require('jspb.Map');
// Not needed in compilation units that have no protos with xids.
goog.forwardDeclare('xid.String');
@@ -371,7 +372,8 @@ jspb.Message.materializeExtensionObject_ = function(msg, suggestedPivot) {
// the object is not an array, since arrays are valid field values.
// NOTE(lukestebbing): We avoid looking at .length to avoid a JIT bug
// in Safari on iOS 8. See the description of CL/86511464 for details.
- if (obj && typeof obj == 'object' && !goog.isArray(obj)) {
+ if (obj && typeof obj == 'object' && !goog.isArray(obj) &&
+ !(jspb.Message.SUPPORTS_UINT8ARRAY_ && obj instanceof Uint8Array)) {
msg.pivot_ = foundIndex - msg.arrayIndexOffset_;
msg.extensionObject_ = obj;
return;
@@ -738,6 +740,62 @@ jspb.Message.getFieldProto3 = function(msg, fieldNumber, defaultValue) {
/**
+ * Gets the value of a map field, lazily creating the map container if
+ * necessary.
+ *
+ * This should only be called from generated code, because it requires knowledge
+ * of serialization/parsing callbacks (which are required by the map at
+ * construction time, and the map may be constructed here).
+ *
+ * The below callbacks are used to allow the map to serialize and parse its
+ * binary wire format data. Their purposes are described in more detail in
+ * `jspb.Map`'s constructor documentation.
+ *
+ * @template K, V
+ * @param {!jspb.Message} msg
+ * @param {number} fieldNumber
+ * @param {boolean|undefined} noLazyCreate
+ * @param {?=} opt_valueCtor
+ * @param {function(number,K)=} opt_keyWriterFn
+ * @param {function():K=} opt_keyReaderFn
+ * @param {function(number,V)|function(number,V,?)|
+ * function(number,V,?,?,?,?)=} opt_valueWriterFn
+ * @param {function():V|
+ * function(V,function(?,?))=} opt_valueReaderFn
+ * @param {function(?,?)|function(?,?,?,?,?)=} opt_valueWriterCallback
+ * @param {function(?,?)=} opt_valueReaderCallback
+ * @return {!jspb.Map<K, V>|undefined}
+ * @protected
+ */
+jspb.Message.getMapField = function(msg, fieldNumber, noLazyCreate,
+ opt_valueCtor, opt_keyWriterFn, opt_keyReaderFn, opt_valueWriterFn,
+ opt_valueReaderFn, opt_valueWriterCallback, opt_valueReaderCallback) {
+ if (!msg.wrappers_) {
+ msg.wrappers_ = {};
+ }
+ // If we already have a map in the map wrappers, return that.
+ if (fieldNumber in msg.wrappers_) {
+ return msg.wrappers_[fieldNumber];
+ } else if (noLazyCreate) {
+ return undefined;
+ } else {
+ // Wrap the underlying elements array with a Map.
+ var arr = jspb.Message.getField(msg, fieldNumber);
+ if (!arr) {
+ arr = [];
+ jspb.Message.setField(msg, fieldNumber, arr);
+ }
+ return msg.wrappers_[fieldNumber] =
+ new jspb.Map(
+ /** @type {!Array<!Array<!Object>>} */ (arr),
+ opt_keyWriterFn, opt_keyReaderFn, opt_valueWriterFn,
+ opt_valueReaderFn, opt_valueCtor, opt_valueWriterCallback,
+ opt_valueReaderCallback);
+ }
+};
+
+
+/**
* Sets the value of a non-extension field.
* @param {!jspb.Message} msg A jspb proto.
* @param {number} fieldNumber The field number.
@@ -953,12 +1011,45 @@ jspb.Message.toMap = function(
/**
+ * Syncs all map fields' contents back to their underlying arrays.
+ * @private
+ */
+jspb.Message.prototype.syncMapFields_ = function() {
+ // This iterates over submessage, map, and repeated fields, which is intended.
+ // Submessages can contain maps which also need to be synced.
+ //
+ // There is a lot of opportunity for optimization here. For example we could
+ // statically determine that some messages have no submessages with maps and
+ // optimize this method away for those just by generating one extra static
+ // boolean per message type.
+ if (this.wrappers_) {
+ for (var fieldNumber in this.wrappers_) {
+ var val = this.wrappers_[fieldNumber];
+ if (goog.isArray(val)) {
+ for (var i = 0; i < val.length; i++) {
+ if (val[i]) {
+ val[i].toArray();
+ }
+ }
+ } else {
+ // Works for submessages and maps.
+ if (val) {
+ val.toArray();
+ }
+ }
+ }
+ }
+};
+
+
+/**
* Returns the internal array of this proto.
* <p>Note: If you use this array to construct a second proto, the content
* would then be partially shared between the two protos.
* @return {!Array} The proto represented as an array.
*/
jspb.Message.prototype.toArray = function() {
+ this.syncMapFields_();
return this.array;
};
@@ -972,6 +1063,7 @@ jspb.Message.prototype.toArray = function() {
* @override
*/
jspb.Message.prototype.toString = function() {
+ this.syncMapFields_();
return this.array.toString();
};
@@ -1293,6 +1385,9 @@ jspb.Message.clone_ = function(obj) {
}
return clonedArray;
}
+ if (jspb.Message.SUPPORTS_UINT8ARRAY_ && obj instanceof Uint8Array) {
+ return new Uint8Array(obj);
+ }
var clone = {};
for (var key in obj) {
if ((o = obj[key]) != null) {
diff --git a/js/message_test.js b/js/message_test.js
index 01add5f1..0b0c0172 100644
--- a/js/message_test.js
+++ b/js/message_test.js
@@ -34,6 +34,7 @@ goog.setTestOnly();
goog.require('goog.json');
goog.require('goog.testing.asserts');
+goog.require('goog.userAgent');
// CommonJS-LoadFromFile: google-protobuf jspb
goog.require('jspb.Message');
@@ -66,6 +67,7 @@ goog.require('proto.jspb.test.Simple1');
goog.require('proto.jspb.test.Simple2');
goog.require('proto.jspb.test.SpecialCases');
goog.require('proto.jspb.test.TestClone');
+goog.require('proto.jspb.test.TestEndsWithBytes');
goog.require('proto.jspb.test.TestGroup');
goog.require('proto.jspb.test.TestGroup1');
goog.require('proto.jspb.test.TestMessageWithOneof');
@@ -438,6 +440,8 @@ describe('Message test suite', function() {
});
it('testClone', function() {
+ var supportsUint8Array =
+ !goog.userAgent.IE || goog.userAgent.isVersionOrHigher('10');
var original = new proto.jspb.test.TestClone();
original.setStr('v1');
var simple1 = new proto.jspb.test.Simple1(['x1', ['y1', 'z1']]);
@@ -445,12 +449,14 @@ describe('Message test suite', function() {
var simple3 = new proto.jspb.test.Simple1(['x3', ['y3', 'z3']]);
original.setSimple1(simple1);
original.setSimple2List([simple2, simple3]);
+ var bytes1 = supportsUint8Array ? new Uint8Array([1, 2, 3]) : '123';
+ original.setBytesField(bytes1);
var extension = new proto.jspb.test.CloneExtension();
extension.setExt('e1');
original.setExtension(proto.jspb.test.IsExtension.extField, extension);
var clone = original.cloneMessage();
assertArrayEquals(['v1',, ['x1', ['y1', 'z1']],,
- [['x2', ['y2', 'z2']], ['x3', ['y3', 'z3']]],,, { 100: [, 'e1'] }],
+ [['x2', ['y2', 'z2']], ['x3', ['y3', 'z3']]], bytes1,, { 100: [, 'e1'] }],
clone.toArray());
clone.setStr('v2');
var simple4 = new proto.jspb.test.Simple1(['a1', ['b1', 'c1']]);
@@ -458,18 +464,26 @@ describe('Message test suite', function() {
var simple6 = new proto.jspb.test.Simple1(['a3', ['b3', 'c3']]);
clone.setSimple1(simple4);
clone.setSimple2List([simple5, simple6]);
+ if (supportsUint8Array) {
+ clone.getBytesField()[0] = 4;
+ assertObjectEquals(bytes1, original.getBytesField());
+ }
+ var bytes2 = supportsUint8Array ? new Uint8Array([4, 5, 6]) : '456';
+ clone.setBytesField(bytes2);
var newExtension = new proto.jspb.test.CloneExtension();
newExtension.setExt('e2');
clone.setExtension(proto.jspb.test.CloneExtension.extField, newExtension);
assertArrayEquals(['v2',, ['a1', ['b1', 'c1']],,
- [['a2', ['b2', 'c2']], ['a3', ['b3', 'c3']]],,, { 100: [, 'e2'] }],
+ [['a2', ['b2', 'c2']], ['a3', ['b3', 'c3']]], bytes2,, { 100: [, 'e2'] }],
clone.toArray());
assertArrayEquals(['v1',, ['x1', ['y1', 'z1']],,
- [['x2', ['y2', 'z2']], ['x3', ['y3', 'z3']]],,, { 100: [, 'e1'] }],
+ [['x2', ['y2', 'z2']], ['x3', ['y3', 'z3']]], bytes1,, { 100: [, 'e1'] }],
original.toArray());
});
it('testCopyInto', function() {
+ var supportsUint8Array =
+ !goog.userAgent.IE || goog.userAgent.isVersionOrHigher('10');
var original = new proto.jspb.test.TestClone();
original.setStr('v1');
var dest = new proto.jspb.test.TestClone();
@@ -484,6 +498,10 @@ describe('Message test suite', function() {
original.setSimple2List([simple2, simple3]);
dest.setSimple1(destSimple1);
dest.setSimple2List([destSimple2, destSimple3]);
+ var bytes1 = supportsUint8Array ? new Uint8Array([1, 2, 3]) : '123';
+ var bytes2 = supportsUint8Array ? new Uint8Array([4, 5, 6]) : '456';
+ original.setBytesField(bytes1);
+ dest.setBytesField(bytes2);
var extension = new proto.jspb.test.CloneExtension();
extension.setExt('e1');
original.setExtension(proto.jspb.test.CloneExtension.extField, extension);
@@ -496,6 +514,15 @@ describe('Message test suite', function() {
dest.getSimple1().setAString('new value');
assertNotEquals(dest.getSimple1().getAString(),
original.getSimple1().getAString());
+ if (supportsUint8Array) {
+ dest.getBytesField()[0] = 7;
+ assertObjectEquals(bytes1, original.getBytesField());
+ assertObjectEquals(new Uint8Array([7, 2, 3]), dest.getBytesField());
+ } else {
+ dest.setBytesField('789');
+ assertObjectEquals(bytes1, original.getBytesField());
+ assertObjectEquals('789', dest.getBytesField());
+ }
dest.getExtension(proto.jspb.test.CloneExtension.extField).
setExt('new value');
assertNotEquals(
diff --git a/js/package.json b/js/package.json
index ae05722a..657b08bd 100644
--- a/js/package.json
+++ b/js/package.json
@@ -1,17 +1,19 @@
{
"name": "google-protobuf",
- "version": "3.0.0-alpha.6",
+ "version": "3.0.0-alpha.6.2",
"description": "Protocol Buffers for JavaScript",
"main": "google-protobuf.js",
- "dependencies": {
+ "files": [
+ "google"
+ ],
+ "dependencies": {},
+ "devDependencies": {
+ "glob": "~6.0.4",
+ "google-closure-compiler": "~20160619.0.0",
"google-closure-library": "~20160125.0.0",
"gulp": "~3.9.0",
"jasmine": "~2.4.1"
},
- "devDependencies": {
- "google-closure-compiler": "~20151216.2.0",
- "glob": "~6.0.4"
- },
"scripts": {
"test": "node ./node_modules/gulp/bin/gulp.js test"
},
@@ -19,6 +21,6 @@
"type": "git",
"url": "https://github.com/google/protobuf/tree/master/js"
},
- "author": "",
+ "author": "Google Protocol Buffers Team",
"license": "Apache-2.0"
}
diff --git a/js/test.proto b/js/test.proto
index 6b9dc891..cf2eafef 100644
--- a/js/test.proto
+++ b/js/test.proto
@@ -160,6 +160,7 @@ message TestClone {
optional string str = 1;
optional Simple1 simple1 = 3;
repeated Simple1 simple2 = 5;
+ optional bytes bytes_field = 6;
optional string unused = 7;
extensions 10 to max;
}
@@ -228,3 +229,7 @@ message TestMessageWithOneof {
}
}
+message TestEndsWithBytes {
+ optional int32 value = 1;
+ optional bytes data = 2;
+}
diff --git a/js/testbinary.proto b/js/testbinary.proto
index 60c70190..116f17fb 100644
--- a/js/testbinary.proto
+++ b/js/testbinary.proto
@@ -183,3 +183,30 @@ extend TestExtendable {
[packed=true];
}
+
+message TestMapFields {
+ map<string, string> map_string_string = 1;
+ map<string, int32> map_string_int32 = 2;
+ map<string, int64> map_string_int64 = 3;
+ map<string, bool> map_string_bool = 4;
+ map<string, double> map_string_double = 5;
+ map<string, MapValueEnum> map_string_enum = 6;
+ map<string, MapValueMessage> map_string_msg = 7;
+
+ map<int32, string> map_int32_string = 8;
+ map<int64, string> map_int64_string = 9;
+ map<bool, string> map_bool_string = 10;
+
+ optional TestMapFields test_map_fields = 11;
+ map<string, TestMapFields> map_string_testmapfields = 12;
+}
+
+enum MapValueEnum {
+ MAP_VALUE_FOO = 0;
+ MAP_VALUE_BAR = 1;
+ MAP_VALUE_BAZ = 2;
+}
+
+message MapValueMessage {
+ optional int32 foo = 1;
+}