Problemas com API XStream FJ-22

Olá pessoal.

Estou fazendo os exercícios opcionais do capítulo 4.5 da apostila FJ-22 da Caelum. A leitura de dados de um XML, para popular a classe Negociacao do projeto do mercado de valores (“Argentum”).

Acontece que apesar de validar no construtor os valores obrigatórios, ao passar um XML incompleto, é criada uma negociação com valores null, gerando NPE, quando deveria na verdade pular o objeto ou acusar durante a leitura do XML.

Seguem as implementações da Negociação e do LeitorXML, bem como o teste JUnit da Negociação incompleta e um teste com log para mostrar a situação.

Suspeito que o XStream está usando Reflection para converter e atualizar os valores das variáveis da Negociação como último recurso, e isso está causando o problema supracitado.

Negociacao.java

package br.com.caelum.argentum.modelo;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Calendar;

public final class Negociacao {

    private final BigDecimal preco;
    private final int quantidade;
    private final Calendar data;

    @Deprecated
    public Negociacao(double preco, int quantidade, Calendar data) {
        this(BigDecimal.valueOf(preco),quantidade,data);
    }

    public Negociacao(BigDecimal preco, int quantidade, Calendar data) {
        if (data == null)
            throw new IllegalArgumentException("data nao pode ser nula");
        if (preco == null)
            throw new IllegalArgumentException("preco nao pode ser nulo");
        else if (preco.compareTo(BigDecimal.ZERO) != 1)
            throw new IllegalArgumentException("valor deve ser maior que 0");
        if (quantidade <= 0)
            throw new IllegalArgumentException("quantidade deve ser maior que 0");

        this.preco = preco;
        this.quantidade = quantidade;
        this.data = (Calendar) data.clone();
    }

    public BigDecimal getPreco() {
        return preco;
    }

    public int getQuantidade() {
        return quantidade;
    }

    public Calendar getData() {
        return (Calendar) data.clone();
    }

    public BigDecimal getVolume() {
        return preco.multiply(BigDecimal.valueOf(quantidade));
    }

    @Override
    public String toString() {
        return "Data: " + new SimpleDateFormat("dd/MM/yyyy").format(data.getTime()) + "\n"
            +   "Quantidade: " + quantidade + "\n"
            +   "Preço: " + preco + "\n"
            +   "Volume: " + getVolume() + "\n";
    }

}

LeitorXML.java

package br.com.caelum.argentum.reader;

import java.io.InputStream;
import java.util.List;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

import br.com.caelum.argentum.modelo.Negociacao;

public class LeitorXML {
    public List<Negociacao> carrega(InputStream inputStream) {
        XStream stream = new XStream(new DomDriver());
        stream.autodetectAnnotations(true);
        stream.alias("negociacao", Negociacao.class);

        @SuppressWarnings("unchecked")
        List<Negociacao> negocios = (List<Negociacao>) stream.fromXML(inputStream);

        return negocios;
    }
}

LeitorXMLTest.java

package br.com.caelum.argentum.reader;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.List;

import org.junit.Test;

import br.com.caelum.argentum.modelo.Negociacao;

public class LeitorXMLTest {
    /**
     * Stub para exibir a Negociacao criada com null
     * @param args
     */
    public static void main (String[] args) {
        String xmlDeTeste =
            "<list>" +
                "<negociacao>" +
                    //"<preco>15.35</preco>" +
                    "<quantidade>1000</quantidade>" +
                    "<data>" +
                        "<time>1322233344455</time>" +
                    "</data>" +
                "</negociacao>" +
            "</list>";

        InputStream xml = new ByteArrayInputStream(xmlDeTeste.getBytes());
        List<Negociacao> list = new LeitorXML().carrega(xml);
        if (!list.isEmpty()) {
            System.out.println("Lista não vazia");

            Negociacao n = list.get(0);
            System.out.println("Preço: " + n.getPreco());
            System.out.println("Quantidade: " + n.getQuantidade());
            System.out.println("Tempo (ms): " + n.getData().getTimeInMillis());
        }
    }

