A classe StreamTokenizer, parte do pacote java.io, oferece uma abordagem para a decomposição de fluxos de entrada em unidades lógicas, conhecidas como tokens. Esta funcionalidade é particularmente útil para a análise de dados textuais simples, como arquivos de configuração, registros (logs) ou até mesmo linguagens de script rudimentares, permitindo identificar e manipular elementos como números, palavras e caracteres específicos de forma estruutrada.
Conceitos Essenciais do StreamTokenizer
O StreamTokenizer é projetado para ler um fluxo de caracteres e produzir uma sequência de tokens. Ele possui um conjunto de regras configuráveis para determinar o que constitui um "token", como o que é uma palavra, um número, ou um delimitador.
Atributos Chave
nval: Armazena o valor numérico do token atual, caso ele seja um número. Caso contrário, seu valor é 0.0.sval: Contém a represantação em string do tokan atual, se este for uma palavra. Caso contrário, énull.ttype: Indica o tipo do token recém-lido. Seus valores podem ser:StreamTokenizer.TT_EOF(-1): Fim do fluxo de entrada.StreamTokenizer.TT_EOL(10): Caractere de nova linha.StreamTokenizer.TT_NUMBER: O token é um número.StreamTokenizer.TT_WORD: O token é uma palavra.- Qualquer outro valor: O token é um caractere único, e
ttypecontém o código ASCII desse caractere.
Construtor
StreamTokenizer(Reader leitor): Cria uma nova instância deStreamTokenizer, utilizando o objetoReaderfornecido como fonte de entrada.
Métodos Importantes
nextToken(): Lê o próximo token do fluxo e retorna seu tipo (o mesmo valor que seria atribuído attype).ordinaryChar(int ch): Designa o caracterechcomo um "caractere comum". Isso significa que ele não será tratado como parte de uma palavra, número ou delimitador, mas como um token individual.resetSyntax(): Restaura as configurações de sintaxe do analisador para seus valores padrão, onde espaços em branco, tabulações e novas linhas são delimitadores, e números e palavras são reconhecidos.wordChars(int baixo, int alto): Define um intervalo de caracteres como parte de uma palavra.parseNumbers(): Configura o analisador para reconhecer e analisar números.whitespaceChars(int baixo, int alto): Define um intervalo de caracteres como "espaço em branco", que é ignorado entre tokens.commentChar(int ch): Define o caracterechcomo o início de um comentário, fazendo com que o analisador ignore tudo até o próximoTT_EOL.
Exemplo Básico de Uso
O código a seguir demonstra a utilização fundamental do StreamTokenizer para extrair números e palavras de uma string, bem como identificar outros caracteres.
import java.io.IOException;
import java.io.StringReader;
public class AnalisadorTokensSimples {
public static void main(String[] args) {
String textoFonte = "O gato pulou sobre 45 obstáculos e a raposa correu 100 metros.";
try (StringReader leitor = new StringReader(textoFonte)) {
StreamTokenizer tokenizer = new StreamTokenizer(leitor);
// Tratamos o ponto como um caractere comum, não como parte de uma palavra ou número.
tokenizer.ordinaryChar('.');
System.out.println("Iniciando análise do texto:");
int tipoToken;
while ((tipoToken = tokenizer.nextToken()) != StreamTokenizer.TT_EOF) {
switch (tipoToken) {
case StreamTokenizer.TT_NUMBER:
System.out.println("Número encontrado: " + tokenizer.nval);
break;
case StreamTokenizer.TT_WORD:
System.out.println("Palavra encontrada: " + tokenizer.sval);
break;
case StreamTokenizer.TT_EOL:
System.out.println("Fim de linha detectado.");
break;
default:
// Qualquer outro caractere
System.out.println("Caractere especial: '" + (char) tokenizer.ttype + "'");
break;
}
}
} catch (IOException e) {
System.err.println("Erro durante a leitura: " + e.getMessage());
}
}
}
Neste exemplo, um StringReader alimenta o StreamTokenizer. O método nextToken() é invocado repetidamente até o fim do fluxo (TT_EOF). Um switch é utilizado para diferenciar entre números, palavras e outros caracteres, com base no valor de ttype.
Aplicações Típicas do StreamTokenizer
O StreamTokenizer é versátil para diversas tarefas de processamento de texto. Vejamos alguns cenários comuns.
1. Análise de Texto Genérica
Para extrair componentes específicos de um texto simples, como palavras e valores numéricos.
import java.io.IOException;
import java.io.StringReader;
public class ExtratorDadosTextuais {
public static void main(String[] args) {
String fraseExemplo = "O total é de $150.75, com um desconto de 20 por cento.";
try (StringReader sr = new StringReader(fraseExemplo)) {
StreamTokenizer analisador = new StreamTokenizer(sr);
analisador.parseNumbers(); // Habilita o reconhecimento de números
analisador.ordinaryChar('$'); // Para tratar o '$' como um caractere individual
int tipo;
while ((tipo = analisador.nextToken()) != StreamTokenizer.TT_EOF) {
if (tipo == StreamTokenizer.TT_WORD) {
System.out.println("Palavra: " + analisador.sval);
} else if (tipo == StreamTokenizer.TT_NUMBER) {
System.out.println("Valor Numérico: " + analisador.nval);
} else {
System.out.println("Caractere: '" + (char) tipo + "'");
}
}
} catch (IOException e) {
System.err.println("Problema na análise do texto: " + e.getMessage());
}
}
}
2. Parsing de Arquivos de Configuração
Útil para ler configurações simples no formato chave=valor, onde cada linha define um parâmetro.
Suponha um arquivo config.ini com o seguinte conteúdo:
servidor.nome=meuHost
servidor.porta=8080
usuario.admin=root
import java.io.FileReader;
import java.io.IOException;
import java.io.StreamTokenizer;
import java.util.HashMap;
import java.util.Map;
public class LeitorConfiguracao {
public static void main(String[] args) {
// Cria um arquivo de configuração simulado para o exemplo
try (java.io.FileWriter writer = new java.io.FileWriter("config.ini")) {
writer.write("servidor.nome=meuHost\n");
writer.write("servidor.porta=8080\n");
writer.write("usuario.admin=root\n");
} catch (IOException e) {
System.err.println("Não foi possível criar 'config.ini': " + e.getMessage());
return;
}
Map<String, String> configuracoes = new HashMap<>();
try (FileReader fr = new FileReader("config.ini")) {
StreamTokenizer parserConfig = new StreamTokenizer(fr);
parserConfig.wordChars('.', '.'); // Permite pontos em nomes de chaves
parserConfig.commentChar('#'); // Define '#' como caractere de comentário
String chave = null;
int tipo;
while ((tipo = parserConfig.nextToken()) != StreamTokenizer.TT_EOF) {
if (tipo == StreamTokenizer.TT_WORD) {
chave = parserConfig.sval;
} else if (tipo == '=') {
// Após a chave e o '=', o próximo token deve ser o valor
if (parserConfig.nextToken() == StreamTokenizer.TT_WORD || parserConfig.ttype == StreamTokenizer.TT_NUMBER) {
String valor = (parserConfig.ttype == StreamTokenizer.TT_WORD) ? parserConfig.sval : String.valueOf(parserConfig.nval);
if (chave != null) {
configuracoes.put(chave, valor);
}
}
}
}
System.out.println("Configurações lidas: " + configuracoes);
} catch (IOException e) {
System.err.println("Erro ao ler o arquivo de configuração: " + e.getMessage());
}
}
}
3. Componente de Análise Léxica Simples
Em compiladores ou interpretadores, o StreamTokenizer pode servir como base para a primeira fase (análise léxica), dividindo o código-fonte em tokens (palavras-chave, identificadores, operadores, literais).
import java.io.IOException;
import java.io.StringReader;
public class AnalisadorCodigoFonte {
public static void main(String[] args) {
String codigoExemplo = "int contador = 50; String nome = \"Teste\";";
try (StringReader sr = new StringReader(codigoExemplo)) {
StreamTokenizer lexer = new StreamTokenizer(sr);
lexer.parseNumbers(); // Habilita o reconhecimento de números
lexer.quoteChar('"'); // Reconhece strings entre aspas duplas
System.out.println("Análise Léxica:");
int tipoToken;
while ((tipoToken = lexer.nextToken()) != StreamTokenizer.TT_EOF) {
switch (tipoToken) {
case StreamTokenizer.TT_WORD:
System.out.println("IDENTIFICADOR/PALAVRA-CHAVE: " + lexer.sval);
break;
case StreamTokenizer.TT_NUMBER:
System.out.println("LITERAL NUMÉRICO: " + lexer.nval);
break;
case '"': // Caractere de aspas duplas indica uma string
System.out.println("LITERAL STRING: " + lexer.sval);
break;
case '=':
System.out.println("OPERADOR: =");
break;
case ';':
System.out.println("PONTUAÇÃO: ;");
break;
default:
System.out.println("CARACTERE DESCONHECIDO: '" + (char) tipoToken + "'");
break;
}
}
} catch (IOException e) {
System.err.println("Erro na análise léxica: " + e.getMessage());
}
}
}
Exemplo Prático: Gerenciador de Propriedades
Este exemplo demonstra como usar StreamTokenizer para carregar propriedades de um arquivo em uma aplicação, similar ao que um arquivo .properties faria, mas com uma sintaxe mais flexível.
Considere um arquivo de propriedades chamado app.props:
# Configurações do Banco de Dados
db.host = localhost
db.port = 5432
db.user = app_user
db.pass = senhaSegura123
import java.io.FileReader;
import java.io.IOException;
import java.io.StreamTokenizer;
import java.util.HashMap;
import java.util.Map;
public class GerenciadorDePropriedades {
private Map<String, String> propriedades = new HashMap<>();
public GerenciadorDePropriedades(String caminhoArquivo) {
carregarPropriedades(caminhoArquivo);
}
private void carregarPropriedades(String caminhoArquivo) {
try (FileReader leitor = new FileReader(caminhoArquivo)) {
StreamTokenizer st = new StreamTokenizer(leitor);
st.wordChars('.', '.'); // Permite pontos em chaves como "db.host"
st.ordinaryChar('='); // Trata '=' como um caractere comum, não um delimitador
st.parseNumbers(); // Habilita o reconhecimento de números
st.commentChar('#'); // Ignora linhas que começam com '#'
String chaveAtual = null;
String valorAtual = null;
int token;
while ((token = st.nextToken()) != StreamTokenizer.TT_EOF) {
if (token == StreamTokenizer.TT_WORD) {
// Se a chave ainda não foi definida, este é o início de uma nova propriedade
if (chaveAtual == null) {
chaveAtual = st.sval;
} else { // Se já temos uma chave, este pode ser um valor que é uma palavra
valorAtual = st.sval;
propriedades.put(chaveAtual, valorAtual);
chaveAtual = null; // Reinicia para a próxima propriedade
valorAtual = null;
}
} else if (token == StreamTokenizer.TT_NUMBER) {
// O valor é um número
valorAtual = String.valueOf((int)st.nval); // Convertendo para int para simplificar
if (chaveAtual != null) {
propriedades.put(chaveAtual, valorAtual);
chaveAtual = null;
valorAtual = null;
}
} else if (token == '=') {
// Após o '=', o próximo token será o valor
// Nada a fazer aqui, apenas prepara para ler o valor
}
}
} catch (IOException e) {
System.err.println("Falha ao carregar propriedades de " + caminhoArquivo + ": " + e.getMessage());
}
}
public String getPropriedade(String chave) {
return propriedades.get(chave);
}
public static void main(String[] args) {
// Criar um arquivo de propriedades de exemplo
try (java.io.FileWriter writer = new java.io.FileWriter("app.props")) {
writer.write("# Configurações do Banco de Dados\n");
writer.write("db.host = localhost\n");
writer.write("db.port = 5432\n");
writer.write("db.user = app_user\n");
writer.write("db.pass = senhaSegura123\n");
} catch (IOException e) {
System.err.println("Erro ao criar o arquivo app.props: " + e.getMessage());
return;
}
GerenciadorDePropriedades gestor = new GerenciadorDePropriedades("app.props");
System.out.println("Host do DB: " + gestor.getPropriedade("db.host"));
System.out.println("Porta do DB: " + gestor.getPropriedade("db.port"));
System.out.println("Usuário do DB: " + gestor.getPropriedade("db.user"));
System.out.println("Senha do DB: " + gestor.getPropriedade("db.pass"));
}
}