Trabalhando com XML

"Se eu enxerguei longe, foi por ter subido nos ombros de gigantes."

A explicacao sobre XML é bem rapida. Aqui esta mais como um guia basico de XML, mas na aula nao compensa ficar falando de caracteres de escape ou ficar detalhando algoritmos de parsing de XML.

Comente a sintaxe hierarquica, declarativa. Suas caracteristicas de interoperabilidade, de ser um padrao. Comente que ha varias formas de se ler um XML, por exemplo carregar tudo pra memoria e montar uma arvore (DOM) ou ir lendo tag a tag e processar cada acontecimento de abertura/fechamento de tag (SAX / StAX).

Aí mostra um XML da bolsa com negocios e fala de APIs para le-los. Comenta que há varias opcoes (JAX-*, uns Commons, XStream). Vamos usar XStream por ser muito facil e suportar recursos que o JAX-* nao suporta (binding com classe imutavel).

Mostra o toXML, fala do alias, mostra o fromXML.

Os dados da bolsa de valores

Como vamos puxar os dados da bolsa de valores para popular nossos candles?

Existem inúmeros formatos para se trabalhar com diversas bolsas. Sem dúvida XML é um formato comumente encontrado em diversas indústrias, inclusive na bolsa de valores.

Utilizaremos esse tal de XML. Para isso, precisamos conhecê-lo mais a fundo, seus objetivos, e como manipulá-lo. Considere que vamos consumir um arquivo XML como o que segue:

<list>
	<negocio>
		<preco>43.5</preco>
		<quantidade>1000</quantidade>
		<data>
			<time>555454646</time>
		</data>
	</negocio>
	<negocio>
		<preco>44.1</preco>
		<quantidade>700</quantidade>
		<data>
			<time>555454646</time>
		</data>
	</negocio>
	<negocio>
		<preco>42.3</preco>
		<quantidade>1200</quantidade>
		<data>
			<time>559329406</time>
		</data>
	</negocio>
</list>

Uma lista de negócios! Cada negócio informa o preço, quantidade e uma data. Essa data é composta por um horário dado no formato de Timestamp, e opcionalmente um Timezone.

XML

Começa falando sobre XML, o que é, como usa, flexibilidade, onde e para que vem sendo usado: comunicação, configuração,...

copiado do http://www.w3schools.com/xml/xml_whatis.asp

XML (eXtensible Markup Language) é uma formalização da W3C para gerar linguagens de marcação que podem se adaptar a quase qualquer tipo de necessidade. Algo bem extensível, flexível, de fácil leitura e hierarquização. Sua definição formal pode ser encontrada em:

http://www.w3.org/XML/

Exemplo de dados que são armazenados em XMLs e que não conhecemos tão bem, é o formato aberto de gráficos vetoriais, o SVG (usado pelo Corel Draw, Firefox, Inkscape, etc), e o Open Document Format (ODF), formato usado pelo OpenOffice, e hoje em dia um padrão ISO de extrema importância. (na verdade o ODF é um ZIP que contém XMLs internamente).

A ideia era criar uma linguagem de marcação que fosse muito fácil de ser lida e gerada por softwares, e pudesse ser integrada as outras linguagens. Entre seus princípios básicos, definidos pelo W3C:

O XML é uma excelente opção para documentos que precisam ter seus dados organizados com uma certa hierarquia (uma árvore), com relação de pai-filho entre seus elementos. Esse tipo de arquivo é dificilmente organizado com CSVs ou properties. Como a própria imagem do wikipedia nos trás e mostra o uso estruturado e encadeado de maneira hierárquica do XML:

O cabeçalho opcional de todo XML é o que se segue:

<?xml version="1.0" encoding="ISO-8859-1"?>

Esses caracteres não devem ser usados como elemento, e devem ser "escapados":

Você pode, em Java, utilizar a classe String e as regex do pacote java.util.regex para criar um programa que lê um arquivo XML. Isso é uma grande perda de tempo, visto que o Java, assim como quase toda e qualquer linguagem existente, possui uma ou mais formas de ler um XML. O Java possui diversas, vamos ver algumas delas, suas vantagens e suas desvantagens.

Lendo XML com Java de maneira difícil, o SAX

O SAX (Simple API for XML) é uma API para ler dados em XML, também conhecido como Parser de XML. Um parser serve para analisar uma estrutura de dados e geralmente o que fazemos é transformá-la em uma outra. Mais texto aqui pra encher linguiça.

Neste processo de análise também podemos ler o arquivo XML para procurar algum determinado elemento e manipular seu conteúdo.

