/FileHelpers/Engines/MultiRecordEngine.cs

https://github.com/drupaltr/FileHelpers · C# · 1036 lines · 653 code · 201 blank · 182 comment · 121 complexity · f24dc1bd4745e8c86719246c34990963 MD5 · raw file

  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.ComponentModel;
  5. using System.Diagnostics;
  6. using System.IO;
  7. using System.Text;
  8. using FileHelpers.Events;
  9. using FileHelpers.MasterDetail;
  10. namespace FileHelpers
  11. {
  12. #region " Delegate "
  13. /// <summary>
  14. /// Delegate thats determines the Type of the current record (Master, Detail, Skip)
  15. /// </summary>
  16. /// <param name="recordString">The string of the current record.</param>
  17. /// <param name="engine">The engine that calls the selector.</param>
  18. /// <returns>the action used for the current record (Master, Detail, Skip)</returns>
  19. public delegate Type RecordTypeSelector(MultiRecordEngine engine, string recordString);
  20. #endregion
  21. /// <summary>
  22. /// <para>This engine allows you to parse and write files that contain
  23. /// records of different types and that are in a linear relationship</para>
  24. /// <para>(for Master-Detail check the <see cref="MasterDetailEngine"/>)</para>
  25. /// </summary>
  26. /// <seealso href="quick_start.html">Quick Start Guide</seealso>
  27. /// <seealso href="class_diagram.html">Class Diagram</seealso>
  28. /// <seealso href="examples.html">Examples of Use</seealso>
  29. /// <seealso href="attributes.html">Attributes List</seealso>
  30. [DebuggerDisplay(
  31. "MultiRecordEngine for types: {ListTypes()}. ErrorMode: {ErrorManager.ErrorMode.ToString()}. Encoding: {Encoding.EncodingName}"
  32. )]
  33. public sealed class MultiRecordEngine
  34. :
  35. EventEngineBase<object>,
  36. IEnumerable,
  37. IDisposable
  38. {
  39. [DebuggerBrowsable(DebuggerBrowsableState.Never)]
  40. private readonly IRecordInfo[] mMultiRecordInfo;
  41. [DebuggerBrowsable(DebuggerBrowsableState.Never)]
  42. private readonly Hashtable mRecordInfoHash;
  43. [DebuggerBrowsable(DebuggerBrowsableState.Never)]
  44. private RecordTypeSelector mRecordSelector;
  45. private string ListTypes()
  46. {
  47. string res = string.Empty;
  48. bool first = true;
  49. foreach (Type t in mRecordInfoHash.Keys) {
  50. if (first)
  51. first = false;
  52. else
  53. res += ", ";
  54. res += t.Name;
  55. }
  56. return res;
  57. }
  58. /// <summary>
  59. /// The Selector used by the engine in Read operations to determine the Type to use.
  60. /// </summary>
  61. public RecordTypeSelector RecordSelector
  62. {
  63. get { return mRecordSelector; }
  64. set { mRecordSelector = value; }
  65. }
  66. [DebuggerBrowsable(DebuggerBrowsableState.Never)]
  67. private readonly Type[] mTypes;
  68. #region " Constructor "
  69. /// <summary>Create a new instance of the MultiRecordEngine</summary>
  70. /// <param name="recordTypes">The Types of the records that this engine can handle.</param>
  71. public MultiRecordEngine(params Type[] recordTypes)
  72. : this(null, recordTypes) {}
  73. /// <summary>Create a new instance of the MultiRecordEngine</summary>
  74. /// <param name="recordTypes">The Types of the records that this engine can handle.</param>
  75. /// <param name="recordSelector">
  76. /// Used only in read operations. The selector indicates to the engine
  77. /// what Type to use in each read line.
  78. /// </param>
  79. public MultiRecordEngine(RecordTypeSelector recordSelector, params Type[] recordTypes)
  80. : base(GetFirstType(recordTypes))
  81. {
  82. mTypes = recordTypes;
  83. mMultiRecordInfo = new IRecordInfo[mTypes.Length];
  84. mRecordInfoHash = new Hashtable(mTypes.Length);
  85. for (int i = 0; i < mTypes.Length; i++) {
  86. if (mTypes[i] == null)
  87. throw new BadUsageException("The type at index " + i.ToString() + " is null.");
  88. if (mRecordInfoHash.Contains(mTypes[i])) {
  89. throw new BadUsageException("The type '" + mTypes[i].Name +
  90. " is already in the engine. You can't pass the same type twice to the constructor.");
  91. }
  92. mMultiRecordInfo[i] = FileHelpers.RecordInfo.Resolve(mTypes[i]);
  93. mRecordInfoHash.Add(mTypes[i], mMultiRecordInfo[i]);
  94. }
  95. mRecordSelector = recordSelector;
  96. }
  97. #endregion
  98. #region " Events "
  99. ///// <summary>Called in read operations just before the record string is translated to a record.</summary>
  100. //public event EventHandler<BeforeReadRecordEventArgs> BeforeReadRecord;
  101. ///// <summary>Called in read operations just after the record was created from a record string.</summary>
  102. //public event EventHandler<AfterReadRecordEventArgs> AfterReadRecord;
  103. ///// <summary>Called in write operations just before the record is converted to a string to write it.</summary>
  104. //public event EventHandler<BeforeWriteRecordEventArgs> BeforeWriteRecord;
  105. ///// <summary>Called in write operations just after the record was converted to a string.</summary>
  106. //public event EventHandler<AfterWriteRecordEventArgs> AfterWriteRecord;
  107. //private bool OnBeforeReadRecord(BeforeReadRecordEventArgs e)
  108. //{
  109. // if (BeforeReadRecord != null)
  110. // {
  111. // BeforeReadRecord(this, e);
  112. // return e.SkipThisRecord;
  113. // }
  114. // return false;
  115. //}
  116. //private bool OnAfterReadRecord(string line, object record)
  117. //{
  118. // if (mRecordInfo.NotifyRead)
  119. // ((INotifyRead)record).AfterRead(this, line);
  120. // if (AfterReadRecord != null)
  121. // {
  122. // AfterReadRecordEventArgs e = new AfterReadRecordEventArgs(line, record, LineNumber);
  123. // AfterReadRecord(this, e);
  124. // return e.SkipThisRecord;
  125. // }
  126. // return false;
  127. //}
  128. //private bool OnBeforeWriteRecord(object record)
  129. //{
  130. // if (mRecordInfo.NotifyWrite)
  131. // ((INotifyWrite)record).BeforeWrite(this);
  132. // if (BeforeWriteRecord != null)
  133. // {
  134. // BeforeWriteRecordEventArgs e = new BeforeWriteRecordEventArgs(record, LineNumber);
  135. // BeforeWriteRecord(this, e);
  136. // return e.SkipThisRecord;
  137. // }
  138. // return false;
  139. //}
  140. //private string OnAfterWriteRecord(string line, object record)
  141. //{
  142. // if (AfterWriteRecord != null)
  143. // {
  144. // AfterWriteRecordEventArgs e = new AfterWriteRecordEventArgs(record, LineNumber, line);
  145. // AfterWriteRecord(this, e);
  146. // return e.RecordLine;
  147. // }
  148. // return line;
  149. //}
  150. #endregion
  151. #region " ReadFile "
  152. /// <summary>
  153. /// Read a File and returns the records.
  154. /// </summary>
  155. /// <param name="fileName">The file with the records.</param>
  156. /// <returns>The read records of different types all mixed.</returns>
  157. public object[] ReadFile(string fileName)
  158. {
  159. using (var fs = new StreamReader(fileName, mEncoding, true, DefaultReadBufferSize))
  160. return ReadStream(fs);
  161. }
  162. #endregion
  163. #region " ReadStream "
  164. /// <summary>
  165. /// Read an array of objects from a stream
  166. /// </summary>
  167. /// <param name="reader">Stream we are reading from</param>
  168. /// <returns>Array of objects</returns>
  169. public object[] ReadStream(TextReader reader)
  170. {
  171. return ReadStream(new NewLineDelimitedRecordReader(reader));
  172. }
  173. /// <include file='MultiRecordEngine.docs.xml' path='doc/ReadStream/*'/>
  174. public object[] ReadStream(IRecordReader reader)
  175. {
  176. if (reader == null)
  177. throw new ArgumentNullException("reader", "The reader of the Stream can´t be null");
  178. if (mRecordSelector == null) {
  179. throw new BadUsageException(
  180. "The Recordselector can´t be null, please pass a not null Selector in the constructor.");
  181. }
  182. ResetFields();
  183. mHeaderText = String.Empty;
  184. mFooterText = String.Empty;
  185. var resArray = new ArrayList();
  186. using (var freader = new ForwardReader(reader, mMultiRecordInfo[0].IgnoreLast)) {
  187. freader.DiscardForward = true;
  188. string currentLine, completeLine;
  189. mLineNumber = 1;
  190. completeLine = freader.ReadNextLine();
  191. currentLine = completeLine;
  192. #if !MINI
  193. if (MustNotifyProgress) // Avoid object creation
  194. OnProgress(new ProgressEventArgs(0, -1));
  195. #endif
  196. int currentRecord = 0;
  197. if (mMultiRecordInfo[0].IgnoreFirst > 0) {
  198. for (int i = 0; i < mMultiRecordInfo[0].IgnoreFirst && currentLine != null; i++) {
  199. mHeaderText += currentLine + StringHelper.NewLine;
  200. currentLine = freader.ReadNextLine();
  201. mLineNumber++;
  202. }
  203. }
  204. bool byPass = false;
  205. var line = new LineInfo(currentLine) {
  206. mReader = freader
  207. };
  208. while (currentLine != null) {
  209. try {
  210. mTotalRecords++;
  211. currentRecord++;
  212. line.ReLoad(currentLine);
  213. var skip = false;
  214. Type currType = null;
  215. try {
  216. currType = mRecordSelector(this, currentLine);
  217. }
  218. catch (Exception ex) {
  219. throw new Exception("Selector failed to process correctly", ex);
  220. }
  221. if (currType != null) {
  222. var info = (RecordInfo) mRecordInfoHash[currType];
  223. if (info == null) {
  224. throw new BadUsageException("A record is of type '" + currType.Name +
  225. "' which this engine is not configured to handle. Try adding this type to the constructor.");
  226. }
  227. var record = info.Operations.CreateRecordHandler();
  228. #if !MINI
  229. if (MustNotifyProgress) // Avoid object creation
  230. OnProgress(new ProgressEventArgs(currentRecord, -1));
  231. BeforeReadEventArgs<object> e = null;
  232. if (MustNotifyRead) // Avoid object creation
  233. {
  234. e = new BeforeReadEventArgs<object>(this, record, currentLine, LineNumber);
  235. skip = OnBeforeReadRecord(e);
  236. if (e.RecordLineChanged)
  237. line.ReLoad(e.RecordLine);
  238. }
  239. #endif
  240. if (skip == false) {
  241. var values = new object[info.FieldCount];
  242. if (info.Operations.StringToRecord(record, line, values)) {
  243. #if !MINI
  244. if (MustNotifyRead) // Avoid object creation
  245. skip = OnAfterReadRecord(currentLine, record, e.RecordLineChanged, LineNumber);
  246. #endif
  247. if (skip == false)
  248. resArray.Add(record);
  249. }
  250. }
  251. }
  252. }
  253. catch (Exception ex) {
  254. switch (mErrorManager.ErrorMode) {
  255. case ErrorMode.ThrowException:
  256. byPass = true;
  257. throw;
  258. case ErrorMode.IgnoreAndContinue:
  259. break;
  260. case ErrorMode.SaveAndContinue:
  261. var err = new ErrorInfo {
  262. mLineNumber = freader.LineNumber,
  263. mExceptionInfo = ex,
  264. mRecordString = completeLine
  265. };
  266. // err.mColumnNumber = mColumnNum;
  267. mErrorManager.AddError(err);
  268. break;
  269. }
  270. }
  271. finally {
  272. if (byPass == false) {
  273. currentLine = freader.ReadNextLine();
  274. completeLine = currentLine;
  275. mLineNumber = freader.LineNumber;
  276. }
  277. }
  278. }
  279. if (mMultiRecordInfo[0].IgnoreLast > 0)
  280. mFooterText = freader.RemainingText;
  281. }
  282. return resArray.ToArray();
  283. }
  284. #endregion
  285. #region " ReadString "
  286. /// <include file='MultiRecordEngine.docs.xml' path='doc/ReadString/*'/>
  287. public object[] ReadString(string source)
  288. {
  289. var reader = new InternalStringReader(source);
  290. object[] res = ReadStream(reader);
  291. reader.Close();
  292. return res;
  293. }
  294. #endregion
  295. #region " WriteFile "
  296. /// <include file='MultiRecordEngine.docs.xml' path='doc/WriteFile/*'/>
  297. public void WriteFile(string fileName, IEnumerable records)
  298. {
  299. WriteFile(fileName, records, -1);
  300. }
  301. /// <include file='MultiRecordEngine.docs.xml' path='doc/WriteFile2/*'/>
  302. public void WriteFile(string fileName, IEnumerable records, int maxRecords)
  303. {
  304. using (var fs = new StreamWriter(fileName, false, mEncoding, DefaultWriteBufferSize)) {
  305. WriteStream(fs, records, maxRecords);
  306. fs.Close();
  307. }
  308. }
  309. #endregion
  310. #region " WriteStream "
  311. /// <summary>
  312. /// Write the records to a file
  313. /// </summary>
  314. /// <param name="writer">Where data is written</param>
  315. /// <param name="records">records to write to the file</param>
  316. public void WriteStream(TextWriter writer, IEnumerable records)
  317. {
  318. WriteStream(writer, records, -1);
  319. }
  320. /// <include file='MultiRecordEngine.docs.xml' path='doc/WriteStream2/*'/>
  321. public void WriteStream(TextWriter writer, IEnumerable records, int maxRecords)
  322. {
  323. if (writer == null)
  324. throw new ArgumentNullException("writer", "The writer of the Stream can be null");
  325. if (records == null)
  326. throw new ArgumentNullException("records", "The records can be null. Try with an empty array.");
  327. ResetFields();
  328. if (!string.IsNullOrEmpty(mHeaderText)) {
  329. if (mHeaderText.EndsWith(StringHelper.NewLine))
  330. writer.Write(mHeaderText);
  331. else
  332. writer.WriteLine(mHeaderText);
  333. }
  334. string currentLine = null;
  335. //ConstructorInfo constr = mType.GetConstructor(new Type[] {});
  336. int max = maxRecords;
  337. if (records is IList) {
  338. max = Math.Min(max < 0
  339. ? int.MaxValue
  340. : max,
  341. ((IList) records).Count);
  342. }
  343. #if !MINI
  344. if (MustNotifyProgress) // Avoid object creation
  345. OnProgress(new ProgressEventArgs(0, max));
  346. #endif
  347. int recIndex = 0;
  348. foreach (var rec in records) {
  349. if (recIndex == maxRecords)
  350. break;
  351. try {
  352. if (rec == null)
  353. throw new BadUsageException("The record at index " + recIndex.ToString() + " is null.");
  354. bool skip = false;
  355. #if !MINI
  356. if (MustNotifyProgress) // Avoid object creation
  357. OnProgress(new ProgressEventArgs(recIndex + 1, max));
  358. if (MustNotifyWrite)
  359. skip = OnBeforeWriteRecord(rec, LineNumber);
  360. #endif
  361. var info = (IRecordInfo) mRecordInfoHash[rec.GetType()];
  362. if (info == null) {
  363. throw new BadUsageException("The record at index " + recIndex.ToString() + " is of type '" +
  364. rec.GetType().Name +
  365. "' and the engine dont handle this type. You can add it to the constructor.");
  366. }
  367. if (skip == false) {
  368. currentLine = info.Operations.RecordToString(rec);
  369. #if !MINI
  370. if (MustNotifyWrite)
  371. currentLine = OnAfterWriteRecord(currentLine, rec);
  372. #endif
  373. writer.WriteLine(currentLine);
  374. }
  375. }
  376. catch (Exception ex) {
  377. switch (mErrorManager.ErrorMode) {
  378. case ErrorMode.ThrowException:
  379. throw;
  380. case ErrorMode.IgnoreAndContinue:
  381. break;
  382. case ErrorMode.SaveAndContinue:
  383. var err = new ErrorInfo {
  384. mLineNumber = mLineNumber,
  385. mExceptionInfo = ex,
  386. mRecordString = currentLine
  387. };
  388. // err.mColumnNumber = mColumnNum;
  389. mErrorManager.AddError(err);
  390. break;
  391. }
  392. }
  393. recIndex++;
  394. }
  395. mTotalRecords = recIndex;
  396. if (!string.IsNullOrEmpty(mFooterText)) {
  397. if (mFooterText.EndsWith(StringHelper.NewLine))
  398. writer.Write(mFooterText);
  399. else
  400. writer.WriteLine(mFooterText);
  401. }
  402. }
  403. #endregion
  404. #region " WriteString "
  405. /// <include file='MultiRecordEngine.docs.xml' path='doc/WriteString/*'/>
  406. public string WriteString(IEnumerable records)
  407. {
  408. return WriteString(records, -1);
  409. }
  410. /// <include file='MultiRecordEngine.docs.xml' path='doc/WriteString2/*'/>
  411. public string WriteString(IEnumerable records, int maxRecords)
  412. {
  413. var sb = new StringBuilder();
  414. var writer = new StringWriter(sb);
  415. WriteStream(writer, records, maxRecords);
  416. string res = writer.ToString();
  417. writer.Close();
  418. return res;
  419. }
  420. #endregion
  421. #region " AppendToFile "
  422. /// <include file='MultiRecordEngine.docs.xml' path='doc/AppendToFile1/*'/>
  423. public void AppendToFile(string fileName, object record)
  424. {
  425. AppendToFile(fileName, new object[] {record});
  426. }
  427. /// <include file='MultiRecordEngine.docs.xml' path='doc/AppendToFile2/*'/>
  428. public void AppendToFile(string fileName, IEnumerable records)
  429. {
  430. using (
  431. TextWriter writer = StreamHelper.CreateFileAppender(fileName,
  432. mEncoding,
  433. true,
  434. false,
  435. DefaultWriteBufferSize)) {
  436. mHeaderText = String.Empty;
  437. mFooterText = String.Empty;
  438. WriteStream(writer, records);
  439. writer.Close();
  440. }
  441. }
  442. #endregion
  443. private static Type GetFirstType(Type[] types)
  444. {
  445. if (types == null)
  446. throw new BadUsageException("A null Type[] is not valid for the MultiRecordEngine.");
  447. else if (types.Length == 0)
  448. throw new BadUsageException("An empty Type[] is not valid for the MultiRecordEngine.");
  449. else if (types.Length == 1) {
  450. throw new BadUsageException(
  451. "You only provide one type to the engine constructor. You need 2 or more types, for one type you can use the FileHelperEngine.");
  452. }
  453. else
  454. return types[0];
  455. }
  456. // ASYNC METHODS --------------
  457. [DebuggerBrowsable(DebuggerBrowsableState.Never)]
  458. private ForwardReader mAsyncReader;
  459. [DebuggerBrowsable(DebuggerBrowsableState.Never)]
  460. private TextWriter mAsyncWriter;
  461. #region " LastRecord "
  462. [DebuggerBrowsable(DebuggerBrowsableState.Never)]
  463. private object mLastRecord;
  464. /// <include file='FileHelperAsyncEngine.docs.xml' path='doc/LastRecord/*'/>
  465. public object LastRecord
  466. {
  467. get { return mLastRecord; }
  468. }
  469. #endregion
  470. #region " BeginReadStream"
  471. /// <summary>
  472. /// Read a generic file as delimited by newlines
  473. /// </summary>
  474. /// <param name="reader">Text reader</param>
  475. public void BeginReadStream(TextReader reader)
  476. {
  477. BeginReadStream(new NewLineDelimitedRecordReader(reader));
  478. }
  479. /// <summary>
  480. /// Method used to use this engine in Async mode. Work together with
  481. /// <see cref="ReadNext"/>. (Remember to call Close after read the
  482. /// data)
  483. /// </summary>
  484. /// <param name="reader">The source Reader.</param>
  485. [EditorBrowsable(EditorBrowsableState.Advanced)]
  486. public void BeginReadStream(IRecordReader reader)
  487. {
  488. if (reader == null)
  489. throw new ArgumentNullException("The TextReader can´t be null.");
  490. ResetFields();
  491. mHeaderText = String.Empty;
  492. mFooterText = String.Empty;
  493. if (RecordInfo.IgnoreFirst > 0) {
  494. for (int i = 0; i < RecordInfo.IgnoreFirst; i++) {
  495. string temp = reader.ReadRecordString();
  496. mLineNumber++;
  497. if (temp != null)
  498. mHeaderText += temp + StringHelper.NewLine;
  499. else
  500. break;
  501. }
  502. }
  503. mAsyncReader = new ForwardReader(reader, RecordInfo.IgnoreLast, mLineNumber) {
  504. DiscardForward = true
  505. };
  506. }
  507. #endregion
  508. #region " BeginReadFile "
  509. /// <summary>
  510. /// Method used to use this engine in Async mode. Work together with
  511. /// <see cref="ReadNext"/>. (Remember to call Close after read the
  512. /// data)
  513. /// </summary>
  514. /// <param name="fileName">The source file.</param>
  515. public void BeginReadFile(string fileName)
  516. {
  517. BeginReadStream(new StreamReader(fileName, mEncoding, true, DefaultReadBufferSize));
  518. }
  519. /// <summary>
  520. /// Method used to use this engine in Async mode. Work together with
  521. /// <see cref="ReadNext"/>. (Remember to call Close after read the
  522. /// data)
  523. /// </summary>
  524. /// <param name="sourceData">The source String</param>
  525. public void BeginReadString(string sourceData)
  526. {
  527. if (sourceData == null)
  528. sourceData = String.Empty;
  529. BeginReadStream(new InternalStringReader(sourceData));
  530. }
  531. #endregion
  532. /// <summary>
  533. /// Save all the buffered data for write to the disk.
  534. /// Useful to long opened async engines that wants to save pending
  535. /// values or for engines used for logging.
  536. /// </summary>
  537. public void Flush()
  538. {
  539. if (mAsyncWriter != null)
  540. mAsyncWriter.Flush();
  541. }
  542. #region " Close "
  543. /// <summary>
  544. /// Close the underlining Readers and Writers. (if any)
  545. /// </summary>
  546. public void Close()
  547. {
  548. try {
  549. if (mAsyncReader != null)
  550. mAsyncReader.Close();
  551. mAsyncReader = null;
  552. }
  553. catch {}
  554. try {
  555. if (mAsyncWriter != null) {
  556. if (!string.IsNullOrEmpty(mFooterText)) {
  557. if (mFooterText.EndsWith(StringHelper.NewLine))
  558. mAsyncWriter.Write(mFooterText);
  559. else
  560. mAsyncWriter.WriteLine(mFooterText);
  561. }
  562. mAsyncWriter.Close();
  563. mAsyncWriter = null;
  564. }
  565. }
  566. catch {}
  567. }
  568. #endregion
  569. // DIFFERENT FROM THE ASYNC ENGINE
  570. #region " ReadNext "
  571. /// <include file='FileHelperAsyncEngine.docs.xml' path='doc/ReadNext/*'/>
  572. public object ReadNext()
  573. {
  574. if (mAsyncReader == null)
  575. throw new BadUsageException("Before call ReadNext you must call BeginReadFile or BeginReadStream.");
  576. ReadNextRecord();
  577. return mLastRecord;
  578. }
  579. private void ReadNextRecord()
  580. {
  581. string currentLine = mAsyncReader.ReadNextLine();
  582. mLineNumber++;
  583. bool byPass = false;
  584. mLastRecord = null;
  585. var line = new LineInfo(currentLine) {
  586. mReader = mAsyncReader
  587. };
  588. while (true) {
  589. if (currentLine != null) {
  590. try {
  591. mTotalRecords++;
  592. Type currType = mRecordSelector(this, currentLine);
  593. line.ReLoad(currentLine);
  594. if (currType != null) {
  595. var info = (RecordInfo) mRecordInfoHash[currType];
  596. if (info == null) {
  597. throw new BadUsageException("A record is of type '" + currType.Name +
  598. "' which this engine is not configured to handle. Try adding this type to the constructor.");
  599. }
  600. var values = new object[info.FieldCount];
  601. mLastRecord = info.Operations.StringToRecord(line, values);
  602. if (mLastRecord != null) {
  603. byPass = true;
  604. return;
  605. }
  606. }
  607. }
  608. catch (Exception ex) {
  609. switch (mErrorManager.ErrorMode) {
  610. case ErrorMode.ThrowException:
  611. byPass = true;
  612. throw;
  613. case ErrorMode.IgnoreAndContinue:
  614. break;
  615. case ErrorMode.SaveAndContinue:
  616. var err = new ErrorInfo {
  617. mLineNumber = mAsyncReader.LineNumber,
  618. mExceptionInfo = ex,
  619. mRecordString = currentLine
  620. };
  621. // err.mColumnNumber = mColumnNum;
  622. mErrorManager.AddError(err);
  623. break;
  624. }
  625. }
  626. finally {
  627. if (byPass == false) {
  628. currentLine = mAsyncReader.ReadNextLine();
  629. mLineNumber = mAsyncReader.LineNumber;
  630. }
  631. }
  632. }
  633. else {
  634. mLastRecord = null;
  635. if (RecordInfo.IgnoreLast > 0)
  636. mFooterText = mAsyncReader.RemainingText;
  637. try {
  638. mAsyncReader.Close();
  639. }
  640. catch {}
  641. return;
  642. }
  643. }
  644. }
  645. /// <include file='FileHelperAsyncEngine.docs.xml' path='doc/ReadNexts/*'/>
  646. public object[] ReadNexts(int numberOfRecords)
  647. {
  648. if (mAsyncReader == null)
  649. throw new BadUsageException("Before call ReadNext you must call BeginReadFile or BeginReadStream.");
  650. var arr = new ArrayList(numberOfRecords);
  651. for (int i = 0; i < numberOfRecords; i++) {
  652. ReadNextRecord();
  653. if (mLastRecord != null)
  654. arr.Add(mLastRecord);
  655. else
  656. break;
  657. }
  658. return arr.ToArray();
  659. }
  660. #endregion
  661. #region " IEnumerable implementation "
  662. /// <summary>Allows to loop record by record in the engine</summary>
  663. /// <returns>The Enumerator</returns>
  664. IEnumerator IEnumerable.GetEnumerator()
  665. {
  666. if (mAsyncReader == null)
  667. throw new FileHelpersException("You must call BeginRead before use the engine in a foreach loop.");
  668. return new AsyncEnumerator(this);
  669. }
  670. private class AsyncEnumerator : IEnumerator
  671. {
  672. private readonly MultiRecordEngine mEngine;
  673. public AsyncEnumerator(MultiRecordEngine engine)
  674. {
  675. mEngine = engine;
  676. }
  677. public bool MoveNext()
  678. {
  679. object res = mEngine.ReadNext();
  680. if (res == null) {
  681. mEngine.Close();
  682. return false;
  683. }
  684. return true;
  685. }
  686. public object Current
  687. {
  688. get { return mEngine.mLastRecord; }
  689. }
  690. public void Reset()
  691. {
  692. // No needed
  693. }
  694. }
  695. #endregion
  696. #region " IDisposable implementation "
  697. /// <summary>
  698. /// Release Resources
  699. /// </summary>
  700. void IDisposable.Dispose()
  701. {
  702. Close();
  703. GC.SuppressFinalize(this);
  704. }
  705. /// <summary>Destructor</summary>
  706. ~MultiRecordEngine()
  707. {
  708. Close();
  709. }
  710. #endregion
  711. #region " WriteNext "
  712. /// <summary>
  713. /// Write the next record to a file or stream opened with
  714. /// <see cref="BeginWriteFile" />, <see cref="BeginWriteStream" /> or
  715. /// <see cref="BeginAppendToFile" /> method.
  716. /// </summary>
  717. /// <param name="record">The record to write.</param>
  718. public void WriteNext(object record)
  719. {
  720. if (mAsyncWriter == null)
  721. throw new BadUsageException("Before call WriteNext you must call BeginWriteFile or BeginWriteStream.");
  722. if (record == null)
  723. throw new BadUsageException("The record to write can´t be null.");
  724. WriteRecord(record);
  725. }
  726. /// <summary>
  727. /// Write the nexts records to a file or stream opened with
  728. /// <see cref="BeginWriteFile" />, <see cref="BeginWriteStream" /> or
  729. /// <see cref="BeginAppendToFile" /> method.
  730. /// </summary>
  731. /// <param name="records">The records to write (Can be an array, ArrayList, etc)</param>
  732. public void WriteNexts(IEnumerable records)
  733. {
  734. if (mAsyncWriter == null)
  735. throw new BadUsageException("Before call WriteNext you must call BeginWriteFile or BeginWriteStream.");
  736. if (records == null)
  737. throw new ArgumentNullException("The record to write can´t be null.");
  738. int nro = 0;
  739. foreach (var rec in records) {
  740. nro++;
  741. if (rec == null)
  742. throw new BadUsageException("The record at index " + nro.ToString() + " is null.");
  743. WriteRecord(rec);
  744. }
  745. }
  746. private void WriteRecord(object record)
  747. {
  748. string currentLine = null;
  749. try {
  750. mLineNumber++;
  751. mTotalRecords++;
  752. var info = (IRecordInfo) mRecordInfoHash[record.GetType()];
  753. if (info == null) {
  754. throw new BadUsageException("A record is of type '" + record.GetType().Name +
  755. "' and the engine dont handle this type. You can add it to the constructor.");
  756. }
  757. currentLine = info.Operations.RecordToString(record);
  758. mAsyncWriter.WriteLine(currentLine);
  759. }
  760. catch (Exception ex) {
  761. switch (mErrorManager.ErrorMode) {
  762. case ErrorMode.ThrowException:
  763. throw;
  764. case ErrorMode.IgnoreAndContinue:
  765. break;
  766. case ErrorMode.SaveAndContinue:
  767. var err = new ErrorInfo {
  768. mLineNumber = mLineNumber,
  769. mExceptionInfo = ex,
  770. mRecordString = currentLine
  771. };
  772. // err.mColumnNumber = mColumnNum;
  773. mErrorManager.AddError(err);
  774. break;
  775. }
  776. }
  777. }
  778. #endregion
  779. #region " BeginWriteStream"
  780. /// <summary>Set the stream to be used in the <see cref="WriteNext" /> operation.</summary>
  781. /// <remarks>
  782. /// <para>When you finish to write to the file you must call
  783. /// <b><see cref="Close" /></b> method.</para>
  784. /// </remarks>
  785. /// <param name="writer">To stream to writes to.</param>
  786. public void BeginWriteStream(TextWriter writer)
  787. {
  788. if (writer == null)
  789. throw new ArgumentException("The TextWriter can´t be null.", "writer");
  790. ResetFields();
  791. mAsyncWriter = writer;
  792. WriteHeader();
  793. }
  794. private void WriteHeader()
  795. {
  796. if (!string.IsNullOrEmpty(mHeaderText)) {
  797. if (mHeaderText.EndsWith(StringHelper.NewLine))
  798. mAsyncWriter.Write(mHeaderText);
  799. else
  800. mAsyncWriter.WriteLine(mHeaderText);
  801. }
  802. }
  803. #endregion
  804. #region " BeginWriteFile "
  805. /// <summary>
  806. /// Open a file for write operations. If exist the engine override it.
  807. /// You can use <see cref="WriteNext"/> or <see cref="WriteNexts"/> to
  808. /// write records.
  809. /// </summary>
  810. /// <remarks>
  811. /// <para>When you finish to write to the file you must call
  812. /// <b><see cref="Close" /></b> method.</para>
  813. /// </remarks>
  814. /// <param name="fileName">The file path to be opened for write.</param>
  815. public void BeginWriteFile(string fileName)
  816. {
  817. BeginWriteStream(new StreamWriter(fileName, false, mEncoding, DefaultWriteBufferSize));
  818. }
  819. #endregion
  820. #region " BeginappendToFile "
  821. /// <summary>Open a file to be appended at the end.</summary>
  822. /// <remarks><para>This method open and seek to the end the file.</para>
  823. /// <para>When you finish to append to the file you must call
  824. /// <b><see cref="Close" /></b> method.</para></remarks>
  825. /// <param name="fileName">The file path to be opened to write at the end.</param>
  826. public void BeginAppendToFile(string fileName)
  827. {
  828. mAsyncWriter = StreamHelper.CreateFileAppender(fileName, mEncoding, false, true, DefaultWriteBufferSize);
  829. mHeaderText = String.Empty;
  830. mFooterText = String.Empty;
  831. }
  832. #endregion
  833. }
  834. }
  835. //#endif