Tutorial

Este é um tutorial de introdução. Leia e entenda como é fácil implementar a análise de expressões em seus projetos usando o tiziu expression.

Introdução

Algumas aplicações necessitam de suporte à análise de expressões para que seus usuários possam configurar/personalizar determinadas funcionalidades. Um gerador de relatórios, por exemplo, pode gerar valores a partir de expressões configuradas em tempo de execução. Ou então, uma aplicação comercial pode ter determinada regra de negócio modificada pelo usuário, de acordo com a sua necessidade, inserindo expressões em uma tela de configuração contendo variáveis e funções pré-configuradas pelo programador.

Avaliando uma expressão

Para avaliar uma determinada expressão com o tiziu é preciso apenas obter uma instância de org.tiziu.Tiziu, através da fábrica org.tiziu.TiziuFactory, e executar o método org.tiziu.Tiziu.evaluate() passando por parâmetro a expressão em questão.

Exemplo:

// declarações de import
import org.tiziu.Tiziu;
import org.tiziu.TiziuFactory;

// código de exemplo
Tiziu tiziu = TiziuFactory.createTiziu();

Object o = tiziu.evaluate("10 + 15 * (30 - (50 / 10))");

System.out.println(o);

Ao executar o código acima, podemos observar o seguinte valor impresso no console:

385

Tabela de símbolos

O tiziu usa uma tabela de símbolos, que nada mais é do que uma instância de org.tiziu.config.SymbolTable, para obter o valor das variáveis e funções analisadas nas expressões. Para que o tiziu tenha acesso a essa tabela, é preciso que sua instância seja especificada no método org.tiziu.TiziuFactory.createTiziu() ao construir uma nova instância de org.tiziu.Tiziu.

Uma instância de org.tiziu.config.SymbolTable permite:

  • configurar variáveis;
  • configurar funções para quantidade arbitrária de parâmetros; e
  • sobrecarregar os operadores do tiziu para tipos específicos.

Configurando variáveis

Segue abaixo um exemplo de como configurar variáveis na tabela de símbolos:

// declarações de import
import org.tiziu.Tiziu;
import org.tiziu.TiziuFactory;
import org.tiziu.config.SymbolTable

// código de exemplo
SymbolTable tabSimbolos = new SymbolTable();

tabSimbolos.setIdentifier("a", 12); 
tabSimbolos.setIdentifier("b", 12.2f);
tabSimbolos.setIdentifier("hoje", new Date());

Tiziu tiziu = TiziuFactory.createTiziu(tabSimbolos);

String expressao = "\"Data atual: \" + hoje + \" valor: \" + (a + b)";

Object o = tiziu.evaluate(expressao);

System.out.println(o);

Ao executar o código acima, podemos observar o seguinte valor impresso no console:

Data atual: Sat Aug 09 14:35:29 BRT 2008 valor: 24.2

Note que a data impressa é a que está em seu computador, ;).

É possível alterar o valor das variáveis após a tabela de símbolos ter sido atribuída à instância de org.tiziu.Tiziu. Para isso, basta apenas atribuir o novo valor da variável usando o mesmo identificador.

Segue abaixo o exemplo de como modificar o valor das váriaveis na tabela de símbolos:

// declarações de import
import org.tiziu.Tiziu;
import org.tiziu.TiziuFactory;
import org.tiziu.config.SymbolTable

// código de exemplo
SymbolTable tabSimbolos = new SymbolTable();

tabSimbolos.setIdentifier("a", 12);
tabSimbolos.setIdentifier("b", 12.2f);
tabSimbolos.setIdentifier("hoje", new Date());

Tiziu tiziu = TiziuFactory.createTiziu(tabSimbolos);

String expressao = "\"Data atual: \" + hoje + \" valor: \" + (a + b)";

Object o = tiziu.evaluate(expressao);

System.out.println(o);

// definindo novos valores para as variáveis 'a' e 'b'.
tabSimbolos.setIdentifier("a", "opa: ");
tabSimbolos.setIdentifier("b", "texto!");

