blob: dc6eda05cdd67e4d7bb8c16547981dc82c50ef7d [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { MapTypeDescription, TypeDescription } from "../description";
import { CodecBuilder } from "./builder";
import { BaseSerializerGenerator, RefState } from "./serializer";
import { CodegenRegistry } from "./router";
import { InternalSerializerType, RefFlags, Serializer } from "../type";
import { Scope } from "./scope";
import Fury from "../fury";
const MapFlags = {
/** Whether track elements ref. */
TRACKING_REF: 0b1,
/** Whether collection has null. */
HAS_NULL: 0b10,
/** Whether collection elements type is not declare type. */
NOT_DECL_ELEMENT_TYPE: 0b100,
/** Whether collection elements type different. */
NOT_SAME_TYPE: 0b1000,
};
class TypeInfo {
private static IS_NULL = 0b10;
private static TRACKING_REF = 0b01;
static elementInfo(typeId: number, isNull: 0 | 1, trackRef: 0 | 1) {
return typeId << 16 | isNull << 1 | trackRef;
}
static isNull(info: number) {
return info & this.IS_NULL;
}
static trackingRef(info: number) {
return info & this.TRACKING_REF;
}
}
class MapChunkWriter {
private preKeyInfo = 0;
private preValueInfo = 0;
private chunkSize = 0;
private chunkOffset = 0;
private header = 0;
constructor(private fury: Fury) {
}
private getHead(keyInfo: number, valueInfo: number) {
let flag = 0;
if (TypeInfo.isNull(keyInfo)) {
flag |= MapFlags.HAS_NULL;
}
if (TypeInfo.trackingRef(keyInfo)) {
flag |= MapFlags.TRACKING_REF;
}
flag <<= 4;
if (TypeInfo.isNull(valueInfo)) {
flag |= MapFlags.HAS_NULL;
}
if (TypeInfo.trackingRef(valueInfo)) {
flag |= MapFlags.TRACKING_REF;
}
return flag;
}
private writeHead(keyInfo: number, valueInfo: number) {
// KV header
const header = this.getHead(keyInfo, valueInfo);
this.fury.binaryWriter.uint8(header);
// chunkSize, max 255
this.chunkOffset = this.fury.binaryWriter.getCursor();
this.fury.binaryWriter.uint8(0);
this.fury.binaryWriter.uint32((keyInfo >> 16) | (valueInfo & 0xFFFF0000));
return header;
}
next(keyInfo: number, valueInfo: number) {
// max size of chunk is 255
if (this.chunkSize == 255
|| this.chunkOffset == 0
|| this.preKeyInfo !== keyInfo
|| this.preValueInfo !== valueInfo
) {
// new chunk
this.endChunk();
this.chunkSize++;
this.preKeyInfo = keyInfo;
this.preValueInfo = valueInfo;
return this.header = this.writeHead(keyInfo, valueInfo);
}
this.chunkSize++;
return this.header;
}
endChunk() {
if (this.chunkOffset > 0) {
this.fury.binaryWriter.setUint8Position(this.chunkOffset, this.chunkSize);
this.chunkSize = 0;
}
}
}
class MapAnySerializer {
private keySerializer: Serializer | null = null;
private valueSerializer: Serializer | null = null;
constructor(private fury: Fury, keySerializerId: null | number, valueSerializerId: null | number) {
if (keySerializerId !== null) {
fury.classResolver.getSerializerById(keySerializerId);
}
if (valueSerializerId !== null) {
fury.classResolver.getSerializerById(valueSerializerId);
}
}
private writeHead(header: number, v: any) {
if (header !== 0) {
if (header & MapFlags.HAS_NULL) {
if (v === null || v === undefined) {
this.fury.binaryWriter.uint8(RefFlags.NullFlag);
}
}
if (header & MapFlags.TRACKING_REF) {
const keyRef = this.fury.referenceResolver.existsWriteObject(v);
if (keyRef !== undefined) {
this.fury.binaryWriter.uint8(RefFlags.RefFlag);
this.fury.binaryWriter.uint16(keyRef);
} else {
this.fury.binaryWriter.uint8(RefFlags.RefValueFlag);
}
} else {
this.fury.binaryWriter.uint8(RefFlags.NotNullValueFlag);
}
}
}
write(value: Map<any, any>) {
const mapChunkWriter = new MapChunkWriter(this.fury);
this.fury.binaryWriter.varInt32(value.size);
for (const [k, v] of value.entries()) {
const keySerializer = this.keySerializer !== null ? this.keySerializer : this.fury.classResolver.getSerializerByData(k);
const valueSerializer = this.valueSerializer !== null ? this.valueSerializer : this.fury.classResolver.getSerializerByData(v);
const header = mapChunkWriter.next(
TypeInfo.elementInfo(keySerializer!.meta.typeId!, k == null ? 1 : 0, keySerializer!.meta.needToWriteRef ? 1 : 0),
TypeInfo.elementInfo(valueSerializer!.meta.typeId!, v == null ? 1 : 0, valueSerializer!.meta.needToWriteRef ? 1 : 0)
);
this.writeHead(header >> 4, k);
keySerializer!.writeInner(k);
this.writeHead(header & 0b00001111, v);
valueSerializer!.writeInner(v);
}
mapChunkWriter.endChunk();
}
private readElement(header: number, serializer: Serializer | null) {
if (header === 0) {
return serializer!.readInner(false);
}
const isSame = !(header & MapFlags.NOT_SAME_TYPE);
const includeNone = header & MapFlags.HAS_NULL;
const trackingRef = header & MapFlags.TRACKING_REF;
let flag = 0;
if (trackingRef || includeNone) {
flag = this.fury.binaryReader.uint8();
}
if (!isSame) {
serializer = this.fury.classResolver.getSerializerByType(this.fury.binaryReader.int16());
}
switch (flag) {
case RefFlags.RefValueFlag:
return serializer!.readInner(true);
case RefFlags.RefFlag:
return this.fury.referenceResolver.getReadObject(this.fury.binaryReader.varUInt32());
case RefFlags.NullFlag:
return null;
case RefFlags.NotNullValueFlag:
return serializer!.readInner(false);
}
}
read(fromRef: boolean): any {
let count = this.fury.binaryReader.varInt32();
const result = new Map();
if (fromRef) {
this.fury.referenceResolver.reference(result);
}
while (count > 0) {
const header = this.fury.binaryReader.uint16();
const chunkSize = header >> 8;
const keyHeader = header >> 12;
const valueHeader = header & 0b00001111;
let keySerializer = null;
let valueSerializer = null;
if (!(keyHeader & MapFlags.NOT_SAME_TYPE)) {
keySerializer = this.fury.classResolver.getSerializerById(this.fury.binaryReader.uint16());
}
if (!(valueHeader & MapFlags.NOT_SAME_TYPE)) {
valueSerializer = this.fury.classResolver.getSerializerById(this.fury.binaryReader.uint16());
}
for (let index = 0; index < chunkSize; index++) {
result.set(
this.readElement(keyHeader, keySerializer),
this.readElement(valueHeader, valueSerializer)
);
count--;
}
}
return result;
}
}
export class MapSerializerGenerator extends BaseSerializerGenerator {
description: MapTypeDescription;
constructor(description: TypeDescription, builder: CodecBuilder, scope: Scope) {
super(description, builder, scope);
this.description = <MapTypeDescription>description;
}
private innerMeta() {
const inner = this.description;
return [this.builder.meta(inner.options.key), this.builder.meta(inner.options.value)];
}
writeStmt(accessor: string): string {
const [keyMeta, valueMeta] = this.innerMeta();
const anySerializer = this.builder.getExternal(MapAnySerializer.name);
return `
new (${anySerializer})(${this.builder.furyName()}, ${keyMeta.typeId}, ${valueMeta.typeId}).write(${accessor})
`;
}
readStmt(accessor: (expr: string) => string, refState: RefState): string {
const anySerializer = this.builder.getExternal(MapAnySerializer.name);
const [keyMeta, valueMeta] = this.innerMeta();
return accessor(`new (${anySerializer})(${this.builder.furyName()}, ${keyMeta.typeId}, ${valueMeta.typeId}).read(${refState.toConditionExpr()})
`);
}
}
CodegenRegistry.registerExternal(MapAnySerializer);
CodegenRegistry.register(InternalSerializerType.MAP, MapSerializerGenerator);