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) { } } }