001package gudusoft.gsqlparser.util.csv; 002 003import java.io.BufferedReader; 004import java.io.File; 005import java.io.FileInputStream; 006import java.io.FileNotFoundException; 007import java.io.IOException; 008import java.io.InputStream; 009import java.io.InputStreamReader; 010import java.io.Reader; 011import java.io.StringReader; 012import java.nio.charset.Charset; 013import java.text.NumberFormat; 014import java.util.HashMap; 015 016/** 017 * A stream based parser for parsing delimited text data from a file or a 018 * stream. 019 */ 020public class CsvReader { 021 private Reader inputStream = null; 022 023 private String fileName = null; 024 025 // this holds all the values for switches that the user is allowed to set 026 private UserSettings userSettings = new UserSettings(); 027 028 private Charset charset = null; 029 030 private boolean useCustomRecordDelimiter = false; 031 032 // this will be our working buffer to hold data chunks 033 // read in from the data file 034 035 private DataBuffer dataBuffer = new DataBuffer(); 036 037 private ColumnBuffer columnBuffer = new ColumnBuffer(); 038 039 private RawRecordBuffer rawBuffer = new RawRecordBuffer(); 040 041 private boolean[] isQualified = null; 042 043 private String rawRecord = ""; 044 045 private HeadersHolder headersHolder = new HeadersHolder(); 046 047 // these are all more or less global loop variables 048 // to keep from needing to pass them all into various 049 // methods during parsing 050 051 private boolean startedColumn = false; 052 053 private boolean startedWithQualifier = false; 054 055 private boolean hasMoreData = true; 056 057 private char lastLetter = '\0'; 058 059 private boolean hasReadNextLine = false; 060 061 private int columnsCount = 0; 062 063 private long currentRecord = 0; 064 065 private String[] values = new String[StaticSettings.INITIAL_COLUMN_COUNT]; 066 067 private boolean initialized = false; 068 069 private boolean closed = false; 070 071 /** 072 * Double up the text qualifier to represent an occurance of the text 073 * qualifier. 074 */ 075 public static final int ESCAPE_MODE_DOUBLED = 1; 076 077 /** 078 * Use a backslash character before the text qualifier to represent an 079 * occurance of the text qualifier. 080 */ 081 public static final int ESCAPE_MODE_BACKSLASH = 2; 082 083 /** 084 * Creates a {@link CsvReader CsvReader} object using a file 085 * as the data source. 086 * 087 * @param fileName 088 * The path to the file to use as the data source. 089 * @param delimiter 090 * The character to use as the column delimiter. 091 * @param charset 092 * The {@link java.nio.charset.Charset Charset} to use while 093 * parsing the data. 094 */ 095 public CsvReader(String fileName, char delimiter, Charset charset) 096 throws FileNotFoundException { 097 if (fileName == null) { 098 throw new IllegalArgumentException( 099 "Parameter fileName can not be null."); 100 } 101 102 if (charset == null) { 103 throw new IllegalArgumentException( 104 "Parameter charset can not be null."); 105 } 106 107 if (!new File(fileName).exists()) { 108 throw new FileNotFoundException("File " + fileName 109 + " does not exist."); 110 } 111 112 this.fileName = fileName; 113 this.userSettings.Delimiter = delimiter; 114 this.charset = charset; 115 116 isQualified = new boolean[values.length]; 117 } 118 119 /** 120 * Creates a {@link CsvReader CsvReader} object using a file 121 * as the data source. Uses ISO-8859-1 as the 122 * {@link java.nio.charset.Charset Charset}. 123 * 124 * @param fileName 125 * The path to the file to use as the data source. 126 * @param delimiter 127 * The character to use as the column delimiter. 128 */ 129 public CsvReader(String fileName, char delimiter) 130 throws FileNotFoundException { 131 this(fileName, delimiter, Charset.forName("ISO-8859-1")); 132 } 133 134 /** 135 * Creates a {@link CsvReader CsvReader} object using a file 136 * as the data source. Uses a comma as the column delimiter and 137 * ISO-8859-1 as the {@link java.nio.charset.Charset Charset}. 138 * 139 * @param fileName 140 * The path to the file to use as the data source. 141 */ 142 public CsvReader(String fileName) throws FileNotFoundException { 143 this(fileName, Letters.COMMA); 144 } 145 146 /** 147 * Constructs a {@link CsvReader CsvReader} object using a 148 * {@link java.io.Reader Reader} object as the data source. 149 * 150 * @param inputStream 151 * The stream to use as the data source. 152 * @param delimiter 153 * The character to use as the column delimiter. 154 */ 155 public CsvReader(Reader inputStream, char delimiter) { 156 if (inputStream == null) { 157 throw new IllegalArgumentException( 158 "Parameter inputStream can not be null."); 159 } 160 161 this.inputStream = inputStream; 162 this.userSettings.Delimiter = delimiter; 163 initialized = true; 164 165 isQualified = new boolean[values.length]; 166 } 167 168 /** 169 * Constructs a {@link CsvReader CsvReader} object using a 170 * {@link java.io.Reader Reader} object as the data source. Uses a 171 * comma as the column delimiter. 172 * 173 * @param inputStream 174 * The stream to use as the data source. 175 */ 176 public CsvReader(Reader inputStream) { 177 this(inputStream, Letters.COMMA); 178 } 179 180 /** 181 * Constructs a {@link CsvReader CsvReader} object using an 182 * {@link java.io.InputStream InputStream} object as the data source. 183 * 184 * @param inputStream 185 * The stream to use as the data source. 186 * @param delimiter 187 * The character to use as the column delimiter. 188 * @param charset 189 * The {@link java.nio.charset.Charset Charset} to use while 190 * parsing the data. 191 */ 192 public CsvReader(InputStream inputStream, char delimiter, Charset charset) { 193 this(new InputStreamReader(inputStream, charset), delimiter); 194 } 195 196 /** 197 * Constructs a {@link CsvReader CsvReader} object using an 198 * {@link java.io.InputStream InputStream} object as the data 199 * source. Uses a comma as the column delimiter. 200 * 201 * @param inputStream 202 * The stream to use as the data source. 203 * @param charset 204 * The {@link java.nio.charset.Charset Charset} to use while 205 * parsing the data. 206 */ 207 public CsvReader(InputStream inputStream, Charset charset) { 208 this(new InputStreamReader(inputStream, charset)); 209 } 210 211 public boolean getCaptureRawRecord() { 212 return userSettings.CaptureRawRecord; 213 } 214 215 public void setCaptureRawRecord(boolean captureRawRecord) { 216 userSettings.CaptureRawRecord = captureRawRecord; 217 } 218 219 public String getRawRecord() { 220 return rawRecord; 221 } 222 223 /** 224 * Gets whether leading and trailing whitespace characters are being trimmed 225 * from non-textqualified column data. Default is true. 226 * 227 * @return Whether leading and trailing whitespace characters are being 228 * trimmed from non-textqualified column data. 229 */ 230 public boolean getTrimWhitespace() { 231 return userSettings.TrimWhitespace; 232 } 233 234 /** 235 * Sets whether leading and trailing whitespace characters should be trimmed 236 * from non-textqualified column data or not. Default is true. 237 * 238 * @param trimWhitespace 239 * Whether leading and trailing whitespace characters should be 240 * trimmed from non-textqualified column data or not. 241 */ 242 public void setTrimWhitespace(boolean trimWhitespace) { 243 userSettings.TrimWhitespace = trimWhitespace; 244 } 245 246 /** 247 * Gets the character being used as the column delimiter. Default is comma, 248 * ','. 249 * 250 * @return The character being used as the column delimiter. 251 */ 252 public char getDelimiter() { 253 return userSettings.Delimiter; 254 } 255 256 /** 257 * Sets the character to use as the column delimiter. Default is comma, ','. 258 * 259 * @param delimiter 260 * The character to use as the column delimiter. 261 */ 262 public void setDelimiter(char delimiter) { 263 userSettings.Delimiter = delimiter; 264 } 265 266 public char getRecordDelimiter() { 267 return userSettings.RecordDelimiter; 268 } 269 270 /** 271 * Sets the character to use as the record delimiter. 272 * 273 * @param recordDelimiter 274 * The character to use as the record delimiter. Default is 275 * combination of standard end of line characters for Windows, 276 * Unix, or Mac. 277 */ 278 public void setRecordDelimiter(char recordDelimiter) { 279 useCustomRecordDelimiter = true; 280 userSettings.RecordDelimiter = recordDelimiter; 281 } 282 283 /** 284 * Gets the character to use as a text qualifier in the data. 285 * 286 * @return The character to use as a text qualifier in the data. 287 */ 288 public char getTextQualifier() { 289 return userSettings.TextQualifier; 290 } 291 292 /** 293 * Sets the character to use as a text qualifier in the data. 294 * 295 * @param textQualifier 296 * The character to use as a text qualifier in the data. 297 */ 298 public void setTextQualifier(char textQualifier) { 299 userSettings.TextQualifier = textQualifier; 300 } 301 302 /** 303 * Whether text qualifiers will be used while parsing or not. 304 * 305 * @return Whether text qualifiers will be used while parsing or not. 306 */ 307 public boolean getUseTextQualifier() { 308 return userSettings.UseTextQualifier; 309 } 310 311 /** 312 * Sets whether text qualifiers will be used while parsing or not. 313 * 314 * @param useTextQualifier 315 * Whether to use a text qualifier while parsing or not. 316 */ 317 public void setUseTextQualifier(boolean useTextQualifier) { 318 userSettings.UseTextQualifier = useTextQualifier; 319 } 320 321 /** 322 * Gets the character being used as a comment signal. 323 * 324 * @return The character being used as a comment signal. 325 */ 326 public char getComment() { 327 return userSettings.Comment; 328 } 329 330 /** 331 * Sets the character to use as a comment signal. 332 * 333 * @param comment 334 * The character to use as a comment signal. 335 */ 336 public void setComment(char comment) { 337 userSettings.Comment = comment; 338 } 339 340 /** 341 * Gets whether comments are being looked for while parsing or not. 342 * 343 * @return Whether comments are being looked for while parsing or not. 344 */ 345 public boolean getUseComments() { 346 return userSettings.UseComments; 347 } 348 349 /** 350 * Sets whether comments are being looked for while parsing or not. 351 * 352 * @param useComments 353 * Whether comments are being looked for while parsing or not. 354 */ 355 public void setUseComments(boolean useComments) { 356 userSettings.UseComments = useComments; 357 } 358 359 /** 360 * Gets the current way to escape an occurance of the text qualifier inside 361 * qualified data. 362 * 363 * @return The current way to escape an occurance of the text qualifier 364 * inside qualified data. 365 */ 366 public int getEscapeMode() { 367 return userSettings.EscapeMode; 368 } 369 370 /** 371 * Sets the current way to escape an occurance of the text qualifier inside 372 * qualified data. 373 * 374 * @param escapeMode 375 * The way to escape an occurance of the text qualifier inside 376 * qualified data. 377 * @exception IllegalArgumentException 378 * When an illegal value is specified for escapeMode. 379 */ 380 public void setEscapeMode(int escapeMode) throws IllegalArgumentException { 381 if (escapeMode != ESCAPE_MODE_DOUBLED 382 && escapeMode != ESCAPE_MODE_BACKSLASH) { 383 throw new IllegalArgumentException( 384 "Parameter escapeMode must be a valid value."); 385 } 386 387 userSettings.EscapeMode = escapeMode; 388 } 389 390 public boolean getSkipEmptyRecords() { 391 return userSettings.SkipEmptyRecords; 392 } 393 394 public void setSkipEmptyRecords(boolean skipEmptyRecords) { 395 userSettings.SkipEmptyRecords = skipEmptyRecords; 396 } 397 398 /** 399 * Safety caution to prevent the parser from using large amounts of memory 400 * in the case where parsing settings like file encodings don't end up 401 * matching the actual format of a file. This switch can be turned off if 402 * the file format is known and tested. With the switch off, the max column 403 * lengths and max column count per record supported by the parser will 404 * greatly increase. Default is true. 405 * 406 * @return The current setting of the safety switch. 407 */ 408 public boolean getSafetySwitch() { 409 return userSettings.SafetySwitch; 410 } 411 412 /** 413 * Safety caution to prevent the parser from using large amounts of memory 414 * in the case where parsing settings like file encodings don't end up 415 * matching the actual format of a file. This switch can be turned off if 416 * the file format is known and tested. With the switch off, the max column 417 * lengths and max column count per record supported by the parser will 418 * greatly increase. Default is true. 419 * 420 * @param safetySwitch 421 */ 422 public void setSafetySwitch(boolean safetySwitch) { 423 userSettings.SafetySwitch = safetySwitch; 424 } 425 426 /** 427 * Gets the count of columns found in this record. 428 * 429 * @return The count of columns found in this record. 430 */ 431 public int getColumnCount() { 432 return columnsCount; 433 } 434 435 /** 436 * Gets the index of the current record. 437 * 438 * @return The index of the current record. 439 */ 440 public long getCurrentRecord() { 441 return currentRecord - 1; 442 } 443 444 /** 445 * Gets the count of headers read in by a previous call to 446 * {@link CsvReader#readHeaders readHeaders()}. 447 * 448 * @return The count of headers read in by a previous call to 449 * {@link CsvReader#readHeaders readHeaders()}. 450 */ 451 public int getHeaderCount() { 452 return headersHolder.Length; 453 } 454 455 /** 456 * Returns the header values as a string array. 457 * 458 * @return The header values as a String array. 459 * @exception IOException 460 * Thrown if this object has already been closed. 461 */ 462 public String[] getHeaders() throws IOException { 463 checkClosed(); 464 465 if (headersHolder.Headers == null) { 466 return null; 467 } else { 468 // use clone here to prevent the outside code from 469 // setting values on the array directly, which would 470 // throw off the index lookup based on header name 471 String[] clone = new String[headersHolder.Length]; 472 System.arraycopy(headersHolder.Headers, 0, clone, 0, 473 headersHolder.Length); 474 return clone; 475 } 476 } 477 478 public void setHeaders(String[] headers) { 479 headersHolder.Headers = headers; 480 481 headersHolder.IndexByName.clear(); 482 483 if (headers != null) { 484 headersHolder.Length = headers.length; 485 } else { 486 headersHolder.Length = 0; 487 } 488 489 // use headersHolder.Length here in case headers is null 490 for (int i = 0; i < headersHolder.Length; i++) { 491 headersHolder.IndexByName.put(headers[i], new Integer(i)); 492 } 493 } 494 495 public String[] getValues() throws IOException { 496 checkClosed(); 497 498 // need to return a clone, and can't use clone because values.Length 499 // might be greater than columnsCount 500 String[] clone = new String[columnsCount]; 501 System.arraycopy(values, 0, clone, 0, columnsCount); 502 return clone; 503 } 504 505 /** 506 * Returns the current column value for a given column index. 507 * 508 * @param columnIndex 509 * The index of the column. 510 * @return The current column value. 511 * @exception IOException 512 * Thrown if this object has already been closed. 513 */ 514 public String get(int columnIndex) throws IOException { 515 checkClosed(); 516 517 if (columnIndex > -1 && columnIndex < columnsCount) { 518 return values[columnIndex]; 519 } else { 520 return ""; 521 } 522 } 523 524 /** 525 * Returns the current column value for a given column header name. 526 * 527 * @param headerName 528 * The header name of the column. 529 * @return The current column value. 530 * @exception IOException 531 * Thrown if this object has already been closed. 532 */ 533 public String get(String headerName) throws IOException { 534 checkClosed(); 535 536 return get(getIndex(headerName)); 537 } 538 539 /** 540 * Creates a {@link CsvReader CsvReader} object using a string 541 * of data as the source. Uses ISO-8859-1 as the 542 * {@link java.nio.charset.Charset Charset}. 543 * 544 * @param data 545 * The String of data to use as the source. 546 * @return A {@link CsvReader CsvReader} object using the 547 * String of data as the source. 548 */ 549 public static CsvReader parse(String data) { 550 if (data == null) { 551 throw new IllegalArgumentException( 552 "Parameter data can not be null."); 553 } 554 555 return new CsvReader(new StringReader(data)); 556 } 557 558 /** 559 * Reads another record. 560 * 561 * @return Whether another record was successfully read or not. 562 * @exception IOException 563 * Thrown if an error occurs while reading data from the 564 * source stream. 565 */ 566 public boolean readRecord() throws IOException { 567 checkClosed(); 568 569 columnsCount = 0; 570 rawBuffer.Position = 0; 571 572 dataBuffer.LineStart = dataBuffer.Position; 573 574 hasReadNextLine = false; 575 576 // check to see if we've already found the end of data 577 578 if (hasMoreData) { 579 // loop over the data stream until the end of data is found 580 // or the end of the record is found 581 582 do { 583 if (dataBuffer.Position == dataBuffer.Count) { 584 checkDataLength(); 585 } else { 586 startedWithQualifier = false; 587 588 // grab the current letter as a char 589 590 char currentLetter = dataBuffer.Buffer[dataBuffer.Position]; 591 592 if (userSettings.UseTextQualifier 593 && currentLetter == userSettings.TextQualifier) { 594 // this will be a text qualified column, so 595 // we need to set startedWithQualifier to make it 596 // enter the seperate branch to handle text 597 // qualified columns 598 599 lastLetter = currentLetter; 600 601 // read qualified 602 startedColumn = true; 603 dataBuffer.ColumnStart = dataBuffer.Position + 1; 604 startedWithQualifier = true; 605 boolean lastLetterWasQualifier = false; 606 607 char escapeChar = userSettings.TextQualifier; 608 609 if (userSettings.EscapeMode == ESCAPE_MODE_BACKSLASH) { 610 escapeChar = Letters.BACKSLASH; 611 } 612 613 boolean eatingTrailingJunk = false; 614 boolean lastLetterWasEscape = false; 615 boolean readingComplexEscape = false; 616 int escape = ComplexEscape.UNICODE; 617 int escapeLength = 0; 618 char escapeValue = (char) 0; 619 620 dataBuffer.Position++; 621 622 do { 623 if (dataBuffer.Position == dataBuffer.Count) { 624 checkDataLength(); 625 } else { 626 // grab the current letter as a char 627 628 currentLetter = dataBuffer.Buffer[dataBuffer.Position]; 629 630 if (eatingTrailingJunk) { 631 dataBuffer.ColumnStart = dataBuffer.Position + 1; 632 633 if (currentLetter == userSettings.Delimiter) { 634 endColumn(); 635 } else if ((!useCustomRecordDelimiter && (currentLetter == Letters.CR || currentLetter == Letters.LF)) 636 || (useCustomRecordDelimiter && currentLetter == userSettings.RecordDelimiter)) { 637 endColumn(); 638 639 endRecord(); 640 } 641 } else if (readingComplexEscape) { 642 escapeLength++; 643 644 switch (escape) { 645 case ComplexEscape.UNICODE: 646 escapeValue *= (char) 16; 647 escapeValue += hexToDec(currentLetter); 648 649 if (escapeLength == 4) { 650 readingComplexEscape = false; 651 } 652 653 break; 654 case ComplexEscape.OCTAL: 655 escapeValue *= (char) 8; 656 escapeValue += (char) (currentLetter - '0'); 657 658 if (escapeLength == 3) { 659 readingComplexEscape = false; 660 } 661 662 break; 663 case ComplexEscape.DECIMAL: 664 escapeValue *= (char) 10; 665 escapeValue += (char) (currentLetter - '0'); 666 667 if (escapeLength == 3) { 668 readingComplexEscape = false; 669 } 670 671 break; 672 case ComplexEscape.HEX: 673 escapeValue *= (char) 16; 674 escapeValue += hexToDec(currentLetter); 675 676 if (escapeLength == 2) { 677 readingComplexEscape = false; 678 } 679 680 break; 681 } 682 683 if (!readingComplexEscape) { 684 appendLetter(escapeValue); 685 } else { 686 dataBuffer.ColumnStart = dataBuffer.Position + 1; 687 } 688 } else if (currentLetter == userSettings.TextQualifier) { 689 if (lastLetterWasEscape) { 690 lastLetterWasEscape = false; 691 lastLetterWasQualifier = false; 692 } else { 693 updateCurrentValue(); 694 695 if (userSettings.EscapeMode == ESCAPE_MODE_DOUBLED) { 696 lastLetterWasEscape = true; 697 } 698 699 lastLetterWasQualifier = true; 700 } 701 } else if (userSettings.EscapeMode == ESCAPE_MODE_BACKSLASH 702 && lastLetterWasEscape) { 703 switch (currentLetter) { 704 case 'n': 705 appendLetter(Letters.LF); 706 break; 707 case 'r': 708 appendLetter(Letters.CR); 709 break; 710 case 't': 711 appendLetter(Letters.TAB); 712 break; 713 case 'b': 714 appendLetter(Letters.BACKSPACE); 715 break; 716 case 'f': 717 appendLetter(Letters.FORM_FEED); 718 break; 719 case 'e': 720 appendLetter(Letters.ESCAPE); 721 break; 722 case 'v': 723 appendLetter(Letters.VERTICAL_TAB); 724 break; 725 case 'a': 726 appendLetter(Letters.ALERT); 727 break; 728 case '0': 729 case '1': 730 case '2': 731 case '3': 732 case '4': 733 case '5': 734 case '6': 735 case '7': 736 escape = ComplexEscape.OCTAL; 737 readingComplexEscape = true; 738 escapeLength = 1; 739 escapeValue = (char) (currentLetter - '0'); 740 dataBuffer.ColumnStart = dataBuffer.Position + 1; 741 break; 742 case 'u': 743 case 'x': 744 case 'o': 745 case 'd': 746 case 'U': 747 case 'X': 748 case 'O': 749 case 'D': 750 switch (currentLetter) { 751 case 'u': 752 case 'U': 753 escape = ComplexEscape.UNICODE; 754 break; 755 case 'x': 756 case 'X': 757 escape = ComplexEscape.HEX; 758 break; 759 case 'o': 760 case 'O': 761 escape = ComplexEscape.OCTAL; 762 break; 763 case 'd': 764 case 'D': 765 escape = ComplexEscape.DECIMAL; 766 break; 767 } 768 769 readingComplexEscape = true; 770 escapeLength = 0; 771 escapeValue = (char) 0; 772 dataBuffer.ColumnStart = dataBuffer.Position + 1; 773 774 break; 775 default: 776 break; 777 } 778 779 lastLetterWasEscape = false; 780 781 // can only happen for ESCAPE_MODE_BACKSLASH 782 } else if (currentLetter == escapeChar) { 783 updateCurrentValue(); 784 lastLetterWasEscape = true; 785 } else { 786 if (lastLetterWasQualifier) { 787 if (currentLetter == userSettings.Delimiter) { 788 endColumn(); 789 } else if ((!useCustomRecordDelimiter && (currentLetter == Letters.CR || currentLetter == Letters.LF)) 790 || (useCustomRecordDelimiter && currentLetter == userSettings.RecordDelimiter)) { 791 endColumn(); 792 793 endRecord(); 794 } else { 795 dataBuffer.ColumnStart = dataBuffer.Position + 1; 796 797 eatingTrailingJunk = true; 798 } 799 800 // make sure to clear the flag for next 801 // run of the loop 802 803 lastLetterWasQualifier = false; 804 } 805 } 806 807 // keep track of the last letter because we need 808 // it for several key decisions 809 810 lastLetter = currentLetter; 811 812 if (startedColumn) { 813 dataBuffer.Position++; 814 815 if (userSettings.SafetySwitch 816 && dataBuffer.Position 817 - dataBuffer.ColumnStart 818 + columnBuffer.Position > 100000) { 819 close(); 820 821 throw new IOException( 822 "Maximum column length of 100,000 exceeded in column " 823 + NumberFormat 824 .getIntegerInstance() 825 .format( 826 columnsCount) 827 + " in record " 828 + NumberFormat 829 .getIntegerInstance() 830 .format( 831 currentRecord) 832 + ". Set the SafetySwitch property to false" 833 + " if you're expecting column lengths greater than 100,000 characters to" 834 + " avoid this error."); 835 } 836 } 837 } // end else 838 839 } while (hasMoreData && startedColumn); 840 } else if (currentLetter == userSettings.Delimiter) { 841 // we encountered a column with no data, so 842 // just send the end column 843 844 lastLetter = currentLetter; 845 846 endColumn(); 847 } else if (useCustomRecordDelimiter 848 && currentLetter == userSettings.RecordDelimiter) { 849 // this will skip blank lines 850 if (startedColumn || columnsCount > 0 851 || !userSettings.SkipEmptyRecords) { 852 endColumn(); 853 854 endRecord(); 855 } else { 856 dataBuffer.LineStart = dataBuffer.Position + 1; 857 } 858 859 lastLetter = currentLetter; 860 } else if (!useCustomRecordDelimiter 861 && (currentLetter == Letters.CR || currentLetter == Letters.LF)) { 862 // this will skip blank lines 863 if (startedColumn 864 || columnsCount > 0 865 || (!userSettings.SkipEmptyRecords && (currentLetter == Letters.CR || lastLetter != Letters.CR))) { 866 endColumn(); 867 868 endRecord(); 869 } else { 870 dataBuffer.LineStart = dataBuffer.Position + 1; 871 } 872 873 lastLetter = currentLetter; 874 } else if (userSettings.UseComments && columnsCount == 0 875 && currentLetter == userSettings.Comment) { 876 // encountered a comment character at the beginning of 877 // the line so just ignore the rest of the line 878 879 lastLetter = currentLetter; 880 881 skipLine(); 882 } else if (userSettings.TrimWhitespace 883 && (currentLetter == Letters.SPACE || currentLetter == Letters.TAB)) { 884 // do nothing, this will trim leading whitespace 885 // for both text qualified columns and non 886 887 startedColumn = true; 888 dataBuffer.ColumnStart = dataBuffer.Position + 1; 889 } else { 890 // since the letter wasn't a special letter, this 891 // will be the first letter of our current column 892 893 startedColumn = true; 894 dataBuffer.ColumnStart = dataBuffer.Position; 895 boolean lastLetterWasBackslash = false; 896 boolean readingComplexEscape = false; 897 int escape = ComplexEscape.UNICODE; 898 int escapeLength = 0; 899 char escapeValue = (char) 0; 900 901 boolean firstLoop = true; 902 903 do { 904 if (!firstLoop 905 && dataBuffer.Position == dataBuffer.Count) { 906 checkDataLength(); 907 } else { 908 if (!firstLoop) { 909 // grab the current letter as a char 910 currentLetter = dataBuffer.Buffer[dataBuffer.Position]; 911 } 912 913 if (!userSettings.UseTextQualifier 914 && userSettings.EscapeMode == ESCAPE_MODE_BACKSLASH 915 && currentLetter == Letters.BACKSLASH) { 916 if (lastLetterWasBackslash) { 917 lastLetterWasBackslash = false; 918 } else { 919 updateCurrentValue(); 920 lastLetterWasBackslash = true; 921 } 922 } else if (readingComplexEscape) { 923 escapeLength++; 924 925 switch (escape) { 926 case ComplexEscape.UNICODE: 927 escapeValue *= (char) 16; 928 escapeValue += hexToDec(currentLetter); 929 930 if (escapeLength == 4) { 931 readingComplexEscape = false; 932 } 933 934 break; 935 case ComplexEscape.OCTAL: 936 escapeValue *= (char) 8; 937 escapeValue += (char) (currentLetter - '0'); 938 939 if (escapeLength == 3) { 940 readingComplexEscape = false; 941 } 942 943 break; 944 case ComplexEscape.DECIMAL: 945 escapeValue *= (char) 10; 946 escapeValue += (char) (currentLetter - '0'); 947 948 if (escapeLength == 3) { 949 readingComplexEscape = false; 950 } 951 952 break; 953 case ComplexEscape.HEX: 954 escapeValue *= (char) 16; 955 escapeValue += hexToDec(currentLetter); 956 957 if (escapeLength == 2) { 958 readingComplexEscape = false; 959 } 960 961 break; 962 } 963 964 if (!readingComplexEscape) { 965 appendLetter(escapeValue); 966 } else { 967 dataBuffer.ColumnStart = dataBuffer.Position + 1; 968 } 969 } else if (userSettings.EscapeMode == ESCAPE_MODE_BACKSLASH 970 && lastLetterWasBackslash) { 971 switch (currentLetter) { 972 case 'n': 973 appendLetter(Letters.LF); 974 break; 975 case 'r': 976 appendLetter(Letters.CR); 977 break; 978 case 't': 979 appendLetter(Letters.TAB); 980 break; 981 case 'b': 982 appendLetter(Letters.BACKSPACE); 983 break; 984 case 'f': 985 appendLetter(Letters.FORM_FEED); 986 break; 987 case 'e': 988 appendLetter(Letters.ESCAPE); 989 break; 990 case 'v': 991 appendLetter(Letters.VERTICAL_TAB); 992 break; 993 case 'a': 994 appendLetter(Letters.ALERT); 995 break; 996 case '0': 997 case '1': 998 case '2': 999 case '3': 1000 case '4': 1001 case '5': 1002 case '6': 1003 case '7': 1004 escape = ComplexEscape.OCTAL; 1005 readingComplexEscape = true; 1006 escapeLength = 1; 1007 escapeValue = (char) (currentLetter - '0'); 1008 dataBuffer.ColumnStart = dataBuffer.Position + 1; 1009 break; 1010 case 'u': 1011 case 'x': 1012 case 'o': 1013 case 'd': 1014 case 'U': 1015 case 'X': 1016 case 'O': 1017 case 'D': 1018 switch (currentLetter) { 1019 case 'u': 1020 case 'U': 1021 escape = ComplexEscape.UNICODE; 1022 break; 1023 case 'x': 1024 case 'X': 1025 escape = ComplexEscape.HEX; 1026 break; 1027 case 'o': 1028 case 'O': 1029 escape = ComplexEscape.OCTAL; 1030 break; 1031 case 'd': 1032 case 'D': 1033 escape = ComplexEscape.DECIMAL; 1034 break; 1035 } 1036 1037 readingComplexEscape = true; 1038 escapeLength = 0; 1039 escapeValue = (char) 0; 1040 dataBuffer.ColumnStart = dataBuffer.Position + 1; 1041 1042 break; 1043 default: 1044 break; 1045 } 1046 1047 lastLetterWasBackslash = false; 1048 } else { 1049 if (currentLetter == userSettings.Delimiter) { 1050 endColumn(); 1051 } else if ((!useCustomRecordDelimiter && (currentLetter == Letters.CR || currentLetter == Letters.LF)) 1052 || (useCustomRecordDelimiter && currentLetter == userSettings.RecordDelimiter)) { 1053 endColumn(); 1054 1055 endRecord(); 1056 } 1057 } 1058 1059 // keep track of the last letter because we need 1060 // it for several key decisions 1061 1062 lastLetter = currentLetter; 1063 firstLoop = false; 1064 1065 if (startedColumn) { 1066 dataBuffer.Position++; 1067 1068 if (userSettings.SafetySwitch 1069 && dataBuffer.Position 1070 - dataBuffer.ColumnStart 1071 + columnBuffer.Position > 100000) { 1072 close(); 1073 1074 throw new IOException( 1075 "Maximum column length of 100,000 exceeded in column " 1076 + NumberFormat 1077 .getIntegerInstance() 1078 .format( 1079 columnsCount) 1080 + " in record " 1081 + NumberFormat 1082 .getIntegerInstance() 1083 .format( 1084 currentRecord) 1085 + ". Set the SafetySwitch property to false" 1086 + " if you're expecting column lengths greater than 100,000 characters to" 1087 + " avoid this error."); 1088 } 1089 } 1090 } // end else 1091 } while (hasMoreData && startedColumn); 1092 } 1093 1094 if (hasMoreData) { 1095 dataBuffer.Position++; 1096 } 1097 } // end else 1098 } while (hasMoreData && !hasReadNextLine); 1099 1100 // check to see if we hit the end of the file 1101 // without processing the current record 1102 1103 if (startedColumn || lastLetter == userSettings.Delimiter) { 1104 endColumn(); 1105 1106 endRecord(); 1107 } 1108 } 1109 1110 if (userSettings.CaptureRawRecord) { 1111 if (hasMoreData) { 1112 if (rawBuffer.Position == 0) { 1113 rawRecord = new String(dataBuffer.Buffer, 1114 dataBuffer.LineStart, dataBuffer.Position 1115 - dataBuffer.LineStart - 1); 1116 } else { 1117 rawRecord = new String(rawBuffer.Buffer, 0, 1118 rawBuffer.Position) 1119 + new String(dataBuffer.Buffer, 1120 dataBuffer.LineStart, dataBuffer.Position 1121 - dataBuffer.LineStart - 1); 1122 } 1123 } else { 1124 // for hasMoreData to ever be false, all data would have had to 1125 // have been 1126 // copied to the raw buffer 1127 rawRecord = new String(rawBuffer.Buffer, 0, rawBuffer.Position); 1128 } 1129 } else { 1130 rawRecord = ""; 1131 } 1132 1133 return hasReadNextLine; 1134 } 1135 1136 /** 1137 * @exception IOException 1138 * Thrown if an error occurs while reading data from the 1139 * source stream. 1140 */ 1141 private void checkDataLength() throws IOException { 1142 if (!initialized) { 1143 if (fileName != null) { 1144 inputStream = new BufferedReader(new InputStreamReader( 1145 new FileInputStream(fileName), charset), 1146 StaticSettings.MAX_FILE_BUFFER_SIZE); 1147 } 1148 1149 charset = null; 1150 initialized = true; 1151 } 1152 1153 updateCurrentValue(); 1154 1155 if (userSettings.CaptureRawRecord && dataBuffer.Count > 0) { 1156 if (rawBuffer.Buffer.length - rawBuffer.Position < dataBuffer.Count 1157 - dataBuffer.LineStart) { 1158 int newLength = rawBuffer.Buffer.length 1159 + Math.max(dataBuffer.Count - dataBuffer.LineStart, 1160 rawBuffer.Buffer.length); 1161 1162 char[] holder = new char[newLength]; 1163 1164 System.arraycopy(rawBuffer.Buffer, 0, holder, 0, 1165 rawBuffer.Position); 1166 1167 rawBuffer.Buffer = holder; 1168 } 1169 1170 System.arraycopy(dataBuffer.Buffer, dataBuffer.LineStart, 1171 rawBuffer.Buffer, rawBuffer.Position, dataBuffer.Count 1172 - dataBuffer.LineStart); 1173 1174 rawBuffer.Position += dataBuffer.Count - dataBuffer.LineStart; 1175 } 1176 1177 try { 1178 dataBuffer.Count = inputStream.read(dataBuffer.Buffer, 0, 1179 dataBuffer.Buffer.length); 1180 } catch (IOException ex) { 1181 close(); 1182 1183 throw ex; 1184 } 1185 1186 // if no more data could be found, set flag stating that 1187 // the end of the data was found 1188 1189 if (dataBuffer.Count == -1) { 1190 hasMoreData = false; 1191 } 1192 1193 dataBuffer.Position = 0; 1194 dataBuffer.LineStart = 0; 1195 dataBuffer.ColumnStart = 0; 1196 } 1197 1198 /** 1199 * Read the first record of data as column headers. 1200 * 1201 * @return Whether the header record was successfully read or not. 1202 * @exception IOException 1203 * Thrown if an error occurs while reading data from the 1204 * source stream. 1205 */ 1206 public boolean readHeaders() throws IOException { 1207 boolean result = readRecord(); 1208 1209 // copy the header data from the column array 1210 // to the header string array 1211 1212 headersHolder.Length = columnsCount; 1213 1214 headersHolder.Headers = new String[columnsCount]; 1215 1216 for (int i = 0; i < headersHolder.Length; i++) { 1217 String columnValue = get(i); 1218 1219 headersHolder.Headers[i] = columnValue; 1220 1221 // if there are duplicate header names, we will save the last one 1222 headersHolder.IndexByName.put(columnValue, new Integer(i)); 1223 } 1224 1225 if (result) { 1226 currentRecord--; 1227 } 1228 1229 columnsCount = 0; 1230 1231 return result; 1232 } 1233 1234 /** 1235 * Returns the column header value for a given column index. 1236 * 1237 * @param columnIndex 1238 * The index of the header column being requested. 1239 * @return The value of the column header at the given column index. 1240 * @exception IOException 1241 * Thrown if this object has already been closed. 1242 */ 1243 public String getHeader(int columnIndex) throws IOException { 1244 checkClosed(); 1245 1246 // check to see if we have read the header record yet 1247 1248 // check to see if the column index is within the bounds 1249 // of our header array 1250 1251 if (columnIndex > -1 && columnIndex < headersHolder.Length) { 1252 // return the processed header data for this column 1253 1254 return headersHolder.Headers[columnIndex]; 1255 } else { 1256 return ""; 1257 } 1258 } 1259 1260 public boolean isQualified(int columnIndex) throws IOException { 1261 checkClosed(); 1262 1263 if (columnIndex < columnsCount && columnIndex > -1) { 1264 return isQualified[columnIndex]; 1265 } else { 1266 return false; 1267 } 1268 } 1269 1270 /** 1271 * @exception IOException 1272 * Thrown if a very rare extreme exception occurs during 1273 * parsing, normally resulting from improper data format. 1274 */ 1275 private void endColumn() throws IOException { 1276 String currentValue = ""; 1277 1278 // must be called before setting startedColumn = false 1279 if (startedColumn) { 1280 if (columnBuffer.Position == 0) { 1281 if (dataBuffer.ColumnStart < dataBuffer.Position) { 1282 int lastLetter = dataBuffer.Position - 1; 1283 1284 if (userSettings.TrimWhitespace && !startedWithQualifier) { 1285 while (lastLetter >= dataBuffer.ColumnStart 1286 && (dataBuffer.Buffer[lastLetter] == Letters.SPACE || dataBuffer.Buffer[lastLetter] == Letters.TAB)) { 1287 lastLetter--; 1288 } 1289 } 1290 1291 currentValue = new String(dataBuffer.Buffer, 1292 dataBuffer.ColumnStart, lastLetter 1293 - dataBuffer.ColumnStart + 1); 1294 } 1295 } else { 1296 updateCurrentValue(); 1297 1298 int lastLetter = columnBuffer.Position - 1; 1299 1300 if (userSettings.TrimWhitespace && !startedWithQualifier) { 1301 while (lastLetter >= 0 1302 && (columnBuffer.Buffer[lastLetter] == Letters.SPACE || columnBuffer.Buffer[lastLetter] == Letters.SPACE)) { 1303 lastLetter--; 1304 } 1305 } 1306 1307 currentValue = new String(columnBuffer.Buffer, 0, 1308 lastLetter + 1); 1309 } 1310 } 1311 1312 columnBuffer.Position = 0; 1313 1314 startedColumn = false; 1315 1316 if (columnsCount >= 100000 && userSettings.SafetySwitch) { 1317 close(); 1318 1319 throw new IOException( 1320 "Maximum column count of 100,000 exceeded in record " 1321 + NumberFormat.getIntegerInstance().format( 1322 currentRecord) 1323 + ". Set the SafetySwitch property to false" 1324 + " if you're expecting more than 100,000 columns per record to" 1325 + " avoid this error."); 1326 } 1327 1328 // check to see if our current holder array for 1329 // column chunks is still big enough to handle another 1330 // column chunk 1331 1332 if (columnsCount == values.length) { 1333 // holder array needs to grow to be able to hold another column 1334 int newLength = values.length * 2; 1335 1336 String[] holder = new String[newLength]; 1337 1338 System.arraycopy(values, 0, holder, 0, values.length); 1339 1340 values = holder; 1341 1342 boolean[] qualifiedHolder = new boolean[newLength]; 1343 1344 System.arraycopy(isQualified, 0, qualifiedHolder, 0, 1345 isQualified.length); 1346 1347 isQualified = qualifiedHolder; 1348 } 1349 1350 values[columnsCount] = currentValue; 1351 1352 isQualified[columnsCount] = startedWithQualifier; 1353 1354 currentValue = ""; 1355 1356 columnsCount++; 1357 } 1358 1359 private void appendLetter(char letter) { 1360 if (columnBuffer.Position == columnBuffer.Buffer.length) { 1361 int newLength = columnBuffer.Buffer.length * 2; 1362 1363 char[] holder = new char[newLength]; 1364 1365 System.arraycopy(columnBuffer.Buffer, 0, holder, 0, 1366 columnBuffer.Position); 1367 1368 columnBuffer.Buffer = holder; 1369 } 1370 columnBuffer.Buffer[columnBuffer.Position++] = letter; 1371 dataBuffer.ColumnStart = dataBuffer.Position + 1; 1372 } 1373 1374 private void updateCurrentValue() { 1375 if (startedColumn && dataBuffer.ColumnStart < dataBuffer.Position) { 1376 if (columnBuffer.Buffer.length - columnBuffer.Position < dataBuffer.Position 1377 - dataBuffer.ColumnStart) { 1378 int newLength = columnBuffer.Buffer.length 1379 + Math.max( 1380 dataBuffer.Position - dataBuffer.ColumnStart, 1381 columnBuffer.Buffer.length); 1382 1383 char[] holder = new char[newLength]; 1384 1385 System.arraycopy(columnBuffer.Buffer, 0, holder, 0, 1386 columnBuffer.Position); 1387 1388 columnBuffer.Buffer = holder; 1389 } 1390 1391 System.arraycopy(dataBuffer.Buffer, dataBuffer.ColumnStart, 1392 columnBuffer.Buffer, columnBuffer.Position, 1393 dataBuffer.Position - dataBuffer.ColumnStart); 1394 1395 columnBuffer.Position += dataBuffer.Position 1396 - dataBuffer.ColumnStart; 1397 } 1398 1399 dataBuffer.ColumnStart = dataBuffer.Position + 1; 1400 } 1401 1402 /** 1403 * @exception IOException 1404 * Thrown if an error occurs while reading data from the 1405 * source stream. 1406 */ 1407 private void endRecord() throws IOException { 1408 // this flag is used as a loop exit condition 1409 // during parsing 1410 1411 hasReadNextLine = true; 1412 1413 currentRecord++; 1414 } 1415 1416 /** 1417 * Gets the corresponding column index for a given column header name. 1418 * 1419 * @param headerName 1420 * The header name of the column. 1421 * @return The column index for the given column header name. Returns 1422 * -1 if not found. 1423 * @exception IOException 1424 * Thrown if this object has already been closed. 1425 */ 1426 public int getIndex(String headerName) throws IOException { 1427 checkClosed(); 1428 1429 Object indexValue = headersHolder.IndexByName.get(headerName); 1430 1431 if (indexValue != null) { 1432 return ((Integer) indexValue).intValue(); 1433 } else { 1434 return -1; 1435 } 1436 } 1437 1438 /** 1439 * Skips the next record of data by parsing each column. Does not 1440 * increment 1441 * {@link CsvReader#getCurrentRecord getCurrentRecord()}. 1442 * 1443 * @return Whether another record was successfully skipped or not. 1444 * @exception IOException 1445 * Thrown if an error occurs while reading data from the 1446 * source stream. 1447 */ 1448 public boolean skipRecord() throws IOException { 1449 checkClosed(); 1450 1451 boolean recordRead = false; 1452 1453 if (hasMoreData) { 1454 recordRead = readRecord(); 1455 1456 if (recordRead) { 1457 currentRecord--; 1458 } 1459 } 1460 1461 return recordRead; 1462 } 1463 1464 /** 1465 * Skips the next line of data using the standard end of line characters and 1466 * does not do any column delimited parsing. 1467 * 1468 * @return Whether a line was successfully skipped or not. 1469 * @exception IOException 1470 * Thrown if an error occurs while reading data from the 1471 * source stream. 1472 */ 1473 public boolean skipLine() throws IOException { 1474 checkClosed(); 1475 1476 // clear public column values for current line 1477 1478 columnsCount = 0; 1479 1480 boolean skippedLine = false; 1481 1482 if (hasMoreData) { 1483 boolean foundEol = false; 1484 1485 do { 1486 if (dataBuffer.Position == dataBuffer.Count) { 1487 checkDataLength(); 1488 } else { 1489 skippedLine = true; 1490 1491 // grab the current letter as a char 1492 1493 char currentLetter = dataBuffer.Buffer[dataBuffer.Position]; 1494 1495 if (currentLetter == Letters.CR 1496 || currentLetter == Letters.LF) { 1497 foundEol = true; 1498 } 1499 1500 // keep track of the last letter because we need 1501 // it for several key decisions 1502 1503 lastLetter = currentLetter; 1504 1505 if (!foundEol) { 1506 dataBuffer.Position++; 1507 } 1508 1509 } // end else 1510 } while (hasMoreData && !foundEol); 1511 1512 columnBuffer.Position = 0; 1513 1514 dataBuffer.LineStart = dataBuffer.Position + 1; 1515 } 1516 1517 rawBuffer.Position = 0; 1518 rawRecord = ""; 1519 1520 return skippedLine; 1521 } 1522 1523 /** 1524 * Closes and releases all related resources. 1525 */ 1526 public void close() { 1527 if (!closed) { 1528 close(true); 1529 1530 closed = true; 1531 } 1532 } 1533 1534 /** 1535 * 1536 */ 1537 private void close(boolean closing) { 1538 if (!closed) { 1539 if (closing) { 1540 charset = null; 1541 headersHolder.Headers = null; 1542 headersHolder.IndexByName = null; 1543 dataBuffer.Buffer = null; 1544 columnBuffer.Buffer = null; 1545 rawBuffer.Buffer = null; 1546 } 1547 1548 try { 1549 if (initialized) { 1550 inputStream.close(); 1551 } 1552 } catch (Exception e) { 1553 // just eat the exception 1554 } 1555 1556 inputStream = null; 1557 1558 closed = true; 1559 } 1560 } 1561 1562 /** 1563 * @exception IOException 1564 * Thrown if this object has already been closed. 1565 */ 1566 private void checkClosed() throws IOException { 1567 if (closed) { 1568 throw new IOException( 1569 "This instance of the CsvReader class has already been closed."); 1570 } 1571 } 1572 1573 /** 1574 * 1575 */ 1576 protected void finalize() { 1577 close(false); 1578 } 1579 1580 private class ComplexEscape { 1581 private static final int UNICODE = 1; 1582 1583 private static final int OCTAL = 2; 1584 1585 private static final int DECIMAL = 3; 1586 1587 private static final int HEX = 4; 1588 } 1589 1590 private static char hexToDec(char hex) { 1591 char result; 1592 1593 if (hex >= 'a') { 1594 result = (char) (hex - 'a' + 10); 1595 } else if (hex >= 'A') { 1596 result = (char) (hex - 'A' + 10); 1597 } else { 1598 result = (char) (hex - '0'); 1599 } 1600 1601 return result; 1602 } 1603 1604 private class DataBuffer { 1605 public char[] Buffer; 1606 1607 public int Position; 1608 1609 // / <summary> 1610 // / How much usable data has been read into the stream, 1611 // / which will not always be as long as Buffer.Length. 1612 // / </summary> 1613 public int Count; 1614 1615 // / <summary> 1616 // / The position of the cursor in the buffer when the 1617 // / current column was started or the last time data 1618 // / was moved out to the column buffer. 1619 // / </summary> 1620 public int ColumnStart; 1621 1622 public int LineStart; 1623 1624 public DataBuffer() { 1625 Buffer = new char[StaticSettings.MAX_BUFFER_SIZE]; 1626 Position = 0; 1627 Count = 0; 1628 ColumnStart = 0; 1629 LineStart = 0; 1630 } 1631 } 1632 1633 private class ColumnBuffer { 1634 public char[] Buffer; 1635 1636 public int Position; 1637 1638 public ColumnBuffer() { 1639 Buffer = new char[StaticSettings.INITIAL_COLUMN_BUFFER_SIZE]; 1640 Position = 0; 1641 } 1642 } 1643 1644 private class RawRecordBuffer { 1645 public char[] Buffer; 1646 1647 public int Position; 1648 1649 public RawRecordBuffer() { 1650 Buffer = new char[StaticSettings.INITIAL_COLUMN_BUFFER_SIZE 1651 * StaticSettings.INITIAL_COLUMN_COUNT]; 1652 Position = 0; 1653 } 1654 } 1655 1656 private class Letters { 1657 public static final char LF = '\n'; 1658 1659 public static final char CR = '\r'; 1660 1661 public static final char QUOTE = '"'; 1662 1663 public static final char COMMA = ','; 1664 1665 public static final char SPACE = ' '; 1666 1667 public static final char TAB = '\t'; 1668 1669 public static final char POUND = '#'; 1670 1671 public static final char BACKSLASH = '\\'; 1672 1673 public static final char NULL = '\0'; 1674 1675 public static final char BACKSPACE = '\b'; 1676 1677 public static final char FORM_FEED = '\f'; 1678 1679 public static final char ESCAPE = '\u001B'; // ASCII/ANSI escape 1680 1681 public static final char VERTICAL_TAB = '\u000B'; 1682 1683 public static final char ALERT = '\u0007'; 1684 } 1685 1686 private class UserSettings { 1687 // having these as publicly accessible members will prevent 1688 // the overhead of the method call that exists on properties 1689 public boolean CaseSensitive; 1690 1691 public char TextQualifier; 1692 1693 public boolean TrimWhitespace; 1694 1695 public boolean UseTextQualifier; 1696 1697 public char Delimiter; 1698 1699 public char RecordDelimiter; 1700 1701 public char Comment; 1702 1703 public boolean UseComments; 1704 1705 public int EscapeMode; 1706 1707 public boolean SafetySwitch; 1708 1709 public boolean SkipEmptyRecords; 1710 1711 public boolean CaptureRawRecord; 1712 1713 public UserSettings() { 1714 CaseSensitive = true; 1715 TextQualifier = Letters.QUOTE; 1716 TrimWhitespace = true; 1717 UseTextQualifier = true; 1718 Delimiter = Letters.COMMA; 1719 RecordDelimiter = Letters.NULL; 1720 Comment = Letters.POUND; 1721 UseComments = false; 1722 EscapeMode = CsvReader.ESCAPE_MODE_DOUBLED; 1723 SafetySwitch = true; 1724 SkipEmptyRecords = true; 1725 CaptureRawRecord = true; 1726 } 1727 } 1728 1729 private class HeadersHolder { 1730 public String[] Headers; 1731 1732 public int Length; 1733 1734 public HashMap IndexByName; 1735 1736 public HeadersHolder() { 1737 Headers = null; 1738 Length = 0; 1739 IndexByName = new HashMap(); 1740 } 1741 } 1742 1743 private class StaticSettings { 1744 // these are static instead of final so they can be changed in unit test 1745 // isn't visible outside this class and is only accessed once during 1746 // CsvReader construction 1747 public static final int MAX_BUFFER_SIZE = 1024; 1748 1749 public static final int MAX_FILE_BUFFER_SIZE = 4 * 1024; 1750 1751 public static final int INITIAL_COLUMN_COUNT = 10; 1752 1753 public static final int INITIAL_COLUMN_BUFFER_SIZE = 50; 1754 } 1755}