CollectionsUtil

13 11 2010

Nova versão do projeto disponivel para download(ver pagina Towel Project), os tutoriais explicados aqui também estão na Wiki do projeto.

A classe CollectionsUtil tem alguns métodos novos que é algo que estou desenvolvendo para depois usar em outra parte do projeto, mas já é bem util.

CollectionsUtil.split(List l, String field)

Esse método retorna uma List com os valores do atributo ‘field’ que estão em cada objeto da lista ‘l’.

Por exemplo, usando o modelo Person

public class Person {
	private String name;
	private int age;
	public Person(String str, double d) {
		this.name = str;
		this.age = (int) d;
	}
}

Se tivermos uma Lista de Person e quisermos uma lista com todas as idades, é possivel fazer o seguinte.

List<Person> list = new ArrayList<Person>();
list.add(new Person("A", 10.0));
list.add(new Person("B", 20.0));
list.add(new Person("C", 30.0));
list.add(new Person("D", 40.0));
list.add(new Person("E", 50.0));

List<Double> ages = CollectionsUtil.split(list, "age");

E ‘ages’ terá os elementos [10.0,20.0,30.0,40.0,50.0].

Mas o mais interessante com isso, é a novo package collections.aggr.
Ela permite aplicar uma função sobre todos os elementos de uma lista e obter o resultado. Funções como, soma, média ou qualquer coisa de acordo com a implementação de AggregateFunction.

Para usa-la é simples:

List<Integer> list = new ArrayList<Integer>();
list.add(10);
list.add(20);
list.add(30);
list.add(40);
list.add(50);

Integer sum = CollectionsUtil.aggregate(new FuncSum(), list).intValue();//.intValue por que FuncSum retorna Number
Double avg = CollectionsUtil.aggregate(new FuncAvg(), list).doubleValue();
System.out.println("sum = " + sum);
System.out.println("avg = " + avg);

As classes FuncSum(Soma) e FuncAvg(Média) já estão implementadas no projeto e existe uma terceira, FuncConcat que concatena Strings.

Também é possivel aplicar sobre uma coleção de objetos diferentes do que a Func espera, para isso, usar o método.

CollectionsUtil.aggregate(List l, AggregateFunc func, String field)

Que o resultado será a função aplicada sobre todos os objetos resultantes de CollectionsUtil.split(l, field).

Sobre a AggregateFunction, para criar novas implementações de funções, criar uma implementação dessa classe, sua assinatura é:

package com.towel.collections.aggr;

/**
 * Functions to work over an object, the object is of the type T
 *
 * @author marcos.vasconcelos
 */
public interface AggregateFunc<T> {
	/**
	 * Init this Func with the initial values.
	 * This method is called when a new Calculation over a Collection is going to ve initiated.
	 * When implementing, reset all values to it initial value.
	 */
	public void init();
	/**
	 * Called over each value in a List.
	 * This method should calculate the new value with a previous value.
	 * @param obj
	 */
	public void update(T obj);
	/**
	 * Called when the iteration is over and the final value is done.
	 * @return The value of the function apllied over all objects passed in Func.update
	 */
	public T getResult();
}

O método init é chamado antes das iterações.
O método update é chamado com cada objeto que a função trabalha.
O método getResult é chamado depois das iterações, e deve retornar o resultado do processo.

Como exemplo, essa é a implementação para média.

package com.towel.collections.aggr;

public class FuncAvg implements AggregateFunc<Number>{
	private Number x;
	private int total;
	@Override
	public void update(Number obj) {
		x = new Double(x.doubleValue() + obj.doubleValue());
		total++;
	}

	@Override
	public Number getResult() {
		return x.doubleValue() / total;
	}

	@Override
	public void init() {
		x = new Double(0);
		total = 0;
	}

}

Se alguém criar uma AggregateFunction interessante e quiser coloca-la no projeto é só me enviar.





ObjectComboBoxModel

4 11 2010

Essa classe está no projeto faz tempo mas nunca falei sobre ela pois ainda existe um problema que não vejo solução, ComboBox podem ser editaveis, e com esse model, não.

Mas pra quem só deseja exibir pode ser uma grande mão na roda.

Ele funciona da seguinte maneira, igual ao ObjectTableModel ele é paremetrizado e é necessario especificar qual o tipo do Objeto que vamos exibir na lista.

Nesse exemplo meu model será Person.

package model;

