using System; using System.IO; using System.Xml; using System.Runtime.Serialization; using System.Collections; using System.Collections.Generic; using System.Reflection; using System.Linq; using System.Collections.Immutable; using System.Net.Sockets; using System.Collections.Concurrent; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Linq.Expressions; namespace ser; #region Attributes & Enums (Mostly unchanged, ensure these exist) public interface I_Serialize { void OnSerialize() { } object OnDeserialize( object enclosing ) => this; } [Flags] public enum Types { Fields = 0b_0001, Props = 0b_0010, Implied = 0b_0100, Explicit = 0b_1000, None = 0b_0000, Default = Fields, All = Fields | Props, } public class Ser : Attribute { public Types Types { get; set; } = Types.Default; } public class Do : Attribute { } public class Dont : Attribute { } public class ChildAttribute : Attribute { public string[] Values { get; private set; } public ChildAttribute( params string[] values ) { this.Values = values; } } public class ChildFieldsAttribute : ChildAttribute { public ChildFieldsAttribute( params string[] values ) : base( values ) { } } public class ChildPropsAttribute : ChildAttribute { public ChildPropsAttribute( params string[] values ) : base( values ) { } } public interface ITypeHandler { bool CanHandle( TypeInfo typeInfo, XmlElement? elem = null ); // Elem needed for Deser void WriteXml( XmlSer xml, XmlWriter writer, object? obj, string name, Type memberType, bool forceType ); object? ReadXml( XmlSer xml, XmlElement elem, Type expectedType, object? existing ); } // --- Enums & Records (Slightly adjusted/renamed) --- public enum Datastructure { Tree, Graph } public enum BackingFieldNaming { Short, Regular } public enum POD { Attributes, Elements } public record struct TypeProxy( Func fnSer, Func fnDes ); public record XmlCfg : imm.Recorded { public bool Verbose { get; init; } = false; public Datastructure Structure { get; init; } = Datastructure.Tree; public int Version { get; init; } = 2; public ImmutableDictionary Proxies { get; init; } = ImmutableDictionary.Empty; public BackingFieldNaming Naming { get; init; } = BackingFieldNaming.Short; public POD POD { get; init; } = POD.Attributes; public ser.Types TypesDefault { get; init; } = ser.Types.Fields; public static XmlCfg Default { get; } = new XmlCfg(); } #endregion #region Reflection & Metadata Cache public record DependentMember( string Name, //Prop or field name TypeInfo Enclosing, MemberInfo EnclosingMember ); public enum MemberMetaType { Invalid, Simple, Composite, } public record MemberMeta( //Base type. Type Type, MemberInfo Info, string XmlName, Func GetValue, Action SetValue, bool IsPodAttribute, bool HasDo, bool HasDont ); public record TypeInfo( Type Type, List Members, bool IsISerializable, bool IsImm, bool IsProxy, TypeProxy? ProxyDef ); public class TypeMetaCache { private readonly ConcurrentDictionary _cache = new(); private readonly XmlCfg _cfg; public TypeMetaCache( XmlCfg cfg ) => _cfg = cfg; public TypeInfo Get( Type type ) { // Expanded in anticpation of more complex ways to specify saves/loads if( _cache.TryGetValue( type, out var ti ) ) return ti; var children = new HashSet(); var tiNew = BuildTypeInfo( type, children ); _cache.AddOrUpdate( type, tiNew, ( t, ti ) => tiNew ); return tiNew; } // Helper to create accessors (using standard reflection - can be optimized) private static Func CreateGetter( MemberInfo mi ) { if( mi is FieldInfo fi ) return fi.GetValue; if( mi is PropertyInfo pi && pi.CanRead ) return pi.GetValue; return _ => null; } private static Action CreateSetter( MemberInfo mi ) { if( mi is FieldInfo fi ) return fi.SetValue; if( mi is PropertyInfo pi && pi.CanWrite ) return pi.SetValue; return ( _, _ ) => { }; } // Helper to create accessors (using standard reflection - can be optimized) private static Func CreateGetter( MemberInfo mi, Func getter ) { if( mi is FieldInfo fi ) { var innerGet = fi.GetValue; return obj => innerGet( getter( obj ) ); } if( mi is PropertyInfo pi && pi.CanRead ) { Func innerGet = pi.GetValue; return obj => innerGet( getter( obj ) ); } // return pi.GetValue; return _ => null; } private static Action CreateSetter( MemberInfo mi, Func getter ) { if( mi is FieldInfo fi ) { Action innerSet = fi.SetValue; return ( obj, value ) => innerSet( getter( obj ), value ); } if( mi is PropertyInfo pi && pi.CanWrite ) { Action innerSet = pi.SetValue; //return innerSet; //var innerSetType = innerSet.GetType(); //var isArgs = innerSetType.Metho return ( obj, value ) => { var leaf = getter( obj ); innerSet( leaf, value ); }; } return ( _, _ ) => { }; } public void AddType( Type type, params string[] children ) { var hashChildren = new HashSet( children ); BuildTypeInfo( type, hashChildren ); } private TypeInfo BuildTypeInfo( Type type, HashSet children ) { if( _cfg.Verbose ) log.info( $"Building TypeInfo for {type.Name}" ); var members = new List(); bool doImpls, doFields, doProps; GetFilters( _cfg.TypesDefault, type, out doImpls, out doFields, out doProps ); var isImm = typeof( imm.Obj ).IsAssignableFrom( type ); var typesAtt = type.GetCustomAttribute( true ); var serTypes = typesAtt?.Types ?? ser.Types.None; if( doFields || doImpls ) { foreach( var fi in refl.GetAllFields( type ) ) { ProcessMember( fi, serTypes.HasFlag( ser.Types.Fields ), children, doImpls, isImm, members ); } } if( doProps || doImpls ) { foreach( var pi in refl.GetAllProperties( type ) ) { ProcessMember( pi, serTypes.HasFlag( ser.Types.Props ), children, doImpls, isImm, members ); } } var (isProxy, proxyDef) = FindProxy( type ); return new TypeInfo( type, members, typeof( ISerializable ).IsAssignableFrom( type ) && !typeof( Delegate ).IsAssignableFrom( type ), // Exclude Delegates isImm, isProxy, proxyDef ); } private (bool, TypeProxy?) FindProxy( Type type ) { var tryType = type; while( tryType != null && tryType != typeof( object ) ) { if( _cfg.Proxies.TryGetValue( tryType, out var proxy ) ) { return (true, proxy); } tryType = tryType.BaseType; } return (false, null); } private void ProcessMember( MemberInfo mi, bool doMember, HashSet childrenOverridden, bool doImpls, bool isImm, List members ) { List children = new( childrenOverridden ); var (hasDo, hasDont, hasImpl, propName) = GetMemberAttributes( mi, out var actualMiForAtts, children ); if( hasDont ) return; // TODO MH Change this to a configurable query(s) if( isImm && ( mi.Name == "MetaStorage" || mi.Name == "Fn" ) ) return; if( mi.GetCustomAttribute( true ) != null ) return; if( !(doMember | hasImpl | hasDo) ) return; var miType = ( mi is FieldInfo fi ) ? fi.FieldType : ( (PropertyInfo)mi ).PropertyType; // CHANGED (moved up) string name = mi.Name; string finalName = name; if( !string.IsNullOrEmpty( propName ) ) { finalName = ( _cfg.Naming == BackingFieldNaming.Short ) ? propName : name; } finalName = refl.TypeToIdentifier( finalName ); // Ensure XML-safe name var overiddenName = false; var getter = CreateGetter( mi ); var setter = CreateSetter( mi ); var blankHashSet = new HashSet(); //Type realMemberType = miType; if( hasImpl && children.Any() ) { //List specialMembers = new(); foreach( var childName in children ) { var memberInfoArr = miType.GetMember( childName ); var miFinal = memberInfoArr?.FirstOrDefault(); if( miFinal == null ) continue; var dependentType = miFinal is FieldInfo fidd ? fidd.FieldType : ( miFinal as PropertyInfo ).PropertyType; bool isPod = Type.GetTypeCode( dependentType ) != TypeCode.Object; //ProcessMember( miFinal, blankHashSet, doImpls, isImm, specialMembers ); //First this one. We need the old getter for the setter. setter = CreateSetter( miFinal, getter ); //Now wrap the getter itself getter = CreateGetter( miFinal, getter ); var depName = $"{finalName}.{childName}"; var memberMeta = new MemberMeta( dependentType, miFinal, depName, getter, setter, isPod && _cfg.POD == POD.Attributes, hasDo, hasDont ); members.Add( memberMeta ); if( _cfg.Verbose ) { log.info( $"{depName} ({mi.Name}) -> {finalName} ({dependentType.Name}) PodAtt: {isPod && _cfg.POD == POD.Attributes}" ); } } return; /* foreach( var childName in children ) { var memberInfoArr = miType.GetMember( childName ); var miFinal = memberInfoArr?.FirstOrDefault(); if( miFinal != null ) { realMemberType = ( miFinal is FieldInfo fin ) ? fin.FieldType : ( miFinal as PropertyInfo ).PropertyType; getter = CreateGetter( miFinal, getter ); setter = CreateSetter( miFinal, setter ); } } //*/ } { // Simplified POD check bool isPod = Type.GetTypeCode( miType ) != TypeCode.Object && !typeof( IEnumerable ).IsAssignableFrom( miType ) || overiddenName; members.Add( new MemberMeta( miType, mi, finalName, getter, setter, isPod && _cfg.POD == POD.Attributes, hasDo, hasDont ) ); if( _cfg.Verbose ) { log.info( $"{mi.Name} ({miType.Name}) -> {finalName} ({miType.Name}) PodAtt: {isPod && _cfg.POD == POD.Attributes}" ); } } } private void ProcessDepedentMember( Type type, HashSet children, List members ) { } private (bool hasDo, bool hasDont, bool hasImpl, string propName) GetMemberAttributes( MemberInfo mi, out MemberInfo actualMi, List children ) { actualMi = mi; string propName = ""; bool isBacking = mi.Name.StartsWith( "<" ) && mi.Name.EndsWith( "BackingField" ); var typesAtt = mi.DeclaringType.GetCustomAttribute( true ); var serTypes = typesAtt?.Types ?? ser.Types.None; var doImpls = serTypes.HasFlag( ser.Types.Implied ); var attDo = actualMi.GetCustomAttribute() != null; var attDont = actualMi.GetCustomAttribute() != null; if( isBacking && mi is FieldInfo ) { var gtIndex = mi.Name.IndexOf( '>' ); propName = mi.Name.Substring( 1, gtIndex - 1 ); var propInfo = mi.DeclaringType?.GetProperty( propName ); if( propInfo != null ) actualMi = propInfo; } var attChildren = actualMi.GetCustomAttribute(); if( attChildren != null ) { children.AddRange( attChildren.Values ); } return ( attDo, attDont, doImpls, propName ); } // --- These helpers are copied/adapted from XmlFormatter2 --- private static void GetFilters( ser.Types typesDefault, Type type, out bool doImpls, out bool doFields, out bool doProps ) { var typesTodo = type.GetCustomAttribute( true )?.Types ?? typesDefault; doImpls = typesTodo.HasFlag( ser.Types.Implied ); doFields = typesTodo.HasFlag( ser.Types.Fields ); doProps = typesTodo.HasFlag( ser.Types.Props ); } } public class TypeResolver { private readonly ConcurrentDictionary _cache = new(); private readonly Assembly[] _assemblies; private static readonly FormatterConverter _conv = new(); public TypeResolver() { _assemblies = AppDomain.CurrentDomain.GetAssemblies(); } public Type Resolve( XmlElement elem, Type? expectedType ) { if( elem.HasAttribute( "_.t" ) ) { var typeName = elem.GetAttribute( "_.t" ); var resolved = FindType( typeName ); if( resolved != null ) return resolved; } return expectedType ?? typeof( object ); // Fallback needed } public Type? FindType( string typeName ) { return _cache.GetOrAdd( typeName, tn => { // Try direct lookup first (might work for fully qualified) var t = Type.GetType( tn ); if( t != null ) return t; // Then search assemblies foreach( Assembly a in _assemblies ) { t = a.GetType( tn ); if( t != null ) return t; } log.warn( $"Could not resolve type: {tn}" ); return null; } ); } public object ConvertSimple( string value, Type type ) { if( type.IsEnum ) return Enum.Parse( type, value ); try { return _conv.Convert( value, type ); } catch( Exception ex ) { object defaultVal = type.IsValueType ? Activator.CreateInstance( type )! : null!; log.warn( $"Conversion failed for '{value}' to {type.Name}: {ex.Message}. Returning default of {defaultVal}({defaultVal.GetType().Name})." ); return defaultVal; } } } #endregion #region XmlSer (Coordinator) public class XmlSer // : IFormatter { internal readonly XmlCfg _cfg; internal readonly TypeMetaCache _meta; internal readonly TypeResolver _resolver; private readonly List _handlers; // Per-operation state internal ObjectIDGenerator _idGen = new(); internal Dictionary _processed = new(); private string _streamSource = ""; public XmlSer( XmlCfg? cfg = null, TypeMetaCache metaCache = null ) { var isCustomConfig = cfg != null; _cfg = cfg ?? XmlCfg.Default; if( _cfg.Verbose ) { log.info( $"Config:" ); log.info( $" {log.var( _cfg.Verbose )}" ); log.info( $" {log.var( _cfg.Structure )}" ); log.info( $" {log.var( _cfg.Version )}" ); log.info( $" {log.var( _cfg.Naming )}" ); log.info( $" {log.var( _cfg.POD )}" ); log.info( $" {log.var( _cfg.TypesDefault )}" ); } _meta = metaCache ?? new TypeMetaCache( _cfg ); _resolver = new TypeResolver(); _handlers = new List { new ProxyHandler(), new ISerializableHandler(), new PrimitiveHandler(), new CollectionHandler(), new ObjectHandler() // Must be last }; if( _cfg.Verbose ) { log.info( $"Handlers in importance..." ); foreach( var h in _handlers ) { log.info( $" {h.GetType().Name}" ); } log.high( "XmlSer Initialized." ); } } internal ITypeHandler GetHandler( Type t, XmlElement? elem = null ) { var ti = _meta.Get( t ); return _handlers.First( h => h.CanHandle( ti, elem ) ); } // --- Context Helpers --- internal void WriteTypeAttr( XmlWriter writer, Type memberType, Type actualType ) { if( memberType != actualType ) { writer.WriteAttributeString( "_.t", actualType.FullName ); } } internal bool HandleGraphWrite( XmlWriter writer, object obj, out bool first ) { first = true; if( _cfg.Structure == Datastructure.Graph ) { long id = _idGen.GetId( obj, out first ); writer.WriteAttributeString( "ref", id.ToString() ); if( first ) _processed[id] = obj; } return first || _cfg.Structure == Datastructure.Tree; // Write if first or if Tree } internal long TrackIfGraph( object obj, XmlElement elem ) { long id = -1; bool first; if( _cfg.Structure == Datastructure.Graph ) { id = _idGen.GetId( obj, out first ); if( elem.HasAttribute( "ref" ) ) { id = long.Parse( elem.GetAttribute( "ref" ) ); } if( !_processed.ContainsKey( id ) ) { _processed[id] = obj; } } return id; } // --- Deserialization --- public T? Deserialize( Stream stream ) => (T?)Deserialize( stream, typeof( T ) ); public object? Deserialize( Stream stream, Type? type = null ) { _streamSource = stream.ToString() ?? "{null}"; // Basic source, improve as needed _processed.Clear(); _idGen = new ObjectIDGenerator(); using var reader = XmlReader.Create( stream, new XmlReaderSettings { IgnoreWhitespace = true } ); XmlDocument doc = new XmlDocument(); try { doc.Load( reader ); } catch( Exception ex ) { log.error( $"XML Load failed: {ex.Message}" ); return null; } if( doc.DocumentElement == null ) return null; return ReadNode( doc.DocumentElement, type ?? typeof( object ), null ); } public void DeserializeInto( Stream stream, T obj ) where T : class { _streamSource = stream.ToString() ?? "{null}"; _processed.Clear(); _idGen = new ObjectIDGenerator(); using var reader = XmlReader.Create( stream, new XmlReaderSettings { IgnoreWhitespace = true } ); XmlDocument doc = new XmlDocument(); try { doc.Load( reader ); } catch( Exception ex ) { log.error( $"XML Load failed: {ex.Message}" ); return; } if( doc.DocumentElement == null ) return; ReadNode( doc.DocumentElement, typeof( T ), obj ); } internal object? ReadNode( XmlElement elem, Type expectedType, object? existing ) { if( elem.HasAttribute( "v" ) && elem.GetAttribute( "v" ) == "null" ) return null; // 1. Handle refs (if Graph) if( _cfg.Structure == Datastructure.Graph && elem.HasAttribute( "ref" ) ) { long id = long.Parse( elem.GetAttribute( "ref" ) ); if( _processed.TryGetValue( id, out var obj ) ) return obj; } // 2. Determine Type & Select Handler var actualType = _resolver.Resolve( elem, expectedType ); var ti = _meta.Get( actualType ); var handler = _handlers.First( h => h.CanHandle( ti, elem ) ); // 3. Delegate return handler.ReadXml( this, elem, actualType, existing ); } // --- Serialization --- public void Serialize( Stream stream, object root ) { _processed.Clear(); _idGen = new ObjectIDGenerator(); var settings = new XmlWriterSettings { Indent = true, Encoding = System.Text.Encoding.UTF8, // Use UTF8 for better compatibility OmitXmlDeclaration = true // Often preferred for fragments/storage }; using var writer = XmlWriter.Create( stream, settings ); writer.WriteStartDocument(); WriteNode( writer, root, "root", root?.GetType() ?? typeof( object ), true ); // Force type on root writer.WriteEndDocument(); writer.Flush(); } internal void WriteNode( XmlWriter writer, object? obj, string name, Type memberType, bool forceType ) { if( _cfg.Verbose ) log.info( $"Writing {name} ({memberType}) force: {forceType}" ); if( obj == null ) { writer.WriteStartElement( name ); writer.WriteAttributeString( "v", "null" ); writer.WriteEndElement(); return; } var actualType = obj.GetType(); var ti = _meta.Get( actualType ); var handler = _handlers.First( h => h.CanHandle( ti ) ); try { handler.WriteXml( this, writer, obj, name, memberType, forceType || memberType != actualType ); } catch( Exception ex ) { log.exception( ex, $"{name}({memberType.Name}) forceType: {forceType}" ); } } } #endregion