O parser lê os dados XML como um fluxo ou uma sequência de dados. Baseado no conteúdo lido, o parser vai disparando eventos. É o mesmo que dizer que o parser SAX funciona orientado a eventos.

Existem vários tipos de eventos, por exemplo:

Para tratar estes eventos, o programador deve passar um objeto listener ao parser que será notificado automaticamente pelo parser quando um desses eventos ocorrer. Comumente, este objeto é chamado de Handler, Observer, ou Listener e é quem faz o trabalho necessário de processamento do XML.

public class NegociacaoHandler extends DefaultHandler {

	@Override
	public void startDocument() throws SAXException {

	}

	@Override
	public void startElement(String uri, String localName, 
				String name, Attributes attributes) throws SAXException {
		// aqui voce é avisado, por exemplo
		// do inicio da tag "<preco>"
	}

	@Override
	public void characters(char[] chars, int offset, int len) throws SAXException {
		// aqui voce seria avisado do inicio
		// do conteudo que fica entre as tags, como por exemplo 30
		// de dentro de "<preco>30</preco>"

		// para saber o que fazer com esses dados, voce precisa antes ter
		// guardado em algum atributo qual era a negociação que estava
		// sendo percorrida
	}

	@Override
	public void endElement(String uri, String localName, String name) throws SAXException {
		// aviso de fechamento de tag
	}
}

A classe DefaultHandler permite que você reescreva métodos que vão te notificar sobre quando um elemento (tag) está sendo aberto, quando está sendo fechado, quando caracteres estão sendo parseados (conteúdo de uma tag), etc.. Você é o responsável por saber em que posição do object model (árvore) está, e que atitude deve ser tomada. A interface ContentHandler define mais alguns outros métodos.

Coloque esse codigo na lousa, apesar de que nao vamos usar. Mostre pra eles como isso ai ficaria complicado, cheio de ifs e Strings. Alguem logo dara a ideia que voce deveria ai colocar um "controller" para disparar "commands".

Discuta o design pattern Observer/Listener. Ele sera revisto no capitulo de Swing Basico.

Nota do Sergio: eu nao coloco esse codigo na lousa e ainda passo batido nele :) Nota do Paulo: é, tem de passar bem rapido aqui. Mas é importante que eles saibam algumas siglas como Sax, Dom, etc.

Curiosidade sobre o SAX