@SuppressWarnings("unused")
public class Person {
	private String name;
	private int age;

	public Person(String str, double d) {
		this.name = str;
		this.age = (int) d;
	}

	public String toString() {
		return "Name: " + name + " age: " + age;
	}
}

Até ai, nada demais, só o modelo.

Para utilizar precisamos criar um Formatter para essa classe, e o meu é o seguinte.

package combobox;

	public class PersonFormatter implements Formatter{
		@Override
		public Object format(Object arg0) {
			Person p = (Person) arg0;
			if(p == null)//No combo box o primeiro item sempre é null, para poder ficar em branco
				return "";
			return p.toString();
		}

		@Override
		public String getName() {
			return "person";
		}

		@Override
		public Object parse(Object arg0) {
			return null;//Nunca sera invocado
		}
	}

Nesse caso, format recebe um objeto Person, e deve retornar uma String que seja o conteudo que deve ser exibido no JComboBox.

E aqui como utilizar:

package combobox;

import java.util.*;
import javax.swing.*l;

import com.towel.awt.ann.*;
import com.towel.bean.Formatter;
import com.towel.swing.ObjectComboBoxModel;
import model.Person;

public class ComboBoxExample extends JFrame {
	private ObjectComboBoxModel<Person> model;
	@Action(method="showPerson")
	private JButton button;

	public ComboBoxExample(){
		super("ComboBoxModel");

		model = new ObjectComboBoxModel<Person>();
		model.setFormatter(new PersonFormatter());
		model.add(new Person("A",10.0));//  Igual ao ObjectTableModel
		model.add(new Person("B",20.0));//os métodos addAll adicionam todos os elementos de uma coleção no model
		model.add(new Person("C",30.0));//e o método setData coloca a coleção para ser os dados do model
		model.add(new Person("D",40.0));
		model.add(new Person("E",50.0));

		JComboBox combo = new JComboBox(model);

		JPanel cont = new JPanel();
		cont.add(combo);
		cont.add(button = new JButton("Show"));

		setContentPane(cont);
		pack();
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setLocationRelativeTo(null);
		setVisible(true);

		new ActionManager(this);//Necessario para mapear @Action com o método
	}

	private void showPerson(){
		Person p = model.getSelectedObject();
		System.out.println(p.toString());
	}

	public static void main(String[] args) {
		new ComboBoxExample();
	}
}

Executando essa classe será exibida uma janela com um combo e um botao que quando pressionado mostrara na saida padrao o valor de Person.toString.

No exemplo também está com a anotação @Action, também do projeto, para entende-la ver este artigo.





Renderers Customizados. ObjectTableModel

29 08 2010

Fiz algumas atualizações no ObjectTableModel e está disponivel no site do projeto. 

A principal modificação é que não é mais necessario ficar criando formatters pra tudo, antes era necessario criar um pra int, pra float, pra double, pra boolean e etc.. 

Os tipos primitivos e Strings são suportados sem precisar nada mais do que “@Resolvable”. 

Outra modificação é que agora é possivel usar TableCellRenderers com os componentes, só é necessario atribuir o Renderer a JTable(Que outra hora mostro como fazer isso). 

Mas por enquanto, aqui vai um exemplo de como mostrar uma tabela que tenha um JCheckBox. 

O Modelo (Sem formatters customizados para tipos que não sejam Strings) 

public class Person {
 @Resolvable
 private String name;
 @Resolvable
 private int age;
 @Resolvable
 private boolean live;
 public Person(String name, int age, boolean live) {
  this.name = name;
  this.age = age;
  this.live = live;
 }
 public void printAttrs() {
  System.out.print("Name: " + name);
  System.out.print(" Age: " + age);
  System.out.println(" Live? " + live);
 }
} 

 E como colocar na tabela.

