using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Reflection; using System.Text; namespace srl; // --- INTERFACES --- public interface Driver { // Structural void BeginScope( string name, Type type, int id ); void EndScope(); void BeginCollection( string name, int count ); void EndCollection(); // Data void WriteAttr( string name, string value ); // Primitives & Compact Strings void WriteRef( string name, int id ); // DAG/Cycle Reference // Metadata Hook void OnProp( MemberInfo member, string name ); } public interface IInput { string? Value { get; } bool IsLeaf { get; } IInput? GetAttr( string name ); IInput? GetChild( string name ); } // --- THE MODEL (Introspection) --- public class TypePlan { public string Name; public bool IsCollection; public bool IsHybrid; // True if Type has a StringParser registered public StringParser Parser; // The "Compact" converter public List Props = new(); } public class PropPlan { public string Name; public string MemberName; public Type Type; public bool IsSmart; // Primitive/Enum/String public MemberInfo Info; // Fast Accessors public Func Getter; public Action Setter; } public static class Model { private static Dictionary _cache = new(); public static TypePlan Get( Type t ) { if( _cache.TryGetValue( t, out var plan ) ) return plan; plan = new TypePlan { Name = t.Name }; // 1. Check for Custom Parsers (Hybrid Mode) if( Parsers.TryGet( t, out var parser ) ) { plan.IsHybrid = true; plan.Parser = parser; } // 2. Check for Collections if( t != typeof( string ) && typeof( IEnumerable ).IsAssignableFrom( t ) ) { plan.IsCollection = true; _cache[t] = plan; return plan; } // 3. Scan Properties foreach( var p in t.GetProperties( BindingFlags.Public | BindingFlags.Instance ) ) { if( Attribute.IsDefined( p, typeof( IgnoreAttribute ) ) ) continue; var prop = new PropPlan { Name = p.Name, // Default to PascalCase, Drivers can lower if needed MemberName = p.Name, Type = p.PropertyType, Info = p, Getter = ( o ) => p.GetValue( o ), Setter = ( o, v ) => p.SetValue( o, v ) }; // Override name var nameAttr = p.GetCustomAttribute(); if( nameAttr != null ) prop.Name = nameAttr.Name; // Is "Smart" (Atomic)? prop.IsSmart = prop.Type.IsPrimitive || prop.Type.IsEnum || prop.Type == typeof( string ) || prop.Type == typeof( Guid ); plan.Props.Add( prop ); } _cache[t] = plan; return plan; } } // --- ATTRIBUTES --- public class IgnoreAttribute : Attribute { } public class NameAttribute : Attribute { public string Name; public NameAttribute( string n ) => Name = n; } // --- UTILITIES --- public struct StringParser { public Func To; public Func From; } public static class Parsers { private static Dictionary _registry = new(); public static void Register( Func to, Func from ) { _registry[typeof( T )] = new StringParser { To = o => to( (T)o ), From = s => from( s ) }; } public static bool TryGet( Type t, out StringParser p ) { if( _registry.TryGetValue( t, out p ) ) return true; // Auto-Discovery could go here (Static Parse methods) return false; } } public static class Binder { // Handles "Cool.Rare" dot notation public static void Apply( object root, string path, string value ) { var current = root; var parts = path.Split( '.' ); for( int i = 0; i < parts.Length; i++ ) { var part = parts[i]; var isLast = i == parts.Length - 1; var plan = Model.Get( current.GetType() ); // Case-Insensitive Match var prop = plan.Props.Find( p => p.Name.Equals( part, StringComparison.OrdinalIgnoreCase ) ); if( prop == null ) return; if( isLast ) { var val = ParseUtils.Convert( value, prop.Type ); prop.Setter( current, val ); } else { var next = prop.Getter( current ); if( next == null ) { next = Activator.CreateInstance( prop.Type ); prop.Setter( current, next ); } current = next; } } } } public static class ParseUtils { public static object Convert( string raw, Type t ) { if( t == typeof( string ) ) return raw; if( t == typeof( int ) ) return int.Parse( raw ); if( t == typeof( float ) ) return float.Parse( raw.Replace( "f", "" ) ); if( t == typeof( bool ) ) return bool.Parse( raw ); if( t.IsEnum ) return Enum.Parse( t, raw ); // Fallback to TypeConverter var cv = TypeDescriptor.GetConverter( t ); if( cv != null && cv.CanConvertFrom( typeof( string ) ) ) return cv.ConvertFrom( raw ); return null; } public static void CopyFields( object src, object dst ) { foreach( var p in src.GetType().GetProperties() ) if( p.CanRead && p.CanWrite ) p.SetValue( dst, p.GetValue( src ) ); } } // --- THE ENGINE (Walker & Loader) --- public static class Walker { public class Context { private Dictionary _seen = new( ReferenceEqualityComparer.Instance ); private int _nextId = 1; public (int, bool) GetId( object o ) { if( _seen.TryGetValue( o, out var id ) ) return (id, false); _seen[o] = _nextId; return (_nextId++, true); } } public static void Serialize( object root, Driver d ) { if( root == null ) return; SerializeRecursive( root, d, new Context(), "root", null ); } private static void SerializeRecursive( object obj, Driver d, Context ctx, string name, MemberInfo? member ) { if( obj == null ) return; Type type = obj.GetType(); var plan = Model.Get( type ); // STRATEGY 1: COMPACT (Hybrid Parser) if( plan.IsHybrid ) { if( member != null ) d.OnProp( member, name ); d.WriteAttr( name, plan.Parser.To( obj ) ); return; } // STRATEGY 2: DAG CHECK bool isRef = !type.IsValueType && type != typeof( string ); if( isRef ) { var (id, isNew) = ctx.GetId( obj ); if( !isNew ) { d.WriteRef( name, id ); return; } if( member != null ) d.OnProp( member, name ); d.BeginScope( name, type, id ); } else { if( member != null ) d.OnProp( member, name ); d.BeginScope( name, type, 0 ); } // STRATEGY 3: COLLECTIONS if( plan.IsCollection ) { var list = (IEnumerable)obj; int count = 0; // Simple count (could optimize for ICollection) foreach( var _ in list ) count++; d.BeginCollection( name, count ); foreach( var item in list ) SerializeRecursive( item, d, ctx, "item", null ); d.EndCollection(); } else { // STRATEGY 4: STANDARD OBJECT foreach( var prop in plan.Props ) { var val = prop.Getter( obj ); if( prop.IsSmart ) { d.OnProp( prop.Info, prop.Name ); d.WriteAttr( prop.Name, val?.ToString() ?? "" ); } else { if( val != null ) SerializeRecursive( val, d, ctx, prop.Name, prop.Info ); } } } d.EndScope(); } } public static class Loader { public static void Load( object target, IInput input ) { if( target == null || input == null ) return; var plan = Model.Get( target.GetType() ); // 1. HYBRID PARSE (Compact String) // If we have a parser AND input is just a value "1,1" if( plan.IsHybrid && input.IsLeaf && !string.IsNullOrWhiteSpace( input.Value ) ) { try { var newObj = plan.Parser.From( input.Value ); ParseUtils.CopyFields( newObj, target ); return; } catch { } } // 2. STRUCTURAL MAP foreach( var prop in plan.Props ) { // Look for Attribute OR Child Element var sub = input.GetAttr( prop.Name ) ?? input.GetChild( prop.Name ); // Look for Dot Notation (e.g. "Pos.X") in attributes // (Note: This simple loop doesn't scan ALL attrs for dots, // it relies on the caller or specific recursive logic. // For full dot support on root, we need to iterate input attributes if possible. // But 'Binder' below handles it if we pass the specific attr key). if( sub != null ) { if( prop.IsSmart ) { if( sub.Value != null ) prop.Setter( target, ParseUtils.Convert( sub.Value, prop.Type ) ); } else { var child = prop.Getter( target ); if( child == null ) { child = Activator.CreateInstance( prop.Type ); prop.Setter( target, child ); } Load( child, sub ); } } } } // Helper to scan all attributes on an element for "Cool.Rare" patterns public static void LoadDotNotations( object target, IEnumerable<(string k, string v)> attrs ) { foreach( var (k, v) in attrs ) { if( k.Contains( '.' ) ) Binder.Apply( target, k, v ); } } }