Como implementar um simples avaliador de regras em Java
Autor: Rafael | Publicado: 2010-11-20T01:46:00-03:00


Como implementar um conjuto de condições definidas pelo usuário de forma simples que possa ser armazenado em um banco de dados em Java ?

Ex.: if(tag1.equals(value1) &amp;&amp; tag2.equals(value2)) { doThis(); } 




se tornar algo do tipo:

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Var&lt;Integer&gt; tag1 = new Var&lt;Integer&gt;();
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Var&lt;Integer&gt; tag2 = new Var&lt;Integer&gt;();
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Var&lt;Integer&gt; value1 = new Var&lt;Integer&gt;();
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Var&lt;Integer&gt; value2 = new Var&lt;Integer&gt;();
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // tag1 == value &amp;&amp; tag2 == value2
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Rule r1 = new Rule(
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; and(
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; eq(tag1, value1), 
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; eq(tag2, value2)
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; )
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; );
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; tag1.bind(10);
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; tag2.bind(30);
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; value1.bind(10);
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; value2.bind(30);
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; System.out.println(r1.eval());

Vamos definir que esse conjunto de condições é uma regra (rule) que precisa ser avaliada com certas variáveis. Para simplificar, nossas regras só podem ter os operadores Equals e NotEquals, e os conectores And e Or. Após você entender como é implementado, você pode estender para os seus próprios operadores e conectores.

Para definirmos nossas regras, precisamos construir um objeto que represente uma função, ou seja, um Functor. Um Functor é semelhante a um callback ou uma interface com somente um método, comumente usando no padrão Command, que podemos passá-los como argumentos para outros objetos executarem a função, a diferença é podemos montar um Functor dinamicamente, semelhante a linguagens funcionais.


interface Functor&lt;T&gt; {
&nbsp;&nbsp;&nbsp; T eval();
} 

Definido o Functor, precisamos definir onde ficaram nossas variáveis e/ou parametros usados na regra:


public class Var&lt;T&gt; implements Functor&lt;T&gt; {
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; private T value;
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; @Override
&nbsp;&nbsp;&nbsp; public T eval() {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return value;
&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; public void bind(T value) {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.value = value;
&nbsp;&nbsp;&nbsp; }
}


Um Var é um caso particular de um Functor onde sempre retornará o valor de uma variável.

Agora vamos definir um Predicado.


public interface Predicate extends Functor&lt;Boolean&gt; {
&nbsp;&nbsp;&nbsp; Boolean eval();
}

Um Predicado é outro caso de um Functor que sempre retorna um valor true ou false. Para o nosso caso de analisador de regras, é que queremos.

Agora vamos começar a construir nossos conectores:


// And

public class AndOp implements Predicate {
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; private Predicate[] ps;

&nbsp;&nbsp;&nbsp; public AndOp(Predicate... ps) {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.ps = ps;
&nbsp;&nbsp;&nbsp; }

&nbsp;&nbsp;&nbsp; @Override
&nbsp;&nbsp;&nbsp; public Boolean eval() {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; for(Predicate p : ps) {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if(!p.eval()) return false; 
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return true;
&nbsp;&nbsp;&nbsp; }
}



// Or

public class OrOp implements Predicate {
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; private Predicate[] ps;

&nbsp;&nbsp;&nbsp; public OrOp(Predicate... ps) {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.ps = ps;
&nbsp;&nbsp;&nbsp; }

&nbsp;&nbsp;&nbsp; @Override
&nbsp;&nbsp;&nbsp; public Boolean eval() {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; for (Predicate p : ps) {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (p.eval())
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return true;
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return false;
&nbsp;&nbsp;&nbsp; }
}

Veja que estamos tentando otimizar a avaliação do conector And para que caso ele encontre algum resultado falso, ele já retorne com o valor false. Do mesmo modo, fazemos para o conector Or, que se encontrarmos um resultado verdadeiro, ele já retorne com o valor true, sem a necessidade de avaliarmos o resto dos resultados.

Agora vamos definir nossos operadores:


public class EqualsOp&lt;T&gt; implements Predicate {
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; private Var&lt;T&gt; v1;
&nbsp;&nbsp;&nbsp; private Var&lt;T&gt; v2;

&nbsp;&nbsp;&nbsp; public EqualsOp(Var&lt;T&gt; v1, Var&lt;T&gt; v2) {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.v1 = v1;
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.v2 = v2;
&nbsp;&nbsp;&nbsp; }

&nbsp;&nbsp;&nbsp; @Override
&nbsp;&nbsp;&nbsp; public Boolean eval() {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return v1.eval().equals(v2.eval());
&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; 
}



public class NotEqualsOp&lt;T&gt; implements Predicate {
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; private Var&lt;T&gt; v1;
&nbsp;&nbsp;&nbsp; private Var&lt;T&gt; v2;

&nbsp;&nbsp;&nbsp; public NotEqualsOp(Var&lt;T&gt; v1, Var&lt;T&gt; v2) {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.v1 = v1;
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.v2 = v2;
&nbsp;&nbsp;&nbsp; }

&nbsp;&nbsp;&nbsp; @Override
&nbsp;&nbsp;&nbsp; public Boolean eval() {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return !v1.eval().equals(v2.eval());
&nbsp;&nbsp;&nbsp; }
}

Veja que os operadores recebem variáveis, enquanto os conectores recebem outros predicados para serem avaliados sob uma certa condição.

Assim, podemos escrever baseado somente nos construtores, a seguinte regra:


Var&lt;Integer&gt; tag1 = new Var&lt;Integer&gt;();
Var&lt;Integer&gt; tag2 = new Var&lt;Integer&gt;();
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
Var&lt;Integer&gt; value1 = new Var&lt;Integer&gt;();
Var&lt;Integer&gt; value2 = new Var&lt;Integer&gt;();



new AndOp(new EqualsOp(tag1, value1), new EqualsOp(tag2, value2));

Para criar a abstração de regra, criamos a classe:


public class Rule {
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; private Predicate f;
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; public Rule(Predicate f) {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.f = f;
&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; public boolean eval() {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return f.eval();
&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
} 


Para simplificar a construção da regra, podemos tentar usar uma sintax mais simples, criando um Factory com métodos státicos:


public class RuleFactory {
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; public static &lt;T&gt; Predicate eq(Var&lt;T&gt; v1, Var&lt;T&gt; v2) {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return new EqualsOp&lt;T&gt;(v1, v2);
&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; public static &lt;T&gt; Predicate ne(Var&lt;T&gt; v1, Var&lt;T&gt; v2) {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return new NotEqualsOp&lt;T&gt;(v1, v2);
&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; public static Predicate and(Predicate op1, Predicate op2) {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return new AndOp(op1, op2);
&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; public static Predicate or(Predicate op1, Predicate op2) {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return new OrOp(op1, op2);
&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; 
} 


Assim, podemos importar estaticamente os métodos e usar na forma abaixo:

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Rule r1 = new Rule(
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; and(
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; eq(tag1, value1), 
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; eq(tag2, value2)
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; )
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; );

Agora é só fazermos o bind das variáveis e efetuar avaliação da regra:


tag1.bind(10);
tag2.bind(30);
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
value1.bind(10);
value2.bind(30);
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
System.out.println(r1.eval() ? "regra verdadeira " : "regra falsa");
Armazenar em um banco de dados

Para armazenar a regra em um banco de dados, utilizei o xstream para serializar meu Functor em um formato xml.


public class RuleXMLConverter {
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; public static String toXML(Rule r) {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; XStream xstream = new XStream();
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return xstream.toXML(r);
&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; public static Rule fromXML(String x) {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; XStream xstream = new XStream();
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return (Rule)xstream.fromXML(x);
&nbsp;&nbsp;&nbsp; }

}



Alternativas

Antes de começar a fazer um novo Functor, dê uma olhada nos frameworks abaixo:

 LambdaJ - manipulate collections in a pseudo-functional and statically typed way

 apache-commos-functor - Function Objects for Java

jga - Generic Algorithms for Java

Caso esteja precisando desenvolver algo em C++ que não já exista no Boost ou no STL (C++ Standart Library) há um ótimo post aqui sobre a implementação de Functors usando sobrecarga de operadores em C++.