public class TableTeste {
 public static void main(String[] args) {
  AnnotationResolver resolver = new AnnotationResolver(Person.class);
  FieldResolver cols[] = resolver.resolve("name:Name,age:Age,live:Live");//Pega as colunas
  final ObjectTableModel<Person> model = new ObjectTableModel<Person>(cols);
  model.setEditableDefault(true);//Para poder modificar o valor
  //Coloca itens na tabela
  model.add(new Person("Marky", 19, true));
  model.add(new Person("Douglas", 20, true));
  model.add(new Person("Adams", 17, true));
  model.add(new Person("Vscs", 37, true));
  model.add(new Person("Pedro", 58, false));
  //Cria e mostra na tela os componentes
  JFrame frame = new JFrame("ObjectTableModel v2.7");
  frame.setContentPane(new JPanel(new BorderLayout()));
  JScrollPane pane = new JScrollPane();
  pane.setViewportView(new JTable(model));
  frame.add(pane, BorderLayout.CENTER);
  JButton show = new JButton("Show");
  show.addActionListener(new ActionListener() {
   public void actionPerformed(ActionEvent e) {
    for(Person p : model.getData())
     p.printAttrs();
   }
  });
  frame.add(show, BorderLayout.SOUTH);
  frame.setSize(400,400);
  frame.pack();
  frame.setLocationRelativeTo(null);
  frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  frame.setVisible(true);
 }
}

Rodando a main do segundo vemos uma tabela onde o terceiro campo é um JCheckBox, e o botão (“show”) mostra os atributos dos objetos das linhas.

Try it!





ActionManager, uma nova maneira de mapear Actions para seus formularios.

29 04 2010

Para usar as classes do projeto acesse a pagina Towel Project desse blog para baixar o projeto.

Bem.. essa idéia surgiu em uma conversa com o Felix (Felagund do GUJ) que também aderiu ao projeto.

Como adoro annotations, criei mais uma para isso, a annotation Action que tem dois atributos.

String method() default “” – Caso queira mapear um botão para simplesmente invocar um método quando pressionada só é necessario passar o nome dele nesse atributo

Class<? extends ActionListener> listener() default ActionListener.class – Caso voce queira que um ActionListener seja chamado voce precisa passar o .class da classe que implementa ele para esse atributo.

Aproveitei o código do artigo que fiz para o binder apenas modificando o modo que crio as ações para os botões.


import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
import com.towel.awt.ann.Action;
import com.towel.awt.ann.ActionManager;
import com.towel.bind.Binder;
import com.towel.bind.annotation.AnnotaddedBinder;
import com.towel.bind.annotation.Bindable;
import com.towel.bind.annotation.Form;
@Form(Pessoa.class)
public class PessoaForm extends JFrame {
 @Bindable(field = "nome")
 private JTextField nome;
 @Bindable(field = "idade", formatter = IntFormatter.class)
 private JTextField idade;
 @Bindable(field = "vivo")
 private JCheckBox vivo;
 private Binder binder;
 @Action(method = "add")//Mapeando a ação para o método add
 private JButton add;
 @Action(listener = Listener.class)//Mapeando a ação para o listener
 private JButton load;
 public PessoaForm() {
  super("PessoaForm");
  nome = new JTextField(20);
  idade = new JTextField(20);
  vivo = new JCheckBox("Vivo?");
  add = new JButton("Add");
  load = new JButton("Load");
  setLayout(new GridLayout(5, 2));
  add(new JLabel("Nome:"));
  add(nome);
  add(new JLabel("Idade:"));
  add(idade);
  add(new JLabel());// Por causa do GridLayout
  add(vivo);
  add(add);
  add(load);
  pack();
  setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  setLocationRelativeTo(null);
  binder = new AnnotaddedBinder(this);
  new ActionManager(this);// Mapeia as @Action para os JButtons.
 }
 public static void main(String[] args) {
  new PessoaForm().setVisible(true);
 }
 // Listener mapeado para o botão "load"
 private class Listener implements ActionListener {
  @Override
  public void actionPerformed(ActionEvent e) {
   Pessoa pessoa = new Pessoa();
   pessoa.setNome("Marky");
   pessoa.setIdade(18);
   pessoa.setVivo(true);// Claro
   binder.updateView(pessoa);
  }
 }
 // Metodo mapeado para o botão "add"
 private void add() {
  Pessoa pessoa = new Pessoa();
  binder.updateModel(pessoa);
  pessoa.printAttrs();
 }
 // IntFormatter sera usado para transformar a String em numero.
 public static class IntFormatter implements mark.utils.bean.Formatter {
  public Object format(Object obj) {
   Integer d = (Integer) obj;
   return d.toString();
  }
  public Object parse(Object obj) {
   return Integer.valueOf(Integer.parseInt((String) obj));
  }
  public String getName() {
   return "int";
  }
 }
}




Binder 2.0 – Agora com Annotations!

3 03 2010

Olá. Demorei um tempo mas agora o Binder esta totalmente reformulado.