// executando a expressão novamente.
o = tiziu.evaluate(expressao);

System.out.println(o);

Ao executar o código acima, podemos observar o resultado da expressão, antes e depois de modificar o valor das variáveis a e b, impresso no console da seguinte forma:

Data atual: Sat Aug 09 14:35:29 BRT 2008 valor: 24.2
Data atual: Sat Aug 09 14:35:29 BRT 2008 valor: opa: texto!

Configurando funções

Uma função, na sintaxe de expressão do tiziu, é formada por um identificador seguido de um '(', seguido de uma lista de parâmetros (que pode ser uma variável, uma expressão, uma chamada a outra função, etc.) separados por ',' e terminado por um ')'.

Para definir uma função no tiziu é preciso atribuir uma instância da interface org.tiziu.config.FunctionCommand para um identificador na tabela de símbolos.

A interface org.tiziu.config.FunctionCommand contém apenas um método como mostrado abaixo:

package org.tiziu.config;

import java.util.List;

public interface FunctionCommand {

  public Object call(List<?> args);
  
}

Ao identificar uma função em uma determinada expressão, o tiziu irá invocar o método org.tiziu.config.FunctionCommand.call() da instância de org.tiziu.config.FunctionCommand correspondente, configurada na tabela de símbolos, e irá atribuir à lista args os parâmetros identificados, seguindo a ordem em que foram escritos pelo usuário.

Segue abaixo um exemplo de implementação de uma função para formatação de datas:

// declarações de import
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

import org.tiziu.config.FunctionCommand;
import org.tiziu.config.IllegalFunctionException;

// implementação da interface org.tiziu.config.FunctionCommand
public class FunctionDateFormat implements FunctionCommand {

  // implementação do método org.tiziu.config.FunctionCommand.call()
  public Object call(List<?> args) {
                
    // verifica se os argumentos esperados foram especificados pelo usuário
    if (args.size() == 2 
      && args.get(0) instanceof Date 
      && args.get(1) instanceof String) {
                        
      // conversão de tipo dos argumentos 
      Date data = (Date) args.get(0);
      String formato = (String) args.get(1);
                
      // criação de SimpleDateFormat para o formato especificado pelo usuário
      SimpleDateFormat sdf = new SimpleDateFormat(formato);
                        
      // retorno da data formatada
      return sdf.format(data);
    }
                
    // dispara uma exceção caso os argumentos sejam inválidos
    throw new IllegalFunctionException("dateFormat", args);
  }

}

Essa implementação espera que o usuário especifique dois parâmetros para a função: uma data (que deverá ser uma instância de java.util.Date) e uma string (que poderá ser uma variável do tipo java.lang.String ou um literal delimitado pelo caractere '"').

Para configurar a nova função no tiziu, devemos fazer o seguinte:

// declarações de import
import org.tiziu.Tiziu;
import org.tiziu.TiziuFactory;
import org.tiziu.config.FunctionCommand;
import org.tiziu.config.SymbolTable;

// código de exemplo

SymbolTable tabSimbolos = new SymbolTable();

// atribuindo uma instância de java.util.Date para o identificador 'hoje'
tabSimbolos.setIdentifier("hoje", new Date());
                
// atribuindo uma instância de FunctionCommand para o identificador 'formataData'
tabSimbolos.setFunction("formataData", new FunctionDateFormat());

Tiziu tiziu = TiziuFactory.createTiziu(tabSimbolos);

String expressao = "\"Data atual: \" + formataData(hoje, \"dd/MM/yyyy\")";

Object o = tiziu.evaluate(expressao);

System.out.println(o);

Execute o código acima e veja o que acontece, ;).

Sobrecarregando operadores

O tiziu permite que seus operadores sejam sobrecarregados para tipos específicos.

Para sobrecarregar um operador é necessário atribuir uma instância de org.tiziu.config.OperationCommand a uma instância de org.tiziu.config.OperationKey na tabela de símbolos.

