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;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing; using System.Drawing;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
@@ -34,38 +35,44 @@ namespace OpenRA
public static void Load(object self, MiniYaml my) 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; object val;
if (kv.Value != null)
val = kv.Value(kv.Key.Name, kv.Key.FieldType, my); if (fli.Loader != null)
else if (!TryGetValueFromYaml(kv.Key, my, out val)) val = fli.Loader(my);
else
{
if (md == null)
md = my.ToDictionary();
if (!TryGetValueFromYaml(fli.YamlName, fli.Field, md, out val))
continue; 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; 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; 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; 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() public static T Load<T>(MiniYaml y) where T : new()
@@ -76,27 +83,31 @@ namespace OpenRA
} }
static readonly object[] NoIndexes = { }; 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 != null)
{ {
if (!field.HasAttribute<FieldFromYamlKeyAttribute>()) var sa = field.GetCustomAttributes<SerializeAttribute>(false).DefaultIfEmpty(SerializeAttribute.Default).First();
field.SetValue(self, GetValue(field.Name, field.FieldType, value, field)); if (!sa.FromYamlKey)
field.SetValue(target, GetValue(field.Name, field.FieldType, value, field));
return; return;
} }
var prop = self.GetType().GetProperty(key.Trim()); var prop = target.GetType().GetProperty(key, flags);
if (prop != null) if (prop != null)
{ {
if (!prop.HasAttribute<FieldFromYamlKeyAttribute>()) var sa = prop.GetCustomAttributes<SerializeAttribute>(false).DefaultIfEmpty(SerializeAttribute.Default).First();
prop.SetValue(self, GetValue(prop.Name, prop.PropertyType, value, prop), NoIndexes); if (!sa.FromYamlKey)
prop.SetValue(target, GetValue(prop.Name, prop.PropertyType, value, prop), NoIndexes);
return; return;
} }
UnknownFieldAction(key.Trim(), self.GetType()); UnknownFieldAction(key, target.GetType());
} }
public static T GetValue<T>(string field, string value) public static T GetValue<T>(string field, string value)
@@ -364,6 +375,22 @@ namespace OpenRA
return InvalidValueAction(value, fieldType, fieldName); 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); UnknownFieldAction("[Type] {0}".F(value), fieldType);
return null; return null;
} }
@@ -378,49 +405,95 @@ namespace OpenRA
return InvalidValueAction(p, fieldType, field); 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); public sealed class FieldLoadInfo
static Dictionary<FieldInfo, Func<string, Type, MiniYaml, object>> GetTypeLoadInfo(Type type)
{ {
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 field = ff;
var ignore = field.GetCustomAttributes<IgnoreAttribute>(false);
var loadUsing = field.GetCustomAttributes<LoadUsingAttribute>(false); var sa = field.GetCustomAttributes<SerializeAttribute>(false).DefaultIfEmpty(SerializeAttribute.Default).First();
var fromYamlKey = field.GetCustomAttributes<FieldFromYamlKeyAttribute>(false); if (!sa.Serialize)
if (loadUsing.Length != 0) continue;
ret[field] = (_1, fieldType, yaml) => loadUsing[0].LoaderFunc(field)(yaml);
else if (fromYamlKey.Length != 0) var yamlName = string.IsNullOrEmpty(sa.YamlName) ? field.Name : sa.YamlName;
ret[field] = (f, ft, yaml) => GetValue(f, ft, yaml.Value, field);
else if (ignore.Length == 0) var loader = sa.GetLoader(type);
ret[field] = null; 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; return ret;
} }
[AttributeUsage(AttributeTargets.Field)] [AttributeUsage(AttributeTargets.Field)]
public sealed class IgnoreAttribute : Attribute { } public sealed class IgnoreAttribute : SerializeAttribute
{
public IgnoreAttribute()
: base(false) { }
}
[AttributeUsage(AttributeTargets.Field)] [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) public LoadUsingAttribute(string loader)
{ {
Loader = 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; public static readonly SerializeAttribute Default = new SerializeAttribute(true);
if (loaderFuncCache == null)
loaderFuncCache = (Func<MiniYaml, object>)Delegate.CreateDelegate(typeof(Func<MiniYaml, object>), field.DeclaringType.GetMethod(Loader, BindingFlag)); public bool IsDefault { get { return this == Default; } }
return loaderFuncCache;
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)] [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class TranslateAttribute : Attribute { } 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. // mirrors DescriptionAttribute from System.ComponentModel but we dont want to have to use that everywhere.
public sealed class DescAttribute : Attribute public sealed class DescAttribute : Attribute

View File

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

View File

@@ -112,7 +112,7 @@ namespace OpenRA
} }
catch (ArgumentException ex) 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; return ret;

View File

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