using Robust.Shared.Interfaces.Reflection;
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.IoC;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
namespace Robust.Shared.Serialization
{
///
public class CustomFormatManager : ICustomFormatManager
{
private Dictionary> _flagFormatters = new Dictionary>();
private Dictionary> _constantFormatters = new Dictionary>();
public WithFormat FlagFormat()
{
if (!_flagFormatters.TryGetValue(typeof(T), out var formatter))
{
formatter = new WithFlagRepresentation(GetFlag());
_flagFormatters.Add(typeof(T), formatter);
}
return formatter;
}
public WithFormat ConstantFormat()
{
if (!_constantFormatters.TryGetValue(typeof(T), out var formatter))
{
formatter = new WithConstantRepresentation(GetConstants());
_constantFormatters.Add(typeof(T), formatter);
}
return formatter;
}
///
/// Get the enum flag type for the given tag .
///
///
/// The tag type to use for finding the flag representation. To learn more,
/// see the .
///
///
/// Thrown if:
///
/// -
/// The tag type corresponds to no enum flag representation.
///
/// -
/// The tag type corresponds to more than one enum flag representation.
///
/// -
/// The tag type corresponds to a non-enum representation.
///
/// -
/// The tag type corresponds to a non-int enum representation.
///
/// -
/// The tag type corresponds to a non-bitflag int enum representation.
///
///
///
///
/// The unique int-backed bitflag enum type for the given tag.
///
private Type GetFlag()
{
var reflectionManager = IoCManager.Resolve();
Type flagType = null;
foreach (Type bitflagType in reflectionManager.FindTypesWithAttribute())
{
foreach (var flagsforAttribute in bitflagType.GetCustomAttributes(true))
{
if (typeof(T) == flagsforAttribute.Tag)
{
if (flagType != null)
{
throw new NotSupportedException($"Multiple bitflag enums declared for the tag {flagsforAttribute.Tag}.");
}
if (!bitflagType.IsEnum)
{
throw new FlagSerializerException($"Could not create FlagSerializer for non-enum {bitflagType}.");
}
if (Enum.GetUnderlyingType(bitflagType) != typeof(int))
{
throw new FlagSerializerException($"Could not create FlagSerializer for non-int enum {bitflagType}.");
}
if (!bitflagType.GetCustomAttributes(false).Any())
{
throw new FlagSerializerException($"Could not create FlagSerializer for non-bitflag enum {bitflagType}.");
}
flagType = bitflagType;
}
}
}
if (flagType == null)
{
throw new FlagSerializerException($"Found no type marked with a `FlagsForAttribute(typeof({typeof(T)}))`.");
}
return flagType;
}
///
/// Get the constant type for the given tag .
///
///
/// The tag type to use for finding the constant representation. To learn more,
/// see the .
///
///
/// Thrown if:
///
/// -
/// The tag type corresponds to no constant representation.
///
/// -
/// The tag type corresponds to more than one constant representation.
///
/// -
/// The tag type corresponds to a non-enum representation.
///
/// -
/// The tag type corresponds to a non-int enum representation.
///
///
///
///
/// The unique int-backed enum constant type for the given tag.
///
private Type GetConstants()
{
var reflectionManager = IoCManager.Resolve();
Type constantType = null;
foreach (Type enumConstantType in reflectionManager.FindTypesWithAttribute())
{
foreach (var constantsForAttribute in enumConstantType.GetCustomAttributes(true))
{
if (typeof(T) == constantsForAttribute.Tag)
{
if (constantType != null)
{
throw new NotSupportedException($"Multiple constant enums declared for the tag {constantsForAttribute.Tag}.");
}
if (!enumConstantType.IsEnum)
{
throw new ConstantSerializerException($"Could not create ConstantSerializer for non-enum {enumConstantType}.");
}
if (Enum.GetUnderlyingType(enumConstantType) != typeof(int))
{
throw new ConstantSerializerException($"Could not create ConstantSerializer for non-int enum {enumConstantType}.");
}
constantType = enumConstantType;
}
}
}
if (constantType == null)
{
throw new FlagSerializerException($"Found no type marked with a `ConstantsForAttribute(typeof({typeof(T)}))`.");
}
return constantType;
}
}
///
/// int representation in terms of some enum flag type.
///
public class WithFlagRepresentation : WithFormat
{
private Type _flagType;
public Type FlagType => _flagType;
private YamlFlagSerializer _serializer;
public WithFlagRepresentation(Type flagType)
{
_flagType = flagType;
_serializer = new YamlFlagSerializer(_flagType, this);
}
public override YamlObjectSerializer.TypeSerializer GetYamlSerializer()
{
return _serializer;
}
public override Type Format => typeof(List);
public override int FromCustomFormat(object obj)
{
var flagNames = (List)obj;
var flags = 0;
foreach (var flagName in flagNames)
{
flags |= (int)Enum.Parse(_flagType, flagName);
}
return flags;
}
public override object ToCustomFormat(int flags)
{
var flagNames = new List();
// Assumption: a bitflag enum has a constructor for every bit value such that
// that bit is set in some other constructor i.e. if a 1 appears somewhere in
// the bits of one of the enum constructors, there is an enum constructor which
// is 1 just in that position.
//
// Otherwise, this code may throw an exception
var maxFlagValue = ((int[])Enum.GetValues(_flagType)).Max();
for (var bitIndex = 1; bitIndex <= maxFlagValue; bitIndex = bitIndex << 1)
{
if ((bitIndex & flags) == bitIndex)
{
var flagName = Enum.GetName(_flagType, bitIndex);
if (flagName == null)
{
throw new FlagSerializerException($"No bitflag corresponding to bit {bitIndex} in {_flagType}, but it was set anyways.");
}
flagNames.Add(flagName);
}
}
return flagNames;
}
}
internal sealed class FlagSerializerException : Exception
{
public FlagSerializerException(string message) : base(message)
{
}
}
///
/// int representation in terms of some constant enum constructors.
///
public class WithConstantRepresentation : WithFormat
{
private Type _constantType;
public Type ConstantType => _constantType;
private YamlConstantSerializer _serializer;
public WithConstantRepresentation(Type constantType)
{
_constantType = constantType;
_serializer = new YamlConstantSerializer(_constantType, this);
}
public override YamlObjectSerializer.TypeSerializer GetYamlSerializer()
{
return _serializer;
}
public override Type Format => typeof(string);
public override int FromCustomFormat(object obj)
{
return (int)Enum.Parse(_constantType, (string)obj);
}
public int FromCustomFormatText(string text)
{
if (Enum.TryParse(_constantType, text, out var val))
{
return (int) val;
}
return int.Parse(text, CultureInfo.InvariantCulture);
}
public override object ToCustomFormat(int value)
{
var constantName = Enum.GetName(_constantType, value);
if (constantName == null)
{
throw new ConstantSerializerException($"No constant corresponding to value {value} in {_constantType}.");
}
return constantName;
}
}
internal sealed class ConstantSerializerException : Exception
{
public ConstantSerializerException(string message) : base(message)
{
}
}
}