Exemple #1
0
        /// <exception cref="UnsupportedTypeException">
        /// Thrown when no case can map <paramref name="type" /> to <paramref name="schema" />.
        /// </exception>
        /// <inheritdoc />
        public virtual Expression BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context)
        {
            var exceptions = new List <Exception>();

            foreach (var @case in Cases)
            {
                var result = @case.BuildExpression(type, schema, context);

                if (result.Expression != null)
                {
                    return(result.Expression);
                }

                exceptions.AddRange(result.Exceptions);
            }

            throw new UnsupportedTypeException(type, $"No deserializer builder case could be applied to {type}.", new AggregateException(exceptions));
        }
Exemple #2
0
        /// <summary>
        /// Builds a <see cref="BinaryDeserializer{T}" /> for a <see cref="StringSchema" />.
        /// </summary>
        /// <returns>
        /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="schema" />
        /// is a <see cref="StringSchema" />; an unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" />
        /// otherwise.
        /// </returns>
        /// <exception cref="UnsupportedTypeException">
        /// Thrown when <see cref="string" /> cannot be converted to <paramref name="type" />.
        /// </exception>
        /// <inheritdoc />
        public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context)
        {
            if (schema is StringSchema stringSchema)
            {
                var readString = typeof(BinaryReader)
                                 .GetMethod(nameof(BinaryReader.ReadString), Type.EmptyTypes);

                try
                {
                    return(BinaryDeserializerBuilderCaseResult.FromExpression(
                               BuildConversion(Expression.Call(context.Reader, readString), type)));
                }
                catch (InvalidOperationException exception)
                {
                    throw new UnsupportedTypeException(type, $"Failed to map {stringSchema} to {type}.", exception);
                }
            }
            else
            {
                return(BinaryDeserializerBuilderCaseResult.FromException(new UnsupportedSchemaException(schema, $"{nameof(BinaryStringDeserializerBuilderCase)} can only be applied to {nameof(StringSchema)}s.")));
            }
        }
        /// <summary>
        /// Builds a <see cref="BinaryDeserializer{T}" /> for a <see cref="RecordSchema" />.
        /// </summary>
        /// <returns>
        /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="type" />
        /// is not an array or primitive type and <paramref name="schema" /> is a <see cref="RecordSchema" />;
        /// an unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" /> otherwise.
        /// </returns>
        /// <inheritdoc />
        public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context)
        {
            if (schema is RecordSchema recordSchema)
            {
                var underlying = Nullable.GetUnderlyingType(type) ?? type;

                if (!underlying.IsArray && !underlying.IsPrimitive)
                {
                    // since record deserialization is potentially recursive, create a top-level
                    // reference:
                    var parameter = Expression.Parameter(
                        Expression.GetDelegateType(context.Reader.Type.MakeByRefType(), underlying));

                    if (!context.References.TryGetValue((recordSchema, type), out var reference))
                    {
                        context.References.Add((recordSchema, type), reference = parameter);
                    }

                    // then build/set the delegate if it hasn’t been built yet:
                    if (parameter == reference)
                    {
                        Expression expression;

                        if (GetRecordConstructor(underlying, recordSchema) is ConstructorInfo constructor)
                        {
                            var parameters = constructor.GetParameters();

                            // map constructor parameters to fields:
                            var mapping = recordSchema.Fields
                                          .Select(field =>
                            {
                                // there will be a match or we wouldn’t have made it this far:
                                var match     = parameters.Single(parameter => IsMatch(field, parameter.Name));
                                var parameter = Expression.Parameter(match.ParameterType);

                                return(
                                    Match: match,
                                    Parameter: parameter,
                                    Assignment: (Expression)Expression.Assign(
                                        parameter,
                                        DeserializerBuilder.BuildExpression(match.ParameterType, field.Type, context)));
                            })
                                          .ToDictionary(r => r.Match, r => (r.Parameter, r.Assignment));

                            expression = Expression.Block(
                                mapping
                                .Select(d => d.Value.Parameter),
                                mapping
                                .Select(d => d.Value.Assignment)
                                .Concat(new[]
                            {
                                Expression.New(
                                    constructor,
                                    parameters
                                    .Select(parameter => mapping.ContainsKey(parameter)
                                                    ? (Expression)mapping[parameter].Parameter
                                                    : Expression.Constant(parameter.DefaultValue))),
                            }));
                        }
                        else
                        {
                            var members = underlying.GetMembers(MemberVisibility);

                            // support dynamic deserialization:
                            var value = Expression.Parameter(
                                underlying.IsAssignableFrom(typeof(ExpandoObject))
                                    ? typeof(ExpandoObject)
                                    : underlying);

                            expression = Expression.Block(
                                new[] { value },
                                new[] { (Expression)Expression.Assign(value, Expression.New(value.Type)) }
                                .Concat(recordSchema.Fields.Select(field =>
                            {
                                var match = members.SingleOrDefault(member => IsMatch(field, member));

                                Expression expression;

                                if (match == null)
                                {
                                    // always deserialize fields to advance the reader:
                                    expression = DeserializerBuilder.BuildExpression(typeof(object), field.Type, context);

                                    // fall back to a dynamic setter if the value supports it:
                                    if (typeof(IDynamicMetaObjectProvider).IsAssignableFrom(value.Type))
                                    {
                                        var flags  = CSharpBinderFlags.None;
                                        var infos  = new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) };
                                        var binder = Binder.SetMember(flags, field.Name, value.Type, infos);
                                        expression = Expression.Dynamic(binder, typeof(void), value, expression);
                                    }
                                }
                                else
                                {
                                    Expression inner;

                                    try
                                    {
                                        inner = DeserializerBuilder.BuildExpression(
                                            match switch
                                        {
                                            FieldInfo fieldMatch => fieldMatch.FieldType,
                                            PropertyInfo propertyMatch => propertyMatch.PropertyType,
                                            MemberInfo unknown => throw new InvalidOperationException($"Record fields can only be mapped to fields and properties."),
                                        },
                                            field.Type,
                                            context);
                                    }
                                    catch (Exception exception)
                                    {
                                        throw new UnsupportedTypeException(type, $"The {match.Name} member on {type} could not be mapped to the {field.Name} field on {recordSchema.FullName}.", exception);
                                    }

                                    expression = Expression.Assign(
                                        Expression.PropertyOrField(value, match.Name),
                                        inner);
                                }

                                return(expression);
                            }))
        /// <summary>
        /// Builds a <see cref="BinaryDeserializer{T}" /> for a <see cref="DurationLogicalType" />.
        /// </summary>
        /// <returns>
        /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="schema" />
        /// has a <see cref="DurationLogicalType" />; an unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" />
        /// otherwise.
        /// </returns>
        /// <exception cref="UnsupportedSchemaException">
        /// Thrown when <paramref name="schema" /> is not a <see cref="FixedSchema" /> with size
        /// <c>12</c>.
        /// </exception>
        /// <exception cref="UnsupportedTypeException">
        /// Thrown when <see cref="TimeSpan" /> cannot be converted to <paramref name="type" />.
        /// </exception>
        /// <inheritdoc />
        public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context)
        {
            if (schema.LogicalType is DurationLogicalType)
            {
                if (!(schema is FixedSchema fixedSchema && fixedSchema.Size == DurationLogicalType.DurationSize))
                {
                    throw new UnsupportedSchemaException(schema);
                }

                var readFixed = typeof(BinaryReader)
                                .GetMethod(nameof(BinaryReader.ReadFixed), new[] { typeof(int) });

                Expression read = Expression.Call(context.Reader, readFixed, Expression.Constant(4));

                if (!BitConverter.IsLittleEndian)
                {
                    var buffer  = Expression.Variable(read.Type);
                    var reverse = typeof(Array)
                                  .GetMethod(nameof(Array.Reverse), new[] { typeof(Array) });

                    read = Expression.Block(
                        new[] { buffer },
                        Expression.Assign(buffer, read),
                        Expression.Call(null, reverse, Expression.Convert(buffer, typeof(Array))),
                        buffer);
                }

                var toUInt32 = typeof(BitConverter)
                               .GetMethod(nameof(BitConverter.ToUInt32), new[] { typeof(byte[]), typeof(int) });

                read = Expression.ConvertChecked(
                    Expression.Call(null, toUInt32, read, Expression.Constant(0)),
                    typeof(long));

                var exceptionConstructor = typeof(OverflowException)
                                           .GetConstructor(new[] { typeof(string) });

                var timeSpanConstructor = typeof(TimeSpan)
                                          .GetConstructor(new[] { typeof(long) });

                try
                {
                    return(BinaryDeserializerBuilderCaseResult.FromExpression(
                               BuildConversion(
                                   Expression.Block(
                                       Expression.IfThen(
                                           Expression.NotEqual(read, Expression.Constant(0L)),
                                           Expression.Throw(
                                               Expression.New(
                                                   exceptionConstructor,
                                                   Expression.Constant($"Durations containing months cannot be accurately deserialized to a {nameof(TimeSpan)}.")))),
                                       Expression.New(
                                           timeSpanConstructor,
                                           Expression.AddChecked(
                                               Expression.MultiplyChecked(read, Expression.Constant(TimeSpan.TicksPerDay)),
                                               Expression.MultiplyChecked(read, Expression.Constant(TimeSpan.TicksPerMillisecond))))),
                                   type)));
                }
                catch (InvalidOperationException exception)
                {
                    throw new UnsupportedTypeException(type, $"Failed to map {schema} to {type}.", exception);
                }
            }
            else
            {
                return(BinaryDeserializerBuilderCaseResult.FromException(new UnsupportedSchemaException(schema, $"{nameof(BinaryDurationDeserializerBuilderCase)} can only be applied schemas with a {nameof(DurationLogicalType)}.")));
            }
        }
        /// <summary>
        /// Builds a <see cref="BinaryDeserializer{T}" /> for a <see cref="TimestampLogicalType" />.
        /// </summary>
        /// <returns>
        /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="schema" />
        /// has a <see cref="TimestampLogicalType" />; an unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" />
        /// otherwise.
        /// </returns>
        /// <exception cref="UnsupportedSchemaException">
        /// Thrown when <paramref name="schema" /> is not a <see cref="LongSchema" /> or when
        /// <paramref name="schema" /> does not have a <see cref="MicrosecondTimestampLogicalType" />
        /// or a <see cref="MillisecondTimestampLogicalType" />.
        /// </exception>
        /// <exception cref="UnsupportedTypeException">
        /// Thrown when <see cref="DateTime" /> cannot be converted to <paramref name="type" />.
        /// </exception>
        /// <inheritdoc />
        public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context)
        {
            if (schema.LogicalType is TimestampLogicalType)
            {
                if (schema is not LongSchema)
                {
                    throw new UnsupportedSchemaException(schema, $"{nameof(TimestampLogicalType)} deserializers can only be built for {nameof(LongSchema)}s.");
                }

                var factor = schema.LogicalType switch
                {
                    MicrosecondTimestampLogicalType => TimeSpan.TicksPerMillisecond / 1000,
                    MillisecondTimestampLogicalType => TimeSpan.TicksPerMillisecond,
                    _ => throw new UnsupportedSchemaException(schema, $"{schema.LogicalType} is not a supported {nameof(TimestampLogicalType)}."),
                };

                var readInteger = typeof(BinaryReader)
                                  .GetMethod(nameof(BinaryReader.ReadInteger), Type.EmptyTypes);

                Expression expression = Expression.Call(context.Reader, readInteger);

                var addTicks = typeof(DateTime)
                               .GetMethod(nameof(DateTime.AddTicks), new[] { typeof(long) });

                try
                {
                    // return Epoch.AddTicks(value * factor);
                    return(BinaryDeserializerBuilderCaseResult.FromExpression(
                               BuildConversion(
                                   Expression.Call(
                                       Expression.Constant(Epoch),
                                       addTicks,
                                       Expression.Multiply(expression, Expression.Constant(factor))),
                                   type)));
                }
                catch (InvalidOperationException exception)
                {
                    throw new UnsupportedTypeException(type, $"Failed to map {schema} to {type}.", exception);
                }
            }
            else
            {
                return(BinaryDeserializerBuilderCaseResult.FromException(new UnsupportedSchemaException(schema, $"{nameof(BinaryTimestampDeserializerBuilderCase)} can only be applied to schemas with a {nameof(TimestampLogicalType)}.")));
            }
        }
Exemple #6
0
        /// <summary>
        /// Builds a <see cref="BinaryDeserializer{T}" /> for a <see cref="UnionSchema" />.
        /// </summary>
        /// <returns>
        /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="schema" />
        /// is a <see cref="UnionSchema" />; an unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" />
        /// otherwise.
        /// </returns>
        /// <exception cref="UnsupportedSchemaException">
        /// Thrown when <paramref name="schema" /> has no <see cref="UnionSchema.Schemas" />.
        /// </exception>
        /// <exception cref="UnsupportedTypeException">
        /// Thrown when <paramref name="type" /> cannot be mapped to each <see cref="Schema" /> in
        /// <paramref name="schema" />.
        /// </exception>
        /// <inheritdoc />
        public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context)
        {
            if (schema is UnionSchema unionSchema)
            {
                if (unionSchema.Schemas.Count < 1)
                {
                    throw new UnsupportedSchemaException(schema, "A deserializer cannot be built for an empty union.");
                }

                var readInteger = typeof(BinaryReader)
                                  .GetMethod(nameof(BinaryReader.ReadInteger), Type.EmptyTypes);

                Expression expression = Expression.Call(context.Reader, readInteger);

                // create a mapping for each schema in the union:
                var cases = unionSchema.Schemas.Select((child, index) =>
                {
                    var selected   = SelectType(type, child);
                    var underlying = Nullable.GetUnderlyingType(selected);

                    if (child is NullSchema && selected.IsValueType && underlying == null)
                    {
                        throw new UnsupportedTypeException(type, $"A deserializer for {unionSchema} cannot be built for {selected} because it contains {nameof(NullSchema)}.");
                    }

                    var @case = DeserializerBuilder.BuildExpression(selected, child, context);

                    return(Expression.SwitchCase(
                               BuildConversion(@case, type),
                               Expression.Constant((long)index)));
                });

                var position = typeof(BinaryReader)
                               .GetProperty(nameof(BinaryReader.Index));

                var exceptionConstructor = typeof(InvalidEncodingException)
                                           .GetConstructor(new[] { typeof(long), typeof(string), typeof(Exception) });

                try
                {
                    // generate a switch on the index:
                    return(BinaryDeserializerBuilderCaseResult.FromExpression(
                               Expression.Switch(
                                   expression,
                                   Expression.Throw(
                                       Expression.New(
                                           exceptionConstructor,
                                           Expression.Property(context.Reader, position),
                                           Expression.Constant($"Invalid union index; expected a value in [0-{unionSchema.Schemas.Count}). This may indicate invalid encoding earlier in the stream."),
                                           Expression.Constant(null, typeof(Exception))),
                                       type),
                                   cases.ToArray())));
                }
                catch (InvalidOperationException exception)
                {
                    throw new UnsupportedTypeException(type, $"Failed to map {unionSchema} to {type}.", exception);
                }
            }
            else
            {
                return(BinaryDeserializerBuilderCaseResult.FromException(new UnsupportedSchemaException(schema, $"{nameof(BinaryUnionDeserializerBuilderCase)} can only be applied to {nameof(UnionSchema)}s.")));
            }
        }
        /// <summary>
        /// Builds a <see cref="BinaryDeserializer{T}" /> for a <see cref="MapSchema" />.
        /// </summary>
        /// <returns>
        /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="type" />
        /// is a dictionary type and <paramref name="schema" /> is a <see cref="MapSchema" />; an
        /// unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" /> otherwise.
        /// </returns>
        /// <exception cref="UnsupportedTypeException">
        /// Thrown when <paramref name="type" /> is not assignable from any supported dictionary
        /// <see cref="Type" /> and does not have a constructor that can be used to instantiate it.
        /// </exception>
        /// <inheritdoc />
        public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context)
        {
            if (schema is MapSchema mapSchema)
            {
                var dictionaryTypes = GetDictionaryTypes(type);

                if (dictionaryTypes is not null || type == typeof(object))
                {
                    // support dynamic mapping:
                    var keyType   = dictionaryTypes?.Key ?? typeof(string);
                    var valueType = dictionaryTypes?.Value ?? typeof(object);

                    var instantiateDictionary = BuildIntermediateDictionary(type, keyType, valueType);

                    var readInteger = typeof(BinaryReader)
                                      .GetMethod(nameof(BinaryReader.ReadInteger), Type.EmptyTypes);

                    var readKey = DeserializerBuilder
                                  .BuildExpression(keyType, new StringSchema(), context);

                    var readValue = DeserializerBuilder
                                    .BuildExpression(valueType, mapSchema.Value, context);

                    var dictionary = Expression.Parameter(instantiateDictionary.Type);
                    var index      = Expression.Variable(typeof(long));
                    var size       = Expression.Variable(typeof(long));
                    var outer      = Expression.Label();
                    var inner      = Expression.Label();

                    var add = dictionary.Type
                              .GetMethod("Add", new[] { readKey.Type, readValue.Type });

                    // var dictionary = new ...;
                    //
                    // outer: while (true)
                    // {
                    //     var size = reader.ReadInteger();
                    //
                    //     // if the block is empty, the map is complete:
                    //     if (size == 0L) break outer;
                    //
                    //     // if the block size is negative, the number of bytes in the block
                    //     // follows, so read and discard:
                    //     if (size < 0L)
                    //     {
                    //          size *= -1L;
                    //          reader.ReadInteger();
                    //     }
                    //
                    //     var index = 0;
                    //
                    //     inner: while (true)
                    //     {
                    //         // primitive for:
                    //         if (index++ == size) break inner;
                    //
                    //         dictionary.Add(..., ...);
                    //     }
                    // }
                    //
                    // return dictionary;
                    Expression expression = Expression.Block(
                        new[] { dictionary, index, size },
                        Expression.Assign(dictionary, instantiateDictionary),
                        Expression.Loop(
                            Expression.Block(
                                Expression.Assign(size, Expression.Call(context.Reader, readInteger)),
                                Expression.IfThen(
                                    Expression.Equal(size, Expression.Constant(0L)),
                                    Expression.Break(outer)),
                                Expression.IfThen(
                                    Expression.LessThan(size, Expression.Constant(0L)),
                                    Expression.Block(
                                        Expression.MultiplyAssign(size, Expression.Constant(-1L)),
                                        Expression.Call(context.Reader, readInteger))),
                                Expression.Assign(index, Expression.Constant(0L)),
                                Expression.Loop(
                                    Expression.Block(
                                        Expression.IfThen(
                                            Expression.Equal(Expression.PostIncrementAssign(index), size),
                                            Expression.Break(inner)),
                                        Expression.Call(dictionary, add, readKey, readValue)),
                                    inner)),
                            outer),
                        dictionary);

                    if (!type.IsAssignableFrom(expression.Type) && GetDictionaryConstructor(type) is ConstructorInfo constructor)
                    {
                        expression = Expression.New(constructor, expression);
                    }

                    try
                    {
                        return(BinaryDeserializerBuilderCaseResult.FromExpression(
                                   BuildConversion(expression, type)));
                    }
                    catch (InvalidOperationException exception)
                    {
                        throw new UnsupportedTypeException(type, $"Failed to map {mapSchema} to {type}.", exception);
                    }
                }
                else
                {
                    return(BinaryDeserializerBuilderCaseResult.FromException(new UnsupportedTypeException(type, $"{nameof(BinaryMapDeserializerBuilderCase)} can only be applied to dictionary types.")));
                }
            }
Exemple #8
0
        /// <summary>
        /// Builds a <see cref="BinaryDeserializer{T}" /> for a <see cref="FixedSchema" />.
        /// </summary>
        /// <returns>
        /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="schema" />
        /// is a <see cref="FixedSchema" />; an unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" />
        /// otherwise.
        /// </returns>
        /// <exception cref="UnsupportedTypeException">
        /// Thrown when <see cref="T:System.Byte[]" /> cannot be converted to <paramref name="type" />.
        /// </exception>
        /// <inheritdoc />
        public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context)
        {
            if (schema is FixedSchema fixedSchema)
            {
                var readFixed = typeof(BinaryReader)
                                .GetMethod(nameof(BinaryReader.ReadFixed), new[] { typeof(int) });

                try
                {
                    return(BinaryDeserializerBuilderCaseResult.FromExpression(
                               BuildConversion(
                                   Expression.Call(
                                       context.Reader,
                                       readFixed,
                                       Expression.Constant(fixedSchema.Size)),
                                   type)));
                }
                catch (InvalidOperationException exception)
                {
                    throw new UnsupportedTypeException(type, $"Failed to map {fixedSchema} to {type}.", exception);
                }
            }
            else
            {
                return(BinaryDeserializerBuilderCaseResult.FromException(new UnsupportedSchemaException(schema, $"{nameof(BinaryFixedDeserializerBuilderCase)} can only be applied to {nameof(FixedSchema)}s.")));
            }
        }
        /// <summary>
        /// Builds a <see cref="BinaryDeserializer{T}" /> for an <see cref="EnumSchema" />.
        /// </summary>
        /// <returns>
        /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="schema" />
        /// is an <see cref="EnumSchema" />; an unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" />
        /// otherwise.
        /// </returns>
        /// <exception cref="UnsupportedTypeException">
        /// Thrown when <paramref name="type" /> is an enum type without a matching member for each
        /// symbol in <paramref name="schema" /> or when <see cref="string" /> cannot be converted
        /// to <paramref name="type" />.
        /// </exception>
        /// <inheritdoc />
        public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context)
        {
            if (schema is EnumSchema enumSchema)
            {
                var readInteger = typeof(BinaryReader)
                                  .GetMethod(nameof(BinaryReader.ReadInteger), Type.EmptyTypes);

                Expression expression = Expression.ConvertChecked(
                    Expression.Call(context.Reader, readInteger),
                    typeof(int));

                var underlying = Nullable.GetUnderlyingType(type) ?? type;

                // enum fields will always be public static, so no need to expose binding flags:
                var fields = underlying.GetFields(BindingFlags.Public | BindingFlags.Static);

                var cases = underlying.IsEnum
                    ? enumSchema.Symbols
                            .Select((symbol, index) =>
                {
                    var match = fields.SingleOrDefault(field => IsMatch(symbol, field));

                    if (enumSchema.Default != null)
                    {
                        match ??= fields.SingleOrDefault(field => IsMatch(enumSchema.Default, field));
                    }

                    if (match == null)
                    {
                        throw new UnsupportedTypeException(type, $"{type} has no value that matches {symbol} and no default value is defined.");
                    }

                    return(Expression.SwitchCase(
                               BuildConversion(Expression.Constant(Enum.Parse(underlying, match.Name)), type),
                               Expression.Constant(index)));
                })
                    : enumSchema.Symbols
                            .Select((symbol, index) =>
                {
                    return(Expression.SwitchCase(
                               BuildConversion(Expression.Constant(symbol), type),
                               Expression.Constant(index)));
                });

                var position = typeof(BinaryReader)
                               .GetProperty(nameof(BinaryReader.Index))
                               .GetGetMethod();

                var exceptionConstructor = typeof(InvalidEncodingException)
                                           .GetConstructor(new[] { typeof(long), typeof(string), typeof(Exception) });

                try
                {
                    // generate a switch on the index:
                    return(BinaryDeserializerBuilderCaseResult.FromExpression(
                               Expression.Switch(
                                   expression,
                                   Expression.Throw(
                                       Expression.New(
                                           exceptionConstructor,
                                           Expression.Property(context.Reader, position),
                                           Expression.Constant($"Invalid enum index; expected a value in [0-{enumSchema.Symbols.Count}). This may indicate invalid encoding earlier in the stream."),
                                           Expression.Constant(null, typeof(Exception))),
                                       type),
                                   cases.ToArray())));
                }
                catch (InvalidOperationException exception)
                {
                    throw new UnsupportedTypeException(type, $"Failed to map {enumSchema} to {type}.", exception);
                }
            }
            else
            {
                return(BinaryDeserializerBuilderCaseResult.FromException(new UnsupportedSchemaException(schema, $"{nameof(BinaryEnumDeserializerBuilderCase)} can only be applied to {nameof(EnumSchema)}s.")));
            }
        }
