diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/bean/copier/MapToMapCopier.java b/hutool-core/src/main/java/org/dromara/hutool/core/bean/copier/MapToMapCopier.java index c7ff20a28..a5996e729 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/bean/copier/MapToMapCopier.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/bean/copier/MapToMapCopier.java @@ -66,10 +66,6 @@ public class MapToMapCopier extends AbsCopier { return; } sValue = entry.getValue(); - // 忽略空值 - if (copyOptions.ignoreNullValue && sValue == null) { - return; - } final Object targetValue = target.get(sKey); // 非覆盖模式下,如果目标值存在,则跳过 @@ -83,6 +79,11 @@ public class MapToMapCopier extends AbsCopier { sValue = this.copyOptions.convertField(typeArguments[1], sValue); } + // 忽略空值 + if (copyOptions.ignoreNullValue && sValue == null) { + return; + } + // 目标赋值 target.put(sKey, sValue); }); diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/map/multi/DirectedWeightGraph.java b/hutool-core/src/main/java/org/dromara/hutool/core/map/multi/DirectedWeightGraph.java index 196a7616e..9bc942394 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/map/multi/DirectedWeightGraph.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/map/multi/DirectedWeightGraph.java @@ -1,13 +1,15 @@ package org.dromara.hutool.core.map.multi; +import org.dromara.hutool.core.exception.HutoolException; + import java.util.*; /** * 权重有向图 * 基于 SPFA 算法实现 可以处理负边 可以进行负权环路检查 * + * @param 点对象类型 * @author NewshiJ - * @date 2024/8/16 09:01 */ public class DirectedWeightGraph { @@ -15,35 +17,46 @@ public class DirectedWeightGraph { private final Set allPoints = new HashSet<>(); // 邻接边 - private final Map>> neighborEdgeMap = new HashMap<>(); + private final Map>> neighborEdgeMap = new HashMap<>(); + + /** + * 获取全部点 + * + * @return 全部点 + */ + public Set getAllPoints() { + return allPoints; + } /** * 添加边 + * * @param fromPoint 开始点 * @param nextPoint 结束点 - * @param weight 权重 + * @param weight 权重 */ - public void putEdge(T fromPoint, T nextPoint, long weight) { + public void putEdge(final T fromPoint, final T nextPoint, final long weight) { allPoints.add(fromPoint); allPoints.add(nextPoint); - Map> nextPointMap = neighborEdgeMap.computeIfAbsent(fromPoint, k -> new HashMap<>()); + final Map> nextPointMap = neighborEdgeMap.computeIfAbsent(fromPoint, k -> new HashMap<>()); nextPointMap.put(nextPoint, new Edge<>(fromPoint, nextPoint, weight)); } /** * 删除边 - * @param fromPoint - * @param nextPoint + * + * @param fromPoint 开始点 + * @param nextPoint 结束点 */ - public void removeEdge(T fromPoint, T nextPoint) { - Map> nextPointMap = neighborEdgeMap.computeIfAbsent(fromPoint, k -> new HashMap<>()); + public void removeEdge(final T fromPoint, final T nextPoint) { + final Map> nextPointMap = neighborEdgeMap.computeIfAbsent(fromPoint, k -> new HashMap<>()); nextPointMap.remove(nextPoint); // 重新计算 所有点位 allPoints.clear(); - neighborEdgeMap.forEach((f,m) -> { + neighborEdgeMap.forEach((f, m) -> { allPoints.add(f); - m.forEach((t,e) -> { + m.forEach((t, e) -> { allPoints.add(t); }); }); @@ -51,21 +64,22 @@ public class DirectedWeightGraph { /** * 删除点 - * @param point + * + * @param point 点 */ - public void removePoint(T point){ + public void removePoint(final T point) { allPoints.remove(point); neighborEdgeMap.remove(point); - neighborEdgeMap.forEach((f,m) -> { + neighborEdgeMap.forEach((f, m) -> { m.remove(point); }); } @Override public String toString() { - StringBuilder builder = new StringBuilder(); - neighborEdgeMap.forEach((from,edgeMap) -> { - edgeMap.forEach((to,edge) -> { + final StringBuilder builder = new StringBuilder(); + neighborEdgeMap.forEach((from, edgeMap) -> { + edgeMap.forEach((to, edge) -> { builder.append(edge); builder.append("\r\n"); }); @@ -79,106 +93,106 @@ public class DirectedWeightGraph { * 基于 SPFA 算法实现 * * @param startPoint 开始节点 - * @throws NegativeRingException 存在负权环路 * @return 最佳路径集合 如果无可触达顶点 返回空 map + * @throws NegativeRingException 存在负权环路 */ - public Map> bestPathMap(T startPoint) throws NegativeRingException{ + public Map> bestPathMap(final T startPoint) throws NegativeRingException { // 全部节点数量 - int pointSize = allPoints.size(); + //final int pointSize = allPoints.size(); // 待访问队列 - LinkedList pointQueue = new LinkedList<>(); + final LinkedList pointQueue = new LinkedList<>(); // 待访问队列中的节点 加速判断 - HashSet inQueuePoints = new HashSet<>(); + final HashSet inQueuePoints = new HashSet<>(); // 最佳路径集合 - HashMap> bestPathMap = new HashMap<>(); + final HashMap> bestPathMap = new HashMap<>(); - Map> map = neighborEdgeMap.get(startPoint); + final Map> map = neighborEdgeMap.get(startPoint); // 无可触达路径 - if(map == null || map.isEmpty()){ + if (map == null || map.isEmpty()) { return new HashMap<>(); } - map.forEach((to,edge) -> { - Path path = new Path<>(edge); + map.forEach((to, edge) -> { + final Path path = new Path<>(edge); bestPathMap.put(to, path); pointQueue.add(to); inQueuePoints.add(to); }); - while (!pointQueue.isEmpty()){ + while (!pointQueue.isEmpty()) { // 当前节点 开始对 currentPoint 进行扩展 - T currentPoint = pointQueue.removeFirst(); + final T currentPoint = pointQueue.removeFirst(); // 到当前节点的最短路径 - Path currentPath = bestPathMap.get(currentPoint); + final Path currentPath = bestPathMap.get(currentPoint); // 标记已出队列 inQueuePoints.remove(currentPoint); - Map> edgeMap = neighborEdgeMap.get(currentPoint); - if(edgeMap == null){ + final Map> edgeMap = neighborEdgeMap.get(currentPoint); + if (edgeMap == null) { continue; } // 扩展当前点的边 - Set>> entrySet = edgeMap.entrySet(); - for (Map.Entry> entry : entrySet) { - T nextPoint = entry.getKey(); - Edge edge = entry.getValue(); + final Set>> entrySet = edgeMap.entrySet(); + for (final Map.Entry> entry : entrySet) { + final T nextPoint = entry.getKey(); + final Edge edge = entry.getValue(); // 不存在路径 第一次访问 将当前路径放置到 bestPathMap 中 - Path oldPath = bestPathMap.get(nextPoint); - if(oldPath == null){ - Path nextPath = currentPath.nextPoint(edge); - bestPathMap.put(nextPoint,nextPath); + final Path oldPath = bestPathMap.get(nextPoint); + if (oldPath == null) { + final Path nextPath = currentPath.nextPoint(edge); + bestPathMap.put(nextPoint, nextPath); // 不在队列里就入队 - if(!inQueuePoints.contains(nextPoint)){ + if (!inQueuePoints.contains(nextPoint)) { inQueuePoints.add(nextPoint); // SLF优化 入队优化 // 每次出队进行判断扩展出的点与队头元素进行判断,若小于进队头,否则入队尾 // 尽可能的让 负环路 上的节点 先进入队列头 - if(pointQueue.isEmpty()){ + if (pointQueue.isEmpty()) { pointQueue.addLast(nextPoint); continue; } - T first = pointQueue.getFirst(); - Path fristPath = bestPathMap.get(first); - if(nextPath.weight < fristPath.weight){ + final T first = pointQueue.getFirst(); + final Path fristPath = bestPathMap.get(first); + if (nextPath.weight < fristPath.weight) { pointQueue.addFirst(nextPoint); - }else { + } else { pointQueue.add(nextPoint); } } continue; } - long newWeight = currentPath.weight + edge.weight; + final long newWeight = currentPath.weight + edge.weight; // 新路径更糟糕 没有优化的必要 - if(newWeight >= oldPath.weight){ + if (newWeight >= oldPath.weight) { continue; } // 更新最佳路径 如果下一跳没有在队列中 将下一跳放到队列里 - Path nextPath = currentPath.nextPoint(edge); - bestPathMap.put(nextPoint,nextPath); + final Path nextPath = currentPath.nextPoint(edge); + bestPathMap.put(nextPoint, nextPath); // 不在队列里就入队 - if(!inQueuePoints.contains(nextPoint)){ + if (!inQueuePoints.contains(nextPoint)) { inQueuePoints.add(nextPoint); // SLF优化 入队优化 // 每次出队进行判断扩展出的点与队头元素进行判断,若小于进队头,否则入队尾 // 尽可能的让 负环路 上的节点 先进入队列头 - if(pointQueue.isEmpty()){ + if (pointQueue.isEmpty()) { pointQueue.addLast(nextPoint); continue; } - T first = pointQueue.getFirst(); - Path fristPath = bestPathMap.get(first); - if(nextPath.weight < fristPath.weight){ + final T first = pointQueue.getFirst(); + final Path fristPath = bestPathMap.get(first); + if (nextPath.weight < fristPath.weight) { pointQueue.addFirst(nextPoint); - }else { + } else { pointQueue.addLast(nextPoint); } } @@ -190,89 +204,112 @@ public class DirectedWeightGraph { /** * 边 - * @param + * + * @param 点类型 */ public static class Edge { // 起始点 - public T fromPoint; + protected T fromPoint; // 目标点 - public T nextPoint; + protected T toPoint; // 权重 - public long weight; + protected long weight; - public Edge(T fromPoint, T nextPoint, long weight) { + /** + * 构造 + */ + public Edge() { + } + + /** + * 构造 + * + * @param fromPoint 起始点 + * @param toPoint 目标点 + * @param weight 权重 + */ + public Edge(final T fromPoint, final T toPoint, final long weight) { this.fromPoint = fromPoint; - this.nextPoint = nextPoint; + this.toPoint = toPoint; this.weight = weight; } @Override public String toString() { - return fromPoint + "->" + nextPoint + "(" + weight + ")"; + return fromPoint + "->" + toPoint + "(" + weight + ")"; } } - public static class Path { - // 开始节点 - public T startPoint; - // 结束节点 - public T endPoint; + /** + * 路径 + * + * @param 点类型 + */ + public static class Path extends Edge { /** * 道路 即依次按照顺序经过的边 */ - public LinkedList> way = new LinkedList<>(); - + private final LinkedList> way = new LinkedList<>(); /** * 已经经过的点 如果 有一个点已经多次经过了 可以判定已经成环 * 当源图是一个非联通图时 或者 开始节点处于图路径中下游时 或者 成环路经过的节点数量较少时 * 使用判断节点经过次数与全部节点数量进行比较会有冗余判断 * 用成环判断 可以加速这种情况 是针对一些特殊的图结构优化了最差情况 */ - public Set passedPoints = new HashSet<>(); + private final Set passedPoints = new HashSet<>(); - // 总权重 - public long weight; + /** + * 构造 + */ + public Path() { + super(); + } - public Path(Edge edge){ - startPoint = edge.fromPoint; - endPoint = edge.nextPoint; + /** + * 构造 + * + * @param edge 边 + */ + public Path(final Edge edge) { + this.fromPoint = edge.fromPoint; + this.toPoint = edge.toPoint; way.add(edge); weight = edge.weight; passedPoints.add(edge.fromPoint); - passedPoints.add(edge.nextPoint); + passedPoints.add(edge.toPoint); } - public Path(){} /** * 生成下一跳 - * @param edge + * + * @param edge 边 + * @return 下一跳 * @throws NegativeRingException 负环路 - * @return */ - public Path nextPoint(Edge edge) throws NegativeRingException { - Path nextPath = new Path<>(); - nextPath.startPoint = startPoint; - nextPath.endPoint = edge.nextPoint; + public Path nextPoint(final Edge edge) throws NegativeRingException { + final Path nextPath = new Path<>(); + nextPath.fromPoint = fromPoint; + nextPath.toPoint = edge.toPoint; nextPath.way.addAll(way); nextPath.way.add(edge); nextPath.weight = weight + edge.weight; nextPath.passedPoints.addAll(passedPoints); // 负环检查 - if(nextPath.passedPoints.contains(edge.nextPoint)){ + if (nextPath.passedPoints.contains(edge.toPoint)) { throw new NegativeRingException("路径:" + nextPath + "存在负环路"); } - nextPath.passedPoints.add(edge.nextPoint); + nextPath.passedPoints.add(edge.toPoint); return nextPath; } @Override public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append(String.format("[%s->%s(%d)] ", startPoint, endPoint, weight)); - for (Edge edge : way) { + final StringBuilder builder = new StringBuilder(); + builder.append(String.format("[%s->%s(%d)] ", fromPoint, toPoint, weight)); + for (final Edge edge : way) { builder.append(edge); builder.append(" "); } @@ -283,8 +320,15 @@ public class DirectedWeightGraph { /** * 负环异常 */ - public static class NegativeRingException extends Exception { - public NegativeRingException(String msg){ + public static class NegativeRingException extends HutoolException { + private static final long serialVersionUID = 1L; + + /** + * 构造 + * + * @param msg 消息 + */ + public NegativeRingException(final String msg) { super(msg); } } diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/bean/copier/Issue3702Test.java b/hutool-core/src/test/java/org/dromara/hutool/core/bean/copier/Issue3702Test.java new file mode 100644 index 000000000..04bd294bb --- /dev/null +++ b/hutool-core/src/test/java/org/dromara/hutool/core/bean/copier/Issue3702Test.java @@ -0,0 +1,41 @@ +package org.dromara.hutool.core.bean.copier; + +import org.dromara.hutool.core.bean.BeanUtil; +import org.dromara.hutool.core.util.ObjUtil; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +/** + * setFieldValueEditor编辑后的值理应继续判断ignoreNullValue + */ +public class Issue3702Test { + @Test + void mapToMapTest() { + final Map map= new HashMap<>(); + map.put("a",""); + map.put("b","b"); + map.put("c","c"); + map.put("d","d"); + + final Map map2= new HashMap<>(); + map2.put("a","a1"); + map2.put("b","b1"); + map2.put("c","c1"); + map2.put("d","d1"); + + final CopyOptions option= CopyOptions.of() + .setIgnoreNullValue(true) + .setIgnoreError(true) + .setFieldEditor((entry)->{ + if(ObjUtil.equals(entry.getValue(), "")){ + entry.setValue(null); + } + return entry; + }); + BeanUtil.copyProperties(map,map2,option); + Assertions.assertEquals("{a=a1, b=b, c=c, d=d}", map2.toString()); + } +} diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/map/DirectedWeightGraphTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/map/DirectedWeightGraphTest.java index 083dd5861..5d5bc42fc 100644 --- a/hutool-core/src/test/java/org/dromara/hutool/core/map/DirectedWeightGraphTest.java +++ b/hutool-core/src/test/java/org/dromara/hutool/core/map/DirectedWeightGraphTest.java @@ -1,20 +1,20 @@ package org.dromara.hutool.core.map; import org.dromara.hutool.core.map.multi.DirectedWeightGraph; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import java.util.List; import java.util.Map; /** * @author newshiJ - * @date 2024/8/14 17:07 */ public class DirectedWeightGraphTest { @Test - public void test1(){ - DirectedWeightGraph graph = new DirectedWeightGraph<>(); + @Disabled + public void test1() { + final DirectedWeightGraph graph = new DirectedWeightGraph<>(); graph.putEdge("A", "B", 14); graph.putEdge("A", "C", 8); graph.putEdge("A", "D", 12); @@ -44,10 +44,10 @@ public class DirectedWeightGraphTest { Map> map = null; try { map = graph.bestPathMap("A"); - map.forEach((k,v) -> { + map.forEach((k, v) -> { System.out.println(v); }); - } catch (DirectedWeightGraph.NegativeRingException e) { + } catch (final DirectedWeightGraph.NegativeRingException e) { e.printStackTrace(); }