1 /* 2 * Copyright 2016 Andrew Rucker Jones. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.opencsv.bean; 17 18 import com.opencsv.*; 19 import com.opencsv.bean.exceptionhandler.CsvExceptionHandler; 20 import com.opencsv.bean.exceptionhandler.ExceptionHandlerThrow; 21 import com.opencsv.bean.util.OpencsvUtils; 22 import com.opencsv.enums.CSVReaderNullFieldIndicator; 23 import org.apache.commons.collections4.ListValuedMap; 24 import org.apache.commons.collections4.MultiValuedMap; 25 import org.apache.commons.collections4.multimap.ArrayListValuedHashMap; 26 import org.apache.commons.lang3.ObjectUtils; 27 import org.apache.commons.lang3.StringUtils; 28 29 import java.io.Reader; 30 import java.lang.reflect.Field; 31 import java.util.LinkedList; 32 import java.util.List; 33 import java.util.Locale; 34 import java.util.ResourceBundle; 35 36 /** 37 * This class makes it possible to bypass all the intermediate steps and classes 38 * in setting up to read from a CSV source to a list of beans. 39 * <p>This is the place to start if you're reading a CSV source into beans, 40 * especially if you're binding the input's columns to the bean's variables 41 * using the annotations {@link CsvBindByName}, {@link CsvCustomBindByName}, 42 * {@link CsvBindByPosition}, or {@link CsvCustomBindByPosition}.</p> 43 * <p>If you want nothing but defaults for the entire import, your code can look 44 * as simple as this, where {@code myreader} is any valid {@link java.io.Reader Reader}:<br> 45 * {@code List<MyBean> result = new CsvToBeanBuilder(myreader).withType(MyBean.class).build().parse();}</p> 46 * <p>This builder is intelligent enough to guess the mapping strategy according to the 47 * following strategy:</p><ol> 48 * <li>If a mapping strategy is explicitly set, it is always used.</li> 49 * <li>If {@link CsvBindByPosition} or {@link CsvCustomBindByPosition} is present, 50 * {@link ColumnPositionMappingStrategy} is used.</li> 51 * <li>Otherwise, {@link HeaderColumnNameMappingStrategy} is used. This includes 52 * the case when {@link CsvBindByName} or {@link CsvCustomBindByName} are being 53 * used. The annotations will automatically be recognized.</li></ol> 54 * 55 * @param <T> Type of the bean to be populated 56 * @author Andrew Rucker Jones 57 * @since 3.9 58 */ 59 public class CsvToBeanBuilder<T> { 60 61 /** @see CsvToBean#mappingStrategy */ 62 private MappingStrategy<? extends T> mappingStrategy = null; 63 64 /** 65 * A CSVReader will be built out of this {@link java.io.Reader}. 66 * @see CsvToBean#csvReader 67 */ 68 private final Reader reader; 69 70 /** 71 * Allow the user to pass in a prebuilt/custom {@link com.opencsv.CSVReader}. 72 */ 73 private final CSVReader csvReader; 74 75 /** @see CsvToBean#filter */ 76 private CsvToBeanFilter filter = null; 77 78 /** 79 * @see CsvToBean#throwExceptions 80 */ 81 private CsvExceptionHandler exceptionHandler = null; 82 83 /** @see com.opencsv.CSVParser#nullFieldIndicator */ 84 private CSVReaderNullFieldIndicator nullFieldIndicator = null; 85 86 /** @see com.opencsv.CSVReader#keepCR */ 87 private boolean keepCR; 88 89 /** @see com.opencsv.CSVReader#skipLines */ 90 private Integer skipLines = null; 91 92 /** @see com.opencsv.CSVReader#verifyReader */ 93 private Boolean verifyReader = null; 94 95 /** @see com.opencsv.CSVParser#separator */ 96 private Character separator = null; 97 98 /** @see com.opencsv.CSVParser#quotechar */ 99 private Character quoteChar = null; 100 101 /** @see com.opencsv.CSVParser#escape */ 102 private Character escapeChar = null; 103 104 /** 105 * @see com.opencsv.CSVParser#strictQuotes 106 */ 107 private Boolean strictQuotes = null; 108 109 /** 110 * @see com.opencsv.CSVParser#ignoreLeadingWhiteSpace 111 */ 112 private Boolean ignoreLeadingWhiteSpace = null; 113 114 /** 115 * @see com.opencsv.CSVParser#ignoreQuotations 116 */ 117 private Boolean ignoreQuotations = null; 118 119 /** 120 * @see com.opencsv.bean.CsvToBean#setThrowExceptions(boolean) 121 */ 122 private Boolean throwsExceptions = true; 123 124 /** 125 * @see HeaderColumnNameMappingStrategy#type 126 */ 127 private Class<? extends T> type = null; 128 129 /** 130 * @see com.opencsv.CSVReader#multilineLimit 131 */ 132 private Integer multilineLimit = null; 133 134 /** 135 * @see com.opencsv.bean.CsvToBean#orderedResults 136 */ 137 private boolean orderedResults = true; 138 139 /** 140 * @see com.opencsv.bean.CsvToBean#ignoreEmptyLines 141 */ 142 private boolean ignoreEmptyLines = false; 143 144 /** 145 * @see com.opencsv.bean.CsvToBean#errorLocale 146 */ 147 private Locale errorLocale = Locale.getDefault(); 148 149 /** 150 * @see com.opencsv.bean.CsvToBean#verifiers 151 */ 152 private final List<BeanVerifier<T>> verifiers = new LinkedList<>(); 153 154 /** 155 * @see com.opencsv.bean.AbstractMappingStrategy#ignoredFields 156 */ 157 private final ListValuedMap<Class<?>, Field> ignoredFields = new ArrayListValuedHashMap<>(); 158 159 /** @see com.opencsv.bean.AbstractMappingStrategy#profile */ 160 private String profile = StringUtils.EMPTY; 161 162 /** 163 * Constructor with the one parameter that is most definitely mandatory, and 164 * always will be. 165 * @param reader The reader that is the source of data for the CSV import 166 */ 167 public CsvToBeanBuilder(Reader reader) { 168 if(reader == null) { 169 throw new IllegalArgumentException(ResourceBundle 170 .getBundle(ICSVParser.DEFAULT_BUNDLE_NAME) // Must be default locale, because we don't have anything else yet 171 .getString("reader.null")); 172 } 173 this.reader = reader; 174 this.csvReader = null; 175 } 176 177 /** 178 * Constructor with the one parameter that is most definitely mandatory, and 179 * always will be. 180 * 181 * @param csvReader The CSVReader that is the source of data for the CSV import 182 */ 183 public CsvToBeanBuilder(CSVReader csvReader) { 184 if (csvReader == null) { 185 throw new IllegalArgumentException(ResourceBundle 186 .getBundle(ICSVParser.DEFAULT_BUNDLE_NAME) // Must be default locale, because we don't have anything else yet 187 .getString("reader.null")); 188 } 189 this.reader = null; 190 this.csvReader = csvReader; 191 } 192 193 /** 194 * Builds the {@link CsvToBean} out of the provided information. 195 * @return A valid {@link CsvToBean} 196 * @throws IllegalStateException If a necessary parameter was not specified. 197 * Currently this means that both the mapping strategy and the bean type 198 * are not set, so it is impossible to determine a mapping strategy. 199 */ 200 public CsvToBean<T> build() throws IllegalStateException { 201 // Check for errors in the configuration first 202 if(mappingStrategy == null && type == null) { 203 throw new IllegalStateException(ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale).getString("strategy.type.missing")); 204 } 205 206 // Build Parser and Reader 207 CsvToBean<T> bean = new CsvToBean<>(); 208 209 if (csvReader != null) { 210 bean.setCsvReader(csvReader); 211 } else { 212 CSVParser parser = buildParser(); 213 bean.setCsvReader(buildReader(parser)); 214 } 215 216 // Set variables in CsvToBean itself 217 218 if (exceptionHandler != null) { 219 bean.setExceptionHandler(exceptionHandler); 220 } else { 221 bean.setThrowExceptions(throwsExceptions); 222 } 223 224 bean.setOrderedResults(orderedResults); 225 if (filter != null) { 226 bean.setFilter(filter); 227 } 228 bean.setVerifiers(verifiers); 229 230 // Now find the mapping strategy and ignore irrelevant fields. 231 // It's possible the mapping strategy has already been primed, so only 232 // pass on our data if the user actually gave us something. 233 if(mappingStrategy == null) { 234 mappingStrategy = OpencsvUtils.determineMappingStrategy(type, errorLocale, profile); 235 } 236 if(!ignoredFields.isEmpty()) { 237 mappingStrategy.ignoreFields(ignoredFields); 238 } 239 bean.setMappingStrategy(mappingStrategy); 240 241 // The error locale comes at the end so it can be propagated through all 242 // of the components of CsvToBean, rendering the error locale homogeneous. 243 bean.setErrorLocale(errorLocale); 244 bean.setIgnoreEmptyLines(ignoreEmptyLines); 245 246 return bean; 247 } 248 249 /** 250 * Builds a {@link CSVParser} from the information provided to this builder. 251 * This is an intermediate step in building the {@link CsvToBean}. 252 * @return An appropriate {@link CSVParser} 253 */ 254 private CSVParser buildParser() { 255 CSVParserBuilder csvpb = new CSVParserBuilder(); 256 if(nullFieldIndicator != null) { 257 csvpb.withFieldAsNull(nullFieldIndicator); 258 } 259 if(separator != null) { 260 csvpb.withSeparator(separator); 261 } 262 if(quoteChar != null) { 263 csvpb.withQuoteChar(quoteChar); 264 } 265 if(escapeChar != null) { 266 csvpb.withEscapeChar(escapeChar); 267 } 268 if(strictQuotes != null) { 269 csvpb.withStrictQuotes(strictQuotes); 270 } 271 if(ignoreLeadingWhiteSpace != null) { 272 csvpb.withIgnoreLeadingWhiteSpace(ignoreLeadingWhiteSpace); 273 } 274 if(ignoreQuotations != null) { 275 csvpb.withIgnoreQuotations(ignoreQuotations); 276 } 277 csvpb.withErrorLocale(errorLocale); 278 279 return csvpb.build(); 280 } 281 282 /** 283 * Builds a {@link CSVReader} from the information provided to this builder. 284 * This is an intermediate step in building the {@link CsvToBean}. 285 * @param parser The {@link CSVParser} necessary for this reader 286 * @return An appropriate {@link CSVReader} 287 */ 288 private CSVReader buildReader(CSVParser parser) { 289 CSVReaderBuilder csvrb = new CSVReaderBuilder(reader); 290 csvrb.withCSVParser(parser); 291 csvrb.withKeepCarriageReturn(keepCR); 292 if(verifyReader != null) { 293 csvrb.withVerifyReader(verifyReader); 294 } 295 if(skipLines != null) { 296 csvrb.withSkipLines(skipLines); 297 } 298 if(multilineLimit != null) { 299 csvrb.withMultilineLimit(multilineLimit); 300 } 301 csvrb.withErrorLocale(errorLocale); 302 return csvrb.build(); 303 } 304 305 /** 306 * @see CsvToBean#setMappingStrategy(com.opencsv.bean.MappingStrategy) 307 * @param mappingStrategy Please see the "See Also" section 308 * @return {@code this} 309 */ 310 public CsvToBeanBuilder<T> withMappingStrategy(MappingStrategy<? extends T> mappingStrategy) { 311 this.mappingStrategy = mappingStrategy; 312 return this; 313 } 314 315 /** 316 * @see CsvToBean#setFilter(com.opencsv.bean.CsvToBeanFilter) 317 * @param filter Please see the "See Also" section 318 * @return {@code this} 319 */ 320 public CsvToBeanBuilder<T> withFilter(CsvToBeanFilter filter) { 321 this.filter = filter; 322 return this; 323 } 324 325 /** 326 * Sets how the CsvToBean will act when an exception occurs. If both withThrowsExcpetion and 327 * {@link #withExceptionHandler(CsvExceptionHandler)} are used then the withExceptionHandler takes 328 * precedence and is used. 329 * 330 * @see CsvToBean#setThrowExceptions(boolean) 331 * @see #withExceptionHandler(CsvExceptionHandler) 332 * @param throwExceptions Please see the "See Also" section 333 * @return {@code this} 334 */ 335 public CsvToBeanBuilder<T> withThrowExceptions(boolean throwExceptions) { 336 this.throwsExceptions = throwExceptions; 337 return this; 338 } 339 340 /** 341 * Sets the handler for recoverable exceptions raised during processing of 342 * records. If both {@link #withThrowExceptions(boolean)} and withExceptionHandler are used then the 343 * withExceptionHandler takes precedence and is used. 344 * <p>If neither this method nor {@link #withThrowExceptions(boolean)} is 345 * called, the default exception handler is 346 * {@link ExceptionHandlerThrow}.</p> 347 * <p>Please note that if both this method and 348 * {@link #withThrowExceptions(boolean)} are called, the last call wins.</p> 349 * 350 * @param exceptionHandler The exception handler to be used. If {@code null}, 351 * this method does nothing. 352 * @return {@code this} 353 * @since 5.2 354 */ 355 public CsvToBeanBuilder<T> withExceptionHandler(CsvExceptionHandler exceptionHandler) { 356 if(exceptionHandler != null) { 357 this.exceptionHandler = exceptionHandler; 358 } 359 return this; 360 } 361 362 /** 363 * @param indicator Which field content will be returned as null: EMPTY_SEPARATORS, EMPTY_QUOTES, 364 * BOTH, NEITHER (default) 365 * @return {@code this} 366 */ 367 public CsvToBeanBuilder<T> withFieldAsNull(CSVReaderNullFieldIndicator indicator) { 368 this.nullFieldIndicator = indicator; 369 return this; 370 } 371 372 /** 373 * @param keepCR True to keep carriage returns in data read, false otherwise 374 * @return {@code this} 375 */ 376 public CsvToBeanBuilder<T> withKeepCarriageReturn(boolean keepCR) { 377 this.keepCR = keepCR; 378 return this; 379 } 380 381 /** 382 * @see CSVReaderBuilder#withVerifyReader(boolean) 383 * @param verifyReader Please see the "See Also" section 384 * @return {@code this} 385 */ 386 public CsvToBeanBuilder<T> withVerifyReader(boolean verifyReader) { 387 this.verifyReader = verifyReader; 388 return this; 389 } 390 391 /** 392 * @see CSVReaderBuilder#withSkipLines(int) 393 * @param skipLines Please see the "See Also" section 394 * @return {@code this} 395 */ 396 public CsvToBeanBuilder<T> withSkipLines( 397 final int skipLines) { 398 this.skipLines = skipLines; 399 return this; 400 } 401 402 /** 403 * @see CSVParser#CSVParser(char, char, char, boolean, boolean, boolean, CSVReaderNullFieldIndicator, Locale) 404 * @param separator Please see the "See Also" section 405 * @return {@code this} 406 */ 407 public CsvToBeanBuilder<T> withSeparator(char separator) { 408 this.separator = separator; 409 return this; 410 } 411 412 /** 413 * @see CSVParser#CSVParser(char, char, char, boolean, boolean, boolean, CSVReaderNullFieldIndicator, Locale) 414 * @param quoteChar Please see the "See Also" section 415 * @return {@code this} 416 */ 417 public CsvToBeanBuilder<T> withQuoteChar(char quoteChar) { 418 this.quoteChar = quoteChar; 419 return this; 420 } 421 422 /** 423 * @see CSVParser#CSVParser(char, char, char, boolean, boolean, boolean, CSVReaderNullFieldIndicator, Locale) 424 * @param escapeChar Please see the "See Also" section 425 * @return {@code this} 426 */ 427 public CsvToBeanBuilder<T> withEscapeChar(char escapeChar) { 428 this.escapeChar = escapeChar; 429 return this; 430 } 431 432 /** 433 * @see CSVParser#CSVParser(char, char, char, boolean, boolean, boolean, CSVReaderNullFieldIndicator, Locale) 434 * @param strictQuotes Please see the "See Also" section 435 * @return {@code this} 436 */ 437 public CsvToBeanBuilder<T> withStrictQuotes(boolean strictQuotes) { 438 this.strictQuotes = strictQuotes; 439 return this; 440 } 441 442 /** 443 * @see CSVParser#CSVParser(char, char, char, boolean, boolean, boolean, CSVReaderNullFieldIndicator, Locale) 444 * @param ignoreLeadingWhiteSpace Please see the "See Also" section 445 * @return {@code this} 446 */ 447 public CsvToBeanBuilder<T> withIgnoreLeadingWhiteSpace(boolean ignoreLeadingWhiteSpace) { 448 this.ignoreLeadingWhiteSpace = ignoreLeadingWhiteSpace; 449 return this; 450 } 451 452 /** 453 * @see CSVParser#CSVParser(char, char, char, boolean, boolean, boolean, CSVReaderNullFieldIndicator, Locale) 454 * @param ignoreQuotations Please see the "See Also" section 455 * @return {@code this} 456 */ 457 public CsvToBeanBuilder<T> withIgnoreQuotations(boolean ignoreQuotations) { 458 this.ignoreQuotations = ignoreQuotations; 459 return this; 460 } 461 462 /** 463 * Sets the type of the bean to be populated. 464 * Ignored if {@link #withMappingStrategy(com.opencsv.bean.MappingStrategy)} 465 * is called. 466 * @param type Class of the destination bean 467 * @return {@code this} 468 * @see HeaderColumnNameMappingStrategy#setType(java.lang.Class) 469 * @see ColumnPositionMappingStrategy#setType(java.lang.Class) 470 */ 471 public CsvToBeanBuilder<T> withType(Class<? extends T> type) { 472 this.type = type; 473 return this; 474 } 475 476 /** 477 * Sets the maximum number of lines allowed in a multiline record. 478 * More than this number in one record results in an IOException. 479 * 480 * @param multilineLimit No more than this number of lines is allowed in a 481 * single input record. The default is {@link CSVReader#DEFAULT_MULTILINE_LIMIT}. 482 * @return {@code this} 483 */ 484 public CsvToBeanBuilder<T> withMultilineLimit(int multilineLimit) { 485 this.multilineLimit = multilineLimit; 486 return this; 487 } 488 489 /** 490 * Sets whether the resulting beans must be ordered as in the input. 491 * 492 * @param orderedResults Whether to order the results or not 493 * @return {@code this} 494 * @see CsvToBean#setOrderedResults(boolean) 495 * @since 4.0 496 */ 497 public CsvToBeanBuilder<T> withOrderedResults(boolean orderedResults) { 498 this.orderedResults = orderedResults; 499 return this; 500 } 501 502 /** 503 * Sets the locale for all error messages. 504 * 505 * @param errorLocale Locale for error messages 506 * @return {@code this} 507 * @see CsvToBean#setErrorLocale(java.util.Locale) 508 * @since 4.0 509 */ 510 public CsvToBeanBuilder<T> withErrorLocale(Locale errorLocale) { 511 this.errorLocale = ObjectUtils.defaultIfNull(errorLocale, Locale.getDefault()); 512 return this; 513 } 514 515 /** 516 * Adds a {@link BeanVerifier} to the list of verifiers to run on all 517 * beans created. 518 * This method may be called as many times as desired. All added verifiers 519 * will be run on every bean. No guarantee is made as to the order in which 520 * the verifiers are run. 521 * 522 * @param verifier A new verifier that is to process all beans after 523 * creation. {@code null} is permissible but has no effect. 524 * @return {@code this} 525 * @since 4.4 526 */ 527 public CsvToBeanBuilder<T> withVerifier(BeanVerifier<T> verifier) { 528 if(verifier != null) { 529 verifiers.add(verifier); 530 } 531 return this; 532 } 533 534 /** 535 * Adds a {@link Field} to the list of fields opencsv should ignore 536 * completely. 537 * <p>May be called as many times as necessary.</p> 538 * @param type The class opencsv will encounter the field in during 539 * processing. In the case of inheritance, this may not be the 540 * declaring class. 541 * @param field The field opencsv is to ignore 542 * @return {@code this} 543 * @throws IllegalArgumentException If one of the parameters is 544 * {@code null} or {@code field} cannot be found in {@code type}. 545 * @since 5.0 546 * @see MappingStrategy#ignoreFields(MultiValuedMap) 547 */ 548 public CsvToBeanBuilder<T> withIgnoreField(Class<?> type, Field field) throws IllegalArgumentException { 549 if (type != null && field != null && field.getDeclaringClass().isAssignableFrom(type)) { 550 ignoredFields.put(type, field); 551 } else { 552 throw new IllegalArgumentException(ResourceBundle.getBundle( 553 ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale) 554 .getString("ignore.field.inconsistent")); 555 } 556 return this; 557 } 558 559 /** 560 * @param ignore Please see the "See Also" section 561 * @return {@code this} 562 * @see CsvToBean#ignoreEmptyLines 563 */ 564 public CsvToBeanBuilder<T> withIgnoreEmptyLine(boolean ignore) { 565 this.ignoreEmptyLines = ignore; 566 return this; 567 } 568 569 /** 570 * Selects a profile for deciding which configurations to use for the bean 571 * fields. 572 * 573 * @param profile The name of the profile to be used 574 * @return {@code this} 575 * @since 5.4 576 */ 577 public CsvToBeanBuilder<T> withProfile(String profile) { 578 this.profile = profile; 579 return this; 580 } 581 }