using System.ComponentModel.DataAnnotations;
using System.Reflection;
using Facepunch.ActionGraphs;
namespace Sandbox;
public static partial class SandboxSystemExtensions
{
///
/// Returns true if this member has this attribute
///
internal static bool HasAttribute( this MemberInfo memberinfo, Type attribute, bool inherit = true )
{
return memberinfo?.IsDefined( attribute, inherit ) ?? false;
}
///
/// Returns true if this type derives from a type named name
///
internal static bool HasBaseType( this Type type, string name )
{
if ( type.BaseType == null ) return false;
if ( type.BaseType.FullName == name ) return true;
return HasBaseType( type.BaseType, name );
}
///
/// Gets an attribute on an enum field value
///
/// The type of the attribute you want to retrieve
/// The enum value
/// The attribute of type T that exists on the enum value
/// ().Description;]]>
public static T GetAttributeOfType( this Enum enumVal ) where T : System.Attribute
{
var type = enumVal.GetType();
var memInfo = type.GetMember( enumVal.ToString() );
var attributes = memInfo[0].GetCustomAttributes( typeof( T ), false );
return (attributes.Length > 0) ? (T)attributes[0] : null;
}
///
/// Returns if this type is based on a given generic type.
///
/// The type to test.
/// The type to test against. Typically this will be something like typeof( MyType<> )
public static bool IsBasedOnGenericType( this Type src, Type test )
{
if ( !test.IsGenericType ) return false;
var type = src;
while ( type != null )
{
if ( type.IsGenericType && type.GetGenericTypeDefinition() == test ) return true;
type = type.BaseType;
}
return false;
}
///
/// Check all s on this property, and get the error messages if there are any.
///
/// The property whose arguments to test.
/// Instance of the object this property is of.
/// If returned false, these will be the error messages to display.
/// Override the property name in error messages.
/// Returns true if all checks have passed or there is no attributes to test, false if there were errors.
public static bool CheckValidationAttributes( this PropertyInfo prop, object obj, out string[] errors, string name = null )
{
if ( prop == null )
throw new System.ArgumentNullException();
if ( obj == null )
throw new System.ArgumentNullException();
var errorList = new List();
var attrs = prop.GetCustomAttributes();
foreach ( var attr in attrs )
{
var valid = attr.IsValid( prop.GetValue( obj ) );
if ( !valid ) errorList.Add( attr.FormatErrorMessage( name ?? prop.Name ) );
}
errors = errorList.ToArray();
return !errorList.Any();
}
///
/// Determine if this property is init-only.
///
/// The property to test.
/// Returns true if the property is init-only, false otherwise.
public static bool IsInitOnly( this PropertyInfo property )
{
if ( !property.CanWrite ) return false;
var setMethod = property.SetMethod;
if ( setMethod == null ) return false;
// Init-only properties are marked with the IsExternalInit type.
return setMethod.ReturnParameter.GetRequiredCustomModifiers().Contains( typeof( System.Runtime.CompilerServices.IsExternalInit ) );
}
///
/// Returns this type's name, with nicer formatting for generic types.
///
public static string ToSimpleString( this Type type, bool includeNamespace = true )
{
var sb = new StringBuilder();
sb.AppendType( type, includeNamespace );
return sb.ToString();
}
///
/// Returns this member's name qualified by its declaring type, with nicer formatting for generics.
///
public static string ToSimpleString( this MemberInfo member, bool includeNamespace = true )
{
if ( member is Type type ) return ToSimpleString( type, includeNamespace );
if ( member is not MethodInfo { IsGenericMethod: true } method )
{
return $"{member.DeclaringType}::{member.Name}";
}
var sb = new StringBuilder();
sb.AppendType( method.DeclaringType, includeNamespace );
sb.Append( "::" );
var quoteIndex = method.Name.IndexOf( '`' );
sb.Append( quoteIndex == -1 ? method.Name : method.Name[..quoteIndex] );
sb.Append( "<" );
var first = true;
foreach ( var arg in method.GetGenericArguments() )
{
if ( first ) first = false;
else sb.Append( ", " );
sb.AppendType( arg, includeNamespace );
}
sb.Append( ">" );
return sb.ToString();
}
///
/// Returns a nice name for the given delegate, based on the method that implements it.
///
public static string ToSimpleString( this Delegate deleg, bool includeNamespace = true )
{
if ( deleg.Method is { Name: "MoveNext", DeclaringType.Name: "AsyncStateMachineBox`1" } )
{
var stateMachineType = deleg.Method.DeclaringType.GetGenericArguments()[1];
return stateMachineType.ToSimpleString();
}
return deleg.Method.ToSimpleString();
}
private static void AppendType( this StringBuilder sb, Type type, bool includeNamespace )
{
if ( type == null )
{
sb.Append( "null" );
return;
}
if ( type.IsGenericMethodParameter || type.IsGenericTypeParameter )
{
return;
}
if ( type.IsArray )
{
sb.AppendType( type.GetElementType(), includeNamespace );
sb.Append( "[" );
for ( var i = 1; i < type.GetArrayRank(); ++i )
{
sb.Append( "," );
}
sb.Append( "]" );
return;
}
if ( type.IsByRef )
{
sb.Append( "ref " );
sb.AppendType( type.GetElementType(), includeNamespace );
return;
}
if ( type.IsPointer )
{
sb.AppendType( type.GetElementType(), includeNamespace );
sb.Append( "*" );
return;
}
if ( type.IsNested )
{
sb.AppendType( type.DeclaringType, includeNamespace );
sb.Append( "." );
}
else if ( type.Namespace != null && includeNamespace )
{
sb.Append( type.Namespace );
sb.Append( "." );
}
if ( type.IsGenericType )
{
if ( Either.IsEitherType( type ) )
{
var options = Either.Unwrap( type );
var first = true;
foreach ( var option in options )
{
if ( first ) first = false;
else sb.Append( " | " );
sb.AppendType( option, includeNamespace );
}
}
else if ( Nullable.GetUnderlyingType( type ) is { } elemType )
{
sb.AppendType( elemType, includeNamespace );
return;
}
else
{
var quoteIndex = type.Name.IndexOf( '`' );
sb.Append( quoteIndex == -1 ? type.Name : type.Name[..quoteIndex] );
sb.Append( "<" );
var first = true;
foreach ( var arg in type.GetGenericArguments() )
{
if ( first ) first = false;
else sb.Append( ", " );
sb.AppendType( arg, includeNamespace );
}
sb.Append( ">" );
}
return;
}
sb.Append( type.Name );
}
}