Merge pull request #5943 from pavlos256/fieldloader
Fieldloader and non-public fields
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 + " ");
|
||||||
|
|||||||
Reference in New Issue
Block a user