aluno: Francisco Germano
ano/sem: 2008/2o.
data do laboratório (num. da semana) : 10/10/2008 (10)
Introdução
O laboratório consistiu na criação num pacote em java que fosse capaz de representar as ações de uma máquina de estados determinística. A máquina de estados genérica podería efetuar comandos ao chegar no estado e ao ter um evento disparado. Pode acontecer também que a condição de guarda seja disparada e um deteminado evento não ative a transição de um estado para outro. Para se encerrar o lab ocorreram testes com um exemplo dado em sala, com uma máquina criada pelo aluno e com uma classe de testes fornecida.
Desenvolvimento
No pacote statemachine foram desenvolvidas 10 classes. Elas são as seguintes
Event
Classe que representa os eventos na máquina de estado. Cada evento possui um nome que o identifica unicamente, um HashMap que armazena as transicoes associadas a esse evento (a transicao nada mais é do que uma relação associada a esse evento entre um estado inicial e um estado final), uma variável para guardar os comandos do evento caso ele possua e outra variável que guarda a guarda do evento se ela existir.
class Event { String name; HashMap mapTransition; EventCommand command; EventGuard guard; Event(String name) { this.name = name; mapTransition = new HashMap<State, State>(); this.guard = null; } }
EventCommand
Classe abstrata que é utilizada na forma de classe anônima nesse laboratório. Sua função é armazenar o comando que deve ser executado quanto um determinado evento for acionado.
public abstract class EventCommand { protected StateMachine machine; public EventCommand(StateMachine machine) { this.machine = machine; } public abstract void execute(String previousState); }
EventGuard
Interface que também é implementada através de classes anônimas. Ela tem a função de implementar uma condição de guarda para o evento. Caso a condição de guarda seja verdadeira, não é possível alterar o estado da máquina com um determinado evento.
public interface EventGuard { boolean shouldPerform(); }
Observer
Interface que faz parte da implementação do padrão observer.
public interface Observer { void update(StateMachine sm, String newStateName); }
State
Classe que representa os estados da máquina de estados. Cada estado possui um nome que o identifica unicamente e um comando que será executdo quando a máquina entrar no estado, caso ele seja definido.
class State { public String name; public StateCommand command; State(String name, StateCommand command) { this.name = name; this.command = command; } }
StateCommand
Classe abstrata que foi utilizada no laboratório através de classes anônimas. Essa classe armazena o comando que deve ser executado quando a máquina de estados chega em um novo estado, esse comando nem sempre existirá.
public abstract class StateCommand { protected StateMachine machine; public abstract void execute(); public StateCommand(StateMachine machine) { this.machine = machine; } }
UndefinedEventException
Essa é uma extenção da classe RunTime exception, portanto ela pode lançar uma unchecked exception. Ela é utilizada quando o evento pelo qual o método chamou não existe.
public class UndefinedEventException extends RuntimeException{ } }
UndefinedStateException
Essa é uma extenção da classe RuntimeException, portanto ela pode lançar uma unchecked exception. Ela é utilizada quando o estado pelo qual o método chamou não existe.
public class UndefinedStateException extends RuntimeException { }
UndefinedTransitionException
Essa é uma extenção da classe Exception, portanto ela pode lançar uma exception. Ela é utilizada quando uma transição não definida é chamada. Isso pode ser verificado se for chamada um evento num estado no qual esse evento não gera efeito (transição inexistente).
public class UndefinedTransitionException extends Exception { }
Para o teste do exemplo dado em sala (representando o Professor e a turma) foram criadas as seguintes 5 classes:
Professor
Classe que representa o professor. Ela é subclasse da Classe WithSM (possuindo acesso aos métodos de uma máquina de estado) e é observadora da classe Turma. Vale a pena destacar que caso o professor esteja com um salário de milhões o GuardEvent é ativado e ele não pode mais ser desmotivado.Os eventos que afetam o professor e os estados pelos quais ele pode passar estão representados no diagrama de estados abaixo:
public class Professor extends WithSM implements statemachine.Observer, TurmaObserver { public Professor() { super(); setUpSM(); sm.registerObserver(this); } public String fazerProva() { if (getState() == "motivado") { return "Prova com noção"; } else if (getState() == "desmotivado" || getState() == "vacinado") { return "Prova ruim, correção carteada"; } else if (getState() == "irado") { return "Prova sem noção, média D"; } else { return "Prova que nunca será feita"; } } public String prepararAula() { if (getState() == "motivado") { return "Aula preparada com carinho (5 horas)"; } else if (getState() == "desmotivado" || getState() == "vacinado") { return "Aula preparada com pressa (1 hora)"; } else if (getState() == "irado") { return "Aula jogada (5 min)"; } else { return "Aula que nunca será feita"; } } private boolean ganhandoMilhoes = false; public boolean isGanhandoMilhoesDeDolares() { return ganhandoMilhoes; } public void setGanhandoMilhoesDeDolares(boolean is) { ganhandoMilhoes = is; } public void update(statemachine.StateMachine sm, String newStateName) { System.out.println("Meu estado agora é " + newStateName); } public void updateFromTurma(Turma t) { if (t.getState() == "prestando atenção" || t.getState() == "cancerizando nos labs") { this.fire("motivar"); } else { this.fire("desmotivar"); } String s= new String(); } private void setUpSM() { //criando estados sm.createState("motivado"); sm.createState("desmotivado"); sm.createState("vacinado"); sm.createState("irado", new statemachine.StateCommand(sm) { public void execute() { System.out.println("nunca mais serei o mesmo!"); } }); sm.setInitialState("motivado"); //evento desmotivar sm.createEvent("desmotivar", "motivado", "desmotivado", new statemachine.EventCommand(sm) { public void execute(String previousState) { System.out.println("Fiquei mais desmotivado!"); } }); String[] from = {"desmotivado", "irado", "vacinado"}; sm.appendTransitionToEvent("desmotivar", from, "irado"); sm.setGuardToEvent("desmotivar", new statemachine.EventGuard() { public boolean shouldPerform() { return !isGanhandoMilhoesDeDolares(); //nunca fica desmotivado se ganha milhoes } }); //evento motivar sm.createEvent("motivar", "desmotivado", "motivado", new statemachine.EventCommand(sm) { public void execute(String previousState) { System.out.println("Oba, fiquei mais motivado!"); } }); String[] from2 = {"irado", "vacinado"}; sm.appendTransitionToEvent("motivar", from2, "vacinado"); sm.appendTransitionToEvent("motivar", "motivado", "motivado"); } }
Turma
A classe Turma representa a turma de alunos que será observada pelo professor e é sub-classe de WithSM, implementando as funcionalidades da biblioteca de máquina de estado. A classe possui um método fire que além de executar a transição de estado avisa ao professor dessa transição (através do método notifyObservers). Os eventos estar_atenta e cancerizar motivam o professor, enquanto os eventos dormir e usar_note desmotivam o professor. O diagrama de estados abaixo representa todos os estados e as transições dessa máquina.
public class Turma extends WithSM { private Vector<TurmaObserver> observers = new Vector<TurmaObserver>(); public Turma() { super(); sm.createState("prestando atenção"); sm.createState("cancerizando nos labs"); sm.createState("dormindo"); sm.createState("usando notebook"); sm.setInitialState("dormindo"); String from[] = {"prestando atenção", "cancerizando nos labs", "dormindo", "usando notebook"}; sm.createEvent("dormir", from, "dormindo"); sm.createEvent("cancerizar", from, "cancerizando nos labs"); sm.createEvent("usar_note", from, "usando notebook"); sm.createEvent("ser_atenta", from, "prestando atenção"); } public void registerObserver(TurmaObserver observer) { observers.add(observer); } private void notifyObservers() { for (TurmaObserver obs : observers) { obs.updateFromTurma(this); } } @Override public void fire(String event) { super.fire(event); notifyObservers(); } }
TurmaObserver
Interface que serve para implementar o padrão observer entre as classes Turma e Professor.
public interface TurmaObserver { void updateFromTurma(Turma t); }
WithSM
Classe que cria uma Maquina de estado utilizando a classe StateMachine. No método fire ele faz a transicao dos eventos e trata as exceção caso seja chamado uma transição que não existe.
public class WithSM { protected StateMachine sm; public WithSM() { sm = new StateMachine(); } public void fire(String eventName){ try{ sm.fireEvent(eventName); } catch(UndefinedTransitionException e) { System.out.println("ERRO: trasição não definida: " + e.getMessage()); } } public String getState(){ return sm.getCurrentStateName(); } }
MainExemplos
A classe MainExemplos foi criada para implementarmos uma interface simples para testarmos o exemplo do professor-turma diretamente na linha de comando.
public class MainExemplos { /** * @param args the command line arguments */ public static void main(String[] args) { int m = 1, p, t, s; Scanner scan = new Scanner(System.in); Professor prof = new Professor(); Turma comp = new Turma(); comp.registerObserver(prof); do { System.out.println("1 - Acoes para o professor"); System.out.println("2 - Acoes para a turma"); System.out.println("0 - Sair"); do { m = scan.nextInt(); } while (m != 0 && m != 1 && m != 2); switch (m) { case 1: { System.out.println("\n1 - Preparar aula"); System.out.println("2 - Fazer prova"); System.out.println("3 - Ajustar salario"); do { p = scan.nextInt(); } while (p != 1 && p != 2 && p != 3); if (p == 1) { System.out.println(prof.prepararAula()+"\n"); } if (p == 2) { System.out.println(prof.fazerProva()+"\n"); } if (p == 3) { System.out.println("\n1 - Pagar salario normal"); System.out.println("2 - Pagar salario milionario"); do { s = scan.nextInt(); } while (s != 1 && s != 2); if (s == 1) { prof.setGanhandoMilhoesDeDolares(false); } if (s == 2) { prof.setGanhandoMilhoesDeDolares(true); } } break; } case 2: { System.out.println("\n1 - Prestar atencao na aula"); System.out.println("2 - Cancerizar no lab"); System.out.println("3 - Usar notebook"); System.out.println("4 - Dormir"); do { t = scan.nextInt(); } while (t != 1 && t != 2 && t != 3 && t != 4); if (t == 1) { comp.fire("ser_atenta"); } if (t == 2) { comp.fire("cancerizar"); } if (t == 3) { comp.fire("usar_note"); } if (t == 4) { comp.fire("dormir"); } break; } } } while (m != 0); } }
Vários testes foram realizados e todos foram bem sucedidos. Abaixo seguem algumas telas de testes manuais.
Lançando foguete
Na sequencia foi solicitado a criacao de uma maquina de estados sugerida pelo aluno para testar o programa. A máquina de estados proposta foi baseada na seguinte contextualização:
Para a FAB a criação de foguetes controlados a distância para serem utilizados como armas é uma nova prioridade. Um jovem engenheiro da computação formado no ITA, que se preocuoa com o desenvolvimento tecnológico da nação, foi trabalhar nesse projeto e contribui criando um novo software para o motor do foguete e para controlá-lo remotamente.
Ele partiu da simples idéia de uma máquina de estado na qual os estágios do motor seriam acionados progressivamente no lançamento e desligados na ordem reversa quando o foguete atingir a altitude desejada.
Um processo de segurança também foi criado pelo engenheiro, para evitar acidentes. A qualquer momento que um erro for detectado, o trabalho dos motores do foguete é interrompido para que explosões sejam previnidas.
As ações do foguete que podem ser mudar de direção, danificar o tanque de combustível (caso controle do foguete seja obtido por uma força inimíga), utilizar nitro e inicializar um processo controlado de autodestruição (caso algum problema no motor seja identificado ou se o foguete for cair em um local errado, por exemplo).
Os diagramas de estados abaixo detalham as idéias do iteano (diagrama do motor e do foguete respectivamente).
Para que a máquina de estados funciona-se e fosse testada foram criadas 5 classes (Foguete, MotorFoguete, MotorObserver, WithSM e MainFoguete). Segue a descricao de cada uma das classes (detalhe para a utilização do padrão observer).
Foguete
Classe que representa o foguete.
public class Foguete extends WithSM implements statemachine.Observer, MotorObserver { float velocidade;//A velocidade sera medida em numeros de Mach int dirigibilidade;//Capacidade do foguete de fazer curvas rapidamente boolean fuel;//Booleano que se mantem verdadeiro enquanto o foguete possuir combustivel public Foguete() { super(); setUpSM(); sm.registerObserver(this); velocidade = 0; dirigibilidade = 0; fuel = true; } //@SuppressWarnings("empty-statement") private void setUpSM() { sm.createState("modoPlanar", new statemachine.StateCommand(sm) { public void execute() { System.out.println("O motor esta desligado e o foguete mantem a velocidade minima para voar"); } }); sm.createState("modoCruzeiro", new statemachine.StateCommand(sm) { public void execute() { System.out.println("O foguete esta viajando na sua velocidade ideal."); } }); sm.createState("autoDestruicao", new statemachine.StateCommand(sm) { public void execute() { int i; char c; System.out.println("O foguete explodira em:"); for (i = 5; i >= 0; i--) { try { System.out.println(i); Thread.sleep(1000); } catch (InterruptedException ex) { Logger.getLogger(Foguete.class.getName()).log(Level.SEVERE, null, ex); } } System.out.println("BOOOOOMMM!!!!"); } }); sm.createState("modoRapido", new statemachine.StateCommand(sm) { public void execute() { System.out.println("O foguete operando em modo acelerado."); } }); sm.createState("modoTurbo", new statemachine.StateCommand(sm) { public void execute() { System.out.println("O foguete esta no limite suportavel. CUIDADO!!!"); } }); sm.createState("parado"); sm.setInitialState("parado"); sm.createEvent("Acelerar", "parado", "modoCruzeiro", new statemachine.EventCommand(sm) { @Override public void execute(String previousState) { System.out.println("ACELERA!!"); previousState = sm.getCurrentStateName(); if (previousState.equals("parado")) { System.out.println("Dada a partida no foguete."); } if (previousState.equals("modoTurbo")) { System.out.println("Houston, we have a problem!"); } } }); sm.appendTransitionToEvent("Acelerar", "modoPlanar", "modoCruzeiro"); sm.appendTransitionToEvent("Acelerar", "modoCruzeiro", "modoRapido"); sm.appendTransitionToEvent("Acelerar", "modoRapido", "modoTurbo"); sm.appendTransitionToEvent("Acelerar", "modoTurbo", "autoDestruicao"); sm.createEvent("Desacelerar", "modoTurbo", "modoRapido", new statemachine.EventCommand(sm) { @Override public void execute(String previousState) { System.out.println("Desacelerando!"); } }); sm.appendTransitionToEvent("Desacelerar", "modoRapido", "modoCruzeiro"); sm.appendTransitionToEvent("Desacelerar", "modoCruzeiro", "modoPlanar"); sm.appendTransitionToEvent("Desacelerar", "modoPlanar", "modoPlanar"); String from[] = {"modoPlanar", "modoCruzeiro", "modoRapido", "modoTurbo"}; sm.createEvent("Desintegrar", from, "autoDestruicao"); sm.setGuardToEvent("Acelerar", new statemachine.EventGuard() { public boolean shouldPerform() { return isThereFuel(); //nao e possivel acelerar caso nao exista combustivel no tanque } }); } public void update(StateMachine sm, String newStateName) { System.out.println("Meu estado agora é " + newStateName); } public void updateFromMotor(MotorFoguete mf, String mens) { if (mens.equals("Abortar")) { this.fire("Desintegrar"); } else { String str = mf.getState(); if (str.equals("desligado")) { this.fire("Desacelerar"); this.velocidade = 1; this.dirigibilidade = 3; } else if (str.equals("estagio1")) { if (mens.equals("Ligar")) { this.fire("Acelerar"); if (this.fuel == true) { this.velocidade = 5; this.dirigibilidade = 2; } } if (mens.equals("Desligar")) { this.fire("Desacelerar"); this.velocidade = 5; this.dirigibilidade = 2; } } else if (str.equals("estagio2")) { if (mens.equals("Ligar")) { this.fire("Acelerar"); if (this.fuel == true) { this.velocidade = 10; this.dirigibilidade = 1; } } if (mens.equals("Desligar")) { this.fire("Desacelerar"); this.velocidade = 10; this.dirigibilidade = 1; } } else if (str.equals("estagio3")) { this.fire("Acelerar"); if (this.fuel == true) { this.velocidade = 30; this.dirigibilidade = 0; } } this.statusFoguete(); } } public void statusFoguete() { System.out.println("\nO foguete esta no modo " + this.getState() + " e o seu status e:"); System.out.println("Velocidade de " + this.velocidade + "mach."); System.out.println("Dirigibilidade nivel " + this.dirigibilidade + "\n"); } public void virar45(String direcao) { if (dirigibilidade >= 1) { System.out.println("Curva de 45 graus para " + direcao); } else { System.out.println("Nao e possivel fazer a curva com a atual velocidade do foguete"); this.statusFoguete(); } } public void virar90(String direcao) { if (dirigibilidade >= 2) { System.out.println("Curva de 90 graus para " + direcao); } else { System.out.println("Nao e possivel fazer a curva com a atual velocidade do foguete"); this.statusFoguete(); } } public void virar180() { if (dirigibilidade >= 3) { System.out.println("Giro de 180 graus."); } else { System.out.println("Nao e possivel fazer a curva com a atual velocidade do foguete"); this.statusFoguete(); } } public void nitro() { int i, j; System.out.println("Nitro faz com que o foguete tenha a velocidade aumentada sem que um novo estagio do motor seja acionado"); System.out.println("Tem certeza que quer utilizar nitro?\n1-Sim\n2-Nao"); Scanner scan = new Scanner(System.in); do { i = scan.nextInt(); } while (i != 1 && i != 2); if (i == 1) { System.out.println("OOOOWWWW! Nitro is activated for 10 seconds!"); this.fire("Acelerar"); if (!this.getState().equals("autoDestruicao")) { for (j = 0; j < 10; j++) { try { System.out.println(j); Thread.sleep(1000); } catch (InterruptedException ex) { Logger.getLogger(Foguete.class.getName()).log(Level.SEVERE, null, ex); } } this.fire("Desacelerar"); if (this.getState().equals("modoPlanar")) { this.velocidade = 1; this.dirigibilidade = 3; } } } } public void setFuel(boolean is) { this.fuel = is; } public boolean isThereFuel() { return this.fuel; } }
WithSM
Classe identica a utilizada no exemplo anterior. Ela é super classe de Foguete e de MotorFoguete.
public class WithSM { protected StateMachine sm; public WithSM() { sm = new StateMachine(); } public void fire(String eventName){ try{ sm.fireEvent(eventName); } catch(UndefinedTransitionException e) { System.out.println("ERRO: trasição não definida: " + e.getMessage()); } } public String getState(){ return sm.getCurrentStateName(); } }
MotorFoguete
Classe que representa o motor do foguete. Ela será observada pelo foguete.
public class MotorFoguete extends WithSM { private Vector<MotorObserver> observers = new Vector<MotorObserver>(); boolean fuel; public MotorFoguete() { super(); fuel=true; sm.createState("desligado"); sm.createState("estagio1"); sm.createState("estagio2"); sm.createState("estagio3"); sm.setInitialState("desligado"); String from[]= {"desligado","estagio1", "estagio2", "estagio3"}; sm.createEvent("Abortar", from, "desligado"); sm.createEvent("Ligar", "desligado", "estagio1"); sm.appendTransitionToEvent("Ligar", "estagio1", "estagio2"); sm.appendTransitionToEvent("Ligar", "estagio2", "estagio3"); sm.appendTransitionToEvent("Ligar", "estagio3", "estagio3"); sm.setGuardToEvent("Ligar", new statemachine.EventGuard() { public boolean shouldPerform() { return isThereFuel(); //nao e possivel ligar o motor caso nao exista combustivel no tanque } }); sm.createEvent("Desligar", "estagio1", "desligado"); sm.appendTransitionToEvent("Desligar", "estagio2", "estagio1"); sm.appendTransitionToEvent("Desligar", "estagio3", "estagio2"); sm.appendTransitionToEvent("Desligar", "desligado", "desligado"); } public void setFuel(boolean is){ fuel=is; } public boolean isThereFuel() { return this.fuel; } public void registerObserver(MotorObserver observer) { observers.add(observer); } private void notifyObservers(String mens) { for (MotorObserver obs : observers) { obs.updateFromMotor(this,mens); } } @Override public void fire(String event) { super.fire(event); notifyObservers(event); } }
MotorObserver
Interface para implementar o método observer entre o motor e o foguete.
public interface MotorObserver { void updateFromMotor(MotorFoguete mf, String mens); }
MainFoguete
Classe Main criada para testes manuais das classes via linha de comando
public class MainFoguete { /** * @param args the command line arguments */ public static void main(String[] args) { // TODO code application logic here int s, f, mf, c, i; Scanner scan = new Scanner(System.in); Foguete fog = new Foguete(); MotorFoguete mfog = new MotorFoguete(); mfog.registerObserver(fog); do { System.out.println("1 - Ordens para o foguete"); System.out.println("2 - Ordens para o motor do foguete"); System.out.println("0 - Sair"); do { s = scan.nextInt(); } while (s != 0 && s != 1 && s != 2); switch (s) { case 1: { System.out.println("\n1 - Virar"); System.out.println("2 - Usar nitro"); System.out.println("3 - Alterar tanque de combustivel"); System.out.println("4 - Iniciar sistema de autodestruicao do foguete"); System.out.println("5 - Verificar status do foguete"); do { f = scan.nextInt(); } while (f != 1 && f != 2 && f != 3 && f != 4 && f != 5); if (f == 1) { System.out.println("1 - Tentar virar 45 graus."); System.out.println("2 - Tentar virar 90 graus."); System.out.println("3 - Tentar virar 180 graus."); do { c = scan.nextInt(); } while (c != 1 && c != 2 && c != 3); if (c == 1 || c == 2) { System.out.println("1 - Virar para direita"); System.out.println("2 - Virar para esquerda"); do { i = scan.nextInt(); } while (i != 1 && i != 2); if (c == 1) { if (i == 1) { fog.virar45("direita"); } if (i == 2) { fog.virar45("esquerda"); } } if (c == 2) { if (i == 1) { fog.virar90("direita"); } if (i == 2) { fog.virar90("esquerda"); } } } if (c == 3) { fog.virar180(); } } if (f == 2) { fog.nitro(); } if (f == 3) { System.out.println("\nVoce gostaria de perfurar o tanque do foguete?\n Lembre-se que isso causara danos irreversiveis e o foguete nunca mais podera acelerar"); System.out.println("1 - Sim"); System.out.println("2 - Nao"); do { c = scan.nextInt(); } while (c != 1 && c != 2); if (c == 1) { fog.setFuel(false); mfog.setFuel(false); } } if (f == 4) { fog.fire("Desintegrar"); } if (f == 5) { fog.statusFoguete(); } break; } case 2: { String str = mfog.getState(); System.out.println("O motor esta no estado " + mfog.getState()); System.out.println("\n1 - Abortar funcionamento do motor"); if (str.equals("estagio1") || str.equals("estagio2")) { System.out.println("2 - Ligar proximo estagio do motor"); System.out.println("3 - Desligar um estagio do motor"); do { mf = scan.nextInt(); } while (mf != 1 && mf != 2 && mf != 3); if (mf == 1) { mfog.fire("Abortar"); } if (mf == 2) { mfog.fire("Ligar"); } if (mf == 3) { mfog.fire("Desligar"); } } if (str.equals("desligado")) { System.out.println("2 - Ligar o motor"); do { mf = scan.nextInt(); } while (mf != 1 && mf != 2); if (mf == 1) { mfog.fire("Abortar"); } if (mf == 2) { mfog.fire("Ligar"); } } if (str.equals("estagio3")) { System.out.println("2 - Desligar um estagio do motor"); do { mf = scan.nextInt(); } while (mf != 1 && mf != 2); if (mf == 1) { mfog.fire("Abortar"); } if (mf == 2) { mfog.fire("Desligar"); } } break; } } if (fog.getState().equals("autoDestruicao")) { s = 0; } } while (s != 0); } }
Foram executados diversos testes manuais e todos foram bem sucedidos. Seguem alguns dos testes executados:
Para encerrar o laboratório foi criado um teste na Classe Test.
Test
Todos os testes propostos foram bem sucedidos comprovando o bom funcionamento do pacote statemachine.
public class Teste { public Teste() { } @Test public void testStateMachineSimple() { StateMachine sm = new StateMachine(); sm.createState("feliz"); sm.createState("triste"); sm.setInitialState("triste"); assertEquals("triste", sm.getCurrentStateName()); sm.createEvent("jogar_bola", "triste", "feliz"); try { sm.fireEvent("jogar_bola"); } catch (UndefinedTransitionException e) { fail("não deve gerar excessão"); } assertEquals("feliz", sm.getCurrentStateName()); try { sm.fireEvent("jogar_bola"); fail("deve gerar excessão"); } catch (UndefinedTransitionException e) { } } @Test public void testArrayInFrom() { StateMachine sm = new StateMachine(); sm.createState("feliz"); sm.createState("triste"); sm.createState("deprimido"); sm.setInitialState("feliz"); assertEquals("feliz", sm.getCurrentStateName()); String[] from = {"triste", "feliz"}; sm.createEvent("perder_mulher", from, "deprimido"); try { sm.fireEvent("perder_mulher"); } catch (UndefinedTransitionException e) { fail("não deve gerar excessão"); } assertEquals("deprimido", sm.getCurrentStateName()); try { sm.fireEvent("perder_mulher"); fail("deve gerar excessão"); } catch (UndefinedTransitionException e) { } sm = new StateMachine(); //outra maquina sm.createState("feliz"); sm.createState("triste"); sm.createState("deprimido"); sm.setInitialState("triste"); assertEquals("triste", sm.getCurrentStateName()); String[] from2 = {"triste", "feliz"}; sm.createEvent("perder_mulher", from2, "deprimido"); try { sm.fireEvent("perder_mulher"); } catch (UndefinedTransitionException e) { fail("não deve gerar excessão"); } assertEquals("deprimido", sm.getCurrentStateName()); try { sm.fireEvent("perder_mulher"); fail("deve gerar excessão"); } catch (UndefinedTransitionException e) { } } @Test public void testMultipleTransitions() { StateMachine sm = new StateMachine(); sm.createState("super_feliz"); sm.createState("feliz"); sm.createState("triste"); sm.createState("deprimido"); sm.setInitialState("super_feliz"); String[] from = {"triste", "feliz"}; sm.createEvent("perder_mulher", from, "deprimido"); sm.appendTransitionToEvent("perder_mulher", "super_feliz", "triste"); try { sm.fireEvent("perder_mulher"); } catch (UndefinedTransitionException e) { fail("não deve gerar excessão"); } assertEquals("triste", sm.getCurrentStateName()); try { sm.fireEvent("perder_mulher"); } catch (UndefinedTransitionException e) { fail("não deve gerar excessão"); } assertEquals("deprimido", sm.getCurrentStateName()); try { sm.fireEvent("perder_mulher"); fail("deve gerar excessão"); } catch (UndefinedTransitionException e) { } } @Test public void testStateCommand() { StateMachine sm = new StateMachine(); sm.createState("super_feliz"); sm.createState("feliz"); sm.createState("triste"); sm.createState("deprimido"); sm.setInitialState("super_feliz"); sm.createEvent("perder_mulher", "super_feliz", "triste", new EventCommand(sm) { public void execute(String previousState) { System.out.println("Eu estou chorando!!! Não estou mais " + previousState + ", agora estou " + this.machine.getCurrentStateName()); } }); try { sm.fireEvent("perder_mulher"); } catch (UndefinedTransitionException e) { } } @Test public void testEventCommand() { StateMachine sm = new StateMachine(); boolean called = false; StateCommand avisar = new StateCommand(sm) { public void execute() { System.out.println("Agora eu fiquei " + this.machine.getCurrentStateName()); } }; sm.createState("super_feliz"); sm.createState("feliz", avisar); sm.createState("triste", avisar); sm.createState("deprimido", avisar); sm.setInitialState("super_feliz"); sm.createEvent("perder_mulher", "super_feliz", "triste"); try { sm.fireEvent("perder_mulher"); } catch (UndefinedTransitionException e) { } } @Test public void testGuard() { temSuperPoderes = true; StateMachine sm = new StateMachine(); sm.createState("super_feliz"); sm.createState("feliz"); sm.createState("triste"); sm.createState("deprimido"); sm.setInitialState("super_feliz"); sm.createEvent("perder_mulher", "super_feliz", "triste"); sm.setGuardToEvent("perder_mulher", new EventGuard() { public boolean shouldPerform() { return !temSuperPoderes; } }); try { assertFalse(sm.fireEvent("perder_mulher")); } catch (UndefinedTransitionException e) { } temSuperPoderes = false; try { assertTrue(sm.fireEvent("perder_mulher")); } catch (UndefinedTransitionException e) { fail("não deve gerar excessão"); } } private boolean temSuperPoderes = false; public boolean getTemSuperPoderes() { return temSuperPoderes; } @Test public void testShouldReturnErrorIfEventDoesntExist() { StateMachine sm = new StateMachine(); sm.createState("feliz"); sm.createState("triste"); sm.setInitialState("triste"); assertEquals("triste", sm.getCurrentStateName()); sm.createEvent("jogar_bola", "triste", "estado_nao_existe"); try { sm.fireEvent("evento_nao_existente"); fail("deve gerar excessão"); } catch (UndefinedTransitionException e) { } catch (UndefinedEventException e) { } } @Test public void testShouldReturnErrorIfStateDoesntExist() { StateMachine sm = new StateMachine(); sm.createState("feliz"); sm.createState("triste"); sm.setInitialState("triste"); assertEquals("triste", sm.getCurrentStateName()); sm.createEvent("jogar_bola", "triste", "estado_nao_existe"); try { sm.fireEvent("jogar_bola"); fail("deve gerar excessão"); } catch (UndefinedStateException e) { } catch (UndefinedTransitionException e) { } } @BeforeClass public static void setUpClass() throws Exception { } @AfterClass public static void tearDownClass() throws Exception { } @Before public void setUp() { } @After public void tearDown() { } // TODO add test methods here. // The methods must be annotated with annotation @Test. For example: // // @Test // public void hello() {} }
Segue abaixo a tela com os testes executados.
A seguir estao os diagramas de classes do pacote statemachine, do pacote exemplo (contendo as classes professor e turma) e do pacote foguete.
Conclusão
O lab se mostrou bem trabalhoso e com uma grande variedade de detalhes (forte encapsulamento, classes anônimas, unchecked exceptions, padrão observer e etc.). Essas características levaram a um bom aprendizado dos novos conceitos abordados, tornando possível que nos próximos laboratórios outras características de java e de OO possam ser aprofundados. Um ponto negativo da atividade foi a grande quantidade de testes (foi testado o exemplo do professor-turma, um exemplo criado pelo aluno e uma classe de testes). Destaque para a dificuldade do teste que pedia a criação de uma máquina de estados, o que pode ser difícil para os graduandos com menor criatividade. Em suma a idéia do lab foi muito boa mas alguns pontos devem ser enchugados para não causar um trabalho repetitivo.