1440 lines
34 KiB
C#
1440 lines
34 KiB
C#
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<object, string> ser, Func<string, string, object> des );
|
|
|
|
//public record struct CollectionCreator( Func<IEnumerable, object> 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<string, List<string>> WLProps = new();
|
|
public Dictionary<string, List<string>> WLFields = new();
|
|
|
|
public Dictionary<Type, TypeProxy> TypeProxy = new();
|
|
|
|
//public Dictionary<Type, CollectionCreator> 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();
|
|
fromStr = string.IsNullOrWhiteSpace(fromStr) ? (stream as FileStream).Name : fromStr;
|
|
fromStr = string.IsNullOrWhiteSpace(fromStr) ? (stream as NetworkStream).Socket.RemoteEndPoint.ToString() : fromStr;
|
|
}
|
|
|
|
public object Deserialize( Stream stream )
|
|
{
|
|
SetFromStr( stream );
|
|
return DeserializeKnownType( stream, null );
|
|
}
|
|
|
|
|
|
public T Deserialize<T>( 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( t == null )
|
|
return Deserialize( doc.DocumentElement );
|
|
|
|
return Deserialize( doc.DocumentElement, null, t, null );
|
|
}
|
|
|
|
public void DeserializeInto<T>( Stream stream, T obj )
|
|
{
|
|
SetFromStr( stream );
|
|
|
|
XmlTextReader reader = new( stream );
|
|
reader.Read();
|
|
|
|
XmlDocument doc = new();
|
|
|
|
doc.Load( reader );
|
|
|
|
HydrateObject<T>( 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.Name}.{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?.Name} 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<T>()
|
|
{
|
|
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<T>( 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.Imm).IsAssignableFrom( narrowType );
|
|
|
|
XmlNodeList allChildren = elem.ChildNodes;
|
|
|
|
bool filterFields, filterProps, doImpls, doFields, doProps;
|
|
HashSet<string> 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<lib.Dont>();
|
|
|
|
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<lib.Dont>();
|
|
}
|
|
|
|
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( 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<lib.Dont>();
|
|
if( dontAtt.Any() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
name = refl.TypeToIdentifier( name );
|
|
|
|
if( FilterField( filterProps, doImpls, whitelistProps, childPi as PropertyInfo, name ) )
|
|
continue;
|
|
|
|
XmlElement childElem = getNamedChild( allChildren, name );
|
|
|
|
if( childElem != null )
|
|
{
|
|
object existingObj = childPi.GetValue( obj );
|
|
|
|
object childObj = Deserialize( childElem, childPi, childPi.PropertyType, existingObj );
|
|
|
|
var setMethod = childPi.GetSetMethod();
|
|
|
|
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.Imm;
|
|
var newObj = imm.Record( $"From XML {fromStr}:{elem.ParentNode?.Name}{elem.Name}" );
|
|
return newObj;
|
|
}
|
|
|
|
}
|
|
|
|
private static bool FilterField( bool filterFields, bool doImpls, HashSet<string> whitelistFields, MemberInfo mi, string name )
|
|
{
|
|
if( doImpls )
|
|
{
|
|
if( mi.GetCustomAttribute<ChildAttribute>( 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?.Name}({type?.Name}) 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().Name} {typeElem.Name} into reflT {mi.ReflectedType.Name} declT {mi.DeclaringType.Name} {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.Name}" );
|
|
|
|
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<T> MakeList<T>( IList list )
|
|
{
|
|
return list as List<T>;
|
|
}
|
|
|
|
private object MakeImmutableArray<T>( List<T> list )
|
|
{
|
|
var arr = list.ToImmutableArray();
|
|
return arr;
|
|
}
|
|
|
|
private object MakeImmutableDictionary<K, V>( List<KeyValuePair<K, V>> list )
|
|
{
|
|
var dict = list.ToImmutableDictionary();
|
|
return dict;
|
|
}
|
|
|
|
|
|
private object MakeDictionary<K, V>( List<KeyValuePair<K, V>> 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();
|
|
|
|
// @@@ GROSS Fix the types so theyre known good.
|
|
var isSubclass = type.IsSubclassOf( existingObjType ) || existingObjType.IsSubclassOf( type );
|
|
|
|
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( $"Activator.CreateInstance" );
|
|
|
|
//Trying the nice way to creat objects first.
|
|
obj = Activator.CreateInstance( type );
|
|
|
|
if( _cfg.VerboseLogging ) log.info( $"Got obj {obj?.ToString()}" );
|
|
}
|
|
catch( Exception ex )
|
|
{
|
|
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.Name}" );
|
|
|
|
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<long, object> m_alreadySerialized = new Dictionary<long, object>();
|
|
|
|
#endregion
|
|
|
|
#region Serialize
|
|
|
|
private string getTypeName( Type type )
|
|
{
|
|
//Assembly ass = type.Assembly;
|
|
|
|
//string assName = ass.GetName().Name;
|
|
|
|
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 )
|
|
{
|
|
if( _cfg.POD == POD.Elements || forceType )
|
|
writer.WriteStartElement( name );
|
|
|
|
SerializeConcrete( writer, mi, root, name, forceType );
|
|
|
|
if( _cfg.POD == POD.Elements || forceType )
|
|
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.Name} {mi?.Name} != {type.Name}" );
|
|
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<string> 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.Imm).IsAssignableFrom( narrowType );
|
|
|
|
if( doFields || doImpls )
|
|
{
|
|
var fields = refl.GetAllFields( narrowType );
|
|
|
|
foreach( var childFi in fields )
|
|
{
|
|
string name = childFi.Name;
|
|
var dontAtt = childFi.GetCustomAttributes<lib.Dont>();
|
|
|
|
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<lib.Dont>();
|
|
}
|
|
|
|
|
|
if( dontAtt.Any() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( isImm )
|
|
{
|
|
if( name == "MetaStorage" ) continue;
|
|
if( name == "Fn" ) continue;
|
|
}
|
|
|
|
if( FilterField( filterFields, doImpls, whitelistFields, childFi as MemberInfo, name ) )
|
|
continue;
|
|
|
|
object[] objs = childFi.GetCustomAttributes( typeof( NonSerializedAttribute ), true );
|
|
|
|
if( objs.Length > 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//if( childFi.GetCustomAttribute<WhitelistAttribute>() )
|
|
|
|
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<lib.Dont>();
|
|
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<string> whitelistFields, out HashSet<string> whitelistProps )
|
|
{
|
|
var custWLFields = mi?.GetCustomAttribute<ChildFieldsAttribute>( true );
|
|
var custWLProps = mi?.GetCustomAttribute<ChildPropsAttribute>( true );
|
|
|
|
filterFields = custWLFields != null;
|
|
filterProps = custWLProps != null;
|
|
|
|
var typesTodo = type.GetCustomAttribute<Ser>( 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.Name} {mi?.Name} != {type.Name}" );
|
|
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
|
|
}
|
|
|
|
}
|