View Javadoc
1   /*
2    * Copyright (c) 2002-2014, Mairie de Paris
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    *
9    *  1. Redistributions of source code must retain the above copyright notice
10   *     and the following disclaimer.
11   *
12   *  2. Redistributions in binary form must reproduce the above copyright notice
13   *     and the following disclaimer in the documentation and/or other materials
14   *     provided with the distribution.
15   *
16   *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17   *     contributors may be used to endorse or promote products derived from
18   *     this software without specific prior written permission.
19   *
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30   * POSSIBILITY OF SUCH DAMAGE.
31   *
32   * License 1.0
33   */
34  package fr.paris.lutece.portal.service.csv;
35  
36  import au.com.bytecode.opencsv.CSVReader;
37  
38  import fr.paris.lutece.portal.business.file.File;
39  import fr.paris.lutece.portal.business.physicalfile.PhysicalFile;
40  import fr.paris.lutece.portal.business.physicalfile.PhysicalFileHome;
41  import fr.paris.lutece.portal.service.i18n.I18nService;
42  import fr.paris.lutece.portal.service.util.AppLogService;
43  import fr.paris.lutece.portal.service.util.AppPropertiesService;
44  
45  import org.apache.commons.fileupload.FileItem;
46  
47  import java.io.ByteArrayInputStream;
48  import java.io.FileNotFoundException;
49  import java.io.FileReader;
50  import java.io.IOException;
51  import java.io.InputStream;
52  import java.io.InputStreamReader;
53  import java.io.Reader;
54  
55  import java.util.ArrayList;
56  import java.util.Collections;
57  import java.util.Iterator;
58  import java.util.List;
59  import java.util.Locale;
60  
61  
62  /**
63   * Service to get data from a CSV file. The CSV might be a physical file, or a
64   * memory file.
65   * Implementations can either be statefull or stateless, but if the separator or
66   * the escape character are controlled by the user, then it has to be statefull.
67   */
68  public abstract class CSVReaderService
69  {
70      private static final String MESSAGE_NO_FILE_FOUND = "portal.util.message.noFileFound";
71      private static final String MESSAGE_ERROR_READING_FILE = "portal.util.message.errorReadingFile";
72      private static final String MESSAGE_ERROR_NUMBER_COLUMNS = "portal.xsl.message.errorNumberColumns";
73      private static final String MESSAGE_UNKOWN_ERROR = "portal.xsl.message.errorUnknown";
74      private static final String PROPERTY_DEFAULT_CSV_SEPARATOR = "lutece.csvReader.defaultCSVSeparator";
75      private static final String PROPERTY_DEFAULT_CSV_ESCAPE_CHARACTER = "lutece.csvReader.defaultCSVEscapeCharacter";
76      private static final String CONSTANT_DEFAULT_CSV_SEPARATOR = ";";
77      private static final String CONSTANT_DEFAULT_CSV_ESCAPE_CHARACTER = "\"";
78      private Character _strCSVSeparator;
79      private Character _strCSVEscapeCharacter;
80  
81      /**
82       * Read a line of the CSV file.
83       * @param strLineDataArray The content of the line of the CSV file.
84       * @param nLineNumber Number of the current line
85       * @param locale the locale
86       * @param strBaseUrl The base URL
87       * @return Returns the list of messages associated with the line.
88       */
89      protected abstract List<CSVMessageDescriptor> readLineOfCSVFile( String[] strLineDataArray, int nLineNumber,
90          Locale locale, String strBaseUrl );
91  
92      /**
93       * Check the line of the CSV file. This method is called once on each line
94       * of the file if the number of columns is correct. If the file is entirely
95       * checked before processing, then this method is called before any line is
96       * processed. Otherwise it is called just before the processing of the line.
97       * @param strLineDataArray The content of the line of the CSV file.
98       * @param nLineNumber Number of the current line
99       * @param locale the locale
100      * @return The list of messages of the lines. <strong>Lines that contain
101      *         messages with messages levels other than
102      *         {@link CSVMessageLevel#INFO INFO} will NOT be processed, and the
103      *         global processing may stop if the ExitOnError flag has been set
104      *         to true !</strong>
105      */
106     protected abstract List<CSVMessageDescriptor> checkLineOfCSVFile( String[] strLineDataArray, int nLineNumber,
107         Locale locale );
108 
109     /**
110      * Get messages after the process is completed.
111      * @param nNbLineParses The number of lines parses. If the first line was
112      *            skipped, it is not counted.
113      * @param nNbLinesWithoutErrors the number of lines parses whitout error.
114      * @param locale The locale
115      * @return A list of messages.
116      */
117     protected abstract List<CSVMessageDescriptor> getEndOfProcessMessages( int nNbLineParses,
118         int nNbLinesWithoutErrors, Locale locale );
119 
120     /**
121      * Get the default CSV separator to use. If the property of the default
122      * separator to use is not set, then the semi-colon is returned.
123      * @return the default CSV separator to use
124      */
125     public static Character getDefaultCSVSeparator(  )
126     {
127         return AppPropertiesService.getProperty( PROPERTY_DEFAULT_CSV_SEPARATOR, CONSTANT_DEFAULT_CSV_SEPARATOR )
128                                    .charAt( 0 );
129     }
130 
131     /**
132      * Get the default CSV escape character to use. If the property of the
133      * default escape character to use is not set, then the comma is returned.
134      * @return the default CSV escape character to use
135      */
136     public static Character getDefaultCSVEscapeCharacter(  )
137     {
138         return AppPropertiesService.getProperty( PROPERTY_DEFAULT_CSV_ESCAPE_CHARACTER,
139             CONSTANT_DEFAULT_CSV_ESCAPE_CHARACTER ).charAt( 0 );
140     }
141 
142     /**
143      * Read a CSV file and call the method
144      * {@link #readLineOfCSVFile(String[], int, Locale, String)
145      * readLineOfCSVFile} for
146      * each of its lines.
147      * @param fileItem FileItem to get the CSV file from. If the creation of the
148      *            input stream associated to this file throws a IOException,
149      *            then an error is returned and the file is not red.
150      * @param nColumnNumber Number of columns of each lines. Use 0 to skip
151      *            column number check (for example if every lines don't have the
152      *            same number of columns)
153      * @param bCheckFileBeforeProcessing Indicates if the file should be check
154      *            before processing any of its line. If it is set to true, then
155      *            then no line is processed if the file has any error.
156      * @param bExitOnError Indicates if the processing of the CSV file should
157      *            end on the first error, or at the end of the file.
158      * @param bSkipFirstLine Indicates if the first line of the file should be
159      *            skipped or not.
160      * @param locale the locale
161      * @param strBaseUrl The base URL
162      * @return Returns the list of errors that occurred during the processing of
163      *         the file. The returned list is sorted
164      * @see CSVMessageDescriptor#compareTo(CSVMessageDescriptor)
165      *      CSVMessageDescriptor.compareTo(CSVMessageDescriptor) for information
166      *      about sort
167      */
168     public List<CSVMessageDescriptor> readCSVFile( FileItem fileItem, int nColumnNumber,
169         boolean bCheckFileBeforeProcessing, boolean bExitOnError, boolean bSkipFirstLine, Locale locale,
170         String strBaseUrl )
171     {
172         if ( fileItem != null )
173         {
174             InputStreamReader inputStreamReader = null;
175 
176             try
177             {
178                 inputStreamReader = new InputStreamReader( fileItem.getInputStream(  ) );
179             }
180             catch ( IOException e )
181             {
182                 AppLogService.error( e.getMessage(  ), e );
183             }
184 
185             if ( inputStreamReader != null )
186             {
187                 CSVReader csvReader = new CSVReader( inputStreamReader, getCSVSeparator(  ), getCSVEscapeCharacter(  ) );
188 
189                 return readCSVFile( inputStreamReader, csvReader, nColumnNumber, bCheckFileBeforeProcessing,
190                     bExitOnError, bSkipFirstLine, locale, strBaseUrl );
191             }
192         }
193 
194         List<CSVMessageDescriptor> listErrors = new ArrayList<CSVMessageDescriptor>(  );
195         CSVMessageDescriptor errorDescription = new CSVMessageDescriptor( CSVMessageLevel.ERROR, 0,
196                 I18nService.getLocalizedString( MESSAGE_NO_FILE_FOUND, locale ) );
197         listErrors.add( errorDescription );
198 
199         return listErrors;
200     }
201 
202     /**
203      * Read a CSV file and call the method
204      * {@link #readLineOfCSVFile(String[], int, Locale, String)
205      * readLineOfCSVFile} for
206      * each of its lines.
207      * @param strPath Path if the file to read in the file system.
208      * @param nColumnNumber Number of columns of each lines. Use 0 to skip
209      *            column number check (for example if every lines don't have the
210      *            same number of columns)
211      * @param bCheckFileBeforeProcessing Indicates if the file should be check
212      *            before processing any of its line. If it is set to true, then
213      *            then no line is processed if the file has any error.
214      * @param bExitOnError Indicates if the processing of the CSV file should
215      *            end on the first error, or at the end of the file.
216      * @param bSkipFirstLine Indicates if the first line of the file should be
217      *            skipped or not.
218      * @param locale the locale
219      * @param strBaseUrl The base URL
220      * @return Returns the list of errors that occurred during the processing of
221      *         the file. The returned list is sorted
222      * @see CSVMessageDescriptor#compareTo(CSVMessageDescriptor)
223      *      CSVMessageDescriptor.compareTo(CSVMessageDescriptor) for information
224      *      about sort
225      */
226     public List<CSVMessageDescriptor> readCSVFile( String strPath, int nColumnNumber,
227         boolean bCheckFileBeforeProcessing, boolean bExitOnError, boolean bSkipFirstLine, Locale locale,
228         String strBaseUrl )
229     {
230         java.io.File file = new java.io.File( strPath );
231 
232         try
233         {
234             FileReader fileReader = new FileReader( file );
235             CSVReader csvReader = new CSVReader( fileReader, getCSVSeparator(  ), getCSVEscapeCharacter(  ) );
236 
237             return readCSVFile( fileReader, csvReader, nColumnNumber, bCheckFileBeforeProcessing, bExitOnError,
238                 bSkipFirstLine, locale, strBaseUrl );
239         }
240         catch ( FileNotFoundException e )
241         {
242             AppLogService.error( e.getMessage(  ), e );
243         }
244 
245         List<CSVMessageDescriptor> listErrors = new ArrayList<CSVMessageDescriptor>(  );
246         CSVMessageDescriptor errorDescription = new CSVMessageDescriptor( CSVMessageLevel.ERROR, 0,
247                 I18nService.getLocalizedString( MESSAGE_NO_FILE_FOUND, locale ) );
248         listErrors.add( errorDescription );
249 
250         return listErrors;
251     }
252 
253     /**
254      * Read a CSV file and call the method
255      * {@link #readLineOfCSVFile(String[], int, Locale, String)
256      * readLineOfCSVFile} for
257      * each of its lines.
258      * @param file File to get the values from. If the physical file of this
259      *            file has no value, then it is gotten from the database.
260      * @param nColumnNumber Number of columns of each lines. Use 0 to skip
261      *            column number check (for example if every lines don't have the
262      *            same number of columns)
263      * @param bCheckFileBeforeProcessing Indicates if the file should be check
264      *            before processing any of its line. If it is set to true, then
265      *            then no line is processed if the file has any error.
266      * @param bExitOnError Indicates if the processing of the CSV file should
267      *            end on the first error, or at the end of the file.
268      * @param bSkipFirstLine Indicates if the first line of the file should be
269      *            skipped or not.
270      * @param locale the locale
271      * @param strBaseUrl The base URL
272      * @return Returns the list of errors that occurred during the processing of
273      *         the file. The returned list is sorted
274      * @see CSVMessageDescriptor#compareTo(CSVMessageDescriptor)
275      *      CSVMessageDescriptor.compareTo(CSVMessageDescriptor) for information
276      *      about sort
277      */
278     public List<CSVMessageDescriptor> readCSVFile( File file, int nColumnNumber, boolean bCheckFileBeforeProcessing,
279         boolean bExitOnError, boolean bSkipFirstLine, Locale locale, String strBaseUrl )
280     {
281         return readCSVFile( file.getPhysicalFile(  ), nColumnNumber, bCheckFileBeforeProcessing, bExitOnError,
282             bSkipFirstLine, locale, strBaseUrl );
283     }
284 
285     /**
286      * Read a CSV file and call the method
287      * {@link #readLineOfCSVFile(String[], int, Locale, String)
288      * readLineOfCSVFile} for
289      * each of its lines.
290      * @param physicalFile The physicalFile to get the values from. If the
291      *            physical file has no value, then it is gotten from the
292      *            database.
293      * @param nColumnNumber Number of columns of each lines. Use 0 to skip
294      *            column number check (for example if every lines don't have the
295      *            same number of columns)
296      * @param bCheckFileBeforeProcessing Indicates if the file should be check
297      *            before processing any of its line. If it is set to true, then
298      *            then no line is processed if the file has any error.
299      * @param bExitOnError Indicates if the processing of the CSV file should
300      *            end on the first error, or at the end of the file.
301      * @param bSkipFirstLine Indicates if the first line of the file should be
302      *            skipped or not.
303      * @param locale the locale
304      * @param strBaseUrl The base URL
305      * @return Returns the list of errors that occurred during the processing of
306      *         the file. The returned list is sorted
307      * @see CSVMessageDescriptor#compareTo(CSVMessageDescriptor)
308      *      CSVMessageDescriptor.compareTo(CSVMessageDescriptor) for information
309      *      about sort
310      */
311     public List<CSVMessageDescriptor> readCSVFile( PhysicalFile physicalFile, int nColumnNumber,
312         boolean bCheckFileBeforeProcessing, boolean bExitOnError, boolean bSkipFirstLine, Locale locale,
313         String strBaseUrl )
314     {
315         PhysicalFile importedPhysicalFile = physicalFile;
316 
317         if ( ( importedPhysicalFile != null ) && ( importedPhysicalFile.getValue(  ) == null ) )
318         {
319             if ( importedPhysicalFile.getValue(  ) == null )
320             {
321                 importedPhysicalFile = PhysicalFileHome.findByPrimaryKey( importedPhysicalFile.getIdPhysicalFile(  ) );
322             }
323 
324             if ( ( importedPhysicalFile != null ) && ( importedPhysicalFile.getValue(  ) == null ) )
325             {
326                 InputStream inputStream = new ByteArrayInputStream( importedPhysicalFile.getValue(  ) );
327                 InputStreamReader inputStreamReader = new InputStreamReader( inputStream );
328                 CSVReader csvReader = new CSVReader( inputStreamReader, getCSVSeparator(  ), getCSVEscapeCharacter(  ) );
329 
330                 return readCSVFile( inputStreamReader, csvReader, nColumnNumber, bCheckFileBeforeProcessing,
331                     bExitOnError, bSkipFirstLine, locale, strBaseUrl );
332             }
333         }
334 
335         List<CSVMessageDescriptor> listErrors = new ArrayList<CSVMessageDescriptor>(  );
336         CSVMessageDescriptor errorDescription = new CSVMessageDescriptor( CSVMessageLevel.ERROR, 0,
337                 I18nService.getLocalizedString( MESSAGE_NO_FILE_FOUND, locale ) );
338         listErrors.add( errorDescription );
339 
340         return listErrors;
341     }
342 
343     /**
344      * Read a CSV file and call the method {@link #readLineOfCSVFile(String[])
345      * readLineOfCSVFile} for each
346      * of its lines.
347      * @param reader The file reader that was used to create the CSV reader.
348      *            This reader will be closed by this method
349      * @param csvReader CSV reader to use to read the CSV file
350      * @param nColumnNumber Number of columns of each lines. Use 0 to skip
351      *            column number check (for example if every lines don't have the
352      *            same number of columns)
353      * @param bCheckFileBeforeProcessing Indicates if the file should be check
354      *            before processing any of its line. If it is set to true, then
355      *            then no line is processed if the file has any error.
356      * @param bExitOnError Indicates if the processing of the CSV file should
357      *            end on the first error, or at the end of the file.
358      * @param bSkipFirstLine Indicates if the first line of the file should be
359      *            skipped or not.
360      * @param locale the locale
361      * @param strBaseUrl The base URL
362      * @return Returns the list of errors that occurred during the processing of
363      *         the file. The returned list is sorted
364      * @see CSVMessageDescriptor#compareTo(CSVMessageDescriptor)
365      *      CSVMessageDescriptor.compareTo(CSVMessageDescriptor) for information
366      *      about sort
367      */
368     protected List<CSVMessageDescriptor> readCSVFile( Reader reader, CSVReader csvReader, int nColumnNumber,
369         boolean bCheckFileBeforeProcessing, boolean bExitOnError, boolean bSkipFirstLine, Locale locale,
370         String strBaseUrl )
371     {
372         List<CSVMessageDescriptor> listMessages = new ArrayList<CSVMessageDescriptor>(  );
373         int nLineNumber = 0;
374 
375         if ( bSkipFirstLine )
376         {
377             try
378             {
379                 nLineNumber++;
380                 csvReader.readNext(  );
381             }
382             catch ( IOException e )
383             {
384                 AppLogService.error( e.getMessage(  ), e );
385 
386                 CSVMessageDescriptor error = new CSVMessageDescriptor( CSVMessageLevel.ERROR, 1,
387                         I18nService.getLocalizedString( MESSAGE_ERROR_READING_FILE, locale ) );
388                 listMessages.add( error );
389 
390                 if ( bExitOnError )
391                 {
392                     try
393                     {
394                         csvReader.close(  );
395                         reader.close(  );
396                     }
397                     catch ( IOException ex )
398                     {
399                         AppLogService.error( ex.getMessage(  ), ex );
400                     }
401 
402                     return listMessages;
403                 }
404             }
405         }
406 
407         List<String[]> listLines = null;
408 
409         if ( bCheckFileBeforeProcessing )
410         {
411             listLines = new ArrayList<String[]>(  );
412 
413             String[] strLine = null;
414 
415             do
416             {
417                 try
418                 {
419                     nLineNumber++;
420                     strLine = csvReader.readNext(  );
421                 }
422                 catch ( IOException e )
423                 {
424                     AppLogService.error( e.getMessage(  ), e );
425 
426                     CSVMessageDescriptor error = new CSVMessageDescriptor( CSVMessageLevel.ERROR, nLineNumber,
427                             I18nService.getLocalizedString( MESSAGE_ERROR_READING_FILE, locale ) );
428                     listMessages.add( error );
429 
430                     if ( bExitOnError )
431                     {
432                         try
433                         {
434                             csvReader.close(  );
435                             reader.close(  );
436                         }
437                         catch ( IOException ex )
438                         {
439                             AppLogService.error( ex.getMessage(  ), ex );
440                         }
441 
442                         Collections.sort( listMessages );
443 
444                         return listMessages;
445                     }
446                 }
447 
448                 if ( strLine != null )
449                 {
450                     listLines.add( strLine );
451                 }
452             }
453             while ( strLine != null );
454 
455             List<CSVMessageDescriptor> listCheckErrors = checkCSVFileValidity( listLines, nColumnNumber,
456                     bSkipFirstLine, locale );
457 
458             if ( listCheckErrors.size(  ) > 0 )
459             {
460                 if ( doesListMessageContainError( listCheckErrors ) )
461                 {
462                     listCheckErrors.addAll( 0, listMessages );
463 
464                     try
465                     {
466                         csvReader.close(  );
467                         reader.close(  );
468                     }
469                     catch ( IOException ex )
470                     {
471                         AppLogService.error( ex.getMessage(  ), ex );
472                     }
473 
474                     Collections.sort( listMessages );
475 
476                     return listCheckErrors;
477                 }
478             }
479 
480             nLineNumber = 0;
481         }
482 
483         boolean bHasMoreLines = true;
484         int nNbLinesWithoutErrors = 0;
485         String[] strLine = null;
486         Iterator<String[]> iterator = null;
487 
488         if ( listLines != null )
489         {
490             iterator = listLines.iterator(  );
491         }
492 
493         while ( bHasMoreLines )
494         {
495             nLineNumber++;
496 
497             if ( iterator != null )
498             {
499                 if ( iterator.hasNext(  ) )
500                 {
501                     strLine = iterator.next(  );
502                 }
503                 else
504                 {
505                     strLine = null;
506                     bHasMoreLines = false;
507                 }
508             }
509             else
510             {
511                 try
512                 {
513                     strLine = csvReader.readNext(  );
514                 }
515                 catch ( IOException e )
516                 {
517                     strLine = null;
518                     AppLogService.error( e.getMessage(  ), e );
519 
520                     CSVMessageDescriptor error = new CSVMessageDescriptor( CSVMessageLevel.ERROR, nLineNumber,
521                             I18nService.getLocalizedString( MESSAGE_ERROR_READING_FILE, locale ) );
522                     listMessages.add( error );
523 
524                     if ( bExitOnError )
525                     {
526                         bHasMoreLines = false;
527                     }
528                 }
529             }
530 
531             if ( strLine != null )
532             {
533                 try
534                 {
535                     List<CSVMessageDescriptor> listLinesMessages = null;
536 
537                     if ( !bCheckFileBeforeProcessing )
538                     {
539                         listLinesMessages = checkCSVLineColumnNumber( strLine, nColumnNumber, nLineNumber, locale );
540 
541                         if ( !doesListMessageContainError( listLinesMessages ) )
542                         {
543                             List<CSVMessageDescriptor> listFileCheckMessages = checkLineOfCSVFile( strLine,
544                                     nLineNumber, locale );
545 
546                             if ( ( listFileCheckMessages != null ) && ( listFileCheckMessages.size(  ) > 0 ) )
547                             {
548                                 if ( ( listLinesMessages != null ) && ( listLinesMessages.size(  ) > 0 ) )
549                                 {
550                                     listLinesMessages.addAll( listFileCheckMessages );
551                                 }
552                                 else
553                                 {
554                                     listLinesMessages = listFileCheckMessages;
555                                 }
556                             }
557                         }
558 
559                         if ( ( listLinesMessages != null ) && ( listLinesMessages.size(  ) > 0 ) )
560                         {
561                             listMessages.addAll( listLinesMessages );
562                         }
563                     }
564 
565                     // If the line has no error
566                     if ( !doesListMessageContainError( listLinesMessages ) )
567                     {
568                         List<CSVMessageDescriptor> listMessagesOfCurrentLine = readLineOfCSVFile( strLine, nLineNumber,
569                                 locale, strBaseUrl );
570 
571                         if ( ( listMessagesOfCurrentLine != null ) && ( listMessagesOfCurrentLine.size(  ) > 0 ) )
572                         {
573                             listMessages.addAll( listMessagesOfCurrentLine );
574                         }
575 
576                         if ( doesListMessageContainError( listMessagesOfCurrentLine ) )
577                         {
578                             if ( bExitOnError )
579                             {
580                                 bHasMoreLines = false;
581                             }
582                         }
583                         else
584                         {
585                             nNbLinesWithoutErrors++;
586                         }
587                     }
588                 }
589                 catch ( Exception e )
590                 {
591                     AppLogService.error( e.getMessage(  ), e );
592 
593                     CSVMessageDescriptor error = new CSVMessageDescriptor( CSVMessageLevel.ERROR, nLineNumber,
594                             I18nService.getLocalizedString( MESSAGE_UNKOWN_ERROR, locale ) );
595                     listMessages.add( error );
596 
597                     if ( bExitOnError )
598                     {
599                         bHasMoreLines = false;
600                     }
601                 }
602             }
603             else
604             {
605                 bHasMoreLines = false;
606             }
607         }
608 
609         try
610         {
611             csvReader.close(  );
612             reader.close(  );
613         }
614         catch ( IOException ex )
615         {
616             AppLogService.error( ex.getMessage(  ), ex );
617         }
618 
619         // We incremented the line number for the last line that didn't exist
620         nLineNumber--;
621 
622         if ( bSkipFirstLine )
623         {
624             nLineNumber--;
625         }
626 
627         List<CSVMessageDescriptor> listMessagesEndOfProcess = getEndOfProcessMessages( nLineNumber,
628                 nNbLinesWithoutErrors, locale );
629 
630         if ( ( listMessagesEndOfProcess != null ) && ( listMessagesEndOfProcess.size(  ) > 0 ) )
631         {
632             listMessages.addAll( 0, listMessagesEndOfProcess );
633         }
634 
635         Collections.sort( listMessages );
636 
637         return listMessages;
638     }
639 
640     /**
641      * Check the validity of the whole CSV file.
642      * @param listLines The list of lines of the file
643      * @param nColumnNumber The number of columns every line must have.
644      * @param bSkipFirstLine True if the first line should be ignored, false
645      *            otherwise
646      * @param locale The locale
647      * @return Returns a list of errors found in the file.
648      */
649     protected List<CSVMessageDescriptor> checkCSVFileValidity( List<String[]> listLines, int nColumnNumber,
650         boolean bSkipFirstLine, Locale locale )
651     {
652         List<CSVMessageDescriptor> listErrors = new ArrayList<CSVMessageDescriptor>(  );
653         int nLineNumber = 0;
654 
655         if ( bSkipFirstLine )
656         {
657             nLineNumber++;
658         }
659 
660         for ( String[] strLine : listLines )
661         {
662             nLineNumber++;
663 
664             List<CSVMessageDescriptor> listMessages = checkCSVLineColumnNumber( strLine, nColumnNumber, nLineNumber,
665                     locale );
666 
667             if ( ( listMessages != null ) && ( listMessages.size(  ) > 0 ) )
668             {
669                 listErrors.addAll( listMessages );
670             }
671 
672             if ( !doesListMessageContainError( listMessages ) )
673             {
674                 listMessages = checkLineOfCSVFile( strLine, nLineNumber, locale );
675 
676                 if ( ( listMessages != null ) && ( listMessages.size(  ) > 0 ) )
677                 {
678                     listErrors.addAll( listMessages );
679                 }
680             }
681         }
682 
683         return listErrors;
684     }
685 
686     /**
687      * Check the number of columns of a line.
688      * @param strLine The line to check
689      * @param nColumnNumber The number of columns the line must have
690      * @param nLineNumber The number of the current line
691      * @param locale The locale
692      * @return The error if an error is found, or null if there is none.
693      */
694     protected List<CSVMessageDescriptor> checkCSVLineColumnNumber( String[] strLine, int nColumnNumber,
695         int nLineNumber, Locale locale )
696     {
697         if ( ( strLine == null ) || ( ( nColumnNumber > 0 ) && ( strLine.length != nColumnNumber ) ) )
698         {
699             List<CSVMessageDescriptor> listMessages = new ArrayList<CSVMessageDescriptor>(  );
700             Object[] args = { ( strLine == null ) ? 0 : strLine.length, nColumnNumber };
701             String strErrorMessage = I18nService.getLocalizedString( MESSAGE_ERROR_NUMBER_COLUMNS, args, locale );
702             CSVMessageDescriptor error = new CSVMessageDescriptor( CSVMessageLevel.ERROR, nLineNumber, strErrorMessage );
703             listMessages.add( error );
704 
705             return listMessages;
706         }
707 
708         return null;
709     }
710 
711     /**
712      * Get the separator used for CSV files. If no separator has been set, then
713      * the default CSV separator is used.
714      * @return the separator used for CSV files, of the default one if non has
715      *         been set.
716      */
717     public Character getCSVSeparator(  )
718     {
719         if ( this._strCSVSeparator == null )
720         {
721             this._strCSVSeparator = getDefaultCSVSeparator(  );
722         }
723 
724         return _strCSVSeparator;
725     }
726 
727     /**
728      * Set the separator to use for CSV files.
729      * @param strCSVSeparator The separator to use for CSV files.
730      */
731     public void setCSVSeparator( Character strCSVSeparator )
732     {
733         this._strCSVSeparator = strCSVSeparator;
734     }
735 
736     /**
737      * Get the escape character used for CSV files. If no escape character has
738      * been set, then the default CSV escape character is used.
739      * @return the escape character used for CSV files, of the default one if
740      *         non has been set.
741      */
742     public Character getCSVEscapeCharacter(  )
743     {
744         if ( this._strCSVEscapeCharacter == null )
745         {
746             this._strCSVEscapeCharacter = getDefaultCSVEscapeCharacter(  );
747         }
748 
749         return _strCSVEscapeCharacter;
750     }
751 
752     /**
753      * Set the escape character to use for CSV files.
754      * @param strCSVEscapeCharacter The escape character to use for CSV files.
755      */
756     public void setCSVEscapeCharacter( Character strCSVEscapeCharacter )
757     {
758         this._strCSVEscapeCharacter = strCSVEscapeCharacter;
759     }
760 
761     /**
762      * Check if a list of messages contains messages with the
763      * {@link CSVMessageLevel#ERROR ERROR} level
764      * @param listMessageOfCurrentLine The list of messages. The list might be
765      *            null or empty.
766      * @return True if an error is found, false otherwise
767      */
768     private boolean doesListMessageContainError( List<CSVMessageDescriptor> listMessageOfCurrentLine )
769     {
770         if ( ( listMessageOfCurrentLine != null ) && ( listMessageOfCurrentLine.size(  ) > 0 ) )
771         {
772             for ( CSVMessageDescriptor message : listMessageOfCurrentLine )
773             {
774                 if ( message.getMessageLevel(  ) == CSVMessageLevel.ERROR )
775                 {
776                     return true;
777                 }
778             }
779         }
780 
781         return false;
782     }
783 }