Merge pull request #5943 from pavlos256/fieldloader

Fieldloader and non-public fields
This commit is contained in:
Paul Chote
2014-07-24 20:29:25 +12:00
4 changed files with 166 additions and 69 deletions

View File

@@ -10,6 +10,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Globalization;
using System.Linq;
@@ -34,38 +35,44 @@ namespace OpenRA
public static void Load(object self, MiniYaml my)
{
var loadDict = typeLoadInfo[self.GetType()];
var loadInfo = typeLoadInfo[self.GetType()];
foreach (var kv in loadDict)
Dictionary<string, MiniYaml> md = null;
foreach (var fli in loadInfo)
{
object val;
if (kv.Value != null)
val = kv.Value(kv.Key.Name, kv.Key.FieldType, my);
else if (!TryGetValueFromYaml(kv.Key, my, out val))
if (fli.Loader != null)
val = fli.Loader(my);
else
{
if (md == null)
md = my.ToDictionary();
if (!TryGetValueFromYaml(fli.YamlName, fli.Field, md, out val))
continue;
}
kv.Key.SetValue(self, val);
fli.Field.SetValue(self, val);
}
}
static bool TryGetValueFromYaml(FieldInfo field, MiniYaml yaml, out object ret)
static bool TryGetValueFromYaml(string yamlName, FieldInfo field, Dictionary<string, MiniYaml> md, out object ret)
{
ret = null;
var n = yaml.Nodes.Where(x => x.Key == field.Name).ToList();
if (n.Count == 0)
MiniYaml yaml;
if (!md.TryGetValue(yamlName, out yaml))
return false;
if (n.Count == 1 && n[0].Value.Nodes.Count == 0)
if (yaml.Nodes.Count == 0)
{
ret = GetValue(field.Name, field.FieldType, n[0].Value.Value, field);
ret = GetValue(field.Name, field.FieldType, yaml.Value, field);
return true;
}
else if (n.Count > 1)
{
throw new InvalidOperationException("The field {0} has multiple definitions:\n{1}"
.F(field.Name, n.Select(m => "\t- " + m.Location).JoinWith("\n")));
}
throw new InvalidOperationException("TryGetValueFromYaml: unable to load field {0} (of type {1})".F(field.Name, field.FieldType));
throw new InvalidOperationException("TryGetValueFromYaml: unable to load field {0} (of type {1})".F(yamlName, field.FieldType));
}
public static T Load<T>(MiniYaml y) where T : new()
@@ -76,27 +83,31 @@ namespace OpenRA
}
static readonly object[] NoIndexes = { };
public static void LoadField(object self, string key, string value)
public static void LoadField(object target, string key, string value)
{
var field = self.GetType().GetField(key.Trim());
const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
key = key.Trim();
var field = target.GetType().GetField(key, flags);
if (field != null)
{
if (!field.HasAttribute<FieldFromYamlKeyAttribute>())
field.SetValue(self, GetValue(field.Name, field.FieldType, value, field));
var sa = field.GetCustomAttributes<SerializeAttribute>(false).DefaultIfEmpty(SerializeAttribute.Default).First();
if (!sa.FromYamlKey)
field.SetValue(target, GetValue(field.Name, field.FieldType, value, field));
return;
}
var prop = self.GetType().GetProperty(key.Trim());
var prop = target.GetType().GetProperty(key, flags);
if (prop != null)
{
if (!prop.HasAttribute<FieldFromYamlKeyAttribute>())
prop.SetValue(self, GetValue(prop.Name, prop.PropertyType, value, prop), NoIndexes);
var sa = prop.GetCustomAttributes<SerializeAttribute>(false).DefaultIfEmpty(SerializeAttribute.Default).First();
if (!sa.FromYamlKey)
prop.SetValue(target, GetValue(prop.Name, prop.PropertyType, value, prop), NoIndexes);
return;
}
UnknownFieldAction(key.Trim(), self.GetType());
UnknownFieldAction(key, target.GetType());
}
public static T GetValue<T>(string field, string value)
@@ -364,6 +375,22 @@ namespace OpenRA
return InvalidValueAction(value, fieldType, fieldName);
}
else
{
var conv = TypeDescriptor.GetConverter(fieldType);
if (conv.CanConvertFrom(typeof(string)))
{
try
{
return conv.ConvertFromInvariantString(value);
}
catch
{
return InvalidValueAction(value, fieldType, fieldName);
}
}
}
UnknownFieldAction("[Type] {0}".F(value), fieldType);
return null;
}
@@ -378,49 +405,95 @@ namespace OpenRA
return InvalidValueAction(p, fieldType, field);
}
static Cache<Type, Dictionary<FieldInfo, Func<string, Type, MiniYaml, object>>> typeLoadInfo = new Cache<Type, Dictionary<FieldInfo, Func<string, Type, MiniYaml, object>>>(GetTypeLoadInfo);
static Dictionary<FieldInfo, Func<string, Type, MiniYaml, object>> GetTypeLoadInfo(Type type)
public sealed class FieldLoadInfo
{
var ret = new Dictionary<FieldInfo, Func<string, Type, MiniYaml, object>>();
public readonly FieldInfo Field;
public readonly SerializeAttribute Attribute;
public readonly string YamlName;
public readonly Func<MiniYaml, object> Loader;
foreach (var ff in type.GetFields())
internal FieldLoadInfo(FieldInfo field, SerializeAttribute attr, string yamlName, Func<MiniYaml, object> loader = null)
{
Field = field;
Attribute = attr;
YamlName = yamlName;
Loader = loader;
}
}
public static IEnumerable<FieldLoadInfo> GetTypeLoadInfo(Type type, bool includePrivateByDefault = false)
{
return typeLoadInfo[type].Where(fli => includePrivateByDefault || fli.Field.IsPublic || (fli.Attribute.Serialize && !fli.Attribute.IsDefault));
}
static Cache<Type, List<FieldLoadInfo>> typeLoadInfo = new Cache<Type, List<FieldLoadInfo>>(BuildTypeLoadInfo);
static List<FieldLoadInfo> BuildTypeLoadInfo(Type type)
{
var ret = new List<FieldLoadInfo>();
foreach (var ff in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
{
var field = ff;
var ignore = field.GetCustomAttributes<IgnoreAttribute>(false);
var loadUsing = field.GetCustomAttributes<LoadUsingAttribute>(false);
var fromYamlKey = field.GetCustomAttributes<FieldFromYamlKeyAttribute>(false);
if (loadUsing.Length != 0)
ret[field] = (_1, fieldType, yaml) => loadUsing[0].LoaderFunc(field)(yaml);
else if (fromYamlKey.Length != 0)
ret[field] = (f, ft, yaml) => GetValue(f, ft, yaml.Value, field);
else if (ignore.Length == 0)
ret[field] = null;
var sa = field.GetCustomAttributes<SerializeAttribute>(false).DefaultIfEmpty(SerializeAttribute.Default).First();
if (!sa.Serialize)
continue;
var yamlName = string.IsNullOrEmpty(sa.YamlName) ? field.Name : sa.YamlName;
var loader = sa.GetLoader(type);
if (loader == null && sa.FromYamlKey)
loader = (yaml) => GetValue(yamlName, field.FieldType, yaml.Value, field);
var fli = new FieldLoadInfo(field, sa, yamlName, loader);
ret.Add(fli);
}
return ret;
}
[AttributeUsage(AttributeTargets.Field)]
public sealed class IgnoreAttribute : Attribute { }
public sealed class IgnoreAttribute : SerializeAttribute
{
public IgnoreAttribute()
: base(false) { }
}
[AttributeUsage(AttributeTargets.Field)]
public sealed class LoadUsingAttribute : Attribute
public sealed class LoadUsingAttribute : SerializeAttribute
{
Func<MiniYaml, object> loaderFuncCache;
public readonly string Loader;
public LoadUsingAttribute(string loader)
{
Loader = loader;
}
}
internal Func<MiniYaml, object> LoaderFunc(FieldInfo field)
[AttributeUsage(AttributeTargets.Field)]
public class SerializeAttribute : Attribute
{
const BindingFlags BindingFlag = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
if (loaderFuncCache == null)
loaderFuncCache = (Func<MiniYaml, object>)Delegate.CreateDelegate(typeof(Func<MiniYaml, object>), field.DeclaringType.GetMethod(Loader, BindingFlag));
return loaderFuncCache;
public static readonly SerializeAttribute Default = new SerializeAttribute(true);
public bool IsDefault { get { return this == Default; } }
public readonly bool Serialize;
public string YamlName;
public string Loader;
public bool FromYamlKey;
public SerializeAttribute(bool serialize = true)
{
Serialize = serialize;
}
internal Func<MiniYaml, object> GetLoader(Type type)
{
const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
if (!string.IsNullOrEmpty(Loader))
return (Func<MiniYaml, object>)Delegate.CreateDelegate(typeof(Func<MiniYaml, object>), type.GetMethod(Loader, flags));
return null;
}
}
@@ -442,7 +515,14 @@ namespace OpenRA
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class TranslateAttribute : Attribute { }
public sealed class FieldFromYamlKeyAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Field)]
public sealed class FieldFromYamlKeyAttribute : FieldLoader.SerializeAttribute
{
public FieldFromYamlKeyAttribute()
{
FromYamlKey = true;
}
}
// mirrors DescriptionAttribute from System.ComponentModel but we dont want to have to use that everywhere.
public sealed class DescAttribute : Attribute

