using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Xml;
namespace UnrealBuildTool
{
///
/// Loads data from disk XML files and stores it in memory.
///
public static class XmlConfigLoader
{
public static void Load(Type Class)
{
XmlConfigLoaderClassData ClassData;
if(Data.TryGetValue(Class, out ClassData))
{
ClassData.LoadXmlData();
}
}
///
/// Initializing data and resets all found classes.
///
public static void Init()
{
LoadData();
foreach(var ClassData in Data)
{
ClassData.Value.ResetData();
}
}
///
/// Cache entry class to store loaded info for given class.
///
class XmlConfigLoaderClassData
{
public XmlConfigLoaderClassData(Type ConfigClass)
{
// Adding empty types array to make sure this is parameterless Reset
// in case of overloading.
DefaultValuesLoader = ConfigClass.GetMethod("Reset", new Type[] { });
}
///
/// Resets previously stored data into class.
///
public void ResetData()
{
bDoneLoading = false;
if(DefaultValuesLoader != null)
{
DefaultValuesLoader.Invoke(null, new object[] { });
}
if (!bDoneLoading)
{
LoadXmlData();
}
}
///
/// Loads previously stored data into class.
///
public void LoadXmlData()
{
foreach (var DataPair in DataMap)
{
DataPair.Key.SetValue(null, DataPair.Value);
}
bDoneLoading = true;
}
///
/// Adds or overrides value in the cache.
///
/// The field info of the class.
/// The value to store.
public void SetValue(FieldInfo Field, object Value)
{
if(DataMap.ContainsKey(Field))
{
DataMap[Field] = Value;
}
else
{
DataMap.Add(Field, Value);
}
}
// A variable to indicate if loading was done during invoking of
// default values loader.
bool bDoneLoading = false;
// Method info loader to invoke before overriding fields with XML files data.
MethodInfo DefaultValuesLoader = null;
// Loaded data map.
Dictionary DataMap = new Dictionary();
}
///
/// Loads BuildConfiguration from XML into memory.
///
private static void LoadData()
{
var ConfigXmlFileName = "BuildConfiguration.xml";
var UE4EnginePath = new FileInfo(Path.Combine(Utils.GetExecutingAssemblyDirectory(), "..", "..")).FullName;
/*
* There are four possible location for this file:
* a. UE4/Engine/Programs/UnrealBuildTool
* b. UE4/Engine/Programs/NoRedist/UnrealBuildTool
* c. UE4/Engine/Saved/UnrealBuildTool
* d. My Documnets/Unreal Engine/UnrealBuildTool
*
* The UBT is looking for it in all four places in the given order and
* overrides already read data with the loaded ones, hence d. has the
* priority. Not defined classes and fields are left alone.
*/
var ConfigLocationHierarchy = new string[]
{
Path.Combine(UE4EnginePath, "Programs", "UnrealBuildTool"),
Path.Combine(UE4EnginePath, "Programs", "NoRedist", "UnrealBuildTool"),
Path.Combine(UE4EnginePath, "Saved", "UnrealBuildTool"),
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "Unreal Engine", "UnrealBuildTool")
};
foreach (var PossibleConfigLocation in ConfigLocationHierarchy)
{
var FilePath = Path.Combine(PossibleConfigLocation, ConfigXmlFileName);
if (File.Exists(FilePath))
{
Load(FilePath);
}
}
}
///
/// Sets values of this class with values from given XML file.
///
/// The path to the file with values.
private static void Load(string ConfigurationXmlPath)
{
var ConfigDocument = new XmlDocument();
ConfigDocument.Load(ConfigurationXmlPath);
var XmlClasses = ConfigDocument.DocumentElement.SelectNodes("/Configuration/*");
foreach (XmlNode XmlClass in XmlClasses)
{
var ClassType = Type.GetType("UnrealBuildTool." + XmlClass.Name);
if(ClassType == null)
{
Log.TraceVerbose("XmlConfig Loading: class '{0}' doesn't exist.", XmlClass.Name);
continue;
}
XmlConfigLoaderClassData ClassData;
if (!Data.TryGetValue(ClassType, out ClassData))
{
ClassData = new XmlConfigLoaderClassData(ClassType);
Data.Add(ClassType, ClassData);
}
var XmlFields = XmlClass.SelectNodes("*");
foreach (XmlNode XmlField in XmlFields)
{
FieldInfo Field = ClassType.GetField(XmlField.Name);
if (Field == null || !Field.IsPublic || !Field.IsStatic)
{
throw new BuildException("BuildConfiguration Loading: field '{0}' doesn't exist or is either non-public or non-static.", XmlField.Name);
}
if(Field.FieldType.IsArray)
{
// If the type is an array type get items for it.
var XmlItems = XmlField.SelectNodes("Item");
// Get the C# type of the array.
var ItemType = Field.FieldType.GetElementType();
// Create the array according to the ItemType.
var OutputArray = Array.CreateInstance(ItemType, XmlItems.Count);
int Id = 0;
foreach(XmlNode XmlItem in XmlItems)
{
// Append values to the OutputArray.
OutputArray.SetValue(ParseFieldData(ItemType, XmlItem.InnerText), Id++);
}
ClassData.SetValue(Field, OutputArray);
}
else
{
ClassData.SetValue(Field, ParseFieldData(Field.FieldType, XmlField.InnerText));
}
}
}
}
private static object ParseFieldData(Type FieldType, string Text)
{
if (FieldType.Equals(typeof(System.String)))
{
return Text;
}
else
{
// Declaring parameters array used by TryParse method.
// Second parameter is "out", so you have to just
// assign placeholder null to it.
object ParsedValue;
if (!TryParse(FieldType, Text, out ParsedValue))
{
throw new BuildException("BuildConfiguration Loading: Parsing {0} value from \"{1}\" failed.", FieldType.Name, Text);
}
// If Invoke returned true, the second object of the
// parameters array is set to the parsed value.
return ParsedValue;
}
}
///
/// Emulates TryParse behavior on custom type. If the type implements
/// Parse(string, IFormatProvider) or Parse(string) static method uses
/// one of them to parse with preference of the one with format
/// provider (but passes invariant culture).
///
/// Type to parse.
/// String representation of the value.
/// Output parsed value.
/// True if parsing succeeded. False otherwise.
private static bool TryParse(Type ParsingType, string UnparsedValue, out object ParsedValue)
{
// Getting Parse method for FieldType which is required,
// if it doesn't exists for complex type, author should add
// one. The signature should be one of:
// static T Parse(string Input, IFormatProvider Provider) or
// static T Parse(string Input)
// where T is containing type.
// The one with format provider is preferred and invoked with
// InvariantCulture.
bool bWithCulture = true;
var ParseMethod = ParsingType.GetMethod("Parse", new Type[] { typeof(System.String), typeof(IFormatProvider) });
if(ParseMethod == null)
{
ParseMethod = ParsingType.GetMethod("Parse", new Type[] { typeof(System.String) });
bWithCulture = false;
}
if (ParseMethod == null)
{
throw new BuildException("BuildConfiguration Loading: Parsing of the type {0} is not supported.", ParsingType.Name);
}
var ParametersList = new List