Skip to content

Commit 24a464f

Browse files
kwondh5217fmbenhassine
authored andcommitted
Support Lambda-style configuration in flat file item builders
Resolves #4818 Signed-off-by: Daeho Kwon <trewq231@naver.com>
1 parent 0564ce6 commit 24a464f

File tree

4 files changed

+658
-2
lines changed

4 files changed

+658
-2
lines changed

spring-batch-infrastructure/src/main/java/org/springframework/batch/infrastructure/item/file/builder/FlatFileItemReaderBuilder.java

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.List;
2626
import java.util.Map;
2727
import java.util.Set;
28+
import java.util.function.Consumer;
2829

2930
import org.apache.commons.logging.Log;
3031
import org.apache.commons.logging.LogFactory;
@@ -63,6 +64,7 @@
6364
* @author Patrick Baumgartner
6465
* @author François Martin
6566
* @author Stefano Cordio
67+
* @author Daeho Kwon
6668
* @since 4.0
6769
* @see FlatFileItemReader
6870
*/
@@ -319,6 +321,35 @@ public DelimitedBuilder<T> delimited() {
319321
return this.delimitedBuilder;
320322
}
321323

324+
/**
325+
* Configure a {@link DelimitedSpec} using a lambda.
326+
* @return the current builder instance
327+
* @since 6.0
328+
*/
329+
public FlatFileItemReaderBuilder<T> delimited(Consumer<DelimitedSpec<T>> config) {
330+
DelimitedSpecImpl<T> spec = new DelimitedSpecImpl<>();
331+
config.accept(spec);
332+
333+
DelimitedBuilder<T> builder = this.delimited();
334+
if (spec.delimiter != null) {
335+
builder.delimiter(spec.delimiter);
336+
}
337+
if (spec.quoteCharacter != null) {
338+
builder.quoteCharacter(spec.quoteCharacter);
339+
}
340+
if (!spec.names.isEmpty()) {
341+
builder.names(spec.names.toArray(new String[0]));
342+
}
343+
if (!spec.included.isEmpty()) {
344+
builder.includedFields(spec.included.toArray(new Integer[0]));
345+
}
346+
347+
builder.fieldSetFactory(spec.fieldSetFactory);
348+
builder.strict(spec.strict);
349+
350+
return this;
351+
}
352+
322353
/**
323354
* Returns an instance of a {@link FixedLengthBuilder} for building a
324355
* {@link FixedLengthTokenizer}. The {@link FixedLengthTokenizer} configured by this
@@ -332,6 +363,30 @@ public FixedLengthBuilder<T> fixedLength() {
332363
return this.fixedLengthBuilder;
333364
}
334365

366+
/**
367+
* Configure a {@link FixedLengthSpec} using a lambda.
368+
* @return the current builder instance
369+
* @since 6.0
370+
*/
371+
public FlatFileItemReaderBuilder<T> fixedLength(Consumer<FixedLengthSpec<T>> config) {
372+
FixedLengthSpecImpl<T> spec = new FixedLengthSpecImpl<>();
373+
config.accept(spec);
374+
375+
FixedLengthBuilder<T> builder = this.fixedLength();
376+
377+
if (!spec.ranges.isEmpty()) {
378+
builder.columns(spec.ranges.toArray(new Range[0]));
379+
}
380+
if (!spec.names.isEmpty()) {
381+
builder.names(spec.names.toArray(new String[0]));
382+
}
383+
384+
builder.fieldSetFactory(spec.fieldSetFactory);
385+
builder.strict(spec.strict);
386+
387+
return this;
388+
}
389+
335390
/**
336391
* The class that will represent the "item" to be returned from the reader. This class
337392
* is used via the {@link BeanWrapperFieldSetMapper}. If more complex logic is
@@ -775,4 +830,240 @@ public FixedLengthTokenizer build() {
775830

776831
}
777832

833+
/**
834+
* A specification for configuring a delimited file tokenizer.
835+
*
836+
* @param <T> the type of the parent {@link FlatFileItemReaderBuilder}
837+
* @since 6.0
838+
*/
839+
public interface DelimitedSpec<T> {
840+
841+
/**
842+
* Define the delimiter for the file.
843+
* @param delimiter String used as a delimiter between fields.
844+
* @return The instance of the specification for chaining.
845+
* @see DelimitedLineTokenizer#setDelimiter(String)
846+
*/
847+
DelimitedSpec<T> delimiter(String delimiter);
848+
849+
/**
850+
* Define the character used to quote fields.
851+
* @param quoteCharacter char used to define quoted fields
852+
* @return The instance of the specification for chaining.
853+
* @see DelimitedLineTokenizer#setQuoteCharacter(char)
854+
*/
855+
DelimitedSpec<T> quoteCharacter(char quoteCharacter);
856+
857+
/**
858+
* A list of indices of the fields within a delimited file to be included
859+
* @param fields indices of the fields
860+
* @return The instance of the specification for chaining.
861+
* @see DelimitedLineTokenizer#setIncludedFields(int[])
862+
*/
863+
DelimitedSpec<T> includedFields(Integer... fields);
864+
865+
/**
866+
* Add an index to the list of fields to be included from the file
867+
* @param field the index to be included
868+
* @return The instance of the specification for chaining.
869+
* @see DelimitedLineTokenizer#setIncludedFields(int[])
870+
*/
871+
DelimitedSpec<T> addIncludedField(int field);
872+
873+
/**
874+
* Names of each of the fields within the fields that are returned in the order
875+
* they occur within the delimited file. Required.
876+
* @param names names of each field
877+
* @return The instance of the specification for chaining.
878+
* @see DelimitedLineTokenizer#setNames(String[])
879+
*/
880+
DelimitedSpec<T> names(String... names);
881+
882+
/**
883+
* A factory for creating the resulting {@link FieldSet}. Defaults to
884+
* {@link DefaultFieldSetFactory}.
885+
* @param fieldSetFactory Factory for creating {@link FieldSet}
886+
* @return The instance of the specification for chaining.
887+
* @see DelimitedLineTokenizer#setFieldSetFactory(FieldSetFactory)
888+
*/
889+
DelimitedSpec<T> fieldSetFactory(FieldSetFactory fieldSetFactory);
890+
891+
/**
892+
* If true (the default) then the number of tokens in line must match the number
893+
* of tokens defined (by {@link Range}, columns, etc.) in {@link LineTokenizer}.
894+
* If false then lines with less tokens will be tolerated and padded with empty
895+
* columns, and lines with more tokens will simply be truncated.
896+
* @param strict the strict flag to set
897+
*/
898+
DelimitedSpec<T> strict(boolean strict);
899+
900+
}
901+
902+
/**
903+
* A specification for configuring a fixed length file tokenizer.
904+
*
905+
* @param <T> the type of the parent {@link FlatFileItemReaderBuilder}
906+
* @since 6.0
907+
*/
908+
public interface FixedLengthSpec<T> {
909+
910+
/**
911+
* The column ranges for each field
912+
* @param ranges column ranges
913+
* @return This instance for chaining
914+
* @see FixedLengthTokenizer#setColumns(Range[])
915+
*/
916+
FixedLengthSpec<T> columns(Range... ranges);
917+
918+
/**
919+
* Add a column range to the existing list
920+
* @param range a new column range
921+
* @return This instance for chaining
922+
* @see FixedLengthTokenizer#setColumns(Range[])
923+
*/
924+
FixedLengthSpec<T> addColumns(Range range);
925+
926+
/**
927+
* Insert a column range to the existing list
928+
* @param range a new column range
929+
* @param index index to add it at
930+
* @return This instance for chaining
931+
* @see FixedLengthTokenizer#setColumns(Range[])
932+
*/
933+
FixedLengthSpec<T> addColumns(Range range, int index);
934+
935+
/**
936+
* The names of the fields to be parsed from the file. Required.
937+
* @param names names of fields
938+
* @return The parent builder
939+
* @see FixedLengthTokenizer#setNames(String[])
940+
*/
941+
FixedLengthSpec<T> names(String... names);
942+
943+
/**
944+
* A factory for creating the resulting {@link FieldSet}. Defaults to
945+
* {@link DefaultFieldSetFactory}.
946+
* @param fieldSetFactory Factory for creating {@link FieldSet}
947+
* @return The instance of the specification for chaining.
948+
* @see FixedLengthTokenizer#setFieldSetFactory(FieldSetFactory)
949+
*/
950+
FixedLengthSpec<T> fieldSetFactory(FieldSetFactory fieldSetFactory);
951+
952+
/**
953+
* Boolean indicating if the number of tokens in a line must match the number of
954+
* fields (ranges) configured. Defaults to true.
955+
* @param strict defaults to true
956+
* @return This instance for chaining
957+
* @see FixedLengthTokenizer#setStrict(boolean)
958+
*/
959+
FixedLengthSpec<T> strict(boolean strict);
960+
961+
}
962+
963+
private static class DelimitedSpecImpl<T> implements DelimitedSpec<T> {
964+
965+
private final List<String> names = new ArrayList<>();
966+
967+
private @Nullable String delimiter;
968+
969+
private @Nullable Character quoteCharacter;
970+
971+
private final List<Integer> included = new ArrayList<>();
972+
973+
private FieldSetFactory fieldSetFactory = new DefaultFieldSetFactory();
974+
975+
private boolean strict = true;
976+
977+
@Override
978+
public DelimitedSpec<T> delimiter(String delimiter) {
979+
this.delimiter = delimiter;
980+
return this;
981+
}
982+
983+
@Override
984+
public DelimitedSpec<T> quoteCharacter(char quoteCharacter) {
985+
this.quoteCharacter = quoteCharacter;
986+
return this;
987+
}
988+
989+
@Override
990+
public DelimitedSpec<T> includedFields(Integer... fields) {
991+
this.included.addAll(Arrays.asList(fields));
992+
return this;
993+
}
994+
995+
@Override
996+
public DelimitedSpec<T> addIncludedField(int field) {
997+
this.included.add(field);
998+
return this;
999+
}
1000+
1001+
@Override
1002+
public DelimitedSpec<T> names(String... names) {
1003+
this.names.addAll(Arrays.asList(names));
1004+
return this;
1005+
}
1006+
1007+
@Override
1008+
public DelimitedSpec<T> fieldSetFactory(FieldSetFactory f) {
1009+
this.fieldSetFactory = f;
1010+
return this;
1011+
}
1012+
1013+
@Override
1014+
public DelimitedSpec<T> strict(boolean strict) {
1015+
this.strict = strict;
1016+
return this;
1017+
}
1018+
1019+
}
1020+
1021+
private static class FixedLengthSpecImpl<T> implements FixedLengthSpec<T> {
1022+
1023+
private final List<Range> ranges = new ArrayList<>();
1024+
1025+
private final List<String> names = new ArrayList<>();
1026+
1027+
private boolean strict = true;
1028+
1029+
private FieldSetFactory fieldSetFactory = new DefaultFieldSetFactory();
1030+
1031+
@Override
1032+
public FixedLengthSpec<T> columns(Range... ranges) {
1033+
this.ranges.addAll(Arrays.asList(ranges));
1034+
return this;
1035+
}
1036+
1037+
@Override
1038+
public FixedLengthSpec<T> addColumns(Range range) {
1039+
this.ranges.add(range);
1040+
return this;
1041+
}
1042+
1043+
@Override
1044+
public FixedLengthSpec<T> addColumns(Range range, int index) {
1045+
this.ranges.add(index, range);
1046+
return this;
1047+
}
1048+
1049+
@Override
1050+
public FixedLengthSpec<T> names(String... names) {
1051+
this.names.addAll(Arrays.asList(names));
1052+
return this;
1053+
}
1054+
1055+
@Override
1056+
public FixedLengthSpec<T> fieldSetFactory(FieldSetFactory f) {
1057+
this.fieldSetFactory = f;
1058+
return this;
1059+
}
1060+
1061+
@Override
1062+
public FixedLengthSpec<T> strict(boolean strict) {
1063+
this.strict = strict;
1064+
return this;
1065+
}
1066+
1067+
}
1068+
7781069
}

0 commit comments

Comments
 (0)