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.
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.
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
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:
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!
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, ;).
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.
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.