Java Arena
Step 3 of 5

A text UI over a DAO

A DAO stores data; a user interface lets a person work with it. In module 25 a text UI read commands and called the program's logic. Here, the logic it calls is a DAO.

The UI gets its DAO

The UI class keeps the DAO in an instance variable whose type is the interface, and gets it, like the Scanner, through its constructor:

Main.java
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        RecipeDao recipes = new InMemoryRecipeDao();
        RecipeUi ui = new RecipeUi(recipes, new Scanner(System.in));
        ui.start();
    }
}
RecipeUi.java
import java.util.List;
import java.util.Scanner;

public class RecipeUi {
    private RecipeDao recipes;
    private Scanner scanner;

    public RecipeUi(RecipeDao recipes, Scanner scanner) {
        this.recipes = recipes;
        this.scanner = scanner;
    }

    public void start() {
        while (true) {
            System.out.print("> ");
            String command = this.scanner.nextLine();
            if (command.equals("quit")) {
                System.out.println("Bye");
                break;
            }
            if (command.equals("list")) {
                list();
            } else if (command.equals("add")) {
                add();
            } else {
                System.out.println("Unknown command: " + command);
            }
        }
    }

    private void list() {
        List<Recipe> all = this.recipes.list();
        if (all.isEmpty()) {
            System.out.println("No recipes yet");
        }
        for (Recipe recipe : all) {
            System.out.println(recipe);
        }
    }

    private void add() {
        String name = this.scanner.nextLine();
        int minutes = Integer.valueOf(this.scanner.nextLine());
        int id = this.recipes.add(name, minutes);
        System.out.println("Saved as recipe " + id);
    }
}
RecipeDao.java
import java.util.List;

public interface RecipeDao {
    List<Recipe> list();
    int add(String name, int minutes);
    Recipe findById(int id);
}
InMemoryRecipeDao.java
import java.util.ArrayList;
import java.util.List;

public class InMemoryRecipeDao implements RecipeDao {
    private List<Recipe> recipes;
    private int nextId;

    public InMemoryRecipeDao() {
        this.recipes = new ArrayList<>();
        this.nextId = 1;
    }

    @Override
    public List<Recipe> list() {
        return new ArrayList<>(this.recipes);
    }

    @Override
    public int add(String name, int minutes) {
        int id = this.nextId;
        this.nextId++;
        this.recipes.add(new Recipe(id, name, minutes));
        return id;
    }

    @Override
    public Recipe findById(int id) {
        for (Recipe recipe : this.recipes) {
            if (recipe.getId() == id) {
                return recipe;
            }
        }
        return null;
    }
}
Recipe.java
public class Recipe {
    private int id;
    private String name;
    private int minutes;

    public Recipe(int id, String name, int minutes) {
        this.id = id;
        this.name = name;
        this.minutes = minutes;
    }

    public int getId() {
        return this.id;
    }

    public String getName() {
        return this.name;
    }

    public int getMinutes() {
        return this.minutes;
    }

    @Override
    public String toString() {
        return "#" + this.id + " " + this.name + ", " + this.minutes + " min";
    }
}
Input
list
add
Chickpea curry
40
add
Herb flatbread
20
list
curry
quit
Output
> No recipes yet
> Saved as recipe 1
> Saved as recipe 2
> #1 Chickpea curry, 40 min
#2 Herb flatbread, 20 min
> Unknown command: curry
> Bye

As in module 25, the program prints > before reading each command, and the output doesn't show what was typed. A command that needs more, such as add, reads it from the next lines, without prompts of its own.

Who knows what

  • The UI reads commands and prints answers. It doesn't know whether the recipes are in a list, a file or a database: it only calls the methods of RecipeDao.
  • The DAO stores and finds recipes. It never reads input or prints anything.
  • main is the only place that names InMemoryRecipeDao. It picks the DAO and the Scanner and hands both to the UI.

That last point is what makes the split pay off. On your own computer, main could create a DAO that uses a database (step 4) and pass that one instead: RecipeUi wouldn't change a line. A test can do the opposite: fill a DAO in memory with exactly the recipes it needs, and give the UI a Scanner that reads a String (module 26):

RecipeDao recipes = new InMemoryRecipeDao();
recipes.add("Chickpea curry", 40);
RecipeUi ui = new RecipeUi(recipes, new Scanner("list\nquit\n"));
ui.start();

Passing an object the parts it needs, instead of letting it create them, is called dependency injection. Big frameworks do it automatically, but a constructor parameter is all it takes.

Your task

Challenge 1 of 3

Write the text UI of the bike repair queue in QueueUi.java. The DAO from step 2 is ready in the other files, and main creates both. In start, before reading each command, print > with print. The commands:

  • list prints every job on a line of its own, or The queue is empty when there are none
  • add reads two more lines, the bike and the problem, adds the job and prints Added job and its id
  • quit prints Bye and ends
  • anything else prints Unknown command: and the command

QueueUi uses only the DAO it's given. For the input shown, the program prints:

> The queue is empty
> Added job 1
> Added job 2
> #1 red road bike: flat tire (waiting)
#2 green cargo bike: squeaky brakes (waiting)
> Unknown command: pump
> Bye

Challenge 2

When a bike is fixed, the mechanic marks its job as ready. Add the command ready to QueueUi: it reads one more line, a job's id. If there's no job with that id, it prints No job and the id. Otherwise it stores the job again as ready, through the DAO's update, and prints Job , the id and is ready.

The other commands stay as they are. For the input shown, the program prints:

> Added job 1
> Added job 2
> Job 2 is ready
> No job 7
> #1 red road bike: flat tire (waiting)
#2 green cargo bike: squeaky brakes (ready)
> Bye

Challenge 3

Two more commands for QueueUi:

  • remove reads a job's id from the next line and removes that job through the DAO. It prints Removed job and the id, or No job and the id when there's none.
  • next prints Next: and the first job in the list that isn't ready yet, or Nothing is waiting when every job is ready (or there are none).

The other commands, ready too, stay as they are. For the input shown, the program prints:

> Added job 1
> Added job 2
> Job 1 is ready
> Next: #2 green cargo bike: squeaky brakes (waiting)
> Removed job 2
> No job 2
> Nothing is waiting
> Bye