Java fundamentals through coding exercises
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

719 lines
23 KiB

  1. # Toteuta painonhallintasovellus
  2. 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.
  3. 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.
  4. ## PainoSovellus
  5. Sisältää ohjelman valikon tulostamisen silmukassa. Toteuta esimerkiksi while-silmukalla.
  6. ## PainoMittaus
  7. 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.
  8. ## Pvm
  9. 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.
  10. 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.
  11. 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:
  12. ```
  13. public Pvm(String pvm) {
  14. String rivi = pvm;
  15. String[] pvmKirjaus = new String[3];
  16. pvmKirjaus = rivi.split("\\.");
  17. this.pp = Integer.parseInt(pvmKirjaus[0]);
  18. this.kk = Integer.parseInt(pvmKirjaus[1]);
  19. this.vv = Integer.parseInt(pvmKirjaus[2]);
  20. }
  21. ```
  22. ## Piirturi
  23. Luokka, joka kykenee skaalautuvasti tuottamaan näyttöä sopivasti käyttävän kuvaajan perustuen annettuun tietoon.
  24. ## TiedostoTyokalut
  25. Luokka, joka kykenee kirjoittamaan tiedostoon talteen mitattuja painoja.
  26. Painot pitää tallentaa tiedostoon nimeltään painotcsv.txt
  27. **Toteuta PainoSovellus-luokkaan metodi:**
  28. `public void tulostaValikko()`
  29. Metodi tulostaa numeroilla valittavan valikon.
  30. **Toteuta Piirturi-luokkaan metodi:**
  31. `public void piirraNaytolle()`
  32. Metodi tulostaa sopivasti skaalatun kuvaajan painon kehityksestä.
  33. **Toteuta TiedostoTyokalut-luokkaan metodi:**
  34. `public PainoMittaus[] lue()`
  35. 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.
  36. Ohjelmiston tulisi toimia muun muassa kuten seuraavassa.
  37. ```
  38. Valikko
  39. 0) Lopeta
  40. 1) Lisää painokirjaus menneelle päivälle
  41. 2) Tulosta painokuvaaja
  42. 3) Lisää painokirjaus tälle päivälle
  43. Anna valintasi (0, 1, 2 tai 3):
  44. 1
  45. Anna paino (muodossa 9.9.2019,85):
  46. 1.9.2019,52
  47. Valikko
  48. 0) Lopeta
  49. 1) Lisää painokirjaus menneelle päivälle
  50. 2) Tulosta painokuvaaja
  51. 3) Lisää painokirjaus tälle päivälle
  52. Anna valintasi (0, 1, 2 tai 3):
  53. 1
  54. Anna paino (muodossa 9.9.2019,85):
  55. 2.9.2019,48
  56. Valikko
  57. 0) Lopeta
  58. 1) Lisää painokirjaus menneelle päivälle
  59. 2) Tulosta painokuvaaja
  60. 3) Lisää painokirjaus tälle päivälle
  61. Anna valintasi (0, 1, 2 tai 3):
  62. 1
  63. Anna paino (muodossa 9.9.2019,85):
  64. 3.9.2019,49
  65. Valikko
  66. 0) Lopeta
  67. 1) Lisää painokirjaus menneelle päivälle
  68. 2) Tulosta painokuvaaja
  69. 3) Lisää painokirjaus tälle päivälle
  70. Anna valintasi (0, 1, 2 tai 3): 1
  71. Anna paino (muodossa 9.9.2019,85):
  72. 4.9.2019,55
  73. Valikko
  74. 0) Lopeta
  75. 1) Lisää painokirjaus menneelle päivälle
  76. 2) Tulosta painokuvaaja
  77. 3) Lisää painokirjaus tälle päivälle
  78. Anna valintasi (0, 1, 2 tai 3):
  79. 1
  80. Anna paino (muodossa 9.9.2019,85):
  81. 5.9.2019,48
  82. Valikko
  83. 0) Lopeta
  84. 1) Lisää painokirjaus menneelle päivälle
  85. 2) Tulosta painokuvaaja
  86. 3) Lisää painokirjaus tälle päivälle
  87. Anna valintasi (0, 1, 2 tai 3):
  88. 1
  89. Anna paino (muodossa 9.9.2019,85):
  90. 6.9.2019,53
  91. Valikko
  92. 0) Lopeta
  93. 1) Lisää painokirjaus menneelle päivälle
  94. 2) Tulosta painokuvaaja
  95. 3) Lisää painokirjaus tälle päivälle
  96. Anna valintasi (0, 1, 2 tai 3):
  97. 2
  98. ############
  99. ########
  100. #########
  101. ###############
  102. ########
  103. #############
  104. Valikko
  105. 0) Lopeta
  106. 1) Lisää painokirjaus menneelle päivälle
  107. 2) Tulosta painokuvaaja
  108. 3) Lisää painokirjaus tälle päivälle
  109. Anna valintasi (0, 1, 2 tai 3):
  110. 0
  111. ```
  112. **PainoSovellus.java**
  113. ```
  114. import java.io.*;
  115. import java.time.LocalDate;
  116. import java.time.format.DateTimeFormatter;
  117. import java.util.*;
  118. public class PainoSovellus {
  119. public static String tiedostopolku = System.getProperty("user.dir");
  120. public static String tiedosto = "painotcsv.txt";
  121. private static int painoMin = 40;
  122. private static int painoMax = 300;
  123. private static String piirturiSymbol = "#";
  124. // NOTE: Private preferred, as this is being read just after the application entry point
  125. private static TiedostoTyokalut tiedostoTyokalu = new TiedostoTyokalut(tiedostopolku, tiedosto);
  126. public static void main(String[] args) {
  127. while (true) {
  128. tulostaValikko();
  129. }
  130. }
  131. // Method prints a numerical menu with various options.
  132. public static void tulostaValikko() {
  133. String kayttajaArvo;
  134. Scanner syote = new Scanner(System.in);
  135. System.out.printf(
  136. "Valikko\n" +
  137. "0) Lopeta\n" +
  138. "1) Lisää painokirjaus menneelle päivälle\n" +
  139. "2) Tulosta painokuvaaja\n" +
  140. "3) Lisää painokirjaus tälle päivälle\n" +
  141. "Anna valintasi (0, 1, 2 tai 3):\n"
  142. );
  143. try {
  144. int valinta = syote.nextInt();
  145. switch (valinta) {
  146. case 0:
  147. System.exit(0);
  148. case 1:
  149. System.out.println("Anna paino (muodossa 9.9.2019,85):");
  150. kayttajaArvo = syote.next();
  151. tiedostoTyokalu.kirjoita(kayttajaArvo);
  152. break;
  153. case 2:
  154. //Piirturi piirturi = new Piirturi();
  155. Piirturi.piirraNaytolle();
  156. break;
  157. case 3:
  158. System.out.println("Anna paino (muodossa 85):");
  159. // Since this variable can also use syntax [date,weight] in case 1, we don't use nextInt() method.
  160. // Input check is done by TiedostoTyokalut.inputValidator() method.
  161. kayttajaArvo = syote.next();
  162. tiedostoTyokalu.kirjoita(kayttajaArvo);
  163. break;
  164. //////////
  165. default:
  166. System.err.println("Antamasi valinta ei ole kelvollinen");
  167. }
  168. } catch (Exception e) {
  169. System.err.printf("Luettu syöte ei ole kelvollinen. Syy:\n%s\n", e);
  170. // Do not exit the application here
  171. }
  172. }
  173. static class PainoMittaus {
  174. private Pvm pvm;
  175. private int paino;
  176. private Object[] data = new Object[2];
  177. public Pvm getPvm() {
  178. return this.pvm;
  179. }
  180. public int getPaino() {
  181. return this.paino;
  182. }
  183. public Object[] getObject() {
  184. this.data[0] = this.pvm;
  185. this.data[1] = this.paino;
  186. return this.data;
  187. }
  188. public PainoMittaus(Pvm pvm, int paino) {
  189. if (paino >= PainoSovellus.painoMin && paino <= PainoSovellus.painoMax) {
  190. this.paino = paino;
  191. this.pvm = pvm;
  192. }
  193. }
  194. }
  195. static class Pvm {
  196. // NOTE: These variables are not used in critical date checks.
  197. // WARNING:
  198. // Doing manual check for these is error-prone as we need to validate ranges of vuosi, then kuukausi
  199. // and then paiva
  200. // Therefore, the manual check methods were replaced with built-in Java date classes.
  201. // Old Pvm class code with manual checks is as commented-out attachment in this document
  202. private int vuosi;
  203. private int kuukausi;
  204. private int paiva;
  205. private LocalDate hetki;
  206. private DateTimeFormatter paivaMuoto = DateTimeFormatter.ofPattern("d.M.yyyy");
  207. public void setVuosi(int vuosi) {
  208. this.vuosi = vuosi;
  209. }
  210. public void setKuukausi(int kuukausi) {
  211. this.kuukausi = kuukausi;
  212. }
  213. public void setPaiva(int paiva) {
  214. this.paiva = paiva;
  215. }
  216. public int getVuosi() {
  217. return this.vuosi;
  218. }
  219. public int getKuukausi() {
  220. return this.kuukausi;
  221. }
  222. public int getPaiva() {
  223. return this.paiva;
  224. }
  225. public void setHetki(LocalDate hetki) {
  226. this.hetki = hetki;
  227. }
  228. public void setHetki(String hetki) {
  229. this.hetki = paivaTarkistaja(hetki);
  230. }
  231. public LocalDate getHetki() {
  232. return this.hetki;
  233. }
  234. // Parameterless constructor automatically gets the current day
  235. public Pvm() {
  236. this.hetki = LocalDate.now();
  237. }
  238. // Read day from user input string
  239. // Validate the format/syntax
  240. public Pvm(String paivamaara) {
  241. this.hetki = paivaTarkistaja(paivamaara);
  242. }
  243. private LocalDate paivaTarkistaja(String paivamaara) {
  244. LocalDate hetkiTestaaja = LocalDate.parse(paivamaara, paivaMuoto);
  245. LocalDate nykyHetki = LocalDate.now();
  246. // Can't add values for future days
  247. if (!hetkiTestaaja.isAfter(nykyHetki)) {
  248. return hetkiTestaaja;
  249. }
  250. // TODO add proper error message, avoid unclear NullPointerException
  251. return null;
  252. }
  253. @Override
  254. public String toString() {
  255. return this.hetki.format(paivaMuoto);
  256. }
  257. }
  258. static class Piirturi {
  259. public Piirturi() {}
  260. public static void piirraNaytolle() {
  261. PainoMittaus[] painot = PainoSovellus.tiedostoTyokalu.lue();
  262. List<Pvm> paivaArvot = new ArrayList<Pvm>();
  263. List<Integer> painoArvot = new ArrayList<Integer>();
  264. // Extract and map gathered painoMittaus objects
  265. for (int i = 0; i < painot.length; i++) {
  266. // These variables have been validated by TiedostoTyokalut.inputValidator
  267. // Mixed data types are not recommended in general in Java.
  268. Pvm paiva = (Pvm) painot[i].getObject()[0];
  269. int paino = (int) painot[i].getObject()[1];
  270. paivaArvot.add(paiva);
  271. painoArvot.add(paino);
  272. }
  273. tulostaDiagrammi(jarjestaArvot(paivaArvot, painoArvot));
  274. }
  275. // Sort mapped painoMittaus object [key,value] pairs by keys (dates)
  276. // Outputs values (weights) in sorted order
  277. private static List<Integer> jarjestaArvot(List<Pvm> paivaArvot, List<Integer> painoArvot) {
  278. List<LocalDate> paivaArvotValid = new ArrayList<LocalDate>();
  279. List<LocalDate> paivaErrors = new ArrayList<LocalDate>();
  280. Map<LocalDate, Integer> piirturiMap = new HashMap<LocalDate, Integer>();
  281. for (int i = 0; i < paivaArvot.size(); i++) {
  282. LocalDate syoteHetki = paivaArvot.get(i).getHetki();
  283. // This is not needed for Map data type as it automatically sorts out
  284. // duplicate keys. This condition was added to be more informative
  285. // for the end user about errors in value parsing operations
  286. if (paivaArvotValid.contains(syoteHetki)) {
  287. if (!paivaErrors.contains(syoteHetki)) {
  288. paivaErrors.add(syoteHetki);
  289. System.err.printf("Varoitus: päivälle %s on määritelty useampi painoarvo.\n" +
  290. "Hyväksytään ensimmäisenä luettu arvo.\n", syoteHetki);
  291. }
  292. }
  293. paivaArvotValid.add(syoteHetki);
  294. }
  295. for (int i = 0; i < paivaArvot.size(); i++) {
  296. piirturiMap.put(paivaArvot.get(i).getHetki(), painoArvot.get(i));
  297. }
  298. // Use TreeMap to sort key, value pairs by keys (dates in our case)
  299. Map<LocalDate, Integer> piirturiTreeMap = new TreeMap<LocalDate, Integer>(piirturiMap);
  300. // Java garbage collector
  301. paivaArvotValid = null;
  302. paivaErrors = null;
  303. return getValues(piirturiTreeMap);
  304. }
  305. // Get values from sorted input Map (sorted by date)
  306. private static <Key, Value> List<Value> getValues(Map<Key, Value> inMap) {
  307. List<Value> painoArvotSorted = new ArrayList<Value>();
  308. for (Map.Entry<Key, Value> entry : inMap.entrySet()) {
  309. painoArvotSorted.add(entry.getValue());
  310. }
  311. return painoArvotSorted;
  312. }
  313. private static void tulostaDiagrammi(List<Integer> syoteArvot) {
  314. List<String> hashSymbols = new ArrayList<String>();
  315. for (int i = 0; i < syoteArvot.size(); i++) {
  316. for (int a = 0; a < (syoteArvot.get(i) - PainoSovellus.painoMin); a++) {
  317. hashSymbols.add(PainoSovellus.piirturiSymbol);
  318. }
  319. System.out.println(String.join("", hashSymbols));
  320. hashSymbols.clear();
  321. }
  322. }
  323. }
  324. static class TiedostoTyokalut {
  325. private String tiedostoPolku, tiedosto;
  326. private File tiedostoKohde;
  327. public TiedostoTyokalut(String tiedostopolku, String tiedosto) {
  328. this.tiedostoPolku = tiedostopolku;
  329. this.tiedosto = tiedosto;
  330. }
  331. // Write a line to the file
  332. public void kirjoita(String newLine) throws Exception {
  333. try {
  334. // TODO Add charset encoding check (UTF-8)
  335. this.tiedostoKohde = new File(this.tiedostoPolku, this.tiedosto);
  336. FileWriter kirjoitin = new FileWriter(this.tiedostoKohde, true);
  337. // TODO Add proper error handling
  338. Object[] painoDataParsed = inputValidator(newLine, false).getObject();
  339. // Alternatively:
  340. // PainoMittaus painoDataParsed = inputValidator(newLine, false);
  341. // painoDataParsed.getPvm() + "," + painoDataParsed.getPaino();
  342. if (!painoDataParsed.toString().isEmpty()) {
  343. kirjoitin.append(painoDataParsed[0] + "," + painoDataParsed[1]);
  344. kirjoitin.append(System.getProperty("line.separator"));
  345. kirjoitin.close();
  346. }
  347. } catch (IOException o) {
  348. System.err.printf("Ei voitu luoda tai avata tiedostoa: %s (polku: %s)", this.tiedosto, this.tiedostoPolku);
  349. }
  350. }
  351. // Read lines from the file
  352. public PainoMittaus[] lue() {
  353. this.tiedostoKohde = new File(this.tiedostoPolku, this.tiedosto);
  354. try {
  355. FileReader lukija = new FileReader(tiedostoKohde);
  356. BufferedReader lukijaVirta = new BufferedReader(lukija);
  357. String lukijaRivi;
  358. int entryCount = 0;
  359. List<PainoMittaus> painoEntries = new ArrayList<PainoMittaus>();
  360. try {
  361. lukijaRivi = lukijaVirta.readLine();
  362. while (lukijaRivi != null) {
  363. // TODO Add proper error handling
  364. PainoMittaus painoData = inputValidator(lukijaRivi, true);
  365. // Skip invalid entries
  366. if (painoData == null) {
  367. System.err.println("Varoitus: luettu arvo oli tyhjä");
  368. continue;
  369. } else {
  370. painoEntries.add(painoData);
  371. entryCount++;
  372. //System.out.println((String.valueOf(painoData.getPaino())));
  373. }
  374. // Move to the next line
  375. lukijaRivi = lukijaVirta.readLine();
  376. }
  377. // Add object painoTauluOlio which contains PainoMittaus objects
  378. PainoMittaus[] painoTauluOlio = new PainoMittaus[entryCount];
  379. // Add PainoMittaus objects
  380. for (int i = 0; i < painoEntries.size(); i++) {
  381. painoTauluOlio[i] = painoEntries.get(i);
  382. }
  383. /*
  384. for (int s = 0; s < painoTauluOlio.length; s++) {
  385. System.out.println(painoTauluOlio[s]);
  386. }
  387. */
  388. return painoTauluOlio;
  389. } catch (IOException e) {
  390. System.err.printf("Ei voitu lukea tiedostoa: %s\n", this.tiedostoKohde);
  391. }
  392. } catch (FileNotFoundException e) {
  393. System.err.printf("Ei voitu avata tiedostoa: %s\n", this.tiedostoKohde);
  394. }
  395. // TODO add proper error message, avoid unclear NullPointerException
  396. return null;
  397. }
  398. private PainoMittaus inputValidator(String paivaPaino, boolean onLukuOperaatio) {
  399. List<String> inData = Arrays.asList(paivaPaino.split(","));
  400. Pvm nykyHetki = new Pvm();
  401. if (inData.size() == 2) {
  402. Pvm tamaHetki = new Pvm(inData.get(0));
  403. // Only past days accepted in write operations when [date,weight] input used
  404. if (!onLukuOperaatio && nykyHetki.equals(tamaHetki)) {
  405. // TODO add proper error message, avoid unclear NullPointerException
  406. return null;
  407. }
  408. return new PainoMittaus(tamaHetki, Integer.parseInt(inData.get(1)));
  409. // Date from current date
  410. } else if (inData.size() == 1) {
  411. return new PainoMittaus(nykyHetki, Integer.parseInt(paivaPaino));
  412. }
  413. // TODO add proper error message, avoid unclear NullPointerException
  414. return null;
  415. }
  416. }
  417. }
  418. ////////////////////////////////////////////////////////////
  419. // OLD Pvm class
  420. // Has manual check methods for input date validity (format and ranges of year, month and day)
  421. /*
  422. class Pvm {
  423. // NOTE: Using DateTimeFormatter without additional getter/setter filler code instead
  424. // is highly recommended! This code contains a lot of overhead due to task requirements
  425. // to have these attributes individually defined in this class.
  426. private int paiva;
  427. private int kuukausi;
  428. private int vuosi;
  429. private String hetki;
  430. private boolean isPast;
  431. private boolean isCurrentDate;
  432. private String formattedDate(int paiva, int kuukausi, int vuosi) {
  433. return paiva + "." + kuukausi + "." + vuosi;
  434. }
  435. private boolean inValueChecker(int input, int min, int max) {
  436. try {
  437. if (input < min || input > max) {
  438. //System.out.printf("min: %s, max: %s, input: %s", min,max,input);
  439. System.err.println("Annettu päivämäärä ei ole hyväksytyllä arvovälillä");
  440. return false;
  441. // TODO what to do next? Ask date again or abort execution?
  442. } else {
  443. return true;
  444. }
  445. } catch (InputMismatchException e) {
  446. System.err.println("Annettu päivämäärä ei ole kelvollisessa muodossa");
  447. }
  448. return false;
  449. }
  450. public boolean setVuosi(int vuosi) {
  451. if (this.inValueChecker(vuosi, PainoSovellus.minVuosi, LocalDate.now().getYear())) {
  452. this.vuosi = vuosi;
  453. return true;
  454. }
  455. return false;
  456. }
  457. public boolean setKuukausi(int kuukausi) {
  458. int maxMonth;
  459. if (this.getVuosi() < LocalDate.now().getYear()) {
  460. this.isPast = true;
  461. maxMonth = 12;
  462. } else {
  463. maxMonth = LocalDate.now().getMonthValue();
  464. this.isPast = false;
  465. }
  466. if (this.inValueChecker(kuukausi, PainoSovellus.minKuukausi, maxMonth)) {
  467. this.kuukausi = kuukausi;
  468. return true;
  469. }
  470. return false;
  471. }
  472. public boolean setPaiva(int paiva) {
  473. int maxDay;
  474. //if (this.getKuukausi() < LocalDate.now().getMonthValue()) {
  475. if(this.isPast) {
  476. switch(this.getKuukausi()) {
  477. case 2:
  478. maxDay = 29;
  479. break;
  480. case 4:
  481. case 6:
  482. case 9:
  483. case 11:
  484. maxDay = 30;
  485. break;
  486. default:
  487. maxDay = 31;
  488. }
  489. } else {
  490. // Exclude the current day. Should be yesterday, at maximum
  491. maxDay = LocalDate.now().getDayOfMonth() - 1;
  492. }
  493. if (this.inValueChecker(paiva, PainoSovellus.minPaiva, maxDay)) {
  494. this.paiva = paiva;
  495. return true;
  496. }
  497. return false;
  498. }
  499. public int getVuosi() {
  500. return this.vuosi;
  501. }
  502. public int getKuukausi() {
  503. return this.kuukausi;
  504. }
  505. public int getPaiva() {
  506. return this.paiva;
  507. }
  508. // Empty constructor
  509. public Pvm() {}
  510. // Date from LocalDate variable
  511. public Pvm(LocalDate date) {
  512. this.vuosi = date.getYear();
  513. this.kuukausi = date.getMonthValue();
  514. this.paiva = date.getDayOfMonth();
  515. this.hetki = this.formattedDate(this.paiva, this.kuukausi, this.vuosi);
  516. }
  517. // Date from manual input. Has additional checks
  518. public Pvm(int paiva, int kuukausi, int vuosi) {
  519. boolean isValidDate = false;
  520. if (this.setVuosi(vuosi)) {
  521. if (this.setKuukausi(kuukausi)) {
  522. if (this.setPaiva(paiva)) {
  523. isValidDate = true;
  524. }
  525. }
  526. if (isValidDate) {
  527. paiva = this.paiva;
  528. kuukausi = this.kuukausi;
  529. vuosi = this.vuosi;
  530. this.hetki = this.formattedDate(paiva, kuukausi, vuosi);
  531. }
  532. }
  533. }
  534. public String toString() {
  535. if (!this.hetki.isEmpty()) {
  536. return this.hetki;
  537. } else {
  538. return null;
  539. }
  540. }
  541. }
  542. */
  543. ```