|
|
- # Toteuta painonhallintasovellus
-
- Tarkoituksena on, että sovellukseen voi noin päivittäin syöttää oman painonsa ja painot tallentuvat myös tiedostoon. Painokehityksestä saa niin sanotulla ASCII grafiikalla piirrettyä kuvaajia käyttäen # merkkiä piirtosymbolina. Kuvassa uusimmat mittaukset näkyvät alimpana. Päivämäärät merkitään muodossa, josta tässä esimerkki: 9.9.2019. Eli päivä.kuukausi.vuosi.
-
- Tarkoitus on palauttaa vain yksi luokka PainoSovellus, jonka sisässä ovat muut tarvittavat luokat: PainoMittaus, Pvm, Piirturi ja TiedostoTyokalut. Yhden Java-luokan sisään voi laittaa muita luokkia siten, että ennen PainoMittaus-luokan viimeistä sulkevaa aaltosuljetta }, lisätään muut luokat omista tiedostoistaan siten, että "public class" alkuinen muoto on muodettu muotoon "class". Kaikki import-määreet, mitä eri luokissa tarvitaan laitetaan PainoSovellus-ohjelman alkuun.
-
-
- ## PainoSovellus
-
- Sisältää ohjelman valikon tulostamisen silmukassa. Toteuta esimerkiksi while-silmukalla.
-
- ## PainoMittaus
-
- Mallintaa yhtä painomittausta model-luokka, joka sisältää päivämäärän ja varsinaisen painon kiloina. Päivämäärä on luettavissa ja tuotettavissa Pvm-luokan avulla. Tee siis 1) yksityinen (private) attribuutti tietotyyppiä int, joka kertoo paljonko päivän paino oli 2) yksityinen (private) attribuutti tietotyyppiä Pvm (tarkoittaa paivamaaraa). Pvm-luokka toteutetaan seuraavassa kohdassa.
-
- ## Pvm
-
- Pvm-luokka (model-luokka), jossa on attribuutit: 1) yksityinen (private) attribuutti tietotyyppiä int, jossa on kuukauden päivä 2) yksityinen (private) attribuutti tietotyyppiä int, jossa on kuukauden numero 3) yksityinen (private) attribuutti tietotyyppiä int, jossa on vuoden arvo.
-
- Parametrittömän konstruktorin kautta voidaan luoda päivämäärä, jonka arvoksi tulee automaattisesti tällähetkellä kuluva päivä. Eli parametritön konstruktori public Pvm() { } asettaa sisällään attribuuttien arvoiksi tämänhetkisen päivämäärän.
-
- Parametrillinen konstruktori pystyy asettamaan päivämäärän arvot sille tulleesta merkkijonosta, eli se pystyy käsittelemään tilanteen, jossa käyttäjä antaa merkkijonona päivämääräksi esimerkiksi 10.3.2020. Java-koodissa tämä käsitellään esimerkiksi seuraavasti:
-
- ```
- public Pvm(String pvm) {
- String rivi = pvm;
- String[] pvmKirjaus = new String[3];
- pvmKirjaus = rivi.split("\\.");
- this.pp = Integer.parseInt(pvmKirjaus[0]);
- this.kk = Integer.parseInt(pvmKirjaus[1]);
- this.vv = Integer.parseInt(pvmKirjaus[2]);
- }
- ```
-
- ## Piirturi
-
- Luokka, joka kykenee skaalautuvasti tuottamaan näyttöä sopivasti käyttävän kuvaajan perustuen annettuun tietoon.
-
- ## TiedostoTyokalut
-
- Luokka, joka kykenee kirjoittamaan tiedostoon talteen mitattuja painoja.
-
- Painot pitää tallentaa tiedostoon nimeltään painotcsv.txt
-
- **Toteuta PainoSovellus-luokkaan metodi:**
-
- `public void tulostaValikko()`
-
- Metodi tulostaa numeroilla valittavan valikon.
-
- **Toteuta Piirturi-luokkaan metodi:**
-
- `public void piirraNaytolle()`
-
- Metodi tulostaa sopivasti skaalatun kuvaajan painon kehityksestä.
-
- **Toteuta TiedostoTyokalut-luokkaan metodi:**
-
- `public PainoMittaus[] lue()`
-
- Metodi pystyy lukemaan tiedostosta painotietoja ohjelman ymmärtämään muotoon, niin että ne saadaan osaksi kuvaajaa. Toisin sanoen palautetaan siis taulukko PainoMittaus-olioita, joissa kuhunkin olioon on asetettu päivämäärä ja painolukema.
-
- Ohjelmiston tulisi toimia muun muassa kuten seuraavassa.
-
- ```
- Valikko
- 0) Lopeta
- 1) Lisää painokirjaus menneelle päivälle
- 2) Tulosta painokuvaaja
- 3) Lisää painokirjaus tälle päivälle
- Anna valintasi (0, 1, 2 tai 3):
- 1
- Anna paino (muodossa 9.9.2019,85):
- 1.9.2019,52
- Valikko
- 0) Lopeta
- 1) Lisää painokirjaus menneelle päivälle
- 2) Tulosta painokuvaaja
- 3) Lisää painokirjaus tälle päivälle
- Anna valintasi (0, 1, 2 tai 3):
- 1
- Anna paino (muodossa 9.9.2019,85):
- 2.9.2019,48
- Valikko
- 0) Lopeta
- 1) Lisää painokirjaus menneelle päivälle
- 2) Tulosta painokuvaaja
- 3) Lisää painokirjaus tälle päivälle
- Anna valintasi (0, 1, 2 tai 3):
- 1
- Anna paino (muodossa 9.9.2019,85):
- 3.9.2019,49
- Valikko
- 0) Lopeta
- 1) Lisää painokirjaus menneelle päivälle
- 2) Tulosta painokuvaaja
- 3) Lisää painokirjaus tälle päivälle
- Anna valintasi (0, 1, 2 tai 3): 1
- Anna paino (muodossa 9.9.2019,85):
- 4.9.2019,55
- Valikko
- 0) Lopeta
- 1) Lisää painokirjaus menneelle päivälle
- 2) Tulosta painokuvaaja
- 3) Lisää painokirjaus tälle päivälle
- Anna valintasi (0, 1, 2 tai 3):
- 1
- Anna paino (muodossa 9.9.2019,85):
- 5.9.2019,48
- Valikko
- 0) Lopeta
- 1) Lisää painokirjaus menneelle päivälle
- 2) Tulosta painokuvaaja
- 3) Lisää painokirjaus tälle päivälle
- Anna valintasi (0, 1, 2 tai 3):
- 1
- Anna paino (muodossa 9.9.2019,85):
- 6.9.2019,53
- Valikko
- 0) Lopeta
- 1) Lisää painokirjaus menneelle päivälle
- 2) Tulosta painokuvaaja
- 3) Lisää painokirjaus tälle päivälle
- Anna valintasi (0, 1, 2 tai 3):
- 2
- ############
- ########
- #########
- ###############
- ########
- #############
- Valikko
- 0) Lopeta
- 1) Lisää painokirjaus menneelle päivälle
- 2) Tulosta painokuvaaja
- 3) Lisää painokirjaus tälle päivälle
- Anna valintasi (0, 1, 2 tai 3):
- 0
- ```
-
- **PainoSovellus.java**
-
- ```
- import java.io.*;
- import java.time.LocalDate;
- import java.time.format.DateTimeFormatter;
- import java.util.*;
-
- public class PainoSovellus {
-
- public static String tiedostopolku = System.getProperty("user.dir");
- public static String tiedosto = "painotcsv.txt";
-
- private static int painoMin = 40;
- private static int painoMax = 300;
-
- private static String piirturiSymbol = "#";
-
- // NOTE: Private preferred, as this is being read just after the application entry point
- private static TiedostoTyokalut tiedostoTyokalu = new TiedostoTyokalut(tiedostopolku, tiedosto);
-
- public static void main(String[] args) {
- while (true) {
- tulostaValikko();
- }
- }
-
- // Method prints a numerical menu with various options.
- public static void tulostaValikko() {
-
- String kayttajaArvo;
- Scanner syote = new Scanner(System.in);
-
- System.out.printf(
- "Valikko\n" +
- "0) Lopeta\n" +
- "1) Lisää painokirjaus menneelle päivälle\n" +
- "2) Tulosta painokuvaaja\n" +
- "3) Lisää painokirjaus tälle päivälle\n" +
- "Anna valintasi (0, 1, 2 tai 3):\n"
- );
- try {
- int valinta = syote.nextInt();
-
- switch (valinta) {
-
- case 0:
- System.exit(0);
-
- case 1:
- System.out.println("Anna paino (muodossa 9.9.2019,85):");
- kayttajaArvo = syote.next();
-
- tiedostoTyokalu.kirjoita(kayttajaArvo);
-
- break;
- case 2:
-
- //Piirturi piirturi = new Piirturi();
- Piirturi.piirraNaytolle();
-
- break;
- case 3:
- System.out.println("Anna paino (muodossa 85):");
-
- // Since this variable can also use syntax [date,weight] in case 1, we don't use nextInt() method.
- // Input check is done by TiedostoTyokalut.inputValidator() method.
- kayttajaArvo = syote.next();
- tiedostoTyokalu.kirjoita(kayttajaArvo);
-
- break;
- //////////
- default:
- System.err.println("Antamasi valinta ei ole kelvollinen");
- }
-
- } catch (Exception e) {
- System.err.printf("Luettu syöte ei ole kelvollinen. Syy:\n%s\n", e);
- // Do not exit the application here
- }
- }
-
- static class PainoMittaus {
-
- private Pvm pvm;
- private int paino;
-
- private Object[] data = new Object[2];
-
- public Pvm getPvm() {
- return this.pvm;
- }
-
- public int getPaino() {
- return this.paino;
- }
-
- public Object[] getObject() {
- this.data[0] = this.pvm;
- this.data[1] = this.paino;
- return this.data;
- }
-
- public PainoMittaus(Pvm pvm, int paino) {
- if (paino >= PainoSovellus.painoMin && paino <= PainoSovellus.painoMax) {
- this.paino = paino;
- this.pvm = pvm;
- }
- }
- }
-
- static class Pvm {
-
- // NOTE: These variables are not used in critical date checks.
-
- // WARNING:
- // Doing manual check for these is error-prone as we need to validate ranges of vuosi, then kuukausi
- // and then paiva
- // Therefore, the manual check methods were replaced with built-in Java date classes.
-
- // Old Pvm class code with manual checks is as commented-out attachment in this document
-
- private int vuosi;
- private int kuukausi;
- private int paiva;
-
- private LocalDate hetki;
- private DateTimeFormatter paivaMuoto = DateTimeFormatter.ofPattern("d.M.yyyy");
-
- public void setVuosi(int vuosi) {
- this.vuosi = vuosi;
- }
-
- public void setKuukausi(int kuukausi) {
- this.kuukausi = kuukausi;
- }
-
- public void setPaiva(int paiva) {
- this.paiva = paiva;
- }
-
- public int getVuosi() {
- return this.vuosi;
- }
-
- public int getKuukausi() {
- return this.kuukausi;
- }
-
- public int getPaiva() {
- return this.paiva;
- }
-
- public void setHetki(LocalDate hetki) {
- this.hetki = hetki;
- }
-
- public void setHetki(String hetki) {
- this.hetki = paivaTarkistaja(hetki);
- }
-
- public LocalDate getHetki() {
- return this.hetki;
- }
-
- // Parameterless constructor automatically gets the current day
- public Pvm() {
- this.hetki = LocalDate.now();
- }
-
- // Read day from user input string
- // Validate the format/syntax
- public Pvm(String paivamaara) {
- this.hetki = paivaTarkistaja(paivamaara);
- }
-
- private LocalDate paivaTarkistaja(String paivamaara) {
- LocalDate hetkiTestaaja = LocalDate.parse(paivamaara, paivaMuoto);
- LocalDate nykyHetki = LocalDate.now();
-
- // Can't add values for future days
- if (!hetkiTestaaja.isAfter(nykyHetki)) {
- return hetkiTestaaja;
- }
-
- // TODO add proper error message, avoid unclear NullPointerException
- return null;
- }
-
- @Override
- public String toString() {
- return this.hetki.format(paivaMuoto);
- }
-
- }
-
- static class Piirturi {
-
- public Piirturi() {}
-
- public static void piirraNaytolle() {
-
- PainoMittaus[] painot = PainoSovellus.tiedostoTyokalu.lue();
-
- List<Pvm> paivaArvot = new ArrayList<Pvm>();
- List<Integer> painoArvot = new ArrayList<Integer>();
-
- // Extract and map gathered painoMittaus objects
- for (int i = 0; i < painot.length; i++) {
-
- // These variables have been validated by TiedostoTyokalut.inputValidator
- // Mixed data types are not recommended in general in Java.
- Pvm paiva = (Pvm) painot[i].getObject()[0];
- int paino = (int) painot[i].getObject()[1];
-
- paivaArvot.add(paiva);
- painoArvot.add(paino);
-
- }
- tulostaDiagrammi(jarjestaArvot(paivaArvot, painoArvot));
- }
-
- // Sort mapped painoMittaus object [key,value] pairs by keys (dates)
- // Outputs values (weights) in sorted order
- private static List<Integer> jarjestaArvot(List<Pvm> paivaArvot, List<Integer> painoArvot) {
-
- List<LocalDate> paivaArvotValid = new ArrayList<LocalDate>();
- List<LocalDate> paivaErrors = new ArrayList<LocalDate>();
-
- Map<LocalDate, Integer> piirturiMap = new HashMap<LocalDate, Integer>();
-
- for (int i = 0; i < paivaArvot.size(); i++) {
-
- LocalDate syoteHetki = paivaArvot.get(i).getHetki();
-
- // This is not needed for Map data type as it automatically sorts out
- // duplicate keys. This condition was added to be more informative
- // for the end user about errors in value parsing operations
- if (paivaArvotValid.contains(syoteHetki)) {
- if (!paivaErrors.contains(syoteHetki)) {
- paivaErrors.add(syoteHetki);
- System.err.printf("Varoitus: päivälle %s on määritelty useampi painoarvo.\n" +
- "Hyväksytään ensimmäisenä luettu arvo.\n", syoteHetki);
- }
- }
- paivaArvotValid.add(syoteHetki);
- }
-
- for (int i = 0; i < paivaArvot.size(); i++) {
- piirturiMap.put(paivaArvot.get(i).getHetki(), painoArvot.get(i));
- }
-
- // Use TreeMap to sort key, value pairs by keys (dates in our case)
- Map<LocalDate, Integer> piirturiTreeMap = new TreeMap<LocalDate, Integer>(piirturiMap);
-
- // Java garbage collector
- paivaArvotValid = null;
- paivaErrors = null;
-
- return getValues(piirturiTreeMap);
-
- }
-
- // Get values from sorted input Map (sorted by date)
- private static <Key, Value> List<Value> getValues(Map<Key, Value> inMap) {
-
- List<Value> painoArvotSorted = new ArrayList<Value>();
-
- for (Map.Entry<Key, Value> entry : inMap.entrySet()) {
- painoArvotSorted.add(entry.getValue());
- }
-
- return painoArvotSorted;
- }
-
- private static void tulostaDiagrammi(List<Integer> syoteArvot) {
-
- List<String> hashSymbols = new ArrayList<String>();
-
- for (int i = 0; i < syoteArvot.size(); i++) {
-
- for (int a = 0; a < (syoteArvot.get(i) - PainoSovellus.painoMin); a++) {
- hashSymbols.add(PainoSovellus.piirturiSymbol);
- }
- System.out.println(String.join("", hashSymbols));
- hashSymbols.clear();
- }
-
- }
- }
-
- static class TiedostoTyokalut {
-
- private String tiedostoPolku, tiedosto;
- private File tiedostoKohde;
-
- public TiedostoTyokalut(String tiedostopolku, String tiedosto) {
- this.tiedostoPolku = tiedostopolku;
- this.tiedosto = tiedosto;
- }
-
- // Write a line to the file
- public void kirjoita(String newLine) throws Exception {
-
- try {
- // TODO Add charset encoding check (UTF-8)
- this.tiedostoKohde = new File(this.tiedostoPolku, this.tiedosto);
- FileWriter kirjoitin = new FileWriter(this.tiedostoKohde, true);
-
- // TODO Add proper error handling
- Object[] painoDataParsed = inputValidator(newLine, false).getObject();
- // Alternatively:
- // PainoMittaus painoDataParsed = inputValidator(newLine, false);
- // painoDataParsed.getPvm() + "," + painoDataParsed.getPaino();
-
- if (!painoDataParsed.toString().isEmpty()) {
- kirjoitin.append(painoDataParsed[0] + "," + painoDataParsed[1]);
- kirjoitin.append(System.getProperty("line.separator"));
- kirjoitin.close();
- }
-
- } catch (IOException o) {
- System.err.printf("Ei voitu luoda tai avata tiedostoa: %s (polku: %s)", this.tiedosto, this.tiedostoPolku);
- }
- }
-
- // Read lines from the file
- public PainoMittaus[] lue() {
-
- this.tiedostoKohde = new File(this.tiedostoPolku, this.tiedosto);
-
- try {
- FileReader lukija = new FileReader(tiedostoKohde);
- BufferedReader lukijaVirta = new BufferedReader(lukija);
- String lukijaRivi;
-
- int entryCount = 0;
- List<PainoMittaus> painoEntries = new ArrayList<PainoMittaus>();
-
- try {
-
- lukijaRivi = lukijaVirta.readLine();
-
- while (lukijaRivi != null) {
-
- // TODO Add proper error handling
- PainoMittaus painoData = inputValidator(lukijaRivi, true);
-
- // Skip invalid entries
- if (painoData == null) {
- System.err.println("Varoitus: luettu arvo oli tyhjä");
- continue;
- } else {
- painoEntries.add(painoData);
- entryCount++;
-
- //System.out.println((String.valueOf(painoData.getPaino())));
-
- }
- // Move to the next line
- lukijaRivi = lukijaVirta.readLine();
- }
-
- // Add object painoTauluOlio which contains PainoMittaus objects
- PainoMittaus[] painoTauluOlio = new PainoMittaus[entryCount];
-
- // Add PainoMittaus objects
- for (int i = 0; i < painoEntries.size(); i++) {
- painoTauluOlio[i] = painoEntries.get(i);
- }
-
- /*
- for (int s = 0; s < painoTauluOlio.length; s++) {
- System.out.println(painoTauluOlio[s]);
- }
- */
-
- return painoTauluOlio;
-
- } catch (IOException e) {
- System.err.printf("Ei voitu lukea tiedostoa: %s\n", this.tiedostoKohde);
- }
-
- } catch (FileNotFoundException e) {
- System.err.printf("Ei voitu avata tiedostoa: %s\n", this.tiedostoKohde);
- }
-
- // TODO add proper error message, avoid unclear NullPointerException
- return null;
-
- }
-
- private PainoMittaus inputValidator(String paivaPaino, boolean onLukuOperaatio) {
-
- List<String> inData = Arrays.asList(paivaPaino.split(","));
- Pvm nykyHetki = new Pvm();
-
- if (inData.size() == 2) {
-
- Pvm tamaHetki = new Pvm(inData.get(0));
-
- // Only past days accepted in write operations when [date,weight] input used
- if (!onLukuOperaatio && nykyHetki.equals(tamaHetki)) {
-
- // TODO add proper error message, avoid unclear NullPointerException
- return null;
- }
-
- return new PainoMittaus(tamaHetki, Integer.parseInt(inData.get(1)));
-
- // Date from current date
- } else if (inData.size() == 1) {
- return new PainoMittaus(nykyHetki, Integer.parseInt(paivaPaino));
- }
-
- // TODO add proper error message, avoid unclear NullPointerException
- return null;
-
- }
-
- }
-
- }
-
- ////////////////////////////////////////////////////////////
-
- // OLD Pvm class
- // Has manual check methods for input date validity (format and ranges of year, month and day)
-
- /*
- class Pvm {
-
- // NOTE: Using DateTimeFormatter without additional getter/setter filler code instead
- // is highly recommended! This code contains a lot of overhead due to task requirements
- // to have these attributes individually defined in this class.
- private int paiva;
- private int kuukausi;
- private int vuosi;
-
- private String hetki;
- private boolean isPast;
- private boolean isCurrentDate;
-
- private String formattedDate(int paiva, int kuukausi, int vuosi) {
-
- return paiva + "." + kuukausi + "." + vuosi;
-
- }
-
- private boolean inValueChecker(int input, int min, int max) {
- try {
- if (input < min || input > max) {
- //System.out.printf("min: %s, max: %s, input: %s", min,max,input);
- System.err.println("Annettu päivämäärä ei ole hyväksytyllä arvovälillä");
- return false;
- // TODO what to do next? Ask date again or abort execution?
- } else {
- return true;
- }
- } catch (InputMismatchException e) {
- System.err.println("Annettu päivämäärä ei ole kelvollisessa muodossa");
- }
- return false;
- }
-
- public boolean setVuosi(int vuosi) {
- if (this.inValueChecker(vuosi, PainoSovellus.minVuosi, LocalDate.now().getYear())) {
- this.vuosi = vuosi;
- return true;
- }
- return false;
- }
-
- public boolean setKuukausi(int kuukausi) {
-
- int maxMonth;
- if (this.getVuosi() < LocalDate.now().getYear()) {
- this.isPast = true;
- maxMonth = 12;
- } else {
- maxMonth = LocalDate.now().getMonthValue();
- this.isPast = false;
- }
-
- if (this.inValueChecker(kuukausi, PainoSovellus.minKuukausi, maxMonth)) {
- this.kuukausi = kuukausi;
- return true;
- }
- return false;
- }
-
- public boolean setPaiva(int paiva) {
-
- int maxDay;
- //if (this.getKuukausi() < LocalDate.now().getMonthValue()) {
- if(this.isPast) {
- switch(this.getKuukausi()) {
- case 2:
- maxDay = 29;
- break;
- case 4:
- case 6:
- case 9:
- case 11:
- maxDay = 30;
- break;
- default:
- maxDay = 31;
- }
- } else {
- // Exclude the current day. Should be yesterday, at maximum
- maxDay = LocalDate.now().getDayOfMonth() - 1;
- }
-
- if (this.inValueChecker(paiva, PainoSovellus.minPaiva, maxDay)) {
- this.paiva = paiva;
- return true;
- }
- return false;
- }
-
- public int getVuosi() {
- return this.vuosi;
- }
-
- public int getKuukausi() {
- return this.kuukausi;
- }
-
- public int getPaiva() {
- return this.paiva;
- }
-
- // Empty constructor
- public Pvm() {}
-
- // Date from LocalDate variable
- public Pvm(LocalDate date) {
- this.vuosi = date.getYear();
- this.kuukausi = date.getMonthValue();
- this.paiva = date.getDayOfMonth();
-
- this.hetki = this.formattedDate(this.paiva, this.kuukausi, this.vuosi);
- }
-
- // Date from manual input. Has additional checks
- public Pvm(int paiva, int kuukausi, int vuosi) {
-
- boolean isValidDate = false;
-
- if (this.setVuosi(vuosi)) {
- if (this.setKuukausi(kuukausi)) {
- if (this.setPaiva(paiva)) {
- isValidDate = true;
- }
- }
-
- if (isValidDate) {
- paiva = this.paiva;
- kuukausi = this.kuukausi;
- vuosi = this.vuosi;
-
- this.hetki = this.formattedDate(paiva, kuukausi, vuosi);
- }
- }
- }
-
-
- public String toString() {
- if (!this.hetki.isEmpty()) {
- return this.hetki;
- } else {
- return null;
- }
- }
-
- }
- */
- ```
|