CSVGenerator

De BIS Wiki
Revisão de 22h46min de 4 de setembro de 2017 por Rodrigogml (discussão | contribs) (CSVGenerator)
Ir para navegação Ir para pesquisar

O CSVGenerator tem o objeto de permitir a geração de arquivos CSV a partir da estrutura de dados utilizada pelo BIS. Aproveito a estrutura dos VOs e BISMetaObjects (VO_), isto é, aceita uma lista de VOs, e gera o arquivo CSV a partir das colunas selecionadas de dentro desses objetos.

Para exemplificar o funcionamento de todas as partes do CSVGenerator vamos imaginar que queremos exportar como CSV nosso cadastro de pessoas, lista de PersonVO.

CSVGeneratorOptions

O CSVGeneratorOptions carrega praticamente toda a configuração de como o arquivo deve ser gerado. Desta forma é possível realizar toda a definição em qualquer cama (UI por exemplo) e passar para o gerador de uma única vez. Permite que seja criado um componente único para manipular o objeto para as UIs e reaproveitado em todas as telas, útil principalmente quando o gerador ganha novas opções.

Entre as configurações básicas do arquivo estão:

  • Delimitador - utilizado para separar os campos do arquivo
  • Localidade - Define a localidade para formatação básica dos valores, como caractere de decimais, data, etc.
  • Títulos das Colunas - Define se deve ser escrito uma linha no começo com os títulos das colunas.
  • Valor de Nulo - Valor que deve ser utilizado caso o objeto tenha um valor nulo. Por padrão não é escrito nada, o equivalente á "", mas pode ser necessário distinguir o "" do null.

CSVGenerator

Todos os atributos de configuração já tem um valor padrão, de forma que para gerar um CSV, basta definir as colunas desejadas e passar a coleção de dados no método .generate().


Exemplos de Uso
    // Define as colunas no options
    options.setColumns(new String[]{
        PersonVO_.VO.displayname(),
        PersonVO_.VO.fullname(), 
        PersonVO_.VO.cpfcnpj()
    });

    // Construtor vazio cria um CSVGeneratorOptions com a opções padrão. Mas depois tem que pegar o options para colocar as colunas ou terá um CSV vazio.
    final CSVGenerator<PersonVO> gen = new CSVGenerator<PersonVO>(options);

    // Retorna o CSV com as colunas selecionadas.
    return gen.generate(personList);


Colunas Repetidas
Note que o nome da coluna é o identificador da coluna, por isso o CSVGenerator não aceita colunas com o mesmo nome, ou, no caso, colunas repetidas. Para inserir a mesma coluna duas vezes, (há vários casos para isso explicados mais a seguir) é necessário incluir uma coluna "personalizada". Também explicada mais a seguir.


O arquivo foi gerado, mas muitas vezes não é o que desejamos. Agora para formatar, configurar e manipular os dados é que precisará de configurações adicionais.


Formatando os Dados

A formatação dos dados é feita de forma simples e de acordo com o locale passado. São formatados os números e datas apenas. De resto os campos são escritos utilizando o equivalente ao .toString() do objeto, e apenas dos objetos conhecidos pelo CSVGenerator, como String, BigDecimal, Enum, Float, Double, etc.. Outros formatos geram exception crítica para forçar um tratamento mais adequado do desenvolvedor.

O CSVGenerator permite a configuração das colunas utilizando a interface BISDataFormatter. Para isso defina a instância do BISDataFormatter desejado na coluna através do CSVGeneratorOptions:

Exemplos de Uso
    // inclui o formatador de cpf/cnpj na coluna com o valor do documento
    options.getColumnFormatter().put(PersonVO_.VO.cpfcnpj(), BISCPFOrCNPJDataFormatter.getInstance());


Formatação é o Último Passo
Note que a formação só é chamada no momento de escrever o objeto no arquivo CSV. Durante todo o processamento do arquivo e opções mais discutidas a seguir não recebem o dado formatado, mas sim no "Object" de acordo com o tipo de dado que foi recebido.


Processamento do Valor da Coluna

Em alguns casos desejamos realizar o processamento do valor da coluna, antes de passar pelo formatador, como por exemplo, caso o valor de uma determinada coluna seja truncado, ou seja realizado um arredondamento específico, etc.

Para estes casos devemos incluir um DataProcess, que nos fornecerá as informações necessárias para processarmos o valor conforme desejado.


