173 lines
6.0 KiB
C#
173 lines
6.0 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 : imm.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 lib.Types TypesDefault { get; init; } = lib.Types.Fields | lib.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<lib.Ser>(true)?.Types ?? _cfg.TypesDefault;
|
|
bool doFields = typesTodo.HasFlag(lib.Types.Fields);
|
|
bool doProps = typesTodo.HasFlag(lib.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<lib.Do>() != null,
|
|
actualMi.GetCustomAttribute<lib.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
|
|
} |