Saudações a todos…
Estou querendo ler um arquivo PDF de dentro de um jar. Porém, na minha implementação recebo uma mensagem do S.O. dizendo que o arquivo pode estar corrompido e a leitura não pôde ser executada. Segue o código que estou executando:
importjava.awt.Desktop;importjava.io.File;importjava.io.FileOutputStream;importjava.io.IOException;importjava.io.InputStream;importjava.io.OutputStream;publicclassPdfReader{publicstaticvoidmain(String[]args){try{/* Buscando os bytes do arquivo dentro do .jar */InputStreaminputStream=ClassLoader.getSystemClassLoader().getResourceAsStream("arquivo_1.pdf");byte[]content=newbyte[inputStream.available()];inputStream.read(content);inputStream.close();/* Criando o arquivo temporário */FiletempFile=File.createTempFile("arquivo_1",".pdf");tempFile.deleteOnExit();/* Escrevendo os bytes encontrados no arquivo do .jar */OutputStreamoutputStream=newFileOutputStream(tempFile);outputStream.write(content);outputStream.flush();outputStream.close();/* Delegando ao S.O. abrir o arquivo */Desktop.getDesktop().open(tempFile);}catch(IOExceptione){e.printStackTrace();}}}
O curioso é que se eu rodar a aplicação diretamente do ecplise (sem a criação do .jar), tudo funciona corretamente. Já tentei remover a compressão dos arquivos na criação do arquivo JAR, porém o erro persiste. Alguém tem alguma idéia de como resolver isso?
Eu tenho uma encrenca particular com o método “available” do InputStream porque sei que ele normalmente não é implementado do jeito que a gente pensa, e sim de outra maneira que não é bem o que a gente quer. Ele só funciona “mais ou menos” para arquivos (não JARs), e olhe lá - se o arquivo tiver mais de 2GB, ele não vai retornar um número coerente.
Se você olhasse qualquer programa meu, viria que não uso isso de jeito nenhum.
Eu nunca, no seu caso, iria carregar o arquivo inteiro PDF dentro da memória e a seguir gravá-lo inteiro em disco. Isso é muito pesado. Um PDF de 20 Mega (já vi isso por aí) iria arrebentar com seu programa.
Normalmente você sempre trabalha com algo parecido com:
“available” não quer dizer “o tamanho do arquivo”.
Ele diz: “a quantidade de bytes que estão disponíveis para serem lidos com um único read”. Por exemplo, o “available” de um SocketInputStream normalmente retorna um valor pequeno, que é o que está disponível no buffer do socket. E o “available” do InputStream retornado por esse getResourceAsStream nem sei o que retorna. Acho que é o tamanho do buffer do ZLIB, e olhe lá.
K
kaique
Bom, com relação ao tamanho do arquivo, o método available() está retornando a quantidade correta de bytes do arquivo (conferi isso no S.O.). Então, acho que todos os bytes do arquivo estão sendo escritos no arquivo temporário. Continuo sem entender porque recebo uma mensagem avisando que o arquivo pode estar corrompido.
entanglement, interessante a sua sugestão. No caso, o benefício disso seria evitar uma enorme quantidade de bytes em memória de uma só vez, correto? Ficarei alerta para esse tipo de situação no futuro, mas acho que essa não seria a solução para o problema.
[]'s.
E
entanglement
Ah, entendi o que ocorreu. É que o “deleteOnExit”, nesse seu programa teste em particular, está sendo executado antes ou durante o Adobe Reader conseguir ler o arquivo - mas para provar isso, use algum programa que compare o arquivo temporário que foi gerado sei lá onde, com o arquivo original que você guardou dentro do JAR.
No Eclipse talvez o deleteOnExit não esteja sendo executado (esse “deleteOnExit” só funciona quando dá na telha, na verdade. Outra coisa que não confio nem um pouco no Java.)
K
kaique
Fala entanglement, blz?
Acho que o problema não é o método deleteOnExit(), pois de acordo com a documentação, esse método somente “marca” o arquivo para ser excluído quando a execução da JVM finalizar. Como o método open da classe Desktop não gera um bloqueio na execução do programa, coloquei um Thread.sleep(5000) (5 segundos) no fim da execução do programa, e ainda assim recebo a mensagem de que o arquivo pode estar corrompido. Por fim, para sanar essa dúvida, removi essa chamada do código e o problema continua aparecendo. Que coisa ein?
[]'s.
E
entanglement
Só para acabar de verificar, veja (separadamente, não com o Adobe Reader) se o arquivo criado no diretório temporário bate realmente (em termos de bytes - pode usar algum utilitário como o fc.exe do Windows para fazer a comparação) com o arquivo original.
K
kaique
Alterei novamente o código para que o arquivo não fosse apagado no fim da execução do programa e verifiquei com o utilitário do próprio S.O. para ver os detalhes do arquivo. Daí, deu para ver que os dois arquivos possuem a mesma quantidade de bytes. =[
[]'s.
R
romarcio
Não leva mal a pergunta, mas você precisa ler o conteúdo do PDF na sua aplicação ou precisa apenas abrir o PDF para o usuário visualiza-lo?
E
entanglement
kaique:
Alterei novamente o código para que o arquivo não fosse apagado no fim da execução do programa e verifiquei com o utilitário do próprio S.O. para ver os detalhes do arquivo. Daí, deu para ver que os dois arquivos possuem a mesma quantidade de bytes. =[
[]'s.
Mesma quantidade é uma coisa. Que os arquivos sejam iguais é outra coisa. Para comparar binariamente 2 arquivos:
no Windows - abra um Command Prompt (tela do DOS), e digite:
fc /b nome_do_primeiro_arquivo nome_do_segundo_arquivo
Se forem iguais, deve mostrar algo como:
Comparing files blable.pdf and bloblu.pdf
FC: no differences encountered
Se forem diferentes, deve mostrar quais são os bytes diferentes.
Acho que o problema ai é o método “avaliable”, na documentação diz que o valor retornado é um n° estimado de bytes, então não da para confiar que será exatamente o mesmo n° de bytes.
Eu modifiquei e funcionou aqui pra mim:
importjava.awt.Desktop;importjava.io.*;publicclassPdfReader{publicstaticvoidmain(String[]args){try{Filefile=newFile("TutorialCompilacao.pdf");InputStreaminputStream=newFileInputStream(file);byte[]content=newbyte[(int)file.length()];inputStream.read(content);/* Criando o arquivo temporário */FiletempFile=File.createTempFile("TutorialCompilacao",".pdf");/* Escrevendo os bytes encontrados no arquivo do .jar */OutputStreamoutputStream=newFileOutputStream(tempFile);outputStream.write(content);outputStream.flush();outputStream.close();/* Delegando ao S.O. abrir o arquivo */Desktop.getDesktop().open(tempFile);}catch(IOExceptione){e.printStackTrace();}}}
Tenta assim.
K
kaique
Opa, tudo bem?
romarcio, acho que nesse caso a bronca não é com o método available, pois eu confiro a quantidade de bytes retornada por ele e realmente é a mesma quantidade de bytes do arquivo original. Em outro post também tinham me alertado sobre isso… =]
Outra coisa, acho que o seu código não funcionaria caso o pdf que você leu esteja dentro de um JAR. Essa criação de arquivo que você utilizou não funciona nesse cenário, sacou?
entanglement, agora sim você me convenceu. Realmente os arquivos não são iguais. Rodei aqui o utilitário que você me indicou e a saída foi bem clara:
O que me deixa sem entender é como isso pode ser possível. Será então que nada me garante que as especificações de InputStream leiam um arquivo na sequência correta de bytes? Ou então que as especificações de OutputStream escrevam uma sequência de bytes na ordem que recebam?
[]'s.
E
entanglement
Qual é o tamanho do arquivo .pdf
Que tal você copiar o arquivo do jeito que lhe falei (usando um array de bytes pequeno e um while)? Isso é mais rápido e eficiente que você imagina. Provavelmente você está tentando ler tudo em um array de bytes em memória porque acha que é mais rápido, não? Mas como você está descompactando um jar isso na verdade nem faz muita diferença em termos de desempenho.