Exemple #10
0
        /// <summary>
        /// Builds a <see cref="BinaryDeserializer{T}" /> for an <see cref="ArraySchema" />.
        /// </summary>
        /// <returns>
        /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="type" />
        /// is an enumerable type and <paramref name="schema" /> is an <see cref="ArraySchema" />;
        /// an unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" /> otherwise.
        /// </returns>
        /// <exception cref="UnsupportedTypeException">
        /// Thrown when <paramref name="type" /> is not assignable from any supported array or
        /// collection <see cref="Type" /> and does not have a constructor that can be used to
        /// instantiate it.
        /// </exception>
        /// <inheritdoc />
        public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context)
        {
            if (schema is ArraySchema arraySchema)
            {
                var enumerableType = GetEnumerableType(type);

                if (enumerableType is not null || type == typeof(object))
                {
                    // support dynamic mapping:
                    var itemType = enumerableType ?? typeof(object);

                    var instantiateCollection = BuildIntermediateCollection(type, itemType);

                    var readInteger = typeof(BinaryReader)
                                      .GetMethod(nameof(BinaryReader.ReadInteger), Type.EmptyTypes);

                    var readItem = DeserializerBuilder
                                   .BuildExpression(itemType, arraySchema.Item, context);

                    var collection = Expression.Parameter(instantiateCollection.Type);
                    var index      = Expression.Variable(typeof(long));
                    var size       = Expression.Variable(typeof(long));
                    var outer      = Expression.Label();
                    var inner      = Expression.Label();

                    var add = collection.Type
                              .GetMethod("Add", new[] { readItem.Type });

                    // var collection = new List<T>();
                    //
                    // outer: while (true)
                    // {
                    //     var size = reader.ReadInteger();
                    //
                    //     // if the block is empty, the array is complete:
                    //     if (size == 0L) break outer;
                    //
                    //     // if the block size is negative, the number of bytes in the block
                    //     // follows, so read and discard:
                    //     if (size < 0L)
                    //     {
                    //          size *= -1L;
                    //          reader.ReadInteger();
                    //     }
                    //
                    //     var index = 0;
                    //
                    //     inner: while (true)
                    //     {
                    //         // primitive for:
                    //         if (index++ == size) break inner;
                    //
                    //         collection.Add(...);
                    //     }
                    // }
                    //
                    // return collection;
                    Expression expression = Expression.Block(
                        new[] { collection, index, size },
                        Expression.Assign(collection, instantiateCollection),
                        Expression.Loop(
                            Expression.Block(
                                Expression.Assign(size, Expression.Call(context.Reader, readInteger)),
                                Expression.IfThen(
                                    Expression.Equal(size, Expression.Constant(0L)),
                                    Expression.Break(outer)),
                                Expression.IfThen(
                                    Expression.LessThan(size, Expression.Constant(0L)),
                                    Expression.Block(
                                        Expression.MultiplyAssign(size, Expression.Constant(-1L)),
                                        Expression.Call(context.Reader, readInteger))),
                                Expression.Assign(index, Expression.Constant(0L)),
                                Expression.Loop(
                                    Expression.Block(
                                        Expression.IfThen(
                                            Expression.Equal(Expression.PostIncrementAssign(index), size),
                                            Expression.Break(inner)),
                                        Expression.Call(collection, add, readItem)),
                                    inner)),
                            outer),
                        collection);

                    if (!type.IsAssignableFrom(expression.Type) && GetCollectionConstructor(type) is ConstructorInfo constructor)
                    {
                        expression = Expression.New(constructor, expression);
                    }

                    try
                    {
                        return(BinaryDeserializerBuilderCaseResult.FromExpression(
                                   BuildConversion(expression, type)));
                    }
                    catch (InvalidOperationException exception)
                    {
                        throw new UnsupportedTypeException(type, $"Failed to map {arraySchema} to {type}.", exception);
                    }
                }
                else
                {
                    return(BinaryDeserializerBuilderCaseResult.FromException(new UnsupportedTypeException(type, $"{nameof(BinaryArrayDeserializerBuilderCase)} can only be applied to enumerable types.")));
                }
            }
Exemple #11
0
 /// <summary>
 /// Builds a <see cref="BinaryDeserializer{T}" /> for a <see cref="NullSchema" />.
 /// </summary>
 /// <returns>
 /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="schema" />
 /// is a <see cref="NullSchema" />; an unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" />
 /// otherwise.
 /// </returns>
 /// <inheritdoc />
 public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context)
 {
     if (schema is NullSchema)
     {
         return(BinaryDeserializerBuilderCaseResult.FromExpression(
                    Expression.Default(type)));
     }
     else
     {
         return(BinaryDeserializerBuilderCaseResult.FromException(new UnsupportedSchemaException(schema, $"{nameof(BinaryNullDeserializerBuilderCase)} can only be applied to {nameof(NullSchema)}s.")));
     }
 }
