Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save HtetWaiYanLin/248f5ff8a3068e3862b3d50c99fc8d67 to your computer and use it in GitHub Desktop.

Select an option

Save HtetWaiYanLin/248f5ff8a3068e3862b3d50c99fc8d67 to your computer and use it in GitHub Desktop.

Revisions

  1. @ammmze ammmze revised this gist Feb 13, 2019. 1 changed file with 1 addition and 2 deletions.
    3 changes: 1 addition & 2 deletions HeaderColumnNameAndOrderMappingStrategyTest.java
    Original file line number Diff line number Diff line change
    @@ -73,8 +73,7 @@ else if(field.isAnnotationPresent(CsvDate.class)) {
    }

    private String extractHeaderName(final BeanField beanField) {
    if (beanField == null || beanField.getField() == null
    || beanField.getField().getDeclaredAnnotationsByType(CsvBindByName.class).length == 0) {
    if (beanField == null || beanField.getField() == null) {
    return StringUtils.EMPTY;
    }

  2. @ammmze ammmze revised this gist Feb 12, 2019. 4 changed files with 216 additions and 93 deletions.
    69 changes: 69 additions & 0 deletions ConverterDateAndJavaTime.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,69 @@
    package com.example.csv;

    import com.opencsv.bean.ConverterDate;
    import com.opencsv.exceptions.CsvDataTypeMismatchException;
    import java.lang.reflect.InvocationTargetException;
    import java.time.LocalDate;
    import java.time.LocalTime;
    import java.time.format.DateTimeFormatter;
    import java.time.temporal.Temporal;
    import java.util.Locale;
    import org.apache.commons.lang3.StringUtils;

    public class ConverterDateAndJavaTime extends ConverterDate {
    private static String DEFAULT_FORMAT = "yyyyMMdd'T'HHmmss";
    private static String DEFAULT_DATE_ONLY_FORMAT = "yyyyMMdd";
    private static String DEFAULT_TIME_ONLY_FORMAT = "HHmmss";
    private DateTimeFormatter format;
    /**
    * @param type The type of the field being populated
    * @param formatString The string to use for formatting the date. See
    * {@link com.opencsv.bean.CsvDate#value()}
    * @param locale If not null or empty, specifies the locale used for
    * converting locale-specific data types
    * @param errorLocale The locale to use for error messages.
    */
    public ConverterDateAndJavaTime(Class<?> type, String locale, Locale errorLocale, String formatString) {
    super(type, locale, errorLocale, formatString);

    // if the type is LocalDate and using the default format, we know it will fail. Lets use just the date portion of the default date format
    if (DEFAULT_FORMAT.equals(formatString) && LocalDate.class.isAssignableFrom(type)) {
    formatString = DEFAULT_DATE_ONLY_FORMAT;
    } else if (DEFAULT_FORMAT.equals(formatString) && LocalTime.class.isAssignableFrom(type)) {
    formatString = DEFAULT_TIME_ONLY_FORMAT;
    }
    if (this.locale != null) {
    format = DateTimeFormatter.ofPattern(formatString, this.locale);
    } else {
    format = DateTimeFormatter.ofPattern(formatString);
    }
    }

    @Override
    public Object convertToRead(String value) throws CsvDataTypeMismatchException {
    if (StringUtils.isNotBlank(value) && Temporal.class.isAssignableFrom(type)) {
    return convertToTemporal(value);
    }
    return super.convertToRead(value);
    }

    @Override
    public String convertToWrite(Object value) throws CsvDataTypeMismatchException {
    if (value != null && Temporal.class.isAssignableFrom(value.getClass())) {
    return convertFromTemporal((Temporal) value);
    }
    return super.convertToWrite(value);
    }

    private Temporal convertToTemporal(String value) {
    try {
    return (Temporal) type.getMethod("parse", CharSequence.class, DateTimeFormatter.class).invoke(null, value, format);
    } catch (NoSuchMethodException|IllegalAccessException|InvocationTargetException e) {
    throw new RuntimeException("Failed to invoke the parse method of " + type.getName(), e);
    }
    }

    private String convertFromTemporal(Temporal value) {
    return format.format(value);
    }
    }
    84 changes: 84 additions & 0 deletions ConverterDateAndJavaTimeTest.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,84 @@
    package com.example.csv;

    import static org.junit.Assert.assertEquals;
    import static org.junit.Assert.assertNotNull;
    import static org.junit.Assert.assertNull;

    import com.opencsv.bean.CsvConverter;
    import com.opencsv.bean.CsvDate;
    import java.time.LocalDate;
    import java.time.LocalDateTime;
    import java.time.LocalTime;
    import java.util.Locale;
    import org.junit.Test;

    public class ConverterDateAndJavaTimeTest {
    private static final Locale ERROR_LOCALE = Locale.getDefault();
    private static final String LOCALE = ERROR_LOCALE.toString();
    private static String DEFAULT_FORMAT;

    static {
    try {
    DEFAULT_FORMAT = (String) CsvDate.class.getMethod("value").getDefaultValue();
    } catch (NoSuchMethodException e) {
    e.printStackTrace();
    }
    }

    @Test
    public void convertToRead_When_ValueIsBlank_Expect_ToReceiveNull() throws Exception {
    assertNull(converter(LocalDate.class, DEFAULT_FORMAT).convertToRead(""));
    }

    @Test
    public void convertToRead_When_ValueIsValidAndTypeIsLocalDate_Expect_ToReceiveLocalDate() throws Exception {
    LocalDate actual = (LocalDate) converter(LocalDate.class, DEFAULT_FORMAT).convertToRead("20190214");
    assertNotNull(actual);
    assertEquals(2019, actual.getYear());
    assertEquals(2, actual.getMonthValue());
    assertEquals(14, actual.getDayOfMonth());
    }

    @Test
    public void convertToRead_When_ValueIsValidAndTypeIsLocalTime_Expect_ToReceiveLocalTime() throws Exception {
    LocalTime actual = (LocalTime) converter(LocalTime.class, DEFAULT_FORMAT).convertToRead("221546");
    assertNotNull(actual);
    assertEquals(22, actual.getHour());
    assertEquals(15, actual.getMinute());
    assertEquals(46, actual.getSecond());
    }

    @Test
    public void convertToRead_When_ValueIsValidAndTypeIsLocalDateTime_Expect_ToReceiveLocalDateTime() throws Exception {
    LocalDateTime actual = (LocalDateTime) converter(LocalDateTime.class, DEFAULT_FORMAT).convertToRead("20190214T221546");
    assertNotNull(actual);
    assertEquals(2019, actual.getYear());
    assertEquals(2, actual.getMonthValue());
    assertEquals(14, actual.getDayOfMonth());
    assertEquals(22, actual.getHour());
    assertEquals(15, actual.getMinute());
    assertEquals(46, actual.getSecond());
    }

    @Test
    public void convertToWrite_When_ValueIsLocalDate_Expect_ToReceiveFormattedDate() throws Exception {
    String actual = converter(LocalDate.class, DEFAULT_FORMAT).convertToWrite(LocalDate.of(2019, 2, 14));
    assertEquals("20190214", actual);
    }

    @Test
    public void convertToWrite_When_ValueIsLocalTime_Expect_ToReceiveFormattedTime() throws Exception {
    String actual = converter(LocalTime.class, DEFAULT_FORMAT).convertToWrite(LocalTime.of(22, 15, 46));
    assertEquals("221546", actual);
    }

    @Test
    public void convertToWrite_When_ValueIsLocalDateTime_Expect_ToReceiveFormattedDateTime() throws Exception {
    String actual = converter(LocalDateTime.class, DEFAULT_FORMAT).convertToWrite(LocalDateTime.of(2019, 2, 14,22, 15, 46));
    assertEquals("20190214T221546", actual);
    }

    private CsvConverter converter(Class type, String format) {
    return new ConverterDateAndJavaTime(type, LOCALE, ERROR_LOCALE, format);
    }
    }
    155 changes: 62 additions & 93 deletions HeaderColumnNameAndOrderMappingStrategyTest.java
    Original file line number Diff line number Diff line change
    @@ -1,120 +1,89 @@
    package com.example.csv;

    import static org.junit.Assert.assertEquals;

    import com.opencsv.bean.AbstractCsvConverter;
    import com.opencsv.bean.BeanField;
    import com.opencsv.bean.CsvBindByName;
    import com.opencsv.bean.StatefulBeanToCsv;
    import com.opencsv.bean.StatefulBeanToCsvBuilder;
    import java.io.StringWriter;
    import java.util.Collections;
    import java.util.List;
    import org.junit.Test;

    public class HeaderColumnNameAndOrderMappingStrategyTest {

    @Test
    public void getCsv_When_AllNamedColumnsAreListedInTheCsvBindByNameOrderAnnotation_Expect_ToReceiveHeadersInCorrectOrder() throws Exception {
    String csv = getCsv(Collections.singletonList(new PojoWithAllColumnsInOrderAnnotation()), PojoWithAllColumnsInOrderAnnotation.class);
    assertEquals("Baz,Foo,Bar", csv.split("\n")[0]);
    }

    @Test
    public void getCsv_When_CustomOrderIsGiven_Expect_ToReceiveColumnDataInTheCorrectOrder() throws Exception {
    PojoWithAllColumnsInOrderAnnotation pojo = new PojoWithAllColumnsInOrderAnnotation();
    pojo.setBar(1);
    pojo.setBaz(2);
    pojo.setFoo(3);
    String csv = getCsv(Collections.singletonList(pojo), PojoWithAllColumnsInOrderAnnotation.class);
    assertEquals("2,3,1", csv.split("\n")[1]);
    import com.opencsv.bean.CsvConverter;
    import com.opencsv.bean.CsvCustomBindByName;
    import com.opencsv.bean.CsvDate;
    import com.opencsv.bean.HeaderColumnNameMappingStrategy;
    import com.opencsv.bean.comparator.LiteralComparator;
    import com.opencsv.exceptions.CsvBadConverterException;
    import com.opencsv.exceptions.CsvRequiredFieldEmptyException;
    import java.lang.reflect.Field;
    import java.util.Arrays;
    import org.apache.commons.lang3.StringUtils;

    public class HeaderColumnNameAndOrderMappingStrategy<T> extends HeaderColumnNameMappingStrategy<T> {

    public HeaderColumnNameAndOrderMappingStrategy(Class<T> type) {
    setType(type);
    }

    @Test
    public void getCsv_When_SomeNamedColumnsAreListedInTheCsvBindByNameOrderAnnotation_Expect_ToReceiveHeaderInOrderOfTheAnnotationAndAllRemainingOnesAreAddedAlphabetically() throws Exception {
    String csv = getCsv(Collections.singletonList(new PojoWithFirstColumnInOrderAnnotation()), PojoWithFirstColumnInOrderAnnotation.class);
    assertEquals("Baz,Bar,Foo", csv.split("\n")[0]);
    }

    private <T> String getCsv(List<T> beans, Class<T> type) throws Exception {
    StringWriter writer = new StringWriter();
    StatefulBeanToCsv<T> csvWriter = new StatefulBeanToCsvBuilder<T>(writer)
    .withApplyQuotesToAll(false)
    .withMappingStrategy(new HeaderColumnNameAndOrderMappingStrategy<>(type))
    .build();
    csvWriter.write(beans);
    return writer.toString();
    }
    @Override
    public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {
    // overriding this method to allow us to preserve the header column name casing

    @CsvBindByNameOrder({"Baz","Foo","Bar"})
    public static class PojoWithAllColumnsInOrderAnnotation {

    @CsvBindByName(column = "Bar")
    private Integer bar;

    @CsvBindByName(column = "Foo")
    private Integer foo;

    @CsvBindByName(column = "Baz")
    private Integer baz;

    public Integer getBar() {
    return bar;
    String[] header = super.generateHeader(bean);
    final int numColumns = findMaxFieldIndex();
    if (!isAnnotationDriven() || numColumns == -1) {
    return header;
    }

    public void setBar(Integer bar) {
    this.bar = bar;
    }
    header = new String[numColumns + 1];

    public Integer getFoo() {
    return foo;
    BeanField beanField;
    for (int i = 0; i <= numColumns; i++) {
    beanField = findField(i);
    String columnHeaderName = extractHeaderName(beanField);
    header[i] = columnHeaderName;
    }
    return header;
    }

    public void setFoo(Integer foo) {
    this.foo = foo;
    }

    public Integer getBaz() {
    return baz;
    }

    public void setBaz(Integer baz) {
    this.baz = baz;
    @Override
    protected void loadFieldMap() throws CsvBadConverterException {
    // overriding this method to support setting column order by the custom `CsvBindByNameOrder` annotation
    if (writeOrder == null && type.isAnnotationPresent(CsvBindByNameOrder.class)) {
    setColumnOrderOnWrite(
    new LiteralComparator<>(Arrays.stream(type.getAnnotation(CsvBindByNameOrder.class).value())
    .map(String::toUpperCase).toArray(String[]::new)));
    }
    super.loadFieldMap();
    }

    @CsvBindByNameOrder({"Baz"})
    public static class PojoWithFirstColumnInOrderAnnotation {

    @CsvBindByName(column = "Bar")
    private Integer bar;
    @Override
    protected CsvConverter determineConverter(Field field, Class<?> elementType, String locale,
    Class<? extends AbstractCsvConverter> customConverter) throws CsvBadConverterException {
    // overrides the converter for the `CsvDate` to use our custom converter that supports java.time api

    @CsvBindByName(column = "Foo")
    private Integer foo;

    @CsvBindByName(column = "Baz")
    private Integer baz;

    public Integer getBar() {
    return bar;
    // A custom converter always takes precedence if specified.
    if(customConverter != null && !customConverter.equals(AbstractCsvConverter.class)) {
    return super.determineConverter(field, elementType, locale, customConverter);
    }

    public void setBar(Integer bar) {
    this.bar = bar;
    // Perhaps a date instead
    else if(field.isAnnotationPresent(CsvDate.class)) {
    String formatString = field.getAnnotation(CsvDate.class).value();
    return new ConverterDateAndJavaTime(elementType, locale, errorLocale, formatString);
    }

    public Integer getFoo() {
    return foo;
    }
    return super.determineConverter(field, elementType, locale, customConverter);
    }

    public void setFoo(Integer foo) {
    this.foo = foo;
    private String extractHeaderName(final BeanField beanField) {
    if (beanField == null || beanField.getField() == null
    || beanField.getField().getDeclaredAnnotationsByType(CsvBindByName.class).length == 0) {
    return StringUtils.EMPTY;
    }

    public Integer getBaz() {
    return baz;
    if (beanField.getField().isAnnotationPresent(CsvBindByName.class)) {
    return beanField.getField().getDeclaredAnnotationsByType(CsvBindByName.class)[0].column();
    } else if (beanField.getField().isAnnotationPresent(CsvCustomBindByName.class)) {
    return beanField.getField().getDeclaredAnnotationsByType(CsvCustomBindByName.class)[0].column();
    }
    return StringUtils.EMPTY;

    public void setBaz(Integer baz) {
    this.baz = baz;
    }
    }
    }
    1 change: 1 addition & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -5,6 +5,7 @@ This creates a MappingStrategy for use with OpenCSV (specifically tested for gen
    1. Preserves the column name casing in the `@CsvBindByName` annotation
    2. Adds a `@CsvBindByNameOrder` annotation you can apply to the bean class to define the order of the columns.
    * Any field not included in the order, but is still annotated with `@CsvBindName` will still be included AFTER all the columns that have a defined order. Those remaining columns will be added in alphabetical order (this is the default behavior of the `HeaderColumnNameMappingStrategy`)
    3. Overrides the converter used with `@CsvDate` to use a custom converter that adds support for the java time api (`LocalDate`, `LocalTime`, and `LocalDateTime` are tested)

    ## Usage

  3. @ammmze ammmze revised this gist Feb 12, 2019. 1 changed file with 8 additions and 3 deletions.
    11 changes: 8 additions & 3 deletions HeaderColumnNameAndOrderMappingStrategy.java
    Original file line number Diff line number Diff line change
    @@ -2,6 +2,7 @@

    import com.opencsv.bean.BeanField;
    import com.opencsv.bean.CsvBindByName;
    import com.opencsv.bean.CsvCustomBindByName;
    import com.opencsv.bean.HeaderColumnNameMappingStrategy;
    import com.opencsv.bean.comparator.LiteralComparator;
    import com.opencsv.exceptions.CsvBadConverterException;
    @@ -54,8 +55,12 @@ private String extractHeaderName(final BeanField beanField) {
    return StringUtils.EMPTY;
    }

    final CsvBindByName bindByNameAnnotation = beanField.getField()
    .getDeclaredAnnotationsByType(CsvBindByName.class)[0];
    return bindByNameAnnotation.column();
    if (beanField.getField().isAnnotationPresent(CsvBindByName.class)) {
    return beanField.getField().getDeclaredAnnotationsByType(CsvBindByName.class)[0].column();
    } else if (beanField.getField().isAnnotationPresent(CsvCustomBindByName.class)) {
    return beanField.getField().getDeclaredAnnotationsByType(CsvCustomBindByName.class)[0].column();
    }
    return StringUtils.EMPTY;

    }
    }
  4. @ammmze ammmze created this gist Feb 12, 2019.
    14 changes: 14 additions & 0 deletions CsvBindByNameOrder.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,14 @@
    package com.example.csv;

    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface CsvBindByNameOrder {
    String[] value() default {};
    }
    61 changes: 61 additions & 0 deletions HeaderColumnNameAndOrderMappingStrategy.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,61 @@
    package com.example.csv;

    import com.opencsv.bean.BeanField;
    import com.opencsv.bean.CsvBindByName;
    import com.opencsv.bean.HeaderColumnNameMappingStrategy;
    import com.opencsv.bean.comparator.LiteralComparator;
    import com.opencsv.exceptions.CsvBadConverterException;
    import com.opencsv.exceptions.CsvRequiredFieldEmptyException;
    import java.util.Arrays;
    import org.apache.commons.lang3.StringUtils;

    public class HeaderColumnNameAndOrderMappingStrategy<T> extends HeaderColumnNameMappingStrategy<T> {

    public HeaderColumnNameAndOrderMappingStrategy(Class<T> type) {
    setType(type);
    }

    @Override
    public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {
    // overriding this method to allow us to preserve the header column name casing

    String[] header = super.generateHeader(bean);
    final int numColumns = findMaxFieldIndex();
    if (!isAnnotationDriven() || numColumns == -1) {
    return header;
    }

    header = new String[numColumns + 1];

    BeanField beanField;
    for (int i = 0; i <= numColumns; i++) {
    beanField = findField(i);
    String columnHeaderName = extractHeaderName(beanField);
    header[i] = columnHeaderName;
    }
    return header;
    }


    @Override
    protected void loadFieldMap() throws CsvBadConverterException {
    // overriding this method to support setting column order by the custom `CsvBindByNameOrder` annotation
    if (writeOrder == null && type.isAnnotationPresent(CsvBindByNameOrder.class)) {
    setColumnOrderOnWrite(
    new LiteralComparator<>(Arrays.stream(type.getAnnotation(CsvBindByNameOrder.class).value())
    .map(String::toUpperCase).toArray(String[]::new)));
    }
    super.loadFieldMap();
    }

    private String extractHeaderName(final BeanField beanField) {
    if (beanField == null || beanField.getField() == null
    || beanField.getField().getDeclaredAnnotationsByType(CsvBindByName.class).length == 0) {
    return StringUtils.EMPTY;
    }

    final CsvBindByName bindByNameAnnotation = beanField.getField()
    .getDeclaredAnnotationsByType(CsvBindByName.class)[0];
    return bindByNameAnnotation.column();
    }
    }
    120 changes: 120 additions & 0 deletions HeaderColumnNameAndOrderMappingStrategyTest.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,120 @@
    package com.example.csv;

    import static org.junit.Assert.assertEquals;

    import com.opencsv.bean.CsvBindByName;
    import com.opencsv.bean.StatefulBeanToCsv;
    import com.opencsv.bean.StatefulBeanToCsvBuilder;
    import java.io.StringWriter;
    import java.util.Collections;
    import java.util.List;
    import org.junit.Test;

    public class HeaderColumnNameAndOrderMappingStrategyTest {

    @Test
    public void getCsv_When_AllNamedColumnsAreListedInTheCsvBindByNameOrderAnnotation_Expect_ToReceiveHeadersInCorrectOrder() throws Exception {
    String csv = getCsv(Collections.singletonList(new PojoWithAllColumnsInOrderAnnotation()), PojoWithAllColumnsInOrderAnnotation.class);
    assertEquals("Baz,Foo,Bar", csv.split("\n")[0]);
    }

    @Test
    public void getCsv_When_CustomOrderIsGiven_Expect_ToReceiveColumnDataInTheCorrectOrder() throws Exception {
    PojoWithAllColumnsInOrderAnnotation pojo = new PojoWithAllColumnsInOrderAnnotation();
    pojo.setBar(1);
    pojo.setBaz(2);
    pojo.setFoo(3);
    String csv = getCsv(Collections.singletonList(pojo), PojoWithAllColumnsInOrderAnnotation.class);
    assertEquals("2,3,1", csv.split("\n")[1]);
    }

    @Test
    public void getCsv_When_SomeNamedColumnsAreListedInTheCsvBindByNameOrderAnnotation_Expect_ToReceiveHeaderInOrderOfTheAnnotationAndAllRemainingOnesAreAddedAlphabetically() throws Exception {
    String csv = getCsv(Collections.singletonList(new PojoWithFirstColumnInOrderAnnotation()), PojoWithFirstColumnInOrderAnnotation.class);
    assertEquals("Baz,Bar,Foo", csv.split("\n")[0]);
    }

    private <T> String getCsv(List<T> beans, Class<T> type) throws Exception {
    StringWriter writer = new StringWriter();
    StatefulBeanToCsv<T> csvWriter = new StatefulBeanToCsvBuilder<T>(writer)
    .withApplyQuotesToAll(false)
    .withMappingStrategy(new HeaderColumnNameAndOrderMappingStrategy<>(type))
    .build();
    csvWriter.write(beans);
    return writer.toString();
    }

    @CsvBindByNameOrder({"Baz","Foo","Bar"})
    public static class PojoWithAllColumnsInOrderAnnotation {

    @CsvBindByName(column = "Bar")
    private Integer bar;

    @CsvBindByName(column = "Foo")
    private Integer foo;

    @CsvBindByName(column = "Baz")
    private Integer baz;

    public Integer getBar() {
    return bar;
    }

    public void setBar(Integer bar) {
    this.bar = bar;
    }

    public Integer getFoo() {
    return foo;
    }

    public void setFoo(Integer foo) {
    this.foo = foo;
    }

    public Integer getBaz() {
    return baz;
    }

    public void setBaz(Integer baz) {
    this.baz = baz;
    }
    }

    @CsvBindByNameOrder({"Baz"})
    public static class PojoWithFirstColumnInOrderAnnotation {

    @CsvBindByName(column = "Bar")
    private Integer bar;

    @CsvBindByName(column = "Foo")
    private Integer foo;

    @CsvBindByName(column = "Baz")
    private Integer baz;

    public Integer getBar() {
    return bar;
    }

    public void setBar(Integer bar) {
    this.bar = bar;
    }

    public Integer getFoo() {
    return foo;
    }

    public void setFoo(Integer foo) {
    this.foo = foo;
    }

    public Integer getBaz() {
    return baz;
    }

    public void setBaz(Integer baz) {
    this.baz = baz;
    }
    }
    }
    52 changes: 52 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,52 @@
    # HeaderColumnNameAndOrderMappingStrategy

    This creates a MappingStrategy for use with OpenCSV (specifically tested for generating a CSV from beans) which does the following:

    1. Preserves the column name casing in the `@CsvBindByName` annotation
    2. Adds a `@CsvBindByNameOrder` annotation you can apply to the bean class to define the order of the columns.
    * Any field not included in the order, but is still annotated with `@CsvBindName` will still be included AFTER all the columns that have a defined order. Those remaining columns will be added in alphabetical order (this is the default behavior of the `HeaderColumnNameMappingStrategy`)

    ## Usage

    **Annotate your bean with something like...**

    ```java
    @CsvBindByNameOrder({"Foo","Bar"})
    public class MyBean {
    @CsvBindByName(column = "Foo")
    private String foo;

    @CsvBindByName(column = "Bar")
    private String bar;

    // getter/setters omitted for brevity
    }
    ```

    **Setup your writer...**

    ```java
    List<MyBean> beans = new ArrayList();
    MyBean bean = new MyBean();
    bean.setFoo("fooit");
    bean.setBar("barit");
    beans.add(bean);

    StringWriter writer = new StringWriter();
    StatefulBeanToCsv<MyBean> csvWriter = new StatefulBeanToCsvBuilder<MyBean>(writer)
    .withApplyQuotesToAll(false)
    .withMappingStrategy(new HeaderColumnNameAndOrderMappingStrategy<>(MyBean.class))
    .build();
    csvWriter.write(beans);
    return writer.toString();
    ```

    **Results**

    With the above you should get something like...

    ```csv
    Foo,Bar
    fooit,barit
    ```