Răsfoiți Sursa

Project import generated by Copybara

PiperOrigin-RevId: 308658750
Protobuf Team 5 ani în urmă
părinte
comite
e9064b8ede

+ 287 - 0
js/experimental/runtime/kernel/message_set.js

@@ -0,0 +1,287 @@
+/*
+##########################################################
+#                                                        #
+#  __          __     _____  _   _ _____ _   _  _____    #
+#  \ \        / /\   |  __ \| \ | |_   _| \ | |/ ____|   #
+#   \ \  /\  / /  \  | |__) |  \| | | | |  \| | |  __    #
+#    \ \/  \/ / /\ \ |  _  /| . ` | | | | . ` | | |_ |   #
+#     \  /\  / ____ \| | \ \| |\  |_| |_| |\  | |__| |   #
+#      \/  \/_/    \_\_|  \_\_| \_|_____|_| \_|\_____|   #
+#                                                        #
+#                                                        #
+##########################################################
+# Do not use this class in your code. This class purely  #
+# exists to make proto code generation easier.           #
+##########################################################
+*/
+goog.module('protobuf.runtime.MessageSet');
+
+const InternalMessage = goog.require('protobuf.binary.InternalMessage');
+const Kernel = goog.require('protobuf.runtime.Kernel');
+
+// These are the tags for the old MessageSet format, which was defined as:
+//   message MessageSet {
+//     repeated group Item = 1 {
+//       required int32 type_id = 2;
+//       required string message = 3;
+//     }
+//   }
+/** @const {number} */
+const MSET_GROUP_FIELD_NUMBER = 1;
+/** @const {number} */
+const MSET_TYPE_ID_FIELD_NUMBER = 2;
+/** @const {number} */
+const MSET_MESSAGE_FIELD_NUMBER = 3;
+
+/**
+ * @param {!Kernel} kernel
+ * @return {!Map<number, !Item>}
+ */
+function createItemMap(kernel) {
+  const itemMap = new Map();
+  let totalCount = 0;
+  for (const item of kernel.getRepeatedGroupIterable(
+           MSET_GROUP_FIELD_NUMBER, Item.fromKernel)) {
+    itemMap.set(item.getTypeId(), item);
+    totalCount++;
+  }
+
+  // Normalize the entries.
+  if (totalCount > itemMap.size) {
+    writeItemMap(kernel, itemMap);
+  }
+  return itemMap;
+}
+
+/**
+ * @param {!Kernel} kernel
+ * @param {!Map<number, !Item>} itemMap
+ */
+function writeItemMap(kernel, itemMap) {
+  kernel.setRepeatedGroupIterable(MSET_GROUP_FIELD_NUMBER, itemMap.values());
+}
+
+/**
+ * @implements {InternalMessage}
+ * @final
+ */
+class MessageSet {
+  /**
+   * @param {!Kernel} kernel
+   * @return {!MessageSet}
+   */
+  static fromKernel(kernel) {
+    const itemMap = createItemMap(kernel);
+    return new MessageSet(kernel, itemMap);
+  }
+
+  /**
+   * @return {!MessageSet}
+   */
+  static createEmpty() {
+    return MessageSet.fromKernel(Kernel.createEmpty());
+  }
+
+  /**
+   * @param {!Kernel} kernel
+   * @param {!Map<number, !Item>} itemMap
+   * @private
+   */
+  constructor(kernel, itemMap) {
+    /** @const {!Kernel} @private */
+    this.kernel_ = kernel;
+    /** @const {!Map<number, !Item>} @private */
+    this.itemMap_ = itemMap;
+  }
+
+
+
+  // code helpers for code gen
+
+  /**
+   * @param {number} typeId
+   * @param {function(!Kernel):T} instanceCreator
+   * @param {number=} pivot
+   * @return {?T}
+   * @template T
+   */
+  getMessageOrNull(typeId, instanceCreator, pivot) {
+    const item = this.itemMap_.get(typeId);
+    return item ? item.getMessageOrNull(instanceCreator, pivot) : null;
+  }
+
+  /**
+   * @param {number} typeId
+   * @param {function(!Kernel):T} instanceCreator
+   * @param {number=} pivot
+   * @return {T}
+   * @template T
+   */
+  getMessageAttach(typeId, instanceCreator, pivot) {
+    let item = this.itemMap_.get(typeId);
+    if (item) {
+      return item.getMessageAttach(instanceCreator, pivot);
+    }
+    const message = instanceCreator(Kernel.createEmpty());
+    this.setMessage(typeId, message);
+    return message;
+  }
+
+  /**
+   * @param {number} typeId
+   * @param {number=} pivot
+   * @return {?Kernel}
+   */
+  getMessageAccessorOrNull(typeId, pivot) {
+    const item = this.itemMap_.get(typeId);
+    return item ? item.getMessageAccessorOrNull(pivot) : null;
+  }
+
+
+  /**
+   * @param {number} typeId
+   */
+  clearMessage(typeId) {
+    if (this.itemMap_.delete(typeId)) {
+      writeItemMap(this.kernel_, this.itemMap_);
+    }
+  }
+
+  /**
+   * @param {number} typeId
+   * @return {boolean}
+   */
+  hasMessage(typeId) {
+    return this.itemMap_.has(typeId);
+  }
+
+  /**
+   * @param {number} typeId
+   * @param {!InternalMessage} value
+   */
+  setMessage(typeId, value) {
+    const item = this.itemMap_.get(typeId);
+    if (item) {
+      item.setMessage(value);
+    } else {
+      this.itemMap_.set(typeId, Item.create(typeId, value));
+      writeItemMap(this.kernel_, this.itemMap_);
+    }
+  }
+
+  /**
+   * @return {!Kernel}
+   * @override
+   */
+  internalGetKernel() {
+    return this.kernel_;
+  }
+}
+
+/**
+ * @implements {InternalMessage}
+ * @final
+ */
+class Item {
+  /**
+   * @param {number} typeId
+   * @param {!InternalMessage} message
+   * @return {!Item}
+   */
+  static create(typeId, message) {
+    const messageSet = Item.fromKernel(Kernel.createEmpty());
+    messageSet.setTypeId_(typeId);
+    messageSet.setMessage(message);
+    return messageSet;
+  }
+
+
+  /**
+   * @param {!Kernel} kernel
+   * @return {!Item}
+   */
+  static fromKernel(kernel) {
+    return new Item(kernel);
+  }
+
+  /**
+   * @param {!Kernel} kernel
+   * @private
+   */
+  constructor(kernel) {
+    /** @const {!Kernel} @private */
+    this.kernel_ = kernel;
+  }
+
+  /**
+   * @param {function(!Kernel):T} instanceCreator
+   * @param {number=} pivot
+   * @return {T}
+   * @template T
+   */
+  getMessage(instanceCreator, pivot) {
+    return this.kernel_.getMessage(
+        MSET_MESSAGE_FIELD_NUMBER, instanceCreator, pivot);
+  }
+
+  /**
+   * @param {function(!Kernel):T} instanceCreator
+   * @param {number=} pivot
+   * @return {?T}
+   * @template T
+   */
+  getMessageOrNull(instanceCreator, pivot) {
+    return this.kernel_.getMessageOrNull(
+        MSET_MESSAGE_FIELD_NUMBER, instanceCreator, pivot);
+  }
+
+  /**
+   * @param {function(!Kernel):T} instanceCreator
+   * @param {number=} pivot
+   * @return {T}
+   * @template T
+   */
+  getMessageAttach(instanceCreator, pivot) {
+    return this.kernel_.getMessageAttach(
+        MSET_MESSAGE_FIELD_NUMBER, instanceCreator, pivot);
+  }
+
+  /**
+   * @param {number=} pivot
+   * @return {?Kernel}
+   */
+  getMessageAccessorOrNull(pivot) {
+    return this.kernel_.getMessageAccessorOrNull(
+        MSET_MESSAGE_FIELD_NUMBER, pivot);
+  }
+
+  /**
+   * @param {!InternalMessage} value
+   */
+  setMessage(value) {
+    this.kernel_.setMessage(3, value);
+  }
+
+  /** @return {number} */
+  getTypeId() {
+    return this.kernel_.getUint32WithDefault(MSET_TYPE_ID_FIELD_NUMBER);
+  }
+
+  /**
+   * @param {number} value
+   * @private
+   */
+  setTypeId_(value) {
+    this.kernel_.setUint32(MSET_TYPE_ID_FIELD_NUMBER, value);
+  }
+
+  /**
+   * @return {!Kernel}
+   * @override
+   */
+  internalGetKernel() {
+    return this.kernel_;
+  }
+}
+
+exports = MessageSet;