View File

@@ -10,6 +10,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Globalization;
using System.Linq;
@@ -19,31 +20,34 @@ namespace OpenRA
{
public static class FieldSaver
{
public static MiniYaml Save(object o)
public static MiniYaml Save(object o, bool includePrivateByDefault = false)
{
var nodes = new List<MiniYamlNode>();
string root = null;
foreach (var f in o.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance))
foreach (var info in FieldLoader.GetTypeLoadInfo(o.GetType(), includePrivateByDefault))
{
if (f.HasAttribute<FieldFromYamlKeyAttribute>())
root = FormatValue(o, f);
if (info.Attribute.FromYamlKey)
root = FormatValue(o, info.Field);
else
nodes.Add(new MiniYamlNode(f.Name, FormatValue(o, f)));
nodes.Add(new MiniYamlNode(info.YamlName, FormatValue(o, info.Field)));
}
return new MiniYaml(root, nodes);
}
public static MiniYaml SaveDifferences(object o, object from)
public static MiniYaml SaveDifferences(object o, object from, bool includePrivateByDefault = false)
{
if (o.GetType() != from.GetType())
throw new InvalidOperationException("FieldLoader: can't diff objects of different types");
var fields = o.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance)
.Where(f => FormatValue(o, f) != FormatValue(from, f));
var fields = FieldLoader.GetTypeLoadInfo(o.GetType(), includePrivateByDefault)
.Where(info => FormatValue(o, info.Field) != FormatValue(from, info.Field));
return new MiniYaml(null, fields.Select(f => new MiniYamlNode(f.Name, FormatValue(o, f))).ToList());
return new MiniYaml(
null,
fields.Select(info => new MiniYamlNode(info.YamlName, FormatValue(o, info.Field))).ToList()
);
}
public static MiniYamlNode SaveField(object o, string field)
@@ -88,6 +92,19 @@ namespace OpenRA
if (t == typeof(DateTime))
return ((DateTime)v).ToString("yyyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture);
// Try the TypeConverter
var conv = TypeDescriptor.GetConverter(t);
if (conv.CanConvertTo(typeof(string)))
{
try
{
return conv.ConvertToInvariantString(v);
}
catch
{
}
}
return v.ToString();
}

