blob: 2b4945b6a57d32330123687fedbcb033d4d1f288 [file] [log] [blame]
// 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.
using System.Collections.Concurrent;
namespace Apache.Fory;
internal static class DictionaryBits
{
public const byte TrackingKeyRef = 0b0000_0001;
public const byte KeyNull = 0b0000_0010;
public const byte DeclaredKeyType = 0b0000_0100;
public const byte TrackingValueRef = 0b0000_1000;
public const byte ValueNull = 0b0001_0000;
public const byte DeclaredValueType = 0b0010_0000;
}
public abstract class DictionaryLikeSerializer<TDictionary, TKey, TValue> : Serializer<TDictionary>
where TDictionary : class, IDictionary<TKey, TValue>
where TKey : notnull
{
public override TDictionary DefaultValue => null!;
protected abstract TDictionary CreateMap(int capacity);
protected virtual KeyValuePair<TKey, TValue>[] SnapshotPairs(TDictionary map)
{
return [.. map];
}
protected virtual void SetValue(TDictionary map, TKey key, TValue value)
{
map[key] = value;
}
public override void WriteData(WriteContext context, in TDictionary value, bool hasGenerics)
{
Serializer<TKey> keySerializer = context.TypeResolver.GetSerializer<TKey>();
Serializer<TValue> valueSerializer = context.TypeResolver.GetSerializer<TValue>();
TypeInfo keyTypeInfo = context.TypeResolver.GetTypeInfo<TKey>();
TypeInfo valueTypeInfo = context.TypeResolver.GetTypeInfo<TValue>();
TDictionary map = value ?? CreateMap(0);
context.Writer.WriteVarUInt32((uint)map.Count);
if (map.Count == 0)
{
return;
}
bool trackKeyRef = context.TrackRef && keyTypeInfo.IsRefType;
bool trackValueRef = context.TrackRef && valueTypeInfo.IsRefType;
bool keyDeclared = hasGenerics && !TypeResolver.NeedToWriteTypeInfoForField(keyTypeInfo);
bool valueDeclared = hasGenerics && !TypeResolver.NeedToWriteTypeInfoForField(valueTypeInfo);
bool keyDynamicType = keyTypeInfo.IsDynamicType;
bool valueDynamicType = valueTypeInfo.IsDynamicType;
KeyValuePair<TKey, TValue>[] pairs = SnapshotPairs(map);
if (keyDynamicType || valueDynamicType)
{
WriteDynamicMapPairs(
pairs,
context,
hasGenerics,
trackKeyRef,
trackValueRef,
keyDeclared,
valueDeclared,
keyDynamicType,
valueDynamicType,
keyTypeInfo,
valueTypeInfo,
keySerializer,
valueSerializer);
return;
}
int index = 0;
while (index < pairs.Length)
{
KeyValuePair<TKey, TValue> pair = pairs[index];
bool keyIsNull = context.TypeResolver.IsNoneObject(keyTypeInfo, pair.Key);
bool valueIsNull = context.TypeResolver.IsNoneObject(valueTypeInfo, pair.Value);
if (keyIsNull || valueIsNull)
{
byte header = 0;
if (trackKeyRef)
{
header |= DictionaryBits.TrackingKeyRef;
}
if (trackValueRef)
{
header |= DictionaryBits.TrackingValueRef;
}
if (keyIsNull)
{
header |= DictionaryBits.KeyNull;
}
if (valueIsNull)
{
header |= DictionaryBits.ValueNull;
}
if (!keyIsNull && keyDeclared)
{
header |= DictionaryBits.DeclaredKeyType;
}
if (!valueIsNull && valueDeclared)
{
header |= DictionaryBits.DeclaredValueType;
}
context.Writer.WriteUInt8(header);
if (!keyIsNull)
{
if (!keyDeclared)
{
context.TypeResolver.WriteTypeInfo(keySerializer, context);
}
keySerializer.Write(context, pair.Key, trackKeyRef ? RefMode.Tracking : RefMode.None, false, hasGenerics);
}
if (!valueIsNull)
{
if (!valueDeclared)
{
context.TypeResolver.WriteTypeInfo(valueSerializer, context);
}
valueSerializer.Write(context, pair.Value, trackValueRef ? RefMode.Tracking : RefMode.None, false, hasGenerics);
}
index += 1;
continue;
}
byte blockHeader = 0;
if (trackKeyRef)
{
blockHeader |= DictionaryBits.TrackingKeyRef;
}
if (trackValueRef)
{
blockHeader |= DictionaryBits.TrackingValueRef;
}
if (keyDeclared)
{
blockHeader |= DictionaryBits.DeclaredKeyType;
}
if (valueDeclared)
{
blockHeader |= DictionaryBits.DeclaredValueType;
}
context.Writer.WriteUInt8(blockHeader);
int chunkSizeOffset = context.Writer.Count;
context.Writer.WriteUInt8(0);
if (!keyDeclared)
{
context.TypeResolver.WriteTypeInfo(keySerializer, context);
}
if (!valueDeclared)
{
context.TypeResolver.WriteTypeInfo(valueSerializer, context);
}
byte chunkSize = 0;
while (index < pairs.Length && chunkSize < byte.MaxValue)
{
KeyValuePair<TKey, TValue> current = pairs[index];
if (context.TypeResolver.IsNoneObject(keyTypeInfo, current.Key) ||
context.TypeResolver.IsNoneObject(valueTypeInfo, current.Value))
{
break;
}
keySerializer.Write(context, current.Key, trackKeyRef ? RefMode.Tracking : RefMode.None, false, hasGenerics);
valueSerializer.Write(context, current.Value, trackValueRef ? RefMode.Tracking : RefMode.None, false, hasGenerics);
chunkSize += 1;
index += 1;
}
context.Writer.SetByte(chunkSizeOffset, chunkSize);
}
}
public override TDictionary ReadData(ReadContext context)
{
Serializer<TKey> keySerializer = context.TypeResolver.GetSerializer<TKey>();
Serializer<TValue> valueSerializer = context.TypeResolver.GetSerializer<TValue>();
TypeInfo keyTypeInfo = context.TypeResolver.GetTypeInfo<TKey>();
TypeInfo valueTypeInfo = context.TypeResolver.GetTypeInfo<TValue>();
int totalLength = checked((int)context.Reader.ReadVarUInt32());
if (totalLength == 0)
{
return CreateMap(0);
}
TDictionary map = CreateMap(totalLength);
bool keyDynamicType = keyTypeInfo.IsDynamicType;
bool valueDynamicType = valueTypeInfo.IsDynamicType;
bool canonicalizeValues = context.TrackRef && valueTypeInfo.IsRefType;
int readCount = 0;
while (readCount < totalLength)
{
byte header = context.Reader.ReadUInt8();
bool trackKeyRef = (header & DictionaryBits.TrackingKeyRef) != 0;
bool keyNull = (header & DictionaryBits.KeyNull) != 0;
bool keyDeclared = (header & DictionaryBits.DeclaredKeyType) != 0;
bool trackValueRef = (header & DictionaryBits.TrackingValueRef) != 0;
bool valueNull = (header & DictionaryBits.ValueNull) != 0;
bool valueDeclared = (header & DictionaryBits.DeclaredValueType) != 0;
if (keyNull && valueNull)
{
// Dictionary-like containers cannot represent a null key.
// Drop this entry instead of mapping it to default(TKey), which would corrupt key semantics.
readCount += 1;
continue;
}
if (keyNull)
{
_ = ReadValueElement(
context,
trackValueRef,
!valueDeclared,
canonicalizeValues,
valueSerializer);
// Preserve stream/reference state by reading value payload, then skip null-key entry.
readCount += 1;
continue;
}
if (valueNull)
{
TKey key = keySerializer.Read(
context,
trackKeyRef ? RefMode.Tracking : RefMode.None,
!keyDeclared);
SetValue(map, key, (TValue)valueSerializer.DefaultObject!);
readCount += 1;
continue;
}
int chunkSize = context.Reader.ReadUInt8();
if (keyDynamicType || valueDynamicType)
{
for (int i = 0; i < chunkSize; i++)
{
TypeInfo? keyTypeInfoForRead = null;
TypeInfo? valueTypeInfoForRead = null;
if (!keyDeclared)
{
if (keyDynamicType)
{
keyTypeInfoForRead = context.TypeResolver.ReadAnyTypeInfo(context);
}
else
{
context.TypeResolver.ReadTypeInfo(keySerializer, context);
}
}
if (!valueDeclared)
{
if (valueDynamicType)
{
valueTypeInfoForRead = context.TypeResolver.ReadAnyTypeInfo(context);
}
else
{
context.TypeResolver.ReadTypeInfo(valueSerializer, context);
}
}
if (keyTypeInfoForRead is not null)
{
context.SetReadTypeInfo(typeof(TKey), keyTypeInfoForRead);
}
TKey key = keySerializer.Read(context, trackKeyRef ? RefMode.Tracking : RefMode.None, false);
if (keyTypeInfoForRead is not null)
{
context.ClearReadTypeInfo(typeof(TKey));
}
if (valueTypeInfoForRead is not null)
{
context.SetReadTypeInfo(typeof(TValue), valueTypeInfoForRead);
}
TValue value = ReadValueElement(
context,
trackValueRef,
false,
canonicalizeValues,
valueSerializer);
if (valueTypeInfoForRead is not null)
{
context.ClearReadTypeInfo(typeof(TValue));
}
SetValue(map, key, value);
}
readCount += chunkSize;
continue;
}
if (!keyDeclared)
{
context.TypeResolver.ReadTypeInfo(keySerializer, context);
}
if (!valueDeclared)
{
context.TypeResolver.ReadTypeInfo(valueSerializer, context);
}
for (int i = 0; i < chunkSize; i++)
{
TKey key = keySerializer.Read(context, trackKeyRef ? RefMode.Tracking : RefMode.None, false);
TValue value = ReadValueElement(context, trackValueRef, false, canonicalizeValues, valueSerializer);
SetValue(map, key, value);
}
if (!keyDeclared)
{
context.ClearReadTypeInfo(typeof(TKey));
}
if (!valueDeclared)
{
context.ClearReadTypeInfo(typeof(TValue));
}
readCount += chunkSize;
}
return map;
}
private static void WriteDynamicMapPairs(
KeyValuePair<TKey, TValue>[] pairs,
WriteContext context,
bool hasGenerics,
bool trackKeyRef,
bool trackValueRef,
bool keyDeclared,
bool valueDeclared,
bool keyDynamicType,
bool valueDynamicType,
TypeInfo keyTypeInfo,
TypeInfo valueTypeInfo,
Serializer<TKey> keySerializer,
Serializer<TValue> valueSerializer)
{
foreach (KeyValuePair<TKey, TValue> pair in pairs)
{
bool keyIsNull = context.TypeResolver.IsNoneObject(keyTypeInfo, pair.Key);
bool valueIsNull = context.TypeResolver.IsNoneObject(valueTypeInfo, pair.Value);
byte header = 0;
if (trackKeyRef)
{
header |= DictionaryBits.TrackingKeyRef;
}
if (trackValueRef)
{
header |= DictionaryBits.TrackingValueRef;
}
if (keyIsNull)
{
header |= DictionaryBits.KeyNull;
}
else if (!keyDynamicType && keyDeclared)
{
header |= DictionaryBits.DeclaredKeyType;
}
if (valueIsNull)
{
header |= DictionaryBits.ValueNull;
}
else if (!valueDynamicType && valueDeclared)
{
header |= DictionaryBits.DeclaredValueType;
}
context.Writer.WriteUInt8(header);
if (keyIsNull && valueIsNull)
{
continue;
}
if (keyIsNull)
{
valueSerializer.Write(
context,
pair.Value,
trackValueRef ? RefMode.Tracking : RefMode.None,
!valueDeclared,
hasGenerics);
continue;
}
if (valueIsNull)
{
keySerializer.Write(
context,
pair.Key,
trackKeyRef ? RefMode.Tracking : RefMode.None,
!keyDeclared,
hasGenerics);
continue;
}
context.Writer.WriteUInt8(1);
if (!keyDeclared)
{
if (keyDynamicType)
{
DynamicAnyCodec.WriteAnyTypeInfo(pair.Key!, context);
}
else
{
context.TypeResolver.WriteTypeInfo(keySerializer, context);
}
}
if (!valueDeclared)
{
if (valueDynamicType)
{
DynamicAnyCodec.WriteAnyTypeInfo(pair.Value!, context);
}
else
{
context.TypeResolver.WriteTypeInfo(valueSerializer, context);
}
}
keySerializer.Write(context, pair.Key, trackKeyRef ? RefMode.Tracking : RefMode.None, false, hasGenerics);
valueSerializer.Write(context, pair.Value, trackValueRef ? RefMode.Tracking : RefMode.None, false, hasGenerics);
}
}
private static TValue ReadValueElement(
ReadContext context,
bool trackValueRef,
bool readTypeInfo,
bool canonicalizeValues,
Serializer<TValue> valueSerializer)
{
if (trackValueRef || !canonicalizeValues)
{
return valueSerializer.Read(context, trackValueRef ? RefMode.Tracking : RefMode.None, readTypeInfo);
}
int start = context.Reader.Cursor;
TValue value = valueSerializer.Read(context, RefMode.None, readTypeInfo);
int end = context.Reader.Cursor;
return context.CanonicalizeNonTrackingRef(value, start, end);
}
}
public class DictionarySerializer<TKey, TValue> : DictionaryLikeSerializer<Dictionary<TKey, TValue>, TKey, TValue>
where TKey : notnull
{
protected override Dictionary<TKey, TValue> CreateMap(int capacity)
{
return new Dictionary<TKey, TValue>(capacity);
}
}
public class SortedDictionarySerializer<TKey, TValue> : DictionaryLikeSerializer<SortedDictionary<TKey, TValue>, TKey, TValue>
where TKey : notnull
{
protected override SortedDictionary<TKey, TValue> CreateMap(int capacity)
{
_ = capacity;
return new SortedDictionary<TKey, TValue>();
}
}
public class SortedListSerializer<TKey, TValue> : DictionaryLikeSerializer<SortedList<TKey, TValue>, TKey, TValue>
where TKey : notnull
{
protected override SortedList<TKey, TValue> CreateMap(int capacity)
{
return new SortedList<TKey, TValue>(capacity);
}
}
public class ConcurrentDictionarySerializer<TKey, TValue> : DictionaryLikeSerializer<ConcurrentDictionary<TKey, TValue>, TKey, TValue>
where TKey : notnull
{
protected override ConcurrentDictionary<TKey, TValue> CreateMap(int capacity)
{
int initialCapacity = Math.Max(capacity, 1);
return new ConcurrentDictionary<TKey, TValue>(Environment.ProcessorCount, initialCapacity);
}
protected override KeyValuePair<TKey, TValue>[] SnapshotPairs(ConcurrentDictionary<TKey, TValue> map)
{
return map.ToArray();
}
}