{"id":"writing-files-end-of-part-11","slug":"end-of-part-11","title":"The end of part 11","text":"This step ends this module, and with it the MOOC's part 11. First a look back at the whole part, and then a last program that puts its class diagrams, exceptions and files to work together.\n\n### What part 11 covered\n\n- **Module 40, class diagrams.** A UML class diagram shows a program's classes as boxes: the name, the fields, and the constructors and methods, with `-` for private and `+` for public, and the types after colons. An arrow shows that one class refers to another, with a multiplicity such as `1`, `0..1` or `*` at its end, and a line with a hollow triangle shows inheritance, dashed for a class that implements an interface. You wrote classes from boxes, and drew boxes from classes.\n- **Module 41, packages.** Classes are grouped in packages, which are folders: a file in `quiz/domain/` starts with `package quiz.domain;`. A class of another package is used by its full name or after an `import`, and from another package a class sees only what's `public` (and a subclass also what's `protected`). Larger programs split their classes into packages by what they're responsible for, such as `domain`, `logic` and `ui`.\n- **Module 42, exceptions.** `try` and `catch`, several `catch` blocks and `finally`; checked exceptions, which must be caught or declared with `throws`, and unchecked ones, which needn't be; try-with-resources; `throw new IllegalArgumentException(...)` to refuse a bad parameter; and reading a stack trace.\n- **Module 43, writing files.** A PrintWriter writes a file and replaces what it held, a FileWriter with `true` appends to it, and `close()`, or better a try-with-resources, makes sure the text reaches the file. Objects go into a file one per line, with a separator that reading can split at, and a class with `save` and `load` keeps its data from one run to the next.\n\n### Other ways to write\n\nJava has more ways to write a file than PrintWriter. One is `Files.write`, the writing twin of module 42's `Files.readAllLines`: `Files.write(Paths.get(\"todo.txt\"), tasks)` writes every String of the list `tasks` into the file, one per line, in a single call. This course sticks with PrintWriter, which works just like `System.out`, but you may meet the others in code that other people write.\n\n### The end of part 11\n\nPart 12 comes next, starting with type parameters (generics): classes of your own that take a type as a parameter, as ArrayList does in `ArrayList<String>`. Part 11 ends with [the MOOC's conclusion page](https://java-programming.mooc.fi/part-11/5-conclusion) on its own site.\n","task":"A last program for part 11: a notes app that keeps its notes in a file, one note per line, with the note's title and text separated by `;`, like `Door code;4417`. Here's its plan:\n\n\n<figure class=\"uml\"><div class=\"uml-scroll\" tabindex=\"0\" role=\"group\" aria-label=\"Class diagram of Notebook and Note\">\n<svg class=\"uml-svg\" viewBox=\"0 0 273 328\" width=\"273\" height=\"328\" style=\"width:17.07rem;min-width:15.65rem;aspect-ratio:273/328\" font-size=\"12\" aria-hidden=\"true\" focusable=\"false\"><path class=\"u-edge\" fill=\"none\" d=\"M136.5 142L136.5 186\"/><path class=\"u-edge\" fill=\"none\" d=\"M131 176L136.5 186L142 176\"/><rect class=\"u-bg\" fill=\"none\" x=\"24\" y=\"6\" width=\"225\" height=\"136\"/><rect class=\"u-head\" fill=\"none\" x=\"24\" y=\"6\" width=\"225\" height=\"24\"/><text x=\"136.5\" y=\"22\" text-anchor=\"middle\" class=\"u-name\">Notebook</text><text x=\"32\" y=\"54\">+Notebook()</text><text x=\"32\" y=\"70\">+add(note: Note): void</text><text x=\"32\" y=\"86\">+count(): int</text><text x=\"32\" y=\"102\">+printAll(): void</text><text x=\"32\" y=\"118\">+save(fileName: String): void</text><text x=\"32\" y=\"134\">+load(fileName: String): void</text><path class=\"u-sep\" fill=\"none\" d=\"M24 30h225M24 38h225\"/><rect class=\"u-frame\" fill=\"none\" x=\"24\" y=\"6\" width=\"225\" height=\"136\"/><rect class=\"u-bg\" fill=\"none\" x=\"6\" y=\"186\" width=\"261\" height=\"136\"/><rect class=\"u-head\" fill=\"none\" x=\"6\" y=\"186\" width=\"261\" height=\"24\"/><text x=\"136.5\" y=\"202\" text-anchor=\"middle\" class=\"u-name\">Note</text><text x=\"14\" y=\"226\">-title: String</text><text x=\"14\" y=\"242\">-text: String</text><text x=\"14\" y=\"266\">+Note(title: String, text: String)</text><text x=\"14\" y=\"282\">+getTitle(): String</text><text x=\"14\" y=\"298\">+getText(): String</text><text x=\"14\" y=\"314\">+toString(): String</text><path class=\"u-sep\" fill=\"none\" d=\"M6 210h261M6 250h261\"/><rect class=\"u-frame\" fill=\"none\" x=\"6\" y=\"186\" width=\"261\" height=\"136\"/><text x=\"144.5\" y=\"181\" class=\"u-mult\">*</text><text x=\"130.5\" y=\"168\" text-anchor=\"end\" class=\"u-role\">notes</text></svg>\n</div><details class=\"uml-text\"><summary>The diagram as text</summary><ul><li><strong>Notebook</strong>, a class<ul><li>public constructor <code>Notebook()</code></li><li>public method <code>add(note: Note): void</code></li><li>public method <code>count(): int</code></li><li>public method <code>printAll(): void</code></li><li>public method <code>save(fileName: String): void</code></li><li>public method <code>load(fileName: String): void</code></li></ul></li><li><strong>Note</strong>, a class<ul><li>private field <code>title: String</code></li><li>private field <code>text: String</code></li><li>public constructor <code>Note(title: String, text: String)</code></li><li>public method <code>getTitle(): String</code></li><li>public method <code>getText(): String</code></li><li>public method <code>toString(): String</code></li></ul></li></ul><p>Connections:</p><ul><li>Each Notebook has any number of Note objects (notes).</li></ul></details></figure>\n\n\nMost of it is written. What's left:\n\n- In `Note.java`, a title with a `;` in it would break the file's format, so the constructor refuses one: it throws an IllegalArgumentException with the message `a title can't contain ;`. A note's text may contain `;`.\n- In `Notebook.java`, `save` writes every note as its title, `;` and its text, one per line, replacing what the file held. `load` reads such a file and adds a note for each line to the notebook. The title ends at the line's first `;`, and everything after that `;` is the text. Both pass exceptions on with `throws Exception`.\n\nA note prints as its title, a colon and its text. The check loads the file shown below, adds a shopping note, saves the notebook into the same file and prints every note:\n\n```\nWifi: the password is on the router\nDoor code: 4417\nShopping: milk; eggs; bread\n```\n\nand `notes.txt` then holds the three notes, the new one as `Shopping;milk; eggs; bread`.\n","kind":"code","seed":"// ==== Notebook.java ====\nimport java.io.PrintWriter;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Scanner;\n\npublic class Notebook {\n    private ArrayList<Note> notes;\n\n    public Notebook() {\n        this.notes = new ArrayList<>();\n    }\n\n    public void add(Note note) {\n        this.notes.add(note);\n    }\n\n    public int count() {\n        return this.notes.size();\n    }\n\n    public void printAll() {\n        for (Note note : this.notes) {\n            System.out.println(note);\n        }\n    }\n\n    // Write the methods save and load here.\n}\n\n// ==== Note.java ====\npublic class Note {\n    private String title;\n    private String text;\n\n    public Note(String title, String text) {\n        this.title = title;\n        this.text = text;\n    }\n\n    public String getTitle() {\n        return this.title;\n    }\n\n    public String getText() {\n        return this.text;\n    }\n\n    @Override\n    public String toString() {\n        return this.title + \": \" + this.text;\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) throws Exception {\n        Notebook notebook = new Notebook();\n        notebook.load(\"notes.txt\");\n        notebook.add(new Note(\"Shopping\", \"milk; eggs; bread\"));\n        notebook.save(\"notes.txt\");\n        notebook.printAll();\n    }\n}\n","solution":"// ==== Notebook.java ====\nimport java.io.PrintWriter;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Scanner;\n\npublic class Notebook {\n    private ArrayList<Note> notes;\n\n    public Notebook() {\n        this.notes = new ArrayList<>();\n    }\n\n    public void add(Note note) {\n        this.notes.add(note);\n    }\n\n    public int count() {\n        return this.notes.size();\n    }\n\n    public void printAll() {\n        for (Note note : this.notes) {\n            System.out.println(note);\n        }\n    }\n\n    public void save(String fileName) throws Exception {\n        try (PrintWriter writer = new PrintWriter(fileName)) {\n            for (Note note : this.notes) {\n                writer.println(note.getTitle() + \";\" + note.getText());\n            }\n        }\n    }\n\n    public void load(String fileName) throws Exception {\n        try (Scanner reader = new Scanner(Paths.get(fileName))) {\n            while (reader.hasNextLine()) {\n                String line = reader.nextLine();\n                int at = line.indexOf(\";\");\n                this.notes.add(new Note(line.substring(0, at), line.substring(at + 1)));\n            }\n        }\n    }\n}\n\n// ==== Note.java ====\npublic class Note {\n    private String title;\n    private String text;\n\n    public Note(String title, String text) {\n        if (title.contains(\";\")) {\n            throw new IllegalArgumentException(\"a title can't contain ;\");\n        }\n        this.title = title;\n        this.text = text;\n    }\n\n    public String getTitle() {\n        return this.title;\n    }\n\n    public String getText() {\n        return this.text;\n    }\n\n    @Override\n    public String toString() {\n        return this.title + \": \" + this.text;\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) throws Exception {\n        Notebook notebook = new Notebook();\n        notebook.load(\"notes.txt\");\n        notebook.add(new Note(\"Shopping\", \"milk; eggs; bread\"));\n        notebook.save(\"notes.txt\");\n        notebook.printAll();\n    }\n}\n","hints":["Note's constructor checks the title with `contains` and throws before it stores anything. `save` works like TodoList's `save` in step 4, writing the title, a `;` and the text of each note. `load` needs only the line's first `;`, since the text may contain more.","In Note's constructor: `if (title.contains(\";\")) { throw new IllegalArgumentException(\"a title can't contain ;\"); }`. `save` writes `note.getTitle() + \";\" + note.getText()`. `load` can't use `split(\";\")`, since the text may contain `;` too. Find the first one with `int at = line.indexOf(\";\");`: the title is `line.substring(0, at)`, and the text is `line.substring(at + 1)`."],"tests":[{"name":"Test 1","stdin":"","call":"Notebook notebook = new Notebook();\nnotebook.load(\"notes.txt\");\nnotebook.add(new Note(\"Shopping\", \"milk; eggs; bread\"));\nnotebook.save(\"notes.txt\");\nnotebook.printAll();\n","files":{"notes.txt":"Wifi;the password is on the router\nDoor code;4417\n"},"writes":{"notes.txt":"Wifi;the password is on the router\nDoor code;4417\nShopping;milk; eggs; bread"},"expect":"Wifi: the password is on the router\nDoor code: 4417\nShopping: milk; eggs; bread","hidden":false},{"name":"Test 2","stdin":"","call":"Notebook notebook = new Notebook();\nnotebook.load(\"notes.txt\");\nnotebook.printAll();\nSystem.out.println(notebook.count());\nnotebook.save(\"copy.txt\");\n","files":{"notes.txt":"Plants;water on Sundays; not on Mondays\nBike;tires at 4 bar\nRecipe;flour;sugar;butter\n"},"writes":{"copy.txt":"Plants;water on Sundays; not on Mondays\nBike;tires at 4 bar\nRecipe;flour;sugar;butter"},"expect":"Plants: water on Sundays; not on Mondays\nBike: tires at 4 bar\nRecipe: flour;sugar;butter\n3","hidden":true},{"name":"Test 3","stdin":"","call":"try {\n    new Note(\"Bank;PIN\", \"1234\");\n    System.out.println(\"accepted\");\n} catch (IllegalArgumentException e) {\n    System.out.println(e.getMessage());\n}\nNote fine = new Note(\"Recipe\", \"flour; sugar\");\nSystem.out.println(fine);\n","expect":"a title can't contain ;\nRecipe: flour; sugar","hidden":true},{"name":"Test 4","stdin":"","call":"Notebook notebook = new Notebook();\nnotebook.add(new Note(\"A\", \"first\"));\nnotebook.add(new Note(\"B\", \"second\"));\nnotebook.save(\"mine.txt\");\nnotebook.add(new Note(\"C\", \"third\"));\nnotebook.save(\"mine.txt\");\nSystem.out.println(notebook.count());\n","writes":{"mine.txt":"A;first\nB;second\nC;third"},"expect":"3","hidden":true}],"require":[],"forbid":[],"more":[{"task":"A last look at exceptions and files together. `save` throws an IllegalArgumentException for an empty item, and one of the files is to go into a folder that doesn't exist. `Files.readAllLines` returns a file's lines in a list. What does the program print?\n","kind":"predict","seed":"import java.io.FileNotFoundException;\nimport java.io.PrintWriter;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\n\npublic class Main {\n    public static void main(String[] args) throws Exception {\n        String[] items = {\"eggs\", \"\", \"milk\"};\n        save(\"list.txt\", items);\n        save(\"shop/list.txt\", items);\n        System.out.println(Files.readAllLines(Paths.get(\"list.txt\")));\n    }\n\n    public static void save(String fileName, String[] items) {\n        try (PrintWriter writer = new PrintWriter(fileName)) {\n            for (String item : items) {\n                if (item.isEmpty()) {\n                    throw new IllegalArgumentException(\"empty item\");\n                }\n                writer.println(item);\n            }\n            System.out.println(\"saved \" + fileName);\n        } catch (IllegalArgumentException e) {\n            System.out.println(\"stopped: \" + e.getMessage());\n        } catch (FileNotFoundException e) {\n            System.out.println(\"no folder for \" + fileName);\n        } finally {\n            System.out.println(\"done\");\n        }\n    }\n}\n","solution":"import java.io.FileNotFoundException;\nimport java.io.PrintWriter;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\n\npublic class Main {\n    public static void main(String[] args) throws Exception {\n        String[] items = {\"eggs\", \"\", \"milk\"};\n        save(\"list.txt\", items);\n        save(\"shop/list.txt\", items);\n        System.out.println(Files.readAllLines(Paths.get(\"list.txt\")));\n    }\n\n    public static void save(String fileName, String[] items) {\n        try (PrintWriter writer = new PrintWriter(fileName)) {\n            for (String item : items) {\n                if (item.isEmpty()) {\n                    throw new IllegalArgumentException(\"empty item\");\n                }\n                writer.println(item);\n            }\n            System.out.println(\"saved \" + fileName);\n        } catch (IllegalArgumentException e) {\n            System.out.println(\"stopped: \" + e.getMessage());\n        } catch (FileNotFoundException e) {\n            System.out.println(\"no folder for \" + fileName);\n        } finally {\n            System.out.println(\"done\");\n        }\n    }\n}\n","hints":["In the first call, `eggs` is written, and then the empty item throws. The writer is closed on the way out, the first `catch` block prints its line, and `finally` prints `done`. `saved list.txt` is never printed.","In the second call, creating the PrintWriter already throws a FileNotFoundException, since there's no folder `shop`. Last, `list.txt` holds the one line written before the problem."],"tests":[{"name":"Output","stdin":"","expect":"stopped: empty item\ndone\nno folder for shop/list.txt\ndone\n[eggs]","hidden":false}],"require":[],"forbid":[],"lines":["stopped: empty item","done","no folder for shop/list.txt","done","[eggs]"]},{"task":"Two roommates keep their own shopping lists, `mine.txt` and `yours.txt`, one item per line in small letters. Write the method `merge(String first, String second, String target)`, which writes every item of the files `first` and `second` into the file `target`, each item only once, in alphabetical order. The method prints nothing, and passes exceptions on with `throws Exception`.\n\nThe check runs `merge(\"mine.txt\", \"yours.txt\", \"shared.txt\");` with the files shown below, and after it `shared.txt` should hold:\n\n```text\napples\nbread\ncoffee\nmilk\n```\n","kind":"code","seed":"import java.io.PrintWriter;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Collections;\n\npublic class Main {\n    public static void main(String[] args) throws Exception {\n        merge(\"mine.txt\", \"yours.txt\", \"shared.txt\");\n    }\n\n    // Write the method merge here.\n}\n","solution":"import java.io.PrintWriter;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Collections;\n\npublic class Main {\n    public static void main(String[] args) throws Exception {\n        merge(\"mine.txt\", \"yours.txt\", \"shared.txt\");\n    }\n\n    public static void merge(String first, String second, String target) throws Exception {\n        ArrayList<String> items = new ArrayList<>();\n        for (String item : Files.readAllLines(Paths.get(first))) {\n            if (!items.contains(item)) {\n                items.add(item);\n            }\n        }\n        for (String item : Files.readAllLines(Paths.get(second))) {\n            if (!items.contains(item)) {\n                items.add(item);\n            }\n        }\n        Collections.sort(items);\n        try (PrintWriter writer = new PrintWriter(target)) {\n            for (String item : items) {\n                writer.println(item);\n            }\n        }\n    }\n}\n","hints":["Collect the items of both files into one list, adding an item only when the list doesn't contain it yet. `Files.readAllLines(Paths.get(first))` returns a file's lines as a list you can loop over.","`Collections.sort(items)` puts the list in alphabetical order. Then write it with a PrintWriter in a try-with-resources, one item per line."],"tests":[{"name":"merge(\"mine.txt\", \"yours.txt\", \"shared.txt\");","stdin":"","call":"merge(\"mine.txt\", \"yours.txt\", \"shared.txt\");\n","files":{"mine.txt":"milk\nbread\napples\n","yours.txt":"coffee\nmilk\n"},"writes":{"shared.txt":"apples\nbread\ncoffee\nmilk"},"expect":"","hidden":false},{"name":"merge(\"mine.txt\", \"yours.txt\", \"together.txt\");","stdin":"","call":"merge(\"mine.txt\", \"yours.txt\", \"together.txt\");\n","files":{"mine.txt":"tea\nrice\ntea\n","yours.txt":"rice\n"},"writes":{"together.txt":"rice\ntea"},"expect":"","hidden":true},{"name":"merge(\"a.txt\", \"b.txt\", \"all.txt\");","stdin":"","call":"merge(\"a.txt\", \"b.txt\", \"all.txt\");\n","files":{"a.txt":"pears\n","b.txt":"oats\nlemons\nfigs\npears\nhoney\n"},"writes":{"all.txt":"figs\nhoney\nlemons\noats\npears"},"expect":"","hidden":true}],"require":[],"forbid":[]}]}