diff options
28 files changed, 573 insertions, 199 deletions
| @@ -1,10 +1,6 @@ | |||
| 1 | # Ignore compiled Java bytecode | 1 | # Ignore compiled Java bytecode |
| 2 | bin/ | 2 | bin/ |
| 3 | stage/ | 3 | stage/ |
| 4 | dist/ | 4 | build/ |
| 5 | *.class | 5 | *.class |
| 6 | 6 | ||
| 7 | # Ignore OS-specific files | ||
| 8 | .DS_Store | ||
| 9 | Thumbs.db | ||
| 10 | |||
diff --git a/service/src/main/java/market/guess/service/catalog/LocalCatalogContext.java b/service/src/main/java/market/guess/service/catalog/LocalCatalogContext.java index 7a32e62..270aadd 100644 --- a/service/src/main/java/market/guess/service/catalog/LocalCatalogContext.java +++ b/service/src/main/java/market/guess/service/catalog/LocalCatalogContext.java | |||
| @@ -48,7 +48,7 @@ public final class LocalCatalogContext implements CatalogContext { | |||
| 48 | @Override | 48 | @Override |
| 49 | public Result<Void> saveState(Path path) { | 49 | public Result<Void> saveState(Path path) { |
| 50 | try { | 50 | try { |
| 51 | context.save(); | 51 | context.save(path); |
| 52 | } catch (Exception e) { | 52 | } catch (Exception e) { |
| 53 | return Result.error("Failed to save.", e.getMessage()); | 53 | return Result.error("Failed to save.", e.getMessage()); |
| 54 | } | 54 | } |
| @@ -58,7 +58,7 @@ public final class LocalCatalogContext implements CatalogContext { | |||
| 58 | @Override | 58 | @Override |
| 59 | public Result<LoadResult> restoreState(Path path) { | 59 | public Result<LoadResult> restoreState(Path path) { |
| 60 | try { | 60 | try { |
| 61 | context.load(); | 61 | context.load(path); |
| 62 | } catch (Exception e) { | 62 | } catch (Exception e) { |
| 63 | return Result.error("Failed to load.", e.getMessage()); | 63 | return Result.error("Failed to load.", e.getMessage()); |
| 64 | } | 64 | } |
diff --git a/service/src/main/java/market/guess/service/catalog/infrastructure/MarketContext.java b/service/src/main/java/market/guess/service/catalog/infrastructure/MarketContext.java index 24521ef..cb2b467 100644 --- a/service/src/main/java/market/guess/service/catalog/infrastructure/MarketContext.java +++ b/service/src/main/java/market/guess/service/catalog/infrastructure/MarketContext.java | |||
| @@ -7,15 +7,24 @@ import java.io.IOException; | |||
| 7 | import java.nio.charset.StandardCharsets; | 7 | import java.nio.charset.StandardCharsets; |
| 8 | import java.nio.file.Files; | 8 | import java.nio.file.Files; |
| 9 | import java.nio.file.Path; | 9 | import java.nio.file.Path; |
| 10 | import java.nio.file.Paths; | ||
| 11 | import java.time.Instant; | 10 | import java.time.Instant; |
| 12 | import java.util.List; | 11 | import java.util.List; |
| 13 | import market.guess.exception.GuessMarketException; | 12 | import market.guess.exception.GuessMarketException; |
| 13 | import market.guess.service.catalog.infrastructure.adapter.EventDeserializer; | ||
| 14 | import market.guess.service.catalog.infrastructure.adapter.InstantTypeAdapter; | 14 | import market.guess.service.catalog.infrastructure.adapter.InstantTypeAdapter; |
| 15 | import market.guess.service.catalog.infrastructure.adapter.LedgerDeserializer; | ||
| 16 | import market.guess.service.catalog.infrastructure.adapter.LmsrTradingMechanismAdapter; | ||
| 17 | import market.guess.service.catalog.infrastructure.adapter.MarketDeserializer; | ||
| 18 | import market.guess.service.catalog.infrastructure.adapter.TradingMechanismAdapter; | ||
| 19 | import market.guess.service.catalog.infrastructure.adapter.UserDeserializer; | ||
| 15 | import market.guess.service.catalog.infrastructure.repository.EventRepository; | 20 | import market.guess.service.catalog.infrastructure.repository.EventRepository; |
| 16 | import market.guess.service.catalog.infrastructure.repository.UserRepository; | 21 | import market.guess.service.catalog.infrastructure.repository.UserRepository; |
| 17 | import market.guess.service.domain.Event; | 22 | import market.guess.service.domain.Event; |
| 23 | import market.guess.service.domain.Market; | ||
| 18 | import market.guess.service.domain.User; | 24 | import market.guess.service.domain.User; |
| 25 | import market.guess.service.ledger.Ledger; | ||
| 26 | import market.guess.service.mechanism.LmsrTradingMechanism; | ||
| 27 | import market.guess.service.mechanism.TradingMechanism; | ||
| 19 | 28 | ||
| 20 | public final class MarketContext { | 29 | public final class MarketContext { |
| 21 | record Wrapper(List<Event> events, List<User> users) {} | 30 | record Wrapper(List<Event> events, List<User> users) {} |
| @@ -26,6 +35,12 @@ public final class MarketContext { | |||
| 26 | private static final Gson GSON = | 35 | private static final Gson GSON = |
| 27 | new GsonBuilder() | 36 | new GsonBuilder() |
| 28 | .registerTypeAdapter(Instant.class, new InstantTypeAdapter()) | 37 | .registerTypeAdapter(Instant.class, new InstantTypeAdapter()) |
| 38 | .registerTypeAdapter(Event.class, new EventDeserializer()) | ||
| 39 | .registerTypeAdapter(Market.class, new MarketDeserializer()) | ||
| 40 | .registerTypeAdapter(Ledger.class, new LedgerDeserializer()) | ||
| 41 | .registerTypeAdapter(TradingMechanism.class, new TradingMechanismAdapter()) | ||
| 42 | .registerTypeAdapter(LmsrTradingMechanism.class, new LmsrTradingMechanismAdapter()) | ||
| 43 | .registerTypeAdapter(User.class, new UserDeserializer()) | ||
| 29 | .setPrettyPrinting() | 44 | .setPrettyPrinting() |
| 30 | .create(); | 45 | .create(); |
| 31 | 46 | ||
| @@ -42,23 +57,25 @@ public final class MarketContext { | |||
| 42 | return users; | 57 | return users; |
| 43 | } | 58 | } |
| 44 | 59 | ||
| 45 | public void save() throws GuessMarketException { | 60 | public void save(Path path) throws GuessMarketException { |
| 46 | var path = Paths.get("test-file.json"); | 61 | var target = withJsonExtension(path); |
| 47 | 62 | ||
| 48 | try { | 63 | try { |
| 49 | if (path.getParent() != null) Files.createDirectories(path.getParent()); | 64 | if (target.getParent() != null) Files.createDirectories(target.getParent()); |
| 50 | Files.writeString( | 65 | Files.writeString( |
| 51 | path, GSON.toJson(new Wrapper(events.getAll(), users.getAll())), StandardCharsets.UTF_8); | 66 | target, |
| 52 | } catch (IOException e) { | 67 | GSON.toJson(new Wrapper(events.getAll(), users.getAll())), |
| 68 | StandardCharsets.UTF_8); | ||
| 69 | } catch (Exception e) { | ||
| 53 | throw new GuessMarketException("Failed to sove the file."); | 70 | throw new GuessMarketException("Failed to sove the file."); |
| 54 | } | 71 | } |
| 55 | } | 72 | } |
| 56 | 73 | ||
| 57 | public void load() throws GuessMarketException { | 74 | public void load(Path path) throws GuessMarketException { |
| 58 | try { | 75 | try { |
| 59 | var wrapper = | 76 | var wrapper = |
| 60 | GSON.fromJson( | 77 | GSON.fromJson( |
| 61 | Files.readString(Path.of("test-file.json"), StandardCharsets.UTF_8), Wrapper.class); | 78 | Files.readString(withJsonExtension(path), StandardCharsets.UTF_8), Wrapper.class); |
| 62 | events.clear(); | 79 | events.clear(); |
| 63 | users.clear(); | 80 | users.clear(); |
| 64 | 81 | ||
| @@ -76,4 +93,9 @@ public final class MarketContext { | |||
| 76 | throw new GuessMarketException("Unable the file."); | 93 | throw new GuessMarketException("Unable the file."); |
| 77 | } | 94 | } |
| 78 | } | 95 | } |
| 96 | |||
| 97 | private static Path withJsonExtension(Path path) { | ||
| 98 | var name = path.getFileName().toString(); | ||
| 99 | return name.endsWith(".json") ? path : path.resolveSibling(name + ".json"); | ||
| 100 | } | ||
| 79 | } | 101 | } |
diff --git a/service/src/main/java/market/guess/service/catalog/infrastructure/adapter/EventDeserializer.java b/service/src/main/java/market/guess/service/catalog/infrastructure/adapter/EventDeserializer.java new file mode 100644 index 0000000..69a8447 --- /dev/null +++ b/service/src/main/java/market/guess/service/catalog/infrastructure/adapter/EventDeserializer.java | |||
| @@ -0,0 +1,51 @@ | |||
| 1 | package market.guess.service.catalog.infrastructure.adapter; | ||
| 2 | |||
| 3 | import com.google.gson.JsonDeserializationContext; | ||
| 4 | import com.google.gson.JsonDeserializer; | ||
| 5 | import com.google.gson.JsonElement; | ||
| 6 | import com.google.gson.JsonParseException; | ||
| 7 | import com.google.gson.reflect.TypeToken; | ||
| 8 | import java.lang.reflect.Type; | ||
| 9 | import java.util.List; | ||
| 10 | import market.guess.api.CommissionTiming; | ||
| 11 | import market.guess.model.event.EventStatus; | ||
| 12 | import market.guess.service.domain.Event; | ||
| 13 | import market.guess.service.domain.Market; | ||
| 14 | import market.guess.service.domain.Trade; | ||
| 15 | import market.guess.service.ledger.Ledger; | ||
| 16 | import market.guess.service.mechanism.TradingMechanism; | ||
| 17 | |||
| 18 | /** | ||
| 19 | * Rebuilds an Event through its constructor instead of Gson's default reflective field-setting, | ||
| 20 | * since Event's fields are final and reflective mutation of final fields is being phased out. | ||
| 21 | */ | ||
| 22 | public final class EventDeserializer implements JsonDeserializer<Event> { | ||
| 23 | @Override | ||
| 24 | public Event deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) | ||
| 25 | throws JsonParseException { | ||
| 26 | var obj = json.getAsJsonObject(); | ||
| 27 | Ledger ledger = context.deserialize(obj.get("ledger"), Ledger.class); | ||
| 28 | List<Market> markets = | ||
| 29 | context.deserialize(obj.get("markets"), new TypeToken<List<Market>>() {}.getType()); | ||
| 30 | List<Trade> trades = | ||
| 31 | context.deserialize(obj.get("trades"), new TypeToken<List<Trade>>() {}.getType()); | ||
| 32 | var winningOptionKey = obj.get("winningOptionKey"); | ||
| 33 | |||
| 34 | return new Event( | ||
| 35 | obj.get("eventKey").getAsString(), | ||
| 36 | obj.get("displayId").getAsInt(), | ||
| 37 | obj.get("name").getAsString(), | ||
| 38 | obj.get("description").getAsString(), | ||
| 39 | obj.get("commissionPercent").getAsInt(), | ||
| 40 | context.deserialize(obj.get("commissionTiming"), CommissionTiming.class), | ||
| 41 | context.deserialize(obj.get("mechanism"), TradingMechanism.class), | ||
| 42 | markets, | ||
| 43 | obj.get("marketMaker").getAsString(), | ||
| 44 | ledger, | ||
| 45 | obj.get("runningId").getAsInt(), | ||
| 46 | trades, | ||
| 47 | context.deserialize(obj.get("state"), EventStatus.class), | ||
| 48 | winningOptionKey == null || winningOptionKey.isJsonNull() ? null : winningOptionKey.getAsString(), | ||
| 49 | obj.get("settlementCommission").getAsBigDecimal()); | ||
| 50 | } | ||
| 51 | } | ||
diff --git a/service/src/main/java/market/guess/service/catalog/infrastructure/adapter/LedgerDeserializer.java b/service/src/main/java/market/guess/service/catalog/infrastructure/adapter/LedgerDeserializer.java new file mode 100644 index 0000000..4b3a892 --- /dev/null +++ b/service/src/main/java/market/guess/service/catalog/infrastructure/adapter/LedgerDeserializer.java | |||
| @@ -0,0 +1,28 @@ | |||
| 1 | package market.guess.service.catalog.infrastructure.adapter; | ||
| 2 | |||
| 3 | import com.google.gson.JsonDeserializationContext; | ||
| 4 | import com.google.gson.JsonDeserializer; | ||
| 5 | import com.google.gson.JsonElement; | ||
| 6 | import com.google.gson.JsonParseException; | ||
| 7 | import com.google.gson.reflect.TypeToken; | ||
| 8 | import java.lang.reflect.Type; | ||
| 9 | import java.util.List; | ||
| 10 | import market.guess.service.helpers.BigDecimalOptions; | ||
| 11 | import market.guess.service.ledger.Ledger; | ||
| 12 | import market.guess.service.ledger.LedgerEntry; | ||
| 13 | |||
| 14 | /** Rebuilds a Ledger through its constructor and public record() method, avoiding reflection on its final fields. */ | ||
| 15 | public final class LedgerDeserializer implements JsonDeserializer<Ledger> { | ||
| 16 | @Override | ||
| 17 | public Ledger deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) | ||
| 18 | throws JsonParseException { | ||
| 19 | var obj = json.getAsJsonObject(); | ||
| 20 | var ledger = new Ledger(obj.get("owner").getAsString(), BigDecimalOptions.ZERO_MONEY); | ||
| 21 | |||
| 22 | List<LedgerEntry> entries = | ||
| 23 | context.deserialize(obj.get("entries"), new TypeToken<List<LedgerEntry>>() {}.getType()); | ||
| 24 | entries.forEach(ledger::record); | ||
| 25 | |||
| 26 | return ledger; | ||
| 27 | } | ||
| 28 | } | ||
diff --git a/service/src/main/java/market/guess/service/catalog/infrastructure/adapter/LmsrTradingMechanismAdapter.java b/service/src/main/java/market/guess/service/catalog/infrastructure/adapter/LmsrTradingMechanismAdapter.java new file mode 100644 index 0000000..c090cec --- /dev/null +++ b/service/src/main/java/market/guess/service/catalog/infrastructure/adapter/LmsrTradingMechanismAdapter.java | |||
| @@ -0,0 +1,42 @@ | |||
| 1 | package market.guess.service.catalog.infrastructure.adapter; | ||
| 2 | |||
| 3 | import com.google.gson.JsonDeserializationContext; | ||
| 4 | import com.google.gson.JsonDeserializer; | ||
| 5 | import com.google.gson.JsonElement; | ||
| 6 | import com.google.gson.JsonObject; | ||
| 7 | import com.google.gson.JsonParseException; | ||
| 8 | import com.google.gson.JsonSerializationContext; | ||
| 9 | import com.google.gson.JsonSerializer; | ||
| 10 | import java.lang.reflect.Type; | ||
| 11 | import java.time.Clock; | ||
| 12 | import market.guess.service.mechanism.LmsrTradingMechanism; | ||
| 13 | |||
| 14 | /** | ||
| 15 | * Rebuilds an LmsrTradingMechanism through its constructor, avoiding reflection on its final | ||
| 16 | * fields. The clock is a placeholder here; MarketContext reattaches the live one after load. | ||
| 17 | * | ||
| 18 | * <p>Also writes the type discriminator {@link TradingMechanismAdapter} reads, since Gson prefers | ||
| 19 | * a type adapter registered for an object's runtime type over one registered for its declared | ||
| 20 | * (interface) type, so the interface-level adapter never gets a chance to add it here. | ||
| 21 | */ | ||
| 22 | public final class LmsrTradingMechanismAdapter | ||
| 23 | implements JsonSerializer<LmsrTradingMechanism>, JsonDeserializer<LmsrTradingMechanism> { | ||
| 24 | @Override | ||
| 25 | public JsonElement serialize( | ||
| 26 | LmsrTradingMechanism src, Type typeOfSrc, JsonSerializationContext context) { | ||
| 27 | var json = new JsonObject(); | ||
| 28 | json.addProperty(TradingMechanismAdapter.TYPE_FIELD, src.getType().name()); | ||
| 29 | json.addProperty("liquidity", src.getLiquidity()); | ||
| 30 | json.add("q", context.serialize(src.getQuantities())); | ||
| 31 | return json; | ||
| 32 | } | ||
| 33 | |||
| 34 | @Override | ||
| 35 | public LmsrTradingMechanism deserialize( | ||
| 36 | JsonElement json, Type typeOfT, JsonDeserializationContext context) | ||
| 37 | throws JsonParseException { | ||
| 38 | var obj = json.getAsJsonObject(); | ||
| 39 | var q = context.<int[]>deserialize(obj.get("q"), int[].class); | ||
| 40 | return new LmsrTradingMechanism(obj.get("liquidity").getAsInt(), q, Clock.systemUTC()); | ||
| 41 | } | ||
| 42 | } | ||
diff --git a/service/src/main/java/market/guess/service/catalog/infrastructure/adapter/MarketDeserializer.java b/service/src/main/java/market/guess/service/catalog/infrastructure/adapter/MarketDeserializer.java new file mode 100644 index 0000000..6757292 --- /dev/null +++ b/service/src/main/java/market/guess/service/catalog/infrastructure/adapter/MarketDeserializer.java | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | package market.guess.service.catalog.infrastructure.adapter; | ||
| 2 | |||
| 3 | import com.google.gson.JsonDeserializationContext; | ||
| 4 | import com.google.gson.JsonDeserializer; | ||
| 5 | import com.google.gson.JsonElement; | ||
| 6 | import com.google.gson.JsonParseException; | ||
| 7 | import java.lang.reflect.Type; | ||
| 8 | import market.guess.service.domain.Market; | ||
| 9 | |||
| 10 | /** Rebuilds a Market through its constructor and public mutator, avoiding reflection on its final fields. */ | ||
| 11 | public final class MarketDeserializer implements JsonDeserializer<Market> { | ||
| 12 | @Override | ||
| 13 | public Market deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) | ||
| 14 | throws JsonParseException { | ||
| 15 | var obj = json.getAsJsonObject(); | ||
| 16 | var market = new Market(obj.get("key").getAsString(), obj.get("name").getAsString()); | ||
| 17 | market.addVolume(obj.get("volume").getAsLong()); | ||
| 18 | return market; | ||
| 19 | } | ||
| 20 | } | ||
diff --git a/service/src/main/java/market/guess/service/catalog/infrastructure/adapter/TradingMechanismAdapter.java b/service/src/main/java/market/guess/service/catalog/infrastructure/adapter/TradingMechanismAdapter.java new file mode 100644 index 0000000..ac3da8b --- /dev/null +++ b/service/src/main/java/market/guess/service/catalog/infrastructure/adapter/TradingMechanismAdapter.java | |||
| @@ -0,0 +1,43 @@ | |||
| 1 | package market.guess.service.catalog.infrastructure.adapter; | ||
| 2 | |||
| 3 | import com.google.gson.JsonDeserializationContext; | ||
| 4 | import com.google.gson.JsonDeserializer; | ||
| 5 | import com.google.gson.JsonElement; | ||
| 6 | import com.google.gson.JsonParseException; | ||
| 7 | import com.google.gson.JsonSerializationContext; | ||
| 8 | import com.google.gson.JsonSerializer; | ||
| 9 | import java.lang.reflect.Type; | ||
| 10 | import market.guess.api.MechanismType; | ||
| 11 | import market.guess.service.mechanism.LmsrTradingMechanism; | ||
| 12 | import market.guess.service.mechanism.OrderBookTradingMechanism; | ||
| 13 | import market.guess.service.mechanism.TradingMechanism; | ||
| 14 | |||
| 15 | /** | ||
| 16 | * TradingMechanism is a sealed interface, so Gson can't tell which implementation to build from | ||
| 17 | * the JSON alone; this tags the JSON with a type discriminator and dispatches on it. | ||
| 18 | */ | ||
| 19 | public final class TradingMechanismAdapter | ||
| 20 | implements JsonSerializer<TradingMechanism>, JsonDeserializer<TradingMechanism> { | ||
| 21 | static final String TYPE_FIELD = "mechanismType"; | ||
| 22 | |||
| 23 | @Override | ||
| 24 | public JsonElement serialize( | ||
| 25 | TradingMechanism src, Type typeOfSrc, JsonSerializationContext context) { | ||
| 26 | var json = context.serialize(src, src.getClass()).getAsJsonObject(); | ||
| 27 | json.addProperty(TYPE_FIELD, src.getType().name()); | ||
| 28 | return json; | ||
| 29 | } | ||
| 30 | |||
| 31 | @Override | ||
| 32 | public TradingMechanism deserialize( | ||
| 33 | JsonElement json, Type typeOfT, JsonDeserializationContext context) | ||
| 34 | throws JsonParseException { | ||
| 35 | var obj = json.getAsJsonObject(); | ||
| 36 | var type = MechanismType.valueOf(obj.get(TYPE_FIELD).getAsString()); | ||
| 37 | |||
| 38 | return switch (type) { | ||
| 39 | case LMSR -> context.deserialize(obj, LmsrTradingMechanism.class); | ||
| 40 | case ORDER_BOOK -> context.deserialize(obj, OrderBookTradingMechanism.class); | ||
| 41 | }; | ||
| 42 | } | ||
| 43 | } | ||
diff --git a/service/src/main/java/market/guess/service/catalog/infrastructure/adapter/UserDeserializer.java b/service/src/main/java/market/guess/service/catalog/infrastructure/adapter/UserDeserializer.java new file mode 100644 index 0000000..e91620b --- /dev/null +++ b/service/src/main/java/market/guess/service/catalog/infrastructure/adapter/UserDeserializer.java | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | package market.guess.service.catalog.infrastructure.adapter; | ||
| 2 | |||
| 3 | import com.google.gson.JsonDeserializationContext; | ||
| 4 | import com.google.gson.JsonDeserializer; | ||
| 5 | import com.google.gson.JsonElement; | ||
| 6 | import com.google.gson.JsonParseException; | ||
| 7 | import java.lang.reflect.Type; | ||
| 8 | import market.guess.service.domain.User; | ||
| 9 | import market.guess.service.ledger.Ledger; | ||
| 10 | |||
| 11 | /** Rebuilds a User through its constructor instead of Gson's default reflective field-setting, since its fields are final. */ | ||
| 12 | public final class UserDeserializer implements JsonDeserializer<User> { | ||
| 13 | @Override | ||
| 14 | public User deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) | ||
| 15 | throws JsonParseException { | ||
| 16 | var obj = json.getAsJsonObject(); | ||
| 17 | Ledger account = context.deserialize(obj.get("account"), Ledger.class); | ||
| 18 | return new User(obj.get("name").getAsString(), account); | ||
| 19 | } | ||
| 20 | } | ||
diff --git a/service/src/main/java/market/guess/service/catalog/infrastructure/mapper/v1/EventMapperV1.java b/service/src/main/java/market/guess/service/catalog/infrastructure/mapper/v1/EventMapperV1.java index 78fd550..f45eb46 100644 --- a/service/src/main/java/market/guess/service/catalog/infrastructure/mapper/v1/EventMapperV1.java +++ b/service/src/main/java/market/guess/service/catalog/infrastructure/mapper/v1/EventMapperV1.java | |||
| @@ -26,8 +26,7 @@ public final class EventMapperV1 implements Mapper<GMEvent, Event> { | |||
| 26 | new LmsrTradingMechanism(source.getGMMethod().getGMLMSR().getB(), options.size()), | 26 | new LmsrTradingMechanism(source.getGMMethod().getGMLMSR().getB(), options.size()), |
| 27 | EventStatus.ACTIVE, | 27 | EventStatus.ACTIVE, |
| 28 | options, | 28 | options, |
| 29 | "Tester"); // ponytail: only user in this console app; swap for a real market-maker | 29 | "Tester"); |
| 30 | // concept if multi-user support lands | ||
| 31 | } | 30 | } |
| 32 | 31 | ||
| 33 | private static String getEventKey(GMEvent event) { | 32 | private static String getEventKey(GMEvent event) { |
diff --git a/service/src/main/java/market/guess/service/catalog/infrastructure/provider/v1/XMLLoaderV1.java b/service/src/main/java/market/guess/service/catalog/infrastructure/provider/v1/XMLLoaderV1.java index 67d0b67..b97114d 100644 --- a/service/src/main/java/market/guess/service/catalog/infrastructure/provider/v1/XMLLoaderV1.java +++ b/service/src/main/java/market/guess/service/catalog/infrastructure/provider/v1/XMLLoaderV1.java | |||
| @@ -20,13 +20,18 @@ public final class XMLLoaderV1 implements Loader { | |||
| 20 | private final EventRepository eventRepository; | 20 | private final EventRepository eventRepository; |
| 21 | private final UserRepository userRepository; | 21 | private final UserRepository userRepository; |
| 22 | private final EventMapperV1 mapper; | 22 | private final EventMapperV1 mapper; |
| 23 | private final XMLValidatorV1 validator; | ||
| 23 | 24 | ||
| 24 | public XMLLoaderV1( | 25 | public XMLLoaderV1( |
| 25 | EventRepository eventRepository, UserRepository userRepository, EventMapperV1 mapper) { | 26 | EventRepository eventRepository, |
| 27 | UserRepository userRepository, | ||
| 28 | EventMapperV1 mapper, | ||
| 29 | XMLValidatorV1 validator) { | ||
| 26 | super(); | 30 | super(); |
| 27 | this.eventRepository = eventRepository; | 31 | this.eventRepository = eventRepository; |
| 28 | this.userRepository = userRepository; | 32 | this.userRepository = userRepository; |
| 29 | this.mapper = mapper; | 33 | this.mapper = mapper; |
| 34 | this.validator = validator; | ||
| 30 | try { | 35 | try { |
| 31 | context = JAXBContext.newInstance(GuessMarket.class); | 36 | context = JAXBContext.newInstance(GuessMarket.class); |
| 32 | } catch (JAXBException e) { | 37 | } catch (JAXBException e) { |
| @@ -36,14 +41,25 @@ public final class XMLLoaderV1 implements Loader { | |||
| 36 | 41 | ||
| 37 | @Override | 42 | @Override |
| 38 | public Result<LoadResult> load(Path path) { | 43 | public Result<LoadResult> load(Path path) { |
| 44 | if (!Files.isRegularFile(path)) { | ||
| 45 | return Result.error("FILE_NOT_FOUND", "No such file."); | ||
| 46 | } | ||
| 47 | |||
| 39 | if (path.getFileName() == null | 48 | if (path.getFileName() == null |
| 40 | || !path.getFileName().toString().toLowerCase().endsWith(".xml")) { | 49 | || !path.getFileName().toString().toLowerCase().endsWith(".xml")) { |
| 41 | return Result.error("NOT_XML", "The file must be an XML."); | 50 | return Result.error("NOT_XML", "The file must be an XML."); |
| 42 | } | 51 | } |
| 43 | 52 | ||
| 44 | try (var stream = Files.newInputStream(path)) { | 53 | try (var stream = Files.newInputStream(path)) { |
| 45 | var doc = (GuessMarket) context.createUnmarshaller().unmarshal(stream); | 54 | var seed = (GuessMarket) context.createUnmarshaller().unmarshal(stream); |
| 46 | for (var event : doc.getGMEvents().getGMEvent()) { | 55 | |
| 56 | var validation = validator.validate(seed); | ||
| 57 | |||
| 58 | if (!validation.isSuccess()) { | ||
| 59 | return Result.error(validation.getMessage(), validation.getDetails()); | ||
| 60 | } | ||
| 61 | |||
| 62 | for (var event : seed.getGMEvents().getGMEvent()) { | ||
| 47 | eventRepository.add(mapper.toDomain(event)); | 63 | eventRepository.add(mapper.toDomain(event)); |
| 48 | } | 64 | } |
| 49 | 65 | ||
diff --git a/service/src/main/java/market/guess/service/catalog/infrastructure/provider/v1/XMLValidatorV1.java b/service/src/main/java/market/guess/service/catalog/infrastructure/provider/v1/XMLValidatorV1.java new file mode 100644 index 0000000..567558a --- /dev/null +++ b/service/src/main/java/market/guess/service/catalog/infrastructure/provider/v1/XMLValidatorV1.java | |||
| @@ -0,0 +1,35 @@ | |||
| 1 | package market.guess.service.catalog.infrastructure.provider.v1; | ||
| 2 | |||
| 3 | import java.util.HashSet; | ||
| 4 | import market.guess.api.Result; | ||
| 5 | import market.guess.service.catalog.model.v1.GuessMarket; | ||
| 6 | |||
| 7 | public final class XMLValidatorV1 { | ||
| 8 | private static final int MIN_COMMISSION = 0; | ||
| 9 | private static final int MAX_COMMISSION = 90; | ||
| 10 | |||
| 11 | public Result<Void> validate(GuessMarket seed) { | ||
| 12 | |||
| 13 | var seenIds = new HashSet<Integer>(); | ||
| 14 | var duplicated = new HashSet<Integer>(); | ||
| 15 | |||
| 16 | for (var e : seed.getGMEvents().getGMEvent()) { | ||
| 17 | |||
| 18 | if (!seenIds.add(e.getId()) && duplicated.add(e.getId())) { | ||
| 19 | return Result.error( | ||
| 20 | "DUPLICATE_ID", | ||
| 21 | "Event id " + e.getId() + " appears more than once, every event id must be unique."); | ||
| 22 | } | ||
| 23 | |||
| 24 | var pct = e.getComision().getValue(); | ||
| 25 | if (pct < MIN_COMMISSION || pct > MAX_COMMISSION) { | ||
| 26 | return Result.error( | ||
| 27 | "COMMISSION_OUT_OF_RANGE", | ||
| 28 | "Commission is %d%%, it must be between %d%% and %d%%." | ||
| 29 | .formatted(pct, MIN_COMMISSION, MAX_COMMISSION)); | ||
| 30 | } | ||
| 31 | } | ||
| 32 | |||
| 33 | return Result.ok(); | ||
| 34 | } | ||
| 35 | } | ||
diff --git a/service/src/main/java/market/guess/service/domain/Event.java b/service/src/main/java/market/guess/service/domain/Event.java index 6089aa3..4c78486 100644 --- a/service/src/main/java/market/guess/service/domain/Event.java +++ b/service/src/main/java/market/guess/service/domain/Event.java | |||
| @@ -57,6 +57,40 @@ public final class Event { | |||
| 57 | this.state = status; | 57 | this.state = status; |
| 58 | } | 58 | } |
| 59 | 59 | ||
| 60 | /** Reconstructs an event with its full accumulated state, e.g. from a saved snapshot. */ | ||
| 61 | public Event( | ||
| 62 | String eventKey, | ||
| 63 | int displayId, | ||
| 64 | String name, | ||
| 65 | String description, | ||
| 66 | int commissionPercent, | ||
| 67 | CommissionTiming commissionTiming, | ||
| 68 | TradingMechanism mechanism, | ||
| 69 | List<Market> markets, | ||
| 70 | String marketMaker, | ||
| 71 | Ledger ledger, | ||
| 72 | int runningId, | ||
| 73 | List<Trade> trades, | ||
| 74 | EventStatus state, | ||
| 75 | String winningOptionKey, | ||
| 76 | BigDecimal settlementCommission) { | ||
| 77 | this.eventKey = eventKey; | ||
| 78 | this.displayId = displayId; | ||
| 79 | this.name = name; | ||
| 80 | this.description = description; | ||
| 81 | this.commissionPercent = commissionPercent; | ||
| 82 | this.commissionTiming = commissionTiming; | ||
| 83 | this.mechanism = mechanism; | ||
| 84 | this.markets = markets; | ||
| 85 | this.marketMaker = marketMaker; | ||
| 86 | this.ledger = ledger; | ||
| 87 | this.runningId = runningId; | ||
| 88 | this.trades.addAll(trades); | ||
| 89 | this.state = state; | ||
| 90 | this.winningOptionKey = winningOptionKey; | ||
| 91 | this.settlementCommission = settlementCommission; | ||
| 92 | } | ||
| 93 | |||
| 60 | public TradingMechanism getMechanism() { | 94 | public TradingMechanism getMechanism() { |
| 61 | return mechanism; | 95 | return mechanism; |
| 62 | } | 96 | } |
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 a00b1a0..86ba5af 100644 --- a/service/src/main/java/market/guess/service/domain/User.java +++ b/service/src/main/java/market/guess/service/domain/User.java | |||
| @@ -17,6 +17,12 @@ public final class User { | |||
| 17 | this.account = new Ledger(name, initialBalance); | 17 | this.account = new Ledger(name, initialBalance); |
| 18 | } | 18 | } |
| 19 | 19 | ||
| 20 | /** Reconstructs a user with its full accumulated ledger, e.g. from a saved snapshot. */ | ||
| 21 | public User(String name, Ledger account) { | ||
| 22 | this.name = name; | ||
| 23 | this.account = account; | ||
| 24 | } | ||
| 25 | |||
| 20 | public String getName() { | 26 | public String getName() { |
| 21 | return name; | 27 | return name; |
| 22 | } | 28 | } |
diff --git a/service/src/main/java/market/guess/service/matching/LocalMatchingEngine.java b/service/src/main/java/market/guess/service/matching/LocalMatchingEngine.java index 6a815c8..22e5332 100644 --- a/service/src/main/java/market/guess/service/matching/LocalMatchingEngine.java +++ b/service/src/main/java/market/guess/service/matching/LocalMatchingEngine.java | |||
| @@ -1,15 +1,22 @@ | |||
| 1 | package market.guess.service.matching; | 1 | package market.guess.service.matching; |
| 2 | 2 | ||
| 3 | import java.time.Clock; | ||
| 3 | import java.util.List; | 4 | import java.util.List; |
| 4 | import market.guess.service.domain.Order; | 5 | import market.guess.service.domain.Order; |
| 5 | import market.guess.service.domain.Trade; | 6 | import market.guess.service.domain.Trade; |
| 6 | 7 | ||
| 7 | public final class LocalMatchingEngine implements MatchingEngine { | 8 | public final class LocalMatchingEngine implements MatchingEngine { |
| 9 | private final Clock clock; | ||
| 10 | |||
| 11 | public LocalMatchingEngine(Clock clock) { | ||
| 12 | super(); | ||
| 13 | this.clock = clock; | ||
| 14 | } | ||
| 8 | 15 | ||
| 9 | @Override | 16 | @Override |
| 10 | public List<Trade> match(Order order) { | 17 | public List<Trade> match(Order order) { |
| 11 | var mechanism = order.event().getMechanism(); | 18 | var mechanism = order.event().getMechanism(); |
| 12 | return mechanism.buy(order.event(), order.market(), order.quantity()); | 19 | return mechanism.buy(order.event(), order.market(), clock.instant(), order.quantity()); |
| 13 | } | 20 | } |
| 14 | 21 | ||
| 15 | @Override | 22 | @Override |
diff --git a/service/src/main/java/market/guess/service/mechanism/LmsrTradingMechanism.java b/service/src/main/java/market/guess/service/mechanism/LmsrTradingMechanism.java index 87c5661..062a095 100644 --- a/service/src/main/java/market/guess/service/mechanism/LmsrTradingMechanism.java +++ b/service/src/main/java/market/guess/service/mechanism/LmsrTradingMechanism.java | |||
| @@ -2,6 +2,7 @@ package market.guess.service.mechanism; | |||
| 2 | 2 | ||
| 3 | import java.math.BigDecimal; | 3 | import java.math.BigDecimal; |
| 4 | import java.time.Clock; | 4 | import java.time.Clock; |
| 5 | import java.time.Instant; | ||
| 5 | import java.util.Arrays; | 6 | import java.util.Arrays; |
| 6 | import java.util.List; | 7 | import java.util.List; |
| 7 | import market.guess.api.MechanismType; | 8 | import market.guess.api.MechanismType; |
| @@ -14,17 +15,23 @@ public final class LmsrTradingMechanism implements TradingMechanism { | |||
| 14 | private final int liquidity; | 15 | private final int liquidity; |
| 15 | private final int[] q; | 16 | private final int[] q; |
| 16 | 17 | ||
| 17 | private final Clock clock; | ||
| 18 | |||
| 19 | public LmsrTradingMechanism(int liquidity, int optionCount) { | 18 | public LmsrTradingMechanism(int liquidity, int optionCount) { |
| 20 | this(liquidity, optionCount, Clock.systemUTC()); | 19 | this.q = new int[optionCount]; |
| 20 | this.liquidity = liquidity; | ||
| 21 | } | 21 | } |
| 22 | 22 | ||
| 23 | public LmsrTradingMechanism(int liquidity, int optionCount, Clock clock) { | 23 | public LmsrTradingMechanism(int liquidity, int[] q, Clock clock) { |
| 24 | super(); | 24 | super(); |
| 25 | this.liquidity = liquidity; | 25 | this.liquidity = liquidity; |
| 26 | this.q = new int[optionCount]; | 26 | this.q = q; |
| 27 | this.clock = clock; | 27 | } |
| 28 | |||
| 29 | public int getLiquidity() { | ||
| 30 | return liquidity; | ||
| 31 | } | ||
| 32 | |||
| 33 | public int[] getQuantities() { | ||
| 34 | return q; | ||
| 28 | } | 35 | } |
| 29 | 36 | ||
| 30 | @Override | 37 | @Override |
| @@ -33,7 +40,7 @@ public final class LmsrTradingMechanism implements TradingMechanism { | |||
| 33 | } | 40 | } |
| 34 | 41 | ||
| 35 | @Override | 42 | @Override |
| 36 | public List<Trade> buy(Event event, Market market, long quantity) { | 43 | public List<Trade> buy(Event event, Market market, Instant at, long quantity) { |
| 37 | var optionIndex = event.getMarkets().indexOf(market); | 44 | var optionIndex = event.getMarkets().indexOf(market); |
| 38 | var before = cost(q); | 45 | var before = cost(q); |
| 39 | 46 | ||
| @@ -46,7 +53,7 @@ public final class LmsrTradingMechanism implements TradingMechanism { | |||
| 46 | return List.of( | 53 | return List.of( |
| 47 | new Trade( | 54 | new Trade( |
| 48 | 0, | 55 | 0, |
| 49 | clock.instant(), | 56 | at, |
| 50 | null, | 57 | null, |
| 51 | market.getKey(), | 58 | market.getKey(), |
| 52 | market.getName(), | 59 | market.getName(), |
diff --git a/service/src/main/java/market/guess/service/mechanism/OrderBookTradingMechanism.java b/service/src/main/java/market/guess/service/mechanism/OrderBookTradingMechanism.java index 4e0dd0a..fd03750 100644 --- a/service/src/main/java/market/guess/service/mechanism/OrderBookTradingMechanism.java +++ b/service/src/main/java/market/guess/service/mechanism/OrderBookTradingMechanism.java | |||
| @@ -2,6 +2,7 @@ package market.guess.service.mechanism; | |||
| 2 | 2 | ||
| 3 | import java.math.BigDecimal; | 3 | import java.math.BigDecimal; |
| 4 | import java.math.RoundingMode; | 4 | import java.math.RoundingMode; |
| 5 | import java.time.Instant; | ||
| 5 | import java.util.ArrayList; | 6 | import java.util.ArrayList; |
| 6 | import java.util.List; | 7 | import java.util.List; |
| 7 | import market.guess.api.MechanismType; | 8 | import market.guess.api.MechanismType; |
| @@ -18,15 +19,15 @@ public final class OrderBookTradingMechanism implements TradingMechanism { | |||
| 18 | 19 | ||
| 19 | private static final class RestingOrder { | 20 | private static final class RestingOrder { |
| 20 | private final BigDecimal price; | 21 | private final BigDecimal price; |
| 21 | private int quantity; | 22 | private long quantity; |
| 22 | 23 | ||
| 23 | private RestingOrder(BigDecimal price, int quantity) { | 24 | private RestingOrder(BigDecimal price, long quantity) { |
| 24 | this.price = price; | 25 | this.price = price; |
| 25 | this.quantity = quantity; | 26 | this.quantity = quantity; |
| 26 | } | 27 | } |
| 27 | } | 28 | } |
| 28 | 29 | ||
| 29 | private final boolean allowMint; // ponytail: mint deferred — cross-option matching, its own pass | 30 | private final boolean allowMint; |
| 30 | private final int initial; | 31 | private final int initial; |
| 31 | private final int d; | 32 | private final int d; |
| 32 | private final List<List<RestingOrder>> | 33 | private final List<List<RestingOrder>> |
| @@ -56,17 +57,17 @@ public final class OrderBookTradingMechanism implements TradingMechanism { | |||
| 56 | } | 57 | } |
| 57 | 58 | ||
| 58 | @Override | 59 | @Override |
| 59 | public List<Trade> buy(Event event, Market market, long quantity) { | 60 | public List<Trade> buy(Event event, Market market, Instant at, long quantity) { |
| 60 | var optionIndex = event.getMarkets().indexOf(market); | 61 | var optionIndex = event.getMarkets().indexOf(market); |
| 61 | return List.of(match(optionIndex, Side.BUY, (int) quantity, null, false)); | 62 | return List.of(match(optionIndex, Side.BUY, at, quantity, null, false)); |
| 62 | } | 63 | } |
| 63 | 64 | ||
| 64 | /** Priced limit order — not on the shared interface, no engine call site yet. */ | 65 | /** Priced limit order — not on the shared interface, no engine call site yet. */ |
| 65 | public Trade placeOrder(int optionIndex, Side side, int quantity, BigDecimal price) { | 66 | public Trade placeOrder(int optionIndex, Side side, Instant at, long quantity, BigDecimal price) { |
| 66 | if (price.signum() < 0 || price.compareTo(BigDecimal.valueOf(d)) > 0) { | 67 | if (price.signum() < 0 || price.compareTo(BigDecimal.valueOf(d)) > 0) { |
| 67 | throw new IllegalArgumentException("Price must be between 0 and " + d); | 68 | throw new IllegalArgumentException("Price must be between 0 and " + d); |
| 68 | } | 69 | } |
| 69 | return match(optionIndex, side, quantity, price, true); | 70 | return match(optionIndex, side, at, quantity, price, true); |
| 70 | } | 71 | } |
| 71 | 72 | ||
| 72 | @Override | 73 | @Override |
| @@ -97,7 +98,12 @@ public final class OrderBookTradingMechanism implements TradingMechanism { | |||
| 97 | } | 98 | } |
| 98 | 99 | ||
| 99 | private Trade match( | 100 | private Trade match( |
| 100 | int optionIndex, Side side, int quantity, BigDecimal limitPrice, boolean restRemainder) { | 101 | int optionIndex, |
| 102 | Side side, | ||
| 103 | Instant at, | ||
| 104 | long quantity, | ||
| 105 | BigDecimal limitPrice, | ||
| 106 | boolean restRemainder) { | ||
| 101 | var opposing = side == Side.BUY ? asks.get(optionIndex) : bids.get(optionIndex); | 107 | var opposing = side == Side.BUY ? asks.get(optionIndex) : bids.get(optionIndex); |
| 102 | 108 | ||
| 103 | var filled = 0; | 109 | var filled = 0; |
| @@ -127,7 +133,7 @@ public final class OrderBookTradingMechanism implements TradingMechanism { | |||
| 127 | } | 133 | } |
| 128 | 134 | ||
| 129 | return new Trade( | 135 | return new Trade( |
| 130 | filled, null, null, null, null, filled, BigDecimalOptions.toMoney(proceeds), proceeds); | 136 | filled, at, null, null, null, filled, BigDecimalOptions.toMoney(proceeds), proceeds); |
| 131 | } | 137 | } |
| 132 | 138 | ||
| 133 | private static boolean crosses(Side side, BigDecimal restingPrice, BigDecimal limitPrice) { | 139 | private static boolean crosses(Side side, BigDecimal restingPrice, BigDecimal limitPrice) { |
diff --git a/service/src/main/java/market/guess/service/mechanism/TradingMechanism.java b/service/src/main/java/market/guess/service/mechanism/TradingMechanism.java index 16a5c6f..6fc5a9a 100644 --- a/service/src/main/java/market/guess/service/mechanism/TradingMechanism.java +++ b/service/src/main/java/market/guess/service/mechanism/TradingMechanism.java | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | package market.guess.service.mechanism; | 1 | package market.guess.service.mechanism; |
| 2 | 2 | ||
| 3 | import java.math.BigDecimal; | 3 | import java.math.BigDecimal; |
| 4 | import java.time.Instant; | ||
| 4 | import java.util.List; | 5 | import java.util.List; |
| 5 | import market.guess.api.MechanismType; | 6 | import market.guess.api.MechanismType; |
| 6 | import market.guess.service.domain.Event; | 7 | import market.guess.service.domain.Event; |
| @@ -11,7 +12,7 @@ public sealed interface TradingMechanism permits LmsrTradingMechanism, OrderBook | |||
| 11 | 12 | ||
| 12 | MechanismType getType(); | 13 | MechanismType getType(); |
| 13 | 14 | ||
| 14 | List<Trade> buy(Event event, Market market, long quantity); | 15 | List<Trade> buy(Event event, Market market, Instant at, long quantity); |
| 15 | 16 | ||
| 16 | BigDecimal[] prices(); | 17 | BigDecimal[] prices(); |
| 17 | } | 18 | } |
diff --git a/tools/_util.sh b/tools/_util.sh new file mode 100644 index 0000000..4650616 --- /dev/null +++ b/tools/_util.sh | |||
| @@ -0,0 +1,47 @@ | |||
| 1 | JAVA_RELEASE=25 | ||
| 2 | |||
| 3 | case "$(uname -s)" in | ||
| 4 | CYGWIN*|MINGW*|MSYS*) SEP=";" ;; | ||
| 5 | *) SEP=":" ;; | ||
| 6 | esac | ||
| 7 | |||
| 8 | MODULES="api service ui-console" | ||
| 9 | |||
| 10 | DEPS_api="" | ||
| 11 | DEPS_service="api" | ||
| 12 | DEPS_ui_console="api service" | ||
| 13 | |||
| 14 | RUNTIME_LIB_DIRS="pico jaxb gson" | ||
| 15 | |||
| 16 | MAIN_CLASS="market.guess.ui.console.App" | ||
| 17 | |||
| 18 | JAVA_HOME_DIR=$(java -XshowSettings:properties -version 2>&1 | sed -n 's/^ *java\.home = //p') | ||
| 19 | JAR="$JAVA_HOME_DIR/bin/jar" | ||
| 20 | [ -x "$JAR" ] || JAR="$JAVA_HOME_DIR/bin/jar.exe" | ||
| 21 | if [ ! -x "$JAR" ]; then | ||
| 22 | echo "Could not locate the 'jar' tool under $JAVA_HOME_DIR" >&2 | ||
| 23 | exit 1 | ||
| 24 | fi | ||
| 25 | |||
| 26 | is_shippable_jar() { | ||
| 27 | case "$1" in *-sources.jar | *-javadoc.jar) return 1 ;; *) return 0 ;; esac | ||
| 28 | } | ||
| 29 | LIBCP="" | ||
| 30 | for j in lib/*/*.jar; do | ||
| 31 | is_shippable_jar "$j" || continue | ||
| 32 | LIBCP="$LIBCP${LIBCP:+$SEP}$j" | ||
| 33 | done | ||
| 34 | |||
| 35 | TMP="${TMPDIR:-/tmp}/gm-build" | ||
| 36 | mkdir -p "$TMP" | ||
| 37 | |||
| 38 | deps_of() { | ||
| 39 | local v="DEPS_$(echo "$1" | tr '-' '_')" | ||
| 40 | eval "printf '%s' \"\${$v}\"" | ||
| 41 | } | ||
| 42 | |||
| 43 | module_cp() { | ||
| 44 | local cp="" | ||
| 45 | for d in "$@"; do cp="$cp${cp:+$SEP}build/classes/$d"; done | ||
| 46 | printf '%s' "$cp" | ||
| 47 | } | ||
diff --git a/tools/build.bat b/tools/build.bat deleted file mode 100644 index eb6b460..0000000 --- a/tools/build.bat +++ /dev/null | |||
| @@ -1,72 +0,0 @@ | |||
| 1 | @echo off | ||
| 2 | setlocal enabledelayedexpansion | ||
| 3 | |||
| 4 | echo === 1. Cleaning Workspace === | ||
| 5 | if exist bin rmdir /s /q bin | ||
| 6 | if exist stage rmdir /s /q stage | ||
| 7 | if exist dist rmdir /s /q dist | ||
| 8 | mkdir bin\classes | ||
| 9 | mkdir bin\test-classes | ||
| 10 | mkdir stage | ||
| 11 | mkdir dist | ||
| 12 | |||
| 13 | echo === 2. Compiling Production Code === | ||
| 14 | dir /s /b src\main\java\*.java > sources.txt | ||
| 15 | javac -d bin\classes -cp "lib/*" @sources.txt | ||
| 16 | if %errorlevel% neq 0 exit /b %errorlevel% | ||
| 17 | |||
| 18 | echo === 3. Compiling Test Code === | ||
| 19 | dir /s /b src\test\java\*.java > test-sources.txt | ||
| 20 | javac -d bin\test-classes -cp "bin\classes;lib/*" @test-sources.txt | ||
| 21 | if %errorlevel% neq 0 exit /b %errorlevel% | ||
| 22 | |||
| 23 | echo === 4. Running Automated Tests === | ||
| 24 | set "JUNIT_JAR=" | ||
| 25 | for %%f in (lib\junit-platform-console-standalone-*.jar) do set "JUNIT_JAR=%%f" | ||
| 26 | if not defined JUNIT_JAR (echo JUnit jar missing. & exit /b 1) | ||
| 27 | |||
| 28 | rem JUnit's --class-path does NOT expand lib\* -> build it explicitly | ||
| 29 | set "TEST_CP=bin\classes;bin\test-classes" | ||
| 30 | for %%j in (lib\*.jar) do set "TEST_CP=!TEST_CP!;%%j" | ||
| 31 | |||
| 32 | java -jar "!JUNIT_JAR!" execute -cp "!TEST_CP!" --scan-classpath | ||
| 33 | if %errorlevel% neq 0 ( | ||
| 34 | echo [X] Tests failed! Aborting build package. | ||
| 35 | exit /b %errorlevel% | ||
| 36 | ) | ||
| 37 | echo [OK] Tests passed successfully! | ||
| 38 | |||
| 39 | echo === 5. Extracting Dependencies for Fat JAR === | ||
| 40 | cd stage | ||
| 41 | for %%j in (..\lib\*.jar) do ( | ||
| 42 | echo %%~nxj | findstr /i "junit-platform-console-standalone" >nul | ||
| 43 | if errorlevel 1 ( | ||
| 44 | echo Extracting: %%~nxj | ||
| 45 | jar -xf "%%j" | ||
| 46 | ) | ||
| 47 | ) | ||
| 48 | |||
| 49 | rem Strip signatures + manifests (they break repacking) but KEEP | ||
| 50 | rem META-INF\services so ServiceLoader-based deps still work at runtime. | ||
| 51 | if exist META-INF ( | ||
| 52 | del /s /q META-INF\*.SF >nul 2>&1 | ||
| 53 | del /s /q META-INF\*.RSA >nul 2>&1 | ||
| 54 | del /s /q META-INF\*.DSA >nul 2>&1 | ||
| 55 | del /q META-INF\MANIFEST.MF >nul 2>&1 | ||
| 56 | ) | ||
| 57 | cd .. | ||
| 58 | |||
| 59 | echo === 6. Merging Assets into Fat JAR === | ||
| 60 | rem Merge ONLY production classes on top of the extracted deps | ||
| 61 | xcopy /e /q /y /i bin\classes\* stage\ >nul | ||
| 62 | rem Package the whole tree (deps + your classes), not just stage\classes | ||
| 63 | jar cfe dist/app.jar com.example.App -C stage . | ||
| 64 | if %errorlevel% neq 0 exit /b %errorlevel% | ||
| 65 | |||
| 66 | del sources.txt test-sources.txt >nul 2>&1 | ||
| 67 | rmdir /s /q stage | ||
| 68 | echo ====================================== | ||
| 69 | echo Fat JAR Created! Run it with: | ||
| 70 | echo java -jar dist/app.jar | ||
| 71 | echo ====================================== | ||
| 72 | pause | ||
diff --git a/tools/build.sh b/tools/build.sh index 6fdf7e1..72880ee 100755 --- a/tools/build.sh +++ b/tools/build.sh | |||
| @@ -1,61 +1,23 @@ | |||
| 1 | #!/bin/bash | 1 | #!/bin/bash |
| 2 | set -e | ||
| 3 | |||
| 4 | echo "=== 1. Cleaning Workspace ===" | ||
| 5 | rm -rf bin stage | ||
| 6 | mkdir -p bin/classes bin/test-classes stage dist # dist was never created | ||
| 7 | |||
| 8 | echo "=== 2. Compiling Production Code ===" | ||
| 9 | javac -d bin/classes -cp "lib/*" @<(find src/main/java -name '*.java') | ||
| 10 | |||
| 11 | echo "=== 3. Compiling Test Code ===" | ||
| 12 | # prod classes are in bin/classes, not bin | ||
| 13 | javac -d bin/test-classes -cp "bin/classes:lib/*" @<(find src/test/java -name '*.java') | ||
| 14 | 2 | ||
| 15 | echo "=== 4. Running Automated Tests ===" | ||
| 16 | JUNIT_JAR=$(find lib -maxdepth 1 -name 'junit-platform-console-standalone-*.jar' | head -n 1) | ||
| 17 | if [ -z "$JUNIT_JAR" ]; then | ||
| 18 | echo "JUnit jar missing." | ||
| 19 | exit 1 | ||
| 20 | fi | ||
| 21 | |||
| 22 | set +e | ||
| 23 | java -jar "$JUNIT_JAR" execute \ | ||
| 24 | -cp "bin/classes:bin/test-classes:lib/*" \ | ||
| 25 | --scan-classpath | ||
| 26 | TEST_STATUS=$? | ||
| 27 | set -e | 3 | set -e |
| 4 | cd "$(dirname "$0")/.." | ||
| 5 | . tools/_util.sh | ||
| 28 | 6 | ||
| 29 | if [ $TEST_STATUS -ne 0 ]; then | 7 | echo "=== Cleaning build/ ===" |
| 30 | echo "❌ Tests failed! Aborting build package." | 8 | rm -rf build |
| 31 | exit 1 | 9 | mkdir -p build/classes |
| 32 | fi | ||
| 33 | echo "✅ Tests passed successfully!" | ||
| 34 | |||
| 35 | echo "=== 5. Extracting Dependencies for Fat JAR ===" | ||
| 36 | cd stage | ||
| 37 | for jarfile in ../lib/*.jar; do | ||
| 38 | [ -f "$jarfile" ] || continue | ||
| 39 | case "$(basename "$jarfile")" in | ||
| 40 | junit-platform-console-standalone-*) continue ;; | ||
| 41 | esac | ||
| 42 | echo "Extracting: $(basename "$jarfile")" | ||
| 43 | jar -xf "$jarfile" | ||
| 44 | done | ||
| 45 | 10 | ||
| 46 | # Strip signatures + manifests (they break repacking), but KEEP | 11 | echo "=== Compiling production code (dependency order) ===" |
| 47 | # META-INF/services so ServiceLoader-based deps still work at runtime. | 12 | for m in $MODULES; do |
| 48 | find META-INF -type f \( -name '*.SF' -o -name '*.RSA' -o -name '*.DSA' -o -name 'MANIFEST.MF' \) -delete 2>/dev/null || true | 13 | deps=$(deps_of "$m") |
| 49 | cd .. | 14 | cp="$(module_cp $deps)" |
| 15 | cp="${cp:+$cp$SEP}$LIBCP" | ||
| 50 | 16 | ||
| 51 | echo "=== 6. Merging Assets into Fat JAR ===" | 17 | find "$m/src/main/java" -name '*.java' >"$TMP/src.txt" |
| 52 | # Merge YOUR production classes on top of the extracted deps | 18 | mkdir -p "build/classes/$m" |
| 53 | cp -r bin/classes/. stage/ | 19 | printf ' %-11s sees: %s\n' "$m" "${deps:-<nothing>}" |
| 54 | # Package the whole tree (deps + your classes), not just stage/classes | 20 | javac --release "$JAVA_RELEASE" -d "build/classes/$m" -cp "$cp" "@$TMP/src.txt" |
| 55 | jar cfe dist/app.jar com.example.App -C stage . | 21 | done |
| 56 | 22 | ||
| 57 | rm -rf stage | 23 | echo "=== Build OK ===" |
| 58 | echo "======================================" | ||
| 59 | echo "🎉 Fat JAR Created! Run it with:" | ||
| 60 | echo "java -jar dist/app.jar" | ||
| 61 | echo "======================================" | ||
diff --git a/tools/package.sh b/tools/package.sh new file mode 100644 index 0000000..5877444 --- /dev/null +++ b/tools/package.sh | |||
| @@ -0,0 +1,82 @@ | |||
| 1 | #!/bin/bash | ||
| 2 | |||
| 3 | set -e | ||
| 4 | cd "$(dirname "$0")/.." | ||
| 5 | . tools/_util.sh | ||
| 6 | |||
| 7 | bash tools/build.sh | ||
| 8 | |||
| 9 | DIST=build/dist | ||
| 10 | rm -rf "$DIST" | ||
| 11 | mkdir -p "$DIST" | ||
| 12 | |||
| 13 | echo "=== Staging runtime libs beside the jars ===" | ||
| 14 | for d in $RUNTIME_LIB_DIRS; do | ||
| 15 | mkdir -p "$DIST/lib/$d" | ||
| 16 | n=0 | ||
| 17 | for j in lib/"$d"/*.jar; do | ||
| 18 | is_shippable_jar "$j" || continue # never ship -sources / -javadoc | ||
| 19 | cp "$j" "$DIST/lib/$d/" | ||
| 20 | n=$((n + 1)) | ||
| 21 | done | ||
| 22 | echo " lib/$d/ ($n jars)" | ||
| 23 | done | ||
| 24 | |||
| 25 | echo "=== Generating console manifest ===" | ||
| 26 | # Generated from what is actually staged, never hand-maintained. Four silent | ||
| 27 | # failure modes: wildcards match nothing; a missing trailing newline makes jar | ||
| 28 | # drop the line; paths are relative to this jar; and java -jar ignores -cp, so | ||
| 29 | # this line is the entire classpath at runtime. | ||
| 30 | MF="$TMP/console-manifest.txt" | ||
| 31 | CPLINE="" | ||
| 32 | for m in $MODULES; do | ||
| 33 | [ "$m" = ui-console ] && continue | ||
| 34 | CPLINE="$CPLINE${CPLINE:+ }$m.jar" | ||
| 35 | done | ||
| 36 | for d in $RUNTIME_LIB_DIRS; do | ||
| 37 | for j in "$DIST"/lib/"$d"/*.jar; do | ||
| 38 | [ -f "$j" ] || continue | ||
| 39 | CPLINE="$CPLINE lib/$d/$(basename "$j")" | ||
| 40 | done | ||
| 41 | done | ||
| 42 | printf 'Class-Path: %s\n\n' "$CPLINE" >"$MF" | ||
| 43 | echo " $CPLINE" | ||
| 44 | |||
| 45 | echo "=== Packaging ===" | ||
| 46 | for m in $MODULES; do | ||
| 47 | if [ "$m" = ui-console ]; then | ||
| 48 | "$JAR" --create --file "$DIST/$m.jar" \ | ||
| 49 | --main-class "$MAIN_CLASS" --manifest "$MF" -C "build/classes/$m" . | ||
| 50 | else | ||
| 51 | "$JAR" --create --file "$DIST/$m.jar" -C "build/classes/$m" . | ||
| 52 | fi | ||
| 53 | echo " $DIST/$m.jar" | ||
| 54 | done | ||
| 55 | |||
| 56 | cat >"$DIST/run.bat" <<'EOF' | ||
| 57 | @echo off | ||
| 58 | cd /d "%~dp0" | ||
| 59 | java -jar "%~dp0ui-console.jar" | ||
| 60 | if errorlevel 1 pause | ||
| 61 | EOF | ||
| 62 | |||
| 63 | echo "=== Manifest as it actually landed in the jar ===" | ||
| 64 | # Cheapest check that prevents a Level-0: if Main-Class or Class-Path didn't | ||
| 65 | # survive, it surfaces here instead of on the grader's machine. | ||
| 66 | rm -rf "$TMP/mf" && mkdir -p "$TMP/mf" | ||
| 67 | (cd "$TMP/mf" && "$JAR" -xf "$OLDPWD/$DIST/ui-console.jar" META-INF/MANIFEST.MF) | ||
| 68 | sed 's/^/ /' "$TMP/mf/META-INF/MANIFEST.MF" | ||
| 69 | |||
| 70 | grep -q '^Main-Class:' "$TMP/mf/META-INF/MANIFEST.MF" || { | ||
| 71 | echo "*** Main-Class missing ***" | ||
| 72 | exit 1 | ||
| 73 | } | ||
| 74 | grep -q 'Class-Path:' "$TMP/mf/META-INF/MANIFEST.MF" || { | ||
| 75 | echo "*** Class-Path missing ***" | ||
| 76 | exit 1 | ||
| 77 | } | ||
| 78 | |||
| 79 | echo "======================================" | ||
| 80 | echo "Self-contained output in $DIST" | ||
| 81 | echo " cd $DIST && java -jar ui-console.jar" | ||
| 82 | echo "======================================" | ||
diff --git a/tools/run.bat b/tools/run.bat deleted file mode 100644 index 8480652..0000000 --- a/tools/run.bat +++ /dev/null | |||
| @@ -1,16 +0,0 @@ | |||
| 1 | @echo off | ||
| 2 | :: clean old binaries | ||
| 3 | rmdir /s /q bin | ||
| 4 | mkdir bin | ||
| 5 | |||
| 6 | :: compile source files and include libraries | ||
| 7 | javac -d "bin/classes" -cp "lib/*" src/main/java/com/example/App.java | ||
| 8 | |||
| 9 | :: run the application if compilation succeeded | ||
| 10 | if %errorlevel% equ 0 ( | ||
| 11 | echo --- Running Application --- | ||
| 12 | java -cp "bin/classes;lib/*" com.example.App | ||
| 13 | ) else ( | ||
| 14 | echo Compilation failed. | ||
| 15 | ) | ||
| 16 | pause | ||
diff --git a/tools/run.sh b/tools/run.sh deleted file mode 100644 index 55f1094..0000000 --- a/tools/run.sh +++ /dev/null | |||
| @@ -1,12 +0,0 @@ | |||
| 1 | #!/bin/bash | ||
| 2 | |||
| 3 | # clean up | ||
| 4 | rm -rf bin && mkdir bin | ||
| 5 | |||
| 6 | # run the app if compilation succeeded | ||
| 7 | if javac -d "bin/classes" -cp "lib/*" src/main/java/com/example/App.java; then | ||
| 8 | echo "--- Running Application ---" | ||
| 9 | java -cp "bin/classes:lib/*" com.example.App | ||
| 10 | else | ||
| 11 | echo "Compilation failed." | ||
| 12 | fi | ||
diff --git a/tools/test.sh b/tools/test.sh new file mode 100644 index 0000000..4eaeb82 --- /dev/null +++ b/tools/test.sh | |||
| @@ -0,0 +1,47 @@ | |||
| 1 | #!/bin/bash | ||
| 2 | |||
| 3 | set -e | ||
| 4 | cd "$(dirname "$0")/.." | ||
| 5 | . tools/_util.sh | ||
| 6 | |||
| 7 | [ -d build/classes ] || { | ||
| 8 | echo "No build/classes - run tools/build.sh first" | ||
| 9 | exit 1 | ||
| 10 | } | ||
| 11 | |||
| 12 | JUNIT_JAR=$(ls lib/junit/junit-platform-console-standalone-*.jar 2>/dev/null | head -n 1) | ||
| 13 | [ -n "$JUNIT_JAR" ] || { | ||
| 14 | echo "JUnit standalone jar missing from lib/junit/" | ||
| 15 | exit 1 | ||
| 16 | } | ||
| 17 | |||
| 18 | PROD_CP="$(module_cp $MODULES)" | ||
| 19 | |||
| 20 | echo "=== Compiling test sources ===" | ||
| 21 | : >"$TMP/tests.txt" | ||
| 22 | for m in $MODULES; do | ||
| 23 | [ -d "$m/src/test/java" ] && find "$m/src/test/java" -name '*.java' >>"$TMP/tests.txt" | ||
| 24 | done | ||
| 25 | if [ ! -s "$TMP/tests.txt" ]; then | ||
| 26 | echo " (no test sources)" | ||
| 27 | exit 0 | ||
| 28 | fi | ||
| 29 | |||
| 30 | mkdir -p build/test-classes | ||
| 31 | javac --release "$JAVA_RELEASE" -d build/test-classes \ | ||
| 32 | -cp "$PROD_CP$SEP$JUNIT_JAR$SEP$LIBCP" "@$TMP/tests.txt" | ||
| 33 | |||
| 34 | echo "=== Running tests ===" | ||
| 35 | |||
| 36 | TEST_CP="$PROD_CP${SEP}build/test-classes" | ||
| 37 | for j in lib/*/*.jar; do | ||
| 38 | case "$j" in *junit-platform-console-standalone*) continue ;; esac | ||
| 39 | TEST_CP="$TEST_CP$SEP$j" | ||
| 40 | done | ||
| 41 | |||
| 42 | AGENT_JAR=$(ls lib/mockito/byte-buddy-agent-*.jar 2>/dev/null | head -n 1) | ||
| 43 | AGENT_ARG="" | ||
| 44 | [ -n "$AGENT_JAR" ] && AGENT_ARG="-javaagent:$AGENT_JAR" | ||
| 45 | |||
| 46 | java $AGENT_ARG -jar "$JUNIT_JAR" execute \ | ||
| 47 | --class-path "$TEST_CP" --scan-class-path --details=summary | ||
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 5ee5aaf..047bad1 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 | |||
| @@ -9,6 +9,7 @@ import market.guess.service.catalog.infrastructure.MarketContext; | |||
| 9 | import market.guess.service.catalog.infrastructure.mapper.v1.EventMapperV1; | 9 | import market.guess.service.catalog.infrastructure.mapper.v1.EventMapperV1; |
| 10 | import market.guess.service.catalog.infrastructure.provider.Loader; | 10 | import market.guess.service.catalog.infrastructure.provider.Loader; |
| 11 | import market.guess.service.catalog.infrastructure.provider.v1.XMLLoaderV1; | 11 | import market.guess.service.catalog.infrastructure.provider.v1.XMLLoaderV1; |
| 12 | import market.guess.service.catalog.infrastructure.provider.v1.XMLValidatorV1; | ||
| 12 | import market.guess.service.catalog.infrastructure.repository.EventRepository; | 13 | import market.guess.service.catalog.infrastructure.repository.EventRepository; |
| 13 | import market.guess.service.catalog.infrastructure.repository.InMemoryEventRepository; | 14 | import market.guess.service.catalog.infrastructure.repository.InMemoryEventRepository; |
| 14 | import market.guess.service.catalog.infrastructure.repository.InMemoryUserRepository; | 15 | import market.guess.service.catalog.infrastructure.repository.InMemoryUserRepository; |
| @@ -22,12 +23,12 @@ import market.guess.service.risk.LocalRiskEngine; | |||
| 22 | import market.guess.service.risk.RiskEngine; | 23 | import market.guess.service.risk.RiskEngine; |
| 23 | import market.guess.service.settlement.LocalSettlementContext; | 24 | import market.guess.service.settlement.LocalSettlementContext; |
| 24 | import market.guess.service.settlement.SettlementContext; | 25 | import market.guess.service.settlement.SettlementContext; |
| 25 | import market.guess.ui.console.command.BuyMenuCommand; | ||
| 26 | import market.guess.ui.console.command.EventDetailsMenuCommand; | 26 | import market.guess.ui.console.command.EventDetailsMenuCommand; |
| 27 | import market.guess.ui.console.command.ExitMenuCommand; | 27 | import market.guess.ui.console.command.ExitMenuCommand; |
| 28 | import market.guess.ui.console.command.ListEventsMenuCommand; | 28 | import market.guess.ui.console.command.ListEventsMenuCommand; |
| 29 | import market.guess.ui.console.command.LoadFileMenuCommand; | 29 | import market.guess.ui.console.command.LoadFileMenuCommand; |
| 30 | import market.guess.ui.console.command.LoadMarketStateMenuCommand; | 30 | import market.guess.ui.console.command.LoadMarketStateMenuCommand; |
| 31 | import market.guess.ui.console.command.PlaceOrderMenuCommand; | ||
| 31 | import market.guess.ui.console.command.SaveMarketStateMenuCommand; | 32 | import market.guess.ui.console.command.SaveMarketStateMenuCommand; |
| 32 | import market.guess.ui.console.command.SettleEventMenuCommand; | 33 | import market.guess.ui.console.command.SettleEventMenuCommand; |
| 33 | import market.guess.ui.console.io.InputProcessor; | 34 | import market.guess.ui.console.io.InputProcessor; |
| @@ -43,6 +44,7 @@ public class App { | |||
| 43 | pico.addComponent(MarketContext.class); | 44 | pico.addComponent(MarketContext.class); |
| 44 | pico.addComponent(EventMapperV1.class); | 45 | pico.addComponent(EventMapperV1.class); |
| 45 | pico.addComponent(Loader.class, XMLLoaderV1.class); | 46 | pico.addComponent(Loader.class, XMLLoaderV1.class); |
| 47 | pico.addComponent(XMLValidatorV1.class); | ||
| 46 | pico.addComponent(GuessMarketContext.class, LocalGuessMarketContext.class); | 48 | pico.addComponent(GuessMarketContext.class, LocalGuessMarketContext.class); |
| 47 | pico.addComponent(CatalogContext.class, LocalCatalogContext.class); | 49 | pico.addComponent(CatalogContext.class, LocalCatalogContext.class); |
| 48 | pico.addComponent(LedgerContext.class); | 50 | pico.addComponent(LedgerContext.class); |
| @@ -56,7 +58,7 @@ public class App { | |||
| 56 | pico.addComponent(LoadFileMenuCommand.class); | 58 | pico.addComponent(LoadFileMenuCommand.class); |
| 57 | pico.addComponent(ListEventsMenuCommand.class); | 59 | pico.addComponent(ListEventsMenuCommand.class); |
| 58 | pico.addComponent(ExitMenuCommand.class); | 60 | pico.addComponent(ExitMenuCommand.class); |
| 59 | pico.addComponent(BuyMenuCommand.class); | 61 | pico.addComponent(PlaceOrderMenuCommand.class); |
| 60 | pico.addComponent(SettleEventMenuCommand.class); | 62 | pico.addComponent(SettleEventMenuCommand.class); |
| 61 | pico.addComponent(EventDetailsMenuCommand.class); | 63 | pico.addComponent(EventDetailsMenuCommand.class); |
| 62 | pico.addComponent(LoadMarketStateMenuCommand.class); | 64 | pico.addComponent(LoadMarketStateMenuCommand.class); |
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 024f1f7..11ec478 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 | |||
| @@ -35,6 +35,6 @@ public final class LoadFileMenuCommand implements MenuCommand { | |||
| 35 | return; | 35 | return; |
| 36 | } | 36 | } |
| 37 | 37 | ||
| 38 | io.println("The file was not loaded, %s ".formatted(result.getMessage())); | 38 | io.println("The file was not loaded, %s ".formatted(result.getDetails())); |
| 39 | } | 39 | } |
| 40 | } | 40 | } |
diff --git a/ui-console/src/main/java/market/guess/ui/console/command/BuyMenuCommand.java b/ui-console/src/main/java/market/guess/ui/console/command/PlaceOrderMenuCommand.java index bee2351..6c2e674 100644 --- a/ui-console/src/main/java/market/guess/ui/console/command/BuyMenuCommand.java +++ b/ui-console/src/main/java/market/guess/ui/console/command/PlaceOrderMenuCommand.java | |||
| @@ -6,12 +6,13 @@ import market.guess.api.GuessMarketContext; | |||
| 6 | import market.guess.model.event.EventStatus; | 6 | import market.guess.model.event.EventStatus; |
| 7 | import market.guess.ui.console.io.InputProcessor; | 7 | import market.guess.ui.console.io.InputProcessor; |
| 8 | 8 | ||
| 9 | public final class BuyMenuCommand implements MenuCommand { | 9 | public final class PlaceOrderMenuCommand implements MenuCommand { |
| 10 | private final GuessMarketContext context; | 10 | private final GuessMarketContext context; |
| 11 | private final CatalogContext catalog; | 11 | private final CatalogContext catalog; |
| 12 | private final InputProcessor io; | 12 | private final InputProcessor io; |
| 13 | 13 | ||
| 14 | public BuyMenuCommand(GuessMarketContext context, CatalogContext catalog, InputProcessor io) { | 14 | public PlaceOrderMenuCommand( |
| 15 | GuessMarketContext context, CatalogContext catalog, InputProcessor io) { | ||
| 15 | super(); | 16 | super(); |
| 16 | this.context = context; | 17 | this.context = context; |
| 17 | this.catalog = catalog; | 18 | this.catalog = catalog; |
| @@ -25,7 +26,7 @@ public final class BuyMenuCommand implements MenuCommand { | |||
| 25 | 26 | ||
| 26 | @Override | 27 | @Override |
| 27 | public String getName() { | 28 | public String getName() { |
| 28 | return "Buy share(s) in an event"; | 29 | return "Create a but order on an event"; |
| 29 | } | 30 | } |
| 30 | 31 | ||
| 31 | @Override | 32 | @Override |