{"id":"libraries-and-tools-jdbc","slug":"databases","title":"A real database with JDBC","text":"The DAO of the last two steps forgets everything when the program ends. A **database** keeps data safe on disk between runs, lets many programs use it at once, and finds rows fast even when there are millions of them. Most databases are used with **SQL**, a language of its own for asking for data (`SELECT`), adding it (`INSERT`), changing it (`UPDATE`) and removing it (`DELETE`).\n\nEverything in this step runs on your own computer only. Java Arena has no database and no database library, so the Java in this step is code to read, and the challenges at the end use only the JDK.\n\n### JDBC and H2\n\n**JDBC** (Java Database Connectivity) is the part of the JDK that talks to databases: the interfaces `Connection`, `PreparedStatement` and `ResultSet` and the class `DriverManager`, in the package `java.sql`. JDBC is the same for every database. What differs is the **driver**, a library from the database's makers that does the real work for their database.\n\n**H2** is a small database written in Java. Its driver and the whole database come in one jar, so it needs no installing: add its dependency to pom.xml (step 1), and a program can create a database in a file of its own.\n\n```xml\n<dependency>\n    <groupId>com.h2database</groupId>\n    <artifactId>h2</artifactId>\n    <version>2.2.224</version>\n</dependency>\n```\n\n### A DAO that uses a database\n\nThe repair queue's DAO, written with JDBC. It implements the same `RepairJobDao` as the DAO in memory:\n\n```java\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class JdbcRepairJobDao implements RepairJobDao {\n    private String url;\n\n    public JdbcRepairJobDao(String url) {\n        this.url = url;\n        String sql = \"CREATE TABLE IF NOT EXISTS RepairJob (\"\n                + \"id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, \"\n                + \"bike VARCHAR(100), problem VARCHAR(200), ready BOOLEAN)\";\n        try (Connection connection = connect();\n                PreparedStatement statement = connection.prepareStatement(sql)) {\n            statement.executeUpdate();\n        } catch (SQLException e) {\n            throw new IllegalStateException(\"Couldn't create the table\", e);\n        }\n    }\n\n    private Connection connect() throws SQLException {\n        return DriverManager.getConnection(this.url, \"sa\", \"\");\n    }\n\n    @Override\n    public List<RepairJob> list() {\n        List<RepairJob> jobs = new ArrayList<>();\n        String sql = \"SELECT id, bike, problem, ready FROM RepairJob ORDER BY id\";\n        try (Connection connection = connect();\n                PreparedStatement statement = connection.prepareStatement(sql);\n                ResultSet rows = statement.executeQuery()) {\n            while (rows.next()) {\n                jobs.add(new RepairJob(rows.getInt(\"id\"), rows.getString(\"bike\"),\n                        rows.getString(\"problem\"), rows.getBoolean(\"ready\")));\n            }\n        } catch (SQLException e) {\n            throw new IllegalStateException(\"Couldn't read the jobs\", e);\n        }\n        return jobs;\n    }\n\n    @Override\n    public boolean remove(int id) {\n        try (Connection connection = connect();\n                PreparedStatement statement = connection.prepareStatement(\"DELETE FROM RepairJob WHERE id = ?\")) {\n            statement.setInt(1, id);\n            return statement.executeUpdate() > 0;\n        } catch (SQLException e) {\n            throw new IllegalStateException(\"Couldn't remove job \" + id, e);\n        }\n    }\n\n    // add, findById and update follow the same pattern.\n}\n```\n\n- **The URL** names the database: `jdbc:h2:./repairs` is an H2 database in the file `repairs.mv.db` in the folder where the program runs, made the first time. `\"sa\"` and `\"\"` are the user name and the password the database is created with.\n- **`Connection`** is an open line to the database, from `DriverManager.getConnection`. JDBC finds H2's driver on the class path by itself.\n- **`PreparedStatement`** is one SQL statement. Each `?` in it is a **parameter**, filled in with `setInt`, `setString` or `setBoolean`, numbered from **1** (not 0, unlike almost everything else in Java). `executeQuery()` runs a `SELECT` and returns its rows; `executeUpdate()` runs the others and returns how many rows they changed, so `remove` returns `true` exactly when a row went away.\n- **`ResultSet`** walks through the rows. It starts *before* the first row: each `next()` moves it one row on and returns `false` when there are no more, so `while (rows.next())` visits every row once. `getInt(\"id\")` and `getString(\"bike\")` read a column of the current row.\n- **try-with-resources** (modules 42 and 43) closes the result set, the statement and the connection at the end, in that order, even when an exception is thrown. A connection left open is a connection the database can't give to anyone else.\n- **`SQLException`** is a checked exception, and `RepairJobDao`'s methods don't declare it, so each method catches it and throws an unchecked one with the cause inside. The rest of the program doesn't change: the UI of step 3 works with this DAO as it is.\n\nTo add a job and get its id, the statement is made with `connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)`, and after `executeUpdate()`, `statement.getGeneratedKeys()` returns a ResultSet whose one row holds the id the database chose.\n\n### Never build SQL from strings\n\nIt's tempting to write `\"DELETE FROM RepairJob WHERE bike = '\" + bike + \"'\"`. Then the user's text becomes part of the SQL itself. A bike named `O'Neil's tandem` breaks the statement, since its `'` ends the quoted text early. Worse, someone can type text that *is* SQL:\n\n```text\nDELETE FROM RepairJob WHERE bike = 'x' OR 'a'='a'\n```\n\n`'a'='a'` is true for every row, so this removes every job. Getting a program to run SQL of the attacker's choice like this is called **SQL injection**, and it has been behind many real data leaks. With a `?` and `setString`, the text is sent to the database as a value, never as SQL: `x' OR 'a'='a` is then just an odd bike name that no row has. Always use parameters for values that come from outside the program.\n\n### On your own computer\n\n1. Make a Maven project as in step 1, and add H2's dependency to pom.xml.\n2. Put `RepairJob`, `RepairJobDao`, `JdbcRepairJobDao` (with the missing methods written), `QueueUi` and `Main` in `src/main/java`, each starting with the project's package line.\n3. In `main`, create `new JdbcRepairJobDao(\"jdbc:h2:./repairs\")` in place of the DAO in memory.\n4. Run it with `mvn compile exec:java -Dexec.mainClass=...` (your Main's full name), add a few jobs and quit. Run it again: the jobs are still there, in `repairs.mv.db`.\n\nThe MOOC's [section 14.4](https://java-programming.mooc.fi/part-14/4-maven-and-third-party-libraries) has the database exercise, with a template whose pom.xml already has H2's dependency. For SQL itself, the MOOC points to a separate course on databases.\n","task":"This program builds SQL from strings, the way you shouldn't. What does it print? Look at which of the lines would remove more than one bike's job.\n","kind":"predict","seed":"public class Main {\n    public static void main(String[] args) {\n        String[] bikes = {\"blue tandem\", \"x' OR 'a'='a\"};\n        for (String bike : bikes) {\n            String sql = \"DELETE FROM RepairJob WHERE bike = '\" + bike + \"'\";\n            System.out.println(sql);\n        }\n    }\n}\n","solution":"public class Main {\n    public static void main(String[] args) {\n        String[] bikes = {\"blue tandem\", \"x' OR 'a'='a\"};\n        for (String bike : bikes) {\n            String sql = \"DELETE FROM RepairJob WHERE bike = '\" + bike + \"'\";\n            System.out.println(sql);\n        }\n    }\n}\n","hints":["The bike's text goes between two `'` characters, exactly as it is: the program adds a `'` before it and one after it.","The second bike's text has `'` characters of its own. Write it in, then add the closing `'` at the end."],"tests":[{"name":"Output","stdin":"","expect":"DELETE FROM RepairJob WHERE bike = 'blue tandem'\nDELETE FROM RepairJob WHERE bike = 'x' OR 'a'='a'","hidden":false}],"require":[],"forbid":[],"lines":["DELETE FROM RepairJob WHERE bike = 'blue tandem'","DELETE FROM RepairJob WHERE bike = 'x' OR 'a'='a'"],"more":[{"task":"Each call to a database's DAO is a trip to the database, and a slow program often makes far more of them than anyone expected. Count them without changing the DAO: write the class `CountingRepairJobDao`, which implements `RepairJobDao`, gets another `RepairJobDao` in its constructor and passes every call on to it, returning what it returns. It also counts:\n\n- `getReads()`: how many times `list` and `findById` were called\n- `getWrites()`: how many times `add`, `update` and `remove` were called, whether or not they found the job\n\nIt works with any DAO: here an `InMemoryRepairJobDao`, on your own computer a `JdbcRepairJobDao`. The program prints:\n\n```\n[#1 red road bike: flat tire (waiting), #2 green cargo bike: squeaky brakes (ready)]\n2 reads, 3 writes\n```\n","kind":"code","seed":"// ==== CountingRepairJobDao.java ====\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class CountingRepairJobDao implements RepairJobDao {\n\n    public CountingRepairJobDao(RepairJobDao inner) {\n\n    }\n\n    public int getReads() {\n        return 0;\n    }\n\n    public int getWrites() {\n        return 0;\n    }\n\n    @Override\n    public List<RepairJob> list() {\n        return new ArrayList<>();\n    }\n\n    @Override\n    public int add(String bike, String problem) {\n        return 0;\n    }\n\n    @Override\n    public RepairJob findById(int id) {\n        return null;\n    }\n\n    @Override\n    public boolean update(RepairJob job) {\n        return false;\n    }\n\n    @Override\n    public boolean remove(int id) {\n        return false;\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        CountingRepairJobDao queue = new CountingRepairJobDao(new InMemoryRepairJobDao());\n        queue.add(\"red road bike\", \"flat tire\");\n        queue.add(\"green cargo bike\", \"squeaky brakes\");\n        RepairJob job = queue.findById(2);\n        queue.update(new RepairJob(2, job.getBike(), job.getProblem(), true));\n        System.out.println(queue.list());\n        System.out.println(queue.getReads() + \" reads, \" + queue.getWrites() + \" writes\");\n    }\n}\n\n// ==== RepairJobDao.java ====\nimport java.util.List;\n\npublic interface RepairJobDao {\n    List<RepairJob> list();\n    int add(String bike, String problem);\n    RepairJob findById(int id);\n    boolean update(RepairJob job);\n    boolean remove(int id);\n}\n\n// ==== InMemoryRepairJobDao.java ====\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class InMemoryRepairJobDao implements RepairJobDao {\n    private List<RepairJob> jobs;\n    private int nextId;\n\n    public InMemoryRepairJobDao() {\n        this.jobs = new ArrayList<>();\n        this.nextId = 1;\n    }\n\n    @Override\n    public List<RepairJob> list() {\n        return new ArrayList<>(this.jobs);\n    }\n\n    @Override\n    public int add(String bike, String problem) {\n        int id = this.nextId;\n        this.nextId++;\n        this.jobs.add(new RepairJob(id, bike, problem, false));\n        return id;\n    }\n\n    @Override\n    public RepairJob findById(int id) {\n        for (RepairJob job : this.jobs) {\n            if (job.getId() == id) {\n                return job;\n            }\n        }\n        return null;\n    }\n\n    @Override\n    public boolean update(RepairJob job) {\n        for (int i = 0; i < this.jobs.size(); i++) {\n            if (this.jobs.get(i).getId() == job.getId()) {\n                this.jobs.set(i, job);\n                return true;\n            }\n        }\n        return false;\n    }\n\n    @Override\n    public boolean remove(int id) {\n        RepairJob job = findById(id);\n        if (job == null) {\n            return false;\n        }\n        this.jobs.remove(job);\n        return true;\n    }\n}\n\n// ==== RepairJob.java ====\npublic class RepairJob {\n    private int id;\n    private String bike;\n    private String problem;\n    private boolean ready;\n\n    public RepairJob(int id, String bike, String problem, boolean ready) {\n        this.id = id;\n        this.bike = bike;\n        this.problem = problem;\n        this.ready = ready;\n    }\n\n    public int getId() {\n        return this.id;\n    }\n\n    public String getBike() {\n        return this.bike;\n    }\n\n    public String getProblem() {\n        return this.problem;\n    }\n\n    public boolean isReady() {\n        return this.ready;\n    }\n\n    @Override\n    public String toString() {\n        String state = \"waiting\";\n        if (this.ready) {\n            state = \"ready\";\n        }\n        return \"#\" + this.id + \" \" + this.bike + \": \" + this.problem + \" (\" + state + \")\";\n    }\n}\n","solution":"// ==== CountingRepairJobDao.java ====\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class CountingRepairJobDao implements RepairJobDao {\n    private RepairJobDao inner;\n    private int reads;\n    private int writes;\n\n    public CountingRepairJobDao(RepairJobDao inner) {\n        this.inner = inner;\n        this.reads = 0;\n        this.writes = 0;\n    }\n\n    public int getReads() {\n        return this.reads;\n    }\n\n    public int getWrites() {\n        return this.writes;\n    }\n\n    @Override\n    public List<RepairJob> list() {\n        this.reads++;\n        return new ArrayList<>(this.inner.list());\n    }\n\n    @Override\n    public int add(String bike, String problem) {\n        this.writes++;\n        return this.inner.add(bike, problem);\n    }\n\n    @Override\n    public RepairJob findById(int id) {\n        this.reads++;\n        return this.inner.findById(id);\n    }\n\n    @Override\n    public boolean update(RepairJob job) {\n        this.writes++;\n        return this.inner.update(job);\n    }\n\n    @Override\n    public boolean remove(int id) {\n        this.writes++;\n        return this.inner.remove(id);\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        CountingRepairJobDao queue = new CountingRepairJobDao(new InMemoryRepairJobDao());\n        queue.add(\"red road bike\", \"flat tire\");\n        queue.add(\"green cargo bike\", \"squeaky brakes\");\n        RepairJob job = queue.findById(2);\n        queue.update(new RepairJob(2, job.getBike(), job.getProblem(), true));\n        System.out.println(queue.list());\n        System.out.println(queue.getReads() + \" reads, \" + queue.getWrites() + \" writes\");\n    }\n}\n\n// ==== RepairJobDao.java ====\nimport java.util.List;\n\npublic interface RepairJobDao {\n    List<RepairJob> list();\n    int add(String bike, String problem);\n    RepairJob findById(int id);\n    boolean update(RepairJob job);\n    boolean remove(int id);\n}\n\n// ==== InMemoryRepairJobDao.java ====\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class InMemoryRepairJobDao implements RepairJobDao {\n    private List<RepairJob> jobs;\n    private int nextId;\n\n    public InMemoryRepairJobDao() {\n        this.jobs = new ArrayList<>();\n        this.nextId = 1;\n    }\n\n    @Override\n    public List<RepairJob> list() {\n        return new ArrayList<>(this.jobs);\n    }\n\n    @Override\n    public int add(String bike, String problem) {\n        int id = this.nextId;\n        this.nextId++;\n        this.jobs.add(new RepairJob(id, bike, problem, false));\n        return id;\n    }\n\n    @Override\n    public RepairJob findById(int id) {\n        for (RepairJob job : this.jobs) {\n            if (job.getId() == id) {\n                return job;\n            }\n        }\n        return null;\n    }\n\n    @Override\n    public boolean update(RepairJob job) {\n        for (int i = 0; i < this.jobs.size(); i++) {\n            if (this.jobs.get(i).getId() == job.getId()) {\n                this.jobs.set(i, job);\n                return true;\n            }\n        }\n        return false;\n    }\n\n    @Override\n    public boolean remove(int id) {\n        RepairJob job = findById(id);\n        if (job == null) {\n            return false;\n        }\n        this.jobs.remove(job);\n        return true;\n    }\n}\n\n// ==== RepairJob.java ====\npublic class RepairJob {\n    private int id;\n    private String bike;\n    private String problem;\n    private boolean ready;\n\n    public RepairJob(int id, String bike, String problem, boolean ready) {\n        this.id = id;\n        this.bike = bike;\n        this.problem = problem;\n        this.ready = ready;\n    }\n\n    public int getId() {\n        return this.id;\n    }\n\n    public String getBike() {\n        return this.bike;\n    }\n\n    public String getProblem() {\n        return this.problem;\n    }\n\n    public boolean isReady() {\n        return this.ready;\n    }\n\n    @Override\n    public String toString() {\n        String state = \"waiting\";\n        if (this.ready) {\n            state = \"ready\";\n        }\n        return \"#\" + this.id + \" \" + this.bike + \": \" + this.problem + \" (\" + state + \")\";\n    }\n}\n","hints":["Keep the DAO from the constructor in an instance variable of type `RepairJobDao`, next to two counters. Each method counts first, then returns what the same method of the inner DAO returns: `return this.inner.add(bike, problem);`.","Don't make a DAO of your own inside the class: the counting DAO stores nothing, so it works around any DAO it's given, one in memory or one with a database."],"tests":[{"name":"Two adds, a find, an update and a list","stdin":"","expect":"[#1 red road bike: flat tire (waiting), #2 green cargo bike: squeaky brakes (ready)]\n2 reads, 3 writes","hidden":false},{"name":"Test 2","stdin":"","call":"CountingRepairJobDao queue = new CountingRepairJobDao(new InMemoryRepairJobDao());\nSystem.out.println(queue.remove(4));\nSystem.out.println(queue.findById(1));\nSystem.out.println(queue.add(\"blue tandem\", \"loose chain\"));\nSystem.out.println(queue.add(\"silver e-bike\", \"dead light\"));\nSystem.out.println(queue.remove(1));\nSystem.out.println(queue.update(new RepairJob(7, \"white racer\", \"bent fork\", true)));\nSystem.out.println(queue.list());\nSystem.out.println(queue.list().size());\nSystem.out.println(queue.getReads() + \" \" + queue.getWrites());\n","expect":"false\nnull\n1\n2\ntrue\nfalse\n[#2 silver e-bike: dead light (waiting)]\n1\n3 5","hidden":true},{"name":"Test 3","stdin":"","call":"InMemoryRepairJobDao memory = new InMemoryRepairJobDao();\nmemory.add(\"black fixie\", \"worn saddle\");\nCountingRepairJobDao inner = new CountingRepairJobDao(memory);\nCountingRepairJobDao outer = new CountingRepairJobDao(inner);\nouter.add(\"orange kids bike\", \"wobbly pedal\");\nouter.findById(1);\nouter.list();\nSystem.out.println(outer.getReads() + \" \" + outer.getWrites());\nSystem.out.println(inner.getReads() + \" \" + inner.getWrites());\nSystem.out.println(memory.list());\n","expect":"2 1\n2 1\n[#1 black fixie: worn saddle (waiting), #2 orange kids bike: wobbly pedal (waiting)]","hidden":true}],"require":[{"pattern":"new\\s+InMemoryRepairJobDao","max":1,"message":"`CountingRepairJobDao` stores nothing itself: it passes each call on to the DAO it gets in its constructor. Only `main` creates an `InMemoryRepairJobDao`."}],"forbid":[]},{"task":"Show that the UI really doesn't care which DAO it gets: write a second DAO that keeps the jobs in memory in a different way, and plug it in. `MapRepairJobDao` implements `RepairJobDao` with a `HashMap<Integer, RepairJob>` from each job's id to the job, and ids that grow from 1 as before.\n\nA HashMap doesn't keep its keys in any promised order (module 31), so `list()` must sort its new list by id before returning it. `findById`, `update` and `remove` find a job by its key, with no loop. `main` already gives a `MapRepairJobDao` to the `QueueUi` of step 3, which stays as it is. For the input shown, the program prints:\n\n```\n> Added job 1\n> Added job 2\n> Job 1 is ready\n> No job 5\n> #1 red road bike: flat tire (ready)\n#2 green cargo bike: squeaky brakes (waiting)\n> Bye\n```\n","kind":"code","seed":"// ==== MapRepairJobDao.java ====\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class MapRepairJobDao implements RepairJobDao {\n\n    @Override\n    public List<RepairJob> list() {\n        return new ArrayList<>();\n    }\n\n    @Override\n    public int add(String bike, String problem) {\n        return 0;\n    }\n\n    @Override\n    public RepairJob findById(int id) {\n        return null;\n    }\n\n    @Override\n    public boolean update(RepairJob job) {\n        return false;\n    }\n\n    @Override\n    public boolean remove(int id) {\n        return false;\n    }\n}\n\n// ==== Main.java ====\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        RepairJobDao queue = new MapRepairJobDao();\n        QueueUi ui = new QueueUi(queue, new Scanner(System.in));\n        ui.start();\n    }\n}\n\n// ==== QueueUi.java ====\nimport java.util.List;\nimport java.util.Scanner;\n\npublic class QueueUi {\n    private RepairJobDao queue;\n    private Scanner scanner;\n\n    public QueueUi(RepairJobDao queue, Scanner scanner) {\n        this.queue = queue;\n        this.scanner = scanner;\n    }\n\n    public void start() {\n        while (true) {\n            System.out.print(\"> \");\n            String command = this.scanner.nextLine();\n            if (command.equals(\"quit\")) {\n                System.out.println(\"Bye\");\n                break;\n            }\n            if (command.equals(\"list\")) {\n                list();\n            } else if (command.equals(\"add\")) {\n                add();\n            } else if (command.equals(\"ready\")) {\n                ready();\n            } else {\n                System.out.println(\"Unknown command: \" + command);\n            }\n        }\n    }\n\n    private void list() {\n        List<RepairJob> jobs = this.queue.list();\n        if (jobs.isEmpty()) {\n            System.out.println(\"The queue is empty\");\n        }\n        for (RepairJob job : jobs) {\n            System.out.println(job);\n        }\n    }\n\n    private void add() {\n        String bike = this.scanner.nextLine();\n        String problem = this.scanner.nextLine();\n        int id = this.queue.add(bike, problem);\n        System.out.println(\"Added job \" + id);\n    }\n\n    private void ready() {\n        int id = Integer.valueOf(this.scanner.nextLine());\n        RepairJob job = this.queue.findById(id);\n        if (job == null) {\n            System.out.println(\"No job \" + id);\n            return;\n        }\n        this.queue.update(new RepairJob(id, job.getBike(), job.getProblem(), true));\n        System.out.println(\"Job \" + id + \" is ready\");\n    }\n}\n\n// ==== RepairJobDao.java ====\nimport java.util.List;\n\npublic interface RepairJobDao {\n    List<RepairJob> list();\n    int add(String bike, String problem);\n    RepairJob findById(int id);\n    boolean update(RepairJob job);\n    boolean remove(int id);\n}\n\n// ==== RepairJob.java ====\npublic class RepairJob {\n    private int id;\n    private String bike;\n    private String problem;\n    private boolean ready;\n\n    public RepairJob(int id, String bike, String problem, boolean ready) {\n        this.id = id;\n        this.bike = bike;\n        this.problem = problem;\n        this.ready = ready;\n    }\n\n    public int getId() {\n        return this.id;\n    }\n\n    public String getBike() {\n        return this.bike;\n    }\n\n    public String getProblem() {\n        return this.problem;\n    }\n\n    public boolean isReady() {\n        return this.ready;\n    }\n\n    @Override\n    public String toString() {\n        String state = \"waiting\";\n        if (this.ready) {\n            state = \"ready\";\n        }\n        return \"#\" + this.id + \" \" + this.bike + \": \" + this.problem + \" (\" + state + \")\";\n    }\n}\n","solution":"// ==== MapRepairJobDao.java ====\nimport java.util.ArrayList;\nimport java.util.Comparator;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\npublic class MapRepairJobDao implements RepairJobDao {\n    private Map<Integer, RepairJob> jobs;\n    private int nextId;\n\n    public MapRepairJobDao() {\n        this.jobs = new HashMap<>();\n        this.nextId = 1;\n    }\n\n    @Override\n    public List<RepairJob> list() {\n        List<RepairJob> all = new ArrayList<>(this.jobs.values());\n        all.sort(Comparator.comparing(RepairJob::getId));\n        return all;\n    }\n\n    @Override\n    public int add(String bike, String problem) {\n        int id = this.nextId;\n        this.nextId++;\n        this.jobs.put(id, new RepairJob(id, bike, problem, false));\n        return id;\n    }\n\n    @Override\n    public RepairJob findById(int id) {\n        return this.jobs.get(id);\n    }\n\n    @Override\n    public boolean update(RepairJob job) {\n        if (!this.jobs.containsKey(job.getId())) {\n            return false;\n        }\n        this.jobs.put(job.getId(), job);\n        return true;\n    }\n\n    @Override\n    public boolean remove(int id) {\n        return this.jobs.remove(id) != null;\n    }\n}\n\n// ==== Main.java ====\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        RepairJobDao queue = new MapRepairJobDao();\n        QueueUi ui = new QueueUi(queue, new Scanner(System.in));\n        ui.start();\n    }\n}\n\n// ==== QueueUi.java ====\nimport java.util.List;\nimport java.util.Scanner;\n\npublic class QueueUi {\n    private RepairJobDao queue;\n    private Scanner scanner;\n\n    public QueueUi(RepairJobDao queue, Scanner scanner) {\n        this.queue = queue;\n        this.scanner = scanner;\n    }\n\n    public void start() {\n        while (true) {\n            System.out.print(\"> \");\n            String command = this.scanner.nextLine();\n            if (command.equals(\"quit\")) {\n                System.out.println(\"Bye\");\n                break;\n            }\n            if (command.equals(\"list\")) {\n                list();\n            } else if (command.equals(\"add\")) {\n                add();\n            } else if (command.equals(\"ready\")) {\n                ready();\n            } else {\n                System.out.println(\"Unknown command: \" + command);\n            }\n        }\n    }\n\n    private void list() {\n        List<RepairJob> jobs = this.queue.list();\n        if (jobs.isEmpty()) {\n            System.out.println(\"The queue is empty\");\n        }\n        for (RepairJob job : jobs) {\n            System.out.println(job);\n        }\n    }\n\n    private void add() {\n        String bike = this.scanner.nextLine();\n        String problem = this.scanner.nextLine();\n        int id = this.queue.add(bike, problem);\n        System.out.println(\"Added job \" + id);\n    }\n\n    private void ready() {\n        int id = Integer.valueOf(this.scanner.nextLine());\n        RepairJob job = this.queue.findById(id);\n        if (job == null) {\n            System.out.println(\"No job \" + id);\n            return;\n        }\n        this.queue.update(new RepairJob(id, job.getBike(), job.getProblem(), true));\n        System.out.println(\"Job \" + id + \" is ready\");\n    }\n}\n\n// ==== RepairJobDao.java ====\nimport java.util.List;\n\npublic interface RepairJobDao {\n    List<RepairJob> list();\n    int add(String bike, String problem);\n    RepairJob findById(int id);\n    boolean update(RepairJob job);\n    boolean remove(int id);\n}\n\n// ==== RepairJob.java ====\npublic class RepairJob {\n    private int id;\n    private String bike;\n    private String problem;\n    private boolean ready;\n\n    public RepairJob(int id, String bike, String problem, boolean ready) {\n        this.id = id;\n        this.bike = bike;\n        this.problem = problem;\n        this.ready = ready;\n    }\n\n    public int getId() {\n        return this.id;\n    }\n\n    public String getBike() {\n        return this.bike;\n    }\n\n    public String getProblem() {\n        return this.problem;\n    }\n\n    public boolean isReady() {\n        return this.ready;\n    }\n\n    @Override\n    public String toString() {\n        String state = \"waiting\";\n        if (this.ready) {\n            state = \"ready\";\n        }\n        return \"#\" + this.id + \" \" + this.bike + \": \" + this.problem + \" (\" + state + \")\";\n    }\n}\n","hints":["Two instance variables: `Map<Integer, RepairJob> jobs = new HashMap<>()` and the next id. `add` puts the new job under its id; `findById` is `this.jobs.get(id)`, which is `null` for a missing key.","`list` copies the values, `new ArrayList<>(this.jobs.values())`, and sorts the copy with `Comparator.comparing(RepairJob::getId)` (module 38). `update` checks `containsKey` first; a map's `remove(key)` returns the removed value, or `null`."],"tests":[{"name":"Test 1","stdin":"add\nred road bike\nflat tire\nadd\ngreen cargo bike\nsqueaky brakes\nready\n1\nready\n5\nlist\nquit\n","expect":"> Added job 1\n> Added job 2\n> Job 1 is ready\n> No job 5\n> #1 red road bike: flat tire (ready)\n#2 green cargo bike: squeaky brakes (waiting)\n> Bye","hidden":false},{"name":"Test 2","stdin":"","call":"RepairJobDao queue = new MapRepairJobDao();\nSystem.out.println(queue.list());\nfor (int i = 1; i <= 12; i++) {\n    queue.add(\"bike \" + i, \"check-up\");\n}\nfor (int i = 1; i <= 10; i++) {\n    queue.remove(i);\n}\nfor (int i = 13; i <= 16; i++) {\n    queue.add(\"bike \" + i, \"new tires\");\n}\nfor (RepairJob job : queue.list()) {\n    System.out.print(job.getId() + \" \");\n}\nSystem.out.println();\nSystem.out.println(queue.remove(3) + \" \" + queue.remove(16) + \" \" + queue.findById(16));\nSystem.out.println(queue.update(new RepairJob(3, \"bike 3\", \"check-up\", true)));\nSystem.out.println(queue.list().size());\n","expect":"[]\n11 12 13 14 15 16\nfalse true null\nfalse\n5","hidden":true},{"name":"Test 3","stdin":"","call":"RepairJobDao queue = new MapRepairJobDao();\nqueue.add(\"blue tandem\", \"loose chain\");\nqueue.add(\"silver e-bike\", \"dead light\");\nqueue.list().clear();\nSystem.out.println(queue.update(new RepairJob(2, \"silver e-bike\", \"dead light\", true)));\nSystem.out.println(queue.list());\n","expect":"true\n[#1 blue tandem: loose chain (waiting), #2 silver e-bike: dead light (ready)]","hidden":true}],"require":[{"pattern":"new\\s+HashMap","message":"Keep the jobs in a `HashMap<Integer, RepairJob>`, from each job's id to the job."}],"forbid":[]}]}