///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // S H A R P L I B // /// // (c) 2003..2025 using System; using System.IO; using System.Xml; using System.Collections; using System.Collections.Generic; using System.Reflection; using System.Linq; using System.Collections.Immutable; using System.Collections.Concurrent; using System.Collections.Immutable; namespace ser; public record CodeGenConfig : imm.Recorded { // Whitelists (if needed, otherwise rely on attributes/defaults) public ImmutableDictionary> WLProps { get; init; } = ImmutableDictionary>.Empty; public ImmutableDictionary> WLFields { get; init; } = ImmutableDictionary>.Empty; // Default member types to process public ser.Types TypesDefault { get; init; } = ser.Types.Fields | ser.Types.Props; // How to handle backing fields (might be less relevant for code gen) public BackingFieldNaming Naming { get; init; } = BackingFieldNaming.Regular; public static CodeGenConfig Default { get; } = new CodeGenConfig(); } public record GenMemberMeta( MemberInfo Info, Type Type, string Name, // Name for code generation (usually original) bool IsPrimitive, bool IsCollection, Type? CollectionElementType, bool HasDo, bool HasDont ); public record TypeStructureInfo( Type Type, List Members, bool IsValueType, bool IsCollection ); public class TypeStructureAnalyzer { private readonly ConcurrentDictionary _cache = new(); private readonly CodeGenConfig _cfg; public TypeStructureAnalyzer(CodeGenConfig cfg) => _cfg = cfg; public TypeStructureInfo Get(Type type) => _cache.GetOrAdd(type, BuildTypeInfo); private TypeStructureInfo BuildTypeInfo(Type type) { var members = new List(); var typesTodo = type.GetCustomAttribute(true)?.Types ?? _cfg.TypesDefault; bool doFields = typesTodo.HasFlag(ser.Types.Fields); bool doProps = typesTodo.HasFlag(ser.Types.Props); // Track processed names to avoid duplicates (e.g., field + prop) var processedNames = new HashSet(); // Process Properties First (often preferred interface) if (doProps) { foreach (var pi in refl.GetAllProperties(type)) { if (ProcessMember(pi, false, false, new HashSet(), false, members)) { processedNames.Add(pi.Name); } } } // Process Fields, avoiding those already covered by properties if (doFields) { foreach (var fi in refl.GetAllFields(type)) { var (isBacking, propName) = IsBackingField(fi); string nameToTest = isBacking ? propName : fi.Name; if (!processedNames.Contains(nameToTest)) { if(ProcessMember(fi, false, false, new HashSet(), false, members)) { processedNames.Add(nameToTest); } } } } return new TypeStructureInfo( type, members, type.IsValueType, typeof(IEnumerable).IsAssignableFrom(type) && type != typeof(string) ); } private bool ProcessMember(MemberInfo mi, bool filter, bool doImpls, HashSet whitelist, bool isImm, List members) { var (hasDo, hasDont, propName) = GetMemberAttributes(mi, out var actualMiForAtts); if (hasDont) return false; if (mi.GetCustomAttribute(true) != null) return false; if (mi.Name.Contains("k__BackingField") && !propName.Any()) return false; // Skip if backing but no prop found string name = string.IsNullOrEmpty(propName) ? mi.Name : propName; // Add filtering logic if needed (based on whitelist, etc.) var type = (mi is FieldInfo fi) ? fi.FieldType : ((PropertyInfo)mi).PropertyType; bool isCollection = typeof(IEnumerable).IsAssignableFrom(type) && type != typeof(string); Type? elementType = isCollection ? GetElementType(type) : null; bool isPrimitive = Type.GetTypeCode(type) != TypeCode.Object && !isCollection; members.Add(new GenMemberMeta( mi, type, name, isPrimitive, isCollection, elementType, hasDo, hasDont )); return true; } private (bool, string) IsBackingField(FieldInfo fi) { if (fi.Name.StartsWith("<") && fi.Name.EndsWith("BackingField")) { var gtIndex = fi.Name.IndexOf('>'); if (gtIndex > 1) { return (true, fi.Name.Substring(1, gtIndex - 1)); } } return (false, ""); } private (bool hasDo, bool hasDont, string propName) GetMemberAttributes(MemberInfo mi, out MemberInfo actualMi) { actualMi = mi; string propName = ""; if (mi is FieldInfo fi && IsBackingField(fi).Item1) { propName = IsBackingField(fi).Item2; var propInfo = mi.DeclaringType?.GetProperty(propName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (propInfo != null) actualMi = propInfo; } else if (mi is PropertyInfo) { propName = mi.Name; } return ( actualMi.GetCustomAttribute() != null, actualMi.GetCustomAttribute() != null, propName ); } private Type GetElementType(Type collectionType) { if (collectionType.IsArray) return collectionType.GetElementType()!; if (collectionType.IsGenericType) return collectionType.GetGenericArguments().Last(); // Usually last (e.g., List, Dict) return typeof(object); // Fallback } // Add GetFilters and FilterField if needed, or simplify as above }