    @Test
    public void carregaXmlComUmaNegociacaoEmListaUnitaria() {
        String xmlDeTeste = "<list>"+
                                "<negociacao>" +
                                    "<preco>43.5</preco>" +
                                    "<quantidade>1000</quantidade>" +
                                    "<data>" +
                                        "<time>1322233344455</time>" +
                                    "</data>" +
                                "</negociacao>" +
                            "</list>";



        InputStream xml = new ByteArrayInputStream(xmlDeTeste.getBytes());
        List<Negociacao> negocios = new LeitorXML().carrega(xml);

        assertEquals(1,negocios.size());
        assertEquals(43.5,negocios.get(0).getPreco().doubleValue(),0.00001);
        assertEquals(1000, negocios.get(0).getQuantidade());
    }

    @Test
    public void carregaXmlComNenhumaNegociacao() {
        String xmlDeTeste = "<list></list>";

        InputStream xml = new ByteArrayInputStream(xmlDeTeste.getBytes());
        List<Negociacao> negocios = new LeitorXML().carrega(xml);

        assertTrue("lista deve estar vazia",negocios.isEmpty());
    }

    @Test
    public void carregaXmlComNegociacaoSemPreco() {
        String xmlDeTeste = "<list>" +
                                "<negociacao>" +
                                    "<quantidade>1000</quantidade>" +
                                    "<data>" +
                                        "<time>1322233344455</time>" +
                                    "</data>" +
                                "</negociacao>" +
                            "</list>";

        InputStream xml = new ByteArrayInputStream(xmlDeTeste.getBytes());
        List<Negociacao> list = new LeitorXML().carrega(xml);
        assertEquals(0, list.size());
    }

    @Test
    public void carregaXmlComNegociacaoSemQuantidade() {
        String xmlDeTeste = "<list>"+
                                "<negociacao>" +
                                    "<preco>43.5</preco>" +
                                    "<data>" +
                                        "<time>1322233344455</time>" +
                                    "</data>" +
                                "</negociacao>" +
                            "</list>";

        InputStream xml = new ByteArrayInputStream(xmlDeTeste.getBytes());
        List<Negociacao> list = new LeitorXML().carrega(xml);
        assertEquals(0, list.size());
    }

    @Test
    public void carregaXmlComVariasNegociacoesEmLista() {
        String xmlDeTeste = "<list>"+
                                "<negociacao>" +
                                    "<preco>43.5</preco>" +
                                    "<quantidade>1000</quantidade>" +
                                    "<data>" +
                                        "<time>1322233344455</time>" +
                                    "</data>" +
                                "</negociacao>" +
                                "<negociacao>" +
                                    "<preco>43.5</preco>" +
                                    "<quantidade>1000</quantidade>" +
                                    "<data>" +
                                        "<time>1322233344455</time>" +
                                    "</data>" +
                                "</negociacao>" +
                                "<negociacao>" +
                                    "<preco>43.5</preco>" +
                                    "<quantidade>1000</quantidade>" +
                                    "<data>" +
                                        "<time>1322233344455</time>" +
                                    "</data>" +
                                "</negociacao>" +
                            "</list>";

        InputStream xml = new ByteArrayInputStream(xmlDeTeste.getBytes());
        List<Negociacao> negocios = new LeitorXML().carrega(xml);

        assertEquals(3,negocios.size());
        assertEquals(43.5,negocios.get(0).getPreco().doubleValue(),0.00001);
        assertEquals(1000, negocios.get(0).getQuantidade());

    }
}

Boa tarde Wilson,

Estou exatamente nesse mesmo capítulo e enfrento o mesmo problema.

Minhas classes estão idênticas as suas, com exceção da tipagem dos valores.

Você conseguiu resolver o problema?

Pro teste passar, eu utilizei o parâmetro “IllegalArgumentException” e desta maneira funcionou, porém não sei se é a melhor maneira de resolver o problema.

Saudações.

Oi Renato, negativo, continuo com esse problema, mas por não ter tido tempo para dedicar a resolvê-lo.

Embora, acredito que se implementarmos a nossa versão do Converter e especificamos para o XStream que, para a classe Negociação, ele deve ser utilizado, poderemos tratar o caso de objetos sem campos, lançando uma exceção ou pulando para a próxima entrada.

Mas como disse, não tive tempo para pesquisar mais a fundo.