A API do Xisemele permite que sejam configurados formatadores para tratar a conversão de tipos de valores de elementos e atributos. É possível configurar um formatador tanto para tipos personalizados como para tipos pré-definidos da implementação de Xisemele.
Configurar um formatador implica em atribuir uma implementação de Formatter para determinado tipo (Class) a uma instância de Xisemele. A interface Formatter é definida abaixo:
public interface Formatter<T> extends Serializable { Class<T> type(); String format(T value); T parse(String text); }
O método Formatter.type() deve retornar o tipo (Class) para qual a implementação de Formatter está configurada. O método Formatter.format(T) deve retornar uma String correspondente à conversão do valor especificado por parâmetro para o tipo T. O inverso deve ser feito pelo método Formatter.parse(String) que deve retornar uma instância do tipo T correspondente à conversão da String especificada por parâmetro.
Veja a seguir como formatadores podem ser configurados na prática.
Considere o tipo personalizado Endereco implementado abaixo:
public class Endereco { private String rua; private int numero; private String bairro; private long cep; public Endereco(String rua, int numero, String bairro, long cep) { this.rua = rua; this.numero = numero; this.bairro = bairro; this.cep = cep; } @Override public String toString() { return "Endereco[rua: " + rua + ", numero: " + numero + ", bairro: " + bairro + ", cep: " + cep + "]"; } // métodos getters e setters }
Essa classe é apenas uma abstração de endereço para exemplificar o uso de formatadores para tipos personalizados. Ela contém os atributos rua, numero, bairro e cep. O método Endereco.toString() foi implementado para retornar uma String com a descrição do endereço. Os métodos getters e setters dos atributos foram omitidos apenas para simplificar o exemplo.
Agora, veja um exemplo de como um Formatter pode ser configurado para esse tipo.
import net.sf.xisemele.api.Formatter; import net.sf.xisemele.api.Reader; import net.sf.xisemele.api.Writer; import net.sf.xisemele.api.Xisemele; import net.sf.xisemele.impl.XisemeleFactory;
Formatter<Endereco> enderecoFormatter = new Formatter<Endereco>() { // (1) public Class<Endereco> type() { return Endereco.class; } public String format(Endereco endereco) { return endereco.getRua() + "," + endereco.getNumero() + "," + endereco.getBairro() + "," + endereco.getCep(); } public Endereco parse(String text) { String[] split = text.split(","); String rua = split[0]; int numero = Integer.parseInt(split[1]); String bairro = split[2]; long cep = Long.parseLong(split[3]); return new Endereco(rua, numero, bairro, cep); } }; Xisemele xisemele = XisemeleFactory.newXisemele(); xisemele.setFormatter(enderecoFormatter); // (2) Writer writer = xisemele.createWriter("raiz") // (3) .within() .element("elementoA").attribute("endereco", new Endereco("Rua qualquer", 123, "Centro", 87123456)) .element("elementoB").within() .element("endereco", new Endereco("Rua fulano", 456, "Bairro X", 87654321)) .endWithin() .endWithin(); String xml = writer.result().ident(true).toXML(); System.out.println( xml ); Reader reader = xisemele.createReader( xml ); Endereco enderecoA = reader.find("raiz/elementoA").attribute("endereco").asType(Endereco.class); // (4) System.out.println("Endereço elementoA"); System.out.println( enderecoA ); System.out.println(); Endereco enderecoB = reader.find("raiz/elementoB/endereco").value().asType(Endereco.class); // (5) System.out.println("Endereço elementoB"); System.out.println( enderecoB );
Na declaração (1) foi implementada a interface Formatter para o tipo Endereco. Em (2) a instância de Xisemele é configurada com esse formatador através do método Xisemele.setFormatter(Formatter). Feito isso, a instância de Xisemele é usada para escrever um XML do zero usando uma instância de Writer obtida a partir da instância de Xisemele configurada, como pode ser observado em (3). Na sequência, o mesmo XML é lido usando uma instância de Reader, obtida através da mesma instância de Xisemele, e a instância de Endereco é recuperada dos elementos elementoA e elementoB convertida pelo método Value.asType(Class), como pode ser observado em (4) e (5), respectivamente.
O método Value.asType(Class) verifica se há um formatador configurado para o tipo especificado por parâmetro para efetuar a conversão. Se não houver um formatador configurado e o tipo (Class) especificado por parâmetro não for um dos tipos pré-definidos, o método irá disparar uma exceção.
Note que no elementoA a instância de Endereco foi atribuída como valor do atributo endereco, e no elementoB outra instância desse mesmo tipo foi atribuída como valor do elemento filho endereco.
O trecho de código acima imprime o seguinte resultado no console:
<?xml version="1.0" encoding="UTF-8"?> <raiz> <elementoA endereco="Rua qualquer,123,Centro,87123456"/> <elementoB> <endereco>Rua fulano,456,Bairro X,87654321</endereco> </elementoB> </raiz> Endereço elementoA Endereco[rua: Rua qualquer, numero: 123, bairro: Centro, cep: 87123456] Endereço elementoB Endereco[rua: Rua fulano, numero: 456, bairro: Bairro X, cep: 87654321]
A API do Xisemele permite sobrescrever a conversão para tipos pré-definidos. A conversão default destes tipos é feita no estilo Tipo.toString() e Tipo.valueOf(String), exceto para o tipo java.util.Date, como será visto mais abaixo.
Por exemplo, para o tipo Boolean a conversão para String resulta em "true/false". Para que o valor seja convertido para "sim/não", um formatador pode ser configurado da seguinte forma:
import net.sf.xisemele.api.Formatter; import net.sf.xisemele.api.Reader; import net.sf.xisemele.api.Writer; import net.sf.xisemele.api.Xisemele; import net.sf.xisemele.impl.XisemeleFactory;
Formatter<Boolean> booleanFormatter = new Formatter<Boolean>() { public Class<Boolean> type() { return Boolean.class; } public String format(Boolean value) { return value ? "sim" : "não"; } public Boolean parse(String text) { return text.equals("sim") ? Boolean.TRUE : Boolean.FALSE; } }; Xisemele xisemele = XisemeleFactory.newXisemele().setFormatter( booleanFormatter ); Writer writer = xisemele.createWriter("raiz") .within() .element("elementoA", Boolean.TRUE) .element("elementoB", Boolean.FALSE) .endWithin(); String xml = writer.result().ident(true).toXML(); System.out.println( xml ); Reader reader = xisemele.createReader( xml ); System.out.println("ElementoA: " + reader.find("raiz/elementoA").value().asBoolean() ); System.out.println("ElementoB: " + reader.find("raiz/elementoB").value().asBoolean() );
O trecho de código acima imprime no console o seguinte resultado:
<?xml version="1.0" encoding="UTF-8"?> <raiz> <elementoA>sim</elementoA> <elementoB>não</elementoB> </raiz> ElementoA: true ElementoB: false
Um formatador pode ser configurado para o tipo java.util.Date da mesma forma que foi demonstrado acima. Porém, o Xisemele já tem um formatador implementado para esse tipo. Em vez de implementar uma instância de Formatter você pode, se quiser, definir apenas o formato que será usado para conversão de datas, como o exemplo abaixo:
import java.util.Date; import net.sf.xisemele.api.Reader; import net.sf.xisemele.api.Writer; import net.sf.xisemele.api.Xisemele; import net.sf.xisemele.impl.XisemeleFactory;
Xisemele xisemele = XisemeleFactory.newXisemele() .setDatePattern("dd/MM/yyyy kk:mm:ss"); // (1) Writer writer = xisemele.createWriter("raiz") .within() .element("data", new Date()) .endWithin(); String xml = writer.result().ident(true).toXML(); System.out.println( xml ); Reader reader = xisemele.createReader( xml ); Date data = reader.find("raiz/data").value().asDate(); System.out.println("Data: " + data);
O método Xisemele.setDatePattern(String) define o padrão que será usado para formatação de datas. No caso da chamada (1) a formatação de datas foi configurada para o padrão "dd/MM/yyyy kk:mm:ss".
O trecho de código acima imprime o seguinte resultado no console:
<?xml version="1.0" encoding="UTF-8"?> <raiz> <data>03/08/2009 12:28:23</data> </raiz> Data: Mon Aug 03 12:28:23 BRT 2009