Attributes/TypeSelectAttribute.cs
using System;
using System.Collections.Generic;
using System.Linq;
namespace ExtendedEditor.Attributes;
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class TypeSelectAttribute : Attribute
{
public bool? AllowNone { get; set; }
public bool? AllowAbstract { get; set; }
public Type[] AllowedTypes { get; set; } = [];
public Type[] DisallowedTypes { get; set; } = [];
public Type[] AllowedBaseTypes { get; set; } = [];
public Type[] DisallowedBaseTypes { get; set; } = [];
public Type[] RequiredBaseTypes { get; set; } = [];
public string? ValidatorName { get; set; } = null;
public Func<Type?, bool>? Validator { get; set; } = null;
public TypeSelectAttribute()
{
}
public TypeSelectAttribute(bool allowNone = false, bool allowAbstract = false)
{
AllowNone = allowNone;
AllowAbstract = allowAbstract;
}
public TypeSelectAttribute(TypeSelectAttribute other)
{
AllowAbstract = other.AllowAbstract;
AllowedTypes = other.AllowedTypes;
DisallowedTypes = other.DisallowedTypes;
AllowedBaseTypes = other.AllowedBaseTypes;
DisallowedBaseTypes = other.DisallowedBaseTypes;
RequiredBaseTypes = other.RequiredBaseTypes;
Validator = other.Validator;
ValidatorName = other.ValidatorName;
}
/// <summary>
/// Returns true if type is allowed, disallowed otherwise.
/// </summary>
/// <remarks>
/// <para>Quick explanation: read from top to bottom, first hit is the result</para>
/// <para>If type is null and <see cref="AllowNone"/> is false - it is disallowed</para>
/// <para>If type is abstract and <see cref="AllowAbstract"/> is false - it is disallowed</para>
/// <para>If type doesn't pass method with <see cref="ValidatorName"/> name in class where <see cref="TypeSelectAttribute"/> us used - it is disallowed. (This method doesn't validate it, only via Editor)</para>
/// <para>If type doesn't pass <see cref="Validator"/> - it is disallowed</para>
/// <para>If type is it is not assignable to all of the types in <see cref="RequiredBaseTypes"/> - it is disallowed</para>
/// <para>If type is in <see cref="AllowedTypes"/> - it is allowed</para>
/// <para>If type is in <see cref="DisallowedTypes"/> - it is disallowed</para>
/// <para>If type extends any of the types in <see cref="AllowedBaseTypes"/> - it is allowed</para>
/// <para>If type extends any of the types in <see cref="DisallowedBaseTypes"/> - it is disallowed</para>
/// <para>Otherwise - allowed</para>
/// </remarks>
public bool IsAllowed(Type? type)
{
if(type is null && !(AllowNone ?? true))
return false;
bool foundAllowed = false;
if(type is not null)
{
if(type.IsAbstract && !(AllowAbstract ?? true))
return false;
if(RequiredBaseTypes is not null && RequiredBaseTypes.Any(t => !type.IsAssignableTo(t)))
return false;
if(AllowedTypes is not null && AllowedTypes.Contains(type))
foundAllowed = true;
if(!foundAllowed && DisallowedTypes is not null && DisallowedTypes.Contains(type))
return false;
if(!foundAllowed && AllowedBaseTypes is not null && AllowedBaseTypes.Any(t => type.IsAssignableTo(t)))
foundAllowed = true;
if(!foundAllowed && DisallowedBaseTypes is not null && DisallowedBaseTypes.Any(t => type.IsAssignableTo(t)))
return false;
}
if(Validator is not null && !Validator.Invoke(type)) // do it last because it can be slow
return false;
return true;
}
public TypeSelectAttribute RestrictBy(TypeSelectAttribute restrictor)
{
bool? allowNone = (restrictor.AllowNone.HasValue && !restrictor.AllowNone.Value) ? false : AllowNone;
bool? allowAbstract = (restrictor.AllowAbstract.HasValue && !restrictor.AllowAbstract.Value) ? false : AllowAbstract;
var disallowedTypes = (DisallowedTypes ?? []).Concat(restrictor.DisallowedTypes ?? []);
var disallowedBaseTypes = (DisallowedBaseTypes ?? []).Concat(restrictor.DisallowedBaseTypes ?? []);
var requiredBaseTypes = (RequiredBaseTypes ?? []).Concat(restrictor.RequiredBaseTypes ?? []);
IEnumerable<Type> allowedTypes = (AllowedTypes ?? []).Where(restrictor.IsAllowed).Concat(restrictor.AllowedTypes);
IEnumerable<Type> allowedBaseTypes = (AllowedBaseTypes ?? []).Where(restrictor.IsAllowed).Concat(restrictor.AllowedBaseTypes);
bool tester(Type? t) => (restrictor.Validator?.Invoke(t) ?? true) && (Validator?.Invoke(t) ?? true);
return new TypeSelectAttribute()
{
AllowNone = allowNone,
AllowAbstract = allowAbstract,
AllowedTypes = [.. allowedTypes],
DisallowedTypes = [.. disallowedTypes],
AllowedBaseTypes = [.. allowedBaseTypes],
DisallowedBaseTypes = [.. disallowedBaseTypes],
RequiredBaseTypes = [.. requiredBaseTypes],
Validator = tester,
};
}
}