diff --git a/src/main/java/org/jpatternmatch/JPatternMatch.java b/src/main/java/org/jpatternmatch/JPatternMatch.java index d3602cb..ea9636b 100644 --- a/src/main/java/org/jpatternmatch/JPatternMatch.java +++ b/src/main/java/org/jpatternmatch/JPatternMatch.java @@ -1,31 +1,23 @@ package org.jpatternmatch; import org.jpatternmatch.common.PatternMatchException; -import static org.jpatternmatch.ArgCheck.*; +import java.lang.reflect.Field; +import java.util.function.Consumer; import java.util.function.Function; -import java.util.function.Supplier; + +import static org.jpatternmatch.ArgCheck.requireArguemntNonNull; +import static org.jpatternmatch.ArgCheck.requireArguemntsNonNull; public class JPatternMatch { + private final T result; private boolean matched = false; - private T result; - private Object ctorInstance; - private JPatternMatch(Object ctorInstance) { - this.ctorInstance = checkArgumentNonNullAndReturn(ctorInstance); + private JPatternMatch(T ctorInstance) { + this.result = ctorInstance; } - /** - * Check the type of parameter instance - * - * @param injectedInstance the object to check the type of - * @param clazz the class type to compare against - * @param func a function for return - * @return the result of applying the function - * @throws IllegalArgumentException if any argument is null - * @throws ClassCastException if the type does not match - */ public static R asTypeOf(Object injectedInstance, Class clazz, Function func) { requireArguemntsNonNull(injectedInstance, clazz, func); @@ -36,14 +28,6 @@ public static R asTypeOf(Object injectedInstance, Class clazz, Functio throw new ClassCastException("타입 매칭에 실패했습니다."); } - /** - * Check the type of parameter instance - * - * @param injectedInstance the object to check for type - * @param clazz the class type to compare against - * @param action a runnable action to execute - * @throws IllegalArgumentException if any argument is null - */ public static void asTypeOf(Object injectedInstance, Class clazz, Runnable action) { requireArguemntsNonNull(injectedInstance, clazz, action); @@ -53,114 +37,114 @@ public static void asTypeOf(Object injectedInstance, Class clazz, Runnabl } } - /** - * Init JPatternMatch for pattern matching - * @param injectedInstance the instance want to check for pattern matching - * @throws IllegalArgumentException if any argument is null - */ - public static JPatternMatch of(Object injectedInstance) { - + public static JPatternMatch of(T injectedInstance) { requireArguemntNonNull(injectedInstance); return new JPatternMatch<>(injectedInstance); } - /** - * Explicitly starting pattern matching - */ - public JPatternMatch match() { + // 1. 타입을 알고 있으면서, 같은 타입에서 값을 비교하는 usecase + public JPatternMatch with(T condition, Consumer action) { + if (!matched && condition.equals(this.result)) { + action.accept(condition); + matched = true; + } return this; } - /** - * Defined return type class - * @param clazz the return class - * @throws IllegalArgumentException if any argument is null + /* + * 2. 타입을 알지 못하면서, 타입에 따라 다른 로직을 수행하는 usecase + * 2-1. Base 타입: Object + * 2-2. 타입 확인 이후 타입: 확인된 타입(Generic) */ - public JPatternMatch returnObject(Class clazz) { - requireArguemntNonNull(clazz); - return new JPatternMatch<>(this.ctorInstance); + public JPatternMatch asTypeOf(Class clazz, Consumer action) { + if (clazz.isInstance(this.result)) { + action.accept(clazz.cast(this.result)); + this.matched = true; + } + return this; } - /** - * Defined compare condition with a runnable - * - * @param condition the condtion you want to compare with control instance - * @param action a runnable action to execute - * @throws IllegalArgumentException if any argument is null + /* + * 3. 타입을 알지 못하면서, 타입을 확인한 후 각 타입의 값을 다시 추출해서 값에 따라 다른 로직을 수행하는 usecase + * record Member( + * String name, + * long age + * ) { } + * -> member.name == "철수" */ - public JPatternMatch with(Object condition, Runnable action) { + public void extract(Class clazz, Consumer> func) { + func.accept(new JPatternMatchExtractExpression<>(this.result, clazz)); + } - requireArguemntsNonNull(condition, action); + public JPatternMatch otherwise(Runnable action) { + + requireArguemntNonNull(action); - if (!matched && condition.equals(this.ctorInstance)) { + if (!matched) { action.run(); - matched = true; } + return this; } - /** - * Defined return type class with a supplier - * - * @param condition the condtion you want to compare with control instance - * @param compareSupplier a supplier to provide the return value - * @throws IllegalArgumentException if any argument is null - */ - public JPatternMatch with(Object condition, Supplier compareSupplier) { - - requireArguemntsNonNull(condition, compareSupplier); + public void elseThrow(Throwable throwable) { + try { + if (!matched) { + throw throwable; + } + } catch (Throwable e) { + throw new RuntimeException(e); + } + } - if (!matched && condition.equals(this.ctorInstance)) { - matched = true; - this.result = compareSupplier.get(); + public T exhaustive() throws PatternMatchException { + if (this.matched) { + return this.result; } - return this; + throw new PatternMatchException("패턴과 일치하는 타입이 없습니다."); } - /** - * End Pattern Matching with default executor - * - * @param action a runnable action to execute - * @throws IllegalArgumentException if any argument is null - */ - public void otherwise(Runnable action) { - requireArguemntNonNull(action); + static class Member { + String name; + long age; - if (!matched) { - action.run(); + public Member(String name, long age) { + this.name = name; + this.age = age; } } - /** - * End Pattern Matching with default return value - * - * @param supplier a supplier to provide the return value if no match was found - * @throws IllegalArgumentException if supplier is null - */ - public T otherwise(Supplier supplier) { - - requireArguemntNonNull(supplier); + public static class JPatternMatchExtractExpression { + private final Object result; + private final Class clazz; - if (this.result == null) { - this.result = supplier.get(); + JPatternMatchExtractExpression(Object result, Class clazz) { + this.result = result; + this.clazz = clazz; } - return this.result; - } - /** - * End Pattern Matching with accurate result matching - * - * @return the result of pattern matching - * @throws PatternMatchException if no matching type was found - */ - public T exhaustive() throws PatternMatchException { - if (this.result == null || !result.getClass().equals(ctorInstance.getClass())) { - throw new PatternMatchException("패턴과 일치하는 타입이 없습니다."); + public void extractByField(String fieldName, Consumer> func) { + if (!clazz.equals(result.getClass())) { + throw new IllegalArgumentException("타입이 일치하지 않습니다."); + } + + // 1. 어떤 필드에 접근할지 받은 후, 해당 필드의 값 추출 + Field field = null; + try { + field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } + Object fieldValue = null; + try { + fieldValue = field.get(this.result); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + func.accept(new JPatternMatch<>(fieldValue)); } - return this.result; } - - } diff --git a/src/test/java/org/jpatternmatch/MatchTests.java b/src/test/java/org/jpatternmatch/MatchTests.java index ab68145..dd20a06 100644 --- a/src/test/java/org/jpatternmatch/MatchTests.java +++ b/src/test/java/org/jpatternmatch/MatchTests.java @@ -1,234 +1,82 @@ package org.jpatternmatch; import org.jpatternmatch.common.PatternMatchException; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; -import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.*; @DisplayName("Match 메서드 테스트") public class MatchTests { @Test - @DisplayName("Map 객체 매칭 로직이 적절한 조건의 호출 여부를 확인한다") - void testMapMatching() { - - // given - Map keyValueObject = new HashMap<>(); - keyValueObject.put("key1", "type"); - keyValueObject.put("key2", "type2"); - - List executionFlags = Arrays.asList(false, false, false, false); - - // when - Map result = JPatternMatch.of(keyValueObject) - .returnObject(Map.class) - .with(createKeyValueMap("key1", "type", "key2", "type3"), () -> { - executionFlags.set(0, true); - return createKeyValueMap("key1", "type", "key2", "type3"); - }) - .with(createKeyValueMap("key1", "type", "key2", "type2"), () -> { - executionFlags.set(1, true); - return createKeyValueMap("key1", "type", "key2", "type2"); - }) - .with(keyValueObject, () -> { - executionFlags.set(2, true); - return keyValueObject; - }) - .otherwise(() -> { - executionFlags.set(3, true); - return createKeyValueMap("default", "default", "default", "default"); - }); - - // then - assertFalse(executionFlags.get(0)); - assertTrue(executionFlags.get(1)); - assertFalse(executionFlags.get(2)); - assertFalse(executionFlags.get(3)); - - assertEquals(result, keyValueObject); - assertEquals(result, createKeyValueMap("key1", "type", "key2", "type2")); - } - - - @Test - @DisplayName("동일 객체 주입 시 System 함수 호출 여부를 확인한다.") - void testSysoutMatchingPatterns() { - // given - Map keyValueObject = new HashMap<>(); - keyValueObject.put("key1", "type"); - keyValueObject.put("key2", "type2"); - - ByteArrayOutputStream outContent = new ByteArrayOutputStream(); - PrintStream originalOut = System.out; - System.setOut(new PrintStream(outContent)); - - // when - try { - JPatternMatch.of(keyValueObject) - .match() - .with(createKeyValueMap("key1", "type", "key2", "type3"), () -> { - System.out.println("첫 번째 매치 실행"); - }) - .with(createKeyValueMap("key1", "type", "key2", "type4"), () -> { - System.out.println("두 번째 매치 실행"); - }) - .with(createKeyValueMap("key1", "type", "key2", "type5"), () -> { - System.out.println("세 번째 매치 실행"); - }) - .otherwise(() -> { - System.out.println("기본 매치 실행"); - }); - - // then - String output = outContent.toString(); - assertFalse(output.contains("첫 번째 매치 실행")); - assertFalse(output.contains("두 번째 매치 실행")); - assertFalse(output.contains("세 번째 매치 실행")); - assertTrue(output.contains("기본 매치 실행")); - } finally { - System.setOut(originalOut); - } + @DisplayName("usecase 1") + void usecase1() { + var base = "first"; + // 1. 타입을 알고 있으면서, 같은 타입에서 값을 비교하는 usecase + JPatternMatch.of(base) + .with("first", System.out::println) + .otherwise(() -> Assertions.fail("값이 first가 아니라서 실패 실패")); } - @Test - @DisplayName("Map 동일 객체 주입 시 적합한 조건의 호출 여부를 확인한다.") - void testMapMatchingPatterns() { - // given - Map keyValueObject = new HashMap<>(); - keyValueObject.put("key1", "type"); - keyValueObject.put("key2", "type2"); - - List executionFlags = Arrays.asList(false, false, false, false); - - // when - JPatternMatch.of(keyValueObject) - .match() - .with(createKeyValueMap("key1", "type", "key2", "type3"), () -> executionFlags.set(0, true)) - .with(createKeyValueMap("key1", "type", "key2", "type4"), () -> executionFlags.set(1, true)) - .with(createKeyValueMap("key1", "type", "key2", "type5"), () -> executionFlags.set(2, true)) - .otherwise(() -> executionFlags.set(3, true)); - - // then - assertFalse(executionFlags.get(0)); - assertFalse(executionFlags.get(1)); - assertFalse(executionFlags.get(2)); - assertTrue(executionFlags.get(3)); + @DisplayName("usecase 2") + void usecase2() { + Object base = "first"; + /* + * 2. 타입을 알지 못하면서, 타입에 따라 다른 로직을 수행하는 usecase + * 2-1. Base 타입: Object + * 2-2. 타입 확인 이후 타입: 확인된 타입(Generic) + */ + JPatternMatch.of(base) + .asTypeOf(String.class, System.out::println) + .elseThrow(new IllegalArgumentException("타입이 일치하지 않습니다.")); } - - @Test - @DisplayName("String 동일 객체 주입 시 적합한 조건의 호출 여부를 확인한다.") - void testStringMatchingPatterns() { - // given - Map keyValueObject = new HashMap<>(); - keyValueObject.put("key1", "type"); - keyValueObject.put("key2", "type2"); - - List executionFlags = Arrays.asList(false, false, false, false); - - // when - JPatternMatch.of(keyValueObject) - .match() - .with(createKeyValueMap("key1", "type", "key2", "type3"), () -> executionFlags.set(0, true)) - .with(createKeyValueMap("key1", "type", "key2", "type4"), () -> executionFlags.set(1, true)) - .with(createKeyValueMap("key1", "type", "key2", "type5"), () -> executionFlags.set(2, true)) - .otherwise(() -> executionFlags.set(3, true)); - - // then - assertFalse(executionFlags.get(0)); - assertFalse(executionFlags.get(1)); - assertFalse(executionFlags.get(2)); - assertTrue(executionFlags.get(3)); + @DisplayName("usecase 3") + void usecase3() { + /* + * 3. 타입을 알지 못하면서, 타입을 확인한 후 각 타입의 값을 다시 추출해서 값에 따라 다른 로직을 수행하는 usecase + * record Member( + * String name, + * long age + * ) { } + * -> member.name == "철수" + */ + var member = new JPatternMatch.Member("철수", 20); + JPatternMatch.of(member) + .extract(JPatternMatch.Member.class, extract -> { + extract.extractByField("name", matcher -> { + matcher.with("철수", System.out::println); + matcher.otherwise(() -> Assertions.fail("name이 철수가 아니어서 실패")); + }); + }); } - @Test - @DisplayName("exhaustive 종료 함수일 때 반환하는 객체가 존재하고, 지정한 타입과 일치할 시의 성공 여부를 확인한다.") - void testExhaustiveHappyCase() throws PatternMatchException { - - // given - Map keyValueObject = new HashMap<>(); - keyValueObject.put("key1", "type"); - keyValueObject.put("key2", "type2"); - - List executionFlags = Arrays.asList(false, false, false); - - // when - Map result = JPatternMatch.of(keyValueObject) - .returnObject(Map.class) - .with(createKeyValueMap("key1", "type", "key2", "type3"), () -> { - executionFlags.set(0, true); - return createKeyValueMap("key1", "type", "key2", "type3"); - }) - .with(createKeyValueMap("key1", "type", "key2", "type2"), () -> { - executionFlags.set(1, true); - return createKeyValueMap("key1", "type", "key2", "type2"); - }) + @DisplayName("exhaustive") + void exhaustive() throws PatternMatchException { + Object base = "first"; + var actual = JPatternMatch.of(base) + .with("first", System.out::println) + .otherwise(() -> Assertions.fail("값이 first가 아니라서 실패 실패")) .exhaustive(); - // then - assertFalse(executionFlags.get(0)); - assertTrue(executionFlags.get(1)); - - assertEquals(keyValueObject, result); + Assertions.assertEquals(base, actual); } - @Test - @DisplayName("exhaustive 종료 함수일 때 반환하는 객체가 없을 시의 실패 여부를 확인한다.") - void testExhaustiveWhenNoReturn() { - - // given - Map keyValueObject = new HashMap<>(); - keyValueObject.put("key1", "type"); - keyValueObject.put("key2", "type2"); - - List executionFlags = Arrays.asList(false, false, false); - - // when - PatternMatchException exception = assertThrows(PatternMatchException.class, () -> { - - JPatternMatch.of(keyValueObject) - .returnObject(Map.class) - .with(createKeyValueMap("key1", "type999", "key2", "type999"), () -> { - executionFlags.set(0, true); - return createKeyValueMap("key1", "type888", "key2", "type888"); - }) - .with(createKeyValueMap("key1", "type777", "key2", "type777"), () -> { - executionFlags.set(1, true); - return createKeyValueMap("key1", "type666", "key2", "key666"); - }) - .with(createKeyValueMap("key1", "type555", "key2", "type555"), () -> { - executionFlags.set(2, true); - return createKeyValueMap("key1", "type444", "key2", "type444"); - }) - .exhaustive(); - }); - - // then - assertFalse(executionFlags.get(0)); - assertFalse(executionFlags.get(1)); - assertFalse(executionFlags.get(2)); - - assertEquals("패턴과 일치하는 타입이 없습니다.", exception.getMessage()); + void qa() { + List.of(1, 2, 3, 4, 5, 6, 12); + var component1 = 1; + var component2 = 2; + var component3 = 3; + var component4 = 4; + var component5 = 5; + var component6 = 6; + var component7 = 7; + var component8 = 8; + // distributed declaration } - - - - private Map createKeyValueMap(String key1, String value1, String key2, String value2) { - Map map = new HashMap<>(); - map.put(key1, value1); - map.put(key2, value2); - return map; - } - }