Conforme foi descrito no post intitulado “Adicionando comportamentos a Agentes JADE” que deu uma introdução no desenvolvimento de comportamentos em agentes JADE, o objetivo deste post é descrever como desenvolver comportamentos compostos para agentes JADE; uma forma mais complexa de trabalhar com comportamentos em agentes JADE.

Comportamentos compostos são espécies de comportamentos mais complexos e que são formados por sub-comportamentos; ou seja, é uma espécie de comportamento formada pela junção de vários comportamentos contidos. Nesta nova modalidade de desenvolver comportamentos, o programador desenvolve um comportamento que executa vários outros comportamentos internos que podem ser do mesmo tipo ou de tipos diferentes. Comportamentos compostos estão organizados em três tipos de subclasses: SequentialBehaviour, para a execução de comportamentos de forma sequencial; ParallelBehaviour, para a execução de comportamentos de forma paralela e FSMBehaviour, para a execução de comportamentos como uma máquina de estados. Assim, cada uma destas classes de comportamentos compostos pode conter comportamentos do tipo OneShotBehaviour, CyclicBehaviour, WakerBehaviour, TickerBehaviour e também comportamentos próprios desenvolvidos pelo programador.

Comportamentos Sequentialbehaviour

Este comportamento executa seus sub-comportamentos de maneira sequencial e termina quando todos seus sub-comportamentos estiverem concluídos. A política de escalonamento dos sub-comportamentos acontece da seguinte forma: o comportamento inicia executando seu primeiro sub-comportamento, quando este sub-comportamento é finalizado (isto é, seu método done() retorna true), o segundo sub-comportamento é executado e assim por diante; quando o último sub-comportamento é finalizado, o comportamento composto sequencial é finalizado. Os sub-comportamentos são adicionados ao comportamento composto com o método addSubBehaviour(). A ordem como estes sub-comportamentos são adicionados indica a ordem de execução dos mesmos. Para utilizar SequentialBehaviour é necessário importar a classe “jade.core.behaviours.SequentialBehaviour”. Conforme pode ser observado na Listagem 1, é descrito um agente que implementa um comportamento do tipo SequentialBehaviour; são adicionados a este comportamento três sub-comportamentos sendo eles o WakerBehaviour, OneShotBehaviour e TickerBehaviour, respectivamente. A Figura 1 destaca como deverá aparecer a janela do console na utilização do código da Listagem 1.

Listagem 1. Código de agente executando comportamento composto SequencialBehaviour.

import jade.core.Agent;
  import jade.core.behaviours.*;
      public class HelloWorldAgent extends Agent{
   
          protected void setup() {
   
              //Mensagens de inicialização do agente
              System.out.println("Olá! Meu nome é " + getLocalName ( ));
              System.out.println("Vou executar três comportamentos de forma sequencial:" );
              
              //criação de um objeto de comportamento do tipo SequentialBehaviour
              SequentialBehaviour comportamento = new SequentialBehaviour(this) {
                  public int onEnd(){
                      myAgent.doDelete();
                      return 0;
                  }
              };
   
              // adição do primeiro comportamento
              comportamento.addSubBehaviour(new WakerBehaviour(this,500) {
                  long t0 = System.currentTimeMillis();
                  protected void onWake() {
                      System.out.println((System.currentTimeMillis() - t0) + 
                              ":Executei meu primeiro comportamento do tipo WakeBehaviour!");
                 }
             });
   
              // adição do segundo comportamento
              comportamento.addSubBehaviour(new OneShotBehaviour(this) {
                  public void action() {
                      System.out.println("Executei meu segundo comportamento do tipo OneShotBehaviour");
                  }
              } );
   
              //adição do terceiro comportamento
              comportamento.addSubBehaviour(new TickerBehaviour(this,700) {
                  
                  int exec = 0;
                  long t1 = System.currentTimeMillis();
                  
                  protected void onTick(){
                      
                      if(exec == 3){ 
                          stop();
                      } else {
                          System.out.println((System.currentTimeMillis() - t1) + 
                              ": Estou executando meu terceiro comportamento do tipo TickerBehaviour");
                          exec++;
                      }
                  }
              });
   
              // adicionando execução de comportamentos SequencialBehaviour
              addBehaviour(comportamento);
          }
          
          protected void takeDown() {
              System.out.println("Fui finalizado com sucesso");
          }
          
      }


Figura 1. Execução de comportamento composto SequencialBehaviour.

Comportamento Parallelbehaviour

