001/* 002 * Copyright 2016-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.springframework.batch.item.file.builder; 017 018import java.beans.PropertyEditor; 019import java.math.BigInteger; 020import java.util.ArrayList; 021import java.util.Arrays; 022import java.util.HashMap; 023import java.util.HashSet; 024import java.util.Iterator; 025import java.util.List; 026import java.util.Map; 027import java.util.Set; 028 029import org.apache.commons.logging.Log; 030import org.apache.commons.logging.LogFactory; 031 032import org.springframework.batch.item.file.FlatFileItemReader; 033import org.springframework.batch.item.file.LineCallbackHandler; 034import org.springframework.batch.item.file.LineMapper; 035import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper; 036import org.springframework.batch.item.file.mapping.DefaultLineMapper; 037import org.springframework.batch.item.file.mapping.FieldSetMapper; 038import org.springframework.batch.item.file.separator.RecordSeparatorPolicy; 039import org.springframework.batch.item.file.separator.SimpleRecordSeparatorPolicy; 040import org.springframework.batch.item.file.transform.DefaultFieldSetFactory; 041import org.springframework.batch.item.file.transform.DelimitedLineTokenizer; 042import org.springframework.batch.item.file.transform.FieldSetFactory; 043import org.springframework.batch.item.file.transform.FixedLengthTokenizer; 044import org.springframework.batch.item.file.transform.LineTokenizer; 045import org.springframework.batch.item.file.transform.Range; 046import org.springframework.beans.factory.BeanFactory; 047import org.springframework.core.io.Resource; 048import org.springframework.util.Assert; 049import org.springframework.util.StringUtils; 050 051/** 052 * A builder implementation for the {@link FlatFileItemReader}. 053 * 054 * @author Michael Minella 055 * @author Glenn Renfro 056 * @author Mahmoud Ben Hassine 057 * @since 4.0 058 * @see FlatFileItemReader 059 */ 060public class FlatFileItemReaderBuilder<T> { 061 062 protected Log logger = LogFactory.getLog(getClass()); 063 064 private boolean strict = true; 065 066 private String encoding = FlatFileItemReader.DEFAULT_CHARSET; 067 068 private RecordSeparatorPolicy recordSeparatorPolicy = 069 new SimpleRecordSeparatorPolicy(); 070 071 private Resource resource; 072 073 private List<String> comments = 074 new ArrayList<>(Arrays.asList(FlatFileItemReader.DEFAULT_COMMENT_PREFIXES)); 075 076 private int linesToSkip = 0; 077 078 private LineCallbackHandler skippedLinesCallback; 079 080 private LineMapper<T> lineMapper; 081 082 private FieldSetMapper<T> fieldSetMapper; 083 084 private LineTokenizer lineTokenizer; 085 086 private DelimitedBuilder<T> delimitedBuilder; 087 088 private FixedLengthBuilder<T> fixedLengthBuilder; 089 090 private Class<? extends T> targetType; 091 092 private String prototypeBeanName; 093 094 private BeanFactory beanFactory; 095 096 private Map<Class<?>, PropertyEditor> customEditors = new HashMap<>(); 097 098 private int distanceLimit = 5; 099 100 private boolean beanMapperStrict = true; 101 102 private BigInteger tokenizerValidator = new BigInteger("0"); 103 104 private boolean saveState = true; 105 106 private String name; 107 108 private int maxItemCount = Integer.MAX_VALUE; 109 110 private int currentItemCount; 111 112 /** 113 * Configure if the state of the {@link org.springframework.batch.item.ItemStreamSupport} 114 * should be persisted within the {@link org.springframework.batch.item.ExecutionContext} 115 * for restart purposes. 116 * 117 * @param saveState defaults to true 118 * @return The current instance of the builder. 119 */ 120 public FlatFileItemReaderBuilder<T> saveState(boolean saveState) { 121 this.saveState = saveState; 122 123 return this; 124 } 125 126 /** 127 * The name used to calculate the key within the 128 * {@link org.springframework.batch.item.ExecutionContext}. Required if 129 * {@link #saveState(boolean)} is set to true. 130 * 131 * @param name name of the reader instance 132 * @return The current instance of the builder. 133 * @see org.springframework.batch.item.ItemStreamSupport#setName(String) 134 */ 135 public FlatFileItemReaderBuilder<T> name(String name) { 136 this.name = name; 137 138 return this; 139 } 140 141 /** 142 * Configure the max number of items to be read. 143 * 144 * @param maxItemCount the max items to be read 145 * @return The current instance of the builder. 146 * @see org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#setMaxItemCount(int) 147 */ 148 public FlatFileItemReaderBuilder<T> maxItemCount(int maxItemCount) { 149 this.maxItemCount = maxItemCount; 150 151 return this; 152 } 153 154 /** 155 * Index for the current item. Used on restarts to indicate where to start from. 156 * 157 * @param currentItemCount current index 158 * @return this instance for method chaining 159 * @see org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#setCurrentItemCount(int) 160 */ 161 public FlatFileItemReaderBuilder<T> currentItemCount(int currentItemCount) { 162 this.currentItemCount = currentItemCount; 163 164 return this; 165 } 166 167 /** 168 * Add a string to the list of Strings that indicate commented lines. 169 * Defaults to {@link FlatFileItemReader#DEFAULT_COMMENT_PREFIXES}. 170 * 171 * @param comment the string to define a commented line. 172 * @return The current instance of the builder. 173 * @see FlatFileItemReader#setComments(String[]) 174 */ 175 public FlatFileItemReaderBuilder<T> addComment(String comment) { 176 this.comments.add(comment); 177 return this; 178 } 179 180 /** 181 * Set an array of Strings that indicate lines that are comments (and therefore skipped by 182 * the reader). This method overrides the default comment prefixes which are 183 * {@link FlatFileItemReader#DEFAULT_COMMENT_PREFIXES}. 184 * 185 * @param comments an array of strings to identify comments. 186 * @return The current instance of the builder. 187 * @see FlatFileItemReader#setComments(String[]) 188 */ 189 public FlatFileItemReaderBuilder<T> comments(String... comments) { 190 this.comments = Arrays.asList(comments); 191 return this; 192 } 193 194 /** 195 * Configure a custom {@link RecordSeparatorPolicy} for the reader. 196 * 197 * @param policy custom policy 198 * @return The current instance of the builder. 199 * @see FlatFileItemReader#setRecordSeparatorPolicy(RecordSeparatorPolicy) 200 */ 201 public FlatFileItemReaderBuilder<T> recordSeparatorPolicy(RecordSeparatorPolicy policy) { 202 this.recordSeparatorPolicy = policy; 203 return this; 204 } 205 206 /** 207 * The {@link Resource} to be used as input. 208 * 209 * @param resource the input to the reader. 210 * @return The current instance of the builder. 211 * @see FlatFileItemReader#setResource(Resource) 212 */ 213 public FlatFileItemReaderBuilder<T> resource(Resource resource) { 214 this.resource = resource; 215 return this; 216 } 217 218 /** 219 * Configure if the reader should be in strict mode (require the input {@link Resource} 220 * to exist). 221 * 222 * @param strict true if the input file is required to exist. 223 * @return The current instance of the builder. 224 * @see FlatFileItemReader#setStrict(boolean) 225 */ 226 public FlatFileItemReaderBuilder<T> strict(boolean strict) { 227 this.strict = strict; 228 return this; 229 } 230 231 /** 232 * Configure the encoding used by the reader to read the input source. 233 * Default value is {@link FlatFileItemReader#DEFAULT_CHARSET}. 234 * 235 * @param encoding to use to read the input source. 236 * @return The current instance of the builder. 237 * @see FlatFileItemReader#setEncoding(String) 238 */ 239 public FlatFileItemReaderBuilder<T> encoding(String encoding) { 240 this.encoding = encoding; 241 return this; 242 } 243 244 /** 245 * The number of lines to skip at the beginning of reading the file. 246 * 247 * @param linesToSkip number of lines to be skipped. 248 * @return The current instance of the builder. 249 * @see FlatFileItemReader#setLinesToSkip(int) 250 */ 251 public FlatFileItemReaderBuilder<T> linesToSkip(int linesToSkip) { 252 this.linesToSkip = linesToSkip; 253 return this; 254 } 255 256 /** 257 * A callback to be called for each line that is skipped. 258 * 259 * @param callback the callback 260 * @return The current instance of the builder. 261 * @see FlatFileItemReader#setSkippedLinesCallback(LineCallbackHandler) 262 */ 263 public FlatFileItemReaderBuilder<T> skippedLinesCallback(LineCallbackHandler callback) { 264 this.skippedLinesCallback = callback; 265 return this; 266 } 267 268 /** 269 * A {@link LineMapper} implementation to be used. 270 * 271 * @param lineMapper {@link LineMapper} 272 * @return The current instance of the builder. 273 * @see FlatFileItemReader#setLineMapper(LineMapper) 274 */ 275 public FlatFileItemReaderBuilder<T> lineMapper(LineMapper<T> lineMapper) { 276 this.lineMapper = lineMapper; 277 return this; 278 } 279 280 /** 281 * A {@link FieldSetMapper} implementation to be used. 282 * 283 * @param mapper a {@link FieldSetMapper} 284 * @return The current instance of the builder. 285 * @see DefaultLineMapper#setFieldSetMapper(FieldSetMapper) 286 */ 287 public FlatFileItemReaderBuilder<T> fieldSetMapper(FieldSetMapper<T> mapper) { 288 this.fieldSetMapper = mapper; 289 return this; 290 } 291 292 /** 293 * A {@link LineTokenizer} implementation to be used. 294 * 295 * @param tokenizer a {@link LineTokenizer} 296 * @return The current instance of the builder. 297 * @see DefaultLineMapper#setLineTokenizer(LineTokenizer) 298 */ 299 public FlatFileItemReaderBuilder<T> lineTokenizer(LineTokenizer tokenizer) { 300 updateTokenizerValidation(tokenizer, 0); 301 302 this.lineTokenizer = tokenizer; 303 return this; 304 } 305 306 /** 307 * Returns an instance of a {@link DelimitedBuilder} for building a 308 * {@link DelimitedLineTokenizer}. The {@link DelimitedLineTokenizer} configured by 309 * this builder will only be used if one is not explicitly configured via 310 * {@link FlatFileItemReaderBuilder#lineTokenizer} 311 * 312 * @return a {@link DelimitedBuilder} 313 * 314 */ 315 public DelimitedBuilder<T> delimited() { 316 this.delimitedBuilder = new DelimitedBuilder<>(this); 317 updateTokenizerValidation(this.delimitedBuilder, 1); 318 return this.delimitedBuilder; 319 } 320 321 /** 322 * Returns an instance of a {@link FixedLengthBuilder} for building a 323 * {@link FixedLengthTokenizer}. The {@link FixedLengthTokenizer} configured by this 324 * builder will only be used if the {@link FlatFileItemReaderBuilder#lineTokenizer} 325 * has not been configured. 326 * 327 * @return a {@link FixedLengthBuilder} 328 */ 329 public FixedLengthBuilder<T> fixedLength() { 330 this.fixedLengthBuilder = new FixedLengthBuilder<>(this); 331 updateTokenizerValidation(this.fixedLengthBuilder, 2); 332 return this.fixedLengthBuilder; 333 } 334 335 /** 336 * The class that will represent the "item" to be returned from the reader. This 337 * class is used via the {@link BeanWrapperFieldSetMapper}. If more complex logic is 338 * required, providing your own {@link FieldSetMapper} via 339 * {@link FlatFileItemReaderBuilder#fieldSetMapper} is required. 340 * 341 * @param targetType The class to map to 342 * @return The current instance of the builder. 343 * @see BeanWrapperFieldSetMapper#setTargetType(Class) 344 */ 345 public FlatFileItemReaderBuilder<T> targetType(Class<? extends T> targetType) { 346 this.targetType = targetType; 347 return this; 348 } 349 350 /** 351 * Configures the id of a prototype scoped bean to be used as the item returned by the 352 * reader. 353 * 354 * @param prototypeBeanName the name of a prototype scoped bean 355 * @return The current instance of the builder. 356 * @see BeanWrapperFieldSetMapper#setPrototypeBeanName(String) 357 */ 358 public FlatFileItemReaderBuilder<T> prototypeBeanName(String prototypeBeanName) { 359 this.prototypeBeanName = prototypeBeanName; 360 return this; 361 } 362 363 /** 364 * Configures the {@link BeanFactory} used to create the beans that are returned as 365 * items. 366 * 367 * @param beanFactory a {@link BeanFactory} 368 * @return The current instance of the builder. 369 * @see BeanWrapperFieldSetMapper#setBeanFactory(BeanFactory) 370 */ 371 public FlatFileItemReaderBuilder<T> beanFactory(BeanFactory beanFactory) { 372 this.beanFactory = beanFactory; 373 return this; 374 } 375 376 /** 377 * Register custom type converters for beans being mapped. 378 * 379 * @param customEditors a {@link Map} of editors 380 * @return The current instance of the builder. 381 * @see BeanWrapperFieldSetMapper#setCustomEditors(Map) 382 */ 383 public FlatFileItemReaderBuilder<T> customEditors(Map<Class<?>, PropertyEditor> customEditors) { 384 if(customEditors != null) { 385 this.customEditors.putAll(customEditors); 386 } 387 388 return this; 389 } 390 391 /** 392 * Configures the maximum tolerance between the actual spelling of a field's name and 393 * the property's name. 394 * 395 * @param distanceLimit distance limit to set 396 * @return The current instance of the builder. 397 * @see BeanWrapperFieldSetMapper#setDistanceLimit(int) 398 */ 399 public FlatFileItemReaderBuilder<T> distanceLimit(int distanceLimit) { 400 this.distanceLimit = distanceLimit; 401 return this; 402 } 403 404 /** 405 * If set to true, mapping will fail if the {@link org.springframework.batch.item.file.transform.FieldSet} 406 * contains fields that cannot be mapped to the bean. 407 * 408 * @param beanMapperStrict defaults to false 409 * @return The current instance of the builder. 410 * @see BeanWrapperFieldSetMapper#setStrict(boolean) 411 */ 412 public FlatFileItemReaderBuilder<T> beanMapperStrict(boolean beanMapperStrict) { 413 this.beanMapperStrict = beanMapperStrict; 414 return this; 415 } 416 417 /** 418 * Builds the {@link FlatFileItemReader}. 419 * 420 * @return a {@link FlatFileItemReader} 421 */ 422 public FlatFileItemReader<T> build() { 423 if(this.saveState) { 424 Assert.state(StringUtils.hasText(this.name), 425 "A name is required when saveState is set to true."); 426 } 427 428 if(this.resource == null) { 429 logger.debug("The resource is null. This is only a valid scenario when " + 430 "injecting it later as in when using the MultiResourceItemReader"); 431 } 432 433 Assert.notNull(this.recordSeparatorPolicy, "A RecordSeparatorPolicy is required."); 434 int validatorValue = this.tokenizerValidator.intValue(); 435 436 FlatFileItemReader<T> reader = new FlatFileItemReader<>(); 437 438 if(StringUtils.hasText(this.name)) { 439 reader.setName(this.name); 440 } 441 442 if(StringUtils.hasText(this.encoding)) { 443 reader.setEncoding(this.encoding); 444 } 445 446 reader.setResource(this.resource); 447 448 if(this.lineMapper != null) { 449 reader.setLineMapper(this.lineMapper); 450 } 451 else { 452 Assert.state(validatorValue == 0 || validatorValue == 1 || validatorValue == 2 || validatorValue == 4, 453 "Only one LineTokenizer option may be configured"); 454 455 DefaultLineMapper<T> lineMapper = new DefaultLineMapper<>(); 456 457 if(this.lineTokenizer != null) { 458 lineMapper.setLineTokenizer(this.lineTokenizer); 459 } 460 else if(this.fixedLengthBuilder != null) { 461 lineMapper.setLineTokenizer(this.fixedLengthBuilder.build()); 462 } 463 else if(this.delimitedBuilder != null) { 464 lineMapper.setLineTokenizer(this.delimitedBuilder.build()); 465 } 466 else { 467 throw new IllegalStateException("No LineTokenizer implementation was provided."); 468 } 469 470 if(this.targetType != null || StringUtils.hasText(this.prototypeBeanName)) { 471 BeanWrapperFieldSetMapper<T> mapper = new BeanWrapperFieldSetMapper<>(); 472 mapper.setTargetType(this.targetType); 473 mapper.setPrototypeBeanName(this.prototypeBeanName); 474 mapper.setStrict(this.beanMapperStrict); 475 mapper.setBeanFactory(this.beanFactory); 476 mapper.setDistanceLimit(this.distanceLimit); 477 mapper.setCustomEditors(this.customEditors); 478 try { 479 mapper.afterPropertiesSet(); 480 } 481 catch (Exception e) { 482 throw new IllegalStateException("Unable to initialize BeanWrapperFieldSetMapper", e); 483 } 484 485 lineMapper.setFieldSetMapper(mapper); 486 } 487 else if(this.fieldSetMapper != null) { 488 lineMapper.setFieldSetMapper(this.fieldSetMapper); 489 } 490 else { 491 throw new IllegalStateException("No FieldSetMapper implementation was provided."); 492 } 493 494 reader.setLineMapper(lineMapper); 495 } 496 497 reader.setLinesToSkip(this.linesToSkip); 498 reader.setComments(this.comments.toArray(new String[this.comments.size()])); 499 500 reader.setSkippedLinesCallback(this.skippedLinesCallback); 501 reader.setRecordSeparatorPolicy(this.recordSeparatorPolicy); 502 reader.setMaxItemCount(this.maxItemCount); 503 reader.setCurrentItemCount(this.currentItemCount); 504 reader.setSaveState(this.saveState); 505 reader.setStrict(this.strict); 506 507 return reader; 508 } 509 510 private void updateTokenizerValidation(Object tokenizer, int index) { 511 if(tokenizer != null) { 512 this.tokenizerValidator = this.tokenizerValidator.flipBit(index); 513 } 514 else { 515 this.tokenizerValidator = this.tokenizerValidator.clearBit(index); 516 } 517 } 518 519 /** 520 * A builder for constructing a {@link DelimitedLineTokenizer} 521 * 522 * @param <T> the type of the parent {@link FlatFileItemReaderBuilder} 523 */ 524 public static class DelimitedBuilder<T> { 525 private FlatFileItemReaderBuilder<T> parent; 526 527 private List<String> names = new ArrayList<>(); 528 529 private String delimiter; 530 531 private Character quoteCharacter; 532 533 private List<Integer> includedFields = new ArrayList<>(); 534 535 private FieldSetFactory fieldSetFactory = new DefaultFieldSetFactory(); 536 537 private boolean strict = true; 538 539 protected DelimitedBuilder(FlatFileItemReaderBuilder<T> parent) { 540 this.parent = parent; 541 } 542 543 /** 544 * Define the delimiter for the file. 545 * 546 * @param delimiter String used as a delimiter between fields. 547 * @return The instance of the builder for chaining. 548 * @see DelimitedLineTokenizer#setDelimiter(String) 549 */ 550 public DelimitedBuilder<T> delimiter(String delimiter) { 551 this.delimiter = delimiter; 552 return this; 553 } 554 555 /** 556 * Define the character used to quote fields. 557 * 558 * @param quoteCharacter char used to define quoted fields 559 * @return The instance of the builder for chaining. 560 * @see DelimitedLineTokenizer#setQuoteCharacter(char) 561 */ 562 public DelimitedBuilder<T> quoteCharacter(char quoteCharacter) { 563 this.quoteCharacter = quoteCharacter; 564 return this; 565 } 566 567 /** 568 * A list of indices of the fields within a delimited file to be included 569 * 570 * @param fields indices of the fields 571 * @return The instance of the builder for chaining. 572 * @see DelimitedLineTokenizer#setIncludedFields(int[]) 573 */ 574 public DelimitedBuilder<T> includedFields(Integer[] fields) { 575 this.includedFields.addAll(Arrays.asList(fields)); 576 return this; 577 } 578 579 /** 580 * Add an index to the list of fields to be included from the file 581 * 582 * @param field the index to be included 583 * @return The instance of the builder for chaining. 584 * @see DelimitedLineTokenizer#setIncludedFields(int[]) 585 */ 586 public DelimitedBuilder<T> addIncludedField(int field) { 587 this.includedFields.add(field); 588 return this; 589 } 590 591 /** 592 * A factory for creating the resulting 593 * {@link org.springframework.batch.item.file.transform.FieldSet}. Defaults to 594 * {@link DefaultFieldSetFactory}. 595 * 596 * @param fieldSetFactory Factory for creating {@link org.springframework.batch.item.file.transform.FieldSet} 597 * @return The instance of the builder for chaining. 598 * @see DelimitedLineTokenizer#setFieldSetFactory(FieldSetFactory) 599 */ 600 public DelimitedBuilder<T> fieldSetFactory(FieldSetFactory fieldSetFactory) { 601 this.fieldSetFactory = fieldSetFactory; 602 return this; 603 } 604 605 /** 606 * Names of each of the fields within the fields that are returned in the order 607 * they occur within the delimited file. Required. 608 * 609 * @param names names of each field 610 * @return The parent {@link FlatFileItemReaderBuilder} 611 * @see DelimitedLineTokenizer#setNames(String[]) 612 */ 613 public FlatFileItemReaderBuilder<T> names(String [] names) { 614 this.names.addAll(Arrays.asList(names)); 615 return this.parent; 616 } 617 618 /** 619 * Returns a {@link DelimitedLineTokenizer} 620 * 621 * @return {@link DelimitedLineTokenizer} 622 */ 623 public DelimitedLineTokenizer build() { 624 Assert.notNull(this.fieldSetFactory, "A FieldSetFactory is required."); 625 Assert.notEmpty(this.names, "A list of field names is required"); 626 627 DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer(); 628 629 tokenizer.setNames(this.names.toArray(new String[this.names.size()])); 630 631 if(StringUtils.hasLength(this.delimiter)) { 632 tokenizer.setDelimiter(this.delimiter); 633 } 634 635 if(this.quoteCharacter != null) { 636 tokenizer.setQuoteCharacter(this.quoteCharacter); 637 } 638 639 if(!this.includedFields.isEmpty()) { 640 Set<Integer> deDupedFields = new HashSet<>(this.includedFields.size()); 641 deDupedFields.addAll(this.includedFields); 642 deDupedFields.remove(null); 643 644 int [] fields = new int[deDupedFields.size()]; 645 Iterator<Integer> iterator = deDupedFields.iterator(); 646 for(int i = 0; i < fields.length; i++) { 647 fields[i] = iterator.next(); 648 } 649 650 tokenizer.setIncludedFields(fields); 651 } 652 653 tokenizer.setFieldSetFactory(this.fieldSetFactory); 654 tokenizer.setStrict(this.strict); 655 656 try { 657 tokenizer.afterPropertiesSet(); 658 } 659 catch (Exception e) { 660 throw new IllegalStateException("Unable to initialize DelimitedLineTokenizer", e); 661 } 662 663 return tokenizer; 664 } 665 } 666 667 /** 668 * A builder for constructing a {@link FixedLengthTokenizer} 669 * 670 * @param <T> the type of the parent {@link FlatFileItemReaderBuilder} 671 */ 672 public static class FixedLengthBuilder<T> { 673 private FlatFileItemReaderBuilder<T> parent; 674 675 private List<Range> ranges = new ArrayList<>(); 676 677 private List<String> names = new ArrayList<>(); 678 679 private boolean strict = true; 680 681 private FieldSetFactory fieldSetFactory = new DefaultFieldSetFactory(); 682 683 protected FixedLengthBuilder(FlatFileItemReaderBuilder<T> parent) { 684 this.parent = parent; 685 } 686 687 /** 688 * The column ranges for each field 689 * 690 * @param ranges column ranges 691 * @return This instance for chaining 692 * @see FixedLengthTokenizer#setColumns(Range[]) 693 */ 694 public FixedLengthBuilder<T> columns(Range[] ranges) { 695 this.ranges.addAll(Arrays.asList(ranges)); 696 return this; 697 } 698 699 /** 700 * Add a column range to the existing list 701 * 702 * @param range a new column range 703 * @return This instance for chaining 704 * @see FixedLengthTokenizer#setColumns(Range[]) 705 */ 706 public FixedLengthBuilder<T> addColumns(Range range) { 707 this.ranges.add(range); 708 return this; 709 } 710 711 /** 712 * Insert a column range to the existing list 713 * 714 * @param range a new column range 715 * @param index index to add it at 716 * @return This instance for chaining 717 * @see FixedLengthTokenizer#setColumns(Range[]) 718 */ 719 public FixedLengthBuilder<T> addColumns(Range range, int index) { 720 this.ranges.add(index, range); 721 return this; 722 } 723 724 /** 725 * The names of the fields to be parsed from the file. Required. 726 * 727 * @param names names of fields 728 * @return The parent builder 729 * @see FixedLengthTokenizer#setNames(String[]) 730 */ 731 public FlatFileItemReaderBuilder<T> names(String [] names) { 732 this.names.addAll(Arrays.asList(names)); 733 return this.parent; 734 } 735 736 /** 737 * Boolean indicating if the number of tokens in a line must match the number of 738 * fields (ranges) configured. Defaults to true. 739 * 740 * @param strict defaults to true 741 * @return This instance for chaining 742 * @see FixedLengthTokenizer#setStrict(boolean) 743 */ 744 public FixedLengthBuilder<T> strict(boolean strict) { 745 this.strict = strict; 746 return this; 747 } 748 749 /** 750 * A factory for creating the resulting 751 * {@link org.springframework.batch.item.file.transform.FieldSet}. Defaults to 752 * {@link DefaultFieldSetFactory}. 753 * @param fieldSetFactory Factory for creating {@link org.springframework.batch.item.file.transform.FieldSet} 754 * @return The instance of the builder for chaining. 755 * @see FixedLengthTokenizer#setFieldSetFactory(FieldSetFactory) 756 */ 757 public FixedLengthBuilder<T> fieldSetFactory(FieldSetFactory fieldSetFactory) { 758 this.fieldSetFactory = fieldSetFactory; 759 return this; 760 } 761 762 /** 763 * Returns a {@link FixedLengthTokenizer} 764 * 765 * @return a {@link FixedLengthTokenizer} 766 */ 767 public FixedLengthTokenizer build() { 768 Assert.notNull(this.fieldSetFactory, "A FieldSetFactory is required."); 769 Assert.notEmpty(this.names, "A list of field names is required."); 770 Assert.notEmpty(this.ranges, "A list of column ranges is required."); 771 772 FixedLengthTokenizer tokenizer = new FixedLengthTokenizer(); 773 774 tokenizer.setNames(this.names.toArray(new String[this.names.size()])); 775 tokenizer.setColumns(this.ranges.toArray(new Range[this.ranges.size()])); 776 tokenizer.setFieldSetFactory(this.fieldSetFactory); 777 tokenizer.setStrict(this.strict); 778 779 return tokenizer; 780 } 781 } 782}