Essa versão pode ser obtida baixando o jar pela pagina Towel Project.

Para usar da antiga forma apenas instancie um NamedBinder invés de Binder em si.

Mas agora o novo.

Para fazer o bind de um objeto para uma tela nós precisamos indicar que a classe sera um formulario e anotar os componentes com os dados do atributo que vamos fazer a união.

Por exemplo vou usar a classe Pessoa.

public class Pessoa {
	private String nome;
	private int idade;
	private boolean vivo;// sem ideias pra outro atributo boolean o.0
	public void printAttrs() {// Método que imprime no console os atributos de
		// pessoa
		System.out.println("Nome: " + getNome());
		System.out.println("Idade: " + getIdade());
		System.out.println("Vivo?: " + vivo);
	}
	public void setNome(String nome) {
		this.nome = nome;
	}
	public String getNome() {
		return nome;
	}
	public void setIdade(int idade) {
		this.idade = idade;
	}
	public int getIdade() {
		return idade;
	}
	public boolean isVivo() {
		return vivo;
	}
	public void setVivo(boolean vivo) {
		this.vivo = vivo;
	}
}

Usamos a anotação @Form para indicar qual classe vamos unir com este formulario e anotação @Bindable para indicar qual atributo da classe esse campo indica.

Parametros do @Bindable
String field: que indica o atributo da classe. (parametro obrigatorio)
Class handler: forma que sera acessado o campo (opcional, default é FieldHandler)
Class formatter: formatter para o campo, caso precise ser formatado ou transformado para outro tipo de atributo se não String (opcional, default é o DefaultFormatter)

Agora no formulario.

import java.awt.GridLayout;
import java.awt.event.*;
import javax.swing.*;
import com.towel.bean.Formatter;
import com.towel.bind.Binder;
import com.towel.bind.annotation.*;
@Form(Pessoa.class)
// Form para Pessoa
public class PessoaForm extends JFrame {
	@Bindable(field = "nome")
	private JTextField nome;
	@Bindable(field = "idade", formatter = IntFormatter.class)
	private JTextField idade;
	@Bindable(field = "vivo")
	// o Binder assume que o JCheckBox trata-se de um atributo boolean
	private JCheckBox vivo;
	private Binder binder;
	public PessoaForm() {
		super("PessoaForm");
		nome = new JTextField(20);
		idade = new JTextField(20);
		vivo = new JCheckBox("Vivo?");
		JButton add = new JButton("Add");
		JButton load = new JButton("Load");
		setLayout(new GridLayout(4, 2));
		add(new JLabel("Nome:"));
		add(nome);
		add(new JLabel("Idade:"));
		add(idade);
		add(new JLabel());// Por causa do GridLayout
		add(vivo);
		add(add);
		add(load);
		load.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent evt) {
				Pessoa pessoa = new Pessoa();
				pessoa.setNome("Marky");
				pessoa.setIdade(18);
				pessoa.setVivo(true);// Claro
				binder.updateView(pessoa);
			}
		});
		add.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent evt) {
				Pessoa pessoa = new Pessoa();
				binder.updateModel(pessoa);
				pessoa.printAttrs();
			}
		});
		pack();
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setLocationRelativeTo(null);
		binder = new AnnotatedBinder(this);
	}
	public static void main(String[] args) {
		new PessoaForm().setVisible(true);
	}
	// IntFormatter sera usado para transformar a String em numero.
	public static class IntFormatter implements Formatter {
		public Object format(Object obj) {
			Integer d = (Integer) obj;
			return d.toString();
		}
		public Object parse(Object obj) {
			return Integer.valueOf(Integer.parseInt((String) obj));
		}
		public String getName() {
			return "int";
		}
	}
}

Repare que rodando este código aparecera uma tela com estes campos e ao apertar o botão load as informações do objeto pessoa sera mostrado nos campos. E o método add coloca os valores dos campos no objeto e imprime o valor dos atributos.

Repare que não foi necessario varios sets para atualizar o objeto nem gets para atualizar a View.

Em breve vou unir um Validator no Binder para garantir a consistencia dos dados.

Até mais.





Mark Utils no Google Code!

1 03 2010

Olá, faz tempo que não posto mas a medida que tive idéias implementei algumas coisas novas e finalmente decedi seguir a dica de hospedar em um controlador de versão publica.

Agora o projeto esta hospedado em http://code.google.com/p/markutils/

