diff options
Diffstat (limited to 'ThirdParty/CsvHelper-master/src/CsvHelper/Configuration/ClassMap.cs')
-rw-r--r-- | ThirdParty/CsvHelper-master/src/CsvHelper/Configuration/ClassMap.cs | 648 |
1 files changed, 648 insertions, 0 deletions
diff --git a/ThirdParty/CsvHelper-master/src/CsvHelper/Configuration/ClassMap.cs b/ThirdParty/CsvHelper-master/src/CsvHelper/Configuration/ClassMap.cs new file mode 100644 index 0000000..b000693 --- /dev/null +++ b/ThirdParty/CsvHelper-master/src/CsvHelper/Configuration/ClassMap.cs @@ -0,0 +1,648 @@ +// Copyright 2009-2022 Josh Close +// This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. +// See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. +// https://github.com/JoshClose/CsvHelper +using CsvHelper.Configuration.Attributes; +using CsvHelper.TypeConversion; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace CsvHelper.Configuration +{ + ///<summary> + /// Maps class members to CSV fields. + ///</summary> + public abstract class ClassMap + { + private static readonly List<Type> enumerableConverters = new List<Type> + { + typeof(ArrayConverter), + typeof(CollectionGenericConverter), + typeof(EnumerableConverter), + typeof(IDictionaryConverter), + typeof(IDictionaryGenericConverter), + typeof(IEnumerableConverter), + typeof(IEnumerableGenericConverter) + }; + + /// <summary> + /// The type of the class this map is for. + /// </summary> + public virtual Type ClassType { get; private set; } + + /// <summary> + /// The class constructor parameter mappings. + /// </summary> + public virtual List<ParameterMap> ParameterMaps { get; } = new List<ParameterMap>(); + + /// <summary> + /// The class member mappings. + /// </summary> + public virtual MemberMapCollection MemberMaps { get; } = new MemberMapCollection(); + + /// <summary> + /// The class member reference mappings. + /// </summary> + public virtual MemberReferenceMapCollection ReferenceMaps { get; } = new MemberReferenceMapCollection(); + + /// <summary> + /// Allow only internal creation of CsvClassMap. + /// </summary> + /// <param name="classType">The type of the class this map is for.</param> + internal ClassMap(Type classType) + { + ClassType = classType; + } + + /// <summary> + /// Maps a member to a CSV field. + /// </summary> + /// <param name="classType">The type of the class this map is for. This may not be the same type + /// as the member.DeclaringType or the current ClassType due to nested member mappings.</param> + /// <param name="member">The member to map.</param> + /// <param name="useExistingMap">If true, an existing map will be used if available. + /// If false, a new map is created for the same member.</param> + /// <returns>The member mapping.</returns> + public MemberMap Map(Type classType, MemberInfo member, bool useExistingMap = true) + { + if (useExistingMap) + { + var existingMap = MemberMaps.Find(member); + if (existingMap != null) + { + return existingMap; + } + } + + var memberMap = MemberMap.CreateGeneric(classType, member); + memberMap.Data.Index = GetMaxIndex() + 1; + MemberMaps.Add(memberMap); + + return memberMap; + } + + /// <summary> + /// Maps a non-member to a CSV field. This allows for writing + /// data that isn't mapped to a class member. + /// </summary> + /// <returns>The member mapping.</returns> + public virtual MemberMap<object, object> Map() + { + var memberMap = new MemberMap<object, object>(null); + memberMap.Data.Index = GetMaxIndex() + 1; + MemberMaps.Add(memberMap); + + return memberMap; + } + + /// <summary> + /// Maps a member to another class map. + /// </summary> + /// <param name="classMapType">The type of the class map.</param> + /// <param name="member">The member.</param> + /// <param name="constructorArgs">Constructor arguments used to create the reference map.</param> + /// <returns>The reference mapping for the member.</returns> + public virtual MemberReferenceMap References(Type classMapType, MemberInfo member, params object[] constructorArgs) + { + if (!typeof(ClassMap).IsAssignableFrom(classMapType)) + { + throw new InvalidOperationException($"Argument {nameof(classMapType)} is not a CsvClassMap."); + } + + var existingMap = ReferenceMaps.Find(member); + + if (existingMap != null) + { + return existingMap; + } + + var map = (ClassMap)ObjectResolver.Current.Resolve(classMapType, constructorArgs); + map.ReIndex(GetMaxIndex() + 1); + var reference = new MemberReferenceMap(member, map); + ReferenceMaps.Add(reference); + + return reference; + } + + /// <summary> + /// Maps a constructor parameter to a CSV field. + /// </summary> + /// <param name="name">The name of the constructor parameter.</param> + public virtual ParameterMap Parameter(string name) + { + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name)); + + var args = new GetConstructorArgs(ClassType); + + return Parameter(() => ConfigurationFunctions.GetConstructor(args), name); + } + + /// <summary> + /// Maps a constructor parameter to a CSV field. + /// </summary> + /// <param name="getConstructor">A function that returns the <see cref="ConstructorInfo"/> for the constructor.</param> + /// <param name="name">The name of the constructor parameter.</param> + public virtual ParameterMap Parameter(Func<ConstructorInfo> getConstructor, string name) + { + if (getConstructor == null) throw new ArgumentNullException(nameof(getConstructor)); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name)); + + var constructor = getConstructor(); + var parameters = constructor.GetParameters(); + var parameter = parameters.SingleOrDefault(p => p.Name == name); + if (parameter == null) + { + throw new ConfigurationException($"Constructor {constructor.GetDefinition()} doesn't contain a paramter with name '{name}'."); + } + + return Parameter(constructor, parameter); + } + + /// <summary> + /// Maps a constructor parameter to a CSV field. + /// </summary> + /// <param name="constructor">The <see cref="ConstructorInfo"/> for the constructor.</param> + /// <param name="parameter">The <see cref="ParameterInfo"/> for the constructor parameter.</param> + public virtual ParameterMap Parameter(ConstructorInfo constructor, ParameterInfo parameter) + { + if (constructor == null) throw new ArgumentNullException(nameof(constructor)); + if (parameter == null) throw new ArgumentNullException(nameof(parameter)); + + if (!constructor.GetParameters().Contains(parameter)) + { + throw new ConfigurationException($"Constructor {constructor.GetDefinition()} doesn't contain parameter '{parameter.GetDefinition()}'."); + } + + var parameterMap = new ParameterMap(parameter); + parameterMap.Data.Index = GetMaxIndex(isParameter: true) + 1; + ParameterMaps.Add(parameterMap); + + return parameterMap; + } + + /// <summary> + /// Auto maps all members for the given type. If a member + /// is mapped again it will override the existing map. + /// </summary> + /// <param name="culture">The culture.</param> + public virtual void AutoMap(CultureInfo culture) + { + AutoMap(new CsvConfiguration(culture)); + } + + /// <summary> + /// Auto maps all members for the given type. If a member + /// is mapped again it will override the existing map. + /// </summary> + /// <param name="configuration">The configuration.</param> + public virtual void AutoMap(CsvConfiguration configuration) + { + AutoMap(new CsvContext(configuration)); + } + + /// <summary> + /// Auto maps all members for the given type. If a member + /// is mapped again it will override the existing map. + /// </summary> + /// <param name="context">The context.</param> + public virtual void AutoMap(CsvContext context) + { + var type = GetGenericType(); + if (typeof(IEnumerable).IsAssignableFrom(type)) + { + throw new ConfigurationException("Types that inherit IEnumerable cannot be auto mapped. " + + "Did you accidentally call GetRecord or WriteRecord which " + + "acts on a single record instead of calling GetRecords or " + + "WriteRecords which acts on a list of records?"); + } + + var mapParents = new LinkedList<Type>(); + var args = new ShouldUseConstructorParametersArgs(type); + if (context.Configuration.ShouldUseConstructorParameters(args)) + { + // This type doesn't have a parameterless constructor so we can't create an + // instance and set it's member. Constructor parameters need to be created + // instead. Writing only uses getters, so members will also be mapped + // for writing purposes. + AutoMapConstructorParameters(this, context, mapParents); + } + + AutoMapMembers(this, context, mapParents); + } + + /// <summary> + /// Get the largest index for the + /// members and references. + /// </summary> + /// <returns>The max index.</returns> + public virtual int GetMaxIndex(bool isParameter = false) + { + if (isParameter) + { + return ParameterMaps.Select(parameterMap => parameterMap.GetMaxIndex()).DefaultIfEmpty(-1).Max(); + } + + if (MemberMaps.Count == 0 && ReferenceMaps.Count == 0) + { + return -1; + } + + var indexes = new List<int>(); + if (MemberMaps.Count > 0) + { + indexes.Add(MemberMaps.Max(pm => pm.Data.Index)); + } + + if (ReferenceMaps.Count > 0) + { + indexes.AddRange(ReferenceMaps.Select(referenceMap => referenceMap.GetMaxIndex())); + } + + return indexes.Max(); + } + + /// <summary> + /// Resets the indexes based on the given start index. + /// </summary> + /// <param name="indexStart">The index start.</param> + /// <returns>The last index + 1.</returns> + public virtual int ReIndex(int indexStart = 0) + { + foreach (var parameterMap in ParameterMaps) + { + parameterMap.Data.Index = indexStart + parameterMap.Data.Index; + } + + foreach (var memberMap in MemberMaps) + { + if (!memberMap.Data.IsIndexSet) + { + memberMap.Data.Index = indexStart + memberMap.Data.Index; + } + } + + foreach (var referenceMap in ReferenceMaps) + { + indexStart = referenceMap.Data.Mapping.ReIndex(indexStart); + } + + return indexStart; + } + + /// <summary> + /// Auto maps the given map and checks for circular references as it goes. + /// </summary> + /// <param name="map">The map to auto map.</param> + /// <param name="context">The context.</param> + /// <param name="mapParents">The list of parents for the map.</param> + /// <param name="indexStart">The index starting point.</param> + protected virtual void AutoMapMembers(ClassMap map, CsvContext context, LinkedList<Type> mapParents, int indexStart = 0) + { + var type = map.GetGenericType(); + + var flags = BindingFlags.Instance | BindingFlags.Public; + if (context.Configuration.IncludePrivateMembers) + { + flags = flags | BindingFlags.NonPublic; + } + + var members = new List<MemberInfo>(); + if ((context.Configuration.MemberTypes & MemberTypes.Properties) == MemberTypes.Properties) + { + // We need to go up the declaration tree and find the actual type the property + // exists on and use that PropertyInfo instead. This is so we can get the private + // set method for the property. + var properties = new List<PropertyInfo>(); + foreach (var property in ReflectionHelper.GetUniqueProperties(type, flags)) + { + if (properties.Any(p => p.Name == property.Name)) + { + // Multiple properties could have the same name if a child class property + // is hiding a parent class property by using `new`. It's possible that + // the order of the properties returned + continue; + } + + properties.Add(ReflectionHelper.GetDeclaringProperty(type, property, flags)); + } + + members.AddRange(properties); + } + + if ((context.Configuration.MemberTypes & MemberTypes.Fields) == MemberTypes.Fields) + { + // We need to go up the declaration tree and find the actual type the field + // exists on and use that FieldInfo instead. + var fields = new List<MemberInfo>(); + foreach (var field in ReflectionHelper.GetUniqueFields(type, flags)) + { + if (fields.Any(p => p.Name == field.Name)) + { + // Multiple fields could have the same name if a child class field + // is hiding a parent class field by using `new`. It's possible that + // the order of the fields returned + continue; + } + + if (!field.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Any()) + { + fields.Add(ReflectionHelper.GetDeclaringField(type, field, flags)); + } + } + + members.AddRange(fields); + } + + foreach (var member in members) + { + if (member.GetCustomAttribute<IgnoreAttribute>() != null) + { + // Ignore this member including its tree if it's a reference. + continue; + } + + var typeConverterType = context.TypeConverterCache.GetConverter(member).GetType(); + + if (context.Configuration.HasHeaderRecord && enumerableConverters.Contains(typeConverterType)) + { + // Enumerable converters can't write the header properly, so skip it. + continue; + } + + var memberTypeInfo = member.MemberType().GetTypeInfo(); + var isDefaultConverter = typeConverterType == typeof(DefaultTypeConverter); + if (isDefaultConverter) + { + // If the type is not one covered by our type converters + // and it has a parameterless constructor, create a + // reference map for it. + + if (context.Configuration.IgnoreReferences) + { + continue; + } + + if (CheckForCircularReference(member.MemberType(), mapParents)) + { + continue; + } + + mapParents.AddLast(type); + var refMapType = typeof(DefaultClassMap<>).MakeGenericType(member.MemberType()); + var refMap = (ClassMap)ObjectResolver.Current.Resolve(refMapType); + + if (memberTypeInfo.HasConstructor() && !memberTypeInfo.HasParameterlessConstructor() && !memberTypeInfo.IsUserDefinedStruct()) + { + AutoMapConstructorParameters(refMap, context, mapParents, Math.Max(map.GetMaxIndex() + 1, indexStart)); + } + + // Need to use Max here for nested types. + AutoMapMembers(refMap, context, mapParents, Math.Max(map.GetMaxIndex() + 1, indexStart)); + mapParents.Drop(mapParents.Find(type)); + + if (refMap.MemberMaps.Count > 0 || refMap.ReferenceMaps.Count > 0) + { + var referenceMap = new MemberReferenceMap(member, refMap); + if (context.Configuration.ReferenceHeaderPrefix != null) + { + var args = new ReferenceHeaderPrefixArgs(member.MemberType(), member.Name); + referenceMap.Data.Prefix = context.Configuration.ReferenceHeaderPrefix(args); + } + + ApplyAttributes(referenceMap); + + map.ReferenceMaps.Add(referenceMap); + } + } + else + { + // Only add the member map if it can be converted later on. + // If the member will use the default converter, don't add it because + // we don't want the .ToString() value to be used when auto mapping. + + // Use the top of the map tree. This will maps that have been auto mapped + // to later on get a reference to a map by doing map.Map( m => m.A.B.C.Id ) + // and it will return the correct parent map type of A instead of C. + var classType = mapParents.First?.Value ?? map.ClassType; + var memberMap = MemberMap.CreateGeneric(classType, member); + + // Use global values as the starting point. + memberMap.Data.TypeConverterOptions = TypeConverterOptions.Merge(new TypeConverterOptions(), context.TypeConverterOptionsCache.GetOptions(member.MemberType()), memberMap.Data.TypeConverterOptions); + memberMap.Data.Index = map.GetMaxIndex() + 1; + + ApplyAttributes(memberMap); + + map.MemberMaps.Add(memberMap); + } + } + + map.ReIndex(indexStart); + } + + /// <summary> + /// Auto maps the given map using constructor parameters. + /// </summary> + /// <param name="map">The map.</param> + /// <param name="context">The context.</param> + /// <param name="mapParents">The list of parents for the map.</param> + /// <param name="indexStart">The index starting point.</param> + protected virtual void AutoMapConstructorParameters(ClassMap map, CsvContext context, LinkedList<Type> mapParents, int indexStart = 0) + { + var type = map.GetGenericType(); + var args = new GetConstructorArgs(map.ClassType); + var constructor = context.Configuration.GetConstructor(args); + var parameters = constructor.GetParameters(); + + foreach (var parameter in parameters) + { + var parameterMap = new ParameterMap(parameter); + + if (parameter.GetCustomAttributes<IgnoreAttribute>(true).Any() || parameter.GetCustomAttributes<ConstantAttribute>(true).Any()) + { + // If there is an IgnoreAttribute or ConstantAttribute, we still need to add a map because a constructor requires + // all parameters to be present. A default value will be used later on. + + ApplyAttributes(parameterMap); + map.ParameterMaps.Add(parameterMap); + continue; + } + + var typeConverterType = context.TypeConverterCache.GetConverter(parameter.ParameterType).GetType(); + var memberTypeInfo = parameter.ParameterType.GetTypeInfo(); + var isDefaultConverter = typeConverterType == typeof(DefaultTypeConverter); + if (isDefaultConverter && (memberTypeInfo.HasParameterlessConstructor() || memberTypeInfo.IsUserDefinedStruct())) + { + // If the type is not one covered by our type converters + // and it has a parameterless constructor, create a + // reference map for it. + + if (context.Configuration.IgnoreReferences) + { + throw new InvalidOperationException($"Configuration '{nameof(CsvConfiguration.IgnoreReferences)}' can't be true " + + "when using types without a default constructor. Constructor parameters " + + "are used and all members including references must be used."); + } + + if (CheckForCircularReference(parameter.ParameterType, mapParents)) + { + throw new InvalidOperationException($"A circular reference was detected in constructor paramter '{parameter.Name}'." + + "Since all parameters must be supplied for a constructor, this parameter can't be skipped."); + } + + mapParents.AddLast(type); + var refMapType = typeof(DefaultClassMap<>).MakeGenericType(parameter.ParameterType); + var refMap = (ClassMap)ObjectResolver.Current.Resolve(refMapType); + AutoMapMembers(refMap, context, mapParents, Math.Max(map.GetMaxIndex(isParameter: true) + 1, indexStart)); + mapParents.Drop(mapParents.Find(type)); + + var referenceMap = new ParameterReferenceMap(parameter, refMap); + if (context.Configuration.ReferenceHeaderPrefix != null) + { + var referenceHeaderPrefix = new ReferenceHeaderPrefixArgs(memberTypeInfo.MemberType(), memberTypeInfo.Name); + referenceMap.Data.Prefix = context.Configuration.ReferenceHeaderPrefix(referenceHeaderPrefix); + } + + ApplyAttributes(referenceMap); + + parameterMap.ReferenceMap = referenceMap; + } + else if (isDefaultConverter && context.Configuration.ShouldUseConstructorParameters(new ShouldUseConstructorParametersArgs(parameter.ParameterType))) + { + // If the type is not one covered by our type converters + // and it should use contructor parameters, create a + // constructor map for it. + + mapParents.AddLast(type); + var constructorMapType = typeof(DefaultClassMap<>).MakeGenericType(parameter.ParameterType); + var constructorMap = (ClassMap)ObjectResolver.Current.Resolve(constructorMapType); + // Need to use Max here for nested types. + AutoMapConstructorParameters(constructorMap, context, mapParents, Math.Max(map.GetMaxIndex(isParameter: true) + 1, indexStart)); + mapParents.Drop(mapParents.Find(type)); + + parameterMap.ConstructorTypeMap = constructorMap; + } + else + { + parameterMap.Data.TypeConverterOptions = TypeConverterOptions.Merge(new TypeConverterOptions(), context.TypeConverterOptionsCache.GetOptions(parameter.ParameterType), parameterMap.Data.TypeConverterOptions); + parameterMap.Data.Index = map.GetMaxIndex(isParameter: true) + 1; + + ApplyAttributes(parameterMap); + } + + map.ParameterMaps.Add(parameterMap); + } + + map.ReIndex(indexStart); + } + + /// <summary> + /// Checks for circular references. + /// </summary> + /// <param name="type">The type to check for.</param> + /// <param name="mapParents">The list of parents to check against.</param> + /// <returns>A value indicating if a circular reference was found. + /// True if a circular reference was found, otherwise false.</returns> + protected virtual bool CheckForCircularReference(Type type, LinkedList<Type> mapParents) + { + if (mapParents.Count == 0) + { + return false; + } + + var node = mapParents.Last; + while (true) + { + if (node?.Value == type) + { + return true; + } + + node = node?.Previous; + if (node == null) + { + break; + } + } + + return false; + } + + /// <summary> + /// Gets the generic type for this class map. + /// </summary> + protected virtual Type GetGenericType() + { + return GetType().GetTypeInfo().BaseType?.GetGenericArguments()[0] ?? throw new ConfigurationException(); + } + + /// <summary> + /// Applies attribute configurations to the map. + /// </summary> + /// <param name="parameterMap">The parameter map.</param> + protected virtual void ApplyAttributes(ParameterMap parameterMap) + { + var parameter = parameterMap.Data.Parameter; + var attributes = parameter.GetCustomAttributes().OfType<IParameterMapper>(); + + foreach (var attribute in attributes) + { + attribute.ApplyTo(parameterMap); + } + } + + /// <summary> + /// Applies attribute configurations to the map. + /// </summary> + /// <param name="referenceMap">The parameter reference map.</param> + protected virtual void ApplyAttributes(ParameterReferenceMap referenceMap) + { + var parameter = referenceMap.Data.Parameter; + var attributes = parameter.GetCustomAttributes().OfType<IParameterReferenceMapper>(); + + foreach (var attribute in attributes) + { + attribute.ApplyTo(referenceMap); + } + } + + /// <summary> + /// Applies attribute configurations to the map. + /// </summary> + /// <param name="memberMap">The member map.</param> + protected virtual void ApplyAttributes(MemberMap memberMap) + { + if (memberMap.Data.Member == null) + { + return; + } + + var member = memberMap.Data.Member; + var attributes = member.GetCustomAttributes().OfType<IMemberMapper>(); + + foreach (var attribute in attributes) + { + attribute.ApplyTo(memberMap); + } + } + + /// <summary> + /// Applies attribute configurations to the map. + /// </summary> + /// <param name="referenceMap">The member reference map.</param> + protected virtual void ApplyAttributes(MemberReferenceMap referenceMap) + { + var member = referenceMap.Data.Member; + var attributes = member.GetCustomAttributes().OfType<IMemberReferenceMapper>(); + + foreach (var attribute in attributes) + { + attribute.ApplyTo(referenceMap); + } + } + } +} |