parent
12236c6977
commit
bb3460d71b
3 changed files with 555 additions and 2 deletions
@ -0,0 +1,285 @@ |
||||
/* |
||||
########################################################## |
||||
# # |
||||
# __ __ _____ _ _ _____ _ _ _____ # |
||||
# \ \ / /\ | __ \| \ | |_ _| \ | |/ ____| # |
||||
# \ \ /\ / / \ | |__) | \| | | | | \| | | __ # |
||||
# \ \/ \/ / /\ \ | _ /| . ` | | | | . ` | | |_ | # |
||||
# \ /\ / ____ \| | \ \| |\ |_| |_| |\ | |__| | # |
||||
# \/ \/_/ \_\_| \_\_| \_|_____|_| \_|\_____| # |
||||
# # |
||||
# # |
||||
########################################################## |
||||
# 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 uint32 type_id = 2;
|
||||
// optional bytes 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(MSET_MESSAGE_FIELD_NUMBER, 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; |
@ -0,0 +1,262 @@ |
||||
/** |
||||
* @fileoverview Tests for message_set.js. |
||||
*/ |
||||
goog.module('protobuf.runtime.MessageSetTest'); |
||||
|
||||
goog.setTestOnly(); |
||||
|
||||
const Kernel = goog.require('protobuf.runtime.Kernel'); |
||||
const MessageSet = goog.require('protobuf.runtime.MessageSet'); |
||||
const TestMessage = goog.require('protobuf.testing.binary.TestMessage'); |
||||
|
||||
/** |
||||
* @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, TestMessage.instanceCreator)) |
||||
.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 = TestMessage.createEmpty(); |
||||
messageSet.setMessage(12345, message); |
||||
expect(messageSet.getMessageOrNull(12345, TestMessage.instanceCreator)) |
||||
.toBe(message); |
||||
}); |
||||
|
||||
it('returns null for cleared message', () => { |
||||
const messageSet = MessageSet.createEmpty(); |
||||
const message = TestMessage.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 = TestMessage.createEmpty(); |
||||
messageSet.setMessage(12345, message); |
||||
expect(messageSet.hasMessage(12345)).toBe(true); |
||||
}); |
||||
|
||||
it('returns false for cleared message', () => { |
||||
const messageSet = MessageSet.createEmpty(); |
||||
const message = TestMessage.createEmpty(); |
||||
messageSet.setMessage(12345, message); |
||||
messageSet.clearMessage(12345); |
||||
expect(messageSet.hasMessage(12345)).toBe(false); |
||||
}); |
||||
|
||||
it('returns false for cleared message without it being present', () => { |
||||
const messageSet = MessageSet.createEmpty(); |
||||
messageSet.clearMessage(12345); |
||||
expect(messageSet.hasMessage(12345)).toBe(false); |
||||
}); |
||||
|
||||
const createMessageSet = () => { |
||||
const messageSet = MessageSet.createEmpty(); |
||||
const message = TestMessage.createEmpty(); |
||||
message.setInt32(1, 2); |
||||
messageSet.setMessage(12345, message); |
||||
|
||||
|
||||
const parsedKernel = |
||||
Kernel.fromArrayBuffer(messageSet.internalGetKernel().serialize()); |
||||
return MessageSet.fromKernel(parsedKernel); |
||||
}; |
||||
|
||||
it('pass through pivot for getMessageOrNull', () => { |
||||
const messageSet = createMessageSet(); |
||||
const message = |
||||
messageSet.getMessageOrNull(12345, TestMessage.instanceCreator, 2); |
||||
expect(message.internalGetKernel().getPivot()).toBe(2); |
||||
}); |
||||
|
||||
it('pass through pivot for getMessageAttach', () => { |
||||
const messageSet = createMessageSet(); |
||||
const message = |
||||
messageSet.getMessageAttach(12345, TestMessage.instanceCreator, 2); |
||||
expect(message.internalGetKernel().getPivot()).toBe(2); |
||||
}); |
||||
|
||||
it('pass through pivot for getMessageAccessorOrNull', () => { |
||||
const messageSet = createMessageSet(); |
||||
const kernel = messageSet.getMessageAccessorOrNull(12345, 2); |
||||
expect(kernel.getPivot()).toBe(2); |
||||
}); |
||||
|
||||
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 outerMessage = Kernel.fromArrayBuffer(arrayBuffer); |
||||
|
||||
const messageSet = outerMessage.getMessage(10, MessageSet.fromKernel); |
||||
|
||||
const message = |
||||
messageSet.getMessageOrNull(12345, TestMessage.instanceCreator); |
||||
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 outerMessage = Kernel.fromArrayBuffer(arrayBuffer); |
||||
outerMessage.getMessageAttach(10, MessageSet.fromKernel); |
||||
|
||||
expect(outerMessage.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
|
||||
)); |
||||
}); |
||||
|
||||
it('allow for large typeIds', () => { |
||||
const messageSet = MessageSet.createEmpty(); |
||||
const message = TestMessage.createEmpty(); |
||||
messageSet.setMessage(0xFFFFFFFE >>> 0, message); |
||||
expect(messageSet.hasMessage(0xFFFFFFFE >>> 0)).toBe(true); |
||||
}); |
||||
}); |
||||
|
||||
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, TestMessage.instanceCreator(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, TestMessage.instanceCreator(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, TestMessage.instanceCreator) |
||||
.internalGetKernel(); |
||||
|
||||
expect(fooMessageParsed.getInt32WithDefault(20)).toBe(30); |
||||
}); |
||||
}); |
Loading…
Reference in new issue