// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using EpicGames.Core;
using UnrealBuildTool;
namespace UnrealBuildTool
{
///
/// Stores parsed values from XML config files which can be applied to a configurable type. Can be serialized to disk in binary form as a cache.
///
class XmlConfigData
{
///
/// The current cache serialization version
///
const int SerializationVersion = 2;
///
/// List of input files. Stored to allow checking cache validity.
///
public FileReference[] InputFiles;
///
/// Abstract description of a target data member.
///
public abstract class TargetMember
{
///
/// Returns Reflection.MemberInfo describing the target class member.
///
public abstract MemberInfo MemberInfo { get; }
///
/// Returns Reflection.Type of the target class member.
///
public abstract Type Type { get; }
///
/// Indicates whether the target class member is static or not.
///
public abstract bool IsStatic { get; }
///
/// Returns the value setter of the target class member.
///
public abstract Action SetValue { get; }
///
/// Returns the value getter of the target class member.
///
public abstract Func GetValue { get; }
}
///
/// Description of a field member.
///
public class TargetField : TargetMember
{
public override MemberInfo MemberInfo => FieldInfo;
public override Type Type => FieldInfo.FieldType;
public override bool IsStatic => FieldInfo.IsStatic;
public override Action SetValue => FieldInfo.SetValue;
public override Func GetValue => FieldInfo.GetValue;
private FieldInfo FieldInfo;
public TargetField(FieldInfo FieldInfo)
{
this.FieldInfo = FieldInfo;
}
}
///
/// Description of a property member.
///
public class TargetProperty : TargetMember
{
public override MemberInfo MemberInfo => PropertyInfo;
public override Type Type => PropertyInfo.PropertyType;
public override bool IsStatic => PropertyInfo.GetGetMethod()!.IsStatic;
public override Action SetValue => PropertyInfo.SetValue;
public override Func GetValue => PropertyInfo.GetValue;
private PropertyInfo PropertyInfo;
public TargetProperty(PropertyInfo PropertyInfo)
{
this.PropertyInfo = PropertyInfo;
}
}
public class ValueInfo
{
public TargetMember Target;
public object Value;
public FileReference SourceFile;
public XmlConfigFileAttribute XmlConfigAttribute;
public ValueInfo(FieldInfo FieldInfo, object Value, FileReference SourceFile, XmlConfigFileAttribute XmlConfigAttribute)
{
this.Target = new TargetField(FieldInfo);
this.Value = Value;
this.SourceFile = SourceFile;
this.XmlConfigAttribute = XmlConfigAttribute;
}
public ValueInfo(PropertyInfo PropertyInfo, object Value, FileReference SourceFile, XmlConfigFileAttribute XmlConfigAttribute)
{
this.Target = new TargetProperty(PropertyInfo);
this.Value = Value;
this.SourceFile = SourceFile;
this.XmlConfigAttribute = XmlConfigAttribute;
}
public ValueInfo(TargetMember Target, object Value, FileReference SourceFile, XmlConfigFileAttribute XmlConfigAttribute)
{
this.Target = Target;
this.Value = Value;
this.SourceFile = SourceFile;
this.XmlConfigAttribute = XmlConfigAttribute;
}
}
///
/// Stores a mapping from type -> member -> value, with all the config values for configurable fields.
///
public Dictionary TypeToValues;
///
/// Constructor
///
///
///
public XmlConfigData(FileReference[] InputFiles, Dictionary TypeToValues)
{
this.InputFiles = InputFiles;
this.TypeToValues = TypeToValues;
}
///
/// Attempts to read a previous block of config values from disk
///
/// The file to read from
/// Array of valid types. Used to resolve serialized type names to concrete types.
/// On success, receives the parsed data
/// True if the data was read and is valid
public static bool TryRead(FileReference Location, IEnumerable Types, [NotNullWhen(true)] out XmlConfigData? Data)
{
// Check the file exists first
if (!FileReference.Exists(Location))
{
Data = null;
return false;
}
// Read the cache from disk
using (BinaryReader Reader = new BinaryReader(File.Open(Location.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)))
{
// Check the serialization version matches
if(Reader.ReadInt32() != SerializationVersion)
{
Data = null;
return false;
}
// Read the input files
FileReference[] InputFiles = Reader.ReadArray(() => Reader.ReadFileReference())!;
// Read the types
int NumTypes = Reader.ReadInt32();
Dictionary TypeToValues = new Dictionary(NumTypes);
for(int TypeIdx = 0; TypeIdx < NumTypes; TypeIdx++)
{
// Read the type name
string TypeName = Reader.ReadString();
// Try to find it in the list of configurable types
Type? Type = Types.FirstOrDefault(x => x.Name == TypeName);
if(Type == null)
{
Data = null;
return false;
}
// Read all the values
ValueInfo[] Values = new ValueInfo[Reader.ReadInt32()];
for (int ValueIdx = 0; ValueIdx < Values.Length; ValueIdx++)
{
string MemberName = Reader.ReadString();
XmlConfigData.TargetMember? TargetMember = GetTargetMemberWithAttribute(Type, MemberName);
if (TargetMember != null)
{
// If TargetMember is not null, we know it has our attribute.
XmlConfigFileAttribute XmlConfigAttribute = TargetMember!.MemberInfo.GetCustomAttribute()!;
// Try to parse the value and add it to the output array
object Value = Reader.ReadObject(TargetMember.Type)!;
// Read the path of the config file that provided this setting
FileReference SourceFile = Reader.ReadFileReference();
Values[ValueIdx] = new ValueInfo(TargetMember, Value, SourceFile, XmlConfigAttribute);
}
else
{
Data = null;
return false;
}
}
// Add it to the type map
TypeToValues.Add(Type, Values);
}
// Return the parsed data
Data = new XmlConfigData(InputFiles.ToArray(), TypeToValues);
return true;
}
}
///
/// Find a data member (field or property) with the given name and attribute and returns TargetMember wrapper created for it.
///
/// Attribute a member has to have to be considered.
/// Type which members are to be searched
/// Name of a member (field or property) to find.
/// TargetMember wrapper or null if no member has been found.
private static XmlConfigData.TargetMember? GetTargetMemberWithAttribute(Type Type, string MemberName)
where T : System.Attribute
{
T? XmlConfigAttribute = null;
FieldInfo? Field = Type.GetField(MemberName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
XmlConfigAttribute = Field?.GetCustomAttribute();
if (Field != null && XmlConfigAttribute != null)
{
return new XmlConfigData.TargetField(Field);
}
PropertyInfo? Property = Type.GetProperty(MemberName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
XmlConfigAttribute = Property?.GetCustomAttribute();
if (Property != null && XmlConfigAttribute != null)
{
return new XmlConfigData.TargetProperty(Property);
}
return null;
}
///
/// Writes the coalesced config hierarchy to disk
///
/// File to write to
public void Write(FileReference Location)
{
DirectoryReference.CreateDirectory(Location.Directory);
using (BinaryWriter Writer = new BinaryWriter(File.Open(Location.FullName, FileMode.Create, FileAccess.Write, FileShare.Read)))
{
Writer.Write(SerializationVersion);
// Save all the input files. The cache will not be valid if these change.
Writer.Write(InputFiles, Item => Writer.Write(Item));
// Write all the categories
Writer.Write(TypeToValues.Count);
foreach(KeyValuePair TypePair in TypeToValues)
{
Writer.Write(TypePair.Key.Name);
Writer.Write(TypePair.Value.Length);
foreach(ValueInfo MemberPair in TypePair.Value)
{
Writer.Write(MemberPair.Target.MemberInfo.Name);
Writer.Write(MemberPair.Target.Type, MemberPair.Value);
Writer.Write(MemberPair.SourceFile);
}
}
}
}
}
}