From 3ccfd561cba28faab40f56449590a1f647b9c9d6 Mon Sep 17 00:00:00 2001 From: jflute Date: Fri, 27 Sep 2024 10:43:34 +0900 Subject: [PATCH] support SimpleExpression, e.g. boolean ? sea : land #42 --- .../expression/dwarf/ExpressionPlainHook.java | 3 + .../dwarf/SimpleExpressionPlainHook.java | 81 ++++++++++++++-- .../dwarf/SimpleExpressionPlainHookTest.java | 95 +++++++++++++++---- 3 files changed, 153 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/lastaflute/di/core/expression/dwarf/ExpressionPlainHook.java b/src/main/java/org/lastaflute/di/core/expression/dwarf/ExpressionPlainHook.java index e29d046..c24ab2c 100644 --- a/src/main/java/org/lastaflute/di/core/expression/dwarf/ExpressionPlainHook.java +++ b/src/main/java/org/lastaflute/di/core/expression/dwarf/ExpressionPlainHook.java @@ -28,6 +28,8 @@ public interface ExpressionPlainHook { // =================================================================================== // Definition // ========== + String HATENA = "?"; + String COLON = ":"; String DQ = "\""; String SQ = "'"; String EXISTS_BEGIN = LdiResourceUtil.class.getName() + ".exists('"; @@ -41,6 +43,7 @@ public interface ExpressionPlainHook { // e.g. provider.config().getJdbcUrl() String PROVIDER_CONFIG = "provider.config()"; String PROVIDER_GET = PROVIDER_CONFIG + ".get"; + String PROVIDER_IS = PROVIDER_CONFIG + ".is"; // e.g. provider.config().getOrDefault("jdbc.connection.pooling.min.size", null) String ORDEFAULT_METHOD_NAME = "getOrDefault"; diff --git a/src/main/java/org/lastaflute/di/core/expression/dwarf/SimpleExpressionPlainHook.java b/src/main/java/org/lastaflute/di/core/expression/dwarf/SimpleExpressionPlainHook.java index fc7ee10..c5d5d68 100644 --- a/src/main/java/org/lastaflute/di/core/expression/dwarf/SimpleExpressionPlainHook.java +++ b/src/main/java/org/lastaflute/di/core/expression/dwarf/SimpleExpressionPlainHook.java @@ -53,28 +53,33 @@ public class SimpleExpressionPlainHook implements ExpressionPlainHook { // ============ @Override public Object hookPlainly(String exp, Map contextMap, LaContainer container, Class resultType) { + final Object hatenaColonResult = resolveHatenaColon(exp, contextMap, container, resultType); // e.g. ? : + if (isReallyResolved(hatenaColonResult)) { + return hatenaColonResult; + } + return doHookPlainly(exp, contextMap, container, resultType); + } + + protected Object doHookPlainly(String exp, Map contextMap, LaContainer container, Class resultType) { // same filter in JavaScript engine however cannot commonize easily because of OGNL-embedded // no fix as small cost for now by jflute (2020/09/30) final String resolvedExp = ExpressionEngine.resolveExpressionVariableSimply(exp, contextMap); - return doHookPlainly(resolvedExp, contextMap, container, resultType); - } - protected Object doHookPlainly(String exp, Map contextMap, LaContainer container, Class resultType) { - final CastResolved resolved = castResolver.resolveCast(exp, resultType); + final CastResolved resolved = castResolver.resolveCast(resolvedExp, resultType); final String realExp; final Class realType; if (resolved != null) { realExp = resolved.getFilteredExp(); realType = resolved.getResolvedType(); } else { - realExp = exp.trim(); + realExp = resolvedExp.trim(); realType = resultType; } return actuallyHookPlainly(realExp, container, realType); } protected Object actuallyHookPlainly(String exp, LaContainer container, Class resultType) { - Object resovled = resolveSimpleString(exp, container, resultType); // "sea" + Object resovled = resolveSimpleString(exp, container, resultType); // e.g. "sea" if (isReallyResolved(resovled)) { return resovled; } @@ -90,7 +95,7 @@ protected Object actuallyHookPlainly(String exp, LaContainer container, Class if (isReallyResolved(resovled)) { return resovled; } - resovled = resolveSimpleTypeExp(exp, container, resultType); // e.g. @org.docksidestage.Sea@class + resovled = resolveSimpleTypeExp(exp, container, resultType); // e.g. @org.docksidestage.Sea@class or call() if (isReallyResolved(resovled)) { return resovled; } @@ -117,6 +122,60 @@ protected boolean isReallyResolved(Object resovled) { return resovled != null; // includes null return object } + // =================================================================================== + // Hatena Colon + // ============ + // @since 1.0.0 + protected Object resolveHatenaColon(String exp, Map contextMap, LaContainer container, Class resultType) { + // _/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/ + // treated as three expressions by hatena colon + // e.g. + // provider.config().isDevelopmentHere() + // ? new org.lastaflute.jta.core.LaTransaction() + // : @org.lastaflute.jta.helper.timer.LjtTimeoutManager@getInstance() + // _/_/_/_/_/_/_/_/_/_/ + if (mayBeSimpleHatenaColon(exp)) { + // e.g. sea ? over : mystic + final String determinationExp = LdiSrl.substringFirstFront(exp, HATENA).trim(); // e.g. sea + final String hatenaRear = LdiSrl.substringFirstRear(exp, HATENA).trim(); // e.g. over : mystic + final String firstSelectionExp = LdiSrl.substringFirstFront(hatenaRear, COLON).trim(); // e.g. over + final String secondSelectionExp = LdiSrl.substringFirstRear(hatenaRear, COLON).trim(); // e.g. mystic + if (isValidHatenaColonElement(determinationExp, firstSelectionExp, secondSelectionExp)) { + try { + final Object determinationResult = doHookPlainly(determinationExp, contextMap, container, resultType); + if (determinationResult instanceof Boolean) { // and not null + final Object selectedResult; + if ((Boolean) determinationResult) { + selectedResult = doHookPlainly(firstSelectionExp, contextMap, container, resultType); + } else { + selectedResult = doHookPlainly(secondSelectionExp, contextMap, container, resultType); + } + return selectedResult; // may be null yet (caller check needed) + } + } catch (RuntimeException continued) { // may be unexpected format? (give up) + logger.debug("Cannot parse it as hatena colon (continued): exp=" + exp, continued); + } + } + } + return null; + } + + protected boolean mayBeSimpleHatenaColon(String exp) { + if (exp.contains(HATENA) && exp.contains(COLON)) { // has hatena colon + if (exp.indexOf(HATENA) < exp.indexOf(COLON)) { // correct order (not : ?) + if (LdiSrl.count(exp, HATENA) == 1 && LdiSrl.count(exp, COLON) == 1) { // no nest (simple only) + return true; + } + } + } + return false; + } + + protected boolean isValidHatenaColonElement(String determinationExp, String firstSelectionExp, String secondSelectionExp) { + // expcet empty expression e.g. sea ? : mystic + return !determinationExp.isEmpty() && !firstSelectionExp.isEmpty() && !secondSelectionExp.isEmpty(); + } + // =================================================================================== // Basic Handling // ============== @@ -171,14 +230,14 @@ protected Object resolveSimpleNewExp(String exp, LaContainer container, Class try { clazz = LdiReflectionUtil.forName(fqcn); } catch (RuntimeException continued) { // may be framework bug - logger.debug("Failed to find class for the name: exp=" + exp + ", fqcn=" + fqcn, continued); + logger.debug("Failed to find class for the name of expression (continued): exp=" + exp + ", fqcn=" + fqcn, continued); return null; } final Object instance; try { instance = LdiReflectionUtil.newInstance(clazz); } catch (RuntimeException continued) { // may be framework bug - logger.debug("Failed to new instance (of expression): exp=" + exp + ", class=" + clazz, continued); + logger.debug("Failed to new instance of expression (continued): exp=" + exp + ", class=" + clazz, continued); return null; } return instance; @@ -355,7 +414,9 @@ protected Object resolveProviderConfig(String exp, LaContainer container, Class< protected boolean isProviderConfigNoArgMethod(String exp) { // LastaFlute uses // e.g. provider.config().getJdbcUrl() - return exp.startsWith(PROVIDER_GET) && exp.endsWith(METHOD_MARK) && !exp.contains("\""); + // provider.config().isDevelopmentHere() // @since 1.0.0 + final boolean configPrefix = exp.startsWith(PROVIDER_GET) || exp.startsWith(PROVIDER_IS); + return configPrefix && exp.endsWith(METHOD_MARK) && !exp.contains("\""); } protected boolean isProviderConfigOrDefaultMethod(String exp) { // LastaFlute uses diff --git a/src/test/java/org/lastaflute/di/core/expression/dwarf/SimpleExpressionPlainHookTest.java b/src/test/java/org/lastaflute/di/core/expression/dwarf/SimpleExpressionPlainHookTest.java index d658625..9c34819 100644 --- a/src/test/java/org/lastaflute/di/core/expression/dwarf/SimpleExpressionPlainHookTest.java +++ b/src/test/java/org/lastaflute/di/core/expression/dwarf/SimpleExpressionPlainHookTest.java @@ -22,6 +22,7 @@ import org.lastaflute.di.core.meta.impl.LaContainerImpl; import org.lastaflute.di.unit.UnitLastaDiTestCase; import org.lastaflute.jta.core.LaTransaction; +import org.lastaflute.jta.helper.timer.LjtTimeoutManager; /** * @author jflute @@ -29,15 +30,15 @@ public class SimpleExpressionPlainHookTest extends UnitLastaDiTestCase { // =================================================================================== - // new Constructor - // =============== - public void test_hookPlainly_newConstructor_basic() { + // Simple new + // ========== + public void test_hookPlainly_simpleNew_basic() { // ## Arrange ## SimpleExpressionPlainHook hook = new SimpleExpressionPlainHook(); String exp = "new org.lastaflute.jta.core.LaTransaction()"; Map contextMap = new HashMap<>(); - LaContainer container = createProviderContainer(new MyMockProvider(false)); - Class resultType = LaTransaction.class; // however unused + LaContainer container = createContainer(); + Class resultType = Object.class; // unused // ## Act ## Object result = hook.hookPlainly(exp, contextMap, container, resultType); @@ -47,6 +48,41 @@ public void test_hookPlainly_newConstructor_basic() { assertTrue(result instanceof LaTransaction); } + // =================================================================================== + // Simple Type + // =========== + public void test_hookPlainly_simpleType_class_basic() { + // ## Arrange ## + SimpleExpressionPlainHook hook = new SimpleExpressionPlainHook(); + String exp = "@org.lastaflute.jta.helper.timer.LjtTimeoutManager@class"; + Map contextMap = new HashMap<>(); + LaContainer container = createContainer(); + Class resultType = Object.class; // unused + + // ## Act ## + Object result = hook.hookPlainly(exp, contextMap, container, resultType); + + // ## Assert ## + assertNotNull(result); + assertTrue(LjtTimeoutManager.class.equals(result)); + } + + public void test_hookPlainly_simpleType_method_basic() { + // ## Arrange ## + SimpleExpressionPlainHook hook = new SimpleExpressionPlainHook(); + String exp = "@org.lastaflute.jta.helper.timer.LjtTimeoutManager@getInstance()"; + Map contextMap = new HashMap<>(); + LaContainer container = createContainer(); + Class resultType = Object.class; // unused + + // ## Act ## + Object result = hook.hookPlainly(exp, contextMap, container, resultType); + + // ## Assert ## + assertNotNull(result); + assertTrue(result instanceof LjtTimeoutManager); + } + // =================================================================================== // Provider Config // =============== @@ -113,38 +149,65 @@ public void test_hookPlainly_providerConfig_isDetermination() { // since 1.0.0 // =================================================================================== // Hatena Colon // ============ - public void test_hookPlainly_hatenaColon_integer_false() { // since 1.0.0 + public void test_hookPlainly_hatenaColon_false() { // since 1.0.0 // ## Arrange ## SimpleExpressionPlainHook hook = new SimpleExpressionPlainHook(); - String exp = "provider.config().isDevelopmentHere() ? new java.lang.Integer(1) : new java.lang.Integer(2)"; + String determinationExp = "provider.config().isDevelopmentHere()"; + String firstSelectionExp = "new org.lastaflute.jta.core.LaTransaction()"; + String secondSelectionExp = "@org.lastaflute.jta.helper.timer.LjtTimeoutManager@getInstance()"; + String exp = determinationExp + " ? " + firstSelectionExp + " : " + secondSelectionExp; Map contextMap = new HashMap<>(); LaContainer container = createProviderContainer(new MyMockProvider(false)); - Class resultType = Integer.class; + Class resultType = Object.class; // unused + + // ## Act ## + Object result = hook.hookPlainly(exp, contextMap, container, resultType); + + // ## Assert ## + log("result: {}", result); + assertNotNull(result); + assertTrue(result instanceof LjtTimeoutManager); + } + + public void test_hookPlainly_hatenaColon_true() { // since 1.0.0 + // ## Arrange ## + SimpleExpressionPlainHook hook = new SimpleExpressionPlainHook(); + String determinationExp = "provider.config().isDevelopmentHere()"; + String firstSelectionExp = "new org.lastaflute.jta.core.LaTransaction()"; + String secondSelectionExp = "@org.lastaflute.jta.helper.timer.LjtTimeoutManager@getInstance()"; + String exp = determinationExp + " ? " + firstSelectionExp + " : " + secondSelectionExp; + Map contextMap = new HashMap<>(); + LaContainer container = createProviderContainer(new MyMockProvider(true)); + Class resultType = Object.class; // unused // ## Act ## Object result = hook.hookPlainly(exp, contextMap, container, resultType); // ## Assert ## + log("result: {}", result); assertNotNull(result); - assertTrue(result instanceof Integer); - assertEquals(2, (int) result); + assertTrue(result instanceof LaTransaction); } - public void test_hookPlainly_hatenaColon_integer_true() { // since 1.0.0 + public void test_hookPlainly_hatenaColon_variousFormat() { // since 1.0.0 // ## Arrange ## SimpleExpressionPlainHook hook = new SimpleExpressionPlainHook(); - String exp = "provider.config().isDevelopmentHere() ? new java.lang.Integer(1) : new java.lang.Integer(2)"; - Map contextMap = new HashMap<>(); + String determinationExp = "provider.config().isDevelopmentHere()"; + String firstSelectionExp = "provider.config().getJdbcUrl()"; + String secondSelectionExp = "@org.lastaflute.jta.helper.timer.LjtTimeoutManager@class"; + String exp = determinationExp + "\n ? " + firstSelectionExp + " \n: \n" + secondSelectionExp; + Map contextMap = new HashMap<>(); LaContainer container = createProviderContainer(new MyMockProvider(true)); - Class resultType = Integer.class; + Class resultType = Object.class; // unused // ## Act ## Object result = hook.hookPlainly(exp, contextMap, container, resultType); // ## Assert ## + log("result: {}", result); assertNotNull(result); - assertTrue(result instanceof Integer); - assertEquals(1, (int) result); + assertTrue(result instanceof String); + assertEquals(MyMockConfig.JDBC_URL, result); } // ===================================================================================