MyAbstractTableModelDinamicForever

março 18, 2010

Quando precisamos utilizar JTables, somos obrigado a fornecer um modelo de dados para ela. Isso se dá usando a classe DefaultTableModel ou criando uma subclasse de AbstractTableModel.

O problemas é que muitas vezes, queremos apenas mostrar alguns dados e poder alterá-los.

Utilizando DefaultTableModel temos que fornecer um vetor de dados e outro de colunas. Com os dados no vetor, a manipulação fica meio complicado por diversos fatores como aumentar sua capacidade.

Outra possibilidade é fornecer um Vector com os dados e outro com as colunas. O Vector é uma classe sincronizada, o que significa perda de performance sendo que muitas vezes não precisaremos de uma estrutura de dados sincronizada.

Criando nossa própria subclasse de AbstractTableModel pode ser um pouco trabalhoso pois poderemos ter diversos TableModels para apenas mostrar dados.

A solução que encontrei foi implementar a classe AbstractTableModel para que receba uma List generica e através de Reflection, acesse os campos tanto para mostrar nas células quanto para atualização.

Para criar a classe de Reflection utilizei o pacote org.apache.commons.beanutils que pode ser baixado em http://commons.apache.org/beanutils/download_beanutils.cgi

Utilizando a classe PropertyUtils, temos os métodos setSimpleValue(target, fieldName, data) que atualiza uma propriedade e o método getSimpleValue(target, fieldName), para acessar uma propriedade, desde que a mesma esteja encapsulada (get() set()).

Então, a classe TableModelDinamic ficará assim:

import java.util.List;
import javax.swing.table.AbstractTableModel;
import orquestralibrary.util.Reflection;

public class TableModelDinamic extends AbstractTableModel {

    protected List list;

    protected String[] columnNames;

    public TableModelDinamic() {
        super();
    }

    public TableModelDinamic(List list) {
        setList(list);
    }

    public TableModelDinamic(List list, String [] cols)
    {
        setList(list);
        columnNames = cols;
    }
   
    public int getRowCount() {
        return list.size();
    }

    public int getColumnCount() {

        if(columnNames == null)
            return list.get(0).getClass()
                    .getDeclaredFields().length;

        return columnNames.length;
    }

    public Object getValueAt(int row, int column) {

        return Reflection.getFieldValue(list.get(row), getFieldName(column));
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        Reflection.setFieldValue(list.get(rowIndex), getFieldName(columnIndex), aValue);
    }
    private String getFieldName(int idx)
    {
        if(columnNames == null)
            return list.get(0).getClass()
                    .getDeclaredFields()[idx].getName();

        return columnNames[idx];
    }

    @Override
    public String getColumnName(int column) {
        return getFieldName(column);
    }

    public List getList() {
        return list;
    }

    public void setList(List _list) {
        list = _list;
        fireTableDataChanged();
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return true;
    }
}

O construtor do nosso model recebe uma lista como argumento no construtor ou uma lista mais os nomes das colunas que serão mostradas na JTable.

Se voce quiser trocar a lista, basta usar o método setList do model.

Para utilizar esta classe, basta apenas instanciar o model passando a sua lista.

import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author marciliosouza
 */
public class Frame extends javax.swing.JFrame {