A interface org.tiziu.config.OperationCommand contém somente um método como mostrado abaixo:

public interface OperationCommand {
        
  public Object evaluate(Object left, Object right);
  
}

O tiziu irá invocar o método org.tiziu.config.OperationCommand.evaluate() passando os argumentos, esquerdo e direito, do operador sobrecarregado para os tipos especificados na tabela de símbolos.

Segue abaixo o exemplo de uma implementação de org.tiziu.config.OperationCommand que será usada para sobrecarregar o operador '+' para o tipo java.lang.String.

// declarações de import
import java.math.BigDecimal;

import org.tiziu.config.OperationCommand;
import org.tiziu.implementation.DefaultOperation;
import org.tiziu.parser.Token;
import org.tiziu.util.Numbers;

// implementação da interface org.tiziu.config.OperationCommand
public class StringSumOperation implements OperationCommand {

  // implementação do método org.tiziu.config.OperationCommand.evaluate()
  public Object evaluate(Object left, Object right) {
    String strLeft = (String)left;
    String strRight = (String)right;
                
    // verifica se os parâmetros são números válidos
    if (isNumber(strLeft) && isNumber(strRight)) {
                        
      return new BigDecimal(strLeft).add(new BigDecimal(strRight));
                        
    }
                
    // delega a operação à instância de org.tiziu.implementation.DefaultOperation
    // caso os parâmetros não sejam números válidos.
    return DefaultOperation.getInstance().evaluate(Token.SUM, left, right);
  }
        
  private boolean isNumber(String str) {
    return Numbers.isInteger(str) || Numbers.isDecimal(str);
  }
  
}

Essa implementação espera receber dois argumentos do tipo java.lang.String. Se ambos os argumentos forem strings contendo números válidos, eles serão convertidos e somados. Senão, a operação será delegada à instância de org.tiziu.implementation.DefaultOperation. A classe org.tiziu.implementation.DefaultOperation, como o próprio nome já diz, é responsável pelas operações default do tiziu. A org.tiziu.util.Numbers é uma classe útil para operações com números.

O operador e os tipos sobrecarregados devem ser especificados na contrutora da classe org.tiziu.config.OperationKey. Segue abaixo um exemplo de como sobrecarregar o operador '+' para o tipo java.lang.String usando a nossa implementação de org.tiziu.config.OperationCommand:

// declarações de import
import org.tiziu.Tiziu;
import org.tiziu.TiziuFactory;
import org.tiziu.config.OperationKey;
import org.tiziu.config.SymbolTable;
import org.tiziu.parser.Token;

// código de exemplo

SymbolTable tabSimbolos = new SymbolTable();

// definição das variáveis do tipo java.lang.String
tabSimbolos.setIdentifier("str1", "10");
tabSimbolos.setIdentifier("str2", "20");

Tiziu tiziu = TiziuFactory.createTiziu(tabSimbolos);
                
String expressao = "str1 + str2";

// executando a expressão antes de sobrecarregar o operador '+'
Object o = tiziu.evaluate(expressao);
                
System.out.println(o);
                
// sobrecarregando o operador '+'
tabSimbolos.setOperation(new OperationKey(Token.SUM, String.class), new StringSumOperation());

// executando a expressão novamente
o = tiziu.evaluate(expressao);
                
System.out.println(o);

Ao executar esse código, obtemos o seguinte resultado impresso no console:

1020
30

O valor "1020" é resultado da concatenação das strings str1 e str2, pois no momento dessa operação o operador '+' não tinha sido sobrecarregado ainda. Já o valor "30" é o resultado da converão das strings para número e da operação de soma, pois nesse momento nós já tínhamos definido a sobrecarga para o operador, especificando a instância de StringSumOperation.

Divirta-se

Experimente criar funções que podem ser combinadas. Sobrecarrege os operadores do tiziu para classes que você implementar. Invente, reinvente, o tiziu é apenas um analisador de expressões que pode ser configurado, ou melhor, que pode ser turbinado! O único limite é a sua criatividade.