修复CopyOptions.setFieldValueEditor后生成null值setIgnoreNullValue无效问题\

This commit is contained in:
Looly 2024-08-22 12:05:18 +08:00
parent 8bc2677267
commit 41dc63b580
4 changed files with 187 additions and 101 deletions

View File

@ -66,10 +66,6 @@ public class MapToMapCopier extends AbsCopier<Map, Map> {
return; return;
} }
sValue = entry.getValue(); sValue = entry.getValue();
// 忽略空值
if (copyOptions.ignoreNullValue && sValue == null) {
return;
}
final Object targetValue = target.get(sKey); final Object targetValue = target.get(sKey);
// 非覆盖模式下如果目标值存在则跳过 // 非覆盖模式下如果目标值存在则跳过
@ -83,6 +79,11 @@ public class MapToMapCopier extends AbsCopier<Map, Map> {
sValue = this.copyOptions.convertField(typeArguments[1], sValue); sValue = this.copyOptions.convertField(typeArguments[1], sValue);
} }
// 忽略空值
if (copyOptions.ignoreNullValue && sValue == null) {
return;
}
// 目标赋值 // 目标赋值
target.put(sKey, sValue); target.put(sKey, sValue);
}); });

View File

@ -1,13 +1,15 @@
package org.dromara.hutool.core.map.multi; package org.dromara.hutool.core.map.multi;
import org.dromara.hutool.core.exception.HutoolException;
import java.util.*; import java.util.*;
/** /**
* 权重有向图 * 权重有向图
* 基于 SPFA 算法实现 可以处理负边 可以进行负权环路检查 * 基于 SPFA 算法实现 可以处理负边 可以进行负权环路检查
* *
* @param <T> 点对象类型
* @author NewshiJ * @author NewshiJ
* @date 2024/8/16 09:01
*/ */
public class DirectedWeightGraph<T> { public class DirectedWeightGraph<T> {
@ -15,35 +17,46 @@ public class DirectedWeightGraph<T> {
private final Set<T> allPoints = new HashSet<>(); private final Set<T> allPoints = new HashSet<>();
// 邻接边 // 邻接边
private final Map<T, Map<T,Edge<T>>> neighborEdgeMap = new HashMap<>(); private final Map<T, Map<T, Edge<T>>> neighborEdgeMap = new HashMap<>();
/**
* 获取全部点
*
* @return 全部点
*/
public Set<T> getAllPoints() {
return allPoints;
}
/** /**
* 添加边 * 添加边
*
* @param fromPoint 开始点 * @param fromPoint 开始点
* @param nextPoint 结束点 * @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(fromPoint);
allPoints.add(nextPoint); allPoints.add(nextPoint);
Map<T, Edge<T>> nextPointMap = neighborEdgeMap.computeIfAbsent(fromPoint, k -> new HashMap<>()); final Map<T, Edge<T>> nextPointMap = neighborEdgeMap.computeIfAbsent(fromPoint, k -> new HashMap<>());
nextPointMap.put(nextPoint, new Edge<>(fromPoint, nextPoint, weight)); nextPointMap.put(nextPoint, new Edge<>(fromPoint, nextPoint, weight));
} }
/** /**
* 删除边 * 删除边
* @param fromPoint *
* @param nextPoint * @param fromPoint 开始点
* @param nextPoint 结束点
*/ */
public void removeEdge(T fromPoint, T nextPoint) { public void removeEdge(final T fromPoint, final T nextPoint) {
Map<T, Edge<T>> nextPointMap = neighborEdgeMap.computeIfAbsent(fromPoint, k -> new HashMap<>()); final Map<T, Edge<T>> nextPointMap = neighborEdgeMap.computeIfAbsent(fromPoint, k -> new HashMap<>());
nextPointMap.remove(nextPoint); nextPointMap.remove(nextPoint);
// 重新计算 所有点位 // 重新计算 所有点位
allPoints.clear(); allPoints.clear();
neighborEdgeMap.forEach((f,m) -> { neighborEdgeMap.forEach((f, m) -> {
allPoints.add(f); allPoints.add(f);
m.forEach((t,e) -> { m.forEach((t, e) -> {
allPoints.add(t); allPoints.add(t);
}); });
}); });
@ -51,21 +64,22 @@ public class DirectedWeightGraph<T> {
/** /**
* 删除点 * 删除点
* @param point *
* @param point
*/ */
public void removePoint(T point){ public void removePoint(final T point) {
allPoints.remove(point); allPoints.remove(point);
neighborEdgeMap.remove(point); neighborEdgeMap.remove(point);
neighborEdgeMap.forEach((f,m) -> { neighborEdgeMap.forEach((f, m) -> {
m.remove(point); m.remove(point);
}); });
} }
@Override @Override
public String toString() { public String toString() {
StringBuilder builder = new StringBuilder(); final StringBuilder builder = new StringBuilder();
neighborEdgeMap.forEach((from,edgeMap) -> { neighborEdgeMap.forEach((from, edgeMap) -> {
edgeMap.forEach((to,edge) -> { edgeMap.forEach((to, edge) -> {
builder.append(edge); builder.append(edge);
builder.append("\r\n"); builder.append("\r\n");
}); });
@ -79,106 +93,106 @@ public class DirectedWeightGraph<T> {
* 基于 SPFA 算法实现 * 基于 SPFA 算法实现
* *
* @param startPoint 开始节点 * @param startPoint 开始节点
* @throws NegativeRingException 存在负权环路
* @return 最佳路径集合 如果无可触达顶点 返回空 map * @return 最佳路径集合 如果无可触达顶点 返回空 map
* @throws NegativeRingException 存在负权环路
*/ */
public Map<T, Path<T>> bestPathMap(T startPoint) throws NegativeRingException{ public Map<T, Path<T>> bestPathMap(final T startPoint) throws NegativeRingException {
// 全部节点数量 // 全部节点数量
int pointSize = allPoints.size(); //final int pointSize = allPoints.size();
// 待访问队列 // 待访问队列
LinkedList<T> pointQueue = new LinkedList<>(); final LinkedList<T> pointQueue = new LinkedList<>();
// 待访问队列中的节点 加速判断 // 待访问队列中的节点 加速判断
HashSet<T> inQueuePoints = new HashSet<>(); final HashSet<T> inQueuePoints = new HashSet<>();
// 最佳路径集合 // 最佳路径集合
HashMap<T, Path<T>> bestPathMap = new HashMap<>(); final HashMap<T, Path<T>> bestPathMap = new HashMap<>();
Map<T, Edge<T>> map = neighborEdgeMap.get(startPoint); final Map<T, Edge<T>> map = neighborEdgeMap.get(startPoint);
// 无可触达路径 // 无可触达路径
if(map == null || map.isEmpty()){ if (map == null || map.isEmpty()) {
return new HashMap<>(); return new HashMap<>();
} }
map.forEach((to,edge) -> { map.forEach((to, edge) -> {
Path<T> path = new Path<>(edge); final Path<T> path = new Path<>(edge);
bestPathMap.put(to, path); bestPathMap.put(to, path);
pointQueue.add(to); pointQueue.add(to);
inQueuePoints.add(to); inQueuePoints.add(to);
}); });
while (!pointQueue.isEmpty()){ while (!pointQueue.isEmpty()) {
// 当前节点 开始对 currentPoint 进行扩展 // 当前节点 开始对 currentPoint 进行扩展
T currentPoint = pointQueue.removeFirst(); final T currentPoint = pointQueue.removeFirst();
// 到当前节点的最短路径 // 到当前节点的最短路径
Path<T> currentPath = bestPathMap.get(currentPoint); final Path<T> currentPath = bestPathMap.get(currentPoint);
// 标记已出队列 // 标记已出队列
inQueuePoints.remove(currentPoint); inQueuePoints.remove(currentPoint);
Map<T, Edge<T>> edgeMap = neighborEdgeMap.get(currentPoint); final Map<T, Edge<T>> edgeMap = neighborEdgeMap.get(currentPoint);
if(edgeMap == null){ if (edgeMap == null) {
continue; continue;
} }
// 扩展当前点的边 // 扩展当前点的边
Set<Map.Entry<T, Edge<T>>> entrySet = edgeMap.entrySet(); final Set<Map.Entry<T, Edge<T>>> entrySet = edgeMap.entrySet();
for (Map.Entry<T, Edge<T>> entry : entrySet) { for (final Map.Entry<T, Edge<T>> entry : entrySet) {
T nextPoint = entry.getKey(); final T nextPoint = entry.getKey();
Edge<T> edge = entry.getValue(); final Edge<T> edge = entry.getValue();
// 不存在路径 第一次访问 将当前路径放置到 bestPathMap // 不存在路径 第一次访问 将当前路径放置到 bestPathMap
Path<T> oldPath = bestPathMap.get(nextPoint); final Path<T> oldPath = bestPathMap.get(nextPoint);
if(oldPath == null){ if (oldPath == null) {
Path<T> nextPath = currentPath.nextPoint(edge); final Path<T> nextPath = currentPath.nextPoint(edge);
bestPathMap.put(nextPoint,nextPath); bestPathMap.put(nextPoint, nextPath);
// 不在队列里就入队 // 不在队列里就入队
if(!inQueuePoints.contains(nextPoint)){ if (!inQueuePoints.contains(nextPoint)) {
inQueuePoints.add(nextPoint); inQueuePoints.add(nextPoint);
// SLF优化 入队优化 // SLF优化 入队优化
// 每次出队进行判断扩展出的点与队头元素进行判断若小于进队头否则入队尾 // 每次出队进行判断扩展出的点与队头元素进行判断若小于进队头否则入队尾
// 尽可能的让 负环路 上的节点 先进入队列头 // 尽可能的让 负环路 上的节点 先进入队列头
if(pointQueue.isEmpty()){ if (pointQueue.isEmpty()) {
pointQueue.addLast(nextPoint); pointQueue.addLast(nextPoint);
continue; continue;
} }
T first = pointQueue.getFirst(); final T first = pointQueue.getFirst();
Path<T> fristPath = bestPathMap.get(first); final Path<T> fristPath = bestPathMap.get(first);
if(nextPath.weight < fristPath.weight){ if (nextPath.weight < fristPath.weight) {
pointQueue.addFirst(nextPoint); pointQueue.addFirst(nextPoint);
}else { } else {
pointQueue.add(nextPoint); pointQueue.add(nextPoint);
} }
} }
continue; continue;
} }
long newWeight = currentPath.weight + edge.weight; final long newWeight = currentPath.weight + edge.weight;
// 新路径更糟糕 没有优化的必要 // 新路径更糟糕 没有优化的必要
if(newWeight >= oldPath.weight){ if (newWeight >= oldPath.weight) {
continue; continue;
} }
// 更新最佳路径 如果下一跳没有在队列中 将下一跳放到队列里 // 更新最佳路径 如果下一跳没有在队列中 将下一跳放到队列里
Path<T> nextPath = currentPath.nextPoint(edge); final Path<T> nextPath = currentPath.nextPoint(edge);
bestPathMap.put(nextPoint,nextPath); bestPathMap.put(nextPoint, nextPath);
// 不在队列里就入队 // 不在队列里就入队
if(!inQueuePoints.contains(nextPoint)){ if (!inQueuePoints.contains(nextPoint)) {
inQueuePoints.add(nextPoint); inQueuePoints.add(nextPoint);
// SLF优化 入队优化 // SLF优化 入队优化
// 每次出队进行判断扩展出的点与队头元素进行判断若小于进队头否则入队尾 // 每次出队进行判断扩展出的点与队头元素进行判断若小于进队头否则入队尾
// 尽可能的让 负环路 上的节点 先进入队列头 // 尽可能的让 负环路 上的节点 先进入队列头
if(pointQueue.isEmpty()){ if (pointQueue.isEmpty()) {
pointQueue.addLast(nextPoint); pointQueue.addLast(nextPoint);
continue; continue;
} }
T first = pointQueue.getFirst(); final T first = pointQueue.getFirst();
Path<T> fristPath = bestPathMap.get(first); final Path<T> fristPath = bestPathMap.get(first);
if(nextPath.weight < fristPath.weight){ if (nextPath.weight < fristPath.weight) {
pointQueue.addFirst(nextPoint); pointQueue.addFirst(nextPoint);
}else { } else {
pointQueue.addLast(nextPoint); pointQueue.addLast(nextPoint);
} }
} }
@ -190,89 +204,112 @@ public class DirectedWeightGraph<T> {
/** /**
* *
* @param <T> *
* @param <T> 点类型
*/ */
public static class Edge<T> { public static class Edge<T> {
// 起始点 // 起始点
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.fromPoint = fromPoint;
this.nextPoint = nextPoint; this.toPoint = toPoint;
this.weight = weight; this.weight = weight;
} }
@Override @Override
public String toString() { public String toString() {
return fromPoint + "->" + nextPoint + "(" + weight + ")"; return fromPoint + "->" + toPoint + "(" + weight + ")";
} }
} }
public static class Path<T> { /**
// 开始节点 * 路径
public T startPoint; *
// 结束节点 * @param <T> 点类型
public T endPoint; */
public static class Path<T> extends Edge<T> {
/** /**
* 道路 即依次按照顺序经过的边 * 道路 即依次按照顺序经过的边
*/ */
public LinkedList<Edge<T>> way = new LinkedList<>(); private final LinkedList<Edge<T>> way = new LinkedList<>();
/** /**
* 已经经过的点 如果 有一个点已经多次经过了 可以判定已经成环 * 已经经过的点 如果 有一个点已经多次经过了 可以判定已经成环
* 当源图是一个非联通图时 或者 开始节点处于图路径中下游时 或者 成环路经过的节点数量较少时 * 当源图是一个非联通图时 或者 开始节点处于图路径中下游时 或者 成环路经过的节点数量较少时
* 使用判断节点经过次数与全部节点数量进行比较会有冗余判断 * 使用判断节点经过次数与全部节点数量进行比较会有冗余判断
* 用成环判断 可以加速这种情况 是针对一些特殊的图结构优化了最差情况 * 用成环判断 可以加速这种情况 是针对一些特殊的图结构优化了最差情况
*/ */
public Set<T> passedPoints = new HashSet<>(); private final Set<T> passedPoints = new HashSet<>();
// 总权重 /**
public long weight; * 构造
*/
public Path() {
super();
}
public Path(Edge<T> edge){ /**
startPoint = edge.fromPoint; * 构造
endPoint = edge.nextPoint; *
* @param edge
*/
public Path(final Edge<T> edge) {
this.fromPoint = edge.fromPoint;
this.toPoint = edge.toPoint;
way.add(edge); way.add(edge);
weight = edge.weight; weight = edge.weight;
passedPoints.add(edge.fromPoint); passedPoints.add(edge.fromPoint);
passedPoints.add(edge.nextPoint); passedPoints.add(edge.toPoint);
} }
public Path(){}
/** /**
* 生成下一跳 * 生成下一跳
* @param edge *
* @param edge
* @return 下一跳
* @throws NegativeRingException 负环路 * @throws NegativeRingException 负环路
* @return
*/ */
public Path<T> nextPoint(Edge<T> edge) throws NegativeRingException { public Path<T> nextPoint(final Edge<T> edge) throws NegativeRingException {
Path<T> nextPath = new Path<>(); final Path<T> nextPath = new Path<>();
nextPath.startPoint = startPoint; nextPath.fromPoint = fromPoint;
nextPath.endPoint = edge.nextPoint; nextPath.toPoint = edge.toPoint;
nextPath.way.addAll(way); nextPath.way.addAll(way);
nextPath.way.add(edge); nextPath.way.add(edge);
nextPath.weight = weight + edge.weight; nextPath.weight = weight + edge.weight;
nextPath.passedPoints.addAll(passedPoints); nextPath.passedPoints.addAll(passedPoints);
// 负环检查 // 负环检查
if(nextPath.passedPoints.contains(edge.nextPoint)){ if (nextPath.passedPoints.contains(edge.toPoint)) {
throw new NegativeRingException("路径:" + nextPath + "存在负环路"); throw new NegativeRingException("路径:" + nextPath + "存在负环路");
} }
nextPath.passedPoints.add(edge.nextPoint); nextPath.passedPoints.add(edge.toPoint);
return nextPath; return nextPath;
} }
@Override @Override
public String toString() { public String toString() {
StringBuilder builder = new StringBuilder(); final StringBuilder builder = new StringBuilder();
builder.append(String.format("[%s->%s(%d)] ", startPoint, endPoint, weight)); builder.append(String.format("[%s->%s(%d)] ", fromPoint, toPoint, weight));
for (Edge<T> edge : way) { for (final Edge<T> edge : way) {
builder.append(edge); builder.append(edge);
builder.append(" "); builder.append(" ");
} }
@ -283,8 +320,15 @@ public class DirectedWeightGraph<T> {
/** /**
* 负环异常 * 负环异常
*/ */
public static class NegativeRingException extends Exception { public static class NegativeRingException extends HutoolException {
public NegativeRingException(String msg){ private static final long serialVersionUID = 1L;
/**
* 构造
*
* @param msg 消息
*/
public NegativeRingException(final String msg) {
super(msg); super(msg);
} }
} }

View File

@ -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<String,String> map= new HashMap<>();
map.put("a","");
map.put("b","b");
map.put("c","c");
map.put("d","d");
final Map<String,String> 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());
}
}

View File

@ -1,20 +1,20 @@
package org.dromara.hutool.core.map; package org.dromara.hutool.core.map;
import org.dromara.hutool.core.map.multi.DirectedWeightGraph; import org.dromara.hutool.core.map.multi.DirectedWeightGraph;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
* @author newshiJ * @author newshiJ
* @date 2024/8/14 17:07
*/ */
public class DirectedWeightGraphTest { public class DirectedWeightGraphTest {
@Test @Test
public void test1(){ @Disabled
DirectedWeightGraph<String> graph = new DirectedWeightGraph<>(); public void test1() {
final DirectedWeightGraph<String> graph = new DirectedWeightGraph<>();
graph.putEdge("A", "B", 14); graph.putEdge("A", "B", 14);
graph.putEdge("A", "C", 8); graph.putEdge("A", "C", 8);
graph.putEdge("A", "D", 12); graph.putEdge("A", "D", 12);
@ -44,10 +44,10 @@ public class DirectedWeightGraphTest {
Map<String, DirectedWeightGraph.Path<String>> map = null; Map<String, DirectedWeightGraph.Path<String>> map = null;
try { try {
map = graph.bestPathMap("A"); map = graph.bestPathMap("A");
map.forEach((k,v) -> { map.forEach((k, v) -> {
System.out.println(v); System.out.println(v);
}); });
} catch (DirectedWeightGraph.NegativeRingException e) { } catch (final DirectedWeightGraph.NegativeRingException e) {
e.printStackTrace(); e.printStackTrace();
} }