View File

@@ -112,7 +112,7 @@ namespace OpenRA
}
catch (ArgumentException ex)
{
throw new InvalidDataException("Duplicate key `{0}' in {1}".F(y.Key, y.Location), ex);
throw new InvalidDataException("Duplicate key '{0}' in {1}".F(y.Key, y.Location), ex);
}
}
return ret;

View File

@@ -323,18 +323,18 @@ namespace OpenRA.Utility
foreach (var line in traitDescLines)
doc.AppendLine(line);
var fields = t.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
if (!fields.Any())
var infos = FieldLoader.GetTypeLoadInfo(t);
if (!infos.Any())
continue;
doc.AppendLine("<table>");
doc.AppendLine("<tr><th>Property</th><th>Default Value</th><th>Type</th><th>Description</th></tr>");
var liveTraitInfo = Game.modData.ObjectCreator.CreateBasic(t);
foreach (var f in fields)
foreach (var info in infos)
{
var fieldDescLines = f.GetCustomAttributes<DescAttribute>(true).SelectMany(d => d.Lines);
var fieldType = FriendlyTypeName(f.FieldType);
var defaultValue = FieldSaver.SaveField(liveTraitInfo, f.Name).Value.Value;
doc.Append("<tr><td>{0}</td><td>{1}</td><td>{2}</td>".F(f.Name, defaultValue, fieldType));
var fieldDescLines = info.Field.GetCustomAttributes<DescAttribute>(true).SelectMany(d => d.Lines);
var fieldType = FriendlyTypeName(info.Field.FieldType);
var defaultValue = FieldSaver.SaveField(liveTraitInfo, info.Field.Name).Value.Value;
doc.Append("<tr><td>{0}</td><td>{1}</td><td>{2}</td>".F(info.YamlName, defaultValue, fieldType));
doc.Append("<td>");
foreach (var line in fieldDescLines)
doc.Append(line + " ");