Commit 2b77da36 authored by Jostein Hjortland Tysse's avatar Jostein Hjortland Tysse
Browse files

Add øving 6

parent 24013d5b
# Observatør-observert-teknikken - HighscoreList-oppgave
Denne oppgaven handler om å bruke observatør-observert-teknikken for å bli informert om endringer i en highscore-liste.
Observatør-observert-teknikken går ut på at det observerte objektet sier ifra til en eller flere observatører om at tilstanden er endret. Dette brukes gjerne når vi har en rekke observatørobjekter som ønsker å vite når en endring skjer i et annet (observert) objekt. Det hadde vært ueffektivt om observatørobjektene skulle sjekket for endringer hele tiden. Derfor definerer vi ofte et felles `interface` som disse kan implementere, slik at det observerte objektet kan kalle på metoder i observatørene når det skjer en endring.
I denne oppgaven skal vi lage en observerbar **HighscoreList** som kan si fra til observatører/lyttere av typen **HighscoreListListener** når nye resultater blir registrert. En hovedprogramklasse kalt **HighscoreProgram** vil bli brukt til å sjekke at det virker. Denne klassen oppretter en **HighscoreList**-instans, legger inn resultater (tall) fra konsollen som legges til lista og skriver ut lista hver gang et nytt resultat faktisk blir lagt til.
Alle filene i denne oppgaven skal lages i [oving6/observable](../../src/main/java/oving6/observable)
## Del 1: Implementasjon av HighscoreList
En **HighscoreList** skal holde styr på heltallsresultater (av typen int/Integer). Lista skal være observerbar ved at den kan registrere lyttere (**HighscoreListListener**-instanser) og si fra til dem når lista blir endret. Lista skal ha en maksimal lengde, som settes i konstruktøren, f.eks. skal en topp 10-liste kunne opprettes med `new HighscoreList(10)`. Nye resultater registreres med metoden `addResult(int)`, som skal finne riktig posisjon og legge resultatet inn (dersom det er godt nok). Dersom lista er for lang, så skal det dårligste resultatet fjernes. NB: _Lavest verdi_ er best, f.eks. antall sekunder på en oppgave eller antall flytt i Sokoban.
**HighscoreListListener**-grensesnittet er vist i klassediagrammet til venstre og må implementeres av alle klasser som ønsker å fungere som lyttere for **HighscoreList**-instanser. Lyttere registrerer seg med **HighscoreList** sin **addHighscoreListListener**-metode og vil siden få beskjed om nye resultater ved at **listChanged**-metoden kalles. Argumentene som tas inn er **HighscoreList**-objektet som ble endret og posisjonen i lista der endringen skjedde.
Her er en oversikt over metoden som må implementeres:
- `HighscoreList(int maxSize)` - konstruktøren tar inn maks antall resultater som lista skal kunne holde. Denne verdien må brukes av **addResult**, slik at resultater som er for dårlige kastes.
- `size()` - returnerer antall elementer i lista, som altså aldri skal overstige maks-antallet
- `int getElement(int)` - returnerer resultatet i posisjonen angitt av argumentet
- `void addResult(int)` - registrere et nytt resultat, og dersom resultatet er godt nok til å komme med på lista, så legges det inn på riktig plass. Dersom lista blir for lang, så må dårligste resultat kastes. Alle registrerte lyttere må få beskjed om en evt. endring av lista, inkludert på hvilken posisjon som ble endret.
- `addHighscoreListListener(HighscoreListListener)` - registrerer en ny lytter
- `removeHighscoreListListener(HighscoreListListener)` - fjerner en tidligere registrert lytter
Klassediagram for **HighscoreList** og **HighscoreListListener**:
![](images/highscore-list.png)
Testkode for denne oppgaven finner du her: [oving6/observable/HighscoreListTest.java](../../src/test/java/oving6/observable/HighscoreListTest.java).
## Del 2: Hovedprogramklasse
Lag en hovedprogramklasse kalt **HighscoreListProgram**, som tester at **HighscoreList**-klassen din virker som den skal. La den opprette en **HighscoreList**-instans, lese inn tall fra konsollet (f.eks. med en `Scanner` og `nextInt`-metoden) og legge disse inn i lista. Sørg for at **HighscoreListProgram** implementerer **HighscoreListListener**-grensesnittet (`HighscoreListProgram implements HighscoreListListener`) og registrerer seg som lytter på **HighscoreList**-instansen via `addHighscoreListListener`. La lyttermetoden `listChanged` skrive ut informasjon og resultatene i **HighscoreList**-instansen og posisjonsargumentet, slik at du ser at alt virker som det skal.
Vi foreslår følgende metoder og oppførsel:
- `void init()` - oppretter en ny **HighscoreList** og registrerer seg selv (altså **HighscoreListProgram**-instansen) som lytter. Dette kan og gjøres i konstruktøren om ønskelig
- `void run()` - leser inn tall (resultater) fra terminalen og legger dem til i listen
- `void listChanged(HighscoreList, int)` (fra `HighscoreListListener`) - observerer endringer i **HighscoreList**-instansen og skriver ut posisjonsargumentet, samt selve listen, til konsollen.
Klassediagrammet viser hvordan klassene henger sammen, og vårt forslag til metoder:
![](images/hl-program.png)
Husk også å lage en `main`-metode som kjører HighscoreListProgram!
# Delegering - Logger-oppgave
Denne oppgaven bruker delegeringsteknikken for å implementere en fleksibel måte å håndtere logging (av feil i programmer) på.
## Logging
Ved kjøring av programmer er det ofte behov for å logge hva som skjer underveis, slik at det blir lettere å drive feilsøking i etterkant. F.eks. kan en lagre feilmeldinger til fil, med tidspunkt og litt om programtilstanden og hvis programmet kræsjer ordentlig, så kan brukeren sende logg-fila som e-post til utviklerne. En enkel måte å støtte logging på er å lage en hjelpeklasse med én metode, f.eks. `log(String melding)`, og så er det hjelpeklassen som bestemmer om meldingen skal vises i statuslinja, skrives til fil, sendes som melding til en alarmsentral osv. Hjelpeklassen kan kanskje brukes av mange programmer, og siden behovene vil variere er det viktig å gjøre dette fleksibelt. Denne oppgaven bruker [grensesnitt](https://www.ntnu.no/wiki/pages/viewpage.action?pageId=65936813) og [delegeringsteknikken](https://www.ntnu.no/wiki/display/tdt4100/Delegeringsteknikken) for å implementere fleksibel logging, litt på samme måte som eksisterende loggingsrammeverk (se f.eks. [Java sin egen loggingsfunksjonalitet](http://docs.oracle.com/javase/6/docs/technotes/guides/logging/overview.html), Apache sitt [log4j-rammeverk](http://logging.apache.org/log4j/), eller Googles ["Java logging framework"](https://www.google.no/search?q=java+logging+frameworks)).
Alle filene i denne oppgaven skal lages i [oving6/delegation](../../src/main/java/oving6/delegation)
### ILogger-grensesnittet
Logging gjøres ved å bruke ulike implementasjoner av **ILogger**, som er definert som følger:
```java
package delegation;
public interface ILogger {
public String ERROR = "error", WARNING = "warning", INFO = "info";
public void log(String severity, String message, Exception exception);
}
```
ILogger-grensesnittet definerer én log-metode som brukes til all logging:
- `severity`-argumentet angir alvorlighetsgraden, og må være en av **String**-verdiene **ERROR**, **WARNING** eller **INFO**, som er definert som konstanter i grensesnittet.
- `message`-argumentet er en melding om hva som var feil.
- `exception`-argumentet er et unntaksobjekt, som kan gi mer informasjon av hva som var feil, men kan være **null**.
En typisk bruk vil være i **catch**-delen av en **try/catch**:
```java
ILogger logger = ...
...
try {
...
} catch (IOException ioe) {
logger.log(ILogger.ERROR, "Feil ved lesing fra fil", ioe);
}
```
Akkurat hvordan logging utføres bestemmes av hvilken implementasjon av ILogger-grensesnittet en bruker, og i denne oppgaven skal du implementere følgende tre klasser:
- **DistributingLogger** - delegerer til andre loggere basert på _alvorlighetsgraden_
- **FilteringLogger** - delegerer til en annen logger, men kun for spesifikke alvorlighetsgrader
- **StreamLogger** - skriver logg-meldingen til en angitt strøm
Hver av disse utgjør én av deloppgavene beskrevet under.
## Del 1 - StreamLogger
En **StreamLogger** sørger for å skrive alle logg-meldinger til en angitt **OutputStream**, med én melding pr. linje (altså linjeskift mellom hver melding). **OutputStream**-objektet må gis inn i konstruktøren:
- `StreamLogger(OutputStream stream)` - initialiserer **StreamLogger**-objektet slik at logg-meldinger skrives til **stream**.
Eksempel på bruk:
```java
ILogger logger = new StreamLogger(System.out);
logger.log(ILogger.INFO, "Denne meldingen er til informasjon og skrives til System.out", null);
```
Husk å kalle **flush**-metoden til OutputStream etter at logg-meldingen er skrevet.
Det skal også være mulig å angi en såkalt _format_-string, dvs. en **String** som fungerer som en slags mal for hva som skrives, f.eks. `"%s: %s (%s)"`:
- `setFormatString(String formatString)` - setter format-string-en som brukes for å lage logg-meldingen som skrives
Effekten av skriving skal være som om man ga format-string-en som første argument til **String.format**-metoden etterfulgt av severity-, message- og exception-argumentene, og så skrev ut det denne metoden returnerer:
```java
String logMessage = String.format(formatString, severity, message, exception);
// skriv logMessage til OutputStream-en her
```
Merk at dersom format-string-en ikke er satt, så skal den ha en fornuftig start-verdi.
Testkode for oppgaven: [oving6/delegation/StreamLoggerTest.java](../../src/test/java/oving6/delegation/StreamLoggerTest.java).
## Del 2 - FilteringLogger
**FilteringLogger**-klassen implementerer **ILogger**-grensesnittet og delegerer til en annen **ILogger**-implementasjon, men bare hvis _alvorlighetsgraden_ er en av et sett angitte verdier. Både loggeren det delegeres til og alvorlighetsgradene angis når **FilteringLogger**-objektet opprettes:
- `FilteringLogger(ILogger logger, String... severities)` - initialiserer **FilteringLogger**-objektet så det delegerer logging til **logger**-argumentet, men bare hvis _alvorlighetsgraden_ som gis til **log**-metoden er en av verdiene angitt i **severities**-argumentet. **severities**-argumentet er et såkalt varargs-argument, som du kan lese mer om her: [Varargs - variabelt antall argumenter](https://www.ntnu.no/wiki/display/tdt4100/Varargs+-+variabelt+antall+argumenter). Det viktigste å vite her er at det du får inn i metoden din vil være en variabel `severities` som er av typen string array (`String[]`). Du kan hente ut elementer her via `severities[0]`, sjekke lengde ved `severities.length` og ellers bruke alle normale arraymetoder.
Det skal også være mulig å sjekke om logging er på og slå logging av og på i etterkant:
- `boolean isLogging(String severity)` - returnerer **true** hvis logging er slått på for den angitte alvorlighetsgraden og **false** ellers.
- `void setIsLogging(String severity, boolean value)` - slår logging på (**value = true**) eller av (**value = false**) for den angitte _alvorlighetsgraden_
Eksempel på bruk:
```java
ILogger syserrLogger = new StreamLogger(System.err);
FilteringLogger logger = new FilteringLogger(syserrLogger, ILogger.ERROR);
logger.log(ILogger.ERROR, "Denne meldingen er alvorlig og skrives til System.err", null);
logger.log(ILogger.WARNING, "Denne meldingen er en advarsel og blir filtrert bort", null);
logger.log(ILogger.INFO, "Denne meldingen er til informasjon og blir filtrert bort", null);
logger.setIsLogging(ILogger.WARNING, true);
logger.log(ILogger.WARNING, "Denne meldingen er en advarsel og blir nå skrevet til System.err", null);
```
Testkode for oppgaven: [oving6/delegation/FilteringLoggerTest.java](../../src/test/java/oving6/delegation/FilteringLoggerTest.java).
## Del 3 - DistributingLogger
**DistributingLogger**-klassen brukes for å fordele logg-meldinger til en av tre andre loggere, avhengig av _alvorlighetsgraden_ til en logg-melding. Den har én hjelpe-logger for meldinger med alvorlighetsgrad **ERROR**, én for meldinger av alvorlighetsgrad **WARNING** og en for meldinger av alvorlighetsgrad **INFO**. Alle disse angis til konstruktøren:
- `DistributingLogger(ILogger errorLogger, ILogger warningLogger, ILogger infoLogger)` - initialiserer objektet slik at den første loggeren brukes til alvorlighetsgraden **ERROR**, den andre til alvorlighetsgraden **WARNING** og den tredje til alvorlighetsgraden **INFO**.
I tillegg skal klassen ha en metode for å sette hver av dem individuelt:
- `void setLogger(String severity, ILogger logger)` - setter/endrer loggeren som brukes for den angitte alvorlighetsgraden.
Eksempel på bruk:
```java
ILogger syserrLogger = new StreamLogger(System.err);
ILogger sysoutLogger = new StreamLogger(System.out);
DistributingLogger logger = new DistributingLogger(syserrLogger, syserrLogger, sysoutLogger);
logger.log(ILogger.ERROR, "Denne meldingen er alvorlig og skrives til System.err", null);
logger.log(ILogger.WARNING, "Denne meldingen er en advarsel og skrives til System.err", null);
logger.log(ILogger.INFO, "Denne meldingen er til informasjon og skrives til System.out", null);
logger.setLogger(ILogger.WARNING, sysoutLogger);
logger.log(ILogger.WARNING, "Denne meldingen er en advarsel, men nå skrives den til System.out", null);
```
Testkode for oppgaven: [oving6/delegation/DistributingLoggerTest.java](../../src/test/java/oving6/delegation/DistributingLoggerTest.java).
# Delegering - The Office-oppgave
Denne oppgaven bruker delegeringsteknikken for å modellere arbeidsfordeling på en “vanlig” arbeidsplass. Denne oppgaven kan muligens oppleves som mindre meningsfull. Dette er kanskje omtrent tilsvarende hvor meningsløst noen typer kontorarbeid kan virke.
Vi skal i dette scenarioet ha en sjef, eller **Manager**, som har én eller flere arbeidere, eller **Clerk**s, altså i en såkalt én-til-mange relasjon. Et **Employee**-grensesnitt definerer en oppførsel som er felles for de ansatte, og implementeres av både **Manager** og **Clerk**.
**Employee**-objekter på denne simulerte arbeidsplassen har to oppgaver:
- utskrift av dokumenter
- utførelse av matematiske beregninger
Alle filene i denne oppgaven skal lages i [oving6/delegation/office](../../src/main/java/oving6/delegation/office)
## Del 1: Employee, Clerk og Printer
**Employee**-grensesnittet har følgende metoder:
- `double doCalculations(BinaryOperator<Double> operation, double value1, double value2)` - regner ut resultatet av å utføre **operation** med argumentene **value1** og **value2**.
- `void printDocument(String document)` - Printer **document**. Hvordan dette gjøres avhenger av den spesifikke implementasjonen.
- `int getTaskCount()` - returnerer hvor mange oppgaver (beregninger og printinger) som har blitt utført av eller på vegne av dette **Employee**-objektet.
- `int getResourceCount()` - antallet employees til rådighet, inkludert **Employee**-objektet metoden blir kalt på. En **Employee** skal altså medregne seg selv i antall ressurser den ansatte har til rådighet. Dette tallet skal inkludere alle **Employee**-objekter nedover i hierarkiet.
Lag dette grensesnittet, og lag så en **Clerk**-klasse som implementerer det. **Clerk** må ha følgende konstruktør:
- `Clerk(Printer printer)`
**Clerk**-klassen må inneholde egen logikk for å løse **doCalculations**, men skal altså delegere **printDocuments** til **Printer**-objektet gitt i konstruktøren.
Definer en **Printer**-klasse med følgende metoder:
- `void printDocument(String document, Employee employee)` - skriver documentet til konsollen og tar vare på dokumentet i **employee** sin historikk.
- `List<String> getPrintHistory(Employee employee)` - returnerer en **List<String>** med alle dokumentene som har blitt printet av **employee** av denne printeren i rekkefølgen de har blitt printet. Om **employee** ikke har printet noen dokumenter ved denne printeren skal en tom liste returneres.
La så **Clerk** delegere **printDocument** til **Printer**. Siden **Clerk** ikke har noen andre ansatte å delegere til, vil **getResourceCount()** alltid være 1.
Testkode for Clerk er her: [oving6/delegation/office/ClerkTest.java](../../src/test/java/oving6/delegation/office/ClerkTest.java)
Testkode for Printer er her: [oving6/delegation/office/PrinterTest.java](../../src/test/java/oving6/delegation/office/PrinterTest.java)
## Del 2: Manager
Vi definerer så sjefen til de hardt-arbeidende **Clerk**-objektene. **Manager**-klassen har følgende konstruktør:
- `Manager (Collection<Employee> employees)` - utløser et **IllegalArgumentException** dersom employees er tom.
La så **Manager** implementere **Employee**-grensesnittet. Implementer **Manager**s oppgaver ved å delegere alle videre til en av arbeiderne i listen med **Employee**-objekter gitt i konstruktøren. Regelen for hvilken **Employee** som får hvilken oppgave delegert til seg kan du bestemme selv, men prøv å gjøre det slik at arbeidet fordeles jevnt på alle. Mens **Clerk** altså har kun én tilgjengelig ressurs vil **Manager**-objekter vil ha flere.
Testkode for Manager er her: [oving6/delegation/office/ManagerTest.java](../../src/test/java/oving6/delegation/office/ManagerTest.java)
## Del 3: Main-metode
Lag en main-metode som illustrerer hva som skjer med effektiviteten når vi legger til flere nivåer med mellomledere.
Lag først et **Manager**-objekt som blir tildelt noen **Clerk**-objekter under seg. Presentér deretter effektiviteten av hierarkiet ved å skrive ut `getTaskCount() / getResourceCount()` for **Manager**-objektet. Vis deretter hvordan effektiviteten faller når vi legger til nivåer med mellomledere ved å lage to eller flere nivåer med **Manager**, hvor lederne på bunnen tildeles **Clerk**-objekter, og skriv ut den nye effektiviteten for topplederne.
# Øving 06: Observatør-Observert og Delegering
**Øvingsmål**
* Lære hva observatør-observert-teknikken er, dens bruksområder og fordeler
* Lære bruk av delegering for å utføre oppgaver i en klasse
**Øvingskrav**
* Kunne definere og implementere et observatørgrensesnitt
* Kunne la en observert klasse fortelle dens observatører om endringer
* Kunne la en klasse delegere utførelsen av oppgaver til interne objekter
## Dette må du gjøre
### Del 1: Programmering
Denne øvingen omfatter både [delegeringsteknikken](https://www.ntnu.no/wiki/display/tdt4100/Delegeringsteknikken) og
[observatør-observert-teknikken](https://www.ntnu.no/wiki/pages/viewpage.action?pageId=66879660). Gjør **minst én** av de fire oppgavene under. For å få 2 poeng må det gjøres **minst én** oppgave
fra **hvert av de to temaene**. Dette anbefales uansett på det *sterkeste*, siden dette må til for å dekke hele pensum.
Gjennomfør enten *minst én* av oppgavene om delegering:
* [The Office (ovinger/src/oving6.delegation.office)](./Office.md) (anbefalt)
* [Logger (ovinger/src/oving6.delegation)](./Logger.md)
ELLER *minst én* av oppgavene om observatør-observert-teknikken:
* [StockListener (ovinger/src/oving6.observable)](./StockListener.md)
* [Highscore (ovinger/src/oving6.observable)](./HighscoreList.md)
Oppgavene skal lagres i mappene som er spesifisert i parentes etter oppgavene.
**I tillegg til oppgaven(e) ovenfor skal du levere en tekstfil hvor du gjør kort rede for delegeringsteknikken og observatør-observert-teknikken.**
### Del 2: Objektdiagram
For en av oppgavene du gjorde i del 1:
Lag en sekvens av kall i main-funksjonen. Denne sekvensen må benytte seg av den passende teknikken fra del 1. Tegn deretter et [objektdiagram](https://www.ntnu.no/wiki/display/tdt4100/Objektdiagrammer) som viser tilstanden til hvert objekt ved slutten av main-funksjonen.
### Hjelp / mistanke om bugs
Ved spørsmål eller behov for hjelp konsulter studassen din i saltiden hans / hennes. Du kan også oppsøke andre studasser på sal eller legge ut et innlegg på [Piazza](https://piazza.com/).
### Godkjenning
Last opp kildekode på Blackboard innen den angitte innleveringsfristen. Innlevert kode skal demonstreres for en læringsassistent innen én uke etter innleveringsfrist. Se for øvrig Blackboard-sidene for informasjon rundt organisering av øvingsopplegget og det tilhørende øvingsreglementet.
# Observatør-observert-teknikken - StockListener-oppgave
Denne oppgaven handler om å bruke observatør-observert-teknikken for å holde en aksjeindeks (**StockIndex**) informert om endringer i én eller flere aksjer (**Stock**).
Observatør-observert-teknikken går ut på at det observerte objektet sier ifra til én eller flere observatører om at tilstanden er endret. I vårt tilfelle skal vi ta utgangspunkt i at aksjer (**Stock**) har en pris, og at personer eller institusjoner (**StockListener**) ønsker å holde seg oppdatert på aksjepriser.
Alle filene i denne oppgaven skal lages i [oving6/observable](../../src/main/java/oving6/observable)
## Del 1: Stock-klassen og StockListener-grensesnittet
Du skal implementere en klasse **Stock** med følgende funksjonalitet:
- `Stock(String, double)` - en konstruktør som tar inn en aksjekode (ticker) og en aksjepris.
- `void setPrice(double)` - endringsmetode for aksjeprisen. Dersom aksjepris er negativ eller lik null, skal metoden utløse en **IllegalArgumentException**.
- `String getTicker()` - metode for å hente aksjekoden.
- `double getPrice()` - metode for å hente aksjeprisen.
Du skal videre definere et lyttergrensesnitt kalt **StockListener**, som observatørene må implementere. Grensesnittet skal inneholde én metode:
- `void stockPriceChanged(Stock stock, double oldValue, double newValue)` - lyttermetode for å holde lytteren oppdatert på aksjeprisen. Metoden skal ta inn et **Stock**-objekt, samt gammel og ny pris. Alle lyttere må implementere denne metoden.
Foreløpig er **Stock** ikke observerbar. For at observatører skal kunne holdes oppdatert, må **Stock**-objekter administrere en liste med lyttere. Derfor må **Stock**-klassen i tillegg ha følgende metoder:
- `void addStockListener(StockListener)` - metode for å legge til nye observatører.
- `void removeStockListener(StockListener)` - metode for å fjerne observatører.
Observatørene skal holdes oppdatert på prisendringer. Derfor må lyttermetoden kalles hos alle registrerte observatører når aksjeprisen endres med **setPrice**-metoden.
Testkode for denne oppgaven finner du her: [oving6/observable/StockTest.java](../../src/test/java/oving6/observable/StockTest.java).
## Del 2: StockIndex implements StockListener
Vi skal nå lage en veldig forenklet versjon av en aksjeindeks. I korte trekk bruker man en aksjeindeks til å måle utviklingen av et utvalg aksjer. Vår enkle, fiktive aksjeindeks **StockIndex** har et navn (**String**), indeks (**double**) og en liste med **Stock**-objektene som er inkludert i indeksen. Indeksen beregnes ut i fra aksjeprisene den "observerer", og vil være lik summen av disse. Når en av aksjeprisene øker eller synker, må tilstanden til **StockIndex**-objektet holdes konsistent med denne utviklingen. Dette lar seg gjøre ved at **StockIndex** observerer én eller flere **Stock**-objekter. Klassen skal ha følgende metoder:
- `StockIndex(String, Stock... )` - konstruktør som tar inn ingen, én eller flere aksjer (**Stock**-objekter). **Stock**-parameteret defineres som et såkalt [varargs-parameter](https://www.ntnu.no/wiki/display/tdt4100/Varargs+-+variabelt+antall+argumenter). NB: **StockIndex**-objektet skal holdes oppdatert på aksjeprisene allerede fra det er instansiert. Dersom en indeks instansieres uten **Stock**-objekter, skal aksjeindeksen være 0.
- `void addStock(Stock)` - metode for å legge til en aksjepris i indeksen.
- `void removeStock(Stock)` - metode for å fjerne en aksjepris fra indeksen.
- `double getIndex()` - hentemetode for indeksen.
I tillegg må **StockIndex**-klassen selvsagt implementere **StockListener** og dermed også lyttermetoden **stockPriceChanged**, slik at indeksen kan holdes oppdatert.
Testkode for denne oppgaven finner du her: [oving6/observable/StockIndexTest.java](../../src/test/java/oving6/observable/StockIndexTest.java).
## Ekstraoppgaver
I en del sammenhenger vil du ikke være interessert i alle småendringer i en aksjepris, men interessert i endringer utenfor et visst område eller av en viss størrelse. Kanskje vil du kjøpe aksjer hvis det er billig nok, ønsker å selge dersom prisen blir høy nok eller ønsker å vite om større endringer som kan være signal om viktige prisendringer. Så for å unngå å sende ut mange uinteressante prisoppdateringer, er det aktuelt med to typer utvidelser av **Stock**-klassen. I begge tilfellene bruker men en egen **addStockListener**-metode for å registrere lytteren og hva slags endring man er interessert i. Implementér utvidelsen(e) i en subklasse som du kaller **SmartStock**. Merk at denne utvidelsen av **Stock** ikke er så relevant å bruke sammen med **StockIndex**, siden den da vil miste noen oppdateringer og dermed kunne risikere å være inkonsistent innimellom.
### Pris*intervall*
I denne utvidelsen skal du støtte lyttere som ønsker å få beskjed kun dersom **Stock**-objektets pris settes utenfor et gitt intervall. Følgende metode må implementeres:
- `void addStockListener(StockListener, double min, double max)` - metode som legger til lyttere med krav til prisintervall.
Lyttere som er registrert med denne metoden skal bare varsles dersom **Stock**-objektets pris endres til en verdi utenfor det angitte intervallet. Hint: Bruk en eller flere **Map<StockListener, Double>**-felt til å holde oversikt over intervallene, evt. definér en hjelpeklasse som har felt for **StockListener** og minimum- og maksimumsverdiene.
### Pris*differanse*
I denne utvidelsen skal du støtte lyttere som ønsker å få beskjed kun når akkumulerte endringer av **Stock**-objektets pris er større enn en gitt differanse. Følgende metode må implementeres:
- `void addStockListener(StockListener, double difference)` - metode som legger til lyttere med krav til prisdifferanse.
Et viktig poeng med denne er varianter er hvilke tidligere verdien som skal gis til lyttermetoden **stockPriceChanged** sitt andre argument. Denne verdien skal være den forrige verdien som ble rapportert, som kan være en annen enn den forrige prisverdien. Anta f.eks. at en lytter registreres med **10** som prisdifferanse og at aksjeprisen starter som **110** og så endres til **118** og videre til **121**. Da skal lyttermetoden **stockPriceChanged** kalles med **110** som gammel verdi og **121** som ny verdi, fordi dette sett fra lytterens perspektiv er forrige verdi den fikk vite om. En annen lytter som var registrert med prisdifferansen **5**, ville fått beskjed allerede ved første endring og da med **110** som gammel verdi og **118** som ny, men den ville ikke få beskjed om endringen fra **118** til **121**, fordi differansen da er for liten. Dersom prisen endrer seg videre til **124**, vil lytteren få beskjed og da med **118** som gammel verdi.
Testkode for denne oppgaven finner du her: [oving6/observable/SmartStockTest.java](../../src/test/java/oving6/observable/SmartStockTest.java).
/.AppendingIteratorTest.java._trace
/.EvenIteratorTest.java._trace
/.FilteringLoggerTest.java._trace
/.MergingIteratorTest.java._trace
/.SkippingIteratorTest.java._trace
/.StreamLoggerTest.java._trace
/.SummingIteratorTest.java._trace
/.DistributingLoggerTest.java._trace
package oving6.delegation;
import java.io.ByteArrayOutputStream;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class DistributingLoggerTest {
private ByteArrayOutputStream infoStream, warnStream, errorStream;
private StreamLogger infoLogger, warnLogger, errorLogger;
private DistributingLogger logger;
private static final String formatString = "%s: %s (%s)";
@BeforeEach
public void setup() {
infoStream = new ByteArrayOutputStream();
warnStream = new ByteArrayOutputStream();
errorStream = new ByteArrayOutputStream();
infoLogger = new StreamLogger(infoStream);
warnLogger = new StreamLogger(warnStream);
errorLogger = new StreamLogger(errorStream);
logger = new DistributingLogger(errorLogger, warnLogger, infoLogger);
}
@Test
@DisplayName("Logge til INFO")
public void testLogToInfo() {
infoLogger.setFormatString(formatString);
logger.log(ILogger.INFO, "Dette er en info-melding.", null);
Assertions.assertEquals(String.format(formatString, ILogger.INFO, "Dette er en info-melding.", null),
infoStream.toString().trim(), "Teste formatet på info-meldingen etter logging");
Assertions.assertEquals("", warnStream.toString(), "Teste warning-strømmen etter å ha logget en info-melding");
Assertions.assertEquals("", errorStream.toString(), "Teste error-strømmen etter å ha logget en info-melding");
}
@Test
@DisplayName("Logge til WARNING")
public void testLogToWarn() {
warnLogger.setFormatString(formatString);
logger.log(ILogger.WARNING, "Dette er en advarsel.", null);
Assertions.assertEquals(String.format(formatString, ILogger.WARNING, "Dette er en advarsel.", null),
warnStream.toString().trim(), "Teste formatet på warning-meldingen etter logging");
Assertions.assertEquals("", infoStream.toString(), "Teste info-strømmen etter å ha logget en info-melding");
Assertions.assertEquals("", errorStream.toString(), "Teste error-strømmen etter å ha logget en info-melding");
}
@Test
@DisplayName("Logge til ERROR")
public void testLogToError() {
Exception exception = new IllegalStateException();
errorLogger.setFormatString(formatString);
logger.log(ILogger.ERROR, "Dette er en feilmelding.", exception);
Assertions.assertEquals(String.format(formatString, ILogger.ERROR, "Dette er en feilmelding.", exception),
errorStream.toString().trim(), "Teste formatet på error-meldingen etter logging");
Assertions.assertEquals("", warnStream.toString(), "Teste warning-strømmen etter å ha logget en error-melding");
Assertions.assertEquals("", infoStream.toString(), "Teste info-strømmen etter å ha logget en error-melding");
}
@Test
@DisplayName("Endre info-logger")
public void testChangeInfoLogger() {
ByteArrayOutputStream newInfoStream = new ByteArrayOutputStream();
StreamLogger newInfoLogger = new StreamLogger(newInfoStream);
infoLogger.setFormatString(formatString);
logger.log(ILogger.INFO, "Dette er en info-melding.", null);
Assertions.assertEquals(String.format(formatString, ILogger.INFO, "Dette er en info-melding.", null),
infoStream.toString().trim(), "Teste formatet på info-melding etter logging");
newInfoLogger.setFormatString(formatString);
logger.setLogger(ILogger.INFO, newInfoLogger);
logger.log(ILogger.INFO, "Dette er den andre info-meldingen.", null);
Assertions.assertEquals(String.format(formatString, ILogger.INFO, "Dette er en info-melding.", null),
infoStream.toString().trim(),
"Teste første info-strøm etter å ha byttet infologger og logget enda en info-melding");
Assertions.assertEquals(String.format(formatString, ILogger.INFO, "Dette er den andre info-meldingen.", null),
newInfoStream.toString().trim(),
"Teste andre info-strøm etter å ha byttet infologget og logget ny info-melding");
}
}
package oving6.delegation;
import java.io.ByteArrayOutputStream;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class FilteringLoggerTest {
private ByteArrayOutputStream stream;
private StreamLogger subLogger;
@BeforeEach
public void setup() {
stream = new ByteArrayOutputStream();
subLogger = new StreamLogger(stream);
}
@Test
@DisplayName("Lage logger uten alvorlighetsgrader")
public void testFilteringLoggerNoSeverities() {
FilteringLogger logger = new FilteringLogger(subLogger);
Assertions.assertFalse(logger.isLogging(ILogger.INFO), "Teste logger uten alvorlighetsgrad for info-logging");
Assertions.assertFalse(logger.isLogging(ILogger.WARNING),
"Teste logger uten alvorlighetsgrad for warning-logging");
Assertions.assertFalse(logger.isLogging(ILogger.ERROR), "Teste logger uten alvorlighetsgrad for error-logging");
}
@Test
@DisplayName("Lage logger med error og info")
public void testFilteringLoggerErrorAndInfo() {
FilteringLogger logger = new FilteringLogger(subLogger, ILogger.ERROR, ILogger.INFO);
Assertions.assertTrue(logger.isLogging(ILogger.INFO), "Teste error- og info-logger for info-logging");
Assertions.assertFalse(logger.isLogging(ILogger.WARNING), "Teste error- og info-logger for warning-logging");
Assertions.assertTrue(logger.isLogging(ILogger.ERROR), "Teste error- og info-logger for error-logging");
}
@Test
@DisplayName("Lage logger med warning, sette error")
public void testFilteringLoggerWarningSetIsLoggingError() {
FilteringLogger logger = new FilteringLogger(subLogger, ILogger.WARNING);
Assertions.assertFalse(logger.isLogging(ILogger.INFO), "Teste warning-logger for info-logging");
Assertions.assertTrue(logger.isLogging(ILogger.WARNING), "Teste warning-logger for warning-logging");
Assertions.assertFalse(logger.isLogging(ILogger.ERROR), "Teste warning-logger for error-logging");
logger.setIsLogging(ILogger.ERROR, true);
Assertions.assertFalse(logger.isLogging(ILogger.INFO),
"Sette error-logging for warning-logger og teste for info-logging");
Assertions.assertTrue(logger.isLogging(ILogger.WARNING),
"Sette error-logging for warning-logger og teste for warning-logging");
Assertions.assertTrue(logger.isLogging(ILogger.ERROR),
"Sette error-logging for warning-logger og error for info-logging");
}
@Test
@DisplayName("Logger med alvorlighetsgrad ERROR")
public void testErrorLogging() {
IllegalStateException exception = new IllegalStateException();
FilteringLogger logger = new FilteringLogger(subLogger, ILogger.ERROR);
String formatString = "%s: %s (%s)";
subLogger.setFormatString(formatString);
logger.log(ILogger.ERROR, "Noe er feil!", exception);
Assertions.assertEquals(String.format(formatString, ILogger.ERROR, "Noe er feil!", exception),
stream.toString().trim(), "Teste formatet på error-melding etter logging");
}
@Test
@DisplayName("Logger med alvorlighetsgrad INFO i ERROR-logger")
public void testInfoToErrorLogger() {
IllegalStateException exception = new IllegalStateException();
FilteringLogger logger = new FilteringLogger(subLogger, ILogger.ERROR);
String formatString = "%s: %s (%s)";
subLogger.setFormatString(formatString);
logger.log(ILogger.INFO, "Noe er feil!", exception);
Assertions.assertEquals("", stream.toString(), "Teste strømmen etter å ha logget info-melding i error-logger");
}
}
package oving6.delegation;
import java.io.ByteArrayOutputStream;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class StreamLoggerTest {
private ByteArrayOutputStream stream;
private StreamLogger logger;
private static final String formatString = "%s: %s (%s)";
private static final String melding = "En melding ble logget!";
@BeforeEach
public void setup() {
stream = new ByteArrayOutputStream();
logger = new StreamLogger(stream);
}
@Test
@DisplayName("Logger infomelding")
public void testLog() {
logger.log(ILogger.INFO, melding, null);
Assertions.assertTrue(stream.toString().contains(melding),
"Teste at strømmen inneholder melding etter at den har blitt logget");
Assertions.assertTrue(stream.toString().contains(ILogger.INFO),
"Teste at strømmen inneholder melding av typen info etter den har blitt logget");
}
@Test
@DisplayName("Logger unntak")
public void testLogException() {
IllegalStateException exception = new IllegalStateException();
logger.setFormatString(formatString);
logger.log(ILogger.INFO, melding, exception);
Assertions.assertEquals(String.format(formatString, ILogger.INFO, melding, exception), stream.toString().trim(),
"Teste formatet på melding som ble logget med exception");
}
}
package oving6.delegation.office;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class ClerkTest {
private Printer printer;
private Clerk clerk;
@BeforeEach