From e90780406703b89307ed7ca11315528760473309 Mon Sep 17 00:00:00 2001
From: huangchengxing <841396397@qq.com>
Date: Wed, 20 Sep 2023 23:56:49 +0800
Subject: [PATCH 1/2] feat(DuplicateStrategy): support when building containers
based on enumerations or methods, if there are multiple corresponding values
for the same key, it is allowed to be processed according to a specific
policy (Gitee #I832VQ)
---
.../cn/crane4j/annotation/ContainerEnum.java | 8 ++
.../crane4j/annotation/ContainerMethod.java | 8 ++
.../crane4j/annotation/DuplicateStrategy.java | 96 +++++++++++++++++++
.../core/container/EnumContainerBuilder.java | 35 ++++++-
.../container/MethodInvokerContainer.java | 16 +++-
.../DefaultMethodContainerFactory.java | 17 +++-
.../container/EnumContainerBuilderTest.java | 31 +++++-
.../core/util/DuplicateStrategyTest.java | 33 +++++++
.../crane4j/core/util/ReflectUtilsTest.java | 17 +++-
9 files changed, 247 insertions(+), 14 deletions(-)
create mode 100644 crane4j-annotation/src/main/java/cn/crane4j/annotation/DuplicateStrategy.java
create mode 100644 crane4j-core/src/test/java/cn/crane4j/core/util/DuplicateStrategyTest.java
diff --git a/crane4j-annotation/src/main/java/cn/crane4j/annotation/ContainerEnum.java b/crane4j-annotation/src/main/java/cn/crane4j/annotation/ContainerEnum.java
index 692cc714..44858eaf 100644
--- a/crane4j-annotation/src/main/java/cn/crane4j/annotation/ContainerEnum.java
+++ b/crane4j-annotation/src/main/java/cn/crane4j/annotation/ContainerEnum.java
@@ -38,4 +38,12 @@ public @interface ContainerEnum {
* @return value field name
*/
String value() default "";
+
+ /**
+ * The strategy for handling duplicate keys.
+ *
+ * @return strategy
+ * @since 2.2.0
+ */
+ DuplicateStrategy duplicateStrategy() default DuplicateStrategy.ALERT;
}
diff --git a/crane4j-annotation/src/main/java/cn/crane4j/annotation/ContainerMethod.java b/crane4j-annotation/src/main/java/cn/crane4j/annotation/ContainerMethod.java
index fe981e61..8fa8db0e 100644
--- a/crane4j-annotation/src/main/java/cn/crane4j/annotation/ContainerMethod.java
+++ b/crane4j-annotation/src/main/java/cn/crane4j/annotation/ContainerMethod.java
@@ -69,6 +69,14 @@ public @interface ContainerMethod {
*/
MappingType type() default MappingType.ONE_TO_ONE;
+ /**
+ * The strategy for handling duplicate keys.
+ *
+ * @return strategy
+ * @since 2.2.0
+ */
+ DuplicateStrategy duplicateStrategy() default DuplicateStrategy.ALERT;
+
/**
* The key field of the data source object returned by the method.
* If {@link #type()} is {@link MappingType#MAPPED}, this parameter is ignored.
diff --git a/crane4j-annotation/src/main/java/cn/crane4j/annotation/DuplicateStrategy.java b/crane4j-annotation/src/main/java/cn/crane4j/annotation/DuplicateStrategy.java
new file mode 100644
index 00000000..14d2fff9
--- /dev/null
+++ b/crane4j-annotation/src/main/java/cn/crane4j/annotation/DuplicateStrategy.java
@@ -0,0 +1,96 @@
+package cn.crane4j.annotation;
+
+import lombok.RequiredArgsConstructor;
+
+/**
+ * An enumeration that defines the strategy for handling duplicate keys.
+ *
+ * @author huangchengxing
+ * @since 2.2.0
+ */
+@RequiredArgsConstructor
+public enum DuplicateStrategy {
+
+ /**
+ * Throws an exception when the key already exists.
+ *
+ * @see IllegalArgumentException
+ */
+ ALERT(new Selector() {
+ @Override
+ public V choose(K key, V oldVal, V newVal) {
+ throw new IllegalArgumentException(
+ "Duplicate key [" + key + "] has been associated with value [" + oldVal + "],"
+ + " can no longer be associated with [" + newVal +"]"
+ );
+ }
+ }),
+
+ /**
+ * When the key already exists, discard the old key value.
+ */
+ DISCARD_NEW(new Selector() {
+ @Override
+ public V choose(K key, V oldVal, V newVal) {
+ return oldVal;
+ }
+ }),
+
+ /**
+ * When the keys are the same, discard the new key value.
+ */
+ DISCARD_OLD(new Selector() {
+ @Override
+ public V choose(K key, V oldVal, V newVal) {
+ return newVal;
+ }
+ }),
+
+ /**
+ * When the keys are the same, discard the new value and old value, return null.
+ */
+ DISCARD(new Selector() {
+ @Override
+ public V choose(K key, V oldVal, V newVal) {
+ return null;
+ }
+ })
+
+ ;
+
+ /**
+ * selector
+ */
+ private final Selector selector;
+
+ /**
+ * Choose to return one of the old and new values.
+ *
+ * @param key key
+ * @param oldVal old val
+ * @param newVal new val
+ * @return val
+ */
+ public V choose(K key, V oldVal, V newVal) {
+ return selector.choose(key, oldVal, newVal);
+ }
+
+ /**
+ * Internal interface, used to select the value to be returned when the key is duplicated.
+ *
+ * @author huangchengxing
+ */
+ @FunctionalInterface
+ private interface Selector {
+
+ /**
+ * Choose to return one of the old and new values.
+ *
+ * @param key key
+ * @param oldVal old val
+ * @param newVal new val
+ * @return val
+ */
+ V choose(K key, V oldVal, V newVal);
+ }
+}
diff --git a/crane4j-core/src/main/java/cn/crane4j/core/container/EnumContainerBuilder.java b/crane4j-core/src/main/java/cn/crane4j/core/container/EnumContainerBuilder.java
index 701971b2..8a739fb6 100644
--- a/crane4j-core/src/main/java/cn/crane4j/core/container/EnumContainerBuilder.java
+++ b/crane4j-core/src/main/java/cn/crane4j/core/container/EnumContainerBuilder.java
@@ -1,6 +1,7 @@
package cn.crane4j.core.container;
import cn.crane4j.annotation.ContainerEnum;
+import cn.crane4j.annotation.DuplicateStrategy;
import cn.crane4j.core.exception.Crane4jException;
import cn.crane4j.core.support.AnnotationFinder;
import cn.crane4j.core.support.MethodInvoker;
@@ -12,11 +13,10 @@ import cn.crane4j.core.util.StringUtils;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
+import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
/**
* A builder class for creating {@link Container}s from enumerations.
@@ -73,6 +73,14 @@ public class EnumContainerBuilder> {
@NonNull
private PropertyOperator propertyOperator = DEFAULT_PROPERTY_OPERATOR;
+ /**
+ * Processing strategy when the key is duplicated.
+ *
+ * @since 2.2.0
+ */
+ @NonNull
+ private DuplicateStrategy duplicateStrategy = DuplicateStrategy.ALERT;
+
/**
* Creates a new instance of the builder with the specified enum type.
*
@@ -171,6 +179,18 @@ public class EnumContainerBuilder> {
return this;
}
+ /**
+ * Set processing strategy of when the key is duplicated.
+ *
+ * @param duplicateStrategy coverage strategy
+ * @return this builder instance
+ * @since 2.2.0
+ */
+ public EnumContainerBuilder duplicateStrategy(DuplicateStrategy duplicateStrategy) {
+ this.duplicateStrategy = duplicateStrategy;
+ return this;
+ }
+
// ============== component ==============
/**
@@ -209,9 +229,15 @@ public class EnumContainerBuilder> {
resolveConfigFromAnnotation(annotation);
}
}
+
// build container
- Map enumMap = (Map) (Map, ?>) Stream.of(enumType.getEnumConstants())
- .collect(Collectors.toMap(keyGetter, valueGetter));
+ Map enumMap = new HashMap<>(enumType.getEnumConstants().length);
+ for (T e : enumType.getEnumConstants()) {
+ K key = (K)keyGetter.apply(e);
+ Object newVal = valueGetter.apply(e);
+ enumMap.compute(key, (k, oldVal) ->
+ Objects.isNull(oldVal) ? newVal : duplicateStrategy.choose(k, oldVal, newVal));
+ }
namespace = StringUtils.emptyToDefault(this.namespace, enumType.getSimpleName());
return ImmutableMapContainer.forMap(namespace, enumMap);
}
@@ -220,6 +246,7 @@ public class EnumContainerBuilder> {
if (namespace == null) {
namespace = StringUtils.emptyToDefault(annotation.namespace(), enumType.getSimpleName());
}
+ duplicateStrategy = annotation.duplicateStrategy();
boolean hasKey = StringUtils.isNotEmpty(annotation.key());
boolean hasValue = StringUtils.isNotEmpty(annotation.value());
if (hasKey && keyGetter == DEFAULT_KEY_GETTER) {
diff --git a/crane4j-core/src/main/java/cn/crane4j/core/container/MethodInvokerContainer.java b/crane4j-core/src/main/java/cn/crane4j/core/container/MethodInvokerContainer.java
index e3301827..c204d6d0 100644
--- a/crane4j-core/src/main/java/cn/crane4j/core/container/MethodInvokerContainer.java
+++ b/crane4j-core/src/main/java/cn/crane4j/core/container/MethodInvokerContainer.java
@@ -1,5 +1,6 @@
package cn.crane4j.core.container;
+import cn.crane4j.annotation.DuplicateStrategy;
import cn.crane4j.annotation.MappingType;
import cn.crane4j.core.support.MethodInvoker;
import cn.crane4j.core.support.container.MethodContainerFactory;
@@ -7,13 +8,15 @@ import cn.crane4j.core.support.container.MethodInvokerContainerCreator;
import cn.crane4j.core.util.Asserts;
import cn.crane4j.core.util.CollectionUtils;
import lombok.Getter;
+import lombok.Setter;
+import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
-import java.util.function.Function;
import java.util.stream.Collectors;
/**
@@ -43,6 +46,9 @@ public class MethodInvokerContainer implements Container