/// <summary> /// Formats and adds a ParserTarget to a ConfigNode /// </summary> private static void SetValue(ParserTarget parserTarget, MemberInfo memberInfo, Object reference, ref ConfigNode node) { // Get the value behind the MemberInfo Object value = Tools.GetValue(memberInfo, reference); if (value == null) { return; } // Format the value String formattedValue = Tools.FormatParsable(value); if (formattedValue == null) { return; } formattedValue = Localizer.Format(formattedValue); // Get a description String description = Tools.GetDescription(memberInfo); // Add it to the config if (String.IsNullOrEmpty(description)) { node.AddValue(parserTarget.FieldName, formattedValue); } else { node.AddValue(parserTarget.FieldName, formattedValue, description); } }
/// <summary> /// Enabels or disables the value editor for a Parser Target /// </summary> private void ToggleValueEditor(ParserTarget target, MemberInfo member, Boolean active) { if (ValueEditors.ContainsKey(target.FieldName) && ValueEditors[target.FieldName].IsOpen) { if (active) { ValueEditors[target.FieldName].Show(); } else { ValueEditors[target.FieldName].Hide(); } } else if (ValueEditors.ContainsKey(target.FieldName)) { if (active) { ValueEditors[target.FieldName].Open(); } else { ValueEditors.Remove(target.FieldName); } } else { Type editorType = GetValueEditor(Tools.MemberType(member)); if (editorType != null) { ValueEditor editor = CreateValueEditor(editorType, target, member); editor.Open(); ValueEditors.Add(target.FieldName, editor); } } }
/// <summary> /// Creates a new instance of a value editor /// </summary> private ValueEditor CreateValueEditor(Type editorType, ParserTarget target, MemberInfo member) { return((ValueEditor)Activator.CreateInstance(editorType, Info.Body.transform.name + " - " + target.FieldName, Info.GetValue, new Func <Object>(() => Tools.GetValue(member, Info.Value)), new Action <Object>(s => Tools.SetValue(member, Info.Value, s)))); }
/// <summary> /// Toggles a subeditor for a ParserTargetCollection /// </summary> private void ToggleCollectionEditor(ParserTarget target, MemberInfo member, Boolean active) { if (Children.ContainsKey(target.FieldName) && Children[target.FieldName].IsOpen) { if (active) { Children[target.FieldName].Show(); } else { Children[target.FieldName].Hide(); } } else if (Children.ContainsKey(target.FieldName)) { if (active) { Children[target.FieldName].Open(); } else { Children.Remove(target.FieldName); } } else { KopernicusEditor editor = new CollectionEditor(() => Tools.GetValue(member, Info.Value), v => Tools.SetValue(member, Info.Value, v), Info.Body, Info.Body.transform.name + " - " + target.FieldName, member, target as ParserTargetCollection, () => Info.Value); editor.Open(); Children.Add(target.FieldName, editor); } }
/// <summary> /// Adds a single value ParserTarget to the ConfigNode tree /// </summary> private static void ProcessSingleValue(ParserTarget parserTarget, MemberInfo memberInfo, Object reference, ref ConfigNode node) { // Get the value of the MemberInfo Object value = Tools.GetValue(memberInfo, reference); if (value == null) { return; } // Is this a value or a node? ConfigType configType = Tools.GetConfigType(value.GetType()); if (configType == ConfigType.Value) { SetValue(parserTarget, memberInfo, reference, ref node); } else { // Create the new node String name = parserTarget.FieldName; if (parserTarget.NameSignificance == NameSignificance.Type) { name += ":" + value.GetType().Name; } ConfigNode valueNode; // Get a description String description = Tools.GetDescription(memberInfo); // Add it to the config if (String.IsNullOrEmpty(description)) { valueNode = node.AddNode(name); } else { valueNode = node.AddNode(name, description); } WriteToConfig(value, ref valueNode); } }
private static void WriteToConfig(Object value, ref ConfigNode node) { // If the value can export to config node directly if (value is IConfigNodeWritable) { ConfigNode values = ((IConfigNodeWritable)value).ValueToNode(); if (values != null) { node.AddData(values); } return; } // Get all ParserTargets from the object Dictionary <ParserTarget, MemberInfo> parserTargets = Tools.GetParserTargets(value.GetType()); // Export all found targets foreach (KeyValuePair <ParserTarget, MemberInfo> keyValuePair in parserTargets) { ParserTarget parserTarget = keyValuePair.Key; MemberInfo memberInfo = keyValuePair.Value; // Is this value hidden? if (Tools.HasAttribute <KittopiaHideOption>(memberInfo) && !Tools.GetAttributes <KittopiaHideOption>(memberInfo)[0].export) { continue; } // Is this a collection or a single value? if (Tools.IsCollection(parserTarget)) { ProcessCollection(parserTarget, memberInfo, value, ref node); } else { ProcessSingleValue(parserTarget, memberInfo, value, ref node); } } }
/** * Load data for ParserTarget field or property from a configuration node * * @param member Member to load data for * @param o Instance of the object which owns member * @param node Configuration node from which to load data **/ public static void LoadObjectMemberFromConfigurationNode(MemberInfo member, object o, ConfigNode node, bool getChilds = true) { // Get the parser target, only one is allowed so it will be first ParserTarget target = (member.GetCustomAttributes(typeof(ParserTarget), true) as ParserTarget[]) [0]; // Figure out if this field exists and if we care bool isNode = node.HasNode(target.fieldName); bool isValue = node.HasValue(target.fieldName); // Obtain the type the member is (can only be field or property) Type targetType = null; object targetValue = null; if (member.MemberType == MemberTypes.Field) { targetType = (member as FieldInfo).FieldType; if (getChilds) { targetValue = (member as FieldInfo).GetValue(o); } else { targetValue = null; } } else { targetType = (member as PropertyInfo).PropertyType; try { if ((member as PropertyInfo).CanRead && getChilds) { targetValue = (member as PropertyInfo).GetValue(o, null); } else { targetValue = null; } } catch (Exception) { } } // If there was no data found for this node if (!isNode && !isValue) { if (!target.optional && !(target.allowMerge && targetValue != null)) { // Error - non optional field is missing throw new ParserTargetMissingException("Missing non-optional field: " + o.GetType() + "." + target.fieldName); } // Nothing to do, so DONT return! return; } // If this object is a value (attempt no merge here) if (isValue) { // The node value string nodeValue = node.GetValue(target.fieldName); // Merge all values of the node if (target.getAll != null) { nodeValue = String.Join(target.getAll, node.GetValues(target.fieldName)); } // If the target is a string, it works natively if (targetType.Equals(typeof(string))) { targetValue = nodeValue; } // Figure out if this object is a parsable type else if (typeof(IParsable).IsAssignableFrom(targetType)) { // Create a new object IParsable targetParsable = (IParsable)Activator.CreateInstance(targetType); targetParsable.SetFromString(nodeValue); targetValue = targetParsable; } // Throw exception or print error else { return; } } // If this object is a node (potentially merge) else { // If the target type is a ConfigNode, this works natively if (targetType == typeof(ConfigNode)) { targetValue = node.GetNode(target.fieldName); } // Check for Ranges else if (targetType == typeof(NumericParser <Double>)) { targetValue = new NumericParser <Double>((Double)CreateObjectFromConfigNode <Range>(node.GetNode(target.fieldName))); } // We need to get an instance of the object we are trying to populate // If we are not allowed to merge, or the object does not exist, make a new instance else if (targetValue == null || !target.allowMerge) { targetValue = CreateObjectFromConfigNode(targetType, node.GetNode(target.fieldName), target.getChild); } // Otherwise we can merge this value else { LoadObjectFromConfigurationNode(targetValue, node.GetNode(target.fieldName), target.getChild); } } // If the member type is a field, set the value if (member.MemberType == MemberTypes.Field) { (member as FieldInfo).SetValue(o, targetValue); } // If the member wasn't a field, it must be a property. If the property is writable, set it. else if ((member as PropertyInfo).CanWrite) { (member as PropertyInfo).SetValue(o, targetValue, null); } }
/** * Load data for ParserTarget field or property from a configuration node * * @param member Member to load data for * @param o Instance of the object which owns member * @param node Configuration node from which to load data **/ public static void LoadObjectMemberFromConfigurationNode(MemberInfo member, object o, ConfigNode node, bool getChilds = true) { // Get the parser target, only one is allowed so it will be first ParserTarget target = (member.GetCustomAttributes((typeof(ParserTarget)), true) as ParserTarget[]) [0]; // Figure out if this field exists and if we care bool isNode = node.HasNode(target.fieldName); bool isValue = node.HasValue(target.fieldName); // Obtain the type the member is (can only be field or property) Type targetType = null; object targetValue = null; if (member.MemberType == MemberTypes.Field) { targetType = (member as FieldInfo).FieldType; if (getChilds) { targetValue = (member as FieldInfo).GetValue(o); } else { targetValue = null; } } else { targetType = (member as PropertyInfo).PropertyType; try { if ((member as PropertyInfo).CanRead && getChilds) { targetValue = (member as PropertyInfo).GetValue(o, null); } else { targetValue = null; } } catch (Exception e) { // Ignore runtime getters if (!e.StackTrace.Contains(".get_")) { Debug.LogException(e); } } } Logger.Active.Log("Parsing Target " + target.fieldName + " in (" + o.GetType() + ") as (" + targetType + ")"); // If there was no data found for this node if (!isNode && !isValue) { if (!target.optional && !(target.allowMerge && targetValue != null)) { // Error - non optional field is missing throw new ParserTargetMissingException("Missing non-optional field: " + o.GetType() + "." + target.fieldName); } // Nothing to do, so DONT return! return; } // Does this node have a required config source type (and if so, check if valid) RequireConfigType[] attributes = member.GetCustomAttributes(typeof(RequireConfigType), true) as RequireConfigType[]; if (attributes.Length > 0) { if ((attributes[0].type == ConfigType.Node && !isNode) || (attributes[0].type == ConfigType.Value && !isValue)) { throw new ParserTargetTypeMismatchException(target.fieldName + " requires config value of " + attributes[0].type); } } // If this object is a value (attempt no merge here) if (isValue) { // The node value string nodeValue = node.GetValue(target.fieldName); // Merge all values of the node if (target.getAll != null) { nodeValue = String.Join(target.getAll, node.GetValues(target.fieldName)); } // If the target is a string, it works natively if (targetType.Equals(typeof(string))) { targetValue = nodeValue; } // Figure out if this object is a parsable type else if ((typeof(IParsable)).IsAssignableFrom(targetType)) { // Create a new object IParsable targetParsable = (IParsable)Activator.CreateInstance(targetType); targetParsable.SetFromString(nodeValue); targetValue = targetParsable; } // Throw exception or print error else { Logger.Active.Log("[Kopernicus]: Configuration.Parser: ParserTarget \"" + target.fieldName + "\" is a non parsable type: " + targetType); return; } } // If this object is a node (potentially merge) else if (isNode) { // If the target type is a ConfigNode, this works natively if (targetType.Equals(typeof(ConfigNode))) { targetValue = node.GetNode(target.fieldName); } // We need to get an instance of the object we are trying to populate // If we are not allowed to merge, or the object does not exist, make a new instance else if (targetValue == null || !target.allowMerge) { targetValue = CreateObjectFromConfigNode(targetType, node.GetNode(target.fieldName), target.getChild); } // Otherwise we can merge this value else { LoadObjectFromConfigurationNode(targetValue, node.GetNode(target.fieldName), target.getChild); } } // If the member type is a field, set the value if (member.MemberType == MemberTypes.Field) { (member as FieldInfo).SetValue(o, targetValue); } // If the member wasn't a field, it must be a property. If the property is writable, set it. else if ((member as PropertyInfo).CanWrite) { (member as PropertyInfo).SetValue(o, targetValue, null); } }
/// <summary> /// Adds a multi-value ParserTarget to the ConfigNode tree /// </summary> private static void ProcessCollection(ParserTarget parserTarget, MemberInfo memberInfo, Object reference, ref ConfigNode node) { // Get the type of the collection Type memberType = Tools.MemberType(memberInfo); // Is the collection a dictionary? if (typeof(IDictionary).IsAssignableFrom(memberType)) { IDictionary dictionary = Tools.GetValue(memberInfo, reference) as IDictionary; // Is the dictionary null? if (dictionary == null) { return; } // Create the new ConfigNode ConfigNode targetNode = null; // Iterate over the elements of the dictionary foreach (DictionaryEntry value in dictionary) { // Null-Check if (value.Key == null || value.Value == null) { continue; } // Create the node if neccessary if (targetNode == null) { targetNode = node; if (parserTarget.FieldName != "self") { // Get a description String description = Tools.GetDescription(memberInfo); // Add it to the config if (String.IsNullOrEmpty(description)) { targetNode = node.AddNode(parserTarget.FieldName); } else { targetNode = node.AddNode(parserTarget.FieldName, description); } } } // The first generic type has to be ConfigType.Value, figure out the type of the second one ConfigType type = Tools.GetConfigType(value.Value.GetType()); // If it is a node, add it to the node if (type == ConfigType.Node) { ConfigNode valueNode = targetNode.AddNode(Tools.FormatParsable(value.Key)); WriteToConfig(value.Value, ref valueNode); } else { targetNode.AddValue(Tools.FormatParsable(value.Key), Tools.FormatParsable(value.Value)); } } } else if (typeof(IList).IsAssignableFrom(memberType)) { IList list = Tools.GetValue(memberInfo, reference) as IList; // Is the dictionary null? if (list == null) { return; } // Create the new ConfigNode ConfigNode targetNode = null; // Iterate over the elements of the list foreach (Object value in list) { // Null-Check if (value == null) { continue; } // Create the node if neccessary if (targetNode == null) { targetNode = node; if (parserTarget.FieldName != "self") { // Get a description String description = Tools.GetDescription(memberInfo); // Add it to the config if (String.IsNullOrEmpty(description)) { targetNode = node.AddNode(parserTarget.FieldName); } else { targetNode = node.AddNode(parserTarget.FieldName, description); } } } // Figure out the config type of type ConfigType type = Tools.GetConfigType(value.GetType()); // If it is a node, add it to the node if (type == ConfigType.Node) { String name = "Value"; if (parserTarget.NameSignificance == NameSignificance.Key) { name = parserTarget.Key; } if (parserTarget.NameSignificance == NameSignificance.Type) { name = value.GetType().Name; } ConfigNode valueNode = targetNode.AddNode(name); WriteToConfig(value, ref valueNode); } else { String name = "value"; if (parserTarget.NameSignificance == NameSignificance.Key) { name = parserTarget.Key; } if (parserTarget.NameSignificance == NameSignificance.Type) { name = value.GetType().Name; } targetNode.AddValue(name, Tools.FormatParsable(value)); } } } }
/// <summary> /// Displays a parser target in the window /// </summary> private void DisplayParserTarget(ParserTarget target, MemberInfo member) { // Don't display hidden options if (Tools.HasAttribute <KittopiaHideOption>(member) && !Tools.GetAttributes <KittopiaHideOption>(member)[0].Show) { return; } // Check if the object is a list or a single element if (!Tools.IsCollection(target)) { GUIHorizontalLayout(() => { GUILabel(target.FieldName, modifier: Alignment(TextAlignmentOptions.Left)); GUIFlexibleSpace(); GUILabel(target.Optional ? "Optional" : "Required", modifier: Alignment(TextAlignmentOptions.Right) .And(TextColor(Color.gray))); }); // Display a KittopiaDescription String description = Tools.GetDescription(member); if (!String.IsNullOrEmpty(description)) { GUISpace(2f); GUILabel(description, modifier: TextColor(Color.gray)); } GUISpace(5f); // If the element is loaded from a config node, it needs a new window // Simply values can be edited with an inline textfield ConfigType configType = Tools.GetConfigType(Tools.GetValue(member, Info.Value)?.GetType() ?? Tools.MemberType(member)); if (configType == ConfigType.Node) { GUIHorizontalLayout(() => { // Edit Button GUIToggleButton( () => Children.ContainsKey(target.FieldName) && Children[target.FieldName].IsVisible, "Edit", e => ToggleSubEditor(target, member, e), -1f, 25f, Enabled <DialogGUIToggleButton>(() => Tools.GetValue(member, Info.Value) != null)); // Button to create or destroy the element if (Tools.HasAttribute <KittopiaUntouchable>(member)) { GUIButton(Tools.GetValue(member, Info.Value) != null ? "x" : "+", () => { }, 25f, 25f, false, () => { }, Enabled <DialogGUIButton>(() => false)); } else { GUIButton(() => Tools.GetValue(member, Info.Value) != null ? "x" : "+", () => { Object value = Tools.GetValue(member, Info.Value); if (value != null) { Tools.Destruct(value); Tools.SetValue(member, Info.Value, null); SetValue(null); } else { Object v = Tools.Construct(Tools.MemberType(member), Info.Body); Tools.SetValue(member, Info.Value, v); SetValue(v); } }, 25f, 25f, false, () => { }); } }); } else { GUIHorizontalLayout(() => { GUITextInput("", false, Int32.MaxValue, s => Tools.ApplyInput(member, s, Info.Value), () => Tools.FormatParsable(Tools.GetValue(member, Info.Value)) ?? "", TMP_InputField.ContentType.Standard, 25f); GUIToggleButton( () => ValueEditors.ContainsKey(target.FieldName) && ValueEditors[target.FieldName].IsVisible, ">", e => ToggleValueEditor(target, member, e), 25f, 25f, Enabled <DialogGUIToggleButton>(() => HasValueEditor(member))); }); } } else { ParserTargetCollection collection = (ParserTargetCollection)target; // Is the collection parsing a subnode, or this one? if (collection.FieldName != "self") { GUIHorizontalLayout(() => { GUILabel(target.FieldName, modifier: Alignment(TextAlignmentOptions.Left)); GUIFlexibleSpace(); GUILabel(target.Optional ? "Optional" : "Required", modifier: Alignment(TextAlignmentOptions.Right) .And(TextColor(Color.gray))); }); // Display a KittopiaDescription String description = Tools.GetDescription(member); if (!String.IsNullOrEmpty(description)) { GUISpace(2f); GUILabel(description, modifier: TextColor(Color.gray)); } GUISpace(5f); GUIHorizontalLayout(() => { // Edit Button GUIToggleButton(() => Children.ContainsKey(target.FieldName) && Children[target.FieldName].IsVisible, "Edit", e => ToggleCollectionEditor(target, member, e), -1f, 25f, Enabled <DialogGUIToggleButton>(() => Tools.GetValue(member, Info.Value) != null)); // Button to create or destroy the element if (Tools.HasAttribute <KittopiaUntouchable>(member)) { GUIButton(Tools.GetValue(member, Info.Value) != null ? "x" : "+", () => { }, 25f, 25f, false, () => { }, Enabled <DialogGUIButton>(() => false)); } else { GUIButton(() => Tools.GetValue(member, Info.Value) != null ? "x" : "+", () => { Object value = Tools.GetValue(member, Info.Value); if (value != null) { Tools.Destruct(value); Tools.SetValue(member, Info.Value, null); SetValue(null); } else { Object v = Tools.Construct(Tools.MemberType(member), Info.Body); Tools.SetValue(member, Info.Value, v); SetValue(v); } }, 25f, 25f, false, () => { }); } }); } else { new CollectionEditor(() => Tools.GetValue(member, Info.Value), v => Tools.SetValue(member, Info.Value, v), Info.Body, Info.Body.transform.name + " - " + target.FieldName, member, (ParserTargetCollection)target, () => Info.Value).DisplayCollection(); } } if (target.FieldName != "self") { // Use a box as a seperator GUISpace(5f); GUIBox(-1f, 1f, () => { }); GUISpace(5f); } }
/// <summary> /// Returns whether the ParserTarget describes a single value or a range of values /// </summary> public static Boolean IsCollection(ParserTarget parserTarget) { return(parserTarget is ParserTargetCollection); }
/// <inheritdoc /> public ParseableValidationAttribute(string propertyName, ParserTarget target, IFormatProvider culture) : base(propertyName) { switch (target) { case ParserTarget.Int8: ErrorGenerator = o => { var val = PreprocessValue <string>(o); if (val is null) { var s = o as string; return(o is null ? string.Empty : sbyte.TryParse(s, NumberStyles.Any, culture, out _) ? string.Empty : $"Incorrect format for {target}"); } return(val); }; break; case ParserTarget.Int16: ErrorGenerator = o => { var val = PreprocessValue <string>(o); if (val is null) { var s = o as string; return(o is null ? string.Empty : short.TryParse(s, NumberStyles.Any, culture, out _) ? string.Empty : $"Incorrect format for {target}"); } return(val); }; break; case ParserTarget.Int32: ErrorGenerator = o => { var val = PreprocessValue <string>(o); if (val is null) { var s = o as string; return(o is null ? string.Empty : int.TryParse(s, NumberStyles.Any, culture, out _) ? string.Empty : $"Incorrect format for {target}"); } return(val); }; break; case ParserTarget.Int64: ErrorGenerator = o => { var val = PreprocessValue <string>(o); if (val is null) { var s = o as string; return(o is null ? string.Empty : long.TryParse(s, NumberStyles.Any, culture, out _) ? string.Empty : $"Incorrect format for {target}"); } return(val); }; break; case ParserTarget.UInt8: ErrorGenerator = o => { var val = PreprocessValue <string>(o); if (val is null) { var s = o as string; return(o is null ? string.Empty : byte.TryParse(s, NumberStyles.Any, culture, out _) ? string.Empty : $"Incorrect format for {target}"); } return(val); }; break; case ParserTarget.UInt16: ErrorGenerator = o => { var val = PreprocessValue <string>(o); if (val is null) { var s = o as string; return(o is null ? string.Empty : ushort.TryParse(s, NumberStyles.Any, culture, out _) ? string.Empty : $"Incorrect format for {target}"); } return(val); }; break; case ParserTarget.UInt32: ErrorGenerator = o => { var val = PreprocessValue <string>(o); if (val is null) { var s = o as string; return(o is null ? string.Empty : uint.TryParse(s, NumberStyles.Any, culture, out _) ? string.Empty : $"Incorrect format for {target}"); } return(val); }; break; case ParserTarget.UInt64: ErrorGenerator = o => { var val = PreprocessValue <string>(o); if (val is null) { var s = o as string; return(o is null ? string.Empty : ulong.TryParse(s, NumberStyles.Any, culture, out _) ? string.Empty : $"Incorrect format for {target}"); } return(val); }; break; case ParserTarget.Single: ErrorGenerator = o => { var val = PreprocessValue <string>(o); if (val is null) { var s = o as string; return(o is null ? string.Empty : float.TryParse(s, NumberStyles.Any, culture, out _) ? string.Empty : $"Incorrect format for {target}"); } return(val); }; break; case ParserTarget.Double: ErrorGenerator = o => { var val = PreprocessValue <string>(o); if (val is null) { var s = o as string; return(o is null ? string.Empty : double.TryParse(s, NumberStyles.Any, culture, out _) ? string.Empty : $"Incorrect format for {target}"); } return(val); }; break; case ParserTarget.Decimal: ErrorGenerator = o => { var val = PreprocessValue <string>(o); if (val is null) { var s = o as string; return(o is null ? string.Empty : decimal.TryParse(s, NumberStyles.Any, culture, out _) ? string.Empty : $"Incorrect format for {target}"); } return(val); }; break; default: throw new ArgumentOutOfRangeException(nameof(target)); } }
/// <inheritdoc /> public ParseableValidationAttribute(string propertyName, ParserTarget target) : this(propertyName, target, CultureInfo.CurrentCulture) { }