/// <summary> /// Read the attributes of the class and create an array /// of how to process the file /// </summary> /// <param name="recordType">Class we are analysing</param> /// <param name="typedRecordAttribute"></param> internal VisynRecordInfo(Type recordType, ITypedRecordAttribute typedRecordAttribute) { RecordType = recordType; SizeHint = 32; RecordConditionSelector = string.Empty; RecordCondition = RecordCondition.None; CommentAnyPlace = true; Operations = new RecordOperations(this); InitRecordFields(typedRecordAttribute); }
/// <summary> /// Parse the attributes on the class and create an ordered list of /// fields we are extracting from the record /// </summary> /// <param name="fields">Complete list of fields in class</param> /// <param name="recordAttribute">Type of record, fixed or delimited</param> /// <returns>List of fields we are extracting</returns> private static FieldBase[] CreateCoreFields(IList <FieldInfo> fields, ITypedRecordAttribute recordAttribute) { var resFields = new List <FieldBase>(); // count of Properties var automaticFields = 0; // count of normal fields var genericFields = 0; for (int i = 0; i < fields.Count; i++) { FieldBase currentField = FieldBase.CreateField(fields[i], recordAttribute); if (currentField == null) { continue; } if (currentField.FieldInfo.IsDefined(typeof(CompilerGeneratedAttribute), false)) { automaticFields++; } else { genericFields++; } // Add to the result resFields.Add(currentField); if (resFields.Count > 1) { CheckForOrderProblems(currentField, resFields); } } if (automaticFields > 0 && genericFields > 0 && SumOrder(resFields) == 0) { throw new BadUsageException(Messages.Messages.Errors.MixOfStandardAndAutoPropertiesFields .ClassName(resFields[0].FieldInfo.DeclaringType.Name) .Text); } SortFieldsByOrder(resFields); CheckForOptionalAndArrayProblems(resFields); return(resFields.ToArray()); }
/// <summary> /// Create a list of fields we are extracting and set /// the size hint for record I/O /// </summary> private void InitRecordFields(ITypedRecordAttribute typedRecordAttribute) { ITypedRecordAttribute recordAttribute = Attributes.GetFirstInherited <TypedRecordAttribute>(RecordType); if (recordAttribute == null) { if (typedRecordAttribute == null) { throw new BadUsageException(Messages.Messages.Errors.ClassWithOutRecordAttribute.ClassName(RecordType.Name).Text); } recordAttribute = typedRecordAttribute; } if (ReflectionHelper.GetDefaultConstructor(RecordType) == null) { throw new BadUsageException(Messages.Messages.Errors.ClassWithOutDefaultConstructor .ClassName(RecordType.Name) .Text); } Attributes.WorkWithFirst <IgnoreFirstAttribute>( RecordType, a => IgnoreFirst = a.NumberOfLines); Attributes.WorkWithFirst <IgnoreLastAttribute>( RecordType, a => IgnoreLast = a.NumberOfLines); Attributes.WorkWithFirst <IgnoreEmptyLinesAttribute>( RecordType, a => { IgnoreEmptyLines = true; IgnoreEmptySpaces = a.IgnoreSpaces; }); #pragma warning disable CS0618 // Type or member is obsolete Attributes.WorkWithFirst <IgnoreCommentedLinesAttribute>( #pragma warning restore CS0618 // Type or member is obsolete RecordType, a => { IgnoreEmptyLines = true; CommentMarker = a.CommentMarker; CommentAnyPlace = a.AnyPlace; }); Attributes.WorkWithFirst <ConditionalRecordAttribute>(RecordType, a => { RecordCondition = a.Condition; RecordConditionSelector = a.ConditionSelector; if (RecordCondition == RecordCondition.ExcludeIfMatchRegex || RecordCondition == RecordCondition.IncludeIfMatchRegex) { RecordConditionRegEx = new Regex(RecordConditionSelector, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture); } }); if (CheckInterface(RecordType, typeof(INotifyRead))) { NotifyRead = true; } if (CheckInterface(RecordType, typeof(INotifyWrite))) { NotifyWrite = true; } // Create fields // Search for cached fields var fields = new List <FieldInfo>(ReflectionHelper.RecursiveGetFields(RecordType)); Fields = CreateCoreFields(fields, recordAttribute); if (FieldCount == 0) { throw new BadUsageException(Messages.Messages.Errors.ClassWithOutFields.ClassName(RecordType.Name).Text); } if (recordAttribute is FixedLengthRecordAttribute) { // Defines the initial size of the StringBuilder SizeHint = 0; for (var i = 0; i < FieldCount; i++) { SizeHint += ((FixedLengthField)Fields[i]).FieldLength; } } }
/// <summary> /// Check the Attributes on the field and return a structure containing /// the settings for this file. /// </summary> /// <param name="fi">Information about this field</param> /// <param name="recordAttribute">Type of record we are reading</param> /// <returns>Null if not used</returns> public static FieldBase CreateField(FieldInfo fi, ITypedRecordAttribute recordAttribute) { FieldBase res = null; MemberInfo mi = fi; var memberName = $"The field: '{ fi.Name}'"; var fieldType = fi.FieldType; var fieldFriendlyName = AutoPropertyName(fi); if (string.IsNullOrEmpty(fieldFriendlyName) == false) { var prop = fi.DeclaringType.GetProperty(fieldFriendlyName); if (prop != null) { memberName = $"The property: '{ prop.Name}'"; mi = prop; } else { fieldFriendlyName = null; } } // If ignored, return null #pragma warning disable 612,618 // disable obsolete warning if (mi.IsDefined(typeof(FieldNotInFileAttribute), true) || mi.IsDefined(typeof(FieldIgnoredAttribute), true) || mi.IsDefined(typeof(FieldHiddenAttribute), true)) #pragma warning restore 612,618 { return(null); } var attributes = (FieldAttribute[])mi.GetCustomAttributes(typeof(FieldAttribute), true); // CHECK USAGE ERRORS !!! // Fixed length record and no attributes at all if (recordAttribute is FixedLengthRecordAttribute && attributes.Length == 0) { throw new BadUsageException($"{memberName} must be marked the FieldFixedLength attribute because the record class is marked with FixedLengthRecord."); } if (attributes.Length > 1) { throw new BadUsageException($"{memberName} has a FieldFixedLength and a FieldDelimiter attribute."); } if (recordAttribute is DelimitedRecordAttribute && mi.IsDefined(typeof(FieldAlignAttribute), false)) { throw new BadUsageException($"{memberName} can't be marked with FieldAlign attribute, it is only valid for fixed length records and are used only for write purpose."); } if (fieldType.IsArray == false && mi.IsDefined(typeof(FieldArrayLengthAttribute), false)) { throw new BadUsageException($"{memberName} can't be marked with FieldArrayLength attribute is only valid for array fields."); } // PROCESS IN NORMAL CONDITIONS if (attributes.Length > 0) { var fieldAttribute = attributes[0]; var fixedLengthAttribute = fieldAttribute as FieldFixedLengthAttribute; if (fixedLengthAttribute != null) { // Fixed Field if (recordAttribute is DelimitedRecordAttribute) { throw new BadUsageException($"{memberName} can't be marked with FieldFixedLength attribute, it is only for the FixedLengthRecords not for delimited ones."); } var alignAttribute = mi.GetFirst <FieldAlignAttribute>(); res = new FixedLengthField(fi, fixedLengthAttribute.Length, alignAttribute); ((FixedLengthField)res).FixedMode = ((FixedLengthRecordAttribute)recordAttribute).FixedMode; } else if (fieldAttribute is FieldDelimiterAttribute) { // Delimited Field if (recordAttribute is FixedLengthRecordAttribute) { throw new BadUsageException($"{memberName} can't be marked with FieldDelimiter attribute, it is only for DelimitedRecords not for fixed ones."); } res = new DelimitedField(fi, ((FieldDelimiterAttribute)fieldAttribute).Delimiter); } else { throw new BadUsageException( $"Custom field attributes are not currently supported. Unknown attribute: {fieldAttribute.GetType().Name} on field: { fi.Name}"); } } else // attributes.Length == 0 { var delimitedRecordAttribute = recordAttribute as DelimitedRecordAttribute; if (delimitedRecordAttribute != null) { res = new DelimitedField(fi, delimitedRecordAttribute.Delimiter); } } if (res != null) { // FieldDiscarded res.Discarded = mi.IsDefined(typeof(FieldValueDiscardedAttribute), false); // FieldTrim mi.WorkWithFirst <FieldTrimAttribute>((x) => { res.TrimMode = x.TrimMode; res.TrimChars = x.TrimChars; }); // FieldQuoted mi.WorkWithFirst <FieldQuotedAttribute>((x) => { if (res is FixedLengthField) { throw new BadUsageException($"{memberName} can't be marked with FieldQuoted attribute, it is only for the delimited records."); } ((DelimitedField)res).QuoteChar = x.QuoteChar; ((DelimitedField)res).QuoteMode = x.QuoteMode; ((DelimitedField)res).QuoteMultiline = x.QuoteMultiline; }); // FieldOrder mi.WorkWithFirst <FieldOrderAttribute>(x => res.FieldOrder = x.Order); // FieldCaption mi.WorkWithFirst <FieldCaptionAttribute>(x => res.FieldCaption = x.Caption); // FieldOptional res.IsOptional = mi.IsDefined(typeof(FieldOptionalAttribute), false); // FieldInNewLine res.InNewLine = mi.IsDefined(typeof(FieldInNewLineAttribute), false); // FieldNotEmpty res.IsNotEmpty = mi.IsDefined(typeof(FieldNotEmptyAttribute), false); // FieldArrayLength if (fieldType.IsArray) { res.IsArray = true; res.ArrayType = fieldType.GetElementType(); // MinValue indicates that there is no FieldArrayLength in the array res.ArrayMinLength = int.MinValue; res.ArrayMaxLength = int.MaxValue; mi.WorkWithFirst <FieldArrayLengthAttribute>((x) => { res.ArrayMinLength = x.MinLength; res.ArrayMaxLength = x.MaxLength; if (res.ArrayMaxLength < res.ArrayMinLength || res.ArrayMinLength < 0 || res.ArrayMaxLength <= 0) { throw new BadUsageException($"{memberName} has invalid length values in the [FieldArrayLength] attribute."); } }); } } if (string.IsNullOrEmpty(res.FieldFriendlyName)) { res.FieldFriendlyName = res.FieldName; } return(res); }