// Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using MessagePack.Formatters; using MessagePack.Internal; namespace MessagePack.Resolvers { /// /// Represents a collection of formatters and resolvers acting as one. /// /// /// This class is not thread-safe for mutations. It is thread-safe when not being written to. /// public static class CompositeResolver { private static readonly ReadOnlyDictionary EmptyFormattersByType = new ReadOnlyDictionary(new Dictionary()); /// /// Initializes a new instance of an with the specified formatters and sub-resolvers. /// /// /// A list of instances of to prefer (above the ). /// The formatters are searched in the order given, so if two formatters support serializing the same type, the first one is used. /// May not be null, but may be . /// /// /// A list of resolvers to use for serializing types for which does not include a formatter. /// The resolvers are searched in the order given, so if two resolvers support serializing the same type, the first one is used. /// May not be null, but may be . /// /// /// An instance of . /// public static IFormatterResolver Create(IReadOnlyList formatters, IReadOnlyList resolvers) { if (formatters is null) { throw new ArgumentNullException(nameof(formatters)); } if (resolvers is null) { throw new ArgumentNullException(nameof(resolvers)); } // Make a copy of the resolvers list provided by the caller to guard against them changing it later. var immutableFormatters = formatters.ToArray(); var immutableResolvers = resolvers.ToArray(); return new CachingResolver(immutableFormatters, immutableResolvers); } public static IFormatterResolver Create(params IFormatterResolver[] resolvers) => Create(Array.Empty(), resolvers); public static IFormatterResolver Create(params IMessagePackFormatter[] formatters) => Create(formatters, Array.Empty()); private class CachingResolver : IFormatterResolver { private readonly ThreadsafeTypeKeyHashTable formattersCache = new ThreadsafeTypeKeyHashTable(); private readonly IMessagePackFormatter[] subFormatters; private readonly IFormatterResolver[] subResolvers; /// /// Initializes a new instance of the class. /// internal CachingResolver(IMessagePackFormatter[] subFormatters, IFormatterResolver[] subResolvers) { this.subFormatters = subFormatters ?? throw new ArgumentNullException(nameof(subFormatters)); this.subResolvers = subResolvers ?? throw new ArgumentNullException(nameof(subResolvers)); } public IMessagePackFormatter GetFormatter() { if (!this.formattersCache.TryGetValue(typeof(T), out IMessagePackFormatter formatter)) { foreach (var subFormatter in this.subFormatters) { if (subFormatter is IMessagePackFormatter) { formatter = subFormatter; goto CACHE; } } foreach (IFormatterResolver resolver in this.subResolvers) { formatter = resolver.GetFormatter(); if (formatter != null) { goto CACHE; } } // when not found, cache null. CACHE: this.formattersCache.TryAdd(typeof(T), formatter); } return (IMessagePackFormatter)formatter; } } } }