// SPDX-License-Identifier: AGPL-3.0-or-later // Copyright (C) 2022-2026 Aykut Alparslan KOC using System.Collections.ObjectModel; using System.Globalization; using System.IO; using System.Reflection; namespace Ferrite.TL.Schema; public static class LayerSchemaCatalog { public const int BaseLayer = 229; private const string Prefix = "Ferrite.TL.Schema.Schemas."; private const string CompatPrefix = "Ferrite.TL.Schema.Compat."; private const string BaseResource = Prefix + "baseLayer.tl"; private const string IntroductionsResource = Prefix + "introductions.tsv"; private const string DtoNamespace = "dto."; private const string PrefixMethodSuffix = "Prefix"; private static readonly object Gate = new object(); private static readonly Dictionary Cache = new Dictionary(); private static IReadOnlyList? _baseSymbols; private static IReadOnlyDictionary? _introductions; private static IReadOnlyDictionary? _compatVectorResults; private static IReadOnlyDictionary? _compatMethods; public static IReadOnlyList Layers { get; } = DiscoverLayers(); public static IReadOnlyDictionary CompatVectorResults { get { lock (Gate) { if (_compatVectorResults == null) { _compatVectorResults = BuildCompatVectorResults(); } return _compatVectorResults; } } } public static IReadOnlyDictionary CompatMethods { get { lock (Gate) { if (_compatMethods == null) { _compatMethods = BuildCompatMethods(); } return _compatMethods; } } } public static LayerSchema Create(int layer) { lock (Gate) { if (Cache.TryGetValue(layer, out LayerSchema? cached)) { return cached; } LayerSchema schema = LayerSchemaReader.Build(layer, Reconstruct(layer)); Cache.Add(layer, schema); return schema; } } private static IReadOnlyDictionary BuildCompatVectorResults() { IReadOnlyDictionary published = Create(BaseLayer).VectorResults; var values = new Dictionary(); foreach (LayerSchemaSymbol symbol in CompatSymbols()) { if (!symbol.IsMethod || symbol.VectorElementType == null || published.ContainsKey(symbol.Id)) { continue; } if (values.TryGetValue(symbol.Id, out string? existing) && !string.Equals(existing, symbol.VectorElementType, StringComparison.Ordinal)) { throw new InvalidOperationException( "Conflicting compatibility vector result: " + symbol.Name + "."); } values[symbol.Id] = symbol.VectorElementType; } return new ReadOnlyDictionary(values); } private static IReadOnlyDictionary BuildCompatMethods() { var values = new Dictionary(); foreach (LayerSchemaSymbol symbol in CompatSymbols()) { if (!symbol.IsMethod) { continue; } LayerConstructor method = symbol.ToConstructor(); if (values.TryGetValue(symbol.Id, out LayerConstructor? existing) && !existing.HasSameShape(method)) { throw new InvalidOperationException( "Conflicting compatibility method: " + symbol.Name + "."); } values[symbol.Id] = method; } return new ReadOnlyDictionary(values); } private static IEnumerable CompatSymbols() { foreach (string name in typeof(LayerSchemaCatalog).GetTypeInfo() .Assembly.GetManifestResourceNames()) { if (!name.StartsWith(CompatPrefix, StringComparison.Ordinal) || !name.EndsWith(".tl", StringComparison.Ordinal)) { continue; } foreach (LayerSchemaSymbol symbol in LayerSchemaReader.Read(ReadResource(name))) { yield return symbol; } } } private static IEnumerable Reconstruct(int layer) { IReadOnlyList baseSymbols = BaseSymbols(); if (layer == BaseLayer) { return Order(baseSymbols); } if (!Layers.Contains(layer)) { throw new ArgumentOutOfRangeException(nameof(layer), layer, "No schema is published for this layer."); } IReadOnlyDictionary introductions = Introductions(); var symbols = new Dictionary(StringComparer.Ordinal); foreach (LayerSchemaSymbol symbol in baseSymbols) { string key = Key(symbol); if (introductions.TryGetValue(key, out int introduced) && introduced > layer) { continue; } symbols[key] = symbol; } foreach (LayerSchemaSymbol symbol in LayerSchemaReader.Read( ReadResource(Prefix + "layer" + layer.ToString( CultureInfo.InvariantCulture) + ".tl"))) { symbols[Key(symbol)] = symbol; } return Order(symbols.Values); } private static IReadOnlyList BaseSymbols() { if (_baseSymbols == null) { _baseSymbols = LayerSchemaReader.Read(ReadResource(BaseResource)) .Where(IsPublished) .ToArray(); } return _baseSymbols; } private static bool IsPublished(LayerSchemaSymbol symbol) { if (symbol.Name.StartsWith(DtoNamespace, StringComparison.Ordinal)) { return false; } return !symbol.IsMethod || !symbol.Name.EndsWith(PrefixMethodSuffix, StringComparison.Ordinal); } private static IReadOnlyDictionary Introductions() { if (_introductions == null) { var values = new Dictionary(StringComparer.Ordinal); using (var reader = new StringReader(ReadResource(IntroductionsResource))) { reader.ReadLine(); string? line = reader.ReadLine(); while (line != null) { if (line.Length > 0) { string[] parts = line.Split('\t'); values[Key(parts[0] == "method", parts[1])] = int.Parse(parts[2], CultureInfo.InvariantCulture); } line = reader.ReadLine(); } } _introductions = values; } return _introductions; } private static IEnumerable Order( IEnumerable symbols) { return symbols .OrderBy(x => x.Name, StringComparer.Ordinal) .ThenBy(x => x.IsMethod); } private static string Key(LayerSchemaSymbol symbol) { return Key(symbol.IsMethod, symbol.Name); } private static string Key(bool isMethod, string name) { return isMethod ? "m:" + name : "c:" + name; } private static IReadOnlyList DiscoverLayers() { var layers = new List { BaseLayer }; foreach (string name in typeof(LayerSchemaCatalog).GetTypeInfo() .Assembly.GetManifestResourceNames()) { if (!name.StartsWith(Prefix, StringComparison.Ordinal) || !name.EndsWith(".tl", StringComparison.Ordinal)) { continue; } string stem = name.Substring(Prefix.Length, name.Length - Prefix.Length - 3); if (stem.StartsWith("layer", StringComparison.Ordinal) && int.TryParse(stem.Substring(5), NumberStyles.None, CultureInfo.InvariantCulture, out int layer) && !layers.Contains(layer)) { layers.Add(layer); } } layers.Sort(); return layers.AsReadOnly(); } private static string ReadResource(string name) { Stream stream = typeof(LayerSchemaCatalog).GetTypeInfo() .Assembly.GetManifestResourceStream(name) ?? throw new InvalidOperationException( "Missing schema resource: " + name); using (stream) using (var reader = new StreamReader(stream)) { return reader.ReadToEnd(); } } }