import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.mutable.MutableLong;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;
@SuppressWarnings("unused")
public class TreeUtil {public static <T, I> List<T> buildByParentId(@NonNull List<T> list,@NonNull Function<T, I> getId,@NonNull Function<T, I> getParentId,@Nullable Comparator<T> comparator,@NonNull BiConsumer<T, List<T>> setSub) {Map<I, T> idNodeMap = list.stream().collect(Collectors.toMap(getId, Function.identity(), (existing, replacement) -> existing));Map<I, List<T>> parentIdMap = new HashMap<>();for (T node : list) {I parentId = getParentId.apply(node);parentIdMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(node);}for (T node : list) {I id = getId.apply(node);List<T> children = parentIdMap.get(id);if (children != null) {sortList(children, comparator);setSub.accept(node, children);}}List<T> roots = list.stream().filter(node -> {I parentId = getParentId.apply(node);return parentId == null || !idNodeMap.containsKey(parentId);}).collect(Collectors.toList());sortList(roots, comparator);return roots;}private static <T> void sortList(List<T> list, Comparator<T> comparator) {if (comparator != null && list != null && !list.isEmpty()) {list.sort(comparator);}}public static <T, C extends String> List<T> buildByCode(@NonNull List<T> list,@NonNull Function<T, C> getCode,@Nullable Comparator<T> comparator,@NonNull Function<T, List<T>> getSub,@NonNull BiConsumer<T, List<T>> setSub) {List<T> sortedCodeList = list.stream().sorted(Comparator.comparing(getCode)).collect(Collectors.toList());Map<C, List<T>> codeGroupMap = new HashMap<>();C flagCode = null;for (T item : sortedCodeList) {C currentCode = getCode.apply(item);if (flagCode == null || !currentCode.startsWith(flagCode)) {flagCode = currentCode;}codeGroupMap.computeIfAbsent(flagCode, k -> new ArrayList<>()).add(item);}List<T> tree = new ArrayList<>();codeGroupMap.forEach((k, v) -> tree.add(buildNodeByCode(v, getCode, getSub, setSub)));sortTree(tree, comparator, getSub);return tree;}private static <T, C extends String> T buildNodeByCode(List<T> subList,Function<T, C> getCode,Function<T, List<T>> getSub,BiConsumer<T, List<T>> setSub) {if (subList.isEmpty()) {throw new IllegalStateException("树构建异常:子节点列表为空");}Collections.reverse(subList);for (int i = 0; i < subList.size() - 1; i++) {T child = subList.get(i);T parent = findParentByCode(child, subList.subList(i + 1, subList.size()), getCode);List<T> children = getSub.apply(parent);if (children == null) {children = new ArrayList<>();setSub.accept(parent, children);}children.add(child);}return subList.get(subList.size() - 1);}private static <T, C extends String> T findParentByCode(T currentNode,List<T> subList,Function<T, C> getCode) {C currentCode = getCode.apply(currentNode);for (T node : subList) {C parentCode = getCode.apply(node);if (currentCode.startsWith(parentCode) && !currentCode.equals(parentCode)) {return node;}}throw new IllegalStateException("构建异常:未找到父节点");}private static <T> void sortTree(List<T> tree,Comparator<T> comparator,Function<T, List<T>> getSub) {sortList(tree, comparator);for (T node : tree) {List<T> sub = getSub.apply(node);if (sub != null && !sub.isEmpty()) {sortTree(sub, comparator, getSub);}}}public static <T, R> List<T> getParent(List<T> list,List<R> ids,Function<? super T, ? extends R> idExtractor,Function<? super T, ? extends R> parentIdExtractor,boolean containSelf) {if (CollectionUtils.isEmpty(list) || CollectionUtils.isEmpty(ids)) {return new ArrayList<>();}Map<R, T> idNodeMap = list.stream().collect(Collectors.toMap(idExtractor, Function.identity()));Set<R> parentIds = new HashSet<>();Deque<R> stack = new LinkedList<>(ids);while (!stack.isEmpty()) {R currentId = stack.pop();if (!parentIds.contains(currentId)) {parentIds.add(currentId);T node = idNodeMap.get(currentId);if (node != null) {R parentId = parentIdExtractor.apply(node);if (parentId != null && !parentIds.contains(parentId)) {stack.push(parentId);}}}}return list.stream().filter(node -> parentIds.contains(idExtractor.apply(node))).collect(Collectors.toList());}public static <T, R> List<T> getChildren(List<T> list,List<R> ids,Function<? super T, ? extends R> idExtractor,Function<? super T, ? extends R> parentIdExtractor,boolean containSelf) {if (CollectionUtils.isEmpty(list) || CollectionUtils.isEmpty(ids)) {return new ArrayList<>();}Map<R, T> idNodeMap = list.stream().collect(Collectors.toMap(idExtractor, Function.identity(), (existing, replacement) -> existing));Map<R, List<T>> parentIdMap = list.stream().collect(Collectors.groupingBy(parentIdExtractor));Set<R> resultIds = new HashSet<>();if (containSelf) {resultIds.addAll(ids);}Queue<R> queue = new LinkedList<>(ids);while (!queue.isEmpty()) {R parentId = queue.poll();List<T> children = parentIdMap.get(parentId);if (children != null) {for (T child : children) {R childId = idExtractor.apply(child);if (!resultIds.contains(childId)) {resultIds.add(childId);queue.add(childId);}}}}return list.stream().filter(node -> resultIds.contains(idExtractor.apply(node))).collect(Collectors.toList());}public static <T, I> List<T> searchTree4All(@NonNull List<T> tree,@NonNull Function<T, I> getKey,@NonNull Function<T, List<T>> getSub,@NonNull I key) {List<T> matched = new ArrayList<>();Queue<T> queue = new LinkedList<>(tree);while (!queue.isEmpty()) {T node = queue.poll();I nodeKey = getKey.apply(node);if (nodeKey != null && nodeKey.equals(key)) {matched.add(node);}List<T> sub = getSub.apply(node);if (sub != null && !sub.isEmpty()) {queue.addAll(sub);}}return matched;}public static <T, I> Optional<T> searchTree4One(@NonNull List<T> tree,@NonNull Function<T, I> getKey,@NonNull Function<T, List<T>> getSub,@NonNull I key) {Queue<T> queue = new LinkedList<>(tree);while (!queue.isEmpty()) {T node = queue.poll();I nodeKey = getKey.apply(node);if (nodeKey != null && nodeKey.equals(key)) {return Optional.of(node);}List<T> sub = getSub.apply(node);if (sub != null && !sub.isEmpty()) {queue.addAll(sub);}}return Optional.empty();}public static <T> List<T> tree2List(@NonNull List<T> tree,@NonNull Function<T, List<T>> getSub) {List<T> list = new ArrayList<>();Queue<T> queue = new LinkedList<>(tree);while (!queue.isEmpty()) {T node = queue.poll();list.add(node);List<T> sub = getSub.apply(node);if (sub != null && !sub.isEmpty()) {queue.addAll(sub);}}return list;}public static <T> void addRandomId(@NonNull List<T> tree,@NonNull Function<T, List<T>> getSub,@NonNull BiConsumer<T, Long> setId,@NonNull BiConsumer<T, Long> setParentId,@Nullable Long parentId,@Nullable MutableLong idCounter) {if (idCounter == null) {idCounter = new MutableLong(1L);}if (parentId == null) {parentId = 0L;}Queue<T> queue = new LinkedList<>(tree);Map<T, Long> parentMap = new HashMap<>();while (!queue.isEmpty()) {T node = queue.poll();long id = idCounter.longValue();idCounter.increment();setId.accept(node, id);setParentId.accept(node, parentMap.getOrDefault(node, parentId));List<T> sub = getSub.apply(node);if (sub != null && !sub.isEmpty()) {for (T child : sub) {parentMap.put(child, id);queue.add(child);}}}}public static <T> void filterTreeByName(@NonNull List<T> tree,@NonNull Function<T, List<T>> getSub,@NonNull Function<T, String> getName,@NonNull String searchName,@NonNull Boolean reserveChild) {if (!StringUtils.hasLength(searchName)) {return;}Queue<T> queue = new LinkedList<>(tree);while (!queue.isEmpty()) {T node = queue.poll();String name = getName.apply(node);List<T> sub = getSub.apply(node);if (reserveChild && StringUtils.hasLength(name) && name.contains(searchName)) {continue;}if (sub != null && !sub.isEmpty()) {filterTreeByName(sub, getSub, getName, searchName, reserveChild);}if ((sub == null || sub.isEmpty()) && (name == null || !name.contains(searchName))) {tree.remove(node);}}}public static <T> void filterTreeById(@NonNull List<T> tree,@NonNull Function<T, List<T>> getSub,@NonNull Function<T, Long> getId,@NonNull Long searchId,@NonNull Boolean reserveChild) {Queue<T> queue = new LinkedList<>(tree);while (!queue.isEmpty()) {T node = queue.poll();Long id = getId.apply(node);List<T> sub = getSub.apply(node);if (reserveChild && id != null && id.equals(searchId)) {continue;}if (sub != null && !sub.isEmpty()) {filterTreeById(sub, getSub, getId, searchId, reserveChild);}if ((sub == null || sub.isEmpty()) && (id == null || !id.equals(searchId))) {tree.remove(node);}}}
}