sharplib/lib/CodeGen.cs

183 lines
5.4 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
}