using System; using System.IO; using System.Xml; using System.Runtime.Serialization; //using System.Web.Configuration; using System.Collections; using System.Collections.Generic; using System.Reflection; using System.Diagnostics; using System.Linq; using System.Collections.Immutable; using System.Net.Sockets; /* element and attribute names - Element names are case-sensitive - Element names must start with a letter or underscore - Element names cannot start with the letters xml(or XML, or Xml, etc) - Element names can contain letters, digits, hyphens, underscores, and periods - Element names cannot contain spaces * TODO * x) Add the ability for correctly named attributes to be able to fill in classes * x) */ namespace lib { 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 enum Datastructure { Invalid, // Breaks on circular datastructures since it will go on forever Tree, // Works for everything. Graph, } public record struct TypeProxy( Func ser, Func des ); //public record struct CollectionCreator( Func FnCreate ); //These 2 enums are for serialization public enum BackingFieldNaming { Invalid, Short, //Use the prop name Regular, } public enum POD { Invalid, Attributes, Elements, } public class XmlFormatter2Cfg : Config { public bool VerboseLogging = false; public Datastructure datastructure = Datastructure.Tree; public int Version = 2; public Dictionary> WLProps = new(); public Dictionary> WLFields = new(); public Dictionary TypeProxy = new(); //public Dictionary CollectionCreator = new(); //For Serialization public BackingFieldNaming Naming = BackingFieldNaming.Short; public POD POD = POD.Attributes; public Types TypesDefault = Types.Fields; } public class XmlFormatter2 : IFormatter { public StreamingContext Context { get; set; } private XmlFormatter2Cfg _cfg = new(); #region Unimplimented public ISurrogateSelector SurrogateSelector { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } public SerializationBinder Binder { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } #endregion public XmlFormatter2() { //Context = new StreamingContext( StreamingContextStates.All ); } public XmlFormatter2( XmlFormatter2Cfg cfg ) { //Context = new StreamingContext( StreamingContextStates.All ); _cfg = cfg; log.high( $"XML serialization is NOT fast" ); } #region Deserialize private static FormatterConverter s_conv = new(); private string fromStr = ""; void SetFromStr( Stream stream ) { fromStr = stream.ToString() ?? "{null}"; if( stream is FileStream fs ) { fromStr = fs.Name; } if( stream is NetworkStream ns ) { fromStr = ns?.Socket?.RemoteEndPoint?.ToString() ?? $"{ns}"; } } public object Deserialize( Stream stream ) { SetFromStr( stream ); var obj = DeserializeKnownType( stream, null ); return obj; } public T? Deserialize( Stream stream ) => (T)DeserializeKnownType( stream, typeof( T ) ); public object? DeserializeKnownType( Stream stream, Type? t ) { SetFromStr( stream ); XmlTextReader reader = new( stream ); object? obj = Deserialize( reader, t ); return obj; } private object? Deserialize( XmlReader reader, Type? t ) { m_alreadySerialized.Clear(); m_objectID = new ObjectIDGenerator(); reader.Read(); XmlDocument doc = new XmlDocument(); doc.Load( reader ); if( doc.DocumentElement == null ) return null; if( t == null ) return Deserialize( doc.DocumentElement ); return Deserialize( doc.DocumentElement, null, t, null ); } public void DeserializeInto( Stream stream, T obj ) { SetFromStr( stream ); XmlTextReader reader = new( stream ); reader.Read(); XmlDocument doc = new(); doc.Load( reader ); HydrateObject( doc.DocumentElement, null, obj ); } private object Deserialize( XmlElement elem ) { //lib.log.info( "object Deserialize( XmlElement elem ) {0} {1}", m_rndVal, m_alreadySerialized.Count ); string typename = elem.HasAttribute( "_.t" ) ? elem.GetAttribute( "_.t" ) : elem.Name; return Deserialize( elem, null, typename ); } private object Deserialize( XmlElement elem, MemberInfo mi, string typename ) { AppDomain currentDomain = AppDomain.CurrentDomain; Assembly[] assems = currentDomain.GetAssemblies(); Type type = null; // @@@@: This should go backwards, we tend to lookup our own stuff, then builtins. // Also, cache a typename into its assembly. foreach( Assembly a in assems ) { type = a.GetType( typename ); if( type != null ) break; } if( type == null ) { return null; } return Deserialize( elem, null, type, null ); } static private bool IsEnumerable( Type type ) => type.IsAssignableTo( typeof( IEnumerable ) ); private object Deserialize( XmlElement elem, MemberInfo? mi, Type type, object? existing /*, object enclosing = null*/ ) { var name = mi?.Name ?? "{NOT_FOUND}"; return Deserialize( elem, mi, type, name, existing ); } private object Deserialize( XmlElement elem, MemberInfo? mi, Type type, string name, object? existing /*, object enclosing = null*/ ) { try { TypeCode typeCode = Type.GetTypeCode( type ); if( _cfg.VerboseLogging ) log.info( $"{type.FriendlyName()}.{name} {existing} {mi?.Name}" ); if( typeCode != TypeCode.Object ) { return DeserializeConcrete( elem, mi, name, type ); } else { if( !type.IsArray ) { if( IsEnumerable( type ) ) { return DeserializeCollection( elem, mi, type ); } else { object obj = DeserializeObject( elem, mi, type, existing ); if( obj is I_Serialize iser ) { if( _cfg.VerboseLogging ) log.info( $"" ); obj = iser.OnDeserialize( null ); } return obj; } } else { return DeserializeArray( elem, mi, type ); } } } catch( Exception ex ) { log.warn( $"Caught exception fn {mi?.Name} type {type?.FriendlyName()} of {ex.Message}" ); } return existing; } private object GetDefault( Type t ) { var types = new Type[1]; types[0] = t; var fn = GetType().GetMethod( "GetDefaultGeneric" ).MakeGenericMethod( types ); return fn.Invoke( this, null ); } static public T GetDefaultGeneric() { return default( T ); } private object DeserializeConcrete( XmlElement elem, MemberInfo mi, string name, Type type ) { if( _cfg.VerboseLogging ) log.info( $"" ); string val = ""; if( elem.HasAttribute( "v" ) ) { val = elem.GetAttribute( "v" ); } else if( elem.HasAttribute( name ) ) { val = elem.GetAttribute( name ); } else { val = elem.InnerText; } if( !type.IsEnum ) { try { return s_conv.Convert( val, type ); } catch( Exception ) { return GetDefault( type ); } } else { return Enum.Parse( type, val ); } } private XmlElement getNamedChild( XmlNodeList list, string name ) { foreach( XmlNode node in list ) { if( node.Name == name ) { return (XmlElement)node; } } return null; } private Type FindType( string shortname ) { Assembly[] ass = AppDomain.CurrentDomain.GetAssemblies(); foreach( Assembly a in ass ) { Type t = a.GetType( shortname ); if( t != null ) { return t; } } return null; } private Type[] mm_consType = new Type[2]; private object[] mm_args = new object[2]; private object DeserializeObject( XmlElement elem, MemberInfo mi, Type type, object existing ) { Type finalType; var obj = GetObjectForDeser( elem, type, out finalType, existing ); return HydrateObject( elem, mi, finalType, obj ); } public object HydrateObject( XmlElement elem, MemberInfo mi, T obj ) { return HydrateObject( elem, mi, typeof( T ), obj ); } private object HydrateObject( XmlElement elem, MemberInfo mi, Type finalType, object obj ) { if( _cfg.VerboseLogging ) log.info( $"" ); if( obj is IList ) { var list = obj as IList; return DeserializeList( elem, mi, finalType, list ); } Type typeISerializable = typeof( ISerializable ); if( obj is ISerializable ) // type.IsSubclassOf( typeISerializable ) ) { XmlNodeList allChildren = elem.ChildNodes; //ISerializable ser = obj as ISerializable; var serInfo = new SerializationInfo( finalType, new FormatterConverter() ); //var serInfoForTypes = new SerializationInfo( type, new FormatterConverter() ); //ser.GetObjectData( serInfoForTypes, Context ); foreach( var objNode in allChildren ) { var node = objNode as XmlElement; string name = node.Name; string childType = node.GetAttribute( "_.t" ); name = refl.TypeToIdentifier( name ); XmlElement childElem = getNamedChild( allChildren, name ); var des = Deserialize( childElem, mi, childType ); serInfo.AddValue( name, des, des.GetType() ); } //ConstructorInfo[] allCons = obj.GetType().GetConstructors( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic ); //var serMem = FormatterServices.GetSerializableMembers( finalType ); //object objUn = FormatterServices.GetSafeUninitializedObject( finalType ); var objUnOnDeser = obj as IDeserializationCallback; mm_consType[0] = typeof( SerializationInfo ); mm_consType[1] = typeof( StreamingContext ); ConstructorInfo serCons = finalType.GetConstructor( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, mm_consType, null ); var context = new StreamingContext( StreamingContextStates.File, obj ); mm_args[0] = serInfo; mm_args[1] = context; serCons.Invoke( obj, mm_args ); if( objUnOnDeser != null ) { objUnOnDeser.OnDeserialization( objUnOnDeser ); } /* ser.GetObjectData( serInfo, Context ); //var serEnum = ; foreach( var serMember in serInfo ) { string name = serMember.Name; name = refl.TypeToIdentifier( name ); XmlElement childElem = getNamedChild( allChildren, name ); var des = Deserialize( childElem, name ); } */ } else { HydrateObjectOfNarrowType( elem, mi, finalType, obj ); } return obj; } private object HydrateObjectOfNarrowType( XmlElement elem, MemberInfo mi, Type narrowType, object obj ) { if( _cfg.VerboseLogging ) log.info( $"" ); var isImm = typeof(imm.Obj).IsAssignableFrom( narrowType ); XmlNodeList allChildren = elem.ChildNodes; bool filterFields, filterProps, doImpls, doFields, doProps; HashSet whitelistFields, whitelistProps; GetFilters( _cfg.TypesDefault, mi, narrowType, out filterFields, out filterProps, out doImpls, out doFields, out doProps, out whitelistFields, out whitelistProps ); if( doFields || doImpls ) { var fields = refl.GetAllFields( narrowType ); foreach( FieldInfo childFi in fields ) { string name = childFi.Name; var dontAtt = childFi.GetCustomAttributes(); var doAtt = childFi.GetCustomAttributes(); string propName = ""; if( name.StartsWith( "<" ) && name.EndsWith( "BackingField" ) ) { var gtIndex = name.IndexOf( '>' ); propName = name.Substring( 1, gtIndex - 1 ); var propInfo = narrowType.GetProperty( propName ); dontAtt = propInfo.GetCustomAttributes(); doAtt = propInfo.GetCustomAttributes(); } if( dontAtt.Any() ) { continue; } //if( name.EndsWith( ) ) //This is to convert c# names that would be bad as XML tags name = refl.TypeToIdentifier( name ); // @@@ TODO This doesnt yet handle propNames! if( !doAtt.Any() && FilterField( filterFields, doImpls, whitelistFields, childFi as MemberInfo, name ) ) continue; var useFieldName = true; //Get the value from an attribute named after the field string attValue = elem.GetAttribute( name ); //Now, if we dont have one, grab one from the property name if( !string.IsNullOrWhiteSpace( propName ) && string.IsNullOrWhiteSpace( attValue ) ) { useFieldName = false; attValue = elem.GetAttribute( propName ); } if( !string.IsNullOrWhiteSpace( attValue ) ) { object existingObj = childFi.GetValue( obj ); object childObj = DeserializeConcrete( elem, childFi, useFieldName ? name : propName, childFi.FieldType ); childFi.SetValue( obj, childObj ); } else { XmlElement childElem = getNamedChild( allChildren, name ); if( childElem == null && !string.IsNullOrWhiteSpace( propName ) ) childElem = getNamedChild( allChildren, propName ); if( childElem != null ) { object existingObj = childFi.GetValue( obj ); object childObj = Deserialize( childElem, childFi, childFi.FieldType, existingObj ); childFi.SetValue( obj, childObj ); } } } } if( doProps || doImpls ) { var props = refl.GetAllProperties( narrowType ); foreach( var childPi in props ) { string name = childPi.Name; var dontAtt = childPi.GetCustomAttributes(); if( dontAtt.Any() ) { continue; } var doAtt = childPi.GetCustomAttributes(); name = refl.TypeToIdentifier( name ); if( !doAtt.Any() && FilterField( filterProps, doImpls, whitelistProps, childPi as PropertyInfo, name ) ) continue; XmlElement childElem = getNamedChild( allChildren, name ); var setMethod = childPi.GetSetMethod(); if( childElem != null ) { object existingObj = childPi.GetValue( obj ); object childObj = Deserialize( childElem, childPi, childPi.PropertyType, existingObj ); if( setMethod != null ) { //Object o = Activator.CreateInstance( setMethod.ReflectedType ); setMethod.Invoke( obj, new object[] { childObj } ); //setMethod.CreateDelegate() } else { childPi.SetValue( obj, childObj ); } } else { object existingObj = childPi.GetValue( obj ); object childObj = DeserializeConcrete( elem, childPi, name, childPi.PropertyType ); if( setMethod != null ) { //Object o = Activator.CreateInstance( setMethod.ReflectedType ); setMethod.Invoke( obj, new object[] { childObj } ); //setMethod.CreateDelegate() } else { childPi.SetValue( obj, childObj ); } } } } if(!isImm) { return obj; } else { var imm = obj as imm.Obj; var newObj = imm.Record( $"From XML {fromStr}:{elem.ParentNode?.Name}{elem.Name}" ); return newObj; } } private static bool FilterField( bool filterFields, bool doImpls, HashSet whitelistFields, MemberInfo mi, string name ) { if( doImpls ) { if( mi.GetCustomAttribute( true ) == null ) return true; } if( filterFields && !whitelistFields.Contains( name ) ) return true; return false; } private object GetObjectForDeser( XmlElement elem, Type type, out Type finalType, object obj ) { finalType = type; if( elem.HasAttribute( "_.t" ) ) { var typename = elem.GetAttribute( "_.t" ); finalType = FindType( typename ); if( finalType == null ) finalType = type; } string refString = elem.GetAttribute( "ref" ); int refInt = refString.Length > 0 ? Convert.ToInt32( refString ) : -1; if( _cfg.VerboseLogging ) log.info( $"{finalType?.FriendlyName()}({type?.FriendlyName()}) refInt {refInt} exitingObj = {obj?.ToString()}" ); obj = createObject( elem, finalType, refInt, obj ); return obj; } private object DeserializeList( XmlElement elem, MemberInfo mi, Type type, IList list ) { if( _cfg.VerboseLogging ) log.info( $"" ); XmlNodeList arrNodeList = elem.ChildNodes; Type t = list.GetType(); Type[] genT = t.GetGenericArguments(); Debug.Assert( genT.Length == 1 ); for( int i = 0; i < arrNodeList.Count; ++i ) { if( arrNodeList.Item( i ) is XmlElement ) { XmlElement arrElem = (XmlElement)arrNodeList.Item( i ); list.Add( Deserialize( arrElem, mi, genT[0], null ) ); } } return list; } private object DeserializeCollection( XmlElement elem, MemberInfo mi, Type type ) { Type typeElem = typeof( object ); if( type.GenericTypeArguments.Length == 1 ) { typeElem = type.GenericTypeArguments[0]; } else if( type.GenericTypeArguments.Length == 2 ) { typeElem = typeof( KeyValuePair<,> ).MakeGenericType( type.GenericTypeArguments ); } if( _cfg.VerboseLogging ) log.info( $"DserCol {type.GetType().FriendlyName()} {typeElem.Name} into reflT {mi.ReflectedType.FriendlyName()} declT {mi.DeclaringType.FriendlyName()} {mi.Name}" ); string refString = elem.GetAttribute( "ref" ); int refInt = refString.Length > 0 ? Convert.ToInt32( refString ) : -1; XmlNodeList arrNodeList = elem.ChildNodes; int length = arrNodeList.Count; Array arr = createArray( typeElem, refInt, length ); for( int i = 0; i < arr.Length; ++i ) { if( arrNodeList.Item( i ) is XmlElement ) { XmlElement arrElem = (XmlElement)arrNodeList.Item( i ); var finalType = typeElem; if( arrElem.HasAttribute( "_.t" ) ) { var typename = arrElem.GetAttribute( "_.t" ); finalType = FindType( typename ); if( finalType == null ) finalType = typeElem; } var arrItem = Deserialize( arrElem, mi, finalType, null ); arr.SetValue( arrItem, i ); } } var listType = ( typeof( List<> ) ).MakeGenericType( typeElem ); IList list = Activator.CreateInstance( listType ) as IList; foreach( var a in arr ) { list.Add( a ); } MethodInfo? toMeth = null; var typeGen = Type.MakeGenericSignatureType( type ); if( _cfg.VerboseLogging ) log.info( $"TypeGen: {typeGen.FriendlyName()}" ); if( type == typeof( ImmutableArray<> ).MakeGenericType( typeElem ) ) { var genMeth = GetType().GetMethod( "MakeImmutableArray", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic ); toMeth = genMeth.MakeGenericMethod( typeElem ); } else if( type == typeof( ImmutableDictionary<,> ).MakeGenericType( typeElem.GenericTypeArguments ) ) { var genMeth = GetType().GetMethod( "MakeImmutableDictionary", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic ); toMeth = genMeth.MakeGenericMethod( typeElem.GenericTypeArguments ); } else if( type == typeof( List<> ).MakeGenericType( typeElem ) ) { var genMeth = GetType().GetMethod( "MakeList", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic ); toMeth = genMeth.MakeGenericMethod( typeElem ); } else if( type == typeof( Dictionary<,> ).MakeGenericType( typeElem.GenericTypeArguments ) ) { var genMeth = GetType().GetMethod( "MakeDictionary", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic ); toMeth = genMeth.MakeGenericMethod( typeElem.GenericTypeArguments ); } var obj = toMeth?.Invoke( this, new object[1] { list } ); return obj; } private List MakeList( IList list ) { return list as List; } private object MakeImmutableArray( List list ) { var arr = list.ToImmutableArray(); return arr; } private object MakeImmutableDictionary( List> list ) { var dict = list.ToImmutableDictionary(); return dict; } private object MakeDictionary( List> list ) { var dict = list.ToDictionary(); return dict; } private object DeserializeArray( XmlElement elem, MemberInfo mi, Type type ) { if( _cfg.VerboseLogging ) log.info( $"" ); Type typeElem = type.GetElementType(); string refString = elem.GetAttribute( "ref" ); int refInt = refString.Length > 0 ? Convert.ToInt32( refString ) : -1; XmlNodeList arrNodeList = elem.ChildNodes; int length = arrNodeList.Count; Array arr = createArray( typeElem, refInt, length ); for( int i = 0; i < arr.Length; ++i ) { if( arrNodeList.Item( i ) is XmlElement ) { XmlElement arrElem = (XmlElement)arrNodeList.Item( i ); var finalType = typeElem; if( arrElem.HasAttribute( "_.t" ) ) { var typename = arrElem.GetAttribute( "_.t" ); finalType = FindType( typename ); if( finalType == null ) finalType = typeElem; } arr.SetValue( Deserialize( arrElem, mi, finalType, null ), i ); } } return arr; } private object createObject( XmlElement elem, string typename, int refInt, object obj ) { Type type = Type.GetType( typename ); return createObject( elem, type, refInt, obj ); } private object createObject( XmlElement elem, Type type, int refInt, object existingObj ) { TypeCode tc = Type.GetTypeCode( type ); if( _cfg.datastructure == Datastructure.Graph && refInt > 0 && m_alreadySerialized.ContainsKey( refInt ) ) { if( _cfg.VerboseLogging ) log.info( $"Reuse object" ); return m_alreadySerialized[refInt]; } // FIRST If there is a proxy deserializer, we skip using the existing object. var isProxy = elem.HasAttribute( "proxy" ); if( isProxy ) { if( _cfg.VerboseLogging ) log.info( $"use Proxy" ); object obj = null; var tryType = type; TypeProxy? proxy = null; while( tryType != typeof( object ) && obj == null ) { //m_cfg.TypeProxy.TryGetValue( ) if( _cfg.TypeProxy.TryGetValue( tryType, out var newProxy ) ) { proxy = newProxy; break; } tryType = tryType.BaseType; } var proxyVal = elem.GetAttribute( "proxy" ); if( proxy.HasValue ) { obj = proxy.Value.des( type.Name, proxyVal ); return obj; } else { log.warn( $"Trying to proxy in {elem.Name}, but there is no proxy available. Falling back to regular hydrate/create" ); } } // SECOND if we have an existing object there of the same type, replace if( existingObj != null ) { var existingObjType = existingObj.GetType(); //var isSubclass = type.IsSubclassOf( existingObjType ) || existingObjType.IsSubclassOf( type ); var isSubclass = type.IsAssignableFrom( existingObjType ); if( isSubclass ) { if( _cfg.VerboseLogging ) log.info( $"Using existing obj {existingObj?.ToString()}" ); return existingObj; } // old //if( type == existingObjType ) return existingObj; } // THIRD create a new object { object obj = null; try { if( _cfg.VerboseLogging ) log.info( $"For {type.FriendlyName()} check for constructors" ); var cons = type.GetConstructor( Type.EmptyTypes ); if( cons != null) { if( _cfg.VerboseLogging ) log.info( $"Activator.CreateInstance" ); obj = Activator.CreateInstance( type ); } else { log.debug( $"For {type.FriendlyName()} use GetUninitializedObject" ); obj = System.Runtime.Serialization.FormatterServices.GetUninitializedObject( type ); } if( _cfg.VerboseLogging ) log.info( $"Got obj {obj?.ToString()}" ); } catch( Exception ) { try { if( _cfg.VerboseLogging ) log.info( $"GetUninitializedObject" ); obj = System.Runtime.Serialization.FormatterServices.GetUninitializedObject( type ); if( _cfg.VerboseLogging ) log.info( $"Got obj {obj?.ToString()}" ); } catch( Exception exInner ) { log.error( $"Got exception {exInner.Message} trying to make an uninitialized object" ); } } if( obj == null ) { log.warn( $"Could not create object of type {type.FriendlyName()}" ); return obj; } if( _cfg.datastructure == Datastructure.Graph && refInt > 0 ) { m_alreadySerialized[refInt] = obj; } return obj; } } private Array createArray( string elemTypename, int refInt, int length ) { Type elemType = Type.GetType( elemTypename ); return createArray( elemType, refInt, length ); } private Array createArray( Type elemType, int refInt, int length ) { TypeCode elemTC = Type.GetTypeCode( elemType ); if( _cfg.datastructure == Datastructure.Graph && refInt > 0 && m_alreadySerialized.ContainsKey( refInt ) ) { return (Array)m_alreadySerialized[refInt]; } else { Array arr = Array.CreateInstance( elemType, length ); if( _cfg.datastructure == Datastructure.Graph ) { m_alreadySerialized[refInt] = arr; } return arr; } } private ObjectIDGenerator m_objectID = new ObjectIDGenerator(); private Dictionary m_alreadySerialized = new Dictionary(); #endregion #region Serialize private string getTypeName( Type type ) { //Assembly ass = type.Assembly; //string assName = ass.GetName().FriendlyName(); return type.FullName; // + ", " + assName; } public void Serialize( Stream stream, object root ) { Serialize( stream, null, typeof( object ), root ); } public void Serialize( Stream stream, MemberInfo mi, Type mType, object root ) { //lib.log.info( "Serialize( Stream stream, object root ) {0} {1}", m_rndVal, m_alreadySerialized.Count ); m_alreadySerialized.Clear(); m_objectID = new ObjectIDGenerator(); XmlTextWriter writer = new XmlTextWriter( stream, System.Text.Encoding.ASCII ); writer.Formatting = Formatting.Indented; Serialize( writer, mi, mType, root ); //Rely on the parent closing the stream. //writer.Close(); writer.Flush(); //lib.log.info( "Serialize END ( Stream stream, object root ) {0} {1}", m_rndVal, m_alreadySerialized.Count ); } private void Serialize( XmlWriter writer, MemberInfo mi, Type mType, object root ) { //writer.WriteStartDocument(); Serialize( writer, mi, mType, root, "root", 1, true ); //writer.WriteEndDocument(); } private void Serialize( XmlWriter writer, MemberInfo mi, Type mType, object root, string name, int depth, bool forceType ) { if( root != null ) { Type type = root.GetType(); TypeCode typeCode = Type.GetTypeCode( type ); if( typeCode != TypeCode.Object ) { bool writeELements = _cfg.POD == POD.Elements || forceType; if( !writeELements && writer is XmlTextWriter textWriter ) { var writeState = textWriter.WriteState; writeELements = writeState != WriteState.Element; } if( writeELements ) writer.WriteStartElement( name ); SerializeConcrete( writer, mi, root, name, forceType ); if( writeELements ) writer.WriteEndElement(); return; } else { writer.WriteStartElement( name ); if( !type.IsArray ) { if( IsEnumerable( type ) ) { SerializeCollection( writer, mi, mType, root, depth ); } else { SerializeObject( writer, mi, mType, root, depth ); } } else { SerializeArray( writer, mi, mType, root, depth ); } } } else { writer.WriteStartElement( name ); writer.WriteAttributeString( "v", "null" ); } writer.WriteEndElement(); } private void SerializeConcrete( XmlWriter writer, MemberInfo mi, object root, string name, bool forceType ) { //TODO: Only write this out if debugging. if( forceType || _cfg.POD == POD.Elements ) { if( forceType ) writer.WriteAttributeString( "_.t", getTypeName( root.GetType() ) ); writer.WriteAttributeString( "v", root.ToString() ); } else { writer.WriteAttributeString( name, root.ToString() ); } } private void SerializeCollection( XmlWriter writer, MemberInfo? mi, Type mType, object root, int depth ) { IEnumerable it = root as IEnumerable; //Array arr = (Array)root; Type typeElem = it.GetType().GenericTypeArguments[0]; Type type = root.GetType(); if( mType != type ) { log.info( $"Coll: {mType.FriendlyName()} {mi?.Name} != {type.FriendlyName()}" ); writer.WriteAttributeString( "_.t", getTypeName( type ) ); } bool first; long refInt = m_objectID.GetId( root, out first ); if( _cfg.datastructure == Datastructure.Graph ) { writer.WriteAttributeString( "ref", refInt.ToString() ); } if( first ) { if( _cfg.datastructure == Datastructure.Graph ) { m_alreadySerialized[refInt] = root; } int i = 0; foreach( var v in it ) { Serialize( writer, null, typeElem, v, "i" + i.ToString(), depth + 1, false ); ++i; } } } private void SerializeObject( XmlWriter writer, MemberInfo mi, Type mType, object root, int depth ) { if( mType != root.GetType() ) { var rType = root.GetType(); var rTypeName = !rType.IsNested ? rType.Name : $"{rType.DeclaringType.Name}.{rType.Name}"; var mTypeName = !mType.IsNested ? mType.Name : $"{mType.DeclaringType.Name}.{mType.Name}"; var rootTypeName = !mType.IsNested ? mType.Name : $"{mType.DeclaringType.Name}.{mType.Name}"; log.info( $"SerObj {mTypeName} != {rTypeName}" ); writer.WriteAttributeString( "_.t", getTypeName( root.GetType() ) ); } if( depth == 1 ) { writer.WriteAttributeString( "_.version.", $"{_cfg.Version}" ); } long refInt = m_objectID.GetId( root, out var first ); // @@@@ FIX for proxies. if( _cfg.datastructure == Datastructure.Graph ) { writer.WriteAttributeString( "ref", refInt.ToString() ); } if( first ) { if( _cfg.datastructure == Datastructure.Graph ) { m_alreadySerialized[refInt] = root; } Type type = root.GetType(); //* Type typeISerializable = typeof( ISerializable ); if( root is ISerializable ser ) { if( root is Delegate ) { return; } var serInfo = new SerializationInfo( type, new FormatterConverter() ); var context = new StreamingContext( StreamingContextStates.File, root ); ser.GetObjectData( serInfo, context ); foreach( var serMember in serInfo ) { string name = serMember.Name; name = refl.TypeToIdentifier( name ); Serialize( writer, mi, mType, serMember.Value, name, depth, true ); } return; } { var tryType = type; TypeProxy? proxy = null; while( tryType != typeof( object ) ) { if( _cfg.TypeProxy.TryGetValue( tryType, out var newProxy ) ) { proxy = newProxy; break; } tryType = tryType.BaseType; } if( proxy.HasValue ) { var proxyStr = proxy.Value.ser( root ); writer.WriteAttributeString( "proxy", proxyStr ); return; } } //*/ SerializeObjectOfNarrowType( writer, mi, root, depth, type ); } } private void SerializeObjectOfNarrowType( XmlWriter writer, MemberInfo mi, object root, int depth, Type narrowType ) { bool filterFields, filterProps, doImpls, doFields, doProps; HashSet whitelistFields, whitelistProps; GetFilters( _cfg.TypesDefault, mi, narrowType, out filterFields, out filterProps, out doImpls, out doFields, out doProps, out whitelistFields, out whitelistProps ); var isImm = typeof(imm.Obj).IsAssignableFrom( narrowType ); if( doFields || doImpls ) { var fields = refl.GetAllFields( narrowType ); foreach( var childFi in fields ) { string name = childFi.Name; var dontAtt = childFi.GetCustomAttributes(); var doAtt = childFi.GetCustomAttributes(); string propName = ""; if( name.StartsWith( "<" ) && name.EndsWith( "BackingField" ) ) { var gtIndex = name.IndexOf( '>' ); propName = name.Substring( 1, gtIndex - 1 ); var propInfo = narrowType.GetProperty( propName ); dontAtt = propInfo.GetCustomAttributes(); doAtt = propInfo.GetCustomAttributes(); } if( dontAtt.Any() ) { continue; } if( isImm ) { if( name == "MetaStorage" ) continue; if( name == "Fn" ) continue; } if( !doAtt.Any() && FilterField( filterFields, doImpls, whitelistFields, childFi as MemberInfo, name ) ) continue; object[] objs = childFi.GetCustomAttributes( typeof( NonSerializedAttribute ), true ); if( objs.Length > 0 ) { continue; } //if( childFi.GetCustomAttribute() ) name = refl.TypeToIdentifier( name ); var finalName = ( _cfg.Naming == BackingFieldNaming.Short && !string.IsNullOrEmpty( propName ) ) ? propName : name; Serialize( writer, childFi, childFi.FieldType, childFi.GetValue( root ), finalName, depth + 1, false ); } } if( doProps || doImpls ) { var props = refl.GetAllProperties( narrowType ); foreach( var childPi in props ) { string name = childPi.Name; var dontAtt = childPi.GetCustomAttributes(); if( dontAtt.Any() ) { continue; } if( isImm ) { if( name == "MetaStorage" ) continue; if( name == "Fn" ) continue; } if( FilterField( filterProps, doImpls, whitelistProps, childPi as MemberInfo, name ) ) continue; object[] objs = childPi.GetCustomAttributes( typeof( NonSerializedAttribute ), true ); if( objs.Length > 0 ) { continue; } name = refl.TypeToIdentifier( name ); Serialize( writer, childPi, childPi.PropertyType, childPi.GetValue( root ), name, depth + 1, false ); } } } private static void GetFilters( Types TypesDefault, MemberInfo mi, Type type, out bool filterFields, out bool filterProps, out bool doImpls, out bool doFields, out bool doProps, out HashSet whitelistFields, out HashSet whitelistProps ) { var custWLFields = mi?.GetCustomAttribute( true ); var custWLProps = mi?.GetCustomAttribute( true ); filterFields = custWLFields != null; filterProps = custWLProps != null; var typesTodo = type.GetCustomAttribute( true )?.Types ?? TypesDefault; doImpls = typesTodo.HasFlag( Types.Implied ); doFields = filterFields || typesTodo.HasFlag( Types.Fields ); doProps = filterProps || typesTodo.HasFlag( Types.Props ); whitelistFields = new( custWLFields?.Values ?? new string[0] ); whitelistProps = new( custWLProps?.Values ?? new string[0] ); } private void SerializeArray( XmlWriter writer, MemberInfo mi, Type mType, object root, int depth ) { Array arr = (Array)root; Type typeElem = arr.GetType().GetElementType(); Type type = root.GetType(); Type typeOfMember = typeof( object ); if( mi is FieldInfo fi ) typeOfMember = fi.FieldType; if( mi is PropertyInfo pi ) typeOfMember = pi.PropertyType; if( typeOfMember != type ) { log.info( $"SerArr {typeOfMember.FriendlyName()} {mi?.Name} != {type.FriendlyName()}" ); writer.WriteAttributeString( "_.t", getTypeName( type ) ); } bool first; long refInt = m_objectID.GetId( root, out first ); if( _cfg.datastructure == Datastructure.Graph ) { writer.WriteAttributeString( "ref", refInt.ToString() ); } if( first ) { if( _cfg.datastructure == Datastructure.Graph ) { m_alreadySerialized[refInt] = root; } //typeElem.MemberType for( int i = 0; i < arr.Length; ++i ) { Serialize( writer, mi, typeElem, arr.GetValue( i ), "i" + i.ToString(), depth + 1, false ); } } } #endregion } }