    /** Creates new form Frame */
    public Frame() {
        super("Simple TableModel");
        initComponents();
    }

    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">
    private void initComponents() {

        jButton1 = new javax.swing.JButton();
        jScrollPane1 = new javax.swing.JScrollPane();
        jTable1 = new javax.swing.JTable();
        jButton2 = new javax.swing.JButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        jButton1.setText("Proximo");
        jButton1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButton1ActionPerformed(evt);
            }
        });

        jTable1.setModel(getModel());
        jScrollPane1.setViewportView(jTable1);

        jButton2.setText("Imprimir");
        jButton2.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButton2ActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                    .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 375, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(jButton2)
                        .addGap(77, 77, 77)
                        .addComponent(jButton1)))
                .addGap(21, 21, 21))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 275, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(jButton1)
                    .addComponent(jButton2))
                .addContainerGap())
        );

        pack();
    }// </editor-fold>

    private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {

        ((TableModelDinamic)jTable1.getModel()).setList(getNextList());
    }

    private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {

        for(Object p : ((TableModelDinamic)jTable1.getModel())
                .getList())
        {
            System.out.println("Nome .: "+((Pessoa)p).getNome() +" Profissao .: "+ ((Pessoa)p).getProfissao() + " Idade .: "+((Pessoa)p).getIdade());
        }
        System.out.println();
    }

    List<List<Pessoa>> lists;
    int idx = 0;
    public List<Pessoa> getNextList()
    {

        if(lists == null)
        {
            List<Pessoa> list1 = new ArrayList<Pessoa>();

            list1.add(new Pessoa("Jose", 33, "Arquiteto"));
            list1.add(new Pessoa("Maria", 29, "Medica"));

            List<Pessoa> list2 = new ArrayList<Pessoa>();

            list2.add(new Pessoa("Marcilio", 22, "Programdor"));
            list2.add(new Pessoa("Kelly", 22, "Psicologa"));

            List<Pessoa> list3 = new ArrayList<Pessoa>();

            list3.add(new Pessoa("Alexandre", 24, "Programdor"));
            list3.add(new Pessoa("Natalia", 22, "Empresaria"));

            List<Pessoa> list4 = new ArrayList<Pessoa>();

            list4.add(new Pessoa("Jalim", 24, ""));

            lists = new ArrayList<List<Pessoa>>();
            lists.add(list1);
            lists.add(list2);
            lists.add(list3);
            lists.add(list4);
        }

        if(idx <= lists.size()-1)
            return lists.get(idx++);

        idx = 0;
        return lists.get(idx);

    }
    /**
    * @param args the command line arguments
    */
    public static void main(String args[]) {
        new Frame().setVisible(true);
    }

    // Variables declaration - do not modify
    private javax.swing.JButton jButton1;
    private javax.swing.JButton jButton2;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JTable jTable1;
    // End of variables declaration

    public TableModelDinamic getModel()
    {
        return new TableModelDinamic(getNextList());
    }
}

Em alguns casos, mesmo utilizando nosso TableModelDinamic, pode ser preciso criar um model especifico. Um exemplo disso é prcisarmos informar em uma coluna uma “quantidade”, em outra o “preco” e queremos que em uma terceira coluna, seja mostrado um subtotal.

Neste caso podemos estender TableModelDinamic e fazer estes cálculos no método setValue.

List.toEnumarable()

março 17, 2010
 As estruturas de dados fornecidos pelo java, se usadas da forma correta são muito boas. Juntando com o poder dos generics e outras malandragens provenientes de nossa imaginação fértil, podemos trabalhar com listas de forma muito flexível e dinâmica.

Quando implementei meu tcc (um sistema desktop para uma empresa de Confecções) não sei por que, vi a necessidade de criar um Framework para me ajudar nesta árdua tarefa e uma das funcionalidades era justamente uma classe para manipular listas de uma forma que achei bem bacana.

Neste sistema, utilizei o JPA. Optei por não usar o Hibernate por não conhecer e também porque queria mesmo ter trabalho.

Utilizando o Netbeans e o JPA, temos a opção de criar “entidades do banco de dados”. Isto é muito útil pois será gerado inteiramente de “grátis” , as classes com todos os atributos da tabela e seus relacionamentos. Daí pra frente, é só separar os homens dos meninos e fazer as coisas segundo os mandamentos de implementação de equals() e hashCode().

Criei uma classe que herda de List, chamada hummm…List. Assim, dentro das minhas classes de dados, eu troco a declaração do pacote List do java, apontando para o meu pacote.

Utilizando o engine do JPA, temos muito a ganhar. Como exemplo, temos as querys que são geradas automaticamente, chamadas de namedquerys, que são pré-compiladas quando o contexto é iniciado, nos dando aumento de performance quando executadas. Também, é mantido um contexto onde ficam guardadas as refências  de objetos que já estão gerenciados (vieram do banco e estão em memória) diminuindo a quantidade de acessos ao banco, dependendo de como está a arquitetura do seu sistema.

Depois de trazidos do banco para a memória, uso um tipo de Comparator para coisas como executar um where, ordenação da lista por campos especificos de cada objeto, tirar uma media de valores ou saber quem é maior ou menor.