Originalmente o SAX foi escrito só para Java e vem de um projeto da comunidade (http://www.saxproject.org), mas existem outras implementações em C++, Perl e Python.

O SAX está atualmente na versão 2 e faz parte do JAX-P (Java API for XML Processing).

O SAX somente sabe ler dados e nunca modificá-los e só consegue ler para frente, nunca para trás. Quando passou um determinado pedaço do XML que já foi lido, não há mais como voltar. O parser SAX não guarda a estrutura do documento XML na memória.

Também não há como fazer acesso aleatório aos itens do documento XML, somente sequencial.

Por outro lado, como os dados vão sendo analisados e transformados (pelo Handler) na hora da leitura, o SAX ocupa pouca memória, principalmente porque nunca vai conhecer o documento inteiro e sim somente um pequeno pedaço. Devido também a leitura sequencial, ele é muito rápido comparado com os parsers que analisam a árvore do documento XML completo.

Quando for necessário ler um documento em partes ou só determinado pedaço e apenas uma vez, o SAX parser é uma excelente opção.

StAX - Streaming API for XML

StAX é um projeto que foi desenvolvido pela empresa BEA e padronizado pela JSR-173. Ele é mais novo do que o SAX e foi criado para facilitar o trabalho com XML. StAX faz parte do Java SE 6 e JAX-P.

Como o SAX, o StAX também lê os dados de maneira sequencial. A diferença entre os dois é a forma como é notificada a ocorrência de um evento.

No Sax temos que registrar um Handler. É o SAX que avisa o Handler e chama os métodos dele. Ele empurra os dados para o Handler e por isso ele é um parser do tipo PUSH, .

O StAX, ao contrário, não precisa deste Handler. Podemos usar a API do StAX para chamar seus métodos, quando e onde é preciso. O cliente decide, e não o parser. É ele quem pega/tira os dados do StAX e por isso é um parser do tipo PULL.

O site http://www.xmlpull.org fornece mais informações sobre a técnica de Pull Parsing, que tem sido considerada por muitos como a forma mais eficiente de processar documentos xml.

A biblioteca XPP3 é a implementação em Java mais conhecida deste conceito. É usada por outras bibliotecas de processamento de xml, como o CodeHaus XStream.

XStream

O XStream é uma alternativa perfeita para os casos de uso de XML em persistência, transmissão de dados e configuração. Sua facilidade de uso e performance elevada são os seus principais atrativos.

É um projeto hospedado na Codehaus, um repositório de código open source focado em Java, que foi formado por desenvolvedores de famosos projetos como o XDoclet, PicoContainer e Maven. O grupo é patrocinado por empresas como a ThoughWorks, BEA e Atlassian. Entre os seis desenvolvedores do projeto, Guilherme Silveira da Caelum está também presente.

http://xstream.codehaus.org

Diversos projetos opensource, como o container de inversão de controle NanoContainer, o framework de redes neurais Joone, dentre outros, passaram a usar XStream depois de experiências com outras bibliotecas. O XStream é conhecido pela sua extrema facilidade de uso. Repare que raramente precisaremos fazer configurações ou mapeamentos, como é extremamente comum nas outras bibliotecas mesmo para os casos mais básicos.

Como gerar o XML de uma negociação? Primeiramente devemos ter uma referência ao bean. Podemos simplesmente criar um e populá-lo, ou este pode ser, por exemplo, uma entidade do Hibernate.

Com a referência negocio em mãos, basta agora pedirmos ao XStream que gera o XML correspondente:

	Negocio negocio = new Negocio(42.3, 100, Calendar.getInstance());

	XStream stream = new XStream(new DomDriver());
	System.out.println(stream.toXML(negocio));

E o resultado é:

<br.com.caelum.argentum.Negocio>
  <preco>42.3</preco>
  <quantidade>100</quantidade>
  <data>
    <time>1220009639873</time>
    <timezone>America/Sao_Paulo</timezone>
  </data>
</br.com.caelum.argentum.Negocio>

A classe XStream atua como façade de acesso para os principais recursos da biblioteca. O construtor da classe XStream recebe como argumento um Driver, que é a engine que vai gerar/consumir o XML. Aqui você pode definir se quer usar SAX, DOM, DOM4J dentre outros, e com isso o XStream vai ser mais rápido, mais lento, usar mais ou menos memória, etc.

O default do XStream é usar um driver chamado XPP3, desenvolvido na universidade de Indiana e conhecido por ser extremamente rápido (leia mais no box de pull parsers). Para usá-lo você precisa de um outro JAR no classpath do seu projeto.

O método toXML retorna uma String. Isso pode gastar muita memória no caso de você serializar uma lista grande de objetos. Ainda existe um overload do toXML, que além de um Object recebe um OutputStream como argumento para você poder gravar diretamente num arquivo, socket, etc.

Diferentemente de outros parsers do Java, o XStream serializa por default os objetos através de seus atributos (sejam privados ou não), e não através de getters e setters.

Repare que o XStream gerou a tag raiz com o nome de br.com.caelum.argentum.Negocio. Isso porque não existe um conversor para ela, então ele usa o próprio nome da classe e gera o XML recursivamente para cada atributo não transiente daquela classe.

Porém, muitas vezes temos um esquema de XML já muito bem definido, ou simplesmente não queremos gerar um XML com cara de java. Para isso podemos utilizar um alias. Vamos modificar nosso código que gera o XML:

	XStream stream = new XStream(new DomDriver());
	stream.alias("negocio", Negocio.class);

Essa configuração também pode ser feita através da anotação @XStramAlias("negocio") em cima da classe Negocio.

Podemos agora fazer o processo inverso. Dado um XML que representa um bean da nossa classe Negocio, queremos popular esse bean. O código é novamente extremamente simples:

	XStream stream = new XStream(new DomDriver());
	stream.alias("negocio", Negocio.class);
	Negocio n = (Negocio) stream.fromXML("<negocio>" +
						"<preco>42.3</preco>" +
						"<quantidade>100</quantidade>" +
					     "</negocio>");
	System.out.println(negocio.getPreco());

Obviamente não teremos um XML dentro de um código Java. O exemplo aqui é meramente ilustrativo (útil em um teste!). Os atributos não existentes ficarão como null no objeto, como é o caso aqui do atributo data, ausente no XML.

O XStream possui um overload do método fromXML que recebe um InputStream como argumento, outro que recebe um Reader.

JAXB ou XStream?

A vantagem do JAXB é ser uma especificação da Sun, e a do XStream é ser mais flexível e deixar trabalhar com classes imutáveis.

@XstreamAlias

Ao invés de chamar stream.alias("negocio", Negocio.class);, podemos fazer essa configuração direto na classe Negocio com uma anotação:

@XstreamAlias("negocio")
public class Negocio {
}

Para habilitar o suporte a anotações, precisamos chamar no xstream:

	stream.autodetectAnnotations(true);

Ou então, se precisarmos processar as anotações de apenas uma única classe, basta indicá-la, como abaixo:

	stream.processAnnotations(Negocio.class);
Pra trabalhar com classes imutaveis, o XStream faz um bypass dos construtores atraves de recursos da JVM em questao, usando magicas... Cada VM possui metodos internos que fazem newinstance sem passar pelo construtor (o framework objenesis (googlecode) faz isso, usando esses recursos).

Exercícios: Lendo o XML

texto

	
	

Para usarmos o XStream, precisamos copiar seus JARs para o nosso projeto e adicioná-los no Build Path. Para facilitar, vamos criar uma pasta lib para colocar todos os arquivos JAR que necessitarmos.

Clique com o botão direito no nome do projeto e vá em New > Folder:

Coloque o nome de lib e clique OK:

Vamos instalar o XStream no nosso projeto. Vá na pasta Caelum no seu Desktop e entre em 16. Localize o arquivo do xstream:

Esse é o mesmo arquivo que é baixado do site do XStream.

Copie esse arquivo para o seu Desktop. Dê dois cliques para abrir o descompactador do Linux. Na próxima tela, clique em Extract:

Com o próprio Desktop selecionado, clique Extract:

Entre na pasta xstream-1.3/lib no seu Desktop. Há vários JARs do XStream lá, vamos precisar apenas do xstream-1.3.jar. O restante são dependências que não precisamos. Copie esse JAR para dentro da sua pasta lib do projeto.

Depois, pelo Eclipse, entre na pasta lib, de refresh nela, selecione o jar, clique com o botão direito e vá em Build Path, Add to build path. A partir de agora o Eclipse considerará as classes do XStream para esse nosso projeto.

Vamos agora, finalmente, implementar a leitura do XML, delegando o trabalho para o XStream. Criamos a classe LeitorXML dentro do pacote br.com.caelum.argentum.reader:

package br.com.caelum.argentum.reader;

// imports...

public class LeitorXML {

	public List<Negocio> carrega(Reader fonte) {
		XStream stream = new XStream(new DomDriver());
		stream.alias("negocio", Negocio.class);
		return (List<Negocio>) stream.fromXML(fonte);
	}

}	

Crie um teste unitário LeitorXMLTest pelo Eclipse para testarmos a leitura do XML. Basta clicar com botão direito na classe LeitorXML e selecionar New > JUnit Test Case:

Lembre-se de colocá-lo no diretório src/test/java.

Para não ter de criar um arquivo XML no sistema de arquivos, podemos usar um truque interessante: coloque o trecho do XML em uma String Java, e passe um StringReader:

	@Test
	public void testLeitorDeXmlCarregaListaDeNegocio() {
		String xmlDeTeste = "..."; // o XML vai aqui!
		
		LeitorXML leitor = new LeitorXML();
		List<Negocio> negocios = leitor.carrega(new StringReader(xmlDeTeste));
	}

Use o seguinte XML de teste, substituindo as reticências no código acima pelo texto abaixo:

	<list>
		<negocio>
			<preco>43.5</preco>
			<quantidade>1000</quantidade>
			<data>
				<time>555454646</time>
			</data>
		</negocio>
	</list>

Faça algumas asserções:

Opcionalmente crie mais de um teste se possível, testando casos excepcionais, como XML inválido, zero negócios, e dados faltando.

(importante, conceitual) E o que falta agora? Testar nosso código com um real XML?

É muito comum sentirmos a vontade de fazer um teste "maior": um teste que realmente abre um FileReader, passa o XML para o LeitorXML e depois chama a CandlestickFactory para quebrar os negócios em candles.

Esse teste seria quase um teste de integração, e não de unidade. Se criássemos esse teste, e ele falhasse, ficaria muito difícil de detectar o ponto de falha! Mais ainda: pode parecer difícil de perceber, mas já que testamos todas as peças (units) do nosso motor (aplicação), é muito provável que ele funcione quando rodado! Só falta testar as "ligas", que normalmente são bem leves e poucas (o tal do baixo acoplamento).

Pensar em sempre testar as menores unidades possíveis força que a gente sempre crie programas com baixo acoplamento: poderíamos ter criado um LeitorXML que devolvesse diretamente uma List, já chamando a fábrica. Mas isso faria com que o nosso teste do leitor de XML testasse muito mais que apenas a leitura de XML (pois estaria passando pela nossa CandlestickFactory).

As vezes nao da pra evitar e os nossos testes de unidade testam mais que a classe X. o ideal é minimizar isso

(opcional) Faça mais testes de unidade de outros casos com o LeitorXML. Algumas ideias:

Novo Capitulo

Outro Capitulo