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