Abaixo, coloco as classes de dados.


<code>//imports *
import orquestralist.list.List;</code>

/**
*
* @author marciliosouza
*/
@Entity
@Table(name = "cliente")
@NamedQueries({
@NamedQuery(name = "Cliente.findAll", query = "SELECT c FROM Cliente c"),
@NamedQuery(name = "Cliente.findByIdcliente", query = "SELECT c FROM Cliente c WHERE c.idcliente = :idcliente"),
@NamedQuery(name = "Cliente.findByCnpjcpf", query = "SELECT c FROM Cliente c WHERE c.cnpjcpf = :cnpjcpf"),
@NamedQuery(name = "Cliente.findByIerg", query = "SELECT c FROM Cliente c WHERE c.ierg = :ierg"),
@NamedQuery(name = "Cliente.findByDatacad", query = "SELECT c FROM Cliente c WHERE c.datacad = :datacad"),
@NamedQuery(name = "Cliente.findByLimite", query = "SELECT c FROM Cliente c WHERE c.limite = :limite")})
public class Cliente implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "idcliente")
private Integer idcliente;
@Basic(optional = false)
@Column(name = "cnpjcpf")
private String cnpjcpf;
@Basic(optional = false)
@Column(name = "ierg")
private String ierg;
@Basic(optional = false)
@Column(name = "datacad")
@Temporal(TemporalType.TIMESTAMP)
private Date datacad;
@Basic(optional = false)
@Column(name = "limite")
private long limite;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "cliente")
private List vendaList;

public Cliente() {
}

//gets sets
}

//imports...

/**
*
* @author marciliosouza
*/
@Entity
@Table(name = "produto")
@NamedQueries({
@NamedQuery(name = "Produto.findAll", query = "SELECT p FROM Produto p"),
@NamedQuery(name = "Produto.findByIdproduto", query = "SELECT p FROM Produto p WHERE p.idproduto = :idproduto"),
@NamedQuery(name = "Produto.findByDescricao", query = "SELECT p FROM Produto p WHERE p.descricao = :descricao")})
public class Produto implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "idproduto")
private Integer idproduto;
@Basic(optional = false)
@Column(name = "descricao")
private String descricao;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "produto")
private List vendaitemList;

public Produto() {
}
}

//imports

/**
*
* @author marciliosouza
*/
@Entity
@Table(name = "vendaitem")
@NamedQueries({
@NamedQuery(name = "Vendaitem.findAll", query = "SELECT v FROM Vendaitem v"),
@NamedQuery(name = "Vendaitem.findByIdvendaitem", query = "SELECT v FROM Vendaitem v WHERE v.idvendaitem = :idvendaitem"),
@NamedQuery(name = "Vendaitem.findByQtde", query = "SELECT v FROM Vendaitem v WHERE v.qtde = :qtde"),
@NamedQuery(name = "Vendaitem.findByPrecovenda", query = "SELECT v FROM Vendaitem v WHERE v.precovenda = :precovenda")})
public class Vendaitem implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "idvendaitem")
private Integer idvendaitem;
@Basic(optional = false)
@Column(name = "qtde")
private int qtde;
@Basic(optional = false)
@Column(name = "precovenda")
private BigDecimal precovenda;
@JoinColumn(name = "idvendaitem", referencedColumnName = "idvenda", insertable = false, updatable = false)
@OneToOne(optional = false)
private Venda venda;
@JoinColumn(name = "produto", referencedColumnName = "idproduto")
@ManyToOne(optional = false)
private Produto produto;

public Vendaitem() {
}

//gets e sets

}

//imports
import orquestralist.list.List;

