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.