+ 239 - 0
js/experimental/runtime/kernel/message_set_test.js

@@ -0,0 +1,239 @@
+/**
+ * @fileoverview Tests for indexer.js.
+ */
+goog.module('protobuf.runtime.MessageSetTest');
+
+goog.setTestOnly();
+
+const InternalMessage = goog.require('protobuf.binary.InternalMessage');
+const Kernel = goog.require('protobuf.runtime.Kernel');
+const MessageSet = goog.require('protobuf.runtime.MessageSet');
+
+/**
+ * @implements {InternalMessage}
+ */
+class KernelWrapper {
+  static createEmpty() {
+    return new KernelWrapper(Kernel.createEmpty());
+  }
+
+  constructor(kernel) {
+    this.kernel = kernel;
+  }
+
+  /**
+   * @return {!Kernel}
+   * @override
+   */
+  internalGetKernel() {
+    return this.kernel;
+  }
+}
+
+/**
+ * @param {...number} bytes
+ * @return {!ArrayBuffer}
+ */
+function createArrayBuffer(...bytes) {
+  return new Uint8Array(bytes).buffer;
+}
+
+describe('MessageSet does', () => {
+  it('returns no messages for empty set', () => {
+    const messageSet = MessageSet.createEmpty();
+    expect(messageSet.getMessageOrNull(12345, k => new KernelWrapper(k)))
+        .toBeNull();
+  });
+
+  it('returns no kernel for empty set', () => {
+    const messageSet = MessageSet.createEmpty();
+    expect(messageSet.getMessageAccessorOrNull(12345)).toBeNull();
+  });
+
+  it('returns message that has been set', () => {
+    const messageSet = MessageSet.createEmpty();
+    const message = KernelWrapper.createEmpty();
+    messageSet.setMessage(12345, message);
+    expect(messageSet.getMessageOrNull(12345, k => new KernelWrapper(k)))
+        .toBe(message);
+  });
+
+  it('returns null for cleared message', () => {
+    const messageSet = MessageSet.createEmpty();
+    const message = KernelWrapper.createEmpty();
+    messageSet.setMessage(12345, message);
+    messageSet.clearMessage(12345);
+    expect(messageSet.getMessageAccessorOrNull(12345)).toBeNull();
+  });
+
+  it('returns false for not present message', () => {
+    const messageSet = MessageSet.createEmpty();
+    expect(messageSet.hasMessage(12345)).toBe(false);
+  });
+
+  it('returns true for present message', () => {
+    const messageSet = MessageSet.createEmpty();
+    const message = KernelWrapper.createEmpty();
+    messageSet.setMessage(12345, message);
+    expect(messageSet.hasMessage(12345)).toBe(true);
+  });
+
+  it('returns false for cleared message', () => {
+    const messageSet = MessageSet.createEmpty();
+    const message = KernelWrapper.createEmpty();
+    messageSet.setMessage(12345, message);
+    messageSet.clearMessage(12345);
+    expect(messageSet.hasMessage(12345)).toBe(false);
+  });
+
+  it('pick the last value in the stream', () => {
+    const arrayBuffer = createArrayBuffer(
+        0x52,  // Tag (field:10, length delimited)
+        0x14,  // Length of 20 bytes
+        0x0B,  // Start group fieldnumber 1
+        0x10,  // Tag (field 2, varint)
+        0xB9,  // 12345
+        0x60,  // 12345
+        0x1A,  // Tag (field 3, length delimited)
+        0x03,  // length 3
+        0xA0,  // Tag (fieldnumber 20, varint)
+        0x01,  // Tag (fieldnumber 20, varint)
+        0x1E,  // 30
+        0x0C,  // Stop Group field number 1
+        // second group
+        0x0B,  // Start group fieldnumber 1
+        0x10,  // Tag (field 2, varint)
+        0xB9,  // 12345
+        0x60,  // 12345
+        0x1A,  // Tag (field 3, length delimited)
+        0x03,  // length 3
+        0xA0,  // Tag (fieldnumber 20, varint)
+        0x01,  // Tag (fieldnumber 20, varint)
+        0x01,  // 1
+        0x0C   // Stop Group field number 1
+    );
+
+    const outterMessage = Kernel.fromArrayBuffer(arrayBuffer);
+
+    const messageSet = outterMessage.getMessage(10, MessageSet.fromKernel);
+
+    const message =
+        messageSet.getMessageOrNull(12345, k => new KernelWrapper(k))
+            .internalGetKernel();
+    expect(message.getInt32WithDefault(20)).toBe(1);
+  });
+
+  it('removes duplicates when read', () => {
+    const arrayBuffer = createArrayBuffer(
+        0x52,  // Tag (field:10, length delimited)
+        0x14,  // Length of 20 bytes
+        0x0B,  // Start group fieldnumber 1
+        0x10,  // Tag (field 2, varint)
+        0xB9,  // 12345
+        0x60,  // 12345
+        0x1A,  // Tag (field 3, length delimited)
+        0x03,  // length 3
+        0xA0,  // Tag (fieldnumber 20, varint)
+        0x01,  // Tag (fieldnumber 20, varint)
+        0x1E,  // 30
+        0x0C,  // Stop Group field number 1
+        // second group
+        0x0B,  // Start group fieldnumber 1
+        0x10,  // Tag (field 2, varint)
+        0xB9,  // 12345
+        0x60,  // 12345
+        0x1A,  // Tag (field 3, length delimited)
+        0x03,  // length 3
+        0xA0,  // Tag (fieldnumber 20, varint)
+        0x01,  // Tag (fieldnumber 20, varint)
+        0x01,  // 1
+        0x0C   // Stop Group field number 1
+    );
+
+
+    const outterMessage = Kernel.fromArrayBuffer(arrayBuffer);
+    outterMessage.getMessageAttach(10, MessageSet.fromKernel);
+
+    expect(outterMessage.serialize())
+        .toEqual(createArrayBuffer(
+            0x52,  // Tag (field:10, length delimited)
+            0x0A,  // Length of 10 bytes
+            0x0B,  // Start group fieldnumber 1
+            0x10,  // Tag (field 2, varint)
+            0xB9,  // 12345
+            0x60,  // 12345
+            0x1A,  // Tag (field 3, length delimited)
+            0x03,  // length 3
+            0xA0,  // Tag (fieldnumber 20, varint)
+            0x01,  // Tag (fieldnumber 20, varint)
+            0x01,  // 1
+            0x0C   // Stop Group field number 1
+            ));
+  });
+});
+
+describe('Optional MessageSet does', () => {
+  // message Bar {
+  //  optional MessageSet mset = 10;
+  //}
+  //
+  // message Foo {
+  //  extend proto2.bridge.MessageSet {
+  //    optional Foo message_set_extension = 12345;
+  //  }
+  //  optional int32 f20 = 20;
+  //}
+
+  it('encode as a field', () => {
+    const fooMessage = Kernel.createEmpty();
+    fooMessage.setInt32(20, 30);
+
+    const messageSet = MessageSet.createEmpty();
+    messageSet.setMessage(12345, new KernelWrapper(fooMessage));
+
+    const barMessage = Kernel.createEmpty();
+    barMessage.setMessage(10, messageSet);
+
+    expect(barMessage.serialize())
+        .toEqual(createArrayBuffer(
+            0x52,  // Tag (field:10, length delimited)
+            0x0A,  // Length of 10 bytes
+            0x0B,  // Start group fieldnumber 1
+            0x10,  // Tag (field 2, varint)
+            0xB9,  // 12345
+            0x60,  // 12345
+            0x1A,  // Tag (field 3, length delimited)
+            0x03,  // length 3
+            0xA0,  // Tag (fieldnumber 20, varint)
+            0x01,  // Tag (fieldnumber 20, varint)
+            0x1E,  // 30
+            0x0C   // Stop Group field number 1
+            ));
+  });
+
+  it('deserializes', () => {
+    const fooMessage = Kernel.createEmpty();
+    fooMessage.setInt32(20, 30);
+
+    const messageSet = MessageSet.createEmpty();
+    messageSet.setMessage(12345, new KernelWrapper(fooMessage));
+
+
+    const barMessage = Kernel.createEmpty();
+    barMessage.setMessage(10, messageSet);
+
+    const arrayBuffer = barMessage.serialize();
+
+    const barMessageParsed = Kernel.fromArrayBuffer(arrayBuffer);
+    expect(barMessageParsed.hasFieldNumber(10)).toBe(true);
+
+    const messageSetParsed =
+        barMessageParsed.getMessage(10, MessageSet.fromKernel);
+
+    const fooMessageParsed =
+        messageSetParsed.getMessageOrNull(12345, k => new KernelWrapper(k))
+            .internalGetKernel();
+
+    expect(fooMessageParsed.getInt32WithDefault(20)).toBe(30);
+  });
+});