Use reflection for JSON parsing and fix question state issue
This commit is contained in:
@@ -22,11 +22,17 @@ public class ShopQuestion extends Question {
|
|||||||
private int selectedCategoryId = -1;
|
private int selectedCategoryId = -1;
|
||||||
|
|
||||||
public ShopQuestion(long responderId, String shopName, ShopService shopService) throws NoSuchPlayerException {
|
public ShopQuestion(long responderId, String shopName, ShopService shopService) throws NoSuchPlayerException {
|
||||||
|
this(responderId, shopName, shopService, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShopQuestion(long responderId, String shopName, ShopService shopService, int selectedCategoryId) throws NoSuchPlayerException {
|
||||||
super(Players.getInstance().getPlayer(responderId), shopName, null, SHOP_QUESTION_ID, responderId);
|
super(Players.getInstance().getPlayer(responderId), shopName, null, SHOP_QUESTION_ID, responderId);
|
||||||
this.shopService = shopService;
|
this.shopService = shopService;
|
||||||
List<ShopCategory> categories = shopService.getCategories();
|
List<ShopCategory> categories = shopService.getCategories();
|
||||||
if (!categories.isEmpty()) {
|
if (selectedCategoryId >= 0 && categories.stream().anyMatch(c -> c.getId() == selectedCategoryId)) {
|
||||||
selectedCategoryId = categories.get(0).getId();
|
this.selectedCategoryId = selectedCategoryId;
|
||||||
|
} else if (!categories.isEmpty()) {
|
||||||
|
this.selectedCategoryId = categories.get(0).getId();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,12 +41,13 @@ public class ShopQuestion extends Question {
|
|||||||
logger.log(Level.INFO, "ShopQuestion.answer: received properties {0}", properties);
|
logger.log(Level.INFO, "ShopQuestion.answer: received properties {0}", properties);
|
||||||
List<ShopCategory> categories = shopService.getCategories();
|
List<ShopCategory> categories = shopService.getCategories();
|
||||||
String categoryIndex = properties.getProperty("category");
|
String categoryIndex = properties.getProperty("category");
|
||||||
|
int newCategoryId = selectedCategoryId;
|
||||||
if (categoryIndex != null && !categories.isEmpty()) {
|
if (categoryIndex != null && !categories.isEmpty()) {
|
||||||
try {
|
try {
|
||||||
int idx = Integer.parseInt(categoryIndex);
|
int idx = Integer.parseInt(categoryIndex);
|
||||||
if (idx >= 0 && idx < categories.size()) {
|
if (idx >= 0 && idx < categories.size()) {
|
||||||
selectedCategoryId = categories.get(idx).getId();
|
newCategoryId = categories.get(idx).getId();
|
||||||
logger.log(Level.INFO, "ShopQuestion.answer: category set to index {0}, id {1}", new Object[] { idx, selectedCategoryId });
|
logger.log(Level.INFO, "ShopQuestion.answer: category set to index {0}, id {1}", new Object[] { idx, newCategoryId });
|
||||||
}
|
}
|
||||||
} catch (NumberFormatException ignored) {
|
} catch (NumberFormatException ignored) {
|
||||||
logger.log(Level.INFO, "ShopQuestion.answer: invalid category index {0}", categoryIndex);
|
logger.log(Level.INFO, "ShopQuestion.answer: invalid category index {0}", categoryIndex);
|
||||||
@@ -59,13 +66,13 @@ public class ShopQuestion extends Question {
|
|||||||
|
|
||||||
if (properties.containsKey("refresh")) {
|
if (properties.containsKey("refresh")) {
|
||||||
logger.log(Level.INFO, "ShopQuestion.answer: refresh requested");
|
logger.log(Level.INFO, "ShopQuestion.answer: refresh requested");
|
||||||
sendQuestion();
|
createNewQuestion(newCategoryId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buyKey == null) { // category change or refresh
|
if (buyKey == null) { // category change or refresh
|
||||||
logger.log(Level.INFO, "ShopQuestion.answer: no buy key present, refreshing UI");
|
logger.log(Level.INFO, "ShopQuestion.answer: no buy key present, refreshing UI");
|
||||||
sendQuestion();
|
createNewQuestion(newCategoryId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,12 +87,23 @@ public class ShopQuestion extends Question {
|
|||||||
logger.log(Level.INFO, "ShopQuestion.answer: purchase failed itemId={0}, message={1}", new Object[] { itemId, result.getMessage() });
|
logger.log(Level.INFO, "ShopQuestion.answer: purchase failed itemId={0}, message={1}", new Object[] { itemId, result.getMessage() });
|
||||||
getResponder().getCommunicator().sendAlertServerMessage(result.getMessage());
|
getResponder().getCommunicator().sendAlertServerMessage(result.getMessage());
|
||||||
}
|
}
|
||||||
|
// Reopen the shop after purchase
|
||||||
|
createNewQuestion(newCategoryId);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
logger.log(Level.INFO, "ShopQuestion.answer: invalid buy key {0}", buyKey);
|
logger.log(Level.INFO, "ShopQuestion.answer: invalid buy key {0}", buyKey);
|
||||||
getResponder().getCommunicator().sendNormalServerMessage("Invalid item selected.");
|
getResponder().getCommunicator().sendNormalServerMessage("Invalid item selected.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void createNewQuestion(int categoryId) {
|
||||||
|
try {
|
||||||
|
ShopQuestion newQuestion = new ShopQuestion(getResponder().getWurmId(), title, shopService, categoryId);
|
||||||
|
newQuestion.sendQuestion();
|
||||||
|
} catch (NoSuchPlayerException e) {
|
||||||
|
logger.log(Level.WARNING, "Failed to create new ShopQuestion", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendQuestion() {
|
public void sendQuestion() {
|
||||||
List<ShopCategory> categories = shopService.getCategories();
|
List<ShopCategory> categories = shopService.getCategories();
|
||||||
|
|||||||
@@ -4,18 +4,19 @@ import com.wurmonline.server.support.JSONArray;
|
|||||||
import com.wurmonline.server.support.JSONObject;
|
import com.wurmonline.server.support.JSONObject;
|
||||||
import com.wurmonline.server.support.JSONTokener;
|
import com.wurmonline.server.support.JSONTokener;
|
||||||
import mod.treestar.shopmod.currencies.ShopCurrency;
|
import mod.treestar.shopmod.currencies.ShopCurrency;
|
||||||
import mod.treestar.shopmod.currencies.WurmBankCurrency;
|
|
||||||
import mod.treestar.shopmod.datamodels.ShopItem;
|
import mod.treestar.shopmod.datamodels.ShopItem;
|
||||||
import mod.treestar.shopmod.purchasehandlers.ShopItemPurchaseEffect;
|
import mod.treestar.shopmod.purchasehandlers.ShopItemPurchaseEffect;
|
||||||
import mod.treestar.shopmod.purchasehandlers.ShopWurmItemPurchaseEffect;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Map;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
@@ -27,10 +28,17 @@ import java.util.logging.Logger;
|
|||||||
public class JsonShopItemProvider implements ShopItemProvider {
|
public class JsonShopItemProvider implements ShopItemProvider {
|
||||||
private static final Logger logger = Logger.getLogger(JsonShopItemProvider.class.getName());
|
private static final Logger logger = Logger.getLogger(JsonShopItemProvider.class.getName());
|
||||||
|
|
||||||
|
private static final String CURRENCIES_PACKAGE = "mod.treestar.shopmod.currencies.";
|
||||||
|
private static final String HANDLERS_PACKAGE = "mod.treestar.shopmod.purchasehandlers.";
|
||||||
|
|
||||||
private final String itemJsonPath;
|
private final String itemJsonPath;
|
||||||
private long lastModified = -1L;
|
private long lastModified = -1L;
|
||||||
private List<ShopItem> cachedItems = Collections.emptyList();
|
private List<ShopItem> cachedItems = Collections.emptyList();
|
||||||
|
|
||||||
|
// Cache for class lookups to avoid repeated reflection
|
||||||
|
private final Map<String, Class<?>> currencyClassCache = new HashMap<>();
|
||||||
|
private final Map<String, Class<?>> handlerClassCache = new HashMap<>();
|
||||||
|
|
||||||
public JsonShopItemProvider(String itemJsonPath) {
|
public JsonShopItemProvider(String itemJsonPath) {
|
||||||
this.itemJsonPath = itemJsonPath;
|
this.itemJsonPath = itemJsonPath;
|
||||||
}
|
}
|
||||||
@@ -97,14 +105,29 @@ public class JsonShopItemProvider implements ShopItemProvider {
|
|||||||
logger.log(Level.WARNING, "Item at index " + index + " missing currency; item will be unusable");
|
logger.log(Level.WARNING, "Item at index " + index + " missing currency; item will be unusable");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
String type = currencyObj.optString("type", "").toLowerCase(Locale.ROOT);
|
String type = currencyObj.optString("type", "");
|
||||||
switch (type) {
|
if (type.isEmpty()) {
|
||||||
case "wurmbankcurrency":
|
logger.log(Level.WARNING, "Item at index " + index + " missing currency type");
|
||||||
long ironAmount = currencyObj.optLong("ironAmount", currencyObj.optLong("priceIron", 0L));
|
return null;
|
||||||
return new WurmBankCurrency(ironAmount);
|
}
|
||||||
default:
|
try {
|
||||||
logger.log(Level.WARNING, "Unknown currency type '" + type + "' for item at index " + index);
|
Class<?> clazz = currencyClassCache.computeIfAbsent(type, t -> {
|
||||||
|
try {
|
||||||
|
return Class.forName(CURRENCIES_PACKAGE + t);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (clazz == null || !ShopCurrency.class.isAssignableFrom(clazz)) {
|
||||||
|
logger.log(Level.WARNING, "Unknown or invalid currency type '" + type + "' for item at index " + index);
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
Object instance = clazz.getDeclaredConstructor().newInstance();
|
||||||
|
applyJsonFields(instance, currencyObj, index);
|
||||||
|
return (ShopCurrency) instance;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.log(Level.WARNING, "Failed to instantiate currency '" + type + "' for item at index " + index, e);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,23 +136,95 @@ public class JsonShopItemProvider implements ShopItemProvider {
|
|||||||
logger.log(Level.WARNING, "Item at index " + index + " missing handler; item will be unusable");
|
logger.log(Level.WARNING, "Item at index " + index + " missing handler; item will be unusable");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
String type = handlerObj.optString("type", "").toLowerCase(Locale.ROOT);
|
String type = handlerObj.optString("type", "");
|
||||||
switch (type) {
|
if (type.isEmpty()) {
|
||||||
case "shopwurmitempurchaseeffect":
|
logger.log(Level.WARNING, "Item at index " + index + " missing handler type");
|
||||||
int templateId = handlerObj.optInt("itemTemplateId", -1);
|
return null;
|
||||||
if (templateId <= 0) {
|
}
|
||||||
logger.log(Level.WARNING, "Handler for item at index " + index + " missing valid itemTemplateId");
|
try {
|
||||||
|
Class<?> clazz = handlerClassCache.computeIfAbsent(type, t -> {
|
||||||
|
try {
|
||||||
|
return Class.forName(HANDLERS_PACKAGE + t);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (clazz == null || !ShopItemPurchaseEffect.class.isAssignableFrom(clazz)) {
|
||||||
|
logger.log(Level.WARNING, "Unknown or invalid handler type '" + type + "' for item at index " + index);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
ShopWurmItemPurchaseEffect effect = new ShopWurmItemPurchaseEffect();
|
Object instance = clazz.getDeclaredConstructor().newInstance();
|
||||||
effect.setItemTemplateId(templateId);
|
applyJsonFields(instance, handlerObj, index);
|
||||||
effect.setQl((float) handlerObj.optDouble("ql", 50));
|
return (ShopItemPurchaseEffect) instance;
|
||||||
effect.setRandomQl(handlerObj.optBoolean("randomQl", false));
|
} catch (Exception e) {
|
||||||
effect.setRarity((byte) handlerObj.optInt("rarity", 0));
|
logger.log(Level.WARNING, "Failed to instantiate handler '" + type + "' for item at index " + index, e);
|
||||||
return effect;
|
|
||||||
default:
|
|
||||||
logger.log(Level.WARNING, "Unknown handler type '" + type + "' for item at index " + index);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private void applyJsonFields(Object instance, JSONObject json, int index) {
|
||||||
|
Class<?> clazz = instance.getClass();
|
||||||
|
Iterator<String> keys = json.keys();
|
||||||
|
while (keys.hasNext()) {
|
||||||
|
String key = keys.next();
|
||||||
|
if ("type".equals(key)) {
|
||||||
|
continue; // skip the type field, it's used for class lookup
|
||||||
|
}
|
||||||
|
String setterName = "set" + Character.toUpperCase(key.charAt(0)) + key.substring(1);
|
||||||
|
Object value = json.get(key);
|
||||||
|
Method setter = findSetter(clazz, setterName);
|
||||||
|
if (setter == null) {
|
||||||
|
logger.log(Level.FINE, "No setter '" + setterName + "' found on " + clazz.getSimpleName() + " for item at index " + index);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Class<?> paramType = setter.getParameterTypes()[0];
|
||||||
|
Object convertedValue = convertValue(value, paramType);
|
||||||
|
if (convertedValue != null) {
|
||||||
|
setter.invoke(instance, convertedValue);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.log(Level.WARNING, "Failed to set field '" + key + "' on " + clazz.getSimpleName() + " for item at index " + index, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Method findSetter(Class<?> clazz, String setterName) {
|
||||||
|
for (Method method : clazz.getMethods()) {
|
||||||
|
if (method.getName().equals(setterName) && method.getParameterCount() == 1) {
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object convertValue(Object value, Class<?> targetType) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Handle primitive types and their wrappers
|
||||||
|
if (targetType == int.class || targetType == Integer.class) {
|
||||||
|
return ((Number) value).intValue();
|
||||||
|
} else if (targetType == long.class || targetType == Long.class) {
|
||||||
|
return ((Number) value).longValue();
|
||||||
|
} else if (targetType == float.class || targetType == Float.class) {
|
||||||
|
return ((Number) value).floatValue();
|
||||||
|
} else if (targetType == double.class || targetType == Double.class) {
|
||||||
|
return ((Number) value).doubleValue();
|
||||||
|
} else if (targetType == byte.class || targetType == Byte.class) {
|
||||||
|
return ((Number) value).byteValue();
|
||||||
|
} else if (targetType == short.class || targetType == Short.class) {
|
||||||
|
return ((Number) value).shortValue();
|
||||||
|
} else if (targetType == boolean.class || targetType == Boolean.class) {
|
||||||
|
return value;
|
||||||
|
} else if (targetType == String.class) {
|
||||||
|
return value.toString();
|
||||||
|
}
|
||||||
|
// If no conversion needed or type matches
|
||||||
|
if (targetType.isInstance(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user