diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e1f5874..6d75363 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,5 +1,5 @@ env: - version: 0.1.3 # 你的版本名稱 + version: 0.1.3.1 # 你的版本名稱 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} plugin_name: ELDependenci-MVC-plugin diff --git a/ELDependenci-MVC-plugin/pom.xml b/ELDependenci-MVC-plugin/pom.xml index 6b0bc8c..9fb77ac 100644 --- a/ELDependenci-MVC-plugin/pom.xml +++ b/ELDependenci-MVC-plugin/pom.xml @@ -10,7 +10,7 @@ 4.0.0 ELDependenci-MVC-plugin - ${project.parent.version} + ${project.parent.version}.1 diff --git a/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/ELDGUI.java b/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/ELDGUI.java index 5716a91..ce7d79c 100644 --- a/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/ELDGUI.java +++ b/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/ELDGUI.java @@ -15,7 +15,6 @@ import com.ericlam.mc.eldgui.view.BukkitRedirectView; import com.ericlam.mc.eldgui.view.BukkitView; import com.ericlam.mc.eldgui.view.LoadingView; -import com.ericlam.mc.eldgui.view.View; import com.google.inject.Injector; import com.google.inject.TypeLiteral; import org.bukkit.Bukkit; @@ -28,10 +27,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import java.util.function.Function; @@ -39,6 +35,7 @@ public final class ELDGUI { + private static final Map, Method[]> declaredMethodMap = new ConcurrentHashMap<>(); private static final Logger LOGGER = LoggerFactory.getLogger(ELDGUI.class); @@ -49,7 +46,6 @@ public final class ELDGUI { private final LifeCycleManager lifeCycleManager; private final Class controllerCls; - private final Object controller; private final Injector injector; private final UISession session; private final Player owner; @@ -61,6 +57,7 @@ public final class ELDGUI { private final Consumer onDestroy; private final ViewJumper goTo; private final BukkitView loadingView; + private final Method[] declaredMethods; private ELDGView currentView; @@ -79,7 +76,6 @@ public ELDGUI( ) { this.session = session; - this.controller = controller; this.injector = injector; this.owner = owner; this.onDestroy = onDestroy; @@ -91,16 +87,22 @@ public ELDGUI( methodParseManager = managerFactory.buildParseManager(this::initMethodParseManager); returnTypeManager = managerFactory.buildReturnTypeManager(this::initReturnTypeManager); this.lifeCycleManager = new LifeCycleManager(controller, methodParseManager); + this.controllerCls = controller.getClass(); + + if (declaredMethodMap.containsKey(controllerCls)) { + this.declaredMethods = declaredMethodMap.get(controllerCls); + } else { + this.declaredMethods = controllerCls.getDeclaredMethods(); + declaredMethodMap.put(controllerCls, declaredMethods); + } var customQualifier = eldgmvcInstallation.getQualifierMap(); - this.eventHandlerMap.put(InventoryClickEvent.class, new ELDGClickEventHandler(controller, methodParseManager, returnTypeManager, customQualifier)); - this.eventHandlerMap.put(InventoryDragEvent.class, new ELDGDragEventHandler(controller, methodParseManager, returnTypeManager, customQualifier)); + this.eventHandlerMap.put(InventoryClickEvent.class, new ELDGClickEventHandler(controller, methodParseManager, returnTypeManager, customQualifier, declaredMethods)); + this.eventHandlerMap.put(InventoryDragEvent.class, new ELDGDragEventHandler(controller, methodParseManager, returnTypeManager, customQualifier, declaredMethods)); this.itemGetterMap.put(InventoryClickEvent.class.getSimpleName(), e -> ((InventoryClickEvent) e).getCurrentItem()); this.itemGetterMap.put(InventoryDragEvent.class.getSimpleName(), e -> ((InventoryDragEvent) e).getOldCursor()); - this.controllerCls = controller.getClass(); - this.lifeCycleManager.onLifeCycle(PostConstruct.class); Optional> loadingViewOpt = Optional.ofNullable(this.controllerCls.getAnnotation(AsyncLoadingView.class)).map(AsyncLoadingView::value); @@ -140,7 +142,7 @@ private synchronized void jumpToController(BukkitRedirectView redirectView) { public void initIndexView(Object controller) { LOGGER.debug("initializing index view"); // debug try { - Optional indexMethod = Arrays.stream(controllerCls.getDeclaredMethods()).filter(m -> m.getName().equalsIgnoreCase("index")).findAny(); + Optional indexMethod = Arrays.stream(declaredMethods).filter(m -> m.getName().equalsIgnoreCase("index")).findAny(); if (indexMethod.isEmpty()) throw new IllegalStateException("cannot find index method from " + controllerCls); Method index = indexMethod.get(); @@ -203,7 +205,7 @@ private void initMethodParseManager(MethodParseManager parser) { FromPattern pattern = (FromPattern) Arrays.stream(annotations).filter(a -> a.annotationType() == FromPattern.class).findAny().orElseThrow(() -> new IllegalStateException("cannot find @FromPattern in List parameters")); if (t instanceof ParameterizedType) { var parat = (ParameterizedType) t; - if (parat.getActualTypeArguments()[0] == ItemStack.class && parat.getRawType() == List.class){ + if (parat.getActualTypeArguments()[0] == ItemStack.class && parat.getRawType() == List.class) { return this.currentView.getEldgContext().getItems(pattern.value()); } } @@ -221,20 +223,10 @@ private void initMethodParseManager(MethodParseManager parser) { parser.registerParser((t, annos) -> Arrays.stream(annos).anyMatch(a -> a.annotationType() == ModelAttribute.class), (annotations, type, event) -> { ModelAttribute modelAttribute = (ModelAttribute) Arrays.stream(annotations).filter(a -> a.annotationType() == ModelAttribute.class).findAny().orElseThrow(() -> new IllegalStateException("cannot find @ModelAttribute")); - var context = this.currentView.getEldgContext(); if (type instanceof ParameterizedType) throw new IllegalStateException("model attribute cannot be generic type"); var model = ((Class) type); - - Map fieldMap = context.getItems(modelAttribute.value()) - .stream() - .filter(item -> context.getAttribute(item, AttributeController.FIELD_TAG) != null) - .collect(Collectors - .toMap( - item -> context.getAttribute(item, AttributeController.FIELD_TAG), - item -> Optional.ofNullable(context.getAttribute(item, AttributeController.VALUE_TAG)).orElseThrow(() -> new IllegalStateException("The value tag of " + item.toString() + " is null.")) - ) - ); + var fieldMap = getFieldMap(modelAttribute.value()); Map toConvert = PersistDataUtils.toNestedMap(fieldMap); LOGGER.debug("using " + toConvert + " to create instance of " + model); return PersistDataUtils.mapToObject(toConvert, model); @@ -242,27 +234,31 @@ private void initMethodParseManager(MethodParseManager parser) { parser.registerParser((t, annos) -> Arrays.stream(annos).anyMatch(a -> a.annotationType() == MapAttribute.class), (annotations, type, event) -> { MapAttribute attribute = (MapAttribute) Arrays.stream(annotations).filter(a -> a.annotationType() == MapAttribute.class).findAny().orElseThrow(() -> new IllegalStateException("cannot find MapAttribute annotation")); - var context = this.currentView.getEldgContext(); boolean isMap = false; - if (type instanceof ParameterizedType){ - var parat = (ParameterizedType)type; + if (type instanceof ParameterizedType) { + var parat = (ParameterizedType) type; isMap = parat.getRawType() == Map.class && parat.getActualTypeArguments()[0] == String.class && parat.getActualTypeArguments()[1] == Object.class; } if (!isMap) throw new IllegalStateException("@MapAttribute 必須使用 Map 作為其類型"); - Map fieldMap = context.getItems(attribute.value()) - .stream() - .filter(item -> context.getAttribute(item, AttributeController.FIELD_TAG) != null) - .collect(Collectors - .toMap( - item -> context.getAttribute(item, AttributeController.FIELD_TAG), - item -> Optional.ofNullable(context.getAttribute(item, AttributeController.VALUE_TAG)).orElseThrow(() -> new IllegalStateException("The value tag of " + item.toString() + " is null.")) - ) - ); + Map fieldMap = getFieldMap(attribute.value()); return PersistDataUtils.toNestedMap(fieldMap); }); } + private Map getFieldMap(char pattern){ + if (this.currentView == null) throw new IllegalStateException("currentView is null"); + var context = this.currentView.getEldgContext(); + Map fieldMap = new HashMap<>(); + for (ItemStack item : context.getItems(pattern)) { + String field = context.getAttribute(item, AttributeController.FIELD_TAG); + if (field == null) continue; + Object value = context.getAttribute(item, AttributeController.VALUE_TAG); + fieldMap.put(field, value); + } + return fieldMap; + } + private ItemStack getItemByEvent(InventoryEvent e) { return Optional.ofNullable(e).map(ee -> itemGetterMap.get(ee.getEventName())).map(f -> f.apply(e)).orElseThrow(() -> new IllegalStateException("no item return by the event or the event is null")); } @@ -315,7 +311,12 @@ private void handleException(Exception ex) { Class exceptionViewHandler = exceptionViewHandlerOpt.orElseGet(eldgmvcInstallation::getDefaultExceptionHandler); ExceptionViewHandler viewHandlerIns = injector.getInstance(exceptionViewHandler); UIController fromController = controllerCls.getAnnotation(UIController.class); - Arrays.stream(exceptionViewHandler.getDeclaredMethods()) + Method[] declaredMethods = Optional.ofNullable(declaredMethodMap.get(exceptionViewHandler)).orElseGet(() -> { + var methods = exceptionViewHandler.getDeclaredMethods(); + declaredMethodMap.put(exceptionViewHandler, methods); + return methods; + }); + Arrays.stream(declaredMethods) .filter(m -> m.isAnnotationPresent(HandleException.class)) .filter(m -> Arrays.stream(m.getAnnotation(HandleException.class).value()).anyMatch(v -> { Class superCls = ex.getClass(); diff --git a/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/ELDGView.java b/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/ELDGView.java index 738770c..2f20bd8 100644 --- a/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/ELDGView.java +++ b/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/ELDGView.java @@ -333,15 +333,15 @@ public C getAttributePrimitive(Class type, ItemStack itemStack, String ke public Map getAsMap(ItemStack item) { String id = getAttributePrimitive(String.class, item, "id"); - return Optional.ofNullable(attributeMap.get(id)).map(ImmutableMap::copyOf).orElseGet(ImmutableMap::of); + return Optional.ofNullable(attributeMap.get(id)).map(HashMap::new).orElseGet(HashMap::new); } @Override - public C getAttribute(ItemStack item, String key) { + public synchronized C getAttribute(ItemStack item, String key) { // instead of using persist data type, use map //return getObjectAttribute(item, key); String id = getIdFromItem(item); - attributeMap.putIfAbsent(id, new ConcurrentHashMap<>()); + attributeMap.putIfAbsent(id, new HashMap<>()); LOGGER.debug("item (" + item.getType() + ") is now: " + getAsMap(item).toString()); return (C) attributeMap.get(id).get(key); } @@ -393,11 +393,11 @@ public void setAttributePrimitive(Class type, ItemStack itemStack, String } @Override - public void setAttribute(ItemStack itemStack, String key, Object value) { + public synchronized void setAttribute(ItemStack itemStack, String key, Object value) { // instead of using persist data type, use map //this.setObjectAttribute(itemStack, key, value); String id = getIdFromItem(itemStack); - this.attributeMap.putIfAbsent(id, new ConcurrentHashMap<>()); + this.attributeMap.putIfAbsent(id, new HashMap<>()); this.attributeMap.get(id).put(key, value); LOGGER.debug("item (" + itemStack.getType() + ") is now: " + getAsMap(itemStack).toString()); @@ -410,7 +410,7 @@ public void setAttributePrimitive(Class type, char pattern, String key, O @Override - public void setAttribute(char pattern, String key, Object value) { + public synchronized void setAttribute(char pattern, String key, Object value) { getItems(pattern).forEach(item -> setAttribute(item, key, value)); } diff --git a/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/PersistDataUtils.java b/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/PersistDataUtils.java index 0b5daa4..01f7c24 100644 --- a/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/PersistDataUtils.java +++ b/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/PersistDataUtils.java @@ -12,6 +12,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.annotation.Nonnull; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -146,6 +147,9 @@ public static T mapToObject(Map map, Class beanClass) { Map m = (Map) value; value = mapToObject(m, field.getType()); } + if (value == null && field.isAnnotationPresent(Nonnull.class)){ + throw new IllegalStateException("property assigned @Nonnull but setting null value."); + } try { field.set(obj, value); }catch (IllegalAccessException e) { diff --git a/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/demo/error/StaticErrorView.java b/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/demo/error/StaticErrorView.java index f340b26..07f29da 100644 --- a/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/demo/error/StaticErrorView.java +++ b/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/demo/error/StaticErrorView.java @@ -26,7 +26,7 @@ public void renderView(Exception ex, UIContext context) { .components( button.icon(Material.BARRIER) .title("&cError: " + ex.getClass().getSimpleName()) - .lore("&c".concat(ex.getMessage())) + .lore("&c" + ex.getMessage()) .create() ); } diff --git a/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/demo/test/TestController.java b/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/demo/test/TestController.java index bd44de5..c85080f 100644 --- a/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/demo/test/TestController.java +++ b/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/demo/test/TestController.java @@ -25,9 +25,10 @@ public void beforeCreate(Player player){ @ClickMapping(view = TestView.class, pattern = 'A') - public void onClick(@ModelAttribute('Z') TestModel test, Player player, @MapAttribute('Z') Map map){ + public BukkitView onClick(@ModelAttribute('Z') TestModel test, Player player, @MapAttribute('Z') Map map){ player.sendMessage(test.toString()); player.sendMessage(map.toString()); + return null; } diff --git a/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/demo/test/TestModel.java b/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/demo/test/TestModel.java index fffcd7c..fbc75b4 100644 --- a/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/demo/test/TestModel.java +++ b/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/demo/test/TestModel.java @@ -13,12 +13,16 @@ public class TestModel { public LocalTime testTime; + // test null + public String txt; + @Override public String toString() { return "TestModel{" + - "testColor=" + testColor.toString() + - ", testDate=" + testDate.toString() + - ", testTime=" + testTime.toString() + + "testColor=" + testColor + + ", testDate=" + testDate + + ", testTime=" + testTime + + ", txt='" + txt + '\'' + '}'; } } diff --git a/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/demo/test/TestView.java b/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/demo/test/TestView.java index a8d9713..78fee7e 100644 --- a/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/demo/test/TestView.java +++ b/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/demo/test/TestView.java @@ -1,5 +1,6 @@ package com.ericlam.mc.eldgui.demo.test; +import com.ericlam.mc.eldgui.component.AttributeController; import com.ericlam.mc.eldgui.component.factory.ButtonFactory; import com.ericlam.mc.eldgui.component.factory.DateSelectorFactory; import com.ericlam.mc.eldgui.component.factory.RGBSelectorFactory; @@ -47,6 +48,11 @@ public void renderView(Void model, UIContext context) { .bindInput("testTime", LocalTime.now()) .label("&aTime Select: (shift move unit, click to +/-, middle to input)") .icon(Material.CLOCK) + .create(), + button.icon(Material.PAPER) + .title("test null string") + .bind(AttributeController.FIELD_TAG, "txt") + .bind(AttributeController.VALUE_TAG, null) .create() ) .and() diff --git a/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/event/ELDGClickEventHandler.java b/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/event/ELDGClickEventHandler.java index 5e7f2d6..714189f 100644 --- a/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/event/ELDGClickEventHandler.java +++ b/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/event/ELDGClickEventHandler.java @@ -15,13 +15,13 @@ public final class ELDGClickEventHandler extends ELDGEventHandler { - public ELDGClickEventHandler(Object controller, MethodParseManager parseManager, ReturnTypeManager returnTypeManager, Map, MVCInstallation.QualifierFilter> customQualifier) { - super(controller, parseManager, returnTypeManager, customQualifier); + public ELDGClickEventHandler(Object controller, MethodParseManager parseManager, ReturnTypeManager returnTypeManager, Map, MVCInstallation.QualifierFilter> customQualifier, Method[] declaredMethods) { + super(controller, parseManager, returnTypeManager, customQualifier, declaredMethods); } @Override - protected Map loadAllHandlers(Object controller) { - return Arrays.stream(controller.getClass().getDeclaredMethods()).parallel() + protected Map loadAllHandlers(Method[] declaredMethods) { + return Arrays.stream(declaredMethods).parallel() .filter(m -> m.isAnnotationPresent(ClickMapping.class)) .collect(Collectors.toMap(m -> m.getAnnotation(ClickMapping.class), m -> m)); } diff --git a/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/event/ELDGDragEventHandler.java b/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/event/ELDGDragEventHandler.java index 6f9d568..5df5b5d 100644 --- a/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/event/ELDGDragEventHandler.java +++ b/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/event/ELDGDragEventHandler.java @@ -15,13 +15,13 @@ public final class ELDGDragEventHandler extends ELDGEventHandler { - public ELDGDragEventHandler(Object controller, MethodParseManager parseManager, ReturnTypeManager returnTypeManager, Map, MVCInstallation.QualifierFilter> customQualifier) { - super(controller, parseManager, returnTypeManager, customQualifier); + public ELDGDragEventHandler(Object controller, MethodParseManager parseManager, ReturnTypeManager returnTypeManager, Map, MVCInstallation.QualifierFilter> customQualifier, Method[] declaredMethods) { + super(controller, parseManager, returnTypeManager, customQualifier, declaredMethods); } @Override - protected Map loadAllHandlers(Object controller) { - return Arrays.stream(controller.getClass().getDeclaredMethods()) + protected Map loadAllHandlers(Method[] declaredMethods) { + return Arrays.stream(declaredMethods) .parallel() .filter(m -> m.isAnnotationPresent(DragMapping.class)) .collect(Collectors.toMap(m -> m.getAnnotation(DragMapping.class), m -> m)); diff --git a/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/event/ELDGEventHandler.java b/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/event/ELDGEventHandler.java index 1f647a3..9ec416d 100644 --- a/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/event/ELDGEventHandler.java +++ b/ELDependenci-MVC-plugin/src/main/java/com/ericlam/mc/eldgui/event/ELDGEventHandler.java @@ -3,6 +3,7 @@ import com.ericlam.mc.eldgui.ELDGView; import com.ericlam.mc.eldgui.MVCInstallation; import com.ericlam.mc.eldgui.view.View; +import com.google.common.collect.ImmutableMap; import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryInteractEvent; import org.bukkit.inventory.Inventory; @@ -15,6 +16,8 @@ public abstract class ELDGEventHandler { + private static final Map, Map> controllerEventMap = new ConcurrentHashMap<>(); + protected final Map eventMap = new ConcurrentHashMap<>(); private final Object uiController; private final MethodParseManager parseManager; @@ -25,22 +28,29 @@ public abstract class ELDGEventHandler, MVCInstallation.QualifierFilter> customQualifier) { + Map, MVCInstallation.QualifierFilter> customQualifier, + Method[] declaredMethods + ) { this.uiController = controller; this.parseManager = parseManager; this.returnTypeManager = returnTypeManager; this.customQualifier = customQualifier; - this.loadAllCommonHandlers(controller); - this.loadAllHandlers(controller).forEach((k, v) -> eventMap.put(toRequestMapping(k), v)); + if (controllerEventMap.containsKey(controller.getClass())){ + this.eventMap.putAll(controllerEventMap.get(controller.getClass())); + }else{ + this.loadAllCommonHandlers(declaredMethods); + this.loadAllHandlers(declaredMethods).forEach((k, v) -> eventMap.put(toRequestMapping(k), v)); + controllerEventMap.put(controller.getClass(), ImmutableMap.copyOf(eventMap)); + } } - private void loadAllCommonHandlers(Object controller) { - Arrays.stream(controller.getClass().getDeclaredMethods()).parallel() + private void loadAllCommonHandlers(Method[] declareMethods) { + Arrays.stream(declareMethods).parallel() .filter(m -> m.isAnnotationPresent(RequestMapping.class)) .forEach(m -> eventMap.put(m.getAnnotation(RequestMapping.class), m)); } - protected abstract Map loadAllHandlers(Object controller); + protected abstract Map loadAllHandlers(Method[] declaredMethods); public void unloadAllHandlers() { eventMap.clear();