O comportamento ParallelBehaviours implementa um comportamento composto que escalona seus sub-comportamentos em paralelo. Toda vez que o método action() do comportamento paralelo for executado, o método action() de seus sub-comportamentos também serão executados. Um comportamento paralelo pode ser instruído para ser finalizado quando todos os comportamentos paralelos estiverem completos, ou quando algum deles for finalizado. Além domais, é possível definir a finalização do comportamento composto para quando um certo número n de sub-comportamentos estiverem sido finalizados. Essas condições são definidas no construtor da classe, passando como parâmetro as constantes when_all, quando for todos os comportamentos finalizados, when_any, quando for algum comportamento finalizado, ou um valor inteiro que especifica o número de sub-comportamentos que são necessários para finalizar o ParallelBehaviour. É importante frisar que para utilizar ParallelBehaviour é necessário importar a classe “import static jade.core.behaviours.ParallelBehaviour”. Também é necessário importar as clausulas que serão utilizadas; para clausula WHEN_ALL, deve ser importado “import static jade.core.behaviours.ParallelBehaviour.WHEN_ALL”; para clausula WHEN_ANY, deve ser importado “import static jade.core.behaviours.ParallelBehaviour.WHEN_ANY”. Como poder observado na Listagem 2, a descrição de um agente que executa comportamentos de forma paralela. São implementados três comportamentos do tipo SimpleBehaviour; o primeiro comportamento finaliza após três interações, o segundo comportamento finaliza após sete interações e o último comportamento é finalizado após nove interações. Comportamentos do tipo SimpleBehaviour são comportamentos atômicos; um tipo de classe abstrata de comportamento que são feitas por uma única tarefa, monolítico e não pode ser interrompido. A clausula WHEN_ALL define que o comportamento paralelo só será finalizado quando todos os seus sub-comportamentos forem executados. Caso se utilize a clausula WHEN_ANY, o comportamento paralelo será finalizado quando o primeiro sub-comportamento for totalmente executado. A Figura 2 ilustra a execução de um comportamento paralelo utilizando a clausula WHEN_ALL, onde todos os sub-comportamentos são executados para o comportamento paralelo ser finalizado; já a Figura 3, ilustra a execução de um comportamento paralelo utilizando a clausula WHEN_ANY, onde o comportamento paralelo é finalizado quando o primeiro sub-comportamento termina de ser executado, neste exemplo o sub-comportamento um. Comportamentos do tipo ParallelBehaviour só permitem usar uma clausula por vez

Listagem 2. Agente executando comportamento composto ParallelBehaviour com clausula WHEN_ALL.

import jade.core.Agent;
  import jade.core.behaviours.*;
  import static jade.core.behaviours.ParallelBehaviour.WHEN_ALL;
      public class HelloWorldAgent extends Agent{
   
          protected void setup() {
   
              System.out.println("Ola! Eu sou o agente " + getLocalName());
              System.out.println("Vou executar três comportamentos de forma concorrentemente");
              
              ParallelBehaviour s = new ParallelBehaviour(this, WHEN_ALL){
                  
                  public int onEnd() {
                      System.out.println("Comportamento Composto Finalizado com Sucesso!");
                      return 0;
                  }
              };
   
              addBehaviour(s);
   
              s.addSubBehaviour(new SimpleBehaviour(this){
                  int quantidade =1;
                  public void action(){
                      System.out.println("Comportamento 1: Executando pela "+ quantidade + " vez");
                      quantidade = quantidade +1;
                  }
   
                  public boolean done(){
                      if(quantidade==4) {
                          System.out.println("Comportamento 1 - Finalizado");
                          return true;
                      }else
                          return false;
                  }
              });
   
              s.addSubBehaviour(new SimpleBehaviour(this) {
                  int quantidade =1;
                  public void action() {
                      System.out.println("Comportamento 2: Executando pela " + quantidade + " vez");
                      quantidade = quantidade +1;
                  }
              
                  public boolean done() {
                      if(quantidade==8) {
                          System.out.println("Comportamento 2 - Finalizado");
                          return true;
                      }else
                          return false;
                  }
              });
   
              s.addSubBehaviour(new SimpleBehaviour(this){
                  int quantidade =1;
                  public void action() {
                      System.out.println("Comportamento 3: Executando pela"+ quantidade + " vez");
                      quantidade = quantidade +1;
                  }
   
                  public boolean done() {
                      if( quantidade==10) {
                          System.out.println(" Comportamento 3 - Finalizado");
                          return true;
                      }else
                              return false;
                  }
              });
          }        
  }    

