diff --git a/src/main/java/com/eactive/eai/transformer/engine/Parser.java b/src/main/java/com/eactive/eai/transformer/engine/Parser.java index 98315d0..5cd7d03 100644 --- a/src/main/java/com/eactive/eai/transformer/engine/Parser.java +++ b/src/main/java/com/eactive/eai/transformer/engine/Parser.java @@ -36,6 +36,8 @@ public class Parser implements Serializable { getJep().setAllowAssignment(true); getJep().setAllowUndeclared(true); + addBooleanConstants(); + try { addGenericFunctions(); } catch (Exception e) { @@ -89,6 +91,13 @@ public class Parser implements Serializable { public void initSymbolTable() { getJep().initSymTab(); + addBooleanConstants(); + } + + /** JEP 기본 상수에 없는 true/false 를 Boolean 타입으로 등록한다. */ + private void addBooleanConstants() { + getJep().addVariable("true", Boolean.TRUE); + getJep().addVariable("false", Boolean.FALSE); } @SuppressWarnings("unchecked") @@ -144,8 +153,9 @@ public class Parser implements Serializable { private void addFunction(String name, PostfixMathCommand command) { if(name == null || command == null) { logger.warn("addFunction ("+name+","+command+") skip null"); - return; + return; } + getJep().addFunction(name, command); // 원본 대소문자 (camelCase 표현식 지원) getJep().addFunction(name.toUpperCase(), command); getJep().addFunction(name.toLowerCase(), command); } diff --git a/src/main/java/com/eactive/eai/transformer/function/generic/ConvertDateTime.java b/src/main/java/com/eactive/eai/transformer/function/generic/ConvertDateTime.java new file mode 100644 index 0000000..04af171 --- /dev/null +++ b/src/main/java/com/eactive/eai/transformer/function/generic/ConvertDateTime.java @@ -0,0 +1,67 @@ +package com.eactive.eai.transformer.function.generic; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Stack; + +import org.nfunk.jep.ParseException; +import org.nfunk.jep.function.PostfixMathCommand; + +/** + * JEP 커스텀 함수 - 날짜/시간 형식 변환 + * + *
사용법: {@code convertDateTime(source, fromFormat, toFormat)} + *
사용법: {@code getCurrent(format)} + *
사용법: {@code getTrinomial(target, compare, operator, trueValue, falseValue)} + *
사용법: {@code replaceAll(source, from, to)} + *
주의: JEP 내장 함수 {@code str()}와 충돌하므로, 변수명으로 {@code str}을 사용하지 말 것.
+ */
+public class ReplaceAll extends PostfixMathCommand {
+ public ReplaceAll() {
+ numberOfParameters = 3;
+ }
+
+ /**
+ * 스택에서 (source, from, to) 순으로 꺼내 전체 치환 후 결과를 반환한다.
+ *
+ * @param inStack JEP 후위 연산 스택
+ * @throws ParseException 파라미터가 String 타입이 아닌 경우
+ */
+ @SuppressWarnings("unchecked")
+ public void run(Stack inStack) throws ParseException {
+ checkStack(inStack);
+
+ Object p3 = inStack.pop();
+ Object p2 = inStack.pop();
+ Object p1 = inStack.pop();
+
+ if (!(p1 instanceof String)) {
+ throw new ParseException("replaceAll(): first parameter (source string) must be a string");
+ }
+ if (!(p2 instanceof String)) {
+ throw new ParseException("replaceAll(): second parameter (from string) must be a string");
+ }
+ if (!(p3 instanceof String)) {
+ throw new ParseException("replaceAll(): third parameter (to string) must be a string");
+ }
+
+ inStack.push(((String) p1).replace((String) p2, (String) p3));
+ }
+}
diff --git a/src/test/java/com/eactive/eai/transformer/ConvertDateTimeFunctionTest.java b/src/test/java/com/eactive/eai/transformer/ConvertDateTimeFunctionTest.java
new file mode 100644
index 0000000..eb8d86f
--- /dev/null
+++ b/src/test/java/com/eactive/eai/transformer/ConvertDateTimeFunctionTest.java
@@ -0,0 +1,85 @@
+package com.eactive.eai.transformer;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.nfunk.jep.JEP;
+
+import com.eactive.eai.transformer.function.generic.ConvertDateTime;
+
+@DisplayName("ConvertDateTime 날짜 변환 함수 테스트")
+class ConvertDateTimeFunctionTest {
+
+ private JEP jep;
+
+ @BeforeEach
+ void setUp() {
+ jep = new JEP();
+ jep.addStandardFunctions();
+ jep.addStandardConstants();
+ jep.setAllowUndeclared(true);
+ jep.addFunction("convertDateTime", new ConvertDateTime());
+ }
+
+ private String eval(String expr) {
+ jep.parseExpression(expr);
+ assertFalse(jep.hasError(), "파싱 오류: " + jep.getErrorInfo());
+ Object result = jep.getValueAsObject();
+ assertFalse(jep.hasError(), "평가 오류: " + jep.getErrorInfo());
+ return (String) result;
+ }
+
+ @Test
+ @DisplayName("yyyyMMdd → yyyy-MM-dd 변환")
+ void testCompactToHyphen() {
+ jep.addVariable("src", "20240508");
+ assertEquals("2024-05-08", eval("convertDateTime(src, \"yyyyMMdd\", \"yyyy-MM-dd\")"));
+ }
+
+ @Test
+ @DisplayName("yyyyMMddHHmmss → yyyy-MM-dd'T'HH:mm:ss 변환 (ISO 8601)")
+ void testToIso8601() {
+ jep.addVariable("src", "20240508143022");
+ assertEquals("2024-05-08T14:30:22",
+ eval("convertDateTime(src, \"yyyyMMddHHmmss\", \"yyyy-MM-dd'T'HH:mm:ss\")"));
+ }
+
+ @Test
+ @DisplayName("yyyy-MM-dd'T'HH:mm:ss → yyyyMMddHHmmss 변환")
+ void testFromIso8601ToCompact() {
+ jep.addVariable("src", "2024-05-08T14:30:22");
+ assertEquals("20240508143022",
+ eval("convertDateTime(src, \"yyyy-MM-dd'T'HH:mm:ss\", \"yyyyMMddHHmmss\")"));
+ }
+
+ @Test
+ @DisplayName("yyyyMMddHHmmssSSS → yyyy-MM-dd HH:mm:ss.SSS 변환 (밀리초 포함)")
+ void testWithMilliseconds() {
+ jep.addVariable("src", "20240508143022123");
+ assertEquals("2024-05-08 14:30:22.123",
+ eval("convertDateTime(src, \"yyyyMMddHHmmssSSS\", \"yyyy-MM-dd HH:mm:ss.SSS\")"));
+ }
+
+ @Test
+ @DisplayName("날짜 포맷만 변환 (시간 없음)")
+ void testDateOnly() {
+ jep.addVariable("src", "2024/05/08");
+ assertEquals("20240508",
+ eval("convertDateTime(src, \"yyyy/MM/dd\", \"yyyyMMdd\")"));
+ }
+
+ @Test
+ @DisplayName("잘못된 날짜 → 오류 발생")
+ void testInvalidDate() {
+ jep.addVariable("src", "99999999");
+ jep.parseExpression("convertDateTime(src, \"yyyyMMdd\", \"yyyy-MM-dd\")");
+ if (!jep.hasError()) {
+ jep.getValueAsObject();
+ assertTrue(jep.hasError(), "잘못된 날짜는 오류가 발생해야 합니다");
+ }
+ }
+}
diff --git a/src/test/java/com/eactive/eai/transformer/GetCurrentFunctionTest.java b/src/test/java/com/eactive/eai/transformer/GetCurrentFunctionTest.java
new file mode 100644
index 0000000..c670ec9
--- /dev/null
+++ b/src/test/java/com/eactive/eai/transformer/GetCurrentFunctionTest.java
@@ -0,0 +1,93 @@
+package com.eactive.eai.transformer;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.nfunk.jep.JEP;
+
+import com.eactive.eai.transformer.function.generic.GetCurrent;
+
+@DisplayName("GetCurrent 현재시간 반환 함수 테스트")
+class GetCurrentFunctionTest {
+
+ private JEP jep;
+
+ @BeforeEach
+ void setUp() {
+ jep = new JEP();
+ jep.addStandardFunctions();
+ jep.addStandardConstants();
+ jep.setAllowUndeclared(true);
+ jep.addFunction("getCurrent", new GetCurrent());
+ }
+
+ private String eval(String expr) {
+ jep.parseExpression(expr);
+ assertFalse(jep.hasError(), "파싱 오류: " + jep.getErrorInfo());
+ Object result = jep.getValueAsObject();
+ assertFalse(jep.hasError(), "평가 오류: " + jep.getErrorInfo());
+ return (String) result;
+ }
+
+ @Test
+ @DisplayName("yyyyMMdd 형식 — 오늘 날짜와 일치")
+ void testDateFormat() {
+ String expected = new SimpleDateFormat("yyyyMMdd").format(new Date());
+ String result = eval("getCurrent(\"yyyyMMdd\")");
+ assertEquals(expected, result);
+ }
+
+ @Test
+ @DisplayName("yyyy-MM-dd 형식 — 오늘 날짜와 일치")
+ void testHyphenDateFormat() {
+ String expected = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
+ String result = eval("getCurrent(\"yyyy-MM-dd\")");
+ assertEquals(expected, result);
+ }
+
+ @Test
+ @DisplayName("yyyyMMddHHmmss 형식 — 결과가 14자리")
+ void testDateTimeFormat() {
+ String result = eval("getCurrent(\"yyyyMMddHHmmss\")");
+ assertNotNull(result);
+ assertTrue(result.matches("\\d{14}"), "결과가 14자리 숫자여야 합니다: " + result);
+ }
+
+ @Test
+ @DisplayName("yyyy-MM-dd'T'HH:mm:ss ISO 8601 형식")
+ void testIso8601Format() {
+ String result = eval("getCurrent(\"yyyy-MM-dd'T'HH:mm:ss\")");
+ assertNotNull(result);
+ assertTrue(result.matches("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}"),
+ "ISO 8601 형식이어야 합니다: " + result);
+ }
+
+ @Test
+ @DisplayName("yyyyMMddHHmmssSSS 형식 — 결과가 17자리")
+ void testDateTimeMillisFormat() {
+ String result = eval("getCurrent(\"yyyyMMddHHmmssSSS\")");
+ assertNotNull(result);
+ assertTrue(result.matches("\\d{17}"), "결과가 17자리 숫자여야 합니다: " + result);
+ }
+
+ @Test
+ @DisplayName("잘못된 포맷 → 오류 발생")
+ void testInvalidFormat() {
+ jep.parseExpression("getCurrent(\"invalid!!!###\")");
+ if (!jep.hasError()) {
+ jep.getValueAsObject();
+ assertTrue(jep.hasError(), "잘못된 포맷은 오류가 발생해야 합니다");
+ }
+ }
+
+ private void assertEquals(String expected, String result) {
+ org.junit.jupiter.api.Assertions.assertEquals(expected, result);
+ }
+}
diff --git a/src/test/java/com/eactive/eai/transformer/GetTrinomialFunctionTest.java b/src/test/java/com/eactive/eai/transformer/GetTrinomialFunctionTest.java
new file mode 100644
index 0000000..f34363e
--- /dev/null
+++ b/src/test/java/com/eactive/eai/transformer/GetTrinomialFunctionTest.java
@@ -0,0 +1,205 @@
+package com.eactive.eai.transformer;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.nfunk.jep.JEP;
+
+import com.eactive.eai.transformer.function.generic.GetTrinomial;
+
+@DisplayName("GetTrinomial 삼항연산자 함수 테스트")
+class GetTrinomialFunctionTest {
+
+ private JEP jep;
+
+ @BeforeEach
+ void setUp() {
+ jep = new JEP();
+ jep.addStandardFunctions();
+ jep.addStandardConstants();
+ jep.setAllowUndeclared(true);
+
+ jep.addVariable("true", Boolean.TRUE);
+ jep.addVariable("false", Boolean.FALSE);
+
+ jep.addFunction("getTrinomial", new GetTrinomial());
+ }
+
+ private Object eval(String expr) {
+ jep.parseExpression(expr);
+ assertFalse(jep.hasError(), "파싱 오류: " + jep.getErrorInfo());
+ Object result = jep.getValueAsObject();
+ assertFalse(jep.hasError(), "평가 오류: " + jep.getErrorInfo());
+ return result;
+ }
+
+ // ─────────────────────────────────────────────────────────────────
+ // 숫자형 비교
+ // ─────────────────────────────────────────────────────────────────
+
+ @Test
+ @DisplayName("숫자 = 비교 — 참")
+ void testNumberEqualTrue() {
+ jep.addVariable("a", 10.0);
+ assertEquals("같음", eval("getTrinomial(a, 10, \"=\", \"같음\", \"다름\")"));
+ }
+
+ @Test
+ @DisplayName("숫자 = 비교 — 거짓")
+ void testNumberEqualFalse() {
+ jep.addVariable("a", 5.0);
+ assertEquals("다름", eval("getTrinomial(a, 10, \"=\", \"같음\", \"다름\")"));
+ }
+
+ @Test
+ @DisplayName("숫자 < 비교 — 참")
+ void testNumberLessThanTrue() {
+ jep.addVariable("score", 70.0);
+ assertEquals("미달", eval("getTrinomial(score, 80, \"<\", \"미달\", \"통과\")"));
+ }
+
+ @Test
+ @DisplayName("숫자 > 비교 — 참")
+ void testNumberGreaterThanTrue() {
+ jep.addVariable("score", 90.0);
+ assertEquals("우수", eval("getTrinomial(score, 80, \">\", \"우수\", \"보통\")"));
+ }
+
+ @Test
+ @DisplayName("숫자 <= 비교 — 경계값")
+ void testNumberLessOrEqual() {
+ jep.addVariable("v", 80.0);
+ assertEquals("이하", eval("getTrinomial(v, 80, \"<=\", \"이하\", \"초과\")"));
+ }
+
+ @Test
+ @DisplayName("숫자 >= 비교 — 경계값")
+ void testNumberGreaterOrEqual() {
+ jep.addVariable("v", 80.0);
+ assertEquals("이상", eval("getTrinomial(v, 80, \">=\", \"이상\", \"미만\")"));
+ }
+
+ // ─────────────────────────────────────────────────────────────────
+ // 문자열 비교
+ // ─────────────────────────────────────────────────────────────────
+
+ @Test
+ @DisplayName("문자열 = 비교 — 참")
+ void testStringEqualTrue() {
+ jep.addVariable("status", "Y");
+ assertEquals("활성", eval("getTrinomial(status, \"Y\", \"=\", \"활성\", \"비활성\")"));
+ }
+
+ @Test
+ @DisplayName("문자열 = 비교 — 거짓")
+ void testStringEqualFalse() {
+ jep.addVariable("status", "N");
+ assertEquals("비활성", eval("getTrinomial(status, \"Y\", \"=\", \"활성\", \"비활성\")"));
+ }
+
+ @Test
+ @DisplayName("문자열 < 비교 — 사전순")
+ void testStringLessThan() {
+ jep.addVariable("s", "apple");
+ assertEquals("앞", eval("getTrinomial(s, \"banana\", \"<\", \"앞\", \"뒤\")"));
+ }
+
+ // ─────────────────────────────────────────────────────────────────
+ // 결과값 타입 — 숫자 반환
+ // ─────────────────────────────────────────────────────────────────
+
+ @Test
+ @DisplayName("결과값이 숫자형")
+ void testNumericReturnValue() {
+ jep.addVariable("flag", "Y");
+ Object result = eval("getTrinomial(flag, \"Y\", \"=\", 1, 0)");
+ assertTrue(result instanceof Number);
+ assertEquals(1.0, ((Number) result).doubleValue());
+ }
+
+ // ─────────────────────────────────────────────────────────────────
+ // boolean 비교
+ // ─────────────────────────────────────────────────────────────────
+
+ @Test
+ @DisplayName("boolean = 비교 — 참 (JEP는 boolean 리터럴 미지원, 변수로 바인딩)")
+ void testBooleanEqual() {
+ jep.addVariable("b", Boolean.TRUE);
+ jep.addVariable("bTrue", Boolean.TRUE);
+ assertEquals("참", eval("getTrinomial(b, bTrue, \"=\", \"참\", \"거짓\")"));
+ }
+
+ @Test
+ @DisplayName("boolean = 비교 — 거짓")
+ void testBooleanEqualFalse() {
+ jep.addVariable("b", Boolean.FALSE);
+ jep.addVariable("bTrue", Boolean.TRUE);
+ assertEquals("거짓", eval("getTrinomial(b, bTrue, \"=\", \"참\", \"거짓\")"));
+ }
+
+ // ─────────────────────────────────────────────────────────────────
+ // 실전 Transformer 표현식 패턴
+ // ─────────────────────────────────────────────────────────────────
+
+ @Test
+ @DisplayName("실전 패턴 — true/false 결과값 (JEP 상수 1.0/0.0 반환 검증)")
+ void testBooleanLiteralReturnValue() {
+ // JEP addStandardConstants()가 true=1.0, false=0.0 으로 등록하는지 확인
+ // → 등록되어 있다면 변수 바인딩 없이 숫자로 반환됨
+ // → 등록 안 됐다면 파싱 오류 또는 undeclared 변수(NaN)로 처리됨
+ jep.addVariable("intVal", 10.0);
+ Object result = eval("getTrinomial(intVal, 10, \"=\", true, false)");
+ assertTrue(result instanceof Number,
+ "true/false 결과값이 Number 타입이어야 함 (실제: " + result.getClass().getSimpleName() + ")");
+ assertEquals(1.0, ((Number) result).doubleValue(), 0.0,
+ "조건 참일 때 true(=1.0) 반환 기대");
+ }
+
+ @Test
+ @DisplayName("실전 패턴 — dot 경로 변수(AGW_…SRCS.INT_VALUE) + boolean 결과값")
+ void testRealWorldExpression() {
+ // Transformer 실사용 표현식:
+ // getTrinomial(AGW_TESTREQ10031S1_SRCS.INT_VALUE, 10, "=", true, false)
+ //
+ // JEP에서 dot(.)은 element-access 연산자로 파싱됨
+ // → 파싱 성공 여부와 변수 바인딩 방법을 확인하기 위한 테스트
+ String expr = "getTrinomial(AGW_TESTREQ10031S1_SRCS.INT_VALUE, 10, \"=\", true, false)";
+
+ // [1] 파싱만 먼저 검증
+ jep.parseExpression(expr);
+ assertFalse(jep.hasError(), "파싱 오류: " + jep.getErrorInfo());
+
+ // [2] 파싱 후 JEP 심볼 테이블에 등록된 변수명 확인
+ // dot-expression 이므로 "AGW_TESTREQ10031S1_SRCS" 가 심볼로 잡힐 것으로 예상
+ java.util.Set