Make FieldLoader/FieldSaver able to work with private fields
* Unify the FieldLoader-related attributes (Ignore, LoadUsing,
FieldFromYamlKey) into a new 'Serialize' attribute. Keep the
previous attributes as shortcuts/aliases to the new attribute.
* Add 'YamlName' field to the new 'Serialize' attribute to allow
yaml names different from the field names. Example:
[Serialize(YamlName = "Width")]
private int widthExpression = "0";
* The FieldLoader will treat private fields like public now and
will try to load their values. The FieldSaver gets an optional
parameter specifying whether to save private fields by default.
The 'Serialize' attribute can be used to fine-tune individual
fields.
* Updated the traits documentation exporter to write the yaml
name if it has been overriden.
This commit is contained in:
@@ -34,38 +34,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))
|
||||
continue;
|
||||
|
||||
kv.Key.SetValue(self, 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;
|
||||
}
|
||||
|
||||
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 +82,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)
|
||||
@@ -378,49 +388,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
|
||||
{
|
||||
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)
|
||||
{
|
||||
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;
|
||||
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 +498,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
|
||||
|
||||
Reference in New Issue
Block a user