E para acessar o código via svn:
http://markutils.googlecode.com/svn/trunk markutils-read-only

Quem quiser participar do projeto seja bem vindo.

Código atualizado e algumas coisas novas que faço um post quando tiver tempo.

Em breve eu crio a To-do list na wiki do projeto.

E se alguém souber uma maneira de fazer o upload das ideias da minha cabeça para o computador me avisem, ok? :P

Na sessão downlad no google code tem a versão 2.0 do mark-utils (decedi versionar agora o projeto ^^).

Em breve vou criar outro projeto no Google Code para hospedar o código de um joguinho que estou criando também.





Binder – Atualizando objetos facilmente.

29 07 2009

Algo comum em programação Swing é ter métodos para atualizar objetos e a view como o seguinte.

public void atualizarObjeto(Pessoa x){
x.setNome(nomeField.getText());
x.setCpf(cpfField.getText());
x.setIdade(Integer.parseInt(idadeField.getText()));
}

public void atualizarObjeto(Pessoa x){
nomeField.setText(x.getNome());
cpfField.setText(x.getCpf());
idadeField.setText(x.getIdade());
}

Nesse exemplo temos apenas 3 atributos mas ao adicionar mais um vemos a necessidade de colocar mais uma linha em cada um desses métodos. Com o tempo esse processo começa a ficar no minimo chato.
Pensando nisso criei a classe Binder.

PS: Para usar precisa baixar os fontes ou o jar na pagina Towel Project.

Considere para o exemplo a seguinte classe:

public class Person {
	private String name;
	private int age;
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public Person getParent() {
		return parent;
	}

	public void setParent(Person parent) {
		this.parent = parent;
	}
}

E um IntFormatter:

public class IntFormatter implements Formatter {
	@Override
	public String format(Object obj) {
		return Integer.toString((Integer) obj);
	}
	@Override
	public String getName() {
		return "int";
	}
	@Override
	public Object parse(String s) {
		return Integer.parseInt(s);
	}
}

“[name:nomeDoAtributo] [fmt:formatter][pfx:prefixo][dflt:valorDefault]”

Onde.
nomeDoAtributo: nome do atributo na classe.
formatter (Opcional): deve ser igual ao retornado pelo método getName do Formatter.
prefixo (Opcional): quando precisamos na mesma tela ter mais de um Binder(Para dois tipos de objetos diferentes). Esse nome deve ser passado no construtor do Binder.
valorDefault (Opcional): no caso em que o valor no objeto for null ou vazio esse valor é assumido para a view.

Todos os parametros são colocados no name dos JComponents.

Exemplo funcional.


import com.towel.bind.Binder;

public class BinderTest extends JFrame {
	private JTextField nomeField, idadeField;
	private JButton button;
	private Binder binder;

	public BinderTest() {
		super("Binder");
		nomeField = new JTextField(10);
		idadeField = new JTextField(10);
		button = new JButton("Run");

		JPanel content = new JPanel(new FlowLayout());
		content.add(new JLabel("Nome:"));
		content.add(nomeField);
		content.add(new JLabel("Idade:"));
		content.add(idadeField);
		content.add(button);

		nomeField.setName("[name:name]");
		idadeField.setName("[name:age][fmt:int]");

		setContentPane(content);

		binder = new Binder(this, Person.class, new IntFormatter());

		button.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				Person person = new Person();
				try {
					binder.updateModel(person);
				} catch (Exception e) {
					// Um formatter pode lançar uma RuntimeException.
				}
				System.out.println(person.getName());
				System.out.println(person.getAge());

				Person newOne = new Person();
				binder.updateView(newOne);
			}
		});

		pack();
		setLocationRelativeTo(null);
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setVisible(true);
	}

	public static void main(String[] args) {
		new BinderTest();
	}
}

Ao clicar no botão podemos ver o que digitamos no console pois após binder.updateModel temos os valores no objeto.
Após isso os campos ficam limpos pois é chamado binder.updateView com um novo Person e esses campos ainda estão vazios.

O construtor com pfx seria como o seguinte.

binder = new Binder("prefixoUsado", this, Person.class, new IntFormatter());

Mas teriamos que colocar esse atributo em cada componente que queiramos.

Binder usa um método recursivo para pegar os componentes, então não se preocupe em alinhar seus componentes em varios JPanels.ributo








Seguir

Obtenha todo post novo entregue na sua caixa de entrada.

Junte-se a 154 outros seguidores