From e0e33766e9767fd0cdebfd2de1325c3f17f7b762 Mon Sep 17 00:00:00 2001 From: ZhouXY108 Date: Mon, 20 Oct 2025 16:23:12 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20`MapModifier`=20?= =?UTF-8?q?=E7=B1=BB=EF=BC=8C=E6=94=AF=E6=8C=81=E9=93=BE=E5=BC=8F=E6=93=8D?= =?UTF-8?q?=E4=BD=9C=E4=BF=AE=E6=94=B9=20Map=20=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 MapModifier 类,提供了一系列对 Map 数据的修改操作方法,如 put、putIfAbsent、putAll、remove、clear 等 - 支持直接修改指定的 Map - 支持从 Supplier 中获取 Map 并进行修改 - 提供了创建不可修改 Map 的方法 - 增加了相应的单元测试用例 --- .../commons/collection/MapModifier.java | 221 ++++++++++++++++++ .../commons/collection/MapModifierTests.java | 118 ++++++++++ 2 files changed, 339 insertions(+) create mode 100644 plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/collection/MapModifier.java create mode 100644 plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/collection/MapModifierTests.java diff --git a/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/collection/MapModifier.java b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/collection/MapModifier.java new file mode 100644 index 0000000..a0dfa74 --- /dev/null +++ b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/collection/MapModifier.java @@ -0,0 +1,221 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package xyz.zhouxy.plusone.commons.collection; + +import static xyz.zhouxy.plusone.commons.util.AssertTools.checkArgumentNotNull; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.common.annotations.Beta; + +/** + * Map 修改器 + * + *

+ * 封装一系列对 Map 数据的修改操作,修改 Map 的数据。可以用于 Map 的数据初始化等操作。 + * + *

+ * // MapModifier
+ * MapModifier modifier = new MapModifier()
+ *     .putAll(commonProperties)
+ *     .put("username", "Ben")
+ *     .put("accountStatus", LOCKED);
+ *
+ * // 从 Supplier 中获取 Map,并修改数据
+ * Map map = modifier.getAndModify(HashMap::new);
+ *
+ * // 可以灵活使用不同 Map 类型的不同构造器
+ * Map map = modifier.getAndModify(() -> new HashMap<>(8));
+ * Map map = modifier.getAndModify(() -> new HashMap<>(anotherMap));
+ * Map map = modifier.getAndModify(TreeMap::new);
+ * Map map = modifier.getAndModify(ConcurrentHashMap::new);
+ *
+ * // 修改已有的 Map
+ * modifier.modify(map);
+ *
+ * // 创建一个有初始化数据的不可变的 Map
+ * Map map = modifier.getUnmodifiableMap();
+ *
+ * // 链式调用创建并初始化数据
+ * Map map = new MapModifier()
+ *     .putAll(commonProperties)
+ *     .put("username", "Ben")
+ *     .put("accountStatus", LOCKED)
+ *     .getAndModify(HashMap::new);
+ * 
+ * + * @author ZhouXY108 + * @since 1.1.0 + */ +@Beta +public class MapModifier { + + @Nonnull + private Consumer> operators; + + /** + * 创建一个空的 MapModifier + */ + public MapModifier() { + this.operators = m -> { + // do nothing + }; + } + + /** + * 添加一个键值对 + * + * @param key 要添加的 {@code key} + * @param value 要添加的 {@code value} + * @return MapModifier + */ + public MapModifier put(K key, V value) { + return addOperationInternal(map -> map.put(key, value)); + } + + /** + * 添加一个键值对,如果 key 已经存在,则不添加 + * + * @param key 要添加的 {@code key} + * @param value 要添加的 {@code value} + * @return MapModifier + */ + public MapModifier putIfAbsent(K key, V value) { + return addOperationInternal(map -> map.putIfAbsent(key, value)); + } + + /** + * 添加多个键值对 + * + * @param otherMap 要添加的键值对集合 + * @return MapModifier + */ + public MapModifier putAll(Map otherMap) { + return addOperationInternal(map -> map.putAll(otherMap)); + } + + /** + * 添加多个键值对 + * + * @param entries 要添加的键值对集合 + * @return MapModifier + */ + @SafeVarargs + public final MapModifier putAll(Map.Entry... entries) { + return addOperationInternal(map -> { + for (Map.Entry entry : entries) { + map.put(entry.getKey(), entry.getValue()); + } + }); + } + + /** + * 当 {@code key} 不存在时,计算对应的值,并添加到 {@code map} 中 + * 调用 {@link Map#computeIfAbsent(Object, Function)} + * + * @param key 要添加的 {@code key} + * @param mappingFunction 计算 {@code key} 对应的值 + * @return MapModifier + */ + public MapModifier computeIfAbsent(K key, + Function mappingFunction) { + return addOperationInternal(map -> map.computeIfAbsent(key, mappingFunction)); + } + + /** + * 当 {@code key} 存在时,计算对应的值,并添加到 {@code map} 中 + * 调用 {@link Map#computeIfPresent(Object, BiFunction)} + * + * @param key 要添加的 {@code key} + * @param remappingFunction 计算 {@code key} 对应的值 + * @return MapModifier + */ + public MapModifier computeIfPresent(K key, + BiFunction remappingFunction) { + return addOperationInternal(map -> map.computeIfPresent(key, remappingFunction)); + } + + /** + * 删除 {@code key} + * + * @param key 要删除的 {@code key} + * @return MapModifier + */ + public MapModifier remove(K key) { + return addOperationInternal(map -> map.remove(key)); + } + + /** + * 清空 {@code map} + * + * @return MapModifier + */ + public MapModifier clear() { + return addOperationInternal(Map::clear); + } + + /** + * 修改 {@code map} + * + * @param map 要修改的 {@code map} + * @return 修改后的 {@code map}。当入参是 {@code null} 时,返回 {@code null}。 + */ + public > void modify(@Nullable T map) { + if (map != null) { + this.operators.accept(map); + } + } + + /** + * 修改 {@code map} + * + * @param mapSupplier {@code map} 的 {@link Supplier} + * @return 修改后的 {@code map}。 + * 当从 {@code mapSupplier} 获取的 {@code map} 为 {@code null} 时,返回 {@code null}。 + */ + @CheckForNull + public > T getAndModify(Supplier mapSupplier) { + checkArgumentNotNull(mapSupplier, "The map supplier cannot be null."); + T map = mapSupplier.get(); + modify(map); + return map; + } + + /** + * 创建一个有初始化数据的不可变的 {@code Map} + * + * @return 不可变的 {@code Map} + */ + public Map getUnmodifiableMap() { + return Collections.unmodifiableMap(getAndModify(HashMap::new)); + } + + private MapModifier addOperationInternal(Consumer> operator) { + this.operators = this.operators.andThen(operator); + return this; + } +} diff --git a/plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/collection/MapModifierTests.java b/plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/collection/MapModifierTests.java new file mode 100644 index 0000000..d1117d3 --- /dev/null +++ b/plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/collection/MapModifierTests.java @@ -0,0 +1,118 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package xyz.zhouxy.plusone.commons.collection; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +import org.junit.jupiter.api.Test; + +import com.google.common.collect.ImmutableMap; + +import lombok.extern.slf4j.Slf4j; + +// TODO 完善单元测试 +@Slf4j +public class MapModifierTests { + + private static final String APP_START_ID = UUID.randomUUID().toString(); + private static final String LOCKED = "LOCKED"; + + private static final Map commonProperties = ImmutableMap.builder() + .put("channel", "MOBILE") + .put("appStartId", APP_START_ID) + .build(); + + @Test + void initMap() { + Map expected = new HashMap() { + { + put("channel", "MOBILE"); + put("appStartId", APP_START_ID); + put("username", "Ben"); + put("accountStatus", LOCKED); + } + }; + + // MapModifier + MapModifier modifier = new MapModifier() + .putAll(commonProperties) + .put("username", "Ben") + .put("accountStatus", LOCKED); + + // 从 Supplier 中获取 Map,并修改数据 + HashMap hashMap1 = modifier.getAndModify(HashMap::new); + assertEquals(expected, hashMap1); + + // 可以灵活使用不同 Map 类型的不同构造器 + HashMap hashMap2 = modifier.getAndModify(() -> new HashMap<>(8)); + assertEquals(expected, hashMap2); + + // HashMap hashMap3 = modifier.getAndModify(() -> new HashMap<>(anotherMap)); + TreeMap treeMap = modifier.getAndModify(TreeMap::new); + assertEquals(expected, treeMap); + ConcurrentHashMap concurrentHashMap = modifier.getAndModify(ConcurrentHashMap::new); + assertEquals(expected, concurrentHashMap); + + // 修改已有的 Map + Map srcMap = new HashMap<>(); + srcMap.put("srcKey1", "srcValue1"); + srcMap.put("srcKey2", "srcValue2"); + modifier.modify(srcMap); + assertEquals(new HashMap() { + { + putAll(commonProperties); + put("username", "Ben"); + put("accountStatus", LOCKED); + put("srcKey1", "srcValue1"); + put("srcKey2", "srcValue2"); + } + }, srcMap); + + // 创建一个有初始化数据的不可变的 {@code Map} + Map unmodifiableMap = modifier.getUnmodifiableMap(); + assertEquals(expected, unmodifiableMap); + assertThrows(UnsupportedOperationException.class, + () -> unmodifiableMap.put("key", "value")); + } + + @Test + void createAndInitData() { + // 链式调用创建并初始化数据 + HashMap map = new MapModifier() + .putAll(commonProperties) + .put("username", "Ben") + .put("accountStatus", LOCKED) + .getAndModify(HashMap::new); + + HashMap expected = new HashMap() { + { + put("channel", "MOBILE"); + put("appStartId", APP_START_ID); + put("username", "Ben"); + put("accountStatus", LOCKED); + } + }; + assertEquals(expected, map); + } +}