#region " © Copyright 2005-07 to Marcos Meli - http://www.marcosmeli.com.ar" // Errors, suggestions, contributions, send a mail to: marcos@filehelpers.com. #endregion using System; using System.Collections; using System.ComponentModel; using System.Data; using System.IO; using System.Reflection; using System.Security.Permissions; using System.Security.Policy; using System.Text; #if ! MINI //using System.Data; using System.Text.RegularExpressions; using System.Threading; using System.Reflection.Emit; #endif namespace FileHelpers { /// An internal class used to store information about the Record Type. /// Is public to provide extensibility of DataSorage from outside the library. internal sealed class RecordInfo { #region " Internal Fields " internal Type mRecordType; internal FieldBase[] mFields; internal int mIgnoreFirst = 0; internal int mIgnoreLast = 0; internal bool mIgnoreEmptyLines = false; internal bool mIgnoreEmptySpaces = false; internal string mCommentMarker = null; internal bool mCommentAnyPlace = true; internal RecordCondition mRecordCondition = RecordCondition.None; internal string mRecordConditionSelector = string.Empty; #if ! MINI internal bool mNotifyRead; internal bool mNotifyWrite; private Regex mConditionRegEx = null; #endif internal int mFieldCount; private ConstructorInfo mRecordConstructor; private static readonly object[] mEmptyObjectArr = new object[] {}; private static readonly Type[] mEmptyTypeArr = new Type[] {}; #endregion #region " Constructor " /// The unique constructor for this class. It needs the subyacent record class. /// The Type of the record class. internal RecordInfo(Type recordType) { mRecordType = recordType; InitFields(); } internal bool IsDelimited { get { return mFields[0] is DelimitedField; } } #endregion #region " CreateAssingMethods " //#if NET_2_0 private delegate object[] GetAllValuesCallback(object record); private GetAllValuesCallback mGetAllValuesHandler; private void CreateGetAllMethod() { if (mGetAllValuesHandler != null) return; DynamicMethod dm = new DynamicMethod("_GetAllValues_FH_RT_", MethodAttributes.Static | MethodAttributes.Public, CallingConventions.Standard, typeof(object[]), new Type[] { typeof(object) }, mRecordType, true); ILGenerator generator = dm.GetILGenerator(); generator.DeclareLocal(typeof(object[])); generator.DeclareLocal(mRecordType); generator.Emit(OpCodes.Ldc_I4, mFieldCount); generator.Emit(OpCodes.Newarr, typeof(object)); generator.Emit(OpCodes.Stloc_0); generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Castclass, mRecordType); generator.Emit(OpCodes.Stloc_1); for (int i = 0; i < mFieldCount; i++) { FieldBase field = mFields[i]; generator.Emit(OpCodes.Ldloc_0); generator.Emit(OpCodes.Ldc_I4, i); generator.Emit(OpCodes.Ldloc_1); generator.Emit(OpCodes.Ldfld, field.mFieldInfo); if (field.mFieldType.IsValueType) { generator.Emit(OpCodes.Box, field.mFieldType); } generator.Emit(OpCodes.Stelem_Ref); //generator.EmitCall(); } // return the value generator.Emit(OpCodes.Ldloc_0); generator.Emit(OpCodes.Ret); mGetAllValuesHandler = (GetAllValuesCallback)dm.CreateDelegate(typeof(GetAllValuesCallback)); } private delegate object CreateAndAssignCallback(object[] values); private CreateAndAssignCallback mCreateHandler; private void CreateAssingMethods() { if (mCreateHandler != null) return; DynamicMethod dm = new DynamicMethod("_CreateAndAssing_FH_RT_", MethodAttributes.Static | MethodAttributes.Public, CallingConventions.Standard, typeof(object), new Type[] { typeof(object[]) }, mRecordType, true); //dm.InitLocals = false; ILGenerator generator = dm.GetILGenerator(); generator.DeclareLocal(mRecordType); //generator.DeclareLocal(typeof(object)); generator.Emit(OpCodes.Newobj, mRecordConstructor); generator.Emit(OpCodes.Stloc_0); for (int i = 0; i < mFieldCount; i++) { FieldBase field = mFields[i]; generator.Emit(OpCodes.Ldloc_0); generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Ldc_I4, i); generator.Emit(OpCodes.Ldelem_Ref); if (field.mFieldType.IsValueType) { generator.Emit(OpCodes.Unbox_Any, field.mFieldType); } else { generator.Emit(OpCodes.Castclass, field.mFieldType); } generator.Emit(OpCodes.Stfld, field.mFieldInfo); //generator.EmitCall(); } // return the value generator.Emit(OpCodes.Ldloc_0); generator.Emit(OpCodes.Ret); mCreateHandler = (CreateAndAssignCallback)dm.CreateDelegate(typeof(CreateAndAssignCallback)); } private delegate object CreateNewObject(); private CreateNewObject mFastConstructor; private void CreateFastConstructor() { if (mFastConstructor != null) return; DynamicMethod dm = new DynamicMethod("_CreateRecordFast_FH_RT_", MethodAttributes.Static | MethodAttributes.Public, CallingConventions.Standard, typeof(object), new Type[] { typeof(object[]) }, mRecordType, true); ILGenerator generator = dm.GetILGenerator(); // generator.DeclareLocal(mRecordType); generator.Emit(OpCodes.Newobj, mRecordConstructor); generator.Emit(OpCodes.Ret); mFastConstructor = (CreateNewObject)dm.CreateDelegate(typeof(CreateNewObject)); } //#endif //NET 2_0 #endregion #region InitFields private void InitFields() { //-> Checked by the AttributeTargets //new BadUsageException("Structures are not supported in the FileHelperEngine only classes are allowed."); TypedRecordAttribute recordAttribute = null; if (mRecordType.IsDefined(typeof (TypedRecordAttribute), true) == false) throw new BadUsageException("The class " + mRecordType.Name + " must be marked with the [DelimitedRecord] or [FixedLengthRecord] Attribute."); else { object[] attbs = mRecordType.GetCustomAttributes(typeof (TypedRecordAttribute), true); recordAttribute = (TypedRecordAttribute) attbs[0]; } if (mRecordType.GetConstructor(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic, null, mEmptyTypeArr, new ParameterModifier[] {}) == null) throw new BadUsageException("The record class " + mRecordType.Name + " need a constructor with no args (public or private)"); if (mRecordType.IsDefined(typeof (IgnoreFirstAttribute), false)) mIgnoreFirst = ((IgnoreFirstAttribute) mRecordType.GetCustomAttributes(typeof (IgnoreFirstAttribute), false)[0]).NumberOfLines; if (mRecordType.IsDefined(typeof (IgnoreLastAttribute), false)) mIgnoreLast = ((IgnoreLastAttribute) mRecordType.GetCustomAttributes(typeof (IgnoreLastAttribute), false)[0]).NumberOfLines; if (mRecordType.IsDefined(typeof (IgnoreEmptyLinesAttribute), false)) { mIgnoreEmptyLines = true; mIgnoreEmptySpaces = ((IgnoreEmptyLinesAttribute) mRecordType.GetCustomAttributes(typeof (IgnoreEmptyLinesAttribute), false)[0]). mIgnoreSpaces; } if (mRecordType.IsDefined(typeof (IgnoreCommentedLinesAttribute), false)) { IgnoreCommentedLinesAttribute ignoreComments = (IgnoreCommentedLinesAttribute) mRecordType.GetCustomAttributes(typeof (IgnoreCommentedLinesAttribute), false)[0]; mCommentMarker = ignoreComments.mCommentMarker; mCommentAnyPlace = ignoreComments.mAnyPlace; } if (mRecordType.IsDefined(typeof (ConditionalRecordAttribute), false)) { ConditionalRecordAttribute conditional = (ConditionalRecordAttribute) mRecordType.GetCustomAttributes(typeof (ConditionalRecordAttribute), false)[0]; mRecordCondition = conditional.mCondition; mRecordConditionSelector = conditional.mConditionSelector; #if ! MINI if (mRecordCondition == RecordCondition.ExcludeIfMatchRegex || mRecordCondition == RecordCondition.IncludeIfMatchRegex) { // mConditionRegEx = new Regex(mRecordConditionSelector, RegexOptions.Compiled | RegexOptions.CultureInvariant |RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture); mConditionRegEx = new Regex(mRecordConditionSelector, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture); } #endif } #if ! MINI if (typeof(INotifyRead).IsAssignableFrom(mRecordType)) mNotifyRead = true; if (typeof(INotifyWrite).IsAssignableFrom(mRecordType)) mNotifyWrite = true; #endif mRecordConstructor = mRecordType.GetConstructor(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic, null, mEmptyTypeArr, new ParameterModifier[] {}); // Create fields ArrayList fields = new ArrayList(); RecursiveGetFields(fields, mRecordType, recordAttribute); mFields = CreateCoreFields(fields, recordAttribute); mFieldCount = mFields.Length; if (recordAttribute is FixedLengthRecordAttribute) { // Defines the initial size of the StringBuilder mSizeHint = 0; for(int i = 0; i < mFieldCount; i++) mSizeHint += ((FixedLengthField) mFields[i]).mFieldLength; } if (mFieldCount == 0) throw new BadUsageException("The record class " + mRecordType.Name + " don't contains any field."); } private void RecursiveGetFields(ArrayList fields, Type currentType, TypedRecordAttribute recordAttribute) { if (currentType.BaseType != null) RecursiveGetFields(fields, currentType.BaseType, recordAttribute); fields.AddRange(currentType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)); } #endregion #region CreateFields private static FieldBase[] CreateCoreFields(ArrayList fields, TypedRecordAttribute recordAttribute) { FieldBase curField; ArrayList arr = new ArrayList(); bool someOptional = false; for (int i = 0; i < fields.Count; i++) { FieldInfo fieldInfo = (FieldInfo) fields[i]; curField = FieldFactory.CreateField(fieldInfo, recordAttribute, someOptional); if (curField != null) { someOptional = curField.mIsOptional; arr.Add(curField); if (arr.Count > 1) ((FieldBase)arr[arr.Count-2]).mNextIsOptional = ((FieldBase)arr[arr.Count-1]).mIsOptional; } } if (arr.Count > 0) { ((FieldBase) arr[0]).mIsFirst = true; ((FieldBase) arr[arr.Count - 1]).mIsLast = true; } return (FieldBase[]) arr.ToArray(typeof (FieldBase)); } #endregion #region GetFieldInfo internal FieldInfo GetFieldInfo(string name) { foreach (FieldBase field in mFields) { if (field.mFieldInfo.Name.ToLower() == name.ToLower()) return field.mFieldInfo; } return null; } #endregion #region CreateRecordObject internal object CreateRecordObject() { #if NET_2_0 CreateFastConstructor(); if (mFastConstructor == null) CreateFastConstructor(); return mFastConstructor(); #else return mRecordConstructor.Invoke(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, RecordInfo.mEmptyObjectArr, null); #endif } #endregion #region StringToRecord internal object StringToRecord(LineInfo line) { if (MustIgnoreLine(line.mLineStr)) return null; object[] mValues = new object[mFieldCount]; // array that holds the fields values for (int i = 0; i < mFieldCount; i++) { mValues[i] = mFields[i].ExtractValue(line); } #if NET_1_1 || MINI object record = CreateRecordObject(); for (int i = 0; i < mFieldCount; i++) { mFields[i].mFieldInfo.SetValue(record, mValues[i]); } return record; #else CreateAssingMethods(); try { // Asign all values via dinamic method that creates an object and assign values return mCreateHandler(mValues); } catch (InvalidCastException) { // Occurrs when the a custom converter returns an invalid value for the field. for (int i = 0; i < mFieldCount; i++) { if (mValues[i] != null && ! mFields[i].mFieldType.IsInstanceOfType(mValues[i])) throw new ConvertException(null, mFields[i].mFieldType, mFields[i].mFieldInfo.Name, line.mReader.LineNumber, -1, "The converter for the field: " + mFields[i].mFieldInfo.Name + " returns an object of Type: " + mValues[i].GetType().Name + " and the field is of type: " + mFields[i].mFieldType.Name); } return null; } #endif } // private static ErrorManager CreateAndAssign(object[] values) // { // ErrorManager record = new ErrorManager(); // record.mErrorMode = (ErrorMode) values[0]; // record.temp = (string) values[1]; // // return record; // } private bool MustIgnoreLine(string line) { if (mIgnoreEmptyLines) if ((mIgnoreEmptySpaces && line.TrimStart().Length == 0) || line.Length == 0) return true; if (mCommentMarker != null && mCommentMarker.Length > 0) if ( (mCommentAnyPlace && line.TrimStart().StartsWith(mCommentMarker)) || line.StartsWith(mCommentMarker)) return true; if (mRecordCondition != RecordCondition.None) { switch (mRecordCondition) { case RecordCondition.ExcludeIfBegins: return ConditionHelper.BeginsWith(line, mRecordConditionSelector); case RecordCondition.IncludeIfBegins: return ! ConditionHelper.BeginsWith(line, mRecordConditionSelector); case RecordCondition.ExcludeIfContains: return ConditionHelper.Contains(line, mRecordConditionSelector); case RecordCondition.IncludeIfContains: return ConditionHelper.Contains(line, mRecordConditionSelector); case RecordCondition.ExcludeIfEnclosed: return ConditionHelper.Enclosed(line, mRecordConditionSelector); case RecordCondition.IncludeIfEnclosed: return ! ConditionHelper.Enclosed(line, mRecordConditionSelector); case RecordCondition.ExcludeIfEnds: return ConditionHelper.EndsWith(line, mRecordConditionSelector); case RecordCondition.IncludeIfEnds: return ! ConditionHelper.EndsWith(line, mRecordConditionSelector); #if ! MINI case RecordCondition.ExcludeIfMatchRegex: return mConditionRegEx.IsMatch(line); case RecordCondition.IncludeIfMatchRegex: return ! mConditionRegEx.IsMatch(line); #endif } } return false; } #endregion #region RecordToString internal int mSizeHint = 32; internal string RecordToString(object record) { StringBuilder sb = new StringBuilder(mSizeHint); //string res = String.Empty; #if NET_1_1 || MINI object[] mValues = new object[mFieldCount]; for (int i = 0; i < mFieldCount; i++) { mValues[i] = mFields[i].mFieldInfo.GetValue(record); } #else CreateGetAllMethod(); object[] mValues = mGetAllValuesHandler(record); #endif for (int f = 0; f < mFieldCount; f++) { mFields[f].AssignToString(sb, mValues[f]); } //_BigSize = Math.Max(_BigSize, sb.Length); return sb.ToString(); } #endregion #region ValuesToRecord /// Returns a record formed with the passed values. /// The source Values. /// A record formed with the passed values. public object ValuesToRecord(object[] values) { for (int i = 0; i < mFieldCount; i++) { if (mFields[i].mFieldType == typeof(DateTime) && values[i] is double) values[i] = DoubleToDate((int)(double)values[i]); values[i] = mFields[i].CreateValueForField(values[i]); } #if NET_1_1 || MINI object record = mRecordConstructor.Invoke(RecordInfo.mEmptyObjectArr); for (int i = 0; i < mFieldCount; i++) { mFields[i].mFieldInfo.SetValue(record, values[i]); } return record; #else CreateAssingMethods(); // Asign all values via dinamic method that creates an object and assign values return mCreateHandler(values); #endif } private DateTime DoubleToDate(int serialNumber) { if (serialNumber < 59) { // Because of the 29-02-1900 bug, any serial date // under 60 is one off... Compensate. serialNumber++; } return new DateTime((serialNumber + 693593) * (10000000L * 24 * 3600)); } #endregion #region RecordToValues /// Get an object[] of the values in the fields of the passed record. /// The source record. /// An object[] of the values in the fields. public object[] RecordToValues(object record) { #if NET_1_1 || MINI object[] res = new object[mFieldCount]; for (int i = 0; i < mFieldCount; i++) { res[i] = mFields[i].mFieldInfo.GetValue(record); } return res; #else CreateGetAllMethod(); return mGetAllValuesHandler(record); #endif } #endregion #if ! MINI #region RecordsToDataTable internal DataTable RecordsToDataTable(ICollection records) { return RecordsToDataTable(records, -1); } internal DataTable RecordsToDataTable(ICollection records, int maxRecords) { DataTable res = CreateEmptyDataTable(); res.BeginLoadData(); res.MinimumCapacity = records.Count; if (maxRecords == -1) { foreach (object r in records) res.Rows.Add(RecordToValues(r)); } else { int i = 0; foreach (object r in records) { if (i == maxRecords) break; res.Rows.Add(RecordToValues(r)); i++; } } res.EndLoadData(); return res; } internal DataTable CreateEmptyDataTable() { DataTable res = new DataTable(); foreach (FieldBase f in mFields) { DataColumn column1; column1 = res.Columns.Add(f.mFieldInfo.Name, f.mFieldInfo.FieldType); column1.ReadOnly = true; } return res; } #endregion #endif #if NET_2_0 public static GetFieldValueCallback CreateGetFieldMethod(FieldInfo fi) { DynamicMethod dm = new DynamicMethod("_GetValue"+ fi.Name + "_FH_RT_", MethodAttributes.Static | MethodAttributes.Public, CallingConventions.Standard, typeof(object), new Type[] { typeof(object) }, fi.DeclaringType, true); ILGenerator generator = dm.GetILGenerator(); generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Castclass, fi.DeclaringType); generator.Emit(OpCodes.Ldfld, fi); generator.Emit(OpCodes.Ret); return (GetFieldValueCallback)dm.CreateDelegate(typeof(GetFieldValueCallback)); } #endif } }