| /* |
| * 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 { TypeInfo } from "../typeInfo"; |
| import { CodecBuilder } from "./builder"; |
| import { BaseSerializerGenerator, SerializerGenerator } from "./serializer"; |
| import { CodegenRegistry } from "./router"; |
| import { TypeId, RefFlags, Serializer } from "../type"; |
| import { Scope } from "./scope"; |
| import { AnyHelper } from "./any"; |
| import { ReadContext, WriteContext } from "../context"; |
| |
| export const CollectionFlags = { |
| /** Whether track elements ref. */ |
| TRACKING_REF: 0b1, |
| |
| /** Whether collection has null. */ |
| HAS_NULL: 0b10, |
| |
| /** Whether collection elements type is not declare type. */ |
| DECL_ELEMENT_TYPE: 0b100, |
| |
| /** Whether collection elements type different. */ |
| SAME_TYPE: 0b1000, |
| }; |
| |
| class CollectionAnySerializer { |
| constructor( |
| private writeContext: WriteContext, |
| private readContext: ReadContext, |
| ) {} |
| |
| private readSerializerWithDepth(serializer: Serializer, fromRef: boolean) { |
| this.readContext.incReadDepth(); |
| const result = serializer.read(fromRef); |
| this.readContext.decReadDepth(); |
| return result; |
| } |
| |
| protected writeElementsHeader(arr: any) { |
| let flag = 0; |
| let isSame = true; |
| let serializer: Serializer | null | undefined = null; |
| let includeNone = false; |
| let trackingRef = false; |
| |
| for (const item of arr) { |
| if (item === undefined || item === null) { |
| includeNone = true; |
| continue; |
| } |
| const current = this.writeContext.typeResolver.getSerializerByData(item); |
| if (!current) { |
| throw new Error("can't detect the type of item in list"); |
| } |
| if (!trackingRef) { |
| trackingRef = current.needToWriteRef(); |
| } |
| if (isSame) { |
| if ( |
| serializer !== null |
| && serializer !== undefined |
| && current !== serializer |
| ) { |
| isSame = false; |
| } else { |
| serializer = current; |
| } |
| } |
| } |
| |
| if (isSame) { |
| flag |= CollectionFlags.SAME_TYPE; |
| } |
| if (includeNone) { |
| flag |= CollectionFlags.HAS_NULL; |
| } |
| if (trackingRef) { |
| flag |= CollectionFlags.TRACKING_REF; |
| } |
| this.writeContext.writer.writeUint8(flag); |
| return { |
| serializer, |
| isSame, |
| flag, |
| includeNone, |
| trackingRef, |
| }; |
| } |
| |
| write(value: any, size: number) { |
| this.writeContext.writer.writeVarUint32Small7(size); |
| if (size === 0) { |
| return; |
| } |
| const { serializer, isSame, includeNone, trackingRef } |
| = this.writeElementsHeader(value); |
| if (isSame) { |
| serializer!.writeTypeInfo(value); |
| if (trackingRef) { |
| for (const item of value) { |
| if (!serializer!.writeRefOrNull(item)) { |
| serializer!.write(item); |
| } |
| } |
| } else if (includeNone) { |
| for (const item of value) { |
| if (item === null || item === undefined) { |
| this.writeContext.writer.writeInt8(RefFlags.NullFlag); |
| } else { |
| this.writeContext.writer.writeInt8(RefFlags.NotNullValueFlag); |
| serializer!.write(item); |
| } |
| } |
| } else { |
| for (const item of value) { |
| serializer!.write(item); |
| } |
| } |
| } else { |
| if (trackingRef) { |
| for (const item of value) { |
| const serializer |
| = this.writeContext.typeResolver.getSerializerByData(item); |
| serializer?.writeRef(item); |
| } |
| } else if (includeNone) { |
| for (const item of value) { |
| if (item === null || item === undefined) { |
| this.writeContext.writer.writeInt8(RefFlags.NullFlag); |
| } else { |
| const serializer |
| = this.writeContext.typeResolver.getSerializerByData(item); |
| this.writeContext.writer.writeInt8(RefFlags.NotNullValueFlag); |
| serializer!.writeNoRef(item); |
| } |
| } |
| } else { |
| for (const item of value) { |
| const serializer |
| = this.writeContext.typeResolver.getSerializerByData(item); |
| serializer!.writeNoRef(item); |
| } |
| } |
| } |
| } |
| |
| read( |
| accessor: (result: any, index: number, v: any) => void, |
| createCollection: (len: number) => any, |
| fromRef: boolean, |
| ): any { |
| void fromRef; |
| const len = this.readContext.reader.readVarUint32Small7(); |
| const result = createCollection(len); |
| if (len === 0) { |
| return result; |
| } |
| this.readContext.checkCollectionSize(len); |
| const flags = this.readContext.reader.readUint8(); |
| // IMPORTANT: collection readers must obey the ref/null bits written on the |
| // wire, not local TypeScript metadata that may imply a different ref |
| // policy. Shared xlang tests intentionally deserialize one ref policy and |
| // then serialize another local payload. DO NOT REMOVE this comment. |
| const isSame = flags & CollectionFlags.SAME_TYPE; |
| const includeNone = flags & CollectionFlags.HAS_NULL; |
| const refTracking = flags & CollectionFlags.TRACKING_REF; |
| |
| if (isSame) { |
| const serializer = AnyHelper.detectSerializer(this.readContext); |
| if (refTracking) { |
| for (let i = 0; i < len; i++) { |
| serializer.readRef(); |
| const refFlag = this.readContext.readRefFlag(); |
| if (refFlag === RefFlags.RefFlag) { |
| const refId = this.readContext.reader.readVarUInt32(); |
| accessor(result, i, this.readContext.getReadRef(refId)); |
| } else if (refFlag === RefFlags.RefValueFlag) { |
| accessor( |
| result, |
| i, |
| this.readSerializerWithDepth(serializer!, true), |
| ); |
| } else { |
| accessor(result, i, null); |
| } |
| } |
| } else if (includeNone) { |
| for (let i = 0; i < len; i++) { |
| const flag = this.readContext.reader.readInt8(); |
| if (flag === RefFlags.NullFlag) { |
| accessor(result, i, null); |
| } else { |
| accessor( |
| result, |
| i, |
| this.readSerializerWithDepth(serializer!, false), |
| ); |
| } |
| } |
| } else { |
| for (let i = 0; i < len; i++) { |
| accessor(result, i, this.readSerializerWithDepth(serializer!, false)); |
| } |
| } |
| } else { |
| if (refTracking) { |
| for (let i = 0; i < len; i++) { |
| const itemSerializer = AnyHelper.detectSerializer(this.readContext); |
| accessor(result, i, itemSerializer!.readRef()); |
| } |
| } else if (includeNone) { |
| for (let i = 0; i < len; i++) { |
| const flag = this.readContext.reader.readInt8(); |
| if (flag === RefFlags.NullFlag) { |
| accessor(result, i, null); |
| } else { |
| const itemSerializer = AnyHelper.detectSerializer(this.readContext); |
| accessor( |
| result, |
| i, |
| this.readSerializerWithDepth(itemSerializer!, false), |
| ); |
| } |
| } |
| } else { |
| for (let i = 0; i < len; i++) { |
| const itemSerializer = AnyHelper.detectSerializer(this.readContext); |
| accessor( |
| result, |
| i, |
| this.readSerializerWithDepth(itemSerializer!, false), |
| ); |
| } |
| } |
| } |
| return result; |
| } |
| } |
| |
| export abstract class CollectionSerializerGenerator extends BaseSerializerGenerator { |
| typeInfo: TypeInfo; |
| innerGenerator: SerializerGenerator; |
| |
| constructor(typeInfo: TypeInfo, builder: CodecBuilder, scope: Scope) { |
| super(typeInfo, builder, scope); |
| this.typeInfo = typeInfo; |
| const inner = this.genericTypeDescriptin()!; |
| this.innerGenerator = CodegenRegistry.newGeneratorByTypeInfo( |
| inner, |
| this.builder, |
| this.scope, |
| ); |
| } |
| |
| abstract genericTypeDescriptin(): TypeInfo | undefined; |
| |
| private isAny() { |
| return this.genericTypeDescriptin()?.typeId === TypeId.UNKNOWN; |
| } |
| |
| abstract newCollection(lenAccessor: string): string; |
| |
| abstract putAccessor(result: string, item: string, index: string): string; |
| |
| abstract sizeProp(): string; |
| |
| private isDeclaredElementType() { |
| const innerTypeId = this.innerGenerator.getTypeId(); |
| return ( |
| innerTypeId !== TypeId.STRUCT |
| && innerTypeId !== TypeId.COMPATIBLE_STRUCT |
| && innerTypeId !== TypeId.NAMED_STRUCT |
| && innerTypeId !== TypeId.NAMED_COMPATIBLE_STRUCT |
| && innerTypeId !== TypeId.EXT |
| && innerTypeId !== TypeId.NAMED_EXT |
| ); |
| } |
| |
| protected writeElementsHeader(accessor: string, flagAccessor: string) { |
| const item = this.scope.uniqueName("item"); |
| const stmts = []; |
| stmts.push(` |
| for (const ${item} of ${accessor}) { |
| if (${item} === null || ${item} === undefined) { |
| ${flagAccessor} |= ${CollectionFlags.HAS_NULL}; |
| break; |
| } |
| } |
| `); |
| stmts.push(`${this.builder.writer.writeUint8(flagAccessor)}`); |
| return stmts.join("\n"); |
| } |
| |
| writeSpecificType(accessor: string): string { |
| const item = this.scope.uniqueName("item"); |
| const flags = this.scope.uniqueName("flags"); |
| const existsId = this.scope.uniqueName("existsId"); |
| const flag = this.isDeclaredElementType() |
| ? CollectionFlags.SAME_TYPE | CollectionFlags.DECL_ELEMENT_TYPE |
| : CollectionFlags.SAME_TYPE; |
| return ` |
| let ${flags} = ${(this.innerGenerator.needToWriteRef() ? CollectionFlags.TRACKING_REF : 0) | flag}; |
| ${this.builder.writer.writeVarUint32Small7(`${accessor}.${this.sizeProp()}`)} |
| if (${accessor}.${this.sizeProp()} > 0) { |
| ${this.writeElementsHeader(accessor, flags)} |
| if (!(${flags} & ${CollectionFlags.DECL_ELEMENT_TYPE})) { |
| ${this.innerGenerator.writeEmbed().writeTypeInfo("null")} |
| } |
| ${this.builder.writer.reserve(`${this.innerGenerator.getFixedSize()} * ${accessor}.${this.sizeProp()}`)}; |
| if (${flags} & ${CollectionFlags.TRACKING_REF}) { |
| for (const ${item} of ${accessor}) { |
| if (${item} !== null && ${item} !== undefined) { |
| const ${existsId} = ${this.builder.referenceResolver.getWrittenRefId(item)}; |
| if (typeof ${existsId} === "number") { |
| ${this.builder.writer.writeInt8(RefFlags.RefFlag)} |
| ${this.builder.writer.writeVarUInt32(existsId)} |
| } else { |
| ${this.builder.referenceResolver.writeRef(item)} |
| ${this.builder.writer.writeInt8(RefFlags.RefValueFlag)}; |
| ${this.innerGenerator.writeEmbed().write(item)} |
| } |
| } else { |
| ${this.builder.writer.writeInt8(RefFlags.NullFlag)}; |
| } |
| } |
| } else if (${flags} & ${CollectionFlags.HAS_NULL}) { |
| for (const ${item} of ${accessor}) { |
| if (${item} !== null && ${item} !== undefined) { |
| ${this.builder.writer.writeInt8(RefFlags.NotNullValueFlag)}; |
| ${this.innerGenerator.writeEmbed().write(item)} |
| } else { |
| ${this.builder.writer.writeInt8(RefFlags.NullFlag)}; |
| } |
| } |
| } else { |
| for (const ${item} of ${accessor}) { |
| ${this.innerGenerator.writeEmbed().write(item)} |
| } |
| } |
| } |
| `; |
| } |
| |
| readSpecificType( |
| accessor: (expr: string) => string, |
| refState: string, |
| ): string { |
| const result = this.scope.uniqueName("result"); |
| const len = this.scope.uniqueName("len"); |
| const flags = this.scope.uniqueName("flags"); |
| const idx = this.scope.uniqueName("idx"); |
| const refFlag = this.scope.uniqueName("refFlag"); |
| const elemSerializer = this.scope.uniqueName("elemSerializer"); |
| const anyHelper = this.builder.getExternal(AnyHelper.name); |
| const readContextName = this.builder.getReadContextName(); |
| // Skip depth tracking for leaf element types (primitives, string, enum, time, typed arrays). |
| const innerIsLeaf = TypeId.isLeafTypeId(this.innerGenerator.getTypeId()!); |
| const readInnerElement = ( |
| assignStmt: (x: any) => string, |
| refState: string, |
| ) => { |
| return innerIsLeaf |
| ? this.innerGenerator.read(assignStmt, refState) |
| : this.innerGenerator.readWithDepth(assignStmt, refState); |
| }; |
| return ` |
| const ${len} = ${this.builder.reader.readVarUint32Small7()}; |
| ${this.builder.getReadContextName()}.checkCollectionSize(${len}); |
| const ${result} = ${this.newCollection(len)}; |
| ${this.maybeReference(result, refState)} |
| if (${len} > 0) { |
| const ${flags} = ${this.builder.reader.readUint8()}; |
| let ${elemSerializer} = null; |
| if (!(${flags} & ${CollectionFlags.DECL_ELEMENT_TYPE})) { |
| ${elemSerializer} = ${anyHelper}.detectSerializer(${readContextName}); |
| } |
| if (${flags} & ${CollectionFlags.TRACKING_REF}) { |
| for (let ${idx} = 0; ${idx} < ${len}; ${idx}++) { |
| const ${refFlag} = ${this.builder.reader.readInt8()}; |
| switch (${refFlag}) { |
| case ${RefFlags.NotNullValueFlag}: |
| case ${RefFlags.RefValueFlag}: |
| if (${elemSerializer}) { |
| ${innerIsLeaf ? "" : `${readContextName}.incReadDepth();`} |
| ${this.putAccessor(result, `${elemSerializer}.read(${refFlag} === ${RefFlags.RefValueFlag})`, idx)} |
| ${innerIsLeaf ? "" : `${readContextName}.decReadDepth();`} |
| } else { |
| ${readInnerElement((x: any) => `${this.putAccessor(result, x, idx)}`, `${refFlag} === ${RefFlags.RefValueFlag}`)} |
| } |
| break; |
| case ${RefFlags.RefFlag}: |
| ${this.putAccessor(result, this.builder.referenceResolver.getReadRef(this.builder.reader.readVarUInt32()), idx)} |
| break; |
| case ${RefFlags.NullFlag}: |
| ${this.putAccessor(result, "null", idx)} |
| break; |
| } |
| } |
| } else if (${flags} & ${CollectionFlags.HAS_NULL}) { |
| for (let ${idx} = 0; ${idx} < ${len}; ${idx}++) { |
| if (${this.builder.reader.readInt8()} == ${RefFlags.NullFlag}) { |
| ${this.putAccessor(result, "null", idx)} |
| } else { |
| if (${elemSerializer}) { |
| ${innerIsLeaf ? "" : `${readContextName}.incReadDepth();`} |
| ${this.putAccessor(result, `${elemSerializer}.read(false)`, idx)} |
| ${innerIsLeaf ? "" : `${readContextName}.decReadDepth();`} |
| } else { |
| ${readInnerElement((x: any) => `${this.putAccessor(result, x, idx)}`, "false")} |
| } |
| } |
| } |
| } else { |
| for (let ${idx} = 0; ${idx} < ${len}; ${idx}++) { |
| if (${elemSerializer}) { |
| ${innerIsLeaf ? "" : `${readContextName}.incReadDepth();`} |
| ${this.putAccessor(result, `${elemSerializer}.read(false)`, idx)} |
| ${innerIsLeaf ? "" : `${readContextName}.decReadDepth();`} |
| } else { |
| ${readInnerElement((x: any) => `${this.putAccessor(result, x, idx)}`, "false")} |
| } |
| } |
| } |
| } |
| ${accessor(result)} |
| `; |
| } |
| |
| write(accessor: string): string { |
| if (this.isAny()) { |
| return ` |
| new (${this.builder.getExternal(CollectionAnySerializer.name)})(${this.builder.getWriteContextName()}, ${this.builder.getReadContextName()}).write(${accessor}, ${accessor}.${this.sizeProp()}) |
| `; |
| } |
| return this.writeSpecificType(accessor); |
| } |
| |
| read(accessor: (expr: string) => string, refState: string): string { |
| if (this.isAny()) { |
| return accessor(`new (${this.builder.getExternal(CollectionAnySerializer.name)})(${this.builder.getWriteContextName()}, ${this.builder.getReadContextName()}).read((result, i, v) => { |
| ${this.putAccessor("result", "v", "i")}; |
| }, (len) => ${this.newCollection("len")}, ${refState}); |
| `); |
| } |
| return this.readSpecificType(accessor, refState); |
| } |
| } |
| |
| CodegenRegistry.registerExternal(CollectionSerializerGenerator); |
| CodegenRegistry.registerExternal(CollectionAnySerializer); |