Figura 2. Comportamento composto paralelo utilizando a clausula WHEN_ALL.

Figura 3. Comportamento composto paralelo utilizando a clausula WHEN_ANY.

Pode-se também definir que um comportamento composto finalize após um certo número de finalizações de seus sub-comportamentos. Pode-se utilizar como parâmetro um valor inteiro, por exemplo: ParallelBehaviour s = new ParallelBehaviour(this, 2), onde 2 indica que após dois sub-comportamentos serem finalizados o comportamento composto será finalizado. É de extrema importância que seja utilizado um valor número de acordo com a quantidade de sub-comportamento implementados; caso existem três sub-comportamentos e seja utilizado como parâmetro numérico que o comportamento paralelo só finalize após quatro sub-comportamentos, os seus três sub-comportamentos serão finalizados mas o comportamento paralelo ficará executando indefinidamente. Tomando o código descrito na Listagem 2 alterando o campo da quantidade de sub-comportamentos finalizados para dois, a Figura 4 ilustra como deverá aparecer na janela do console, neste caso o comportamento paralelo é finalizado quando um segundo sub-comportamento é finalizado.

Figura 4. Ilustração de comportamento composto com condição de parada igual a 2 sub-comportamentos finalizados.

Comportamento Fsmbehaviour

Este comportamento é baseado no escalonamento por uma máquina finita de estados (Finite State Machine). O FSMBehaviour executa cada sub-comportamento de acordo com uma máquina de estados finitos definido pelo usuário. Mais especificamente, cada sub-comportamento representa um estado definido na máquina de estados finitos. Ela fornece métodos para registrar estados (sub-comportamentos) e transições que definem como dar-se-á o escalonamento dos sub-comportamentos. Os passos básicos para se definir um FSMBehaviour são:


  • Registrar um comportamento único como estado inicial, passando como parâmetros o comportamento e uma String que nomeia este estado. Para isto utiliza-se o método registerFirstState();
  • Registrar um ou mais comportamentos como estados finais, utilizando o método registerLastState();
  • Registrar um ou mais comportamentos como estados intermediários utilizando o método registerState();
  • Para cada estado, registrar as transições deste com os outros estados utilizando o método registerTransition(). Por exemplo, suponha que você tenha um estado definido como X e outro estado definido como Y e você deseja informar que a transição será feita do estado X para o Y, quando o estado X retornar o valor 1. O método seria definido como registerTransition(X,Y,1).

Semelhante a um comportamento sequencial, um comportamento FSMBehaviour mantém um apontador para o sub comportamento corrente. Quando um sub-comportamento estiver concluído (o método done() retorna true), o comportamento FSMBehaviour verifica na sua tabela de transição interna e, com base nela, seleciona a próximo sub-comportamento para disparar na próxima vez que o seu método action() for executado. As transições em um comportamento FSMBehaviour são marcadas com um rótulo inteiro. Quando um sub-comportamento atual do comportamento FSMBehaviour é concluído, o valor de retorno do seu método onEnd() é utilizado como um valor de saída e comparado com os rótulos de todas as transições que saem do atual estado do subcomportamento. A primeira transição cujo rótulo corresponde a este valor de saída é utilizado como seu estado de destino, que se tornará o sub-comportamento atual a ser executado. O método registerState() é usado para adicionar membros a uma instância FSMBehaviour, aceita dois argumentos: uma string que define o nome do estado que está sendo registado e o sub-comportamento que vai ser executado nesse estado. O método registerTransition() é usado para adicionar transições para uma instância FSMBehaviour, aceita três argumentos: duas Strings que definem o estado de origem e o estado de destino da transição e um valor inteiro definindo a transição do rótulo. Os métodos registerFirstState() e registerLastState() permitem registrar o estado de entrada e os estados de finalização do FSMBehaviour, respectivamente. Vale destacar que um comportamento FSMBehaviour só pode conter um sub-comportamento como estado inicial, mas pode conter vários sub-comportamentos como estados finais. O comportamento FSMBehaviour termina quando um estado de terminação é atingido e totalmente executado.

Conforme ilustrado na Figura 5, considere um agente que realiza um determinado comportamento x. Ao final deste comportamento é verificada se sua operação foi concluída. Caso esta operação não seja concluída, um comportamento z é efetuado. Ao término do comportamento z, o comportamento x é executado novamente e toda a verificação ocorre novamente. Na execução em que o comportamento x estiver concluído, será invocado o último comportamento do agente, o comportamento y.

Figura 5. Ilustração de execução de comportamentos como uma máquina de estados.

