| // 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(); |
| } |
| } |