Search code examples
javabean-io

write a header so that segment default name is also printed on the basis of max occurrence


Right now, I am able to write the header but not able to print the header name which is inside the segment is there any way to do that?

input file-

1john dew BA xxx  
1sam hart MA yyy

It is not printing a default name inside segment.
output file-

Record,FirstName,LastName,Title,Filler  
1,john,dew,22,85,22,85,22,85,BA,xxx  
1,sam,hart,78,45,78,45,78,45,MA,yyy

Java:

public class XlsWriter {
    public static void main(String[] args) throws Exception {

        StreamFactory factory = StreamFactory.newInstance();

        factory.load("C:\\Users\\PV5057094\\Demo_workspace\\XlsxMapper\\src\\main\\resources\\Employee.xml");


    BeanReader br = factory.createReader("EmployeeInfo",new File("C:\\Temp\\Soc\\textInput.txt"));

        BeanWriter out = factory.createWriter("EmployeeInfoCSV", new File("C:\\Temp\\Soc\\output.csv"));

        out.write("headers",null);
        Object record;

        while ((record=br.read())!=null) {


            out.write(record);

            System.out.println("Record Written:" + record.toString());

        }

        // in.close();
        out.flush();
        out.close();
    }

}

BeanIO-
<?xml version="1.0" encoding="UTF-8"?>
<beanio xmlns="http://www.beanio.org/2012/03"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.beanio.org/2012/03 http://www.beanio.org/2012/03/mapping.xsd">

    <stream name="EmployeeInfo" format="fixedlength">

        <record name="a" minOccurs="0" maxOccurs="unbounded"
            class="com.Employee">
            <field name="record" length="3" literal="AAA" rid="true" />
            <field name="firstName" length="5" />
            <field name="lastName" length="5" />
            <segment name="error" collection="list" minOccurs="0"
                maxOccurs="3" class="com.Error">
                <field name="origin_of_error" length="2" />
                <field name="fld_name" length="2" />

            </segment>
            <field name="title" length="5" />
            <field name="filler" length="5" />
        </record>

    </stream>


    <stream name="EmployeeInfoCSV" format="csv">
        <record name="headers" minOccurs="1" maxOccurs="1">
            <field name="recordColumn" default="Record" />
            <field name="firstNameColumn" default="FirstName" />
            <field name="lastNameColumn" default="LastName" />
            <segment name="error" collection="list" minOccurs="0"
                maxOccurs="5" class="com.Error">
                <field name="origin_of_error" default="origin_of_error" />
                <field name="fld_name" default="fld_name" />

            </segment>
            <field name="titleColumn" default="Title" />
            <field name="fillerColumn" default="Filler" />
        </record>
        <record name="a" minOccurs="0" maxOccurs="unbounded"
            class="com.Employee">
            <field name="record" length="3" literal="AAA" rid="true" />
            <field name="firstName" length="5" />
            <field name="lastName" length="5" />
            <segment name="error" collection="list" minOccurs="0"
                maxOccurs="3" class="com.Error">
                <field name="origin_of_error" length="2" />
                <field name="fld_name" length="2" />

            </segment>
            <field name="title" length="5" />
            <field name="filler" length="5" />
        </record>
    </stream>
</beanio>

Expected output-

Record,FirstName,LastName,origin_of_error,fld_name,origin_of_error,fld_name,origin_of_error,fld_name,Title,Filler
1,john,dew,22,85,22,85,22,85,BA,xxx
1,sam,hart,78,45,78,45,78,45,MA,yyy


Solution

  • To have the column headers dynamic, we need to move the column headers into classes of their own.

    Add a HeaderColumns and ErrorColumns classes to contain the names of the different columns.

    public class HeaderColumns {
    
      private String recordColumn = "Record";
      private String firstNameColumn = "FirstName";
      private String lastNameColumn = "LastName";
      private String titleColumn = "Title";
      private String fillerColumn = "Filler";
      private List<ErrorColumns> errorColumns;
    
      public void addErrorColumns(final ErrorColumns errorColumn) {
        if (errorColumns == null) {
          errorColumns = new ArrayList<>();
        }
        errorColumns.add(errorColumn);
      }
      // getter/setters removed
    }
    

    Note the addErrorColumns method.

    public class ErrorColumns {
    
      private String originOfErrorColumn = "origin_of_error";
      private String fldNameColumn = "fld_name";
      // getter/setters removed
    }
    

    We need to add a method to the Employee class to return the count of errors that was read.

    public class Employee {
    
      private String record;
      private String firstName;
      private String lastName;
      private String title;
      private String filler;
      private int errorCount;
      private List<Error> error;
    
      public int getErrorCount() {
        return error != null ? error.size() : 0;
      }
      // getter/setters removed
    }
    

    The columns' record definition headers in the mapping file now looks a lot like the actual record definition for the data a.

    <stream name="EmployeeInfoCSV" format="csv">
      <record name="headers" minOccurs="1" maxOccurs="1" 
              class="com.HeaderColumns">
        <field name="recordColumn" rid="true" literal="Record"/>
        <field name="firstNameColumn"/>
        <field name="lastNameColumn"/>
        <segment name="errorColumns" collection="list" minOccurs="0" maxOccurs="unbounded" 
                 class="com.ErrorColumns">
          <field name="originOfErrorColumn"/>
          <field name="fldNameColumn"/>
        </segment>
        <field name="titleColumn"/>
        <field name="fillerColumn"/>
      </record>
      <record name="a" minOccurs="0" maxOccurs="unbounded" 
              class="com.Employee">
        <field name="record" length="1"/>
        <field name="firstName" maxLength="5"/>
        <field name="lastName" maxLength="5"/>
        <segment name="error" collection="list" minOccurs="0" maxOccurs="unbounded" 
                 class="com.Error">
          <field name="origin_of_error" maxLength="2"/>
          <field name="fld_name" maxLength="2"/>
        </segment>
        <field name="title" maxLength="5"/>
        <field name="filler" maxLength="5"/>
      </record>
    </stream>
    

    Now we need to make sure that the errorColumns list get filled with the same number of objects that we read for the first Employee record.

    public static void main(String[] args) throws Exception {
    
        StreamFactory factory = StreamFactory.newInstance();
        factory.load("C:\\Users\\PV5057094\\Demo_workspace\\XlsxMapper\\src\\main\\resources\\Employee.xml");
    
        BeanReader br = factory.createReader("EmployeeInfo",new File("C:\\Temp\\Soc\\textInput.txt"));
        BeanWriter out = factory.createWriter("EmployeeInfoCSV", new File("C:\\Temp\\Soc\\output.csv"));
    
        boolean columnHeadersWritten = false;
        Object record;
        while ((record=br.read())!=null) {
    
          if (!columnHeadersWritten) {
            final Employee employee = (Employee) record;
            final HeaderColumns headerColumns = new HeaderColumns();
            for (int i = 0; i < employee.getErrorCount(); i++) {
              headerColumns.addErrorColumns(new ErrorColumns());
            }
            out.write(headerColumns);
            columnHeadersWritten = true;
          }
    
          out.write(record);
          System.out.println("Record Written:" + record.toString());
        }
    
        in.close();
        out.flush();
        out.close();
    }
    

    Now we have dynamic column headers based on the actual amount of errors that is read from the first Employee record.