Mostrar mensagens com a etiqueta java. Mostrar todas as mensagens
Mostrar mensagens com a etiqueta java. Mostrar todas as mensagens

quarta-feira, dezembro 20, 2006

Observadores em Java

É recorrente, para os programadores, terem que conceber programas em que algumas partes estão dependentes do estado ou dos dados que algum componente detem. Por exemplo, ter um programa de chat no qual, cada mensagem recebida pela rede tem que se apresentada na interface ao mesmo tempo que é guardada num histórico da aplicação.

Uma forma elegante de resolver este problema é de recorrer a uma "software pattern" denominado Observer. Neste pattern, muitas vezes também chamado publish/subscribe, os objectos observadores registam-se junto dos que pretendem observar à espera de um evento.

Em Java, a API suporta explicitamente este pattern disponibilizando as classes Observer e Observable e vou aqui dar um exemplo muito ingénuo de como utilizá-las.
Aproveitando a ideia do client de chat, vou aqui apresentar o código esqueleto para a aplicação deste pattern.
Para começar, temos que ter o objecto que vai ser alvo de observação, em Java este objecto tem que estender a classe java.util.Observable:

import java.util.Observable;

public class
MsgObservable extends Observable implements Runnable
{
private String msg;

public void
run()
{
while(true)
{
msg = waitForMsg();

// marca o objecto como alterado
setChanged();

// avisa os observadores da mudança
notifyObservers( msg );
}
}

private String
waitForMsg() throws Exception
{
// recebe as mensagens
}
}
Neste excerto de código, os pontos importantes são as funções setChanged() e notifyObservers(...) que avisam os observadores de um evento.

Visto que se faz sentido ter algo para observar se estiver alguém a observar, temos que criar um observador, por exemplo, a classe para fazer o logging:

import java.util.Observable;
import java.util.Observer;

public class
LogWriter implements Observer
{
public void
update(Observable obj, Object arg)
{
logMessage(arg);
}

private void
LogMessage(Object arg) {
// TODO
}
}
A parte importante de este excerto de código é a função update( Observable obj, Object arg ) que é a função que é executada quando o observador é notificado de um evento.

Agora só falta colar isto tudo de forma a que os observadores possam ser notificados dos eventos do objecto observado. Mais uma vez simplificando, em muito, o código para o essencial:

import java.util.Observer;

ChatClient implements Observer
{

ChatClient
{
// criar um MsgObservable
// para receber as mensagens da rede
MsgObservable msgObj = new MsgObservable();

// criar os observadores
LogWriter log = new LogWriter();
ChatUI ui = new ChatUI();

// associar os observadores ao objecto observado
msgObj.addObserver(log);
msgObj.addObserver(ui);

// iniciar o MsgObservable
msgObj.run();
}
}
Aqui temos uma forma bastante elegante de separar bastantes componentes de código e de limitar a sua zona de interacção no código facilitando a sua reutilização.

É também importante referir que o Observable está muito dependente do desempenho das funções update( Observable obj, Object arg ), visto que só retorna o seu fluxo normal de execução depois de todos os observadores tenham sido notificados.

quinta-feira, dezembro 07, 2006

Melhorar o Eclipse no Ubuntu

Para muitos programadores de Java, o Eclipse é uma ferramenta essencial para se conseguir produzir melhor código, mais rapidamente.

No Ubuntu, o Eclipse vem configurado para usar o GCJ, um compilador open-source de Java, devido a uma política de uso exclusivo de software livre. Contudo, no meu pobre computador com uns míseros 256mb de RAM, só o Eclipse ocupava mais de 100mb de RAM, por vezes ultrapassando os 110mb. Mas o consumo de RAM não seria dramático se não tornasse o Eclipse irritantemente lento, afectando igualmente o resto do sistema.

Depois de instalar a JDK da Sun, julguei que o Eclipse passaria a usar a JVM(Java Virtual Machine) da Sun para correr, visto que este passou a ser a JVM por omissão no sistema. Na realidade, o Eclipse continuou a usar o GCJ.
Assim, para mudar de JVM no qual o Eclipse corre é necessário alterar o ficheiro /etc/eclipse/java_home e acrescentar o destino da JVM da Sun, tipicamente, /usr/lib/jvm/java-1.5.0-sun, no princípio do ficheiro.

Depois de esta alteração, o consumo do Eclipse passou a situar-se abaixo dos 100mb de RAM, mas mais importante, passou a ser, de facto, possível usá-lo sem recorrer permanentemente a uma das virtudes com maior carência entre os seres humanos: a paciência.

segunda-feira, outubro 23, 2006

Usar o Apache Lucene: parte 1

O meu estágio na faculdade, no grupo XLDB, tem-me levado a investigar com particular atenção e interesse o mundo da Information Retrieval e tem-me levado a experimentar uma série de bibliotecas.
Uma das bibliotecas mais famosas é o Apache Lucene , que é amplamente reputada pelo seu desempenho tanto em indexação como em pesquisas, e pela sua escalabilidade. Só por curiosidade, o Lucene é a base do sistema de pesquisa da Amazon.
Contudo a qualidade dos seus resultados de pesquisa não é das mais brilhantes visto que não parece haver particular esforço na melhoria dos algoritmos de classificação.