/**
*
* @author marciliosouza
*/
@Entity
@Table(name = "venda")
@NamedQueries({
@NamedQuery(name = "Venda.findAll", query = "SELECT v FROM Venda v"),
@NamedQuery(name = "Venda.findByIdvenda", query = "SELECT v FROM Venda v WHERE v.idvenda = :idvenda"),
@NamedQuery(name = "Venda.findByData", query = "SELECT v FROM Venda v WHERE v.data = :data"),
@NamedQuery(name = "Venda.findByVlrtotal", query = "SELECT v FROM Venda v WHERE v.vlrtotal = :vlrtotal")})
public class Venda implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "idvenda")
private Integer idvenda;
@Basic(optional = false)
@Column(name = "data")
@Temporal(TemporalType.TIMESTAMP)
private Date data;
@Basic(optional = false)
@Column(name = "vlrtotal")
private BigDecimal vlrtotal;
@JoinColumn(name = "cliente", referencedColumnName = "idcliente")
@ManyToOne(optional = false)
private Cliente cliente;
@OneToOne(cascade = CascadeType.ALL, mappedBy = "venda")
private List vendaitem;

public Venda() {
}

}

Agora, alguns usos Lista:

public List<Venda> example(Cliente cli)
    {
        return cli.getVendaList().where(new ICompare<Venda>() {

            public boolean compare(Venda t) {
                return t.getVlrtotal().compareTo(BigDecimal.valueOf(500)) > 0;
            }
        });
    }


</pre>
cli.getVendaList().get(0)
                .getVendaitem().where(new ICompare<Vendaitem>() {            public boolean compare(Vendaitem t) {
                return t.getProduto().getDescricao().equals("Produto1");
            }
        });

[sourcecode]

[sourcecode language="java"]
cli.getVendaList().distinct();
[sourcecode]

Obtém o total dos itens do Pedido

[sourcecode language="java"]

cli.getVendaList().get(0).getVendaitem()
                .sum(new IGetValue<Vendaitem>() {

            public Number getValue(Vendaitem t) {
                return t.getPrecovenda().multiply(BigDecimal.valueOf(t.getQtde()));
            }
        });

Analisando a classe Cliente, vemos que ela tem um List<Venda>.
Na mesma classe, trocamos a declaralção do pacote para
orquestra.list, e utilizamos a classe List "with" aditivos.
Estes metodos utilitários (where por exemplo) sempre retorna uma lista
tipada mas nunca altera a lista Original.
Para o metodo where, implementamos o metodo compare e o mesmo será
executado para todos os objetos da lista, retornando uma nova lista
com os objetos que retornaram true no metodo compare().
Há outros usos, como tirar uma média como o exemplo abaixo. A interface
IGetValue<Venda> retornará o campo que será usado para o cálculo.

Outro ponto importante aqui é o uso de Generics que nos permite
especificar diretamente os campos que queremos sem precisarmos de
conversão, usar strings (que é o caso de muitos Frameworks) ou
Annotations.
Retorna a media de valores de vendas para o cliente.

    public Number mediaVlrVenda(Cliente cli)
    {
        return cli.getVendaList().avg(new IGetValue<Venda>() {
            public Number getValue(Venda t) {
                return t.getVlrtotal();
            }
        });
    }

Retorna a venda com maior valor.

    public Number mediaVlrVenda(Cliente cli)
    {
        return cli.getVendaList().max(new Comparator<Venda>() {

            public int compare(Venda o1, Venda o2) {
                return o1.getVlrtotal().compareTo(o2.getVlrtotal());
            }
        });
}

Executa um metodo em cada objeto da Lista de itens que esta dentro da

Lista de Vendas e depois a ordena por data de emissão:


        list.forEach(new Action<Venda>() {

            public void action(Venda t) {

                t.getVendaitem().forEach(new Action<Vendaitem>() {
                    public void action(Vendaitem t) {
                        t.setPrecovenda(BigDecimal.ZERO);
                    }
                });
            }
        }).orderBy(new Comparator<Venda>() {

            public int compare(Venda o1, Venda o2) {
                return o1.getData().compareTo(o2.getData());
            }
        });

Resumindo, o que pretendo com este post, é tentar mostrar o quanto
listas podem ser extensíveis. Aqui, mostrei poucos exemplos, mas podemos
implementar diversos outros metódos dentro da List para facilitar sua
manipulação.

Hello world!

março 16, 2010

“Tudo no mundo começou com um sim. Uma molécula disse sim a outra molécula e nasceu a vida. Mas antes da pré-história havia a pré-história da pré-história e havia o nunca e havia o sim. Sempre houve. Não sei o que, mas sei que o universo jamais começou.” C.L


Seguir

Obtenha todo post novo entregue na sua caixa de entrada.