Quasar/Server/Core/ProtoBuf/Serializers/EnumSerializer.cs

310 lines
12 KiB
C#

#if !NO_RUNTIME
using System;
using ProtoBuf.Meta;
#if FEAT_IKVM
using Type = IKVM.Reflection.Type;
using IKVM.Reflection;
#else
using System.Reflection;
#endif
namespace ProtoBuf.Serializers
{
internal sealed class EnumSerializer : IProtoSerializer
{
public struct EnumPair
{
public readonly object RawValue; // note that this is boxing, but I'll live with it
#if !FEAT_IKVM
public readonly Enum TypedValue; // note that this is boxing, but I'll live with it
#endif
public readonly int WireValue;
public EnumPair(int wireValue, object raw, Type type)
{
WireValue = wireValue;
RawValue = raw;
#if !FEAT_IKVM
TypedValue = (Enum) Enum.ToObject(type, raw);
#endif
}
}
private readonly Type enumType;
private readonly EnumPair[] map;
public EnumSerializer(Type enumType, EnumPair[] map)
{
if (enumType == null) throw new ArgumentNullException("enumType");
this.enumType = enumType;
this.map = map;
if (map != null)
{
for (int i = 1; i < map.Length; i++)
for (int j = 0; j < i; j++)
{
if (map[i].WireValue == map[j].WireValue && !Equals(map[i].RawValue, map[j].RawValue))
{
throw new ProtoException("Multiple enums with wire-value " + map[i].WireValue.ToString());
}
if (Equals(map[i].RawValue, map[j].RawValue) && map[i].WireValue != map[j].WireValue)
{
throw new ProtoException("Multiple enums with deserialized-value " + map[i].RawValue);
}
}
}
}
private ProtoTypeCode GetTypeCode()
{
Type type = Helpers.GetUnderlyingType(enumType);
if (type == null) type = enumType;
return Helpers.GetTypeCode(type);
}
public Type ExpectedType
{
get { return enumType; }
}
bool IProtoSerializer.RequiresOldValue
{
get { return false; }
}
bool IProtoSerializer.ReturnsValue
{
get { return true; }
}
#if !FEAT_IKVM
private int EnumToWire(object value)
{
unchecked
{
switch (GetTypeCode())
{
// unbox then convert to int
case ProtoTypeCode.Byte:
return (int) (byte) value;
case ProtoTypeCode.SByte:
return (int) (sbyte) value;
case ProtoTypeCode.Int16:
return (int) (short) value;
case ProtoTypeCode.Int32:
return (int) value;
case ProtoTypeCode.Int64:
return (int) (long) value;
case ProtoTypeCode.UInt16:
return (int) (ushort) value;
case ProtoTypeCode.UInt32:
return (int) (uint) value;
case ProtoTypeCode.UInt64:
return (int) (ulong) value;
default:
throw new InvalidOperationException();
}
}
}
private object WireToEnum(int value)
{
unchecked
{
switch (GetTypeCode())
{
// convert from int then box
case ProtoTypeCode.Byte:
return Enum.ToObject(enumType, (byte) value);
case ProtoTypeCode.SByte:
return Enum.ToObject(enumType, (sbyte) value);
case ProtoTypeCode.Int16:
return Enum.ToObject(enumType, (short) value);
case ProtoTypeCode.Int32:
return Enum.ToObject(enumType, value);
case ProtoTypeCode.Int64:
return Enum.ToObject(enumType, (long) value);
case ProtoTypeCode.UInt16:
return Enum.ToObject(enumType, (ushort) value);
case ProtoTypeCode.UInt32:
return Enum.ToObject(enumType, (uint) value);
case ProtoTypeCode.UInt64:
return Enum.ToObject(enumType, (ulong) value);
default:
throw new InvalidOperationException();
}
}
}
public object Read(object value, ProtoReader source)
{
Helpers.DebugAssert(value == null); // since replaces
int wireValue = source.ReadInt32();
if (map == null)
{
return WireToEnum(wireValue);
}
for (int i = 0; i < map.Length; i++)
{
if (map[i].WireValue == wireValue)
{
return map[i].TypedValue;
}
}
source.ThrowEnumException(ExpectedType, wireValue);
return null; // to make compiler happy
}
public void Write(object value, ProtoWriter dest)
{
if (map == null)
{
ProtoWriter.WriteInt32(EnumToWire(value), dest);
}
else
{
for (int i = 0; i < map.Length; i++)
{
if (object.Equals(map[i].TypedValue, value))
{
ProtoWriter.WriteInt32(map[i].WireValue, dest);
return;
}
}
ProtoWriter.ThrowEnumException(dest, value);
}
}
#endif
#if FEAT_COMPILER
void IProtoSerializer.EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom)
{
ProtoTypeCode typeCode = GetTypeCode();
if (map == null)
{
ctx.LoadValue(valueFrom);
ctx.ConvertToInt32(typeCode, false);
ctx.EmitBasicWrite("WriteInt32", null);
}
else
{
using (Compiler.Local loc = ctx.GetLocalWithValue(ExpectedType, valueFrom))
{
Compiler.CodeLabel @continue = ctx.DefineLabel();
for (int i = 0; i < map.Length; i++)
{
Compiler.CodeLabel tryNextValue = ctx.DefineLabel(), processThisValue = ctx.DefineLabel();
ctx.LoadValue(loc);
WriteEnumValue(ctx, typeCode, map[i].RawValue);
ctx.BranchIfEqual(processThisValue, true);
ctx.Branch(tryNextValue, true);
ctx.MarkLabel(processThisValue);
ctx.LoadValue(map[i].WireValue);
ctx.EmitBasicWrite("WriteInt32", null);
ctx.Branch(@continue, false);
ctx.MarkLabel(tryNextValue);
}
ctx.LoadReaderWriter();
ctx.LoadValue(loc);
ctx.CastToObject(ExpectedType);
ctx.EmitCall(ctx.MapType(typeof(ProtoWriter)).GetMethod("ThrowEnumException"));
ctx.MarkLabel(@continue);
}
}
}
void IProtoSerializer.EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom)
{
ProtoTypeCode typeCode = GetTypeCode();
if (map == null)
{
ctx.EmitBasicRead("ReadInt32", ctx.MapType(typeof(int)));
ctx.ConvertFromInt32(typeCode, false);
}
else
{
int[] wireValues = new int[map.Length];
object[] values = new object[map.Length];
for (int i = 0; i < map.Length; i++)
{
wireValues[i] = map[i].WireValue;
values[i] = map[i].RawValue;
}
using (Compiler.Local result = new Compiler.Local(ctx, ExpectedType))
using (Compiler.Local wireValue = new Compiler.Local(ctx, ctx.MapType(typeof(int))))
{
ctx.EmitBasicRead("ReadInt32", ctx.MapType(typeof(int)));
ctx.StoreValue(wireValue);
Compiler.CodeLabel @continue = ctx.DefineLabel();
foreach (BasicList.Group group in BasicList.GetContiguousGroups(wireValues, values))
{
Compiler.CodeLabel tryNextGroup = ctx.DefineLabel();
int groupItemCount = group.Items.Count;
if (groupItemCount == 1)
{
// discreet group; use an equality test
ctx.LoadValue(wireValue);
ctx.LoadValue(group.First);
Compiler.CodeLabel processThisValue = ctx.DefineLabel();
ctx.BranchIfEqual(processThisValue, true);
ctx.Branch(tryNextGroup, false);
WriteEnumValue(ctx, typeCode, processThisValue, @continue, group.Items[0], @result);
}
else
{
// implement as a jump-table-based switch
ctx.LoadValue(wireValue);
ctx.LoadValue(group.First);
ctx.Subtract(); // jump-tables are zero-based
Compiler.CodeLabel[] jmp = new Compiler.CodeLabel[groupItemCount];
for (int i = 0; i < groupItemCount; i++) {
jmp[i] = ctx.DefineLabel();
}
ctx.Switch(jmp);
// write the default...
ctx.Branch(tryNextGroup, false);
for (int i = 0; i < groupItemCount; i++)
{
WriteEnumValue(ctx, typeCode, jmp[i], @continue, group.Items[i], @result);
}
}
ctx.MarkLabel(tryNextGroup);
}
// throw source.CreateEnumException(ExpectedType, wireValue);
ctx.LoadReaderWriter();
ctx.LoadValue(ExpectedType);
ctx.LoadValue(wireValue);
ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("ThrowEnumException"));
ctx.MarkLabel(@continue);
ctx.LoadValue(result);
}
}
}
private static void WriteEnumValue(Compiler.CompilerContext ctx, ProtoTypeCode typeCode, object value)
{
switch (typeCode)
{
case ProtoTypeCode.Byte: ctx.LoadValue((int)(byte)value); break;
case ProtoTypeCode.SByte: ctx.LoadValue((int)(sbyte)value); break;
case ProtoTypeCode.Int16: ctx.LoadValue((int)(short)value); break;
case ProtoTypeCode.Int32: ctx.LoadValue((int)(int)value); break;
case ProtoTypeCode.Int64: ctx.LoadValue((long)(long)value); break;
case ProtoTypeCode.UInt16: ctx.LoadValue((int)(ushort)value); break;
case ProtoTypeCode.UInt32: ctx.LoadValue((int)(uint)value); break;
case ProtoTypeCode.UInt64: ctx.LoadValue((long)(ulong)value); break;
default: throw new InvalidOperationException();
}
}
private static void WriteEnumValue(Compiler.CompilerContext ctx, ProtoTypeCode typeCode, Compiler.CodeLabel handler, Compiler.CodeLabel @continue, object value, Compiler.Local local)
{
ctx.MarkLabel(handler);
WriteEnumValue(ctx, typeCode, value);
ctx.StoreValue(local);
ctx.Branch(@continue, false); // "continue"
}
#endif
}
}
#endif