Para programadores que queiram implementar funcionalidades de pesquisa nas suas aplicações, o Lucene é uma boa proposta. Fácil de usar e com documentação abundante.

Para que possam ter um bom ponto de partida, de seguida apresento um pedaço de código em Java que implementa um programa que indexa todos os ficheiros .txt de um dada directoria e sub-directorias. Este código foi feito usando o Lucene 2.0.0.

Mas antes de chegarmos ao código propriamente dito, julgo que é importante explicar o que são de facto esses índices. Os índices são ficheiro onde, a cada termo se mantêm uma lista de ficheiros onde estes termos aparecem. Graças a isto, é possível, sem grande esforço, que dado um termo se obtenha todos os ficheiros onde este aparece.

Agora, já vos autorizo ver o código;)
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Date;

import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexWriter;

public class lucene_indexing {

public static void main(String[] args) throws IOException {
File indexDir = new File("/directoria/onde/ficará/o/index");
File dataDir = new File("/directoria/a/indexar");

long start = new Date().getTime();
int numIndexed = index(indexDir, dataDir);
long end = new Date().getTime();

System.out.println("> "+ numIndexed +" indexados em: "+ (end - start) +"ms");
}

public static int index(File indexDir, File dataDir) throws IOException {
if (!dataDir.exists() || !dataDir.isDirectory()) {
throw new IOException (dataDir + "não existe ou não é directoria");
}

IndexWriter writer = new IndexWriter(indexDir, new StandardAnalyzer(), true);
writer.setUseCompoundFile(false);

indexDirectory(writer, dataDir);

int numIndexed = writer.docCount();
writer.optimize();
writer.close();

return numIndexed;
}

public static void indexDirectory(IndexWriter writer, File dir) throws IOException {
File[] files = dir.listFiles();

for (File file : files) {
if (file.isDirectory()) {
indexDirectory(writer, file);
}
else if (file.getName().endsWith(".txt")) {
indexFile(writer, file);
}
}
}

public static void indexFile(IndexWriter writer, File file) throws IOException {
if (file.isHidden() || !file.exists() || !file.canRead()) {
return;
}

System.out.println("A indexar: \t"+ file.getCanonicalPath());

Document doc = new Document();
doc.add(new Field("content", new FileReader(file)));
doc.add(new Field("filename", file.getPath(), Field.Store.YES, Field.Index.UN_TOKENIZED));
writer.addDocument(doc);
}
}

A função index(File indexDir, File dataDir) inicia o processo de indexação e é-lhe indicada a directoria onde residirá o index e a directoria a indexar.
Nesta parte do código é criado o escritor do índice, componente essencial para este programa.
Se repararmos nesta linha de código: IndexWriter writer = new IndexWriter(indexDir, new StandardAnalyzer(), true);, vemos que é passado ao escritor do índice um analisador. Este analisador é essencial no processo de indexação já que é ele que tem a responsabilidade de processar o texto e de definir quais são os separadores que delimitam os termos, tais como espaços, vírgulas. Em particular, o StandarAnalyzer possui regras complexas que o permite, por exemplo, manter e-mails como um todo invés de os dividir em parcelas.
Outra linha crucial é: writer.optimize();, esta chamada permite unificar vários ficheiros de índice que foram criados durante a indexação, com o intuito de ser possível obter um melhor desempenho. Esta chamada é feita uma única vez, no final do processo de indexação.

A função indexDirectory(IndexWriter writer, File dir) chama a função que indexa, caso encontre um ficheiro .txt, caso encontre uma directoria, chama-se recursivamente.

A função indexFile(IndexWriter writer, File file) é a função que indexa os ficheiros .txt, indexando o seu conteúdo transformando-o em parcelas mas sem o guardar e indexando o nome do ficheiro qualquer tipo de processamento ou interpretação.
Porquê esta diferença na indexação entre o conteúdo e o nome do ficheiro? No caso do conteúdo, nós queremos ser capazes de efectuar pesquisas aos termos que o compõe, como tal é preciso processar as parcelas de texto do ficheiro, tipicamente palavra a palavra, mas não guardamos o conteúdo por motivos de espaço em disco, só guardamos o processamento das parcelas de esse conteúdo. Isto permite diminuir em muito o tamanho dos índices face aos ficheiros que lhes deram origem.
No caso do nome do ficheiro, como vamos querer saber que ficheiros é que contêm determinados termos, vamos guardar os seus nomes para poderem ser apresentados e como não vamos fazer pesquisas sobre os nomes dos ficheiros, não é necessário processar os seus nomes, só guardá-los.

Agora, só é preciso alterar no código a directoria a indexar e a directoria onde vai ficar o índice, e compilar este código sem esquecer de colocar o .jar do Lucene no classpath.
Depois de compilar, é só correr, sem esquecer, novamente, de colocar a referência ao Lucene no classpath.

Num futuro próximo, irei explicar como é que é possível efectuar pesquisas sobre os índices criados.