/// <summary> /// Auto maps the given map using constructor parameters. /// </summary> /// <param name="map">The map to auto map.</param> /// <param name="options">Options for auto mapping.</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, AutoMapOptions options, LinkedList <Type> mapParents, int indexStart = 0) { var type = map.GetGenericType(); var constructor = options.GetConstructor(map.ClassType); var parameters = constructor.GetParameters(); foreach (var parameter in parameters) { var typeConverterType = TypeConverterFactory.Current.GetConverter(parameter.ParameterType).GetType(); var parameterMap = new ParameterMap(parameter); 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 (options.IgnoreReferences) { throw new InvalidOperationException($"Configuration '{nameof( options.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)ReflectionHelper.CreateInstance(refMapType); var refOptions = options.Copy(); refOptions.IgnoreReferences = false; AutoMapMembers(refMap, options, mapParents, Math.Max(map.GetMaxIndex() + 1, indexStart)); mapParents.Drop(mapParents.Find(type)); var referenceMap = new ParameterReferenceMap(parameter, refMap); if (options.PrefixReferenceHeaders) { referenceMap.Prefix(); } parameterMap.ReferenceMap = referenceMap; } else if (options.ShouldUseConstructorParameters(parameter.ParameterType)) { mapParents.AddLast(type); var constructorMapType = typeof(DefaultClassMap <>).MakeGenericType(parameter.ParameterType); var constructorMap = (ClassMap)ReflectionHelper.CreateInstance(constructorMapType); var constructorOptions = options.Copy(); constructorOptions.IgnoreReferences = false; // Need to use Max here for nested types. AutoMapConstructorParameters(constructorMap, constructorOptions, mapParents, Math.Max(map.GetMaxIndex() + 1, indexStart)); mapParents.Drop(mapParents.Find(type)); parameterMap.ConstructorTypeMap = constructorMap; } else { parameterMap.Data.TypeConverterOptions = TypeConverterOptions.Merge(new TypeConverterOptions(), options.TypeConverterOptionsFactory.GetOptions(parameter.ParameterType), parameterMap.Data.TypeConverterOptions); parameterMap.Data.Index = map.GetMaxIndex() + 1; } map.ParameterMaps.Add(parameterMap); } map.ReIndex(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="options">Options for auto mapping.</param> /// <param name="mapParents">The list of parents for the map.</param> /// <param name="indexStart">The index starting point.</param> internal static void AutoMapInternal(CsvClassMap map, AutoMapOptions options, LinkedList <Type> mapParents, int indexStart = 0) { var type = map.GetType().GetTypeInfo().BaseType.GetGenericArguments()[0]; if (typeof(IEnumerable).IsAssignableFrom(type)) { throw new CsvConfigurationException("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 flags = BindingFlags.Instance | BindingFlags.Public; if (options.IncludePrivateProperties) { flags = flags | BindingFlags.NonPublic; } var members = new List <MemberInfo>(); if ((options.MemberTypes & MemberTypes.Properties) == MemberTypes.Properties) { var properties = type.GetProperties(flags); members.AddRange(properties); } if ((options.MemberTypes & MemberTypes.Fields) == MemberTypes.Fields) { var fields = new List <MemberInfo>(); foreach (var field in type.GetFields(flags)) { if (!field.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Any()) { fields.Add(field); } } members.AddRange(fields); } foreach (var member in members) { var typeConverterType = TypeConverterFactory.GetConverter(member.MemberType()).GetType(); if (typeConverterType == typeof(EnumerableConverter)) { // The IEnumerable converter just throws an exception so skip it. continue; } var isDefaultConverter = typeConverterType == typeof(DefaultTypeConverter); var hasDefaultConstructor = member.MemberType().GetConstructor(new Type[0]) != null; if (isDefaultConverter && hasDefaultConstructor) { if (options.IgnoreReferences) { continue; } // If the type is not one covered by our type converters // and it has a parameterless constructor, create a // reference map for it. if (CheckForCircularReference(member.MemberType(), mapParents)) { continue; } mapParents.AddLast(type); var refMapType = typeof(DefaultCsvClassMap <>).MakeGenericType(member.MemberType()); var refMap = (CsvClassMap)ReflectionHelper.CreateInstance(refMapType); var refOptions = options.Copy(); refOptions.IgnoreReferences = false; AutoMapInternal(refMap, options, mapParents, map.GetMaxIndex() + 1); if (refMap.PropertyMaps.Count > 0 || refMap.ReferenceMaps.Count > 0) { var referenceMap = new CsvPropertyReferenceMap(member, refMap); if (options.PrefixReferenceHeaders) { referenceMap.Prefix(); } map.ReferenceMaps.Add(referenceMap); } } else { var propertyMap = new CsvPropertyMap(member); // Use global values as the starting point. propertyMap.Data.TypeConverterOptions = TypeConverterOptions.Merge(options.TypeConverterOptionsFactory.GetOptions(member.MemberType())); propertyMap.Data.Index = map.GetMaxIndex() + 1; if (!isDefaultConverter) { // Only add the property/field map if it can be converted later on. // If the property/field will use the default converter, don't add it because // we don't want the .ToString() value to be used when auto mapping. map.PropertyMaps.Add(propertyMap); } } } map.ReIndex(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="options">Options for auto mapping.</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, AutoMapOptions options, LinkedList <Type> mapParents, int indexStart = 0) { var type = map.GetGenericType(); var flags = BindingFlags.Instance | BindingFlags.Public; if (options.IncludePrivateMembers) { flags = flags | BindingFlags.NonPublic; } var members = new List <MemberInfo>(); if (options.MemberTypes.HasFlag(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 type.GetProperties(flags)) { properties.Add(ReflectionHelper.GetDeclaringProperty(type, property, flags)); } members.AddRange(properties); } if (options.MemberTypes.HasFlag(MemberTypes.Fields)) { var fields = new List <MemberInfo>(); foreach (var field in type.GetFields(flags)) { if (!field.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Any()) { fields.Add(field); } } members.AddRange(fields); } foreach (var member in members) { var typeConverterType = TypeConverterFactory.Current.GetConverter(member.MemberType()).GetType(); if (options.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 && (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 (options.IgnoreReferences) { continue; } if (CheckForCircularReference(member.MemberType(), mapParents)) { continue; } mapParents.AddLast(type); var refMapType = typeof(DefaultClassMap <>).MakeGenericType(member.MemberType()); var refMap = (ClassMap)ReflectionHelper.CreateInstance(refMapType); var refOptions = options.Copy(); refOptions.IgnoreReferences = false; // Need to use Max here for nested types. AutoMapMembers(refMap, options, 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 (options.PrefixReferenceHeaders) { referenceMap.Prefix(); } map.ReferenceMaps.Add(referenceMap); } } else { var memberMap = MemberMap.CreateGeneric(map.ClassType, member); // Use global values as the starting point. memberMap.Data.TypeConverterOptions = TypeConverterOptions.Merge(new TypeConverterOptions(), options.TypeConverterOptionsFactory.GetOptions(member.MemberType()), memberMap.Data.TypeConverterOptions); memberMap.Data.Index = map.GetMaxIndex() + 1; if (!isDefaultConverter) { // 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. map.MemberMaps.Add(memberMap); } } } map.ReIndex(indexStart); }