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-uilib 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. |
EM PREPARAÇÃO
ÉPOCA NORMAL
O objectivo do projecto e desenvolver um gestor de uma rede de terminais de comunicação, denominado por prr. Genericamente, o programa permite a gestão e consulta de clientes, terminais e comunicações.
Especificamente, o sistema disponibiliza aos seus gestores varios serviços, entre outros: (i) registar dados sobre os clientes; (ii) registar dados sobre os terminais; (iii) registar dados sobre comunicações efectuadas; (iv) fazer pesquisas sobre comunicações efectuadas; (v) contabilizar o saldo associado a terminais.
Os utilizadores podem ter vários terminais, cada um deles associado a um número e a um plano tarifário. Nos pontos seguintes, descrevem-se as propriedades de cada entidade da aplicação.
Os clientes, terminais e comunicações possuem chaves únicas, uma cadeia de caracteres para os clientes e um inteiro para os terminais e para as comunicações.
Cada cliente, para além da chave única, é identificado pelo nome (cadeia de caracteres) e número de identificação fiscal (inteiro).
Existem três tipos de clientes: Normal (situação inicial, após o registo), Ouro e Platina. O custo das comunicações está indexado ao tipo de cliente (ver planos tarifários). Um cliente transita de tipo nas seguintes condições:
Antes | Depois | Condições |
Normal | Ouro | o saldo (após realizar um pagamento) é superior a 500 cêntimos |
Normal | Platina | (não é possível) |
Ouro | Normal | o saldo (após realizar uma comunicação) é inferior a 0 cêntimos |
Ouro | Platina | após realizar 5 comunicações consecutivas do tipo MMS (a contabilização da 5ª comunicação ainda considera que o cliente é do tipo Ouro) |
Platina | Ouro | após realizar 2 comunicações consecutivas do tipo SMS (a contabilização da 2ª comunicação ainda considera que o cliente é do tipo Platina) |
Platina | Normal | tentativa de realizar uma comunicação MMS para um terminal 2G |
Os terminais são identificados por um número (exactamente 6 dígitos) e estão associados a um cliente. Cada terminal pode estar ligado, em silêncio, ou desligado. Neste último caso, não é permitido iniciar ou receber comunicações. Quando o terminal está em silêncio, pode iniciar qualquer tipo de comunicação, mas só pode receber comunicações de texto. Cada terminal tem uma contabilidade própria, sendo sempre possível saber o saldo que lhe está associado.
Os terminais 2G só conseguem realizar comunicações SMS e de VOZ, não podendo nem iniciar nem receber comunicações MMS. Quando um terminal está em silêncio, não pode receber comunicações de VOZ ou MMS.
Para promover as comunicações, quando uma comunicação não se efectua por o terminal de destino estar em silêncio ou desligado, regista-se a tentativa de contacto, para que, assim que seja possível a realização do contacto pretendido, se enviarem mensagens aos terminais de origem. O registo da tentativa de contactos só tem lugar quando o cliente do terminal de destino tem activa a recepção de contactos falhados no instante em que se tentou efectuar a comunicação (que não teve lugar por o terminal de destino o não permitir).
Cada comunicação, além do identificador único, possui ainda informação sobre o terminal que a originou, o terminal de destino, a sua duração (no caso do texto, esta duração é zero) e o tipo de comunicação (voz, texto ou vídeo). A primeira comunicação tem como identificador “1”, devendo o identificador das comunicações subsequentes ser obtido por incremento unitário do identificador anterior.
Existem 3 tipos de comunicação entre terminais: texto (denominada SMS), voz (denominada VOZ) e vídeo (denominada MMS). O custo das comunicações não é uniforme (ver Planos tarifários), dependendo do terminal e do cliente, entre outros factores. Também dependendo do cliente, é possível avisar quem o tentou contactar quando: (i) um terminal desligado é colocado em silêncio; (ii) um terminal desligado é ligado; ou (iii) um terminal em silêncio é ligado.
Os custos de uma comunicação dependem do tipo de utilizador que inicia a comunicação e do tipo da comunicação: SMS, VOZ ou MMS.
Quando é efectuada uma comunicação do tipo SMS, o custo ou é fixo ou depende do número (representado na tabela por N) de caracteres enviados:
Normal | Ouro | Platina | |
N < 50 caracteres | 10 cêntimos | 10 cêntimos | 0 cêntimos |
50 caracteres <= N < 100 caracteres | 16 cêntimos | 10 cêntimos | 4 cêntimos |
N >= 100 caracteres | 2 * N cêntimos | 2 * N cêntimos | 4 cêntimos |
Quando é efectuada uma comunicação do tipo VOZ ou MMS, o custo é proporcional ao tempo de conversação e quando se liga para um terminal registado como amigo é aplicado um desconto de 50%. O custo, em cêntimos por minuto, é o seguinte para terminais não registados como amigos:
Normal | Ouro | Platina | |
VOZ | 20 cêntimos | 10 cêntimos | 10 cêntimos |
MMS | 30 cêntimos | 20 cêntimos | 10 cêntimos |
Por questões fiscais, todos os cálculos envolvendo os custos das comunicações devem ser apresentados em cêntimos, não sendo possível fazer arredondamentos. Por exemplo, uma comunicação de VOZ que dure 6 minutos tem um custo de 120 cêntimos para um cliente Normal e 60 cêntimos para clientes Ouro ou Platina.
A aplicação permite manter informação sobre as entidades do modelo. 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).
Deve ser possível efectuar pesquisas sujeitas a vários critérios e sobre as diferentes entidades geridas pela aplicação.
A base de dados com os conceitos pré-definidos é carregada no início da aplicação.
É possível guardar e recuperar o estado actual da aplicação, preservando toda a informação relevante, descrita acima.
A seguinte funcionalidade sobre clientes deve ser suportada pela aplicação: (i) visualizar um cliente; (ii) registar um novo cliente; (iii) permitir o registo de contactos falhados; (iv) inibir o registo de contactos falhados; (v) calcular o saldo de um cliente.
Um cliente não pode ser removido, sendo sempre possível aceder a todo o seu historial.
A seguinte funcionalidade sobre terminais deve ser suportada pela aplicação: (i) visualizar um terminal; (ii) registar um novo terminal; (iii) ligar, desligar e colocar no silêncio um terminal; (iv) adicionar e remover telefones da lista dos telefones amigos de um terminal; (v) proceder a um pagamento; (vi) calcular o saldo de um terminal.
Um terminal não pode ser removido, sendo sempre possível aceder a todo o seu historial.
A seguinte funcionalidade de pesquisa deve ser suportada: (i) comunicações efectuadas por um cliente; (ii) terminais que iniciaram comunicações para um determinado terminal; (iii) terminais que iniciaram uma comunicação para um determinado cliente; (iv) terminais com um determinado plano tarifário; (v) clientes com pagamentos em atraso.
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. Assim, deve ser possível:
Embora na especificação actual não seja possível remover entidades, a inclusão desta funcionalidade deve ser prevista, por forma a minimizar o impacto da sua futura inclusão.
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 procederem à 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-uilib e prr-app). As mensagens não podem ser usadas no núcleo da aplicação (prr-core). Além disso, não podem ser definidas novas. Potenciais omissões devem ser esclarecidas antes de qualquer implementação.
A apresentação de valores monetários (preços, saldos, etc.) é sempre feita com arredondamento ao inteiro mais próximo (note-se que a representação interna não deve ser arredondada).
As excepções usadas na interacção (subclasses de pt.tecnico.uilib.menus.CommandException), excepto se indicado, são lançadas pelos comandos (subclasses de pt.tecnico.uilib.menus.Command) e tratadas pelos menus (instâncias de subclasses de pt.tecnico.uilib.menus.Menu). Outras excepções não devem substituir as fornecidas nos casos descritos.
A apresentação de listas de entidades do domínio (clientes, etc.) faz-se por ordem crescente da respectiva chave: dependendo dos casos, a ordem pode ser numérica ou lexicográfica (UTF-8), não havendo distinção entre maiúsculas e minúsculas.
As acções deste menu permitem gerir a salvaguarda do estado da aplicação, abrir submenus e aceder a alguma informação global. A lista completa é a seguinte: Abrir, Guardar, Gestão de clientes, Gestão de terminais, Consultas, Consultas, Mostrar saldo global.
As etiquetas das opções deste menu estão definidas na classe prr.app.main.Label. Todos os métodos correspondentes às mensagens de diálogo para este menu estão definidos na classe prr.app.main.Message.
Inicialmente, a aplicação está vazia ou tem apenas informação sobre as entidades que foram carregados no arranque (via ficheiro textual).
O conteúdo da aplicação (toda a informação 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:
Note-se que a opção Abrir não permite a leitura de ficheiros de texto (estes apenas são utilizados na inicialização da aplicação).
A opção Sair nunca implica a salvaguarda do estado da aplicação, mesmo que existam alterações.
Esta opção apresenta o valor correspondente ao saldo global (soma dos sados de todos os clientes registados na rede). Embora internamente os valores dos saldos estejam representados em vírgula flutuante, a apresentação é arredondada ao inteiro mais próximo.
A apresentação faz-se através da mensagem Message.currentBalance().
Este menu permite efectuar operações sobre a base de dados de clientes. A lista completa é a seguinte: Visualizar todos os clientes, Registar cliente, Activar recepção de contactos falhados, Desactivar recepção de contactos falhados, Mostrar saldo de cliente.
Todos os métodos correspondentes às mensagens de diálogo para este menu estão definidos na classe prr.app.products.Message.
Sempre que é pedido o identificador de um cliente (Prompt.clientKey()) e o identificador não existir (excepto no processo de registo), é lançada a excepção UnknownClientKeyException. Na ocorrência de excepções, as operações não têm efeito.
O formato de apresentação de cada cliente é o seguinte, em que nTerminals é o número de terminais activos associados ao cliente e balance é o somatório do saldo de cada um desses terminais (valor arredondado ao inteiro mais próximo). Os valores para clientType são NORMAL, GOLD, ou PLATINUM. Os valores para answeringStatus são ON ou OFF. Se o cliente não tiver terminais, o saldo é 0 (zero).
CLIENTE|clientId|clientName|NIF|clientType|answeringStatus|nTerminals|balance
O sistema pede o identificador que ficará associado ao cliente (identificador único}. De seguida, pede o nome do cliente (requestClientName()) e o número de identificação fiscal (requestNIF()). Após o registo, o cliente fica no estado Normal e o registo de contactos falhados fica activo.
Caso o identificador indicado já exista, deve ser lançada a excepção DuplicateClientKeyException, não se realizando o registo.
É pedido o identificador do parceiro e são apresentados os lotes por ele fornecidos. A ordenação é como indicada para a listagem de todos os produtos disponíveis (lotes).
O sistema pede o identificador do cliente, apresentando o seu saldo. Tal como acima, o saldo do cliente é o somatório do saldo de cada um desses terminais (valor arredondado ao inteiro mais próximo).
Este menu permite efectuar operações sobre a base de dados de parceiros. A lista completa é a seguinte: Mostrar todos os terminais, Registar terminal, Menu de administração de terminal.
Todos os métodos correspondentes às mensagens de diálogo para este menu estão definidos na classe prr.app.terminals.Message.
Sempre que for pedido o identificador de um terminal (Prompt.terminalKey()) e o terminal não existir, é lançada a excepção UnknownTerminalKeyException (excepto no processo de registo). No processo de registo, caso o identificador indicado já exista, deve ser lançada a excepção DuplicateTerminalKeyException.
O formato de apresentação de cada terminal é o seguinte:
TERMINAL|terminalId|clientId|terminalType|terminalStatus|terminalBalance|friend1,...,friendN
Os valores para o campo terminalType são 2G ou 3G. Os valores para o campo terminalStatus são ON, OFF, SILENCE. Os valores friend1, ..., friendN são os identificadores dos terminais amigos e são apresentados por ordem crescente desses identificadores.
O sistema pede o número identificador do terminal. Se o número introduzido não contiver exactamente 6 dígitos, o sistema volta a pedir o número identificador. Se já existir um terminal com o mesmo número identificador, deve ser apresentada a mensagem {\small\url{duplicateMobile()}}, não se realizando qualquer acção.
De seguida, o sistema pede o tipo de telemóvel ({\small\url{typeReq()}}). A resposta deve ser \texttt{2G} (i.e., {\small\url{mobile2G()}}) (é registado um telemóvel da geração 2G), ou \texttt{3G} (i.e., {\small\url{mobile3G()}}) (é registado um telemóvel da geração 3G). Se a resposta não corresponder a nenhum dos dois valores, a pergunta {\small\url{typeReq()}} é repetida até se obter uma resposta válida. Finalmente, pede o identificador do cliente a que ficará associado. Se não existir um cliente com o identificador introduzido, deve ser lançada a excepção adequada (ver~§\ref{sec:3:Clientes}) e não se regista o telemóvel.
Quando um terminal é registado, fica no estado ligado, com saldo zero e com uma lista de amigos vazia.
É pedido o identificador do produto (Prompt.partnerKey()) e o identificador do produto (Prompt.productKey()). Se as notificações relativas ao produto estavam activas para o parceiro em causa, passam a estar inactivas, e vice-versa.
Se o produto indicado não existir, é lançada a excepção UnknownProductKeyException.
É pedido o identificador do parceiro e apresentadas todas as compras com ele realizadas. O formato de apresentação é como descrito abaixo (visualização de transacções).
É pedido o identificador do parceiro e apresentadas todas as vendas (e desagregações) com ele realizadas. O formato de apresentação é como descrito abaixo (visualização de transacções).
Este menu apresenta as operações relacionadas com transacções. A lista completa é a seguinte: Visualizar, Registar Desagregação, Registar Venda, Registar Compra, Receber Pagamento de Venda.
Todos os métodos correspondentes às mensagens de diálogo para este menu estão definidos na classe prr.app.transactions.Message.
Sempre que é pedido o identificador do parceiro (Prompt.partnerKey()), é lançada a excepção UnknownPartnerKeyException, se o parceiro indicado não existir. Sempre que é pedido o identificador de produto (Prompt.productKey()), é lançada a excepção UnknownProductKeyException, se o produto indicado não existir. Sempre que é pedido o identificador da transacção (Prompt.transactionKey()), é lançada a excepção UnknownTransactionKeyException, se a transacção indicada não existir.
O sistema pede o identificador da transacção a visualizar.
Nas apresentações, o campo valor-base é o valor da transacção sem multas/descontos.
Se a transacção for respeitante a uma venda a um parceiro, apresenta-se com o seguinte formato:
VENDA|id|idParceiro|idProduto|quantidade|valor-base|valor-a-pagamento|data-limite|data-pagamento
O campo valor-a-pagamento corresponde ao valor que será realmente pago (considerando possíveis multas/descontos à data da visualização). A data de pagamento (e o separador correspondente) só é apresentada se a venda tiver sido paga.
Se a transacção corresponder a uma compra a um parceiro, apresenta-se com o seguinte formato:
COMPRA|id|idParceiro|idProduto|quantidade|valor-pago|data-pagamento
Se a transacção for uma desagregação, então deve ser apresentada, primeiro a "venda" do produto derivado, seguida da "compra" dos componentes obtidos na desagregação. O formato para N componentes é o seguinte:
DESAGREGAÇÃO|id|idPa|idPr|quantidade|vbase|vpag|data|idC1:q1:v1#...#idCN:qN:vN
O valor base de uma desagregação corresponde à diferença entre a compra e a venda. O valor pago corresponde ao valor realmente pago pelo parceito (é um valor não negativo, podendo ser 0 caso o valor base seja negativo. Nesta linha, idPa é o identificador do parceiro; idPr é o identificador do produto; vbase é o valor real da transacção (diferencial); vpag é o valor a pagar (não negativo); data é a data da transacção; idCx, qx, vx, são (respectivamente) o identificador, a quantidade e o valor do componente x (não confundir com valor unitário). Cada componente é separado do seguinte por #.
Para registar uma desagregação, é pedido o identificador do parceiro que pede a operação e o identificador do produto a desagregar e a respectiva quantidade (Prompt.amount()). Se a quantidade for superior às existências actuais, deve ser lançada a excepção UnavailableProductException (não se realiza a desagregação). Se o produto a desagregar não for derivado, a acção não tem efeito.
A actualização dos produtos do entreposto, bem como o respectivo valor, tem lugar logo após o registo da desagregação, ou seja, considera-se que a operação é instantânea.
Esta transacção regista o produto de origem e os lotes dos resultantes (associados ao parceiro que pede a operação), bem como os respectivos valores.
Para registar uma venda, é pedido o identificador do parceiro, a data limite para o pagamento (Prompt.paymentDeadline()), o identificador do produto a vender e a respectiva quantidade (Prompt.amount()). Se a quantidade for superior às existências actuais, deve ser lançada a excepção UnavailableProductException (não se realiza a venda).
A determinação das existências começa com o preço mais baixo e vai prosseguindo por todos os parceiros que tenham o produto disponível. O preço a pagar é determinado pelo preço das várias fracções.
Se a venda corresponder a um produto derivado e não existir produto suficiente nos lotes disponíveis, o produto pode ser fabricado a partir de componentes de outros lotes. As existências dos componentes têm de ser suficientes para satisfazer a totalidade das necessidades de produto derivado a fabricar. O preço é calculado como descrito acima.
A actualização dos produtos do entreposto tem lugar logo após o registo da venda, ou seja, considera-se que a venda é instantânea.
É pedido o identificador do parceiro a quem se realiza a compra, o identificador do produto que se está a comprar, o preço do produto (Prompt.price()) e a respectiva quantidade (Prompt.amount()). O produto comprado mantém informação sobre o processo de aquisição: em particular, se vários parceiros fornecerem o mesmo produto, podem fazê-lo com preços diferentes. O entreposto poderá, então, vender um dado produto com diferentes preços.
Se o identificador do produto for desconhecido, então é perguntado ao parceiro se quer introduzir uma receita para um produto derivado (Prompt.addRecipe()). Se a resposta for negativa, trata-se de um produto simples. Caso contrário, é pedido o número de componentes da receita (Prompt.numberOfComponents()), o valor do agravamento (Prompt.alpha()) e, em ciclo, os identificadores (Prompt.productkey()) e quantidades (Prompt.amount()) dos vários componentes.
A actualização de existências dos produtos do entreposto tem lugar logo após o registo da compra, ou seja, considera-se que a compra é instantânea. A actualização do saldo do entreposto também é assumida como instantânea, i.e., assume-se que a compra é paga a pronto.
Apenas é permitido o pagamento de vendas a parceiros. Tentativas de pagamento de transacções que não correspondam a vendas não produzem nenhum resultado.
É pedido o identificador da venda a pagar. Se a venda já tiver sido paga, não é realizada nenhuma acção.
Este menu apresenta as operações relacionadas com consultas. A lista completa é a seguinte: Mostrar produtos com preço abaixo de limite, Mostrar facturas pagas por parceiro.
Sempre que é pedido o identificador do parceiro (Prompt.partnerKey()), é lançada a excepção UnknownPartnerKeyException, se o parceiro indicado não existir. Sempre que é pedido o identificador de produto (Prompt.productKey()), é lançada a excepção UnknownProductKeyException, se o produto indicado não existir.
A apresentação de resultados é como indicado nos casos já descritos de apresentação das várias entidades.
Pede-se o valor limite pretendido (Prompt.priceLimit()) e apresentam-se todos os lotes cujo preço é inferior ao preço indicado. É apresentado um lote por linha.
Pede-se o identificador do parceiro e apresentam-se as transacções do parceiro que já estão pagas (uma transacção por linha).
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.
As várias entidades têm os formatos descritos abaixo. Assume-se que os títulos não podem conter o carácter | e que o preço é um número inteiro (sugere-se a utilização do método String.split para o processamento preliminar destas linhas). Não existem entradas mal-formadas.
Cada linha tem uma descrição distinta, mas que segue os seguintes formatos:
CLIENT|id|nome|xxx|nível TERMINAL|idTerminal|idClient|type|state|balance FRIENDS|idTerminal|idTerminal1,...,idTerminalN
As definições de clientes precedem sempre as dos terminais. As ligações entre amigos estão sempre após a definição das restantes entidades.
Um exemplo de conteúdo do ficheiro inicial é como se segue:
Exemplo de ficheiro de entrada textual |
---|
CLIENT|cli001|Manuel Pinheiro|103443|NORMAL
CLIENT|cli002|Pedro Pinheiro|103447|NORMAL
CLIENT|Cli201|Ludgero Oliveira|103440|NORMAL
CLIENT|cli Es|Maria Eucalipto|103441|GOLD
CLIENT|01|Oliveira Preto|103547|NORMAL
CLIENT|cli003|Pedro Oliveira|103449|PLATINUM
TERMINAL|969001|cli001|2G|ON|-900
TERMINAL|969003|cli002|2G|ON|0
TERMINAL|969002|cli002|3G|SILENCE|330
TERMINAL|969007|cli Es|3G|ON|700
TERMINAL|969008|cli003|2G|OFF|2500
TERMINAL|969009|cli003|2G|OFF|1200
TERMINAL|969010|cli003|2G|ON|-2100
TERMINAL|969006|cli003|3G|ON|4800
TERMINAL|969005|cli003|3G|ON|3400
TERMINAL|969004|cli003|2G|ON|1000
FRIENDS|969001|969008,969009,969004
FRIENDS|969004|969001
FRIENDS|969003|969008
|
A codificação dos ficheiros a ler é garantidamente UTF-8.
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 (prr.app.App.main). As propriedades são tratadas automaticamente pelo código de apoio.
java -Dimport=test.import -Din=test.in -Dout=test.outhyp prr.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).