diff options
| author | Kostya <mail@sartin.in> | 2026-08-14 20:32:49 +0000 |
|---|---|---|
| committer | Kostya <mail@sartin.in> | 2026-08-14 20:32:49 +0000 |
| commit | 001f6444fec8fcd5312028cfdae90f4f93424559 (patch) | |
| tree | 75c10317663be26fcd34efddb03d2c334f017a4c /ui-console/src/main/java/market/guess/ui/console | |
| parent | 12611a5f0116351ab9114f5403222aead7a8329f (diff) | |
| download | guess-market-001f6444fec8fcd5312028cfdae90f4f93424559.tar.gz guess-market-001f6444fec8fcd5312028cfdae90f4f93424559.tar.xz guess-market-001f6444fec8fcd5312028cfdae90f4f93424559.zip | |
Collapse console I/O interfaces into InputProcessor, drop unneeded atomics
InputProvider/OutputProvider each had one implementation and no test
exercised the seam, so fold Console{Input,Output}Provider straight into
InputProcessor and update Menu/MenuCommand call sites accordingly.
Account and Event both use AtomicInteger for a ledger/trade id counter
that's only ever touched next to a plain, non-thread-safe ArrayList add
in the same method - the atomic bought no real thread-safety. Swapped
both to plain int with ++.
Diffstat (limited to 'ui-console/src/main/java/market/guess/ui/console')
10 files changed, 58 insertions, 112 deletions
diff --git a/ui-console/src/main/java/market/guess/ui/console/App.java b/ui-console/src/main/java/market/guess/ui/console/App.java index 34638ac..3d42e9b 100644 --- a/ui-console/src/main/java/market/guess/ui/console/App.java +++ b/ui-console/src/main/java/market/guess/ui/console/App.java | |||
| @@ -14,11 +14,7 @@ import market.guess.service.repository.UserRepository; | |||
| 14 | import market.guess.ui.console.command.ExitMenuCommand; | 14 | import market.guess.ui.console.command.ExitMenuCommand; |
| 15 | import market.guess.ui.console.command.ListEventsMenuCommand; | 15 | import market.guess.ui.console.command.ListEventsMenuCommand; |
| 16 | import market.guess.ui.console.command.LoadFileMenuCommand; | 16 | import market.guess.ui.console.command.LoadFileMenuCommand; |
| 17 | import market.guess.ui.console.io.ConsoleInputProvider; | ||
| 18 | import market.guess.ui.console.io.ConsoleOutputProvider; | ||
| 19 | import market.guess.ui.console.io.InputProcessor; | 17 | import market.guess.ui.console.io.InputProcessor; |
| 20 | import market.guess.ui.console.io.InputProvider; | ||
| 21 | import market.guess.ui.console.io.OutputProvider; | ||
| 22 | import org.picocontainer.DefaultPicoContainer; | 18 | import org.picocontainer.DefaultPicoContainer; |
| 23 | import org.picocontainer.behaviors.Caching; | 19 | import org.picocontainer.behaviors.Caching; |
| 24 | 20 | ||
| @@ -38,8 +34,6 @@ public class App { | |||
| 38 | pico.addComponent(ListEventsMenuCommand.class); | 34 | pico.addComponent(ListEventsMenuCommand.class); |
| 39 | pico.addComponent(ExitMenuCommand.class); | 35 | pico.addComponent(ExitMenuCommand.class); |
| 40 | 36 | ||
| 41 | pico.addComponent(InputProvider.class, ConsoleInputProvider.class); | ||
| 42 | pico.addComponent(OutputProvider.class, ConsoleOutputProvider.class); | ||
| 43 | pico.addComponent(InputProcessor.class); | 37 | pico.addComponent(InputProcessor.class); |
| 44 | pico.addComponent(Menu.class); | 38 | pico.addComponent(Menu.class); |
| 45 | 39 | ||
diff --git a/ui-console/src/main/java/market/guess/ui/console/Menu.java b/ui-console/src/main/java/market/guess/ui/console/Menu.java index c1762dc..18b72b7 100644 --- a/ui-console/src/main/java/market/guess/ui/console/Menu.java +++ b/ui-console/src/main/java/market/guess/ui/console/Menu.java | |||
| @@ -3,32 +3,29 @@ package market.guess.ui.console; | |||
| 3 | import java.util.List; | 3 | import java.util.List; |
| 4 | import market.guess.ui.console.command.MenuCommand; | 4 | import market.guess.ui.console.command.MenuCommand; |
| 5 | import market.guess.ui.console.io.InputProcessor; | 5 | import market.guess.ui.console.io.InputProcessor; |
| 6 | import market.guess.ui.console.io.OutputProvider; | ||
| 7 | import org.picocontainer.Startable; | 6 | import org.picocontainer.Startable; |
| 8 | 7 | ||
| 9 | public final class Menu implements Startable { | 8 | public final class Menu implements Startable { |
| 10 | private final OutputProvider out; | 9 | private final InputProcessor io; |
| 11 | private final InputProcessor in; | ||
| 12 | private final List<MenuCommand> commands; | 10 | private final List<MenuCommand> commands; |
| 13 | 11 | ||
| 14 | public Menu(List<MenuCommand> commands, OutputProvider out, InputProcessor in) { | 12 | public Menu(List<MenuCommand> commands, InputProcessor io) { |
| 15 | this.commands = commands; | 13 | this.commands = commands; |
| 16 | this.out = out; | 14 | this.io = io; |
| 17 | this.in = in; | ||
| 18 | } | 15 | } |
| 19 | 16 | ||
| 20 | @Override | 17 | @Override |
| 21 | public void start() { | 18 | public void start() { |
| 22 | var max = commands.stream().mapToInt(MenuCommand::getIndex).max().orElse(0); | 19 | var max = commands.stream().mapToInt(MenuCommand::getIndex).max().orElse(0); |
| 23 | out.splashScreen(); | 20 | io.splashScreen(); |
| 24 | while (true) { | 21 | while (true) { |
| 25 | printMenu(); | 22 | printMenu(); |
| 26 | 23 | ||
| 27 | var selection = in.readInt("Choose a command: ", 1, max); | 24 | var selection = io.readInt("Choose a command: ", 1, max); |
| 28 | var commandOptional = commands.stream().filter(c -> c.getIndex() == selection).findFirst(); | 25 | var commandOptional = commands.stream().filter(c -> c.getIndex() == selection).findFirst(); |
| 29 | 26 | ||
| 30 | if (commandOptional.isEmpty()) { | 27 | if (commandOptional.isEmpty()) { |
| 31 | out.println("There is no command %d.".formatted(selection)); | 28 | io.println("There is no command %d.".formatted(selection)); |
| 32 | continue; | 29 | continue; |
| 33 | } | 30 | } |
| 34 | var command = commandOptional.get(); | 31 | var command = commandOptional.get(); |
| @@ -45,9 +42,9 @@ public final class Menu implements Startable { | |||
| 45 | } | 42 | } |
| 46 | 43 | ||
| 47 | private void printMenu() { | 44 | private void printMenu() { |
| 48 | out.newLine(); | 45 | io.newLine(); |
| 49 | for (var command : commands) { | 46 | for (var command : commands) { |
| 50 | out.println(" [%d]->> %s".formatted(command.getIndex(), command.getName())); | 47 | io.println(" [%d]->> %s".formatted(command.getIndex(), command.getName())); |
| 51 | } | 48 | } |
| 52 | } | 49 | } |
| 53 | } | 50 | } |
diff --git a/ui-console/src/main/java/market/guess/ui/console/command/ExitMenuCommand.java b/ui-console/src/main/java/market/guess/ui/console/command/ExitMenuCommand.java index 372c686..23ed9c9 100644 --- a/ui-console/src/main/java/market/guess/ui/console/command/ExitMenuCommand.java +++ b/ui-console/src/main/java/market/guess/ui/console/command/ExitMenuCommand.java | |||
| @@ -1,13 +1,13 @@ | |||
| 1 | package market.guess.ui.console.command; | 1 | package market.guess.ui.console.command; |
| 2 | 2 | ||
| 3 | import market.guess.ui.console.io.OutputProvider; | 3 | import market.guess.ui.console.io.InputProcessor; |
| 4 | 4 | ||
| 5 | public final class ExitMenuCommand implements MenuCommand { | 5 | public final class ExitMenuCommand implements MenuCommand { |
| 6 | private final OutputProvider out; | 6 | private final InputProcessor io; |
| 7 | 7 | ||
| 8 | public ExitMenuCommand(OutputProvider out) { | 8 | public ExitMenuCommand(InputProcessor io) { |
| 9 | super(); | 9 | super(); |
| 10 | this.out = out; | 10 | this.io = io; |
| 11 | } | 11 | } |
| 12 | 12 | ||
| 13 | @Override | 13 | @Override |
| @@ -32,6 +32,6 @@ public final class ExitMenuCommand implements MenuCommand { | |||
| 32 | 32 | ||
| 33 | @Override | 33 | @Override |
| 34 | public void execute() { | 34 | public void execute() { |
| 35 | out.println("Goodbye."); | 35 | io.println("Goodbye."); |
| 36 | } | 36 | } |
| 37 | } | 37 | } |
diff --git a/ui-console/src/main/java/market/guess/ui/console/command/ListEventsMenuCommand.java b/ui-console/src/main/java/market/guess/ui/console/command/ListEventsMenuCommand.java index 6e6ace9..bad621d 100644 --- a/ui-console/src/main/java/market/guess/ui/console/command/ListEventsMenuCommand.java +++ b/ui-console/src/main/java/market/guess/ui/console/command/ListEventsMenuCommand.java | |||
| @@ -2,16 +2,16 @@ package market.guess.ui.console.command; | |||
| 2 | 2 | ||
| 3 | import market.guess.api.CommissionTiming; | 3 | import market.guess.api.CommissionTiming; |
| 4 | import market.guess.api.GuessMarketContext; | 4 | import market.guess.api.GuessMarketContext; |
| 5 | import market.guess.ui.console.io.OutputProvider; | 5 | import market.guess.ui.console.io.InputProcessor; |
| 6 | 6 | ||
| 7 | public final class ListEventsMenuCommand implements MenuCommand { | 7 | public final class ListEventsMenuCommand implements MenuCommand { |
| 8 | private final GuessMarketContext context; | 8 | private final GuessMarketContext context; |
| 9 | private final OutputProvider out; | 9 | private final InputProcessor io; |
| 10 | 10 | ||
| 11 | public ListEventsMenuCommand(GuessMarketContext context, OutputProvider out) { | 11 | public ListEventsMenuCommand(GuessMarketContext context, InputProcessor io) { |
| 12 | super(); | 12 | super(); |
| 13 | this.context = context; | 13 | this.context = context; |
| 14 | this.out = out; | 14 | this.io = io; |
| 15 | } | 15 | } |
| 16 | 16 | ||
| 17 | @Override | 17 | @Override |
| @@ -34,14 +34,14 @@ public final class ListEventsMenuCommand implements MenuCommand { | |||
| 34 | var events = context.listEvents(); | 34 | var events = context.listEvents(); |
| 35 | 35 | ||
| 36 | for (var event : events) { | 36 | for (var event : events) { |
| 37 | out.newLine(); | 37 | io.newLine(); |
| 38 | out.println(" %s".formatted(event.name())); | 38 | io.println(" %s".formatted(event.name())); |
| 39 | out.println(" %s".formatted(event.description())); | 39 | io.println(" %s".formatted(event.description())); |
| 40 | out.println( | 40 | io.println( |
| 41 | " Commission: %d%% (%s)" | 41 | " Commission: %d%% (%s)" |
| 42 | .formatted(event.commissionPercent(), timing(event.commissionTiming()))); | 42 | .formatted(event.commissionPercent(), timing(event.commissionTiming()))); |
| 43 | out.println(" Options: " + String.join(" | ", event.optionNames())); | 43 | io.println(" Options: " + String.join(" | ", event.optionNames())); |
| 44 | out.println(" Status: " + event.status()); | 44 | io.println(" Status: " + event.status()); |
| 45 | } | 45 | } |
| 46 | } | 46 | } |
| 47 | 47 | ||
diff --git a/ui-console/src/main/java/market/guess/ui/console/command/LoadFileMenuCommand.java b/ui-console/src/main/java/market/guess/ui/console/command/LoadFileMenuCommand.java index 4a56483..7005632 100644 --- a/ui-console/src/main/java/market/guess/ui/console/command/LoadFileMenuCommand.java +++ b/ui-console/src/main/java/market/guess/ui/console/command/LoadFileMenuCommand.java | |||
| @@ -3,17 +3,14 @@ package market.guess.ui.console.command; | |||
| 3 | import java.nio.file.Path; | 3 | import java.nio.file.Path; |
| 4 | import market.guess.api.GuessMarketContext; | 4 | import market.guess.api.GuessMarketContext; |
| 5 | import market.guess.ui.console.io.InputProcessor; | 5 | import market.guess.ui.console.io.InputProcessor; |
| 6 | import market.guess.ui.console.io.OutputProvider; | ||
| 7 | 6 | ||
| 8 | public final class LoadFileMenuCommand implements MenuCommand { | 7 | public final class LoadFileMenuCommand implements MenuCommand { |
| 9 | private final GuessMarketContext context; | 8 | private final GuessMarketContext context; |
| 10 | private final OutputProvider out; | 9 | private final InputProcessor io; |
| 11 | private final InputProcessor in; | ||
| 12 | 10 | ||
| 13 | public LoadFileMenuCommand(GuessMarketContext context, InputProcessor in, OutputProvider out) { | 11 | public LoadFileMenuCommand(GuessMarketContext context, InputProcessor io) { |
| 14 | this.context = context; | 12 | this.context = context; |
| 15 | this.out = out; | 13 | this.io = io; |
| 16 | this.in = in; | ||
| 17 | } | 14 | } |
| 18 | 15 | ||
| 19 | @Override | 16 | @Override |
| @@ -33,18 +30,18 @@ public final class LoadFileMenuCommand implements MenuCommand { | |||
| 33 | 30 | ||
| 34 | @Override | 31 | @Override |
| 35 | public void execute() { | 32 | public void execute() { |
| 36 | var path = in.readPath("Pleae enter the full file path to the XML file: "); | 33 | var path = io.readPath("Pleae enter the full file path to the XML file: "); |
| 37 | var result = context.loadEvents(Path.of(path)); | 34 | var result = context.loadEvents(Path.of(path)); |
| 38 | 35 | ||
| 39 | if (result.success()) { | 36 | if (result.success()) { |
| 40 | out.println("Loaded %d event(s) from %s.".formatted(result.eventsLoaded(), result.source())); | 37 | io.println("Loaded %d event(s) from %s.".formatted(result.eventsLoaded(), result.source())); |
| 41 | return; | 38 | return; |
| 42 | } | 39 | } |
| 43 | 40 | ||
| 44 | out.println( | 41 | io.println( |
| 45 | "The file was not loaded. %d problem(s) were found: ".formatted(result.issues().size())); | 42 | "The file was not loaded. %d problem(s) were found: ".formatted(result.issues().size())); |
| 46 | for (var issue : result.issues()) { | 43 | for (var issue : result.issues()) { |
| 47 | out.println(" - %s: %s".formatted(issue.location(), issue.humanizedMessage())); | 44 | io.println(" - %s: %s".formatted(issue.location(), issue.humanizedMessage())); |
| 48 | } | 45 | } |
| 49 | } | 46 | } |
| 50 | } | 47 | } |
diff --git a/ui-console/src/main/java/market/guess/ui/console/io/ConsoleInputProvider.java b/ui-console/src/main/java/market/guess/ui/console/io/ConsoleInputProvider.java deleted file mode 100644 index 5c93f71..0000000 --- a/ui-console/src/main/java/market/guess/ui/console/io/ConsoleInputProvider.java +++ /dev/null | |||
| @@ -1,17 +0,0 @@ | |||
| 1 | package market.guess.ui.console.io; | ||
| 2 | |||
| 3 | import java.util.Scanner; | ||
| 4 | |||
| 5 | public class ConsoleInputProvider implements InputProvider { | ||
| 6 | private final Scanner scanner = new Scanner(System.in); | ||
| 7 | |||
| 8 | @Override | ||
| 9 | public void close() throws Exception { | ||
| 10 | scanner.close(); | ||
| 11 | } | ||
| 12 | |||
| 13 | @Override | ||
| 14 | public String nextLine() { | ||
| 15 | return scanner.hasNextLine() ? scanner.nextLine() : null; | ||
| 16 | } | ||
| 17 | } | ||
diff --git a/ui-console/src/main/java/market/guess/ui/console/io/ConsoleOutputProvider.java b/ui-console/src/main/java/market/guess/ui/console/io/ConsoleOutputProvider.java deleted file mode 100644 index 6fa7595..0000000 --- a/ui-console/src/main/java/market/guess/ui/console/io/ConsoleOutputProvider.java +++ /dev/null | |||
| @@ -1,24 +0,0 @@ | |||
| 1 | package market.guess.ui.console.io; | ||
| 2 | |||
| 3 | public class ConsoleOutputProvider implements OutputProvider { | ||
| 4 | |||
| 5 | @Override | ||
| 6 | public void print(Object object) { | ||
| 7 | IO.print(object); | ||
| 8 | } | ||
| 9 | |||
| 10 | @Override | ||
| 11 | public void println(Object object) { | ||
| 12 | IO.println(object); | ||
| 13 | } | ||
| 14 | |||
| 15 | @Override | ||
| 16 | public void newLine() { | ||
| 17 | IO.println(""); | ||
| 18 | } | ||
| 19 | |||
| 20 | @Override | ||
| 21 | public void splashScreen() { | ||
| 22 | IO.println("<<-- GUESS MARKET -->>"); | ||
| 23 | } | ||
| 24 | } | ||
diff --git a/ui-console/src/main/java/market/guess/ui/console/io/InputProcessor.java b/ui-console/src/main/java/market/guess/ui/console/io/InputProcessor.java index 9f03acb..4889c36 100644 --- a/ui-console/src/main/java/market/guess/ui/console/io/InputProcessor.java +++ b/ui-console/src/main/java/market/guess/ui/console/io/InputProcessor.java | |||
| @@ -1,41 +1,52 @@ | |||
| 1 | package market.guess.ui.console.io; | 1 | package market.guess.ui.console.io; |
| 2 | 2 | ||
| 3 | import java.util.Scanner; | ||
| 4 | |||
| 3 | public final class InputProcessor { | 5 | public final class InputProcessor { |
| 4 | private final OutputProvider out; | 6 | private final Scanner scanner = new Scanner(System.in); |
| 5 | private final InputProvider in; | 7 | |
| 8 | public void print(Object object) { | ||
| 9 | IO.print(object); | ||
| 10 | } | ||
| 11 | |||
| 12 | public void println(Object object) { | ||
| 13 | IO.println(object); | ||
| 14 | } | ||
| 6 | 15 | ||
| 7 | public InputProcessor(InputProvider in, OutputProvider out) { | 16 | public void newLine() { |
| 8 | super(); | 17 | IO.println(""); |
| 9 | this.in = in; | 18 | } |
| 10 | this.out = out; | 19 | |
| 20 | public void splashScreen() { | ||
| 21 | IO.println("<<-- GUESS MARKET -->>"); | ||
| 11 | } | 22 | } |
| 12 | 23 | ||
| 13 | public String readLine(String prompt) { | 24 | public String readLine(String prompt) { |
| 14 | out.print(prompt); | 25 | print(prompt); |
| 15 | var line = in.nextLine(); | 26 | var line = nextLine(); |
| 16 | 27 | ||
| 17 | return line.trim(); | 28 | return line.trim(); |
| 18 | } | 29 | } |
| 19 | 30 | ||
| 20 | public int readInt(String prompt, int min, int max) { | 31 | public int readInt(String prompt, int min, int max) { |
| 21 | while (true) { | 32 | while (true) { |
| 22 | var raw = in.nextLine(); | 33 | var raw = nextLine(); |
| 23 | if (raw.isBlank()) { | 34 | if (raw.isBlank()) { |
| 24 | out.println("Please enter a number between %d and %d.".formatted(min, max)); | 35 | println("Please enter a number between %d and %d.".formatted(min, max)); |
| 25 | continue; | 36 | continue; |
| 26 | } | 37 | } |
| 27 | try { | 38 | try { |
| 28 | var value = Integer.parseInt(raw); | 39 | var value = Integer.parseInt(raw); |
| 29 | 40 | ||
| 30 | if (value > max || value < min) { | 41 | if (value > max || value < min) { |
| 31 | out.println( | 42 | println( |
| 32 | "%d is out of range, please enter a number between %d and %d." | 43 | "%d is out of range, please enter a number between %d and %d." |
| 33 | .formatted(value, min, max)); | 44 | .formatted(value, min, max)); |
| 34 | continue; | 45 | continue; |
| 35 | } | 46 | } |
| 36 | return value; | 47 | return value; |
| 37 | } catch (NumberFormatException e) { | 48 | } catch (NumberFormatException e) { |
| 38 | out.println( | 49 | println( |
| 39 | "%s in not a number, please enter a number between %d and %d." | 50 | "%s in not a number, please enter a number between %d and %d." |
| 40 | .formatted(raw, min, max)); | 51 | .formatted(raw, min, max)); |
| 41 | continue; | 52 | continue; |
| @@ -48,11 +59,15 @@ public final class InputProcessor { | |||
| 48 | var raw = readLine(prompt); | 59 | var raw = readLine(prompt); |
| 49 | 60 | ||
| 50 | if (raw.isBlank()) { | 61 | if (raw.isBlank()) { |
| 51 | out.println("Please enter a file path."); | 62 | println("Please enter a file path."); |
| 52 | continue; | 63 | continue; |
| 53 | } | 64 | } |
| 54 | 65 | ||
| 55 | return raw; | 66 | return raw; |
| 56 | } | 67 | } |
| 57 | } | 68 | } |
| 69 | |||
| 70 | private String nextLine() { | ||
| 71 | return scanner.hasNextLine() ? scanner.nextLine() : null; | ||
| 72 | } | ||
| 58 | } | 73 | } |
diff --git a/ui-console/src/main/java/market/guess/ui/console/io/InputProvider.java b/ui-console/src/main/java/market/guess/ui/console/io/InputProvider.java deleted file mode 100644 index 9404bb7..0000000 --- a/ui-console/src/main/java/market/guess/ui/console/io/InputProvider.java +++ /dev/null | |||
| @@ -1,5 +0,0 @@ | |||
| 1 | package market.guess.ui.console.io; | ||
| 2 | |||
| 3 | public interface InputProvider extends AutoCloseable { | ||
| 4 | String nextLine(); | ||
| 5 | } | ||
diff --git a/ui-console/src/main/java/market/guess/ui/console/io/OutputProvider.java b/ui-console/src/main/java/market/guess/ui/console/io/OutputProvider.java deleted file mode 100644 index e419351..0000000 --- a/ui-console/src/main/java/market/guess/ui/console/io/OutputProvider.java +++ /dev/null | |||
| @@ -1,11 +0,0 @@ | |||
| 1 | package market.guess.ui.console.io; | ||
| 2 | |||
| 3 | public interface OutputProvider { | ||
| 4 | void print(Object object); | ||
| 5 | |||
| 6 | void newLine(); | ||
| 7 | |||
| 8 | void splashScreen(); | ||
| 9 | |||
| 10 | void println(Object object); | ||
| 11 | } | ||