Created
October 11, 2013 09:26
-
-
Save yodiz/6932018 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
namespace GLIM.Utilities.Text | |
{ | |
public enum FieldOption | |
{ | |
Required = 0, | |
Optional = 1 | |
} | |
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] | |
public class FixedRecordAttribute : Attribute {} | |
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] | |
public class FixedAttribute : Attribute | |
{ | |
public int Length { get; private set; } | |
public FieldOption Option { get; private set; } | |
public FixedAttribute(int length, FieldOption option = FieldOption.Required) | |
{ | |
Length = length; | |
Option = option; | |
} | |
} | |
/// <summary> | |
/// Parses a line of text into a data-structure decorated with the FixedAttribute. | |
/// The line consist of fields of a fixed length that corisponds to the given data-structure | |
/// </summary> | |
public static class Fixed<T> | |
{ | |
public static T Parse(string line) | |
{ | |
return (T)Fixed.Parse(typeof(T), line); | |
} | |
public static int Length() | |
{ | |
return Fixed.Length(typeof(T)); | |
} | |
} | |
public static class Fixed | |
{ | |
private class FieldInfo | |
{ | |
public readonly FixedAttribute Attr; | |
public readonly Action<object, object> SetValue; | |
public readonly Func<string, object> Convert; | |
public FieldInfo(FixedAttribute attr, Action<object, object> setValue, Func<string, object> converter) | |
{ | |
Attr = attr; | |
SetValue = setValue; | |
Convert = converter; | |
} | |
} | |
public static object Parse(Type ofType, string line) | |
{ | |
return CreateInstance(ofType, line, GetFieldInfo(ofType)); | |
} | |
private static object CreateInstance(Type ofType, string line, IEnumerable<FieldInfo> fields) | |
{ | |
var obj = Activator.CreateInstance(ofType); | |
var current = 0; | |
foreach (var mf in fields) | |
{ | |
var length = mf.Attr.Length; | |
if (mf.Attr.Option == FieldOption.Optional && (current + length > line.Length)) | |
{ | |
//Field is marked as optional and the length given is larger than the length of the line | |
} | |
else | |
{ | |
var value = line.Substring(current, length); | |
var convertedValue = mf.Convert(value); | |
mf.SetValue(obj, convertedValue); | |
} | |
current += length; | |
} | |
return obj; | |
} | |
private static readonly Dictionary<Type, FieldInfo[]> CachedReflection = new Dictionary<Type, FieldInfo[]>(); | |
private static IEnumerable<FieldInfo> GetFieldInfo(Type forType) | |
{ | |
if (!CachedReflection.ContainsKey(forType)) | |
CachedReflection[forType] = ReflectFieldsAndProperties(forType); | |
return CachedReflection[forType]; | |
} | |
private static Func<string, object> ConverterFor(Type type) | |
{ | |
Func<string, object> enumConv = | |
// Its questionable to set the default value of the enum if the value is empty. | |
// A correct behavior would be to throw an error | |
// or have a type representing that the value can be missing | |
// ie. Nullable<EnumType>. | |
value => (value.Trim().Length == 0) ? Activator.CreateInstance(type) : Enum.ToObject(type, Convert.ChangeType(value, Enum.GetUnderlyingType(type))); | |
return type.BaseType == typeof(Enum) ? enumConv : value => Convert.ChangeType(value.Trim(), type); | |
} | |
private static FieldInfo[] ReflectFieldsAndProperties(Type forType) | |
{ | |
var membersWithFixed = | |
forType.GetFields() | |
.Select(m => { | |
var attrs = m.GetCustomAttributes(typeof(FixedAttribute), false).OfType<FixedAttribute>().ToArray(); | |
var fattrs = m.GetCustomAttributes(typeof(FixedRecordAttribute), false).OfType<FixedRecordAttribute>(); | |
if (attrs.Any()) | |
{ | |
var attr = attrs.First(); | |
return new FieldInfo(attr, m.SetValue, ConverterFor(m.FieldType)); | |
} | |
if (fattrs.Any()) | |
{ | |
var length = Length(m.FieldType); | |
return new FieldInfo(new FixedAttribute(length), m.SetValue, s => Parse(m.FieldType, s)); | |
} | |
return null; | |
}).Where(x => x != null); | |
var propertiesWithFixed = | |
forType.GetProperties() | |
.Select(m => { | |
var attrs = m.GetCustomAttributes(typeof(FixedAttribute), false).OfType<FixedAttribute>().ToArray(); | |
var fattrs = m.GetCustomAttributes(typeof(FixedRecordAttribute), false).OfType<FixedRecordAttribute>(); | |
if (attrs.Any()) | |
{ | |
var attr = attrs.First(); | |
return new FieldInfo(attr, m.SetValue, ConverterFor(m.PropertyType)); | |
} | |
if (fattrs.Any()) | |
{ | |
var length = Length(m.PropertyType); | |
return new FieldInfo( | |
new FixedAttribute(length), | |
m.SetValue, | |
s => Parse(m.PropertyType, s)); | |
} | |
return null; | |
}).Where(x => x != null); | |
var fields = membersWithFixed.Concat(propertiesWithFixed).ToArray(); | |
return fields; | |
} | |
public static int Length(Type ofType) | |
{ | |
return GetFieldInfo(ofType).Select(x => x.Attr.Length).Sum(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment