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 }