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.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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 + " ");
|
||||
|
||||
Reference in New Issue
Block a user