diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/collections/MultiLevelMapHelper.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/collections/MultiLevelMapHelper.java new file mode 100644 index 00000000..d7667c2c --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/collections/MultiLevelMapHelper.java @@ -0,0 +1,141 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2023. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.utils.collections; + + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + + +/******************************************************************************* + ** Help you use a multi-level map, such as: + ** Map[String, Map[String, Integer]] countryStateCountMap = new HashMap[](); + ** + ** Where you always want to put new maps at the lower-level if they aren't there, + ** and similarly, you want to start with a 0 for the value under each (final) key. + ** So instead of like: + ** + ** countryStateCountMap.computeIfAbsent("US", () -> new HashMap()); + ** Map stateCountMap = countryStateCountMap.get("US"); + ** stateCountMap.putIfAbsent("MO", 0); + ** stateCountMap.put(stateCountMap.get("MO") + count); + ** + ** You can just do: + ** MultiLevelMapHelper.getOrPutNextLevel(countryStateCountMap, "US", + ** stateMap -> MultiLevelMapHelper.getOrPutAndIncrement(stateMap, "MO", count)); + ** + ** Or for a bigger map, such as: + ** Map[Integer, Map[String, Map[Integer, Map[String, Integer]]]] bigOleMap = new HashMap[](); + * + ** getOrPutNextLevel(bigOleMap, clientId, + ** map -> getOrPutNextLevel(map, sku, + ** map2 -> getOrPutNextLevel(map2, warehouseId, + ** map3 -> getOrPutAndIncrement(map3, state)))); + ** + *******************************************************************************/ +public class MultiLevelMapHelper +{ + + /******************************************************************************* + ** For the given map, + ** If the key is not found, run the supplier and put its result under that key + ** Then get the value under that key and pass it to the Consumer next + *******************************************************************************/ + public static void getOrPutNextLevel(Map map, K key, Supplier notFoundSupplier, Consumer next) + { + map.computeIfAbsent(key, k -> notFoundSupplier.get()); + V v = map.get(key); + next.accept(v); + } + + + + /******************************************************************************* + ** For the given map, + ** If the key is not found, make a new map[1] and put its result under that key + ** Then get the value under that key (the next level map) and pass it to the Consumer next. + ** + ** [1] - the new map will be the same type as the input map, if possible - else + ** will be a new HashMap. To control the map type, see the overload that takes + ** a Supplier notFoundSupplier. + *******************************************************************************/ + public static void getOrPutNextLevel(Map> map, K key, Consumer> next) + { + map.computeIfAbsent(key, k -> + { + try + { + return (map.getClass().getConstructor().newInstance()); + } + catch(Exception e) + { + return (new HashMap<>()); + } + }); + Map v = map.get(key); + next.accept(v); + } + + + + /******************************************************************************* + ** For the given map, + ** If the key is not found, run the supplier and put its result under that key + ** Then apply the Function next to that value, replacing the value under the key. + *******************************************************************************/ + public static void getOrPutFinalLevel(Map map, K key, Supplier notFoundSupplier, Function next) + { + map.computeIfAbsent(key, k -> notFoundSupplier.get()); + V v = map.get(key); + map.put(key, next.apply(v)); + } + + + + /******************************************************************************* + ** For the given map, + ** If the key is not found, put a 1 under it. + ** else increment the value under the key. + *******************************************************************************/ + public static void getOrPutAndIncrement(Map map, K key) + { + getOrPutAndIncrement(map, key, 1); + } + + + + /******************************************************************************* + ** For the given map, + ** If the key is not found, put the Integer amount under it. + ** else increment the value under the key by the Integer amount. + *******************************************************************************/ + public static void getOrPutAndIncrement(Map map, K key, Integer amount) + { + map.putIfAbsent(key, 0); + Integer v = map.get(key); + map.put(key, v + amount); + } + +} diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/collections/MultiLevelMapHelperTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/collections/MultiLevelMapHelperTest.java new file mode 100644 index 00000000..08baf30b --- /dev/null +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/collections/MultiLevelMapHelperTest.java @@ -0,0 +1,72 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2023. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.utils.collections; + + +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Test; +import static com.kingsrook.qqq.backend.core.utils.collections.MultiLevelMapHelper.getOrPutAndIncrement; +import static com.kingsrook.qqq.backend.core.utils.collections.MultiLevelMapHelper.getOrPutNextLevel; +import static org.junit.jupiter.api.Assertions.assertEquals; + + +/******************************************************************************* + ** Unit test for MultiLevelMapHelper + *******************************************************************************/ +class MultiLevelMapHelperTest +{ + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void test() + { + Map>>> bigOleMap = new HashMap<>(); + + Integer clientId = 120; + String sku = "BASIC1"; + Integer warehouseId = 10; + String state = "MO"; + Integer quantity = 5; + + getOrPutNextLevel(bigOleMap, clientId, + map -> getOrPutNextLevel(map, sku, + map2 -> getOrPutNextLevel(map2, warehouseId, + map3 -> MultiLevelMapHelper.getOrPutFinalLevel(map3, state, () -> 0, v -> v + quantity)))); + assertEquals(5, bigOleMap.get(120).get("BASIC1").get(10).get("MO")); + + getOrPutNextLevel(bigOleMap, clientId, + map -> getOrPutNextLevel(map, sku, + map2 -> getOrPutNextLevel(map2, warehouseId, + map3 -> getOrPutAndIncrement(map3, state)))); + assertEquals(6, bigOleMap.get(120).get("BASIC1").get(10).get("MO")); + + getOrPutNextLevel(bigOleMap, clientId, + map -> getOrPutNextLevel(map, sku, + map2 -> getOrPutNextLevel(map2, warehouseId, + map3 -> getOrPutAndIncrement(map3, state, quantity)))); + assertEquals(11, bigOleMap.get(120).get("BASIC1").get(10).get("MO")); + } + +} \ No newline at end of file