(→Serialização) |
(→Interacção com o utilizador) |
||
Line 138: | Line 138: | ||
As excepções usadas na interacção, excepto se indicado, são subclasses de '''pt.tecnico.po.ui.DialogException''', são lançadas pelos comandos e tratadas por '''pt.tecnico.po.ui.Menu'''. Outras excepções não devem substituir as fornecidas nos casos descritos. | As excepções usadas na interacção, excepto se indicado, são subclasses de '''pt.tecnico.po.ui.DialogException''', são lançadas pelos comandos e tratadas por '''pt.tecnico.po.ui.Menu'''. Outras excepções não devem substituir as fornecidas nos casos descritos. | ||
− | {{CVSCode|Note-se que o programa principal e os comandos e menus, a seguir descritos, já estão implementados nas ''packages'' '''mmt.app''', '''mmt.app.main''', '''mmt.app.service''', '''mmt.app.passenger''' e '''mmt.app.itinerary'''. Estas classes são de uso obrigatório e estão disponíveis no [[Programação com Objectos/Projecto de Programação com Objectos/Repositório CVS|CVS]] (módulo '''mmt-app''').}} | + | {{CVSCode|Note-se que o programa principal e os comandos e menus, a seguir descritos, já estão parcialmente implementados nas ''packages'' '''mmt.app''', '''mmt.app.main''', '''mmt.app.service''', '''mmt.app.passenger''' e '''mmt.app.itinerary'''. Estas classes são de uso obrigatório e estão disponíveis no [[Programação com Objectos/Projecto de Programação com Objectos/Repositório CVS|CVS]] (módulo '''mmt-app''').}} |
== Menu Principal == | == Menu Principal == |
AVISOS - Avaliação em Época Normal |
---|
Esclarecimento de dúvidas:
|
Requisitos para desenvolvimento, material de apoio e actualizações do enunciado (ver informação completa em Projecto de Programação com Objectos):
|
Processo de avaliação (ver informação completa em Avaliação do Projecto):
|
Material de Uso Obrigatório |
---|
As bibliotecas po-uuilib e o conteúdo inicial do CVS são de uso obrigatório: |
|
A máquina virtual, fornecida para desenvolvimento do projecto, já contém todo o material de apoio. |
Uso Obrigatório: Repositório CVS |
Apenas se consideram para avaliação os projectos existentes no repositório CVS oficial.
Trabalhos não presentes no repositório no final do prazo têm classificação 0 (zero) (não são aceites outras formas de entrega). Não são admitidas justificações para atrasos em sincronizações do repositório. A indisponibilidade temporária do repositório, desde que inferior a 24 horas, não justifica atrasos na submissão de um trabalho. |
O objectivo do projecto é criar uma aplicação que gere os itinerários de passageiros que usam comboios.
Os itinerários são constituídos por um ou mais segmentos, realizados por comboios que cumprem horários pré-definidos.
Neste texto, o tipo negrito indica um literal (i.e., é exactamente como apresentado); o símbolo indica um espaço; e o tipo itálico indica uma parte variável (i.e., uma descrição).
Existem três conceitos básicos: serviços, passageiros e itinerários.
Um serviço é um percurso realizado por um comboio ao longo de várias estações. A cada estação de um serviço está associado o momento de partida do comboio e um dado número de vagas diárias. Assume-se que todos os serviços se realizam em todos os dias. Além das informações anteriores, o serviço tem ainda um identificador numérico único (definido na criação do serviço).
Exemplo de horário: Serviço 850: Valença >> Porto - Campanhã |
---|
Serviço #850 - 10.95 07:36 Valença 07:47 Vila Nova de Cerveira 07:56 Caminha 08:03 Âncora Praia 08:16 Viana do Castelo 08:20 Areia-Darque 08:30 Barroselas 08:37 Tamel 08:53 Barcelos 09:05 Nine 09:13 Famalicão 09:21 Trofa 09:35 Ermesinde 09:45 Porto - Campanhã |
Cada segmento de um serviço tem um custo base, que depende da distância entre estações. A distância entre estações corresponde ao tempo gasto na viagem entre essas estações. Assim, considerando que a totalidade do custo do serviço 850 (no exemplo acima) é de €10.95 (para 129 minutos), o custo do segmento Viana do Castelo >> Nine (para 49 minutos) é de 10.95 * 49 / 129 = €4.16.
O preço pago pelo passageiro depende ainda da sua categoria, que pode ser normal ou outra (a adição de novas categorias deve ter impacto reduzido no código da aplicação).
Um passageiro tem um identificador único (número inteiro atribuído automaticamente, de forma sequencial) e um nome (cadeia de caracteres).
O identificador único é atribuído automaticamente no momento do registo do passageiro, sendo atribuído o identificador 0 (zero) ao primeiro passageiro.
Os passageiros são categorizados como normais, frequentes ou especiais. Um passageiro é considerado frequente enquanto o valor dos últimos 10 itinerários for superior a €250. Volta a ser normal se esta condição deixar de se verificar. Um cliente é considerado especial enquanto tiver gasto mais de €2500 nos últimos 10 itinerários. Deixa de ser especial se esta condição deixar de se verificar. Se se verificarem simultâneamente as condições para ser um cliente frequente e especial, o cliente é considerado especial. Aplicam-se as seguintes condições para atribuição de descontos em itinerários: passageiros normais, 0%; passageiros frequentes, 15%; passageiros especiais, 50%.
Um itinerário corresponde a uma viagem realizada num dado dia, com base num conjunto de segmentos lógicos, correspondentes a partes de serviços, i.e., partidas (ou chegadas) agendadas para cada estação. É possível saber, através do serviço associado, qual o percurso a realizar nesse segmento.
O custo base do itinerário é a soma dos custos parciais de cada serviço que o constitui, correspondentes aos segmentos realizados nesses serviços. Dependendo da categoria do cliente, podem ser realizados descontos.
Exemplo de itinerário: Évora >> Loulé |
---|
Itinerário para 2017-10-18 - Évora >> Loulé Serviço #690 - 6.94 07:06 Évora 07:17 Casa Branca 07:31 Vendas Novas 07:53 Pinhal Novo Serviço #180 - 28.28 09:06 Pinhal Novo 10:54 Tunes 11:01 Albufeira - Ferreiras 11:13 Loulé |
A aplicação permite manter informação sobre serviços. Permite ainda registar e gerir passageiros e criar novos itinerários para eles. Possui ainda a capacidade de preservar o seu estado (não é possível manter várias versões do estado da aplicação em simultâneo).
A base de dados de serviços é carregada no início da aplicação, constituindo um grafo lógico. É possível fazer várias consultas sobre serviços (ver abaixo).
Não é possível adicionar ou remover serviços durante a execução da aplicação.
Deve ser possível adicionar novas consultas sobre serviços sem impacto no código já realizado para a aplicação.
A aplicação permite realizar várias operações sobre passageiros. É possível obter a lista completa de passageiros conhecidos, assim como informação detalhada sobre passageiros individuais. É ainda possível registar novos passageiros e alterar o nome dos passageiros.
Os passageiros podem estar activos (podem adquirir itinerários) ou inactivos (não podem adquirir itinerários).
Deve ser possível adicionar novas consultas sobre passageiros sem impacto no código já realizado para a aplicação.
A aplicação permite obter informações sobre todos os itinerários já comprados ou, em pormenor, sobre os itinerários de passageiros específicos. Permite ainda criar novos itinerários.
Deve ser possível adicionar novas consultas sobre itinerários sem impacto no código já realizado para a aplicação.
É possível reiniciar (ou seja, mantendo a informação sobre serviços, eliminar a informação sobre passageiros e itinerários), guardar e recuperar o estado actual da aplicação, preservando todos a informação sobre serviços, passageiros e itinerários.
Descreve-se nesta secção a funcionalidade máxima da interface com o utilizador. Em geral, os comandos pedem toda a informação antes de proceder à sua validação (excepto onde indicado). Todos os menus têm automaticamente a opção Sair (fecha o menu).
As operações de pedido e apresentação de informação ao utilizador devem realizar-se através dos objectos form e display, respectivamente, presentes em cada comando. As mensagens são produzidas pelos métodos das bibliotecas de suporte (po-uuilib e mmt-app). As mensagens não podem ser usadas no núcleo da aplicação (mmt-core). Além disso, não podem ser definidas novas. Potenciais omissões devem ser esclarecidas antes de qualquer implementação.
As excepções usadas na interacção, excepto se indicado, são subclasses de pt.tecnico.po.ui.DialogException, são lançadas pelos comandos e tratadas por pt.tecnico.po.ui.Menu. Outras excepções não devem substituir as fornecidas nos casos descritos.
As acções do menu permitem gerir a salvaguarda do estado da aplicação e abrir submenus. A lista completa é a seguinte: Reiniciar, Abrir, Guardar e Consulta de Horários, Manipulação de Passageiros e Manipulação de Itinerários. Inicialmente, a aplicação apenas tem informação sobre os serviços que foram carregados no arranque.
As etiquetas das opções deste menu estão definidas na classe mmt.app.main.Label. Todos os métodos correspondentes às mensagens de diálogo para este menu estão definidos na classe mmt.app.main.Message.
O conteúdo da aplicação (inclui todos os serviços, passageiros e itinerários actualmente em memória) pode ser guardado para posterior recuperação (via serialização Java: java.io.Serializable). Na leitura e escrita do estado da aplicação, devem ser tratadas as excepções associadas. A funcionalidade é a seguinte:
As opções Reiniciar e Abrir substituem a informação na aplicação nas condições indicadas acima.
A opção Sair nunca guarda o estado da aplicação, mesmo que existam alterações.
Este menu permite efectuar consultas sobre a base de dados de serviços. A lista completa é a seguinte: Mostrar todos os serviços, Mostrar serviço com um dado número, Mostrar serviços com origem numa estação dada, Mostrar serviços com término numa estação dada.
As etiquetas das opções deste menu estão definidas na classe mmt.app.service.Label. Todos os métodos correspondentes às mensagens de diálogo para este menu estão definidos na classe mmt.app.service.Message.
Sempre que for pedido o identificador de um serviço (requestServiceId()) e o serviço não existir, é lançada a excepção mmt.app.NoSuchServiceException. Sempre que for pedido o nome de uma estção (requestStationName()) e a estação não existir, é lançada a excepção mmt.app.NoSuchStationException.
Apresenta informações sobre todos os serviços conhecidos. A lista é ordenada pelo identificador do serviço e o formato é o descrito em Mostrar serviço com um dado número.
Apresenta informações sobre um serviço. Depois de pedir o identificador do serviço (requestServiceId()), apresenta a informação sobre o serviço, tal como indicado no formato seguinte, para um serviço com N paragens (HH;MM representa o tempo em horas e minutos). O preço deve ser apresentado com duas casas decimais.
Formato de apresentação de serviço |
---|
Serviço #número-do-serviço - preço HH:MM nome-de-estação-1 ... HH:MM nome-de-estação-N |
Exemplo de apresentação de serviço: Serviço 180: Porto >> Faro |
---|
<text> Serviço #180 - 51.50 05:47 Porto - Campanhã 05:52 Vila Nova de Gaia-Devesas 06:21 Aveiro 06:46 Coimbra-B 08:23 Lisboa - Oriente 08:31 Lisboa - Entrecampos 09:06 Pinhal Novo 10:54 Tunes 11:01 Albufeira - Ferreiras 11:13 Loulé 11:23 Faro </text> |
Apresenta os números e estações terminais dos serviços com origem na estação indicada como resposta a requestStationName(), ordenado por hora de partida do serviço.
O formato de apresentação é o descrito em Mostrar serviço com um dado número.
Apresenta os números e estações de origem dos serviços com término na estação indicada como resposta a requestStationName(), ordenado por hora de chegada do serviço.
O formato de apresentação é o descrito em Mostrar serviço com um dado número.
Este menu permite efectuar operações sobre um passageiro. A lista completa é a seguinte: Mostrar passageiros, Mostrar passageiro, Registar passageiro, Alterar nome de passageiro, Suspender passageiro, Activar passageiro.
As etiquetas das opções deste menu estão definidas na classe mmt.app.passenger.Label. Todos os métodos correspondentes às mensagens de diálogo para este menu estão definidos na classe mmt.app.passenger.Message.
Sempre que for pedido o identificador de um passageiro (requestPassengerId()) e o passageiro não existir, é lançada a excepção mmt.app.NoSuchPassengerException.
Apresenta informações sobre todos os passageiros conhecidos. A lista é ordenada pelo identificador e o formato é o descrito em Mostrar passageiro.
Apresenta informações sobre um passageiro. Depois de pedir o identificador do passageiro (requestPassengerId()), apresenta o nome, o tipo de passageiro (NORMAL, FREQUENTE, ESPECIAL), o número de itinerários adquiridos e o valor pago por esses itinerários (o valor pago deve ser apresentado com duas casas decimais). É ainda indicado se o passageiro está activo (ACTIVO) ou inactivo (INACTIVO).
Formato:
id | nome | tipo | número-de-itinerários | valor-pago | estado
Exemplo:
1234|John Doe|NORMAL|123|9876.54|ACTIVO
Permite registar um passageiro no sistema. É pedido o nome do passageiro (requestPassengerName()). O novo passageiro é do tipo Normal.
Permite alterar o nome do passageiro. Para tal, pede-se o identificador do passageiro cujo nome deve ser alterado (requestPassengerId()) e o novo nome (requestPassengerName()).
Permite suspender (temporariamente) o passageiro indicado através do identificador pedido (requestPassengerId()). A suspensão de um passageiro apenas impede que reserve itinerários, mas não elimina os seus itinerários nem restante informação, sendo possível continuar a fazer consultas,
Permite activar o passageiro indicado através do identificador pedido (requestPassengerId()). O passageiro volta a poder adquirir itinerários.
Este menu permite efectuar operações sobre itinerários. A lista completa é a seguinte: Mostrar todos os itinerários, Mostrar itinerários associados a um passageiro, Registar itinerário para um passageiro.
As etiquetas das opções deste menu estão definidas na classe mmt.app.itinerary.Label. Todos os métodos correspondentes às mensagens de diálogo para este menu estão definidos na classe mmt.app.itinerary.Message.
Apresenta todos os itinerários registados para todos os passageiros. O formato de apresentação é como para os serviços, mas apenas se apresenta o segmento viajado e a classe seleccionada no segmento e o respectivo custo. A lista é ordenada pelo número de passageiro e, para cada passageiro, pela estação de oriegem do itinerário.
O formato de apresentação é como definido para a apresentação dos itinerários de um passageiro, mas apresenta-se a informação relativamente a todos os passageiros.
Apresenta os itinerários do passageiro cujo identificador é pedido através de requestPassengerId(). A lista é ordenada pela data dos itinerários.
O formato de apresentação é análogo ao dos serviços, iniciado com uma linha com o identificador do passageiro, à qual se segue a lista de itinerários, cada um precedido da linha com o número de ordem do itinerário (I representa o número) e da data correspondente:
== Passageiro P == Itinerário I para data
Exemplo de apresentação de itinerários para um passageiro |
---|
<text> Passageiro 2Itinerário 1 para 2017-10-18 Serviço #692 - 5.23 09:06 Évora 09:17 Casa Branca 09:31 Vendas Novas 09:53 Pinhal Novo Serviço #570 - 9.78 10:48 Pinhal Novo 11:19 Grândola 11:33 Ermidas-Sado 11:55 Funcheira 12:24 Santa Clara-Sabóia 12:52 Messines-Alte 13:02 Tunes Itinerário 2 para 2017-10-25 Serviço #184 - 5.15 15:35 Tunes 17:23 Pinhal Novo Serviço #596 - 5.23 17:48 Pinhal Novo 18:10 Vendas Novas 18:24 Casa Branca 18:35 Évora </text> |
Esta acção permite registar um novo itinerário para um passageiro.
Para tal, pede-se o identificador do passageiro (requestPassengerId()), a nome da estação de partida (requestDepartureStationName()), o nome da estação de destino (requestArrivalStationName()), a data da partida, no formato YYYY-MM-DD (requestDepartureDate()), e a hora mínima para a partida, no formato HH:MM (requestDepartureTime()).
Se a data não estiver num formato correcto, é lançada a excepção mmt.app.BadDateException, não sendo realizada nenhuma acção.
Se a hora não estiver num formato correcto, é lançada a excepção mmt.app.BadTimeException, não sendo realizada nenhuma acção.
Como resultado, o sistema apresenta uma lista com todos os candidatos encontrados (ordenados hierarquicamente pelos seguintes critérios: hora de partida, hora de chegada e tempo de viagem). Se forem encontradas múltiplas alternativas com o mesmo serviço de partida, apenas se apresenta a que chega mais cedo (excepto se existir uma ligação directa nesse serviço, mesmo que mais lenta). De seguida, o sistema pede o número da alternativa (requestItineraryChoice()) a guardar (1 para a primeira alternativa, etc.) no registo dos itinerários adquiridos pelo passageiro, actualizando-se o estado correspondente.
Se a escolha for 0 (zero), não é realizada nenhuma acção e os itinerários candidatos são descartados.
Se a escolha não corresponder a um número válido, é lançada a excepção mmt.app.NoSuchItineraryException, não sendo realizada nenhuma acção.
O formato de apresentação das escolhas é como definido para os itinerários, precedido da linha com o número da escolha (N representa a escolha), mas sem a linha relativa ao passageiro.
Exemplo de apresentação de hipótese de itinerário: Évora >> Silves (hipótese 17) |
---|
Itinerário 17 para 2017-10-19 Serviço #690 - Turística 5.23 07:06 Évora 07:17 Casa Branca 07:31 Vendas Novas 07:52 Pinhal Novo Serviço #180 - Turística 5.15 09:06 Pinhal Novo 10:52 Tunes Serviço #5904 - Única 1.83 11:06 Tunes 11:12 Algoz 11:18 Alcantarilha 11:23 Poço Barreto 11:30 Silves |
Além das opções de manipulação de ficheiros descritas no menu principal, é possível iniciar a aplicação com um ficheiro de texto especificado pela propriedade Java import.
O formato é como exemplificado a seguir. A notação "..." significa repetição de formato. <text> SERVICE|180|300|51.5|05:47|Porto - Campanhã|...|11:23|Faro SERVICE|694|200|12.2|16:57|Évora|17:08|Casa Branca|...|18:36|Lisboa - Oriente SERVICE|420|300|11.45|08:36|Valença|09:09|Viana do Castelo|09:50|Nine|10:18|Porto - Campanhã SERVICE|5500|150|11.35|15:48|Elvas|...|18:25|Entroncamento PASSENGER|Obi-Wan|ACTIVE PASSENGER|Yoda|INACTIVE ITINERARY|0|2017-10-18|690/07:06/07:52|180/09:06/10:52|5904/11:06/11:30 </text> Assume-se que não existem entradas mal-formadas nestes ficheiros.
Devem ser possíveis extensões ou alterações de funcionalidade com impacto mínimo no código já produzido para a aplicação. O objectivo é aumentar a flexibilidade da aplicação relativamente ao suporte de novas funções, sem que isso implique alterações no código core da aplicação.
Usando os ficheiros test.import, test.in e test.out, é possível verificar automaticamente o resultado correcto do programa. Note-se que é necessária a definição apropriada da variável CLASSPATH (ou da opção equivalente -cp do comando java), para localizar as classes do programa, incluindo a que contém o método correspondente ao ponto de entrada da aplicação (mmt.app.App.main). As propriedades são tratadas automaticamente pelo código de apoio.
java -Dimport=test.import -Din=test.in -Dout=test.outhyp mmt.app.App
Assumindo que aqueles ficheiros estão no directório onde é dado o comando de execução, o programa produz o ficheiro de saída test.outhyp. Em caso de sucesso, os ficheiros das saídas esperada (test.out) e obtida (test.outhyp) devem ser iguais. A comparação pode ser feita com o comando:
diff -b test.out test.outhyp
Este comando não deve produzir qualquer resultado quando os ficheiros são iguais. Note-se, contudo, que este teste não garante o correcto funcionamento do código desenvolvido, apenas verificando alguns aspectos da sua funcionalidade.
Tal como indicado acima, algumas classes fornecidas como material de apoio, são de uso obrigatório e não podem ser alteradas. Outras dessas classes são de uso obrigatório e têm de ser alteradas.
A serialização Java usa as classes da package java.io, em particular, a interface java.io.Serializable e as classes de leitura java.io.ObjectInputStream e escrita java.io.ObjectOutputStream (entre outras).
A representação e manipulação de datas e tempos deve ser realizada através das classes da package java.time, em particular das classes java.time.LocalDate e java.time.LocalTime. Diferenças entre tempos são representadas pela classe java.time.Duration.