Exemplos de Uso
    options.getColumnDataProcess().put(PersonVO_.VO.displayname(), new CSVColumnDataProcess() {
      @Override
      public Object getColumnValue(BISVO vo, String columnName, HashMap<String, Object> iterableCache) throws BISException {
        String t = (String) BISUtilsReflex.getPropertyValue(vo, columnName);
        // Trunca se tiver mais de 50 caracteres
        if (t != null && t.length() > 50) t = t.substring(0, 50);
        return t;
      }

      @Override
      public String getColumnTitle(String columnName) {
        // Retorna nulo para que seja recuperado normalmente pelo caption do BISAnnotation
        return null;
      }
    });


ColumnDataProcess sobrepõe o funcionamento do CSVGenerator
Quando um ColumnDataProcess é definido para uma determinada coluna, o CSVGenerator não tenta busca o ID da coluna como se fosse um Path para pesquisar no VO. Ele passa direto para o ColumnDataProcess se virar com as informações.

Esse funcionamento é esperado, para evitar buscar valores que não existem, ou colunas personalizadas, como será explicado mais adiante.


Colunas Personalizada

Nem sempre a informação que desejamos escrever no CSV está dentro do VO. Como por exemplo, se queremos escrever uma coluna de "Total", sendo o resultado do cálculo envolvendo outros valores que estão no VO, mas este valor não está no VO diretamente. Ou qualquer outra coluna com qualquer outro valor, até mesmo escrever o System.currentTimeMillis() para verificar a velocidade de geração dos dados. Qualquer que seja a informação a ser escrita, você deve usar o mesmo ColumnDataProcess explicado acima e retornar qualquer valor desejado.

Como o ColumnDataProcess é colocado sempre 'por cima' de uma coluna existente, podemos realizar de duas maneiras: incluir uma coluna qualquer que não esteja sendo utilizada (só recomendável se o valor manipulado for de alguma maneira uma variação do valor original), ou criar uma coluna com um id customizado (mais recomendado), como por exemplo:


Exemplos de Uso
    // Define as colunas no options
    String columnTotal = "Total";
    options.setColumns(new String[]{
        ...,
        columnTotal,
        ..., 
        ...
    });

    ...

    options.getColumnDataProcess().put(columnTotal, new CSVColumnDataProcess() {
        ...
    });


Como as colunas com ColumnDataProcess não tentam resolver o valor no VO utilizando o ID da coluna, não resultará em erro.


ID Personalizado sem ColumnDataProcess
Note que criar uma coluna com um ID qualquer (que não seja um path de atributos do VO) e não incluir o ColumnDataProcess fará justamente com que o CSVGenerator tente obter o valor através de reflexão no objeto, resultando em Exceptions!


Agrupando Valores

Em alguns momentos temos os dados todos detalhados, como estão na tabela, mas queremos apresentar os dados de forma agrupada, similar a funcionalidade do 'group by' utilizado no SQL. Para esses casos o CSVGenerator tem uma funcionalidade que permite que os registros sejam agrupados de acordo com campos chaves. Por exemplo, um relatório de vendas, temos todas as vendas de itens detalhada uma a uma, mas queremos exportar um arquivo com apenas 1 linha para cada código para cada empresa. Neste caso devemos definir a coluna de código como chave do agrupamento:


Exemplos de Uso
    options.setGroupByColumns(new String[]{
        companyColumnID,
        codeColumnID
    });


Desta forma, toda vez que o CSVGenerator encontrar um novo objeto com o mesmo valor na coluna companyColumnID e na codeColumnID, ela será "ignorada" já que já temos essa linha.

Funções de Agrupamento

O agrupamento de valores, conforme descrito acima, funciona bem se o objetivo for ter um funcionamento similar ao "Distinct" do SQL. Mas normalmente o agrupamento dos valores faz mais sentido quando desejamos "Sumarizar" os valores encontrados nas linhas repetidas. Se o caso for esse, devemos definir que função desejamos fazer com os outros valores um à um. Se for mesmo o caso de ir somando os valores a medida que as colunas chaves se repetem, devemos utilizar a função SUM. Definindo desta forma:


Exemplos de Uso
    // Faz com que os valores da coluna sejam todos somados a medida que as colunas chaves de repetem
    options.getGroupByFunctions().put(sumColumnID, CSVGroupByFunction.SUM);


Deste modo teremos apenas 1 linha com cada valor distinto das colunas definidas como chave, e os valores da coluna 'sumColumnID' serão somados.

O CSVGenerator também suporta outras funções como 'MIN' (Valor Mínimo), 'MAX (Valor Máximo), etc. Consulte o JavaDoc dos elementos da enum CSVGroupByFunction para mais detalhes sobre as funções.