aboutsummaryrefslogtreecommitdiffstats
path: root/service/src/main
diff options
context:
space:
mode:
authorKostya <mail@sartin.in>2026-08-14 18:49:26 +0000
committerKostya <mail@sartin.in>2026-08-14 18:49:26 +0000
commit12611a5f0116351ab9114f5403222aead7a8329f (patch)
treef403cb522e4ea63ac2e919d7e72d6afa83eea878 /service/src/main
parentec716e23ea8d827d4401ced8f34d533e30209d55 (diff)
downloadguess-market-12611a5f0116351ab9114f5403222aead7a8329f.tar.gz
guess-market-12611a5f0116351ab9114f5403222aead7a8329f.tar.xz
guess-market-12611a5f0116351ab9114f5403222aead7a8329f.zip
Implement in-memory repositories, XML load validation, and menu exit/list commands
- Add generic Repository<T> 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
Diffstat (limited to 'service/src/main')
-rw-r--r--service/src/main/java/market/guess/service/LocalGuessMarketContext.java11
-rw-r--r--service/src/main/java/market/guess/service/domain/Trade.java4
-rw-r--r--service/src/main/java/market/guess/service/domain/User.java4
-rw-r--r--service/src/main/java/market/guess/service/infrastructure/adapter/InstantTypeAdapter.java3
-rw-r--r--service/src/main/java/market/guess/service/infrastructure/mapper/v2/EventMapperV2.java66
-rw-r--r--service/src/main/java/market/guess/service/infrastructure/provider/LoadValidator.java22
-rw-r--r--service/src/main/java/market/guess/service/infrastructure/provider/v1/XMLLoaderV1.java (renamed from service/src/main/java/market/guess/service/infrastructure/provider/v1/XMLLoader.java)33
-rw-r--r--service/src/main/java/market/guess/service/mechanism/OrderBookTradingMechanism.java40
-rw-r--r--service/src/main/java/market/guess/service/repository/EventRepository.java9
-rw-r--r--service/src/main/java/market/guess/service/repository/InMemoryEventRepository.java20
-rw-r--r--service/src/main/java/market/guess/service/repository/InMemoryUserRepository.java22
-rw-r--r--service/src/main/java/market/guess/service/repository/Repository.java12
-rw-r--r--service/src/main/java/market/guess/service/repository/UserRepository.java8
13 files changed, 201 insertions, 53 deletions
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 {
41 41
42 @Override 42 @Override
43 public List<EventSummaryDTO> listEvents() { 43 public List<EventSummaryDTO> listEvents() {
44 return eventRepository.getAllEvents().stream().map(Event::toEventSummary).toList(); 44 return eventRepository.getAll().stream().map(Event::toEventSummary).toList();
45 } 45 }
46 46
47 @Override 47 @Override
@@ -58,9 +58,14 @@ public final class LocalGuessMarketContext implements GuessMarketContext {
58 public PurchaseReceiptDTO buyShares( 58 public PurchaseReceiptDTO buyShares(
59 String userName, String eventKey, String optionKey, int quantity) 59 String userName, String eventKey, String optionKey, int quantity)
60 throws GuessMarketException { 60 throws GuessMarketException {
61 var event = eventRepository.getEvent(eventKey); 61 var optional = eventRepository.get(eventKey);
62
63 var userOptional = userRepository.get(userName);
64 if (optional.isEmpty() || userOptional.isEmpty()) throw new GuessMarketException();
65
66 var event = optional.get();
67 var user = userOptional.get();
62 var option = event.getOption(optionKey); 68 var option = event.getOption(optionKey);
63 var user = userRepository.getUser(userName);
64 var trade = tradingOperation.buy(event, user, option, quantity); 69 var trade = tradingOperation.buy(event, user, option, quantity);
65 return new PurchaseReceiptDTO(); 70 return new PurchaseReceiptDTO();
66 } 71 }
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;
4import java.time.Instant; 4import java.time.Instant;
5 5
6public record Trade( 6public record Trade(
7 long seq, 7 int id,
8 Instant at, 8 Instant at,
9 String userName, 9 String userName,
10 String optionKey, 10 String optionKey,
11 String optionName, 11 String optionName,
12 long quantity, 12 int uantity,
13 BigDecimal sharesCost, 13 BigDecimal sharesCost,
14 BigDecimal commission) { 14 BigDecimal commission) {
15 15
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 {
6 private final String name; 6 private final String name;
7 private final Account account; 7 private final Account account;
8 8
9 public User(String name, int initialBalance) {
10 this(name, BigDecimalOptions.toMoney(initialBalance));
11 }
12
9 public User(String name, BigDecimal initialBalance) { 13 public User(String name, BigDecimal initialBalance) {
10 this.name = name; 14 this.name = name;
11 this.account = new Account(name, initialBalance); 15 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.*;
4import java.lang.reflect.Type; 4import java.lang.reflect.Type;
5import java.time.Instant; 5import java.time.Instant;
6 6
7public class InstantTypeAdapter implements JsonSerializer<Instant>, JsonDeserializer<Instant> { 7public final class InstantTypeAdapter
8 implements JsonSerializer<Instant>, JsonDeserializer<Instant> {
8 @Override 9 @Override
9 public JsonElement serialize(Instant src, Type typeOfSrc, JsonSerializationContext context) { 10 public JsonElement serialize(Instant src, Type typeOfSrc, JsonSerializationContext context) {
10 return new JsonPrimitive(src.toString()); 11 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 @@
1package market.guess.service.infrastructure.mapper.v2;
2
3import java.util.ArrayList;
4import java.util.List;
5import market.guess.api.CommissionTiming;
6import market.guess.api.EventStatus;
7import market.guess.service.domain.Event;
8import market.guess.service.domain.Option;
9import market.guess.service.infrastructure.mapper.Mapper;
10import market.guess.service.mechanism.LmsrTradingMechanism;
11import market.guess.service.mechanism.OrderBookTradingMechanism;
12import market.guess.service.mechanism.TradingMechanism;
13import market.guess.service.model.v2.GMEvent;
14import market.guess.service.model.v2.GMLMSR;
15import market.guess.service.model.v2.GMOrderBook;
16
17public final class EventMapperV2 implements Mapper<GMEvent, Event> {
18
19 @Override
20 public Event toDomain(GMEvent source) {
21 return new Event(
22 getEventKey(source),
23 source.getId(),
24 source.getName(),
25 source.getDescription(),
26 source.getCommission().getValue(),
27 getTiming(source.getCommission().getType()),
28 getMechanism(source),
29 EventStatus.NOT_STARTED,
30 getOptions(source),
31 "");
32 }
33
34 private static String getEventKey(GMEvent event) {
35 return String.valueOf(event.getId());
36 }
37
38 private static CommissionTiming getTiming(String type) throws IllegalArgumentException {
39 return switch (type) {
40 case "on-close" -> CommissionTiming.ON_CLOSE;
41 case "on-purchase" -> CommissionTiming.ON_PURCHASE;
42 default -> throw new IllegalArgumentException("Unknown commission type: " + type);
43 };
44 }
45
46 private static TradingMechanism getMechanism(GMEvent source) {
47 if (source.getGMMethod().getGMLMSR() instanceof GMLMSR lmsr) {
48 return new LmsrTradingMechanism(lmsr.getB());
49 }
50 if (source.getGMMethod().getGMOrderBook() instanceof GMOrderBook orderBook) {
51 return new OrderBookTradingMechanism(
52 orderBook.getAllowMint().equalsIgnoreCase("true"),
53 orderBook.getInitial(),
54 orderBook.getD());
55 }
56 throw new IllegalArgumentException();
57 }
58
59 private static List<Option> getOptions(GMEvent x) {
60 var ret = new ArrayList<Option>(x.getGMOptions().getGMOption().size());
61 for (var i = 0; i < x.getGMOptions().getGMOption().size(); i++) {
62 ret.add(new Option(getEventKey(x) + ":" + i, x.getGMOptions().getGMOption().get(i)));
63 }
64 return ret;
65 }
66}
diff --git a/service/src/main/java/market/guess/service/infrastructure/provider/LoadValidator.java b/service/src/main/java/market/guess/service/infrastructure/provider/LoadValidator.java
new file mode 100644
index 0000000..c1c3895
--- /dev/null
+++ b/service/src/main/java/market/guess/service/infrastructure/provider/LoadValidator.java
@@ -0,0 +1,22 @@
1package market.guess.service.infrastructure.provider;
2
3import java.util.ArrayList;
4import java.util.Collections;
5import java.util.List;
6import market.guess.api.ProblemDTO;
7
8public final class LoadValidator {
9 private final List<ProblemDTO> problems = new ArrayList<>();
10
11 public void append(String code, String humanizedMessage, String location) {
12 problems.add(new ProblemDTO(code, humanizedMessage, location));
13 }
14
15 public boolean isValid() {
16 return problems.isEmpty();
17 }
18
19 public List<ProblemDTO> getIssues() {
20 return Collections.unmodifiableList(problems);
21 }
22}
diff --git a/service/src/main/java/market/guess/service/infrastructure/provider/v1/XMLLoader.java b/service/src/main/java/market/guess/service/infrastructure/provider/v1/XMLLoaderV1.java
index 9333350..7af22ea 100644
--- a/service/src/main/java/market/guess/service/infrastructure/provider/v1/XMLLoader.java
+++ b/service/src/main/java/market/guess/service/infrastructure/provider/v1/XMLLoaderV1.java
@@ -4,28 +4,24 @@ import jakarta.xml.bind.JAXBContext;
4import jakarta.xml.bind.JAXBException; 4import jakarta.xml.bind.JAXBException;
5import java.nio.file.Files; 5import java.nio.file.Files;
6import java.nio.file.Path; 6import java.nio.file.Path;
7import java.util.List;
8import market.guess.api.LoadResultDTO; 7import market.guess.api.LoadResultDTO;
9import market.guess.service.domain.BigDecimalOptions; 8import market.guess.service.domain.User;
10import market.guess.service.domain.Event; 9import market.guess.service.infrastructure.mapper.v1.EventMapperV1;
11import market.guess.service.infrastructure.mapper.Mapper; 10import market.guess.service.infrastructure.provider.LoadValidator;
12import market.guess.service.infrastructure.provider.Loader; 11import market.guess.service.infrastructure.provider.Loader;
13import market.guess.service.model.v1.GMEvent;
14import market.guess.service.model.v1.GuessMarket; 12import market.guess.service.model.v1.GuessMarket;
15import market.guess.service.repository.EventRepository; 13import market.guess.service.repository.EventRepository;
16import market.guess.service.repository.UserRepository; 14import market.guess.service.repository.UserRepository;
17 15
18public final class XMLLoader implements Loader { 16public final class XMLLoaderV1 implements Loader {
19 private final JAXBContext context; 17 private final JAXBContext context;
20 18
21 private final EventRepository eventRepository; 19 private final EventRepository eventRepository;
22 private final UserRepository userRepository; 20 private final UserRepository userRepository;
23 private final Mapper<GMEvent, Event> mapper; 21 private final EventMapperV1 mapper;
24 22
25 public XMLLoader( 23 public XMLLoaderV1(
26 EventRepository eventRepository, 24 EventRepository eventRepository, UserRepository userRepository, EventMapperV1 mapper) {
27 UserRepository userRepository,
28 Mapper<GMEvent, Event> mapper) {
29 super(); 25 super();
30 this.eventRepository = eventRepository; 26 this.eventRepository = eventRepository;
31 this.userRepository = userRepository; 27 this.userRepository = userRepository;
@@ -39,22 +35,27 @@ public final class XMLLoader implements Loader {
39 35
40 @Override 36 @Override
41 public LoadResultDTO load(Path path) { 37 public LoadResultDTO load(Path path) {
38 var validator = new LoadValidator();
39 if (path.getFileName() == null
40 || !path.getFileName().toString().toLowerCase().endsWith(".xml")) {
41 validator.append("NOT_XML", "The file must be an XML.", path.toString());
42 }
42 43
43 try (var stream = Files.newInputStream(path)) { 44 try (var stream = Files.newInputStream(path)) {
44 45
45 var doc = (GuessMarket) context.createUnmarshaller().unmarshal(stream); 46 var doc = (GuessMarket) context.createUnmarshaller().unmarshal(stream);
46 47
47 for (var event : doc.getGMEvents().getGMEvent()) { 48 for (var event : doc.getGMEvents().getGMEvent()) {
48 eventRepository.addEvent(mapper.toDomain(event)); 49 eventRepository.add(mapper.toDomain(event));
49 } 50 }
50 51
51 userRepository.addUser("Tester", BigDecimalOptions.toMoney(9_999_999)); 52 userRepository.add(new User("Tester", 9_999_999));
52 53
53 return LoadResultDTO.success("udk", 1); 54 return LoadResultDTO.success(path.toString(), eventRepository.getAll().size());
54 } catch (Exception e) { 55 } catch (Exception e) {
55 56 validator.append("UNREADABLE", "The file could not be read.", path.toString());
56 } 57 }
57 58
58 return LoadResultDTO.failure(path.toFile().getName(), List.of()); 59 return LoadResultDTO.failure(path.toString(), validator.getIssues());
59 } 60 }
60} 61}
diff --git a/service/src/main/java/market/guess/service/mechanism/OrderBookTradingMechanism.java b/service/src/main/java/market/guess/service/mechanism/OrderBookTradingMechanism.java
new file mode 100644
index 0000000..62feb02
--- /dev/null
+++ b/service/src/main/java/market/guess/service/mechanism/OrderBookTradingMechanism.java
@@ -0,0 +1,40 @@
1package market.guess.service.mechanism;
2
3import java.math.BigDecimal;
4import market.guess.api.MechanismType;
5
6public final class OrderBookTradingMechanism implements TradingMechanism {
7 private final boolean allowMint;
8 private final int initial;
9 private final int d;
10
11 public OrderBookTradingMechanism(boolean allowMint, int initial, int d) {
12 super();
13 this.allowMint = allowMint;
14 this.initial = initial;
15 this.d = d;
16 }
17
18 @Override
19 public MechanismType type() {
20 return MechanismType.ORDER_BOOK;
21 }
22
23 @Override
24 public BigDecimal openingCost(int optionCount) {
25 // TODO Auto-generated method stub
26 throw new UnsupportedOperationException("Unimplemented method 'openingCost'");
27 }
28
29 @Override
30 public BigDecimal costOfBuying(long[] q, int optionIndex, long quantity) {
31 // TODO Auto-generated method stub
32 throw new UnsupportedOperationException("Unimplemented method 'costOfBuying'");
33 }
34
35 @Override
36 public double[] prices(long[] q) {
37 // TODO Auto-generated method stub
38 throw new UnsupportedOperationException("Unimplemented method 'prices'");
39 }
40}
diff --git a/service/src/main/java/market/guess/service/repository/EventRepository.java b/service/src/main/java/market/guess/service/repository/EventRepository.java
index 94999cb..afc5371 100644
--- a/service/src/main/java/market/guess/service/repository/EventRepository.java
+++ b/service/src/main/java/market/guess/service/repository/EventRepository.java
@@ -1,12 +1,5 @@
1package market.guess.service.repository; 1package market.guess.service.repository;
2 2
3import java.util.List;
4import market.guess.service.domain.Event; 3import market.guess.service.domain.Event;
5 4
6public interface EventRepository { 5public interface EventRepository extends Repository<Event> {}
7 Event getEvent(String id);
8
9 List<Event> getAllEvents();
10
11 void addEvent(Event event);
12}
diff --git a/service/src/main/java/market/guess/service/repository/InMemoryEventRepository.java b/service/src/main/java/market/guess/service/repository/InMemoryEventRepository.java
index 81eba74..eb7763e 100644
--- a/service/src/main/java/market/guess/service/repository/InMemoryEventRepository.java
+++ b/service/src/main/java/market/guess/service/repository/InMemoryEventRepository.java
@@ -1,25 +1,27 @@
1package market.guess.service.repository; 1package market.guess.service.repository;
2 2
3import java.util.ArrayList;
4import java.util.Collections;
3import java.util.List; 5import java.util.List;
6import java.util.Optional;
4import market.guess.service.domain.Event; 7import market.guess.service.domain.Event;
5 8
6public final class InMemoryEventRepository implements EventRepository { 9public final class InMemoryEventRepository implements EventRepository {
10 private final List<Event> events = new ArrayList<>();
7 11
8 @Override 12 @Override
9 public Event getEvent(String id) { 13 public Event add(Event element) {
10 // TODO Auto-generated method stub 14 events.add(element);
11 throw new UnsupportedOperationException("Unimplemented method 'getEvent'"); 15 return element;
12 } 16 }
13 17
14 @Override 18 @Override
15 public List<Event> getAllEvents() { 19 public Optional<Event> get(String id) {
16 // TODO Auto-generated method stub 20 return events.stream().filter(event -> event.getEventKey().equalsIgnoreCase(id)).findFirst();
17 throw new UnsupportedOperationException("Unimplemented method 'getAllEvents'");
18 } 21 }
19 22
20 @Override 23 @Override
21 public void addEvent(Event event) { 24 public List<Event> getAll() {
22 // TODO Auto-generated method stub 25 return Collections.unmodifiableList(events);
23 throw new UnsupportedOperationException("Unimplemented method 'addEvent'");
24 } 26 }
25} 27}
diff --git a/service/src/main/java/market/guess/service/repository/InMemoryUserRepository.java b/service/src/main/java/market/guess/service/repository/InMemoryUserRepository.java
index 951dea8..0b5cfe7 100644
--- a/service/src/main/java/market/guess/service/repository/InMemoryUserRepository.java
+++ b/service/src/main/java/market/guess/service/repository/InMemoryUserRepository.java
@@ -1,19 +1,27 @@
1package market.guess.service.repository; 1package market.guess.service.repository;
2 2
3import java.math.BigDecimal; 3import java.util.ArrayList;
4import java.util.Collections;
5import java.util.List;
6import java.util.Optional;
4import market.guess.service.domain.User; 7import market.guess.service.domain.User;
5 8
6public final class InMemoryUserRepository implements UserRepository { 9public final class InMemoryUserRepository implements UserRepository {
10 private final List<User> users = new ArrayList<>();
7 11
8 @Override 12 @Override
9 public User getUser(String name) { 13 public User add(User element) {
10 // TODO Auto-generated method stub 14 users.add(element);
11 throw new UnsupportedOperationException("Unimplemented method 'getUser'"); 15 return element;
12 } 16 }
13 17
14 @Override 18 @Override
15 public void addUser(String name, BigDecimal initialBalance) { 19 public Optional<User> get(String id) {
16 // TODO Auto-generated method stub 20 return users.stream().filter(u -> u.getName().equalsIgnoreCase(id)).findFirst();
17 throw new UnsupportedOperationException("Unimplemented method 'addUser'"); 21 }
22
23 @Override
24 public List<User> getAll() {
25 return Collections.unmodifiableList(users);
18 } 26 }
19} 27}
diff --git a/service/src/main/java/market/guess/service/repository/Repository.java b/service/src/main/java/market/guess/service/repository/Repository.java
new file mode 100644
index 0000000..8ea1817
--- /dev/null
+++ b/service/src/main/java/market/guess/service/repository/Repository.java
@@ -0,0 +1,12 @@
1package market.guess.service.repository;
2
3import java.util.List;
4import java.util.Optional;
5
6public interface Repository<T> {
7 T add(T element);
8
9 Optional<T> get(String id);
10
11 List<T> getAll();
12}
diff --git a/service/src/main/java/market/guess/service/repository/UserRepository.java b/service/src/main/java/market/guess/service/repository/UserRepository.java
index 78d620d..86cc9a6 100644
--- a/service/src/main/java/market/guess/service/repository/UserRepository.java
+++ b/service/src/main/java/market/guess/service/repository/UserRepository.java
@@ -1,11 +1,5 @@
1package market.guess.service.repository; 1package market.guess.service.repository;
2 2
3import java.math.BigDecimal;
4import market.guess.service.domain.User; 3import market.guess.service.domain.User;
5 4
6public interface UserRepository { 5public interface UserRepository extends Repository<User> {}
7
8 User getUser(String name);
9
10 void addUser(String name, BigDecimal initialBalance);
11}