Exemple #12
0
        /// <summary>
        /// Builds a <see cref="BinaryDeserializer{T}" /> for a <see cref="DecimalLogicalType" />.
        /// </summary>
        /// <returns>
        /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="schema" />
        /// has a <see cref="DecimalLogicalType" />; an unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" />
        /// otherwise.
        /// </returns>
        /// <exception cref="UnsupportedSchemaException">
        /// Thrown when <paramref name="schema" /> is not a <see cref="BytesSchema" /> or a
        /// <see cref="FixedSchema "/>.
        /// </exception>
        /// <exception cref="UnsupportedTypeException">
        /// Thrown when <see cref="decimal" /> cannot be converted to <paramref name="type" />.
        /// </exception>
        /// <inheritdoc />
        public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context)
        {
            if (schema.LogicalType is DecimalLogicalType decimalLogicalType)
            {
                var precision = decimalLogicalType.Precision;
                var scale     = decimalLogicalType.Scale;

                Expression expression;

                // figure out the size:
                if (schema is BytesSchema)
                {
                    var readBytes = typeof(BinaryReader)
                                    .GetMethod(nameof(BinaryReader.ReadBytes), Type.EmptyTypes);

                    expression = Expression.Call(context.Reader, readBytes);
                }
                else if (schema is FixedSchema fixedSchema)
                {
                    var readFixed = typeof(BinaryReader)
                                    .GetMethod(nameof(BinaryReader.ReadFixed), new[] { typeof(long) });

                    expression = Expression.Call(context.Reader, readFixed, Expression.Constant((long)fixedSchema.Size));
                }
                else
                {
                    throw new UnsupportedSchemaException(schema);
                }

                // declare variables for in-place transformation:
                var bytes     = Expression.Variable(typeof(byte[]));
                var remainder = Expression.Variable(typeof(BigInteger));

                var divide = typeof(BigInteger)
                             .GetMethod(nameof(BigInteger.DivRem), new[] { typeof(BigInteger), typeof(BigInteger), typeof(BigInteger).MakeByRefType() });

                var integerConstructor = typeof(BigInteger)
                                         .GetConstructor(new[] { typeof(byte[]) });

                var reverse = typeof(Array)
                              .GetMethod(nameof(Array.Reverse), new[] { typeof(Array) });

                // var bytes = ...;
                //
                // // BigInteger is little-endian, so reverse:
                // Array.Reverse(bytes);
                //
                // var whole = BigInteger.DivRem(new BigInteger(bytes), BigInteger.Pow(10, scale), out var remainder);
                // var fraction = (decimal)remainder / (decimal)Math.Pow(10, scale);
                //
                // return whole + fraction;
                expression = Expression.Block(
                    new[] { bytes, remainder },
                    Expression.Assign(bytes, expression),
                    Expression.Call(null, reverse, bytes),
                    Expression.Add(
                        Expression.ConvertChecked(
                            Expression.Call(
                                null,
                                divide,
                                Expression.New(integerConstructor, bytes),
                                Expression.Constant(BigInteger.Pow(10, scale)),
                                remainder),
                            typeof(decimal)),
                        Expression.Divide(
                            Expression.ConvertChecked(remainder, typeof(decimal)),
                            Expression.Constant((decimal)Math.Pow(10, scale)))));

                try
                {
                    return(BinaryDeserializerBuilderCaseResult.FromExpression(
                               BuildConversion(expression, type)));
                }
                catch (InvalidOperationException exception)
                {
                    throw new UnsupportedTypeException(type, $"Failed to map {schema} to {type}.", exception);
                }
            }
            else
            {
                return(BinaryDeserializerBuilderCaseResult.FromException(new UnsupportedSchemaException(schema, $"{nameof(BinaryDecimalDeserializerBuilderCase)} can only be applied schemas with a {nameof(DecimalLogicalType)}.")));
            }
        }