/// <summary> /// Ensures the given config node has at least one of the given values. /// </summary> /// <param name="configNode"></param> /// <param name="values"></param> /// <param name="obj"></param> /// <returns></returns> public static bool AtLeastOne(ConfigNode configNode, string[] values, IContractConfiguratorFactory obj) { string output = ""; foreach (string value in values) { if (configNode.HasValue(value)) { return(true); } if (value == values.First()) { output = value; } else if (value == values.Last()) { output += " or " + value; } else { output += ", " + value; } } if (values.Count() == 2) { LoggingUtil.LogError(obj, obj.ErrorPrefix(configNode) + ": Either " + output + " is required."); } else { LoggingUtil.LogError(obj, obj.ErrorPrefix(configNode) + ": One of " + output + " is required."); } return(false); }
/// <summary> /// Performs validation to check if the given config node has values that were not expected. /// </summary> /// <param name="configNode">The ConfigNode to check.</param> /// <param name="obj">IContractConfiguratorFactory object for error reporting</param> /// <returns>Always true, but logs a warning if unexpected keys were found</returns> public static bool ValidateUnexpectedValues(ConfigNode configNode, IContractConfiguratorFactory obj) { bool valid = true; if (!keysFound.ContainsKey(configNode)) { obj.hasWarnings = true; LoggingUtil.LogWarning(obj.GetType(), obj.ErrorPrefix() + ": did not attempt to load values for ConfigNode!"); return(false); } Dictionary <string, int> found = keysFound[configNode]; foreach (ConfigNode.Value pair in configNode.values) { if (!found.ContainsKey(pair.name)) { obj.hasWarnings = true; LoggingUtil.LogWarning(obj.GetType(), obj.ErrorPrefix() + ": unexpected attribute '" + pair.name + "' found, ignored."); } } foreach (ConfigNode child in configNode.nodes) { // Exceptions if (child.name == "PARAMETER" && (obj is ContractType || obj is ParameterFactory) || child.name == "REQUIREMENT" && (obj is ContractType || obj is ParameterFactory || obj is ContractRequirement) || child.name == "BEHAVIOUR" && (obj is ContractType) || child.name == "ORBIT" && (obj is Behaviour.OrbitGeneratorFactory || obj is Behaviour.SpawnVesselFactory || obj is Behaviour.SpawnKerbalFactory)) { continue; } if (!found.ContainsKey(child.name)) { obj.hasWarnings = true; LoggingUtil.LogWarning(obj.GetType(), obj.ErrorPrefix() + ": unexpected child node '" + child.name + "' found, ignored."); } } return(valid); }
/// <summary> /// Validates that the given config node does NOT contain the given value. /// </summary> /// <param name="configNode">The ConfigNode to check.</param> /// <param name="field">The field to exclude</param> /// <param name="obj">IContractConfiguratorFactory object for error reporting</param> /// <returns>Always true, but logs a warning for an unexpected value.</returns> public static bool ValidateExcludedValue(ConfigNode configNode, string field, IContractConfiguratorFactory obj) { if (configNode.HasNode(field) || configNode.HasValue(field)) { LoggingUtil.LogWarning(obj.GetType(), obj.ErrorPrefix() + ": unexpected entry '" + field + "' found, ignored."); } return(true); }
/// <summary> /// Attempts to parse a value from the config node. Validates return values using the /// given function. /// </summary> /// <typeparam name="T">The type of value to convert to.</typeparam> /// <param name="configNode">The ConfigNode to read from.</param> /// <param name="key">The key to examine.</param> /// <param name="setter">Function used to set the output value</param> /// <param name="obj">Factory object for error messages.</param> /// <param name="validation">Validation function to run against the returned value</param> /// <returns>The parsed value (or default value if not found)</returns> public static bool ParseValue <T>(ConfigNode configNode, string key, Action <T> setter, IContractConfiguratorFactory obj, Func <T, bool> validation) { // Check for required value if (!configNode.HasValue(key) && !configNode.HasNode(key)) { LoggingUtil.LogError(obj, obj.ErrorPrefix(configNode) + ": Missing required value '" + key + "'."); return(false); } return(ParseValue <T>(configNode, key, setter, obj, default(T), validation)); }
/// <summary> /// Attempts to parse a value from the config node. Returns a default value if not found. /// </summary> /// <typeparam name="T">The type of value to convert to.</typeparam> /// <param name="configNode">The ConfigNode to read from.</param> /// <param name="key">The key to examine.</param> /// <param name="setter">Function used to set the output value</param> /// <param name="obj">Factory object for error messages.</param> /// <returns>The parsed value (or default value if not found)</returns> public static bool ParseValue <T>(ConfigNode configNode, string key, Action <T> setter, IContractConfiguratorFactory obj) { // Check for required value if (!configNode.HasValue(key) && !configNode.HasNode(key)) { LoggingUtil.LogError(obj, "{0}: Missing required value '{1}'.", obj.ErrorPrefix(configNode), key); return(false); } return(ParseValue <T>(configNode, key, setter, obj, default(T), x => true)); }
/// <summary> /// Checks whether the mandatory field exists, and if not logs and error. Returns true /// only if the validation succeeded. /// </summary> /// <param name="configNode">The ConfigNode to check.</param> /// <param name="field">The child that is expected</param> /// <param name="obj">IContractConfiguratorFactory object for error reporting</param> /// <returns>Whether the validation succeeded, additionally logs an error on failure.</returns> public static bool ValidateMandatoryChild(ConfigNode configNode, string field, IContractConfiguratorFactory obj) { if (!configNode.HasNode(field)) { LoggingUtil.LogError(obj.GetType(), obj.ErrorPrefix() + ": missing required child node '" + field + "'."); return(false); } return(true); }
/// <summary> /// Performs validation to check if the given config node has values that were not expected. /// </summary> /// <param name="configNode">The ConfigNode to check.</param> /// <param name="obj">IContractConfiguratorFactory object for error reporting</param> /// <returns>Always true, but logs a warning if unexpected keys were found</returns> public static bool ValidateUnexpectedValues(ConfigNode configNode, IContractConfiguratorFactory obj) { if (!keysFound.ContainsKey(configNode)) { LoggingUtil.LogWarning(obj.GetType(), obj.ErrorPrefix() + ": did not attempt to load values for ConfigNode!"); return(false); } Dictionary <string, int> found = keysFound[configNode]; foreach (ConfigNode.Value pair in configNode.values) { if (!found.ContainsKey(pair.name)) { LoggingUtil.LogWarning(obj.GetType(), obj.ErrorPrefix() + ": unexpected entry '" + pair.name + "' found, ignored."); } } return(true); }
/* * Attempts to parse a value from the config node. Returns a default value if not found. * Validates return values using the given function. */ public static bool ParseValue <T>(ConfigNode configNode, string key, ref T value, IContractConfiguratorFactory obj, T defaultValue, Func <T, bool> validation) { if (ParseValue <T>(configNode, key, ref value, obj, defaultValue)) { try { if (!validation.Invoke(value)) { // In general, the validation function should throw an exception and give a much better message LoggingUtil.LogError(obj, obj.ErrorPrefix(configNode) + ": The value supplied for " + key + " (" + value + ") is invalid."); return(false); } } catch (Exception e) { LoggingUtil.LogError(obj, obj.ErrorPrefix(configNode) + ": The value supplied for " + key + " (" + value + ") is invalid: " + e.Message); LoggingUtil.LogDebug(obj, e.StackTrace); return(false); } return(true); } return(false); }
/// <summary> /// Ensures that the config node does not have items from the two mutually exclusive groups. /// </summary> /// <param name="configNode">The configNode to verify.</param> /// <param name="group1">The first group of keys.</param> /// <param name="group2">The second group of keys</param> /// <param name="obj">IContractConfiguratorFactory for logging</param> /// <returns>Whether the condition is satisfied</returns> public static bool MutuallyExclusive(ConfigNode configNode, string[] group1, string[] group2, IContractConfiguratorFactory obj) { string group1String = ""; string group2String = ""; bool group1Value = false; bool group2Value = false; foreach (string value in group1) { if (configNode.HasValue(value)) { group1Value = true; } if (value == group1.First()) { group1String = value; } else { group1String += ", " + value; } } foreach (string value in group2) { if (configNode.HasValue(value)) { group2Value = true; } if (value == group2.First()) { group2String = value; } else { group2String += ", " + value; } } if (group1Value && group2Value) { LoggingUtil.LogError(obj, obj.ErrorPrefix(configNode) + ": The values " + group1String + " and " + group2String + " are mutually exclusive."); return(false); } return(true); }
/* * Attempts to parse a value from the config node. */ public static bool ParseValue <T>(ConfigNode configNode, string key, ref T value, IContractConfiguratorFactory obj) { try { value = ParseValue <T>(configNode, key); return(value != null); } catch (Exception e) { LoggingUtil.LogError(obj, obj.ErrorPrefix(configNode) + ": Error parsing " + key + ": " + configNode.id + e.Message); LoggingUtil.LogDebug(obj, e.StackTrace); return(false); } finally { AddFoundKey(configNode, key); } }
/// <summary> /// Parses the child DATA nodes out of the given config node, and returns the parsed values back in dataValues. /// </summary> /// <param name="configNode">The ConfigNode to load child DATA nodes from.</param> /// <param name="obj">The ContractConfigurator object to load from.</param> /// <param name="dataValues"></param> /// <param name="uniquenessChecks"></param> /// <returns></returns> public bool ParseDataNodes(ConfigNode configNode, IContractConfiguratorFactory obj, Dictionary <string, ContractType.DataValueInfo> dataValues, Dictionary <string, UniquenessCheck> uniquenessChecks) { bool valid = true; foreach (ConfigNode data in ConfigNodeUtil.GetChildNodes(configNode, "DATA")) { Type type = null; bool requiredValue = true; bool hidden = true; bool isLiteral = false; string title = ""; ConfigNodeUtil.SetCurrentDataNode(null); valid &= ConfigNodeUtil.ParseValue <Type>(data, "type", x => type = x, obj); valid &= ConfigNodeUtil.ParseValue <bool>(data, "requiredValue", x => requiredValue = x, obj, true); valid &= ConfigNodeUtil.ParseValue <string>(data, "title", x => title = x, obj, ""); valid &= ConfigNodeUtil.ParseValue <bool>(data, "hidden", x => hidden = x, obj, false); valid &= ConfigNodeUtil.ParseValue <bool>(data, "isLiteral", x => isLiteral = x, obj, false); bool doneTitleWarning = false; UniquenessCheck uniquenessCheck = UniquenessCheck.NONE; // Backwards compatibility for Contract Configurator 1.8.3 if (data.HasValue("uniqueValue") || data.HasValue("activeUniqueValue")) { LoggingUtil.LogWarning(this, "The use of uniqueValue and activeUniqueValue is obsolete since Contract Configurator 1.9.0, use uniquenessCheck instead."); bool uniqueValue = false; bool activeUniqueValue = false; valid &= ConfigNodeUtil.ParseValue <bool>(data, "uniqueValue", x => uniqueValue = x, obj, false); valid &= ConfigNodeUtil.ParseValue <bool>(data, "activeUniqueValue", x => activeUniqueValue = x, obj, false); uniquenessCheck = activeUniqueValue ? UniquenessCheck.CONTRACT_ACTIVE : uniqueValue ? UniquenessCheck.CONTRACT_ALL : UniquenessCheck.NONE; } else { valid &= ConfigNodeUtil.ParseValue <UniquenessCheck>(data, "uniquenessCheck", x => uniquenessCheck = x, obj, UniquenessCheck.NONE); } ConfigNodeUtil.SetCurrentDataNode(this); if (type != null) { foreach (ConfigNode.Value pair in data.values) { string name = pair.name; if (name != "type" && name != "title" && name != "hidden" && name != "requiredValue" && name != "uniqueValue" && name != "activeUniqueValue" && name != "uniquenessCheck" && name != "isLiteral") { if (uniquenessCheck != UniquenessCheck.NONE) { uniquenessChecks[name] = uniquenessCheck; } object value = null; // Create the setter function Type actionType = typeof(Action <>).MakeGenericType(type); Delegate del = Delegate.CreateDelegate(actionType, value, typeof(DataNode).GetMethod("NullAction")); // Set the ParseValue method generic MethodInfo method = (isLiteral ? methodParseValueLiteral : methodParseValue).MakeGenericMethod(new Type[] { type }); // Invoke the ParseValue method if (isLiteral) { this[name] = method.Invoke(null, new object[] { data, name, false }); } else { valid &= (bool)method.Invoke(null, new object[] { data, name, del, obj }); } dataValues[name] = new ContractType.DataValueInfo(title, requiredValue, hidden, type); // Recommend a title if (!data.HasValue("title") && requiredValue && !IsDeterministic(name) && !hidden && !doneTitleWarning && !dataValues[name].IsIgnoredType()) { doneTitleWarning = true; LoggingUtil.Log(obj.minVersion >= ContractConfigurator.ENHANCED_UI_VERSION ? LoggingUtil.LogLevel.ERROR : LoggingUtil.LogLevel.WARNING, this, obj.ErrorPrefix() + ": " + name + ": The field 'title' is required in for data node values where 'requiredValue' is true. Alternatively, the attribute 'hidden' can be set to true (but be careful - this can cause player confusion if all lines for the contract type show as 'Met' and the contract isn't generating)."); // Error on newer versions of contract packs if (obj.minVersion >= ContractConfigurator.ENHANCED_UI_VERSION) { valid = false; } } } } } } return(valid); }
/// <summary> /// Attempts to parse a value from the config node. Returns a default value if not found. /// Validates return values using the given function. /// </summary> /// <typeparam name="T">The type of value to convert to.</typeparam> /// <param name="configNode">The ConfigNode to read from.</param> /// <param name="key">The key to examine.</param> /// <param name="setter">Function used to set the output value</param> /// <param name="obj">Factory object for error messages.</param> /// <param name="defaultValue">Default value to use if there is no key in the config node</param> /// <param name="validation">Validation function to run against the returned value</param> /// <returns>The parsed value (or default value if not found)</returns> public static bool ParseValue <T>(ConfigNode configNode, string key, Action <T> setter, IContractConfiguratorFactory obj, T defaultValue, Func <T, bool> validation) { // Initialize the data type of the expression if (currentDataNode != null && !currentDataNode.IsInitialized(key)) { currentDataNode.BlankInit(key, typeof(T)); } bool valid = true; T value = defaultValue; if (configNode.HasValue(key) || configNode.HasNode(key)) { try { // Check whether there's a value if (configNode.HasValue(key) && string.IsNullOrEmpty(configNode.GetValue(key))) { LoggingUtil.LogError(obj, obj.ErrorPrefix(configNode) + ": Required value '" + key + "' is empty."); valid = false; } else { // Load value value = ParseValue <T>(configNode, key, true); } // If value was non-null, run validation if (value != null && (typeof(T) != typeof(string) || ((string)(object)value) != "")) { try { valid = validation.Invoke(value); if (!valid) { // In general, the validation function should throw an exception and give a much better message LoggingUtil.LogError(obj, obj.ErrorPrefix(configNode) + ": A validation error occured while loading the key '" + key + "' with value '" + value + "'."); } } catch (Exception e) { if (e is DataNode.ValueNotInitialized) { throw; } LoggingUtil.LogError(obj, obj.ErrorPrefix(configNode) + ": A validation error occured while loading the key '" + key + "' with value '" + value + "'."); LoggingUtil.LogException(e); valid = false; } } } catch (Exception e) { if (e.GetType() == typeof(DataNode.ValueNotInitialized)) { string dependency = ((DataNode.ValueNotInitialized)e).key; string path = currentDataNode.Path() + key; LoggingUtil.LogVerbose(typeof(ConfigNodeUtil), "Trying to load " + path + ", but " + dependency + " is uninitialized."); // Defer loading this value DeferredLoadObject <T> loadObj = null; if (!deferredLoads.ContainsKey(path) || deferredLoads[path].GetType().GetGenericArguments().First() != typeof(T)) { deferredLoads[path] = new DeferredLoadObject <T>(configNode, key, setter, obj, validation, currentDataNode); } loadObj = (DeferredLoadObject <T>)deferredLoads[path]; // New dependency - try again if (!loadObj.dependencies.Contains(dependency)) { LoggingUtil.LogVerbose(typeof(ConfigNodeUtil), " New dependency, will re-attempt to load later."); loadObj.dependencies.Add(dependency); return(true); } } LoggingUtil.LogError(obj, obj.ErrorPrefix(configNode) + ": Error parsing " + key); // Return immediately on deferred load error if (e.GetType() == typeof(DataNode.ValueNotInitialized)) { DataNode.ValueNotInitialized vni = e as DataNode.ValueNotInitialized; LoggingUtil.LogException(new Exception("Unknown identifier '@" + vni.key + "'.")); return(false); } LoggingUtil.LogException(e); valid = false; } finally { AddFoundKey(configNode, key); } } // Store the value if (currentDataNode != null) { currentDataNode[key] = value; if (!currentDataNode.IsDeterministic(key) && initialLoad) { currentDataNode.DeferredLoads.Add(new DeferredLoadObject <T>(configNode, key, setter, obj, validation, currentDataNode)); } } // Invoke the setter function if (valid) { setter.Invoke(value); } return(valid); }
/// <summary> /// Parses the child DATA nodes out of the given config node, and returns the parsed values back in dataValues. /// </summary> /// <param name="configNode">The ConfigNode to load child DATA nodes from.</param> /// <param name="obj">The ContractConfigurator object to load from.</param> /// <param name="dataValues"></param> /// <param name="uniquenessChecks"></param> /// <returns></returns> public bool ParseDataNodes(ConfigNode configNode, IContractConfiguratorFactory obj, Dictionary<string, ContractType.DataValueInfo> dataValues, Dictionary<string, UniquenessCheck> uniquenessChecks) { bool valid = true; foreach (ConfigNode data in ConfigNodeUtil.GetChildNodes(configNode, "DATA")) { Type type = null; bool requiredValue = true; bool hidden = true; string title = ""; ConfigNodeUtil.SetCurrentDataNode(null); valid &= ConfigNodeUtil.ParseValue<Type>(data, "type", x => type = x, obj); valid &= ConfigNodeUtil.ParseValue<bool>(data, "requiredValue", x => requiredValue = x, obj, true); valid &= ConfigNodeUtil.ParseValue<string>(data, "title", x => title = x, obj, ""); valid &= ConfigNodeUtil.ParseValue<bool>(data, "hidden", x => hidden = x, obj, false); bool doneTitleWarning = false; UniquenessCheck uniquenessCheck = UniquenessCheck.NONE; // Backwards compatibility for Contract Configurator 1.8.3 if (data.HasValue("uniqueValue") || data.HasValue("activeUniqueValue")) { LoggingUtil.LogWarning(this, "The use of uniqueValue and activeUniqueValue is obsolete since Contract Configurator 1.9.0, use uniquenessCheck instead."); bool uniqueValue = false; bool activeUniqueValue = false; valid &= ConfigNodeUtil.ParseValue<bool>(data, "uniqueValue", x => uniqueValue = x, obj, false); valid &= ConfigNodeUtil.ParseValue<bool>(data, "activeUniqueValue", x => activeUniqueValue = x, obj, false); uniquenessCheck = activeUniqueValue ? UniquenessCheck.CONTRACT_ACTIVE : uniqueValue ? UniquenessCheck.CONTRACT_ALL : UniquenessCheck.NONE; } else { valid &= ConfigNodeUtil.ParseValue<UniquenessCheck>(data, "uniquenessCheck", x => uniquenessCheck = x, obj, UniquenessCheck.NONE); } ConfigNodeUtil.SetCurrentDataNode(this); if (type != null) { foreach (ConfigNode.Value pair in data.values) { string name = pair.name; if (name != "type" && name != "title" && name != "hidden" && name != "requiredValue" && name != "uniqueValue" && name != "activeUniqueValue" && name != "uniquenessCheck") { if (uniquenessCheck != UniquenessCheck.NONE) { uniquenessChecks[name] = uniquenessCheck; } object value = null; // Create the setter function Type actionType = typeof(Action<>).MakeGenericType(type); Delegate del = Delegate.CreateDelegate(actionType, value, typeof(DataNode).GetMethod("NullAction")); // Set the ParseValue method generic MethodInfo method = methodParseValue.MakeGenericMethod(new Type[] { type }); // Invoke the ParseValue method valid &= (bool)method.Invoke(null, new object[] { data, name, del, obj }); dataValues[name] = new ContractType.DataValueInfo(title, requiredValue, hidden, type); // Recommend a title if (!data.HasValue("title") && requiredValue && !IsDeterministic(name) && !hidden && !doneTitleWarning && !dataValues[name].IsIgnoredType()) { doneTitleWarning = true; LoggingUtil.Log(obj.minVersion >= ContractConfigurator.ENHANCED_UI_VERSION ? LoggingUtil.LogLevel.ERROR : LoggingUtil.LogLevel.WARNING, this, obj.ErrorPrefix() + ": " + name + ": The field 'title' is required in for data node values where 'requiredValue' is true. Alternatively, the attribute 'hidden' can be set to true (but be careful - this can cause player confusion if all lines for the contract type show as 'Met' and the contract isn't generating)."); // Error on newer versions of contract packs if (obj.minVersion >= ContractConfigurator.ENHANCED_UI_VERSION) { valid = false; } } } } } } return valid; }
protected static T SelectUnique(List <T> input) { // Check if there's no values if (input == null || !input.Any()) { return(default(T)); } // Get details from the base parser ContractType contractType = BaseParser.currentParser.currentDataNode.Root.Factory as ContractType; string key = BaseParser.currentParser.currentKey; DataNode.UniquenessCheck uniquenessCheck = contractType.uniquenessChecks.ContainsKey(key) ? contractType.uniquenessChecks[key] : DataNode.UniquenessCheck.NONE; DataNode dataNode = BaseParser.currentParser.currentDataNode; // Provide warning of a better method if (dataNode != null && dataNode.IsDeterministic(key) && (uniquenessCheck == DataNode.UniquenessCheck.CONTRACT_ALL || uniquenessCheck == DataNode.UniquenessCheck.CONTRACT_ACTIVE)) { IContractConfiguratorFactory factory = BaseParser.currentParser.currentDataNode.Factory; LoggingUtil.LogWarning(factory, factory.ErrorPrefix() + ": Consider using a DATA_EXPAND node instead of the SelectUnique function when the values are deterministic - this will cause the player to see the full set of values in mission control before the contract is offered."); } // Check for properly uniquness check if (uniquenessCheck == DataNode.UniquenessCheck.NONE) { throw new NotSupportedException("The SelectUnique method can only be used in DATA nodes with the uniquenessCheck attribute set."); } // Get the active/offered contract lists IEnumerable <ConfiguredContract> contractList = ConfiguredContract.CurrentContracts. Where(c => c != null && c.contractType != null); // Add in finished contracts if (uniquenessCheck == DataNode.UniquenessCheck.CONTRACT_ALL || uniquenessCheck == DataNode.UniquenessCheck.GROUP_ALL) { contractList = contractList.Union(ConfiguredContract.CompletedContracts. Where(c => c != null && c.contractType != null)); } // Filter anything that doesn't have our key contractList = contractList.Where(c => c.uniqueData.ContainsKey(key)); // Check for contracts of the same type if (uniquenessCheck == DataNode.UniquenessCheck.CONTRACT_ALL || uniquenessCheck == DataNode.UniquenessCheck.CONTRACT_ACTIVE) { contractList = contractList.Where(c => c.contractType.name == contractType.name); } // Check for a shared group else if (contractType.group != null) { contractList = contractList.Where(c => c.contractType.group != null && c.contractType.group.name == contractType.group.name); } // Shared lack of group else { contractList = contractList.Where(c => c.contractType.group == null); } // Get the valid values IEnumerable <T> values; // Special case for vessels if (typeof(T) == typeof(Vessel)) { values = input.Where(t => !contractList.Any(c => c.uniqueData[key].Equals(((Vessel)(object)t).id))); } else { values = input.Where(t => !contractList.Any(c => c.uniqueData[key].Equals(t))); } // Make a random selection from what's left return(values.Skip(r.Next(values.Count())).FirstOrDefault()); }
/// <summary> /// Ensures the given config node has at exactly one of the given values. /// </summary> /// <param name="configNode"></param> /// <param name="values"></param> /// <param name="obj"></param> /// <returns></returns> public static bool OnlyOne(ConfigNode configNode, string[] values, IContractConfiguratorFactory obj) { int count = 0; string output = ""; foreach (string value in values) { if (configNode.HasValue(value)) { count++; } if (value == values.First()) { output = value; } else if (value == values.Last()) { output += " or " + value; } else { output += ", " + value; } } if (count != 1) { LoggingUtil.LogError(obj, "{0}: Exactly one of the following types is allowed: {1}", obj.ErrorPrefix(configNode), output); return(false); } else { return(true); } }
/// <summary> /// Attempts to parse a value from the config node. Returns a default value if not found. /// Validates return values using the given function. /// </summary> /// <typeparam name="T">The type of value to convert to.</typeparam> /// <param name="configNode">The ConfigNode to read from.</param> /// <param name="key">The key to examine.</param> /// <param name="setter">Function used to set the output value</param> /// <param name="obj">Factory object for error messages.</param> /// <param name="defaultValue">Default value to use if there is no key in the config node</param> /// <param name="validation">Validation function to run against the returned value</param> /// <returns>The parsed value (or default value if not found)</returns> public static bool ParseValue <T>(ConfigNode configNode, string key, Action <T> setter, IContractConfiguratorFactory obj, T defaultValue, Func <T, bool> validation) { // Initialize the data type of the expression if (currentDataNode != null && !currentDataNode.IsInitialized(key)) { currentDataNode.BlankInit(key, typeof(T)); } bool valid = true; T value = defaultValue; if (configNode.HasValue(key)) { try { // Load value value = ParseValue <T>(configNode, key, true); // If value was non-null, run validation if (value != null) { try { valid = validation.Invoke(value); if (!valid) { // In general, the validation function should throw an exception and give a much better message LoggingUtil.LogError(obj, obj.ErrorPrefix(configNode) + ": The value supplied for " + key + " (" + value + ") is invalid."); } } catch (Exception e) { LoggingUtil.LogError(obj, obj.ErrorPrefix(configNode) + ": The value supplied for " + key + " (" + value + ") is invalid."); LoggingUtil.LogException(e); valid = false; } } } catch (Exception e) { if (e.GetType() == typeof(DataNode.ValueNotInitialized)) { string dependency = ((DataNode.ValueNotInitialized)e).key; string path = currentDataNode.Path() + key; LoggingUtil.LogVerbose(typeof(ConfigNodeUtil), "Trying to load " + key + ", but " + dependency + " is uninitialized."); // Defer loading this value DeferredLoadObject <T> loadObj = null; if (!deferredLoads.ContainsKey(path)) { deferredLoads[path] = new DeferredLoadObject <T>(configNode, key, setter, obj, validation, currentDataNode); } loadObj = (DeferredLoadObject <T>)deferredLoads[path]; // New dependency - try again if (!loadObj.dependencies.Contains(dependency)) { LoggingUtil.LogVerbose(typeof(ConfigNodeUtil), " New dependency, will re-attempt to load later."); loadObj.dependencies.Add(dependency); return(true); } } LoggingUtil.LogError(obj, obj.ErrorPrefix(configNode) + ": Error parsing " + key); LoggingUtil.LogException(e); // Return immediately on deferred load error if (e.GetType() == typeof(DataNode.ValueNotInitialized)) { return(false); } valid = false; } finally { AddFoundKey(configNode, key); } } // Store the value if (currentDataNode != null) { LoggingUtil.LogVerbose(typeof(ConfigNodeUtil), "DataNode[" + currentDataNode.Name + "], storing " + key + " = " + value); currentDataNode[key] = value; if (!currentDataNode.IsDeterministic(key) && initialLoad) { currentDataNode.DeferredLoads.Add(new DeferredLoadObject <T>(configNode, key, setter, obj, validation, currentDataNode)); } } // Invoke the setter function if (valid) { setter.Invoke(value); } return(valid); }