From 12611a5f0116351ab9114f5403222aead7a8329f Mon Sep 17 00:00:00 2001 From: Kostya Date: Fri, 14 Aug 2026 18:49:26 +0000 Subject: Implement in-memory repositories, XML load validation, and menu exit/list commands - Add generic Repository interface; EventRepository/UserRepository extend it - Implement InMemoryEventRepository/InMemoryUserRepository backed by ArrayList - Rename XMLLoader to XMLLoaderV1, add LoadValidator and mapper/v2 package - Add OrderBookTradingMechanism - Add User(String, int) convenience constructor - Add ListEventsMenuCommand and ExitMenuCommand, isExit() on MenuCommand - Menu now sizes selection range by max command index and exits on isExit() - LoadFileMenuCommand reports validation issues on failed loads - Untrack test-data/ and config.properties from .gitignore --- .gitignore | 3 - .../guess/service/LocalGuessMarketContext.java | 11 +++- .../java/market/guess/service/domain/Trade.java | 4 +- .../java/market/guess/service/domain/User.java | 4 ++ .../infrastructure/adapter/InstantTypeAdapter.java | 3 +- .../infrastructure/mapper/v2/EventMapperV2.java | 66 ++++++++++++++++++++++ .../infrastructure/provider/LoadValidator.java | 22 ++++++++ .../infrastructure/provider/v1/XMLLoader.java | 60 -------------------- .../infrastructure/provider/v1/XMLLoaderV1.java | 61 ++++++++++++++++++++ .../mechanism/OrderBookTradingMechanism.java | 40 +++++++++++++ .../guess/service/repository/EventRepository.java | 9 +-- .../repository/InMemoryEventRepository.java | 20 ++++--- .../service/repository/InMemoryUserRepository.java | 22 +++++--- .../guess/service/repository/Repository.java | 12 ++++ .../guess/service/repository/UserRepository.java | 8 +-- test-data/ex-1/multiple.xml | 47 +++++++++++++++ .../src/main/java/market/guess/ui/console/App.java | 10 +++- .../main/java/market/guess/ui/console/Menu.java | 8 ++- .../guess/ui/console/command/ExitMenuCommand.java | 37 ++++++++++++ .../ui/console/command/ListEventsMenuCommand.java | 51 +++++++++++++++++ .../ui/console/command/LoadFileMenuCommand.java | 6 ++ .../guess/ui/console/command/MenuCommand.java | 4 ++ 22 files changed, 403 insertions(+), 105 deletions(-) create mode 100644 service/src/main/java/market/guess/service/infrastructure/mapper/v2/EventMapperV2.java create mode 100644 service/src/main/java/market/guess/service/infrastructure/provider/LoadValidator.java delete mode 100644 service/src/main/java/market/guess/service/infrastructure/provider/v1/XMLLoader.java create mode 100644 service/src/main/java/market/guess/service/infrastructure/provider/v1/XMLLoaderV1.java create mode 100644 service/src/main/java/market/guess/service/mechanism/OrderBookTradingMechanism.java create mode 100644 service/src/main/java/market/guess/service/repository/Repository.java create mode 100644 test-data/ex-1/multiple.xml create mode 100644 ui-console/src/main/java/market/guess/ui/console/command/ExitMenuCommand.java create mode 100644 ui-console/src/main/java/market/guess/ui/console/command/ListEventsMenuCommand.java diff --git a/.gitignore b/.gitignore index 8e3b8c9..7a9d622 100644 --- a/.gitignore +++ b/.gitignore @@ -2,12 +2,9 @@ bin/ stage/ dist/ -test-data/ *.class # Ignore OS-specific files .DS_Store Thumbs.db -config.properties - diff --git a/service/src/main/java/market/guess/service/LocalGuessMarketContext.java b/service/src/main/java/market/guess/service/LocalGuessMarketContext.java index 16ee1b8..19c7297 100644 --- a/service/src/main/java/market/guess/service/LocalGuessMarketContext.java +++ b/service/src/main/java/market/guess/service/LocalGuessMarketContext.java @@ -41,7 +41,7 @@ public final class LocalGuessMarketContext implements GuessMarketContext { @Override public List listEvents() { - return eventRepository.getAllEvents().stream().map(Event::toEventSummary).toList(); + return eventRepository.getAll().stream().map(Event::toEventSummary).toList(); } @Override @@ -58,9 +58,14 @@ public final class LocalGuessMarketContext implements GuessMarketContext { public PurchaseReceiptDTO buyShares( String userName, String eventKey, String optionKey, int quantity) throws GuessMarketException { - var event = eventRepository.getEvent(eventKey); + var optional = eventRepository.get(eventKey); + + var userOptional = userRepository.get(userName); + if (optional.isEmpty() || userOptional.isEmpty()) throw new GuessMarketException(); + + var event = optional.get(); + var user = userOptional.get(); var option = event.getOption(optionKey); - var user = userRepository.getUser(userName); var trade = tradingOperation.buy(event, user, option, quantity); return new PurchaseReceiptDTO(); } diff --git a/service/src/main/java/market/guess/service/domain/Trade.java b/service/src/main/java/market/guess/service/domain/Trade.java index cb6ed93..0c07749 100644 --- a/service/src/main/java/market/guess/service/domain/Trade.java +++ b/service/src/main/java/market/guess/service/domain/Trade.java @@ -4,12 +4,12 @@ import java.math.BigDecimal; import java.time.Instant; public record Trade( - long seq, + int id, Instant at, String userName, String optionKey, String optionName, - long quantity, + int uantity, BigDecimal sharesCost, BigDecimal commission) { diff --git a/service/src/main/java/market/guess/service/domain/User.java b/service/src/main/java/market/guess/service/domain/User.java index af9ac78..b2084b4 100644 --- a/service/src/main/java/market/guess/service/domain/User.java +++ b/service/src/main/java/market/guess/service/domain/User.java @@ -6,6 +6,10 @@ public final class User { private final String name; private final Account account; + public User(String name, int initialBalance) { + this(name, BigDecimalOptions.toMoney(initialBalance)); + } + public User(String name, BigDecimal initialBalance) { this.name = name; this.account = new Account(name, initialBalance); diff --git a/service/src/main/java/market/guess/service/infrastructure/adapter/InstantTypeAdapter.java b/service/src/main/java/market/guess/service/infrastructure/adapter/InstantTypeAdapter.java index 0e8a0e0..bc5ddb0 100644 --- a/service/src/main/java/market/guess/service/infrastructure/adapter/InstantTypeAdapter.java +++ b/service/src/main/java/market/guess/service/infrastructure/adapter/InstantTypeAdapter.java @@ -4,7 +4,8 @@ import com.google.gson.*; import java.lang.reflect.Type; import java.time.Instant; -public class InstantTypeAdapter implements JsonSerializer, JsonDeserializer { +public final class InstantTypeAdapter + implements JsonSerializer, JsonDeserializer { @Override public JsonElement serialize(Instant src, Type typeOfSrc, JsonSerializationContext context) { return new JsonPrimitive(src.toString()); diff --git a/service/src/main/java/market/guess/service/infrastructure/mapper/v2/EventMapperV2.java b/service/src/main/java/market/guess/service/infrastructure/mapper/v2/EventMapperV2.java new file mode 100644 index 0000000..687ec33 --- /dev/null +++ b/service/src/main/java/market/guess/service/infrastructure/mapper/v2/EventMapperV2.java @@ -0,0 +1,66 @@ +package market.guess.service.infrastructure.mapper.v2; + +import java.util.ArrayList; +import java.util.List; +import market.guess.api.CommissionTiming; +import market.guess.api.EventStatus; +import market.guess.service.domain.Event; +import market.guess.service.domain.Option; +import market.guess.service.infrastructure.mapper.Mapper; +import market.guess.service.mechanism.LmsrTradingMechanism; +import market.guess.service.mechanism.OrderBookTradingMechanism; +import market.guess.service.mechanism.TradingMechanism; +import market.guess.service.model.v2.GMEvent; +import market.guess.service.model.v2.GMLMSR; +import market.guess.service.model.v2.GMOrderBook; + +public final class EventMapperV2 implements Mapper { + + @Override + public Event toDomain(GMEvent source) { + return new Event( + getEventKey(source), + source.getId(), + source.getName(), + source.getDescription(), + source.getCommission().getValue(), + getTiming(source.getCommission().getType()), + getMechanism(source), + EventStatus.NOT_STARTED, + getOptions(source), + ""); + } + + private static String getEventKey(GMEvent event) { + return String.valueOf(event.getId()); + } + + private static CommissionTiming getTiming(String type) throws IllegalArgumentException { + return switch (type) { + case "on-close" -> CommissionTiming.ON_CLOSE; + case "on-purchase" -> CommissionTiming.ON_PURCHASE; + default -> throw new IllegalArgumentException("Unknown commission type: " + type); + }; + } + + private static TradingMechanism getMechanism(GMEvent source) { + if (source.getGMMethod().getGMLMSR() instanceof GMLMSR lmsr) { + return new LmsrTradingMechanism(lmsr.getB()); + } + if (source.getGMMethod().getGMOrderBook() instanceof GMOrderBook orderBook) { + return new OrderBookTradingMechanism( + orderBook.getAllowMint().equalsIgnoreCase("true"), + orderBook.getInitial(), + orderBook.getD()); + } + throw new IllegalArgumentException(); + } + + private static List