A Listagem 3, a seguir descreve o código de um agente que possui três comportamentos estruturados como uma máquina de estados, conforme ilustrado na Figura 5. Conforme pode ser observado, o comportamento X é invocado 5 vezes; quando o comportamento X é finalizado, é verificado se ele já foi executado 5 vezes. Em caso negativo, o comportamento FSMBehaviour passa a transição de estado para o comportamento Z que é executado até ser finalizado. Após o comportamento Z ser finalizado, o comportamento FSMBehaviour passa o próximo estado de transição para o comportamento X. Caso o comportamento X tenha sido executado 5 vezes, o comportamento FSMBehaviour transita o próximo estado para o comportamento Y que é o estado final da máquina de estados.

Listagem 3. Agente com comportamento composto do tipo máquina de estados.


  import jade.core.Agent;
  import jade.core.behaviours.*;
     public class HelloWorldAgent extends Agent{
   
          protected void setup() {
   
              FSMBehaviour compFSM = new FSMBehaviour(this) {
                  public int onEnd () {
                      System.out.println("Comportamento FSM finalizado com sucesso!" );
                      return 0;
                  }
              };
              
              // Registro do primeiro estado - Comportamento X
              compFSM.registerFirstState(new OneShotBehaviour(this){
                  int c =0;
                  public void action(){
                      System.out.println("Executação do Comportamento X");
                      c++;
                  }
              
                  public int onEnd() {
                      return (c>4?1:0);
                  }
              }, "X");
              
              // Registro do segundo estado - Comportamento Z
              compFSM.registerState(new OneShotBehaviour(this){
                  
                  public void action(){
                      System.out.println("Executação do Comportamento Z");
                  }
                  
                  public int onEnd(){
                      return 2;
                  }
              }, "Z");
   
              // Registro do terceiro e último estado - Comportamento Z
              compFSM.registerLastState(new OneShotBehaviour(this){
             
                  public void action(){
                      System.out.println("Executando meu último comportamento .");
                  }
              },"Y");
   
              // definição das transações
              // transação do comportamento X para Z, caso onEnd () do comportamento X retorne 0
              compFSM.registerTransition( "X" , "Z" , 0 ); 
              
              // transação do comportamento X para Y, caso onEnd () do comportamento X retorne 1
              compFSM.registerTransition("X","Y",1);
   
   
              // definição da transição padrão (não importa o tipo de retorno)
              // como a máquina de estados é finita, necessário zerar os estados de X e Z Sring[]{"X" , "Z"}
              compFSM.registerDefaultTransition( "Z" , "X", new String[]{"X" , "Z"});
              
              // acionar o comportamento como máquina de estados
              addBehaviour (compFSM) ;
   
          }        
  }    

Conforme pode ser observado na Figura 6, o comportamento X foi executado cinco vezes e ao final de cada execução o comportamento Z era invocado. Quando c>4, a execução de X estará completa e com isto, o último comportamento será invocado. Conforme pode ser observado no código da Listagem 3, no código deste agente que foi definido um estado com o método registerState(Comportamento, Nome do Comportamento). As transições foram definidas como registerTransition(Origem, Destino, Retorno da Origem). O método registerDefaultTransition() define uma transição padrão, ou seja, uma transição que ocorre sempre de um estado para o outro independente do retorno obtido na execução do estado de origem.

É importante enfatizar que a linha de código compFSM.registerDefaultTransition("Z","X", new String[]{"X", "Z"}) registra uma transição padrão entre Z e X, mas como ambos estados já foram executados e como são comportamentos OneShot só poderiam executar uma vez. Por isto, temos o argumento new String[]{"X","Z"} zerando as informações sobre estes estados, possibilitando que possam ser novamente executados.

Figura 6. Ilustração da execução de comportamento composto do tipo máquina de estados.

Conforme pode ter sido observado neste post, existem comportamentos pré-definidos que levam a construção de comportamentos mais complexos. Estes comportamentos podem ser realizados de três formas: comportamentos sequenciais, comportamentos paralelos e comportamentos como uma máquina de estados. Estas três classes de comportamentos podem envolver vários tipos de sub-comportamentos para serem executados. Cabe ao desenvolver, na análise das regras de negócio verificar qual ou quais classes são melhores soluções para a sua aplicação. Como por exemplo, em aplicações standalone comportamentos sequenciais são viáveis; para aplicações distribuídas o melhor são comportamentos que executam de forma paralela e/ou como uma máquina de estados. Inclusive uma aplicação pode fazer uso de vários tipos de classes diferentes e de forma simultânea.