From 89ff40fec125d8e2c0017ed2a82ed5e55cda6146 Mon Sep 17 00:00:00 2001 From: wcnnkh <416078075@qq.com> Date: Sun, 30 Nov 2025 20:45:40 +0800 Subject: [PATCH 01/16] Upgrade version to 1.0.1 --- aop/pom.xml | 2 +- beans/pom.xml | 2 +- core/pom.xml | 2 +- dom/pom.xml | 2 +- jdbc/pom.xml | 2 +- json/pom.xml | 2 +- messaging/pom.xml | 2 +- pom.xml | 22 +++++++++++----------- slf4j/pom.xml | 2 +- xml/pom.xml | 2 +- 10 files changed, 20 insertions(+), 20 deletions(-) diff --git a/aop/pom.xml b/aop/pom.xml index f9a2d41f9..35221ccd0 100644 --- a/aop/pom.xml +++ b/aop/pom.xml @@ -3,7 +3,7 @@ run.soeasy.framework framework - 1.0.0 + 1.0.1 aop aop diff --git a/beans/pom.xml b/beans/pom.xml index 118a8218c..774b1e1f9 100644 --- a/beans/pom.xml +++ b/beans/pom.xml @@ -3,7 +3,7 @@ run.soeasy.framework framework - 1.0.0 + 1.0.1 beans beans diff --git a/core/pom.xml b/core/pom.xml index aa22d0ce8..19aaa9385 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -4,7 +4,7 @@ run.soeasy.framework framework - 1.0.0 + 1.0.1 core core diff --git a/dom/pom.xml b/dom/pom.xml index e7d7054a5..649aca9b6 100644 --- a/dom/pom.xml +++ b/dom/pom.xml @@ -3,7 +3,7 @@ run.soeasy.framework framework - 1.0.0 + 1.0.1 dom dom diff --git a/jdbc/pom.xml b/jdbc/pom.xml index 6db3f881e..77fce3b88 100644 --- a/jdbc/pom.xml +++ b/jdbc/pom.xml @@ -3,7 +3,7 @@ run.soeasy.framework framework - 1.0.0 + 1.0.1 jdbc jdbc diff --git a/json/pom.xml b/json/pom.xml index dcef92ea2..74e4908dc 100644 --- a/json/pom.xml +++ b/json/pom.xml @@ -3,7 +3,7 @@ run.soeasy.framework framework - 1.0.0 + 1.0.1 json json diff --git a/messaging/pom.xml b/messaging/pom.xml index 964c97153..7db0d5178 100644 --- a/messaging/pom.xml +++ b/messaging/pom.xml @@ -3,7 +3,7 @@ run.soeasy.framework framework - 1.0.0 + 1.0.1 messaging messaging diff --git a/pom.xml b/pom.xml index 254bf84d3..ea8d4c17d 100644 --- a/pom.xml +++ b/pom.xml @@ -5,8 +5,8 @@ - - 1.0.0 + + 1.0.1 pom framework 为自定义框架提供抽象和基础实现 @@ -65,47 +65,47 @@ run.soeasy.framework core - 1.0.0 + 1.0.1 run.soeasy.framework beans - 1.0.0 + 1.0.1 run.soeasy.framework jdbc - 1.0.0 + 1.0.1 run.soeasy.framework json - 1.0.0 + 1.0.1 run.soeasy.framework messaging - 1.0.0 + 1.0.1 run.soeasy.framework slf4j - 1.0.0 + 1.0.1 run.soeasy.framework dom - 1.0.0 + 1.0.1 run.soeasy.framework aop - 1.0.0 + 1.0.1 run.soeasy.framework xml - 1.0.0 + 1.0.1 diff --git a/slf4j/pom.xml b/slf4j/pom.xml index 6fdbe518b..66ab363c3 100644 --- a/slf4j/pom.xml +++ b/slf4j/pom.xml @@ -3,7 +3,7 @@ run.soeasy.framework framework - 1.0.0 + 1.0.1 slf4j slf4j diff --git a/xml/pom.xml b/xml/pom.xml index 712f41478..98ab3e32d 100644 --- a/xml/pom.xml +++ b/xml/pom.xml @@ -3,7 +3,7 @@ run.soeasy.framework framework - 1.0.0 + 1.0.1 xml xml From e5e19b29559bc1cadbcbefd1855b3f9147d69c7a Mon Sep 17 00:00:00 2001 From: wcnnkh <416078075@qq.com> Date: Tue, 2 Dec 2025 11:39:42 +0800 Subject: [PATCH 02/16] =?UTF-8?q?=E4=BC=98=E5=8C=96offset=20paging?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../framework/core/page/OffsetPaging.java | 251 ++++++++++++------ 1 file changed, 177 insertions(+), 74 deletions(-) diff --git a/core/src/main/java/run/soeasy/framework/core/page/OffsetPaging.java b/core/src/main/java/run/soeasy/framework/core/page/OffsetPaging.java index b95e26ab6..afacf2200 100644 --- a/core/src/main/java/run/soeasy/framework/core/page/OffsetPaging.java +++ b/core/src/main/java/run/soeasy/framework/core/page/OffsetPaging.java @@ -1,94 +1,197 @@ package run.soeasy.framework.core.page; +import java.util.Collection; import java.util.List; import lombok.NonNull; +import run.soeasy.framework.core.Assert; +import run.soeasy.framework.core.collection.CollectionUtils; import run.soeasy.framework.core.collection.Listable; /** - * 使用偏移量进行分页 - * 基于偏移量(offset)和每页大小(pageSize)实现的分页机制 + * 使用偏移量进行分页 基于偏移量(offset)和每页大小(pageSize)实现的分页机制 * * @author soeasy.run * * @param 分页内容的元素类型 */ public class OffsetPaging extends CursorPaging { - /** - * 在内存中对list进行分页 - * - * @param offset 偏移量,从0开始 - * @param pageSize 每页大小 - * @param elements 原始数据列表 - */ - public OffsetPaging(long offset, int pageSize, List elements) { - this(elements.size(), offset, pageSize, (cursorId, length) -> { - int fromIndex = Math.toIntExact(cursorId); - if (fromIndex >= elements.size()) { - return Listable.empty(); - } - List list = elements.subList(fromIndex, Math.min(fromIndex + length, elements.size())); - return Listable.forCollection(list); - }); - } + /** + * 在内存中对list进行分页 + * + * @param offset 偏移量,从0开始 + * @param pageSize 每页大小 + * @param elements 原始数据列表 + */ + public OffsetPaging(long offset, int pageSize, List elements) { + this(elements.size(), offset, pageSize, (cursorId, length) -> { + int fromIndex = Math.toIntExact(cursorId); + if (fromIndex >= elements.size()) { + return Listable.empty(); + } + List list = elements.subList(fromIndex, Math.min(Math.addExact(fromIndex, length), elements.size())); + return Listable.forCollection(list); + }); + } - /** - * 未知数量的构造 - * - * @param offset 偏移量,从0开始 - * @param pageSize 每页大小 - * @param pagingQuery 分页查询器,不可为null - */ - public OffsetPaging(long offset, int pageSize, @NonNull PagingQuery> pagingQuery) { - super(offset, pageSize, (cursorId, length) -> { - Listable listable = pagingQuery.query(cursorId, length); - return new Cursor<>(cursorId, listable, listable.hasElements() ? (cursorId + length) : null); - }); - } + /** + * 未知数量的构造 + * + * @param offset 偏移量,从0开始 + * @param pageSize 每页大小 + * @param pagingQuery 分页查询器,不可为null + */ + public OffsetPaging(long offset, int pageSize, @NonNull PagingQuery> pagingQuery) { + super(offset, pageSize, (cursorId, length) -> { + Listable listable = pagingQuery.query(cursorId, length); + long nextCursorId = Math.addExact(cursorId, length);// 应该给异常还是返回没有下一页? + return new Cursor<>(cursorId, listable, listable.hasElements() ? nextCursorId : null); + }); + } - /** - * 已知总数的构造 - * - * @param total 总记录数 - * @param offset 偏移量,从0开始 - * @param pageSize 每页大小 - * @param pagingQuery 分页查询器,不可为null - */ - public OffsetPaging(long total, long offset, int pageSize, @NonNull PagingQuery> pagingQuery) { - super(total, offset, pageSize, (cursorId, length) -> { - long nextCursorId = cursorId + length; - return new Cursor<>(cursorId, pagingQuery.query(cursorId, length), - nextCursorId < total ? nextCursorId : null); - }); - } + /** + * 已知总数的构造 + * + * @param total 总记录数 + * @param offset 偏移量,从0开始 + * @param pageSize 每页大小 + * @param pagingQuery 分页查询器,不可为null + */ + public OffsetPaging(long total, long offset, int pageSize, @NonNull PagingQuery> pagingQuery) { + super(total, offset, pageSize, (cursorId, length) -> { + long nextCursorId = Math.addExact(cursorId, length); + return new Cursor<>(cursorId, pagingQuery.query(cursorId, length), + nextCursorId < total ? nextCursorId : null); + }); + } - /** - * 获取当前页码 - * - * @return 当前页码,从1开始 - */ - public final long getPageNumber() { - return (getCursorId() / getPageSize()) + 1; - } + /** + * 获取页码 + * + * @param offset 偏移量, 从0开始 + * @param pageSize 每页大小, 不能小于0 + * @return 从1开始 + */ + public static long getPageNumber(long offset, long pageSize) { + Assert.isTrue(offset >= 0, "Offset must be greater than or equal to 0"); + Assert.isTrue(pageSize > 0, "PageSize must be greater than to 0"); + return Math.addExact(offset / pageSize, 1); + } - /** - * 跳转到指定页码 - * - * @param pageNumber 目标页码,从1开始 - * @return 新的OffsetPaging实例 - */ - public final OffsetPaging jumpToPage(long pageNumber) { - return jumpToPage(pageNumber, getPageSize()); - } + /** + * 获取偏移量 + * + * @param pageNumber 页码,从1开始 + * @param pageSize 每页大小, 不能小于0 + * @return + */ + public static long getOffset(long pageNumber, long pageSize) { + Assert.isTrue(pageNumber > 0, "PageNumber must be greater than to 0"); + Assert.isTrue(pageSize > 0, "PageSize must be greater than to 0"); + return Math.multiplyExact((pageNumber - 1), pageSize); + } - /** - * 跳转到指定页码并指定每页大小 - * - * @param pageNumber 目标页码,从1开始 - * @param pageSize 每页大小 - * @return 新的OffsetPaging实例 - */ - public OffsetPaging jumpToPage(long pageNumber, int pageSize) { - return new OffsetPaging<>(getTotal(), pageNumber * pageSize, pageSize, this::query); - } + /** + * 基于偏移量创建OffsetPaging实例(未知总条数) + * + * @param 分页元素类型 + * @param offset 偏移量,从0开始 + * @param pageSize 每页大小,需大于0 + * @param offsetQuery 偏移量查询器,入参为偏移量和每页大小,返回对应数据集 + * @return 未知总条数的OffsetPaging实例 + */ + public static OffsetPaging of(long offset, int pageSize, + PagingQuery> offsetQuery) { + return new OffsetPaging<>(offset, pageSize, (o, limit) -> { + Collection elements = offsetQuery.query(o, limit); + if (CollectionUtils.isEmpty(elements)) { + return Listable.empty(); + } + return Listable.forCollection(elements); + }); + } + + /** + * 基于页码创建OffsetPaging实例(未知总条数) + * + * @param 分页元素类型 + * @param pageNumber 页码,从1开始 + * @param pageSize 每页大小,需大于0 + * @param pageQuery 页码查询器,入参为页码和每页大小,返回对应数据集 + * @return 未知总条数的OffsetPaging实例 + */ + public static OffsetPaging ofUnknownPageNumber(long pageNumber, int pageSize, + PagingQuery> pageQuery) { + return of(getOffset(pageNumber, pageSize), pageSize, (offset, limit) -> { + return pageQuery.query(getPageNumber(offset, limit), pageSize); + }); + } + + /** + * 基于偏移量和总条数创建OffsetPaging实例(已知总条数) + * + * @param 分页元素类型 + * @param total 总记录数,需大于等于0 + * @param offset 偏移量,从0开始 + * @param pageSize 每页大小,需大于0 + * @param offsetQuery 偏移量查询器,入参为偏移量和每页大小,返回对应数据集 + * @return 已知总条数的OffsetPaging实例 + */ + public static OffsetPaging of(long total, long offset, int pageSize, + PagingQuery> offsetQuery) { + return new OffsetPaging<>(total, offset, pageSize, (o, limit) -> { + Collection elements = offsetQuery.query(o, limit); + if (CollectionUtils.isEmpty(elements)) { + return Listable.empty(); + } + return Listable.forCollection(elements); + }); + } + + /** + * 基于页码和总条数创建OffsetPaging实例(已知总条数) + * + * @param 分页元素类型 + * @param total 总记录数,需大于等于0 + * @param pageNumber 页码,从1开始 + * @param pageSize 每页大小,需大于0 + * @param pageQuery 页码查询器,入参为页码和每页大小,返回对应数据集 + * @return 已知总条数的OffsetPaging实例 + */ + public static OffsetPaging ofPageNumber(long total, long pageNumber, int pageSize, + PagingQuery> pageQuery) { + return of(total, getOffset(pageNumber, pageSize), pageSize, (offset, limit) -> { + return pageQuery.query(getPageNumber(offset, limit), pageSize); + }); + } + + /** + * 获取当前页码 + * + * @return 当前页码,从1开始 + */ + public final long getPageNumber() { + return getPageNumber(getCursorId(), getPageSize()); + } + + /** + * 跳转到指定页码 + * + * @param pageNumber 目标页码,从1开始 + * @return 新的OffsetPaging实例 + */ + public final OffsetPaging jumpToPage(long pageNumber) { + return jumpToPage(pageNumber, getPageSize()); + } + + /** + * 跳转到指定页码并指定每页大小 + * + * @param pageNumber 目标页码,从1开始 + * @param pageSize 每页大小 + * @return 新的OffsetPaging实例 + */ + public OffsetPaging jumpToPage(long pageNumber, int pageSize) { + return new OffsetPaging<>(getTotal(), getOffset(pageNumber, pageSize), pageSize, this::query); + } } \ No newline at end of file From 4bdb3541fa304998c0a5ad930c5284c59b11ecb5 Mon Sep 17 00:00:00 2001 From: wcnnkh <416078075@qq.com> Date: Tue, 2 Dec 2025 14:54:27 +0800 Subject: [PATCH 03/16] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=88=86=E9=A1=B5?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../soeasy/framework/core/page/Cursor.java | 23 +-- .../framework/core/page/CursorPaging.java | 181 ++++++++++-------- .../framework/core/page/OffsetPaging.java | 166 ++++++---------- .../framework/core/page/PageIterator.java | 7 +- .../soeasy/framework/core/page/Pageable.java | 62 +++--- .../soeasy/framework/core/page/Paging.java | 129 +++++++------ .../framework/core/type/ClassUtils.java | 25 +-- .../framework/core/page/OffsetPagingTest.java | 2 +- .../framework/core/page/PageIteratorTest.java | 2 +- 9 files changed, 289 insertions(+), 308 deletions(-) diff --git a/core/src/main/java/run/soeasy/framework/core/page/Cursor.java b/core/src/main/java/run/soeasy/framework/core/page/Cursor.java index b5ba75da6..0a91d2da9 100644 --- a/core/src/main/java/run/soeasy/framework/core/page/Cursor.java +++ b/core/src/main/java/run/soeasy/framework/core/page/Cursor.java @@ -8,8 +8,7 @@ import run.soeasy.framework.core.collection.ListableWrapper; /** - * 游标实现类 - * 封装分页数据及游标信息,实现序列化以便跨进程传递 + * 游标实现类 封装分页数据及游标信息,实现序列化以便跨进程传递 * * @author soeasy.run * @@ -18,13 +17,15 @@ */ @Data public class Cursor implements Pageable, ListableWrapper>, Serializable { - private static final long serialVersionUID = 1L; - - /** 当前页的游标ID,用于标识数据起始位置 */ - private final K cursorId; - /** 分页数据的源集合,不可为null */ - @NonNull - private final Listable source; - /** 下一页的游标ID,null表示无下一页 */ - private final K nextCursorId; + private static final long serialVersionUID = 1L; + + /** 当前页的游标ID,用于标识数据起始位置 */ + private final K cursorId; + /** 分页数据的源集合,不可为null */ + @NonNull + private final Listable source; + /** 下一页的游标ID,null表示无下一页 */ + private final K nextCursorId; + + private final Long total; } \ No newline at end of file diff --git a/core/src/main/java/run/soeasy/framework/core/page/CursorPaging.java b/core/src/main/java/run/soeasy/framework/core/page/CursorPaging.java index 205fee445..db953af05 100644 --- a/core/src/main/java/run/soeasy/framework/core/page/CursorPaging.java +++ b/core/src/main/java/run/soeasy/framework/core/page/CursorPaging.java @@ -6,8 +6,7 @@ import run.soeasy.framework.core.collection.Listable; /** - * 基于游标的分页实现 - * 使用懒加载机制,首次访问数据时执行实际查询 + * 基于游标的分页实现 使用懒加载机制,首次访问数据时执行实际查询 * * @author soeasy.run * @@ -16,97 +15,111 @@ */ @RequiredArgsConstructor public class CursorPaging implements Paging { - /** 当前页数据,使用双重检查锁定机制延迟初始化 */ - private volatile Pageable currentPage; - /** 总记录数,null表示总数未知,需遍历所有页计算 */ - private final Long total; - /** 当前页的游标ID */ - private final K cursorId; - /** 每页大小,0表示使用默认值 */ - private final int pageSize; - /** 分页查询器,不可为null */ - @NonNull - private final PagingQuery> pagingQuery; + /** 当前页数据,使用双重检查锁定机制延迟初始化 */ + private volatile Pageable currentPage; + /** 总记录数,null表示总数未知,需遍历所有页计算 */ + private final Long total; + /** 当前页的游标ID */ + private final K cursorId; + /** 每页大小,0表示使用默认值 */ + private final int pageSize; + /** 分页查询器,不可为null */ + @NonNull + private final PagingQuery> pagingQuery; - /** - * 创建每页无固定数量限制的分页 - * - * @param cursorId 起始游标ID - * @param pagingQuery 分页查询器,不可为null - */ - public CursorPaging(K cursorId, @NonNull PagingQuery> pagingQuery) { - this(cursorId, 0, pagingQuery); - } + /** + * 创建每页无固定数量限制的分页 + * + * @param cursorId 起始游标ID + * @param pagingQuery 分页查询器,不可为null + */ + public CursorPaging(K cursorId, @NonNull PagingQuery> pagingQuery) { + this(cursorId, 0, pagingQuery); + } - /** - * 创建指定每页大小的分页 - * - * @param cursorId 起始游标ID - * @param pageSize 每页大小,0表示使用默认值 - * @param pagingQuery 分页查询器,不可为null - */ - public CursorPaging(K cursorId, int pageSize, @NonNull PagingQuery> pagingQuery) { - this(null, cursorId, pageSize, pagingQuery); - } + /** + * 创建指定每页大小的分页 + * + * @param cursorId 起始游标ID + * @param pageSize 每页大小,0表示使用默认值 + * @param pagingQuery 分页查询器,不可为null + */ + public CursorPaging(K cursorId, int pageSize, @NonNull PagingQuery> pagingQuery) { + this(null, cursorId, pageSize, pagingQuery); + } - /** - * 获取当前页数据,使用双重检查锁定实现懒加载 - * 首次调用时执行实际查询,并缓存结果 - * - * @return 当前页的Pageable对象 - */ - public final Pageable getCurrentPage() { - if (currentPage == null) { - synchronized (this) { - if (currentPage == null) { - currentPage = pagingQuery.query(cursorId, pageSize); - if (currentPage == null) { - // 处理无数据情况 - currentPage = new Cursor<>(cursorId, Listable.empty(), null); - } - } - } - } - return currentPage; - } + /** + * 获取当前页数据,使用双重检查锁定实现懒加载 首次调用时执行实际查询,并缓存结果 + * + * @return 当前页的Pageable对象 + */ + public final Pageable getCurrentPage() { + if (currentPage == null) { + synchronized (this) { + if (currentPage == null) { + currentPage = pagingQuery.query(cursorId, pageSize); + if (currentPage == null) { + // 处理无数据情况 + currentPage = new Cursor<>(cursorId, Listable.empty(), null, null); + } + } + } + } + return currentPage; + } - @Override - public final K getCursorId() { - return cursorId; - } + @Override + public final K getCursorId() { + return cursorId; + } - @Override - public final Elements getElements() { - return getCurrentPage().getElements(); - } + @Override + public final Elements getElements() { + return getCurrentPage().getElements(); + } - @Override - public final K getNextCursorId() { - return getCurrentPage().getNextCursorId(); - } + @Override + public final K getNextCursorId() { + return getCurrentPage().getNextCursorId(); + } - @Override - public final int getPageSize() { - return pageSize; - } + @Override + public final int getPageSize() { + return pageSize; + } - @Override - public boolean isKnowTotal() { - return total != null; - } + @Override + public boolean isKnowTotal() { + return total != null || (currentPage != null && currentPage.isKnowTotal()); + } - @Override - public long getTotal() { - return total == null ? pages().stream().mapToLong((e) -> e.getElements().count()).sum() : total; - } + @Override + public Long getTotal() { + if (total != null) { + return total; + } - @Override - public final boolean hasNextPage() { - return getCurrentPage().hasNextPage(); - } + if (currentPage != null && currentPage.isKnowTotal()) { + return currentPage.getTotal(); + } - @Override - public Paging query(K cursorId, int pageSize) { - return new CursorPaging<>(total, cursorId, pageSize, pagingQuery); - } + long total = 0; + for (Paging paging : pages()) { + if (paging.isKnowTotal()) { + return paging.getTotal(); + } + total = Math.addExact(paging.getElements().count(), total); + } + return total; + } + + @Override + public final boolean hasNextPage() { + return getCurrentPage().hasNextPage(); + } + + @Override + public Paging query(K cursorId, int pageSize) { + return new CursorPaging<>(total, cursorId, pageSize, pagingQuery); + } } \ No newline at end of file diff --git a/core/src/main/java/run/soeasy/framework/core/page/OffsetPaging.java b/core/src/main/java/run/soeasy/framework/core/page/OffsetPaging.java index afacf2200..f5ef11acc 100644 --- a/core/src/main/java/run/soeasy/framework/core/page/OffsetPaging.java +++ b/core/src/main/java/run/soeasy/framework/core/page/OffsetPaging.java @@ -1,11 +1,12 @@ package run.soeasy.framework.core.page; import java.util.Collection; +import java.util.Collections; import java.util.List; +import java.util.function.Function; import lombok.NonNull; import run.soeasy.framework.core.Assert; -import run.soeasy.framework.core.collection.CollectionUtils; import run.soeasy.framework.core.collection.Listable; /** @@ -16,52 +17,27 @@ * @param 分页内容的元素类型 */ public class OffsetPaging extends CursorPaging { - /** - * 在内存中对list进行分页 - * - * @param offset 偏移量,从0开始 - * @param pageSize 每页大小 - * @param elements 原始数据列表 - */ - public OffsetPaging(long offset, int pageSize, List elements) { - this(elements.size(), offset, pageSize, (cursorId, length) -> { - int fromIndex = Math.toIntExact(cursorId); - if (fromIndex >= elements.size()) { - return Listable.empty(); - } - List list = elements.subList(fromIndex, Math.min(Math.addExact(fromIndex, length), elements.size())); - return Listable.forCollection(list); - }); - } - /** - * 未知数量的构造 - * - * @param offset 偏移量,从0开始 - * @param pageSize 每页大小 - * @param pagingQuery 分页查询器,不可为null - */ - public OffsetPaging(long offset, int pageSize, @NonNull PagingQuery> pagingQuery) { + public OffsetPaging(long offset, int pageSize, @NonNull PagingQuery offsetQuery, + @NonNull Function> elementMapper, + @NonNull Function totalMapper) { super(offset, pageSize, (cursorId, length) -> { - Listable listable = pagingQuery.query(cursorId, length); - long nextCursorId = Math.addExact(cursorId, length);// 应该给异常还是返回没有下一页? - return new Cursor<>(cursorId, listable, listable.hasElements() ? nextCursorId : null); - }); - } - - /** - * 已知总数的构造 - * - * @param total 总记录数 - * @param offset 偏移量,从0开始 - * @param pageSize 每页大小 - * @param pagingQuery 分页查询器,不可为null - */ - public OffsetPaging(long total, long offset, int pageSize, @NonNull PagingQuery> pagingQuery) { - super(total, offset, pageSize, (cursorId, length) -> { - long nextCursorId = Math.addExact(cursorId, length); - return new Cursor<>(cursorId, pagingQuery.query(cursorId, length), - nextCursorId < total ? nextCursorId : null); + T result = offsetQuery.query(cursorId, length); + Listable elements = result == null ? null : elementMapper.apply(result); + if (elements == null) { + elements = Listable.empty(); + } + Number totalNumber = result == null ? null : totalMapper.apply(result); + Long total = totalNumber == null ? null : totalNumber.longValue(); + Long nextCursorId; + if (total == null) { + // 未知数量 + nextCursorId = elements.hasElements() ? Math.addExact(cursorId, length) : null; + } else { + // 已知数量 + nextCursorId = (total - cursorId) > 1 ? Math.addExact(cursorId, length) : null; + } + return new Cursor<>(cursorId, elements, nextCursorId, total); }); } @@ -91,78 +67,47 @@ public static long getOffset(long pageNumber, long pageSize) { return Math.multiplyExact((pageNumber - 1), pageSize); } - /** - * 基于偏移量创建OffsetPaging实例(未知总条数) - * - * @param 分页元素类型 - * @param offset 偏移量,从0开始 - * @param pageSize 每页大小,需大于0 - * @param offsetQuery 偏移量查询器,入参为偏移量和每页大小,返回对应数据集 - * @return 未知总条数的OffsetPaging实例 - */ - public static OffsetPaging of(long offset, int pageSize, - PagingQuery> offsetQuery) { - return new OffsetPaging<>(offset, pageSize, (o, limit) -> { - Collection elements = offsetQuery.query(o, limit); - if (CollectionUtils.isEmpty(elements)) { - return Listable.empty(); + public static OffsetPaging of(long offset, int pageSize, List elements) { + return of(offset, pageSize, (cursorId, length) -> { + int fromIndex = Math.toIntExact(cursorId); + if (fromIndex >= elements.size()) { + return Collections.emptyList(); } - return Listable.forCollection(elements); - }); + return elements.subList(fromIndex, Math.min(Math.addExact(fromIndex, length), elements.size())); + }, Function.identity(), (e) -> elements.size()); } - /** - * 基于页码创建OffsetPaging实例(未知总条数) - * - * @param 分页元素类型 - * @param pageNumber 页码,从1开始 - * @param pageSize 每页大小,需大于0 - * @param pageQuery 页码查询器,入参为页码和每页大小,返回对应数据集 - * @return 未知总条数的OffsetPaging实例 - */ - public static OffsetPaging ofUnknownPageNumber(long pageNumber, int pageSize, - PagingQuery> pageQuery) { + public static OffsetPaging ofPageNumber(long pageNumber, int pageSize, List elements) { + return of(getPageNumber(pageNumber, pageSize), pageSize, elements); + } + + public static OffsetPaging of(long offset, int pageSize, + @NonNull PagingQuery offsetQuery, + @NonNull Function> elementMapper, + @NonNull Function totalMapper) { + return new OffsetPaging<>(offset, pageSize, offsetQuery, (e) -> { + Collection elements = elementMapper.apply(e); + return elements == null ? null : Listable.forCollection(elements); + }, totalMapper); + } + + public static OffsetPaging ofPageNumber(long pageNumber, int pageSize, + @NonNull PagingQuery pageNumberQuery, + @NonNull Function> elementMapper, + @NonNull Function totalMapper) { return of(getOffset(pageNumber, pageSize), pageSize, (offset, limit) -> { - return pageQuery.query(getPageNumber(offset, limit), pageSize); - }); + return pageNumberQuery.query(getPageNumber(offset, limit), limit); + }, elementMapper, totalMapper); } - /** - * 基于偏移量和总条数创建OffsetPaging实例(已知总条数) - * - * @param 分页元素类型 - * @param total 总记录数,需大于等于0 - * @param offset 偏移量,从0开始 - * @param pageSize 每页大小,需大于0 - * @param offsetQuery 偏移量查询器,入参为偏移量和每页大小,返回对应数据集 - * @return 已知总条数的OffsetPaging实例 - */ - public static OffsetPaging of(long total, long offset, int pageSize, - PagingQuery> offsetQuery) { - return new OffsetPaging<>(total, offset, pageSize, (o, limit) -> { - Collection elements = offsetQuery.query(o, limit); - if (CollectionUtils.isEmpty(elements)) { - return Listable.empty(); - } - return Listable.forCollection(elements); - }); + public static OffsetPaging of(long offset, int pageSize, + @NonNull PagingQuery> offsetQuery, Long total) { + return of(offset, pageSize, offsetQuery, Function.identity(), (e) -> total); } - /** - * 基于页码和总条数创建OffsetPaging实例(已知总条数) - * - * @param 分页元素类型 - * @param total 总记录数,需大于等于0 - * @param pageNumber 页码,从1开始 - * @param pageSize 每页大小,需大于0 - * @param pageQuery 页码查询器,入参为页码和每页大小,返回对应数据集 - * @return 已知总条数的OffsetPaging实例 - */ - public static OffsetPaging ofPageNumber(long total, long pageNumber, int pageSize, - PagingQuery> pageQuery) { - return of(total, getOffset(pageNumber, pageSize), pageSize, (offset, limit) -> { - return pageQuery.query(getPageNumber(offset, limit), pageSize); - }); + public static OffsetPaging ofPageNumber(long pageNumber, int pageSize, + @NonNull PagingQuery> offsetQuery, Long total) { + return ofPageNumber(pageNumber, pageSize, offsetQuery, Function.identity(), (e) -> total); } /** @@ -192,6 +137,7 @@ public final OffsetPaging jumpToPage(long pageNumber) { * @return 新的OffsetPaging实例 */ public OffsetPaging jumpToPage(long pageNumber, int pageSize) { - return new OffsetPaging<>(getTotal(), getOffset(pageNumber, pageSize), pageSize, this::query); + return new OffsetPaging<>(getOffset(pageNumber, pageSize), pageSize, this::query, Function.identity(), + (e) -> e.isKnowTotal() ? e.getTotal() : null); } } \ No newline at end of file diff --git a/core/src/main/java/run/soeasy/framework/core/page/PageIterator.java b/core/src/main/java/run/soeasy/framework/core/page/PageIterator.java index 0ec30a848..3c5ee3711 100644 --- a/core/src/main/java/run/soeasy/framework/core/page/PageIterator.java +++ b/core/src/main/java/run/soeasy/framework/core/page/PageIterator.java @@ -6,15 +6,17 @@ import java.util.NoSuchElementException; import java.util.function.Supplier; +import lombok.Getter; import lombok.NonNull; import lombok.RequiredArgsConstructor; import run.soeasy.framework.core.collection.Listable; @RequiredArgsConstructor public class PageIterator implements Iterator> { + @Getter + private final int pageSize; @NonNull private final Iterator iterator; - private final int pageSize; private volatile int page = 0; private volatile Supplier> supplier; @@ -27,7 +29,8 @@ public synchronized boolean hasNext() { } long offset = page * pageSize; - Pageable pageable = new Cursor(offset, Listable.forCollection(list), offset + pageSize); + Pageable pageable = new Cursor(offset, Listable.forCollection(list), offset + pageSize, + null); supplier = () -> pageable; } return supplier != null; diff --git a/core/src/main/java/run/soeasy/framework/core/page/Pageable.java b/core/src/main/java/run/soeasy/framework/core/page/Pageable.java index f65cd9a1e..4001451b1 100644 --- a/core/src/main/java/run/soeasy/framework/core/page/Pageable.java +++ b/core/src/main/java/run/soeasy/framework/core/page/Pageable.java @@ -3,8 +3,7 @@ import run.soeasy.framework.core.collection.Listable; /** - * 可分页的对象 - * 定义基于游标的分页机制,用于高效处理大数据集的分页查询 + * 可分页的对象 定义基于游标的分页机制,用于高效处理大数据集的分页查询 * * @author soeasy.run * @@ -12,29 +11,42 @@ * @param 分页内容的元素类型 */ public interface Pageable extends Listable { - /** - * 获取当前页的游标ID - * 游标ID用于标识当前分页位置,通常是上一页最后一个元素的唯一标识 - * - * @return 当前游标ID,首次请求时可为null - */ - K getCursorId(); + /** + * 获取当前页的游标ID 游标ID用于标识当前分页位置,通常是上一页最后一个元素的唯一标识 + * + * @return 当前游标ID,首次请求时可为null + */ + K getCursorId(); - /** - * 获取下一页的游标ID - * 用于标识下一页数据的起始位置 - * - * @return 下一页游标ID,若无下一页则返回null - */ - K getNextCursorId(); + /** + * 获取下一页的游标ID 用于标识下一页数据的起始位置 + * + * @return 下一页游标ID,若无下一页则返回null + */ + K getNextCursorId(); - /** - * 判断是否存在下一页 - * 基于{@link #getNextCursorId()}的返回值进行判断 - * - * @return 存在下一页返回true,否则返回false - */ - default boolean hasNextPage() { - return getNextCursorId() != null; - } + /** + * 判断是否存在下一页 基于{@link #getNextCursorId()}的返回值进行判断 + * + * @return 存在下一页返回true,否则返回false + */ + default boolean hasNextPage() { + return getNextCursorId() != null; + } + + /** + * 判断是否已知总数 某些场景下(如大数据集)可能无法高效获取总数,此时返回false 未知总数的分页在调用getTotal()时会使用代价较高的循环计数 + * + * @return 如果已知总数返回true,否则返回false + */ + default boolean isKnowTotal() { + return getTotal() != null; + } + + /** + * 获取总记录数 如果isKnowTotal()返回false,此方法可能会触发全量数据遍历,性能开销较大 + * + * @return 总记录数 + */ + Long getTotal(); } \ No newline at end of file diff --git a/core/src/main/java/run/soeasy/framework/core/page/Paging.java b/core/src/main/java/run/soeasy/framework/core/page/Paging.java index 01f5c6d62..d384f3a6f 100644 --- a/core/src/main/java/run/soeasy/framework/core/page/Paging.java +++ b/core/src/main/java/run/soeasy/framework/core/page/Paging.java @@ -2,12 +2,12 @@ import java.util.NoSuchElementException; +import run.soeasy.framework.core.Assert; import run.soeasy.framework.core.collection.Elements; import run.soeasy.framework.core.collection.LinkedIterator; /** - * 分页接口 - * 定义基于游标的分页查询机制,支持获取分页元数据和遍历所有页 + * 分页接口 定义基于游标的分页查询机制,支持获取分页元数据和遍历所有页 * * @author soeasy.run * @@ -15,71 +15,74 @@ * @param 分页内容的元素类型 */ public interface Paging extends Pageable, PagingQuery> { - /** - * 判断是否已知总数 - * 某些场景下(如大数据集)可能无法高效获取总数,此时返回false - * 未知总数的分页在调用getTotal()时会使用代价较高的循环计数 - * - * @return 如果已知总数返回true,否则返回false - */ - boolean isKnowTotal(); + /** + * 获取总页数 + * + * @param total 总记录数,需 ≥ 0 + * @param pageSize 每页大小,需 > 0 + * @return 总页数(≥ 0) + * @throws ArithmeticException 当计算过程中发生数值溢出时抛出 + */ + public static long getPages(long total, long pageSize) { + Assert.isTrue(total >= 0, "Total must be greater than or equal to 0"); + Assert.isTrue(pageSize > 0, "PageSize must be greater than 0"); - /** - * 获取总记录数 - * 如果isKnowTotal()返回false,此方法可能会触发全量数据遍历,性能开销较大 - * - * @return 总记录数 - */ - long getTotal(); + if (total == 0) { + return 0; + } - /** - * 获取总页数 - * 计算公式为:ceil(总记录数 / 每页数量) - * - * @return 总页数 - * @throws ArithmeticException 如果pageSize为0 - */ - default long getPages() { - return (long) Math.ceil((double) getTotal() / getPageSize()); - } + // 3. 等价变形公式:(total - 1) / pageSize + 1(避免total + pageSize溢出) + // 原公式 (total + pageSize -1)/pageSize 等价于 (total-1)/pageSize + 1(total>0时) + // 优势:仅做减法/除法/加法,无大数相加,从根源避免溢出 + return Math.addExact(((total - 1) / pageSize), 1); + } - /** - * 获取每页数量 - * - * @return 每页数量,如果返回0表示每页数量不确定 - */ - int getPageSize(); + /** + * 获取总页数 + * + * @return 总页数 + * @throws ArithmeticException 如果pageSize为0 + */ + default long getPages() { + return getPages(getTotal(), getPageSize()); + } - /** - * 获取下一页 - * - * @return 下一页的分页对象 - * @throws NoSuchElementException 如果没有下一页 - */ - default Paging nextPage() { - if (!hasNextPage()) { - throw new NoSuchElementException("There is no next page"); - } - return jumpTo(getNextCursorId()); - } + /** + * 获取每页数量 + * + * @return 每页数量,如果返回0表示每页数量不确定 + */ + int getPageSize(); - /** - * 获取所有页的元素集合 - * 支持流式遍历所有页,延迟加载后续页的数据 - * - * @return 包含所有页的元素集合 - */ - default Elements> pages() { - return Elements.of(() -> new LinkedIterator<>(this, Paging::hasNextPage, Paging::nextPage)); - } + /** + * 获取下一页 + * + * @return 下一页的分页对象 + * @throws NoSuchElementException 如果没有下一页 + */ + default Paging nextPage() { + if (!hasNextPage()) { + throw new NoSuchElementException("There is no next page"); + } + return jumpTo(getNextCursorId()); + } - /** - * 跳转到指定游标位置的页 - * - * @param cursorId 目标页的游标ID - * @return 指定位置的分页对象 - */ - default Paging jumpTo(K cursorId) { - return query(cursorId, getPageSize()); - } + /** + * 获取所有页的元素集合 支持流式遍历所有页,延迟加载后续页的数据 + * + * @return 包含所有页的元素集合 + */ + default Elements> pages() { + return Elements.of(() -> new LinkedIterator<>(this, Paging::hasNextPage, Paging::nextPage)); + } + + /** + * 跳转到指定游标位置的页 + * + * @param cursorId 目标页的游标ID + * @return 指定位置的分页对象 + */ + default Paging jumpTo(K cursorId) { + return query(cursorId, getPageSize()); + } } \ No newline at end of file diff --git a/core/src/main/java/run/soeasy/framework/core/type/ClassUtils.java b/core/src/main/java/run/soeasy/framework/core/type/ClassUtils.java index 7b3088637..2d23fe941 100644 --- a/core/src/main/java/run/soeasy/framework/core/type/ClassUtils.java +++ b/core/src/main/java/run/soeasy/framework/core/type/ClassUtils.java @@ -291,7 +291,7 @@ public static Paging, Class> getInterfaces(@NonNull Class sourceC return new CursorPaging, Class>(sourceClass, (clazz, size) -> { Class[] interfaces = clazz.getInterfaces(); List> list = interfaces == null ? Collections.emptyList() : Arrays.asList(interfaces); - return new Cursor<>(clazz, Listable.forCollection(list), clazz.getSuperclass()); + return new Cursor<>(clazz, Listable.forCollection(list), clazz.getSuperclass(), null); }); } @@ -513,22 +513,25 @@ public static boolean isPrimitiveWrapper(@NonNull Type type) { *

* 核心逻辑: *

    - *
  • 输入为 Java 标准基本类型(byte/short/int/long/float/double/boolean/char): - * 通过内部预定义的映射表({@code primitiveTypeToWrapperMap})快速匹配,返回对应的包装类(如 {@code int.class → Integer.class}、{@code boolean.class → Boolean.class}); - *
  • 输入为非基本类型(如包装类、普通类、接口、数组等): - * 不进行转换,直接返回输入类型本身(如 {@code Integer.class → Integer.class}、{@code String.class → String.class})。 + *
  • 输入为 Java 标准基本类型(byte/short/int/long/float/double/boolean/char): + * 通过内部预定义的映射表({@code primitiveTypeToWrapperMap})快速匹配,返回对应的包装类(如 + * {@code int.class → Integer.class}、{@code boolean.class → Boolean.class}); + *
  • 输入为非基本类型(如包装类、普通类、接口、数组等): 不进行转换,直接返回输入类型本身(如 + * {@code Integer.class → Integer.class}、{@code String.class → String.class})。 *
- * 该方法设计初衷是简化“基本类型与包装类”的统一处理逻辑,避免因输入类型差异导致的额外判断,同时保证返回结果非 null(除非输入为 null,但已通过 {@code @NonNull} 限制)。 + * 该方法设计初衷是简化“基本类型与包装类”的统一处理逻辑,避免因输入类型差异导致的额外判断,同时保证返回结果非 null(除非输入为 null,但已通过 + * {@code @NonNull} 限制)。 * * @param type 输入类型(支持基本类型、包装类、普通类、接口等),不可为 null * @return 基本类型返回对应包装类;非基本类型返回自身 * @throws NullPointerException 若传入的 {@code type} 为 null * @see Class#isPrimitive() 用于判断输入是否为基本类型的核心方法 - * @see Integer#TYPE 基本类型对应的 Class 对象(如 {@code Integer.TYPE} 等价于 {@code int.class}) + * @see Integer#TYPE 基本类型对应的 Class 对象(如 {@code Integer.TYPE} 等价于 + * {@code int.class}) * @see java.lang.Boolean#TYPE 布尔基本类型对应的 Class 对象示例 */ public static Class getPrimitiveWrapper(@NonNull Class type) { - return type.isPrimitive() ? primitiveTypeToWrapperMap.get(type) : type; + return type.isPrimitive() ? primitiveTypeToWrapperMap.get(type) : type; } /** @@ -654,9 +657,9 @@ public static boolean isNumber(Class type) { return false; } // 匹配原生数字类型 - if(type.isPrimitive()) { - if (type == long.class || type == int.class || type == byte.class || type == short.class || type == float.class - || type == double.class) { + if (type.isPrimitive()) { + if (type == long.class || type == int.class || type == byte.class || type == short.class + || type == float.class || type == double.class) { return true; } } diff --git a/core/src/test/java/run/soeasy/framework/core/page/OffsetPagingTest.java b/core/src/test/java/run/soeasy/framework/core/page/OffsetPagingTest.java index 5d0e6e970..e5d524057 100644 --- a/core/src/test/java/run/soeasy/framework/core/page/OffsetPagingTest.java +++ b/core/src/test/java/run/soeasy/framework/core/page/OffsetPagingTest.java @@ -18,7 +18,7 @@ public void test() { } for (int i = 1; i < count; i++) { - OffsetPaging paginations = new OffsetPaging(0, i, list); + OffsetPaging paginations = OffsetPaging.of(0, i, list); Assert.assertTrue(paginations.jumpToPage(paginations.getPages() + 1).getElements().count() == 0); Assert.assertTrue(!paginations.jumpToPage(paginations.getPages()).hasNextPage()); String[] leftArray = list.toArray(new String[0]); diff --git a/core/src/test/java/run/soeasy/framework/core/page/PageIteratorTest.java b/core/src/test/java/run/soeasy/framework/core/page/PageIteratorTest.java index 1cda359d7..64646da6c 100644 --- a/core/src/test/java/run/soeasy/framework/core/page/PageIteratorTest.java +++ b/core/src/test/java/run/soeasy/framework/core/page/PageIteratorTest.java @@ -15,7 +15,7 @@ public void test() { } for (int pageSize = 200; pageSize < 2002; pageSize++) { - PageIterator pageIterator = new PageIterator<>(list.iterator(), pageSize); + PageIterator pageIterator = new PageIterator<>(pageSize, list.iterator()); long total = 0; while (pageIterator.hasNext()) { total += pageIterator.next().getElements().count(); From 930abf4de27a40017e1f67904876c6bb890915a3 Mon Sep 17 00:00:00 2001 From: wcnnkh <416078075@qq.com> Date: Tue, 2 Dec 2025 15:04:22 +0800 Subject: [PATCH 04/16] cleanup --- .../framework/core/page/OffsetPaging.java | 156 +++++++++++++----- 1 file changed, 115 insertions(+), 41 deletions(-) diff --git a/core/src/main/java/run/soeasy/framework/core/page/OffsetPaging.java b/core/src/main/java/run/soeasy/framework/core/page/OffsetPaging.java index f5ef11acc..347071684 100644 --- a/core/src/main/java/run/soeasy/framework/core/page/OffsetPaging.java +++ b/core/src/main/java/run/soeasy/framework/core/page/OffsetPaging.java @@ -18,27 +18,17 @@ */ public class OffsetPaging extends CursorPaging { - public OffsetPaging(long offset, int pageSize, @NonNull PagingQuery offsetQuery, - @NonNull Function> elementMapper, - @NonNull Function totalMapper) { - super(offset, pageSize, (cursorId, length) -> { - T result = offsetQuery.query(cursorId, length); - Listable elements = result == null ? null : elementMapper.apply(result); - if (elements == null) { - elements = Listable.empty(); - } - Number totalNumber = result == null ? null : totalMapper.apply(result); - Long total = totalNumber == null ? null : totalNumber.longValue(); - Long nextCursorId; - if (total == null) { - // 未知数量 - nextCursorId = elements.hasElements() ? Math.addExact(cursorId, length) : null; - } else { - // 已知数量 - nextCursorId = (total - cursorId) > 1 ? Math.addExact(cursorId, length) : null; - } - return new Cursor<>(cursorId, elements, nextCursorId, total); - }); + /** + * 获取偏移量 + * + * @param pageNumber 页码,从1开始 + * @param pageSize 每页大小, 不能小于0 + * @return + */ + public static long getOffset(long pageNumber, long pageSize) { + Assert.isTrue(pageNumber > 0, "PageNumber must be greater than to 0"); + Assert.isTrue(pageSize > 0, "PageSize must be greater than to 0"); + return Math.multiplyExact((pageNumber - 1), pageSize); } /** @@ -55,18 +45,14 @@ public static long getPageNumber(long offset, long pageSize) { } /** - * 获取偏移量 + * 基于偏移量和内存列表创建OffsetPaging实例 * - * @param pageNumber 页码,从1开始 - * @param pageSize 每页大小, 不能小于0 - * @return + * @param 分页元素类型 + * @param offset 偏移量,从0开始 + * @param pageSize 每页大小,需大于0 + * @param elements 原始数据列表,基于该列表进行内存分页 + * @return OffsetPaging 内存分页后的实例 */ - public static long getOffset(long pageNumber, long pageSize) { - Assert.isTrue(pageNumber > 0, "PageNumber must be greater than to 0"); - Assert.isTrue(pageSize > 0, "PageSize must be greater than to 0"); - return Math.multiplyExact((pageNumber - 1), pageSize); - } - public static OffsetPaging of(long offset, int pageSize, List elements) { return of(offset, pageSize, (cursorId, length) -> { int fromIndex = Math.toIntExact(cursorId); @@ -77,10 +63,34 @@ public static OffsetPaging of(long offset, int pageSize, List elements }, Function.identity(), (e) -> elements.size()); } - public static OffsetPaging ofPageNumber(long pageNumber, int pageSize, List elements) { - return of(getPageNumber(pageNumber, pageSize), pageSize, elements); + /** + * 基于偏移量和指定总数创建OffsetPaging实例 + * + * @param 分页查询结果类型(元素集合) + * @param 分页元素类型 + * @param offset 偏移量,从0开始 + * @param pageSize 每页大小,需大于0 + * @param offsetQuery 分页查询器:入参为偏移量、每页大小,返回元素集合Collection + * @param total 总记录数(可为null,表示未知总数) + * @return OffsetPaging 分页实例 + */ + public static OffsetPaging of(long offset, int pageSize, + @NonNull PagingQuery> offsetQuery, Long total) { + return of(offset, pageSize, offsetQuery, Function.identity(), (e) -> total); } + /** + * 基于偏移量创建OffsetPaging实例(支持自定义结果映射和总数映射) + * + * @param 分页查询的原始结果类型 + * @param 分页元素类型 + * @param offset 偏移量,从0开始 + * @param pageSize 每页大小,需大于0 + * @param offsetQuery 分页查询器:入参为偏移量、每页大小,返回原始查询结果T + * @param elementMapper 结果映射器:将原始查询结果T转换为分页元素集合Collection(不可为null) + * @param totalMapper 总数映射器:将原始查询结果T转换为总记录数(Number类型,不可为null) + * @return OffsetPaging 分页实例 + */ public static OffsetPaging of(long offset, int pageSize, @NonNull PagingQuery offsetQuery, @NonNull Function> elementMapper, @@ -91,6 +101,47 @@ public static OffsetPaging of(long offset, int pageSize, }, totalMapper); } + /** + * 基于页码和内存列表创建OffsetPaging实例 + * + * @param 分页元素类型 + * @param pageNumber 页码,从1开始 + * @param pageSize 每页大小,需大于0 + * @param elements 原始数据列表,基于该列表进行内存分页 + * @return OffsetPaging 内存分页后的实例 + */ + public static OffsetPaging ofPageNumber(long pageNumber, int pageSize, List elements) { + return of(getPageNumber(pageNumber, pageSize), pageSize, elements); + } + + /** + * 基于页码和指定总数创建OffsetPaging实例 + * + * @param 分页查询结果类型(元素集合) + * @param 分页元素类型 + * @param pageNumber 页码,从1开始 + * @param pageSize 每页大小,需大于0 + * @param offsetQuery 分页查询器:入参为页码、每页大小,返回元素集合Collection + * @param total 总记录数(可为null,表示未知总数) + * @return OffsetPaging 分页实例 + */ + public static OffsetPaging ofPageNumber(long pageNumber, int pageSize, + @NonNull PagingQuery> offsetQuery, Long total) { + return ofPageNumber(pageNumber, pageSize, offsetQuery, Function.identity(), (e) -> total); + } + + /** + * 基于页码创建OffsetPaging实例(支持自定义结果映射和总数映射) + * + * @param 分页查询的原始结果类型 + * @param 分页元素类型 + * @param pageNumber 页码,从1开始 + * @param pageSize 每页大小,需大于0 + * @param pageNumberQuery 分页查询器:入参为页码、每页大小,返回原始查询结果T + * @param elementMapper 结果映射器:将原始查询结果T转换为分页元素集合Collection(不可为null) + * @param totalMapper 总数映射器:将原始查询结果T转换为总记录数(Number类型,不可为null) + * @return OffsetPaging 分页实例 + */ public static OffsetPaging ofPageNumber(long pageNumber, int pageSize, @NonNull PagingQuery pageNumberQuery, @NonNull Function> elementMapper, @@ -100,14 +151,37 @@ public static OffsetPaging ofPageNumber(long pageNumber, int pageSize, }, elementMapper, totalMapper); } - public static OffsetPaging of(long offset, int pageSize, - @NonNull PagingQuery> offsetQuery, Long total) { - return of(offset, pageSize, offsetQuery, Function.identity(), (e) -> total); - } - - public static OffsetPaging ofPageNumber(long pageNumber, int pageSize, - @NonNull PagingQuery> offsetQuery, Long total) { - return ofPageNumber(pageNumber, pageSize, offsetQuery, Function.identity(), (e) -> total); + /** + * 通用分页构造器(支持自定义结果映射&总数映射) + * + * @param 分页查询的原始结果类型 + * @param offset 偏移量,从0开始 + * @param pageSize 每页大小,需大于0 + * @param offsetQuery 分页查询器:入参为偏移量、每页大小,返回原始查询结果T + * @param elementMapper 结果映射器:将原始查询结果T转换为分页元素列表Listable(不可为null) + * @param totalMapper 总数映射器:将原始查询结果T转换为总记录数(Number类型,不可为null) + */ + public OffsetPaging(long offset, int pageSize, @NonNull PagingQuery offsetQuery, + @NonNull Function> elementMapper, + @NonNull Function totalMapper) { + super(offset, pageSize, (cursorId, length) -> { + T result = offsetQuery.query(cursorId, length); + Listable elements = result == null ? null : elementMapper.apply(result); + if (elements == null) { + elements = Listable.empty(); + } + Number totalNumber = result == null ? null : totalMapper.apply(result); + Long total = totalNumber == null ? null : totalNumber.longValue(); + Long nextCursorId; + if (total == null) { + // 未知数量 + nextCursorId = elements.hasElements() ? Math.addExact(cursorId, length) : null; + } else { + // 已知数量 + nextCursorId = (total - cursorId) > 1 ? Math.addExact(cursorId, length) : null; + } + return new Cursor<>(cursorId, elements, nextCursorId, total); + }); } /** From 30c2b84f27c17498b7a1af273bc2244a8a567a25 Mon Sep 17 00:00:00 2001 From: wcnnkh <416078075@qq.com> Date: Tue, 2 Dec 2025 15:13:37 +0800 Subject: [PATCH 05/16] cleanup --- .../java/run/soeasy/framework/core/page/CursorPaging.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/run/soeasy/framework/core/page/CursorPaging.java b/core/src/main/java/run/soeasy/framework/core/page/CursorPaging.java index db953af05..8f6a854c8 100644 --- a/core/src/main/java/run/soeasy/framework/core/page/CursorPaging.java +++ b/core/src/main/java/run/soeasy/framework/core/page/CursorPaging.java @@ -53,7 +53,7 @@ public CursorPaging(K cursorId, int pageSize, @NonNull PagingQuery getCurrentPage() { + private Pageable getCurrentPage() { if (currentPage == null) { synchronized (this) { if (currentPage == null) { @@ -120,6 +120,6 @@ public final boolean hasNextPage() { @Override public Paging query(K cursorId, int pageSize) { - return new CursorPaging<>(total, cursorId, pageSize, pagingQuery); + return new CursorPaging<>(isKnowTotal() ? getTotal() : total, cursorId, pageSize, pagingQuery); } } \ No newline at end of file From ae537972c77b5f5bd4235093240c190b09ed4c2f Mon Sep 17 00:00:00 2001 From: wcnnkh <416078075@qq.com> Date: Tue, 2 Dec 2025 16:36:05 +0800 Subject: [PATCH 06/16] cleanup --- .../java/run/soeasy/framework/core/page/CursorPaging.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/run/soeasy/framework/core/page/CursorPaging.java b/core/src/main/java/run/soeasy/framework/core/page/CursorPaging.java index 8f6a854c8..cab700fdb 100644 --- a/core/src/main/java/run/soeasy/framework/core/page/CursorPaging.java +++ b/core/src/main/java/run/soeasy/framework/core/page/CursorPaging.java @@ -99,8 +99,8 @@ public Long getTotal() { return total; } - if (currentPage != null && currentPage.isKnowTotal()) { - return currentPage.getTotal(); + if (getCurrentPage().isKnowTotal()) { + return getCurrentPage().getTotal(); } long total = 0; From 6602fb77f1af94dd0ddb5cda2ed297741f016703 Mon Sep 17 00:00:00 2001 From: wcnnkh <416078075@qq.com> Date: Tue, 2 Dec 2025 18:30:04 +0800 Subject: [PATCH 07/16] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=88=86=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../soeasy/framework/core/page/Cursor.java | 9 + .../framework/core/page/CursorPaging.java | 157 +++++++++++++++--- .../framework/core/page/OffsetPaging.java | 34 ++-- .../framework/core/page/PagingQuery.java | 1 - 4 files changed, 158 insertions(+), 43 deletions(-) diff --git a/core/src/main/java/run/soeasy/framework/core/page/Cursor.java b/core/src/main/java/run/soeasy/framework/core/page/Cursor.java index 0a91d2da9..618bbf0ef 100644 --- a/core/src/main/java/run/soeasy/framework/core/page/Cursor.java +++ b/core/src/main/java/run/soeasy/framework/core/page/Cursor.java @@ -4,6 +4,7 @@ import lombok.Data; import lombok.NonNull; +import run.soeasy.framework.core.Assert; import run.soeasy.framework.core.collection.Listable; import run.soeasy.framework.core.collection.ListableWrapper; @@ -28,4 +29,12 @@ public class Cursor implements Pageable, ListableWrapper source, K nextCursorId, Long total) { + Assert.isTrue(total == null || total >= 0, "Total must be greater than or equal to 0"); + this.cursorId = cursorId; + this.source = source; + this.nextCursorId = nextCursorId; + this.total = total; + } } \ No newline at end of file diff --git a/core/src/main/java/run/soeasy/framework/core/page/CursorPaging.java b/core/src/main/java/run/soeasy/framework/core/page/CursorPaging.java index cab700fdb..e13d91eab 100644 --- a/core/src/main/java/run/soeasy/framework/core/page/CursorPaging.java +++ b/core/src/main/java/run/soeasy/framework/core/page/CursorPaging.java @@ -1,57 +1,88 @@ package run.soeasy.framework.core.page; import lombok.NonNull; -import lombok.RequiredArgsConstructor; +import run.soeasy.framework.core.Assert; import run.soeasy.framework.core.collection.Elements; import run.soeasy.framework.core.collection.Listable; /** * 基于游标的分页实现 使用懒加载机制,首次访问数据时执行实际查询 + *

核心特性: + * 1. 懒加载:当前页数据首次访问时才执行查询,避免无效数据库/远程调用; + * 2. 游标定位:通过游标ID(K类型)标识分页位置,而非传统偏移量; + * 3. 总数懒计算:总记录数优先取查询结果的已知总数,未知时遍历所有页累加计算; + * 4. 线程安全:双重检查锁定保证懒加载的线程安全性。

* * @author soeasy.run * - * @param 游标的类型,用于标识分页位置 + * @param 游标的类型,用于标识分页位置(如Long型偏移量、String型唯一标识等) * @param 分页内容的元素类型 */ -@RequiredArgsConstructor public class CursorPaging implements Paging { - /** 当前页数据,使用双重检查锁定机制延迟初始化 */ + /** + * 当前页数据,使用双重检查锁定机制延迟初始化 + *

首次调用{@link #getCurrentPage()}时执行实际查询,查询后缓存结果

+ */ private volatile Pageable currentPage; - /** 总记录数,null表示总数未知,需遍历所有页计算 */ - private final Long total; - /** 当前页的游标ID */ + + /** + * 当前页的游标ID + *

作为分页查询的起始位置标识,由构造器传入,不可修改

+ */ private final K cursorId; - /** 每页大小,0表示使用默认值 */ + + /** + * 每页大小 + *

表示单次查询返回的最大元素数量,构造时校验需大于0

+ */ private final int pageSize; - /** 分页查询器,不可为null */ + + /** + * 分页查询器,不可为null + *

封装实际的分页查询逻辑,入参为游标ID和每页大小,返回分页结果Pageable

+ */ @NonNull private final PagingQuery> pagingQuery; + + /** + * 总记录数,null表示总数未知,需遍历所有页计算 + *

若手动通过{@link #setTotal(Long)}设置,则优先使用该值;否则通过{@link #getTotal()}计算

+ */ + private Long total; /** - * 创建每页无固定数量限制的分页 + * 创建默认每页大小的游标分页实例(每页大小为{@link Integer#MAX_VALUE}) + *

适用于“一次性查询所有数据”的场景,本质是不分页查询

* - * @param cursorId 起始游标ID - * @param pagingQuery 分页查询器,不可为null + * @param cursorId 起始游标ID,作为分页查询的起始位置标识 + * @param pagingQuery 分页查询器,不可为null,封装实际查询逻辑 + * @throws IllegalArgumentException 若pagingQuery为null时抛出(由@NonNull注解触发) */ public CursorPaging(K cursorId, @NonNull PagingQuery> pagingQuery) { - this(cursorId, 0, pagingQuery); + this(cursorId, Integer.MAX_VALUE, pagingQuery); } /** - * 创建指定每页大小的分页 + * 创建指定每页大小的分页实例 * - * @param cursorId 起始游标ID - * @param pageSize 每页大小,0表示使用默认值 - * @param pagingQuery 分页查询器,不可为null + * @param cursorId 起始游标ID,作为分页查询的起始位置标识 + * @param pageSize 每页大小,需大于0 + * @param pagingQuery 分页查询器,不可为null,封装实际查询逻辑 + * @throws IllegalArgumentException 若pageSize≤0或pagingQuery为null时抛出 */ public CursorPaging(K cursorId, int pageSize, @NonNull PagingQuery> pagingQuery) { - this(null, cursorId, pageSize, pagingQuery); + Assert.isTrue(pageSize > 0, "PageSize must be greater than to 0"); + this.cursorId = cursorId; + this.pageSize = pageSize; + this.pagingQuery = pagingQuery; } /** - * 获取当前页数据,使用双重检查锁定实现懒加载 首次调用时执行实际查询,并缓存结果 + * 获取当前页数据,使用双重检查锁定实现懒加载 + *

首次调用时执行实际查询,并缓存结果;后续调用直接返回缓存的currentPage

+ *

线程安全:通过volatile修饰currentPage + 同步代码块,保证多线程下仅执行一次查询

* - * @return 当前页的Pageable对象 + * @return 当前页的Pageable对象,永不返回null(无数据时返回空Pageable) */ private Pageable getCurrentPage() { if (currentPage == null) { @@ -68,31 +99,60 @@ private Pageable getCurrentPage() { return currentPage; } + /** + * 获取当前页的游标ID + * + * @return 当前页的游标ID,与构造器传入的起始游标ID一致,不可修改 + */ @Override public final K getCursorId() { return cursorId; } + /** + * 获取当前页的元素列表(懒加载) + *

首次调用时触发{@link #getCurrentPage()}执行实际查询,后续调用返回缓存数据

+ * + * @return 当前页的元素集合Elements,永不返回null(无数据时返回空Elements) + */ @Override public final Elements getElements() { return getCurrentPage().getElements(); } + /** + * 获取下一页的游标ID(懒加载) + *

首次调用时触发{@link #getCurrentPage()}执行实际查询,后续调用返回缓存结果

+ *

返回null表示无下一页数据

+ * + * @return 下一页的游标ID,null表示无下一页 + */ @Override public final K getNextCursorId() { return getCurrentPage().getNextCursorId(); } + /** + * 获取每页大小 + * + * @return 每页大小,构造时校验需大于0,不可修改 + */ @Override public final int getPageSize() { return pageSize; } - @Override - public boolean isKnowTotal() { - return total != null || (currentPage != null && currentPage.isKnowTotal()); - } - + /** + * 获取总记录数(懒计算) + *

计算规则(优先级从高到低): + * 1. 若已通过{@link #setTotal(Long)}设置total,直接返回该值; + * 2. 若当前页查询结果包含已知总数(isKnowTotal=true),返回当前页的总数; + * 3. 遍历所有分页,累加每一页的元素数量得到总记录数(性能较低,仅适用于总数未知场景)。

+ *

注意:遍历累加时使用{@link Math#addExact(long, long)},总数溢出会抛出ArithmeticException

+ * + * @return 总记录数,≥0;若总数未知且遍历后无数据,返回0 + * @throws ArithmeticException 当累加总数超出Long范围时抛出 + */ @Override public Long getTotal() { if (total != null) { @@ -113,13 +173,58 @@ public Long getTotal() { return total; } + /** + * 判断是否存在下一页(懒加载) + *

首次调用时触发{@link #getCurrentPage()}执行实际查询,后续调用返回缓存结果

+ * + * @return true表示存在下一页,false表示无下一页 + */ @Override public final boolean hasNextPage() { return getCurrentPage().hasNextPage(); } + /** + * 判断是否已知总记录数 + *

判断规则: + * 1. 若已手动设置total(非null),返回true; + * 2. 若当前页查询结果包含已知总数,返回true; + * 3. 其他情况返回false(需遍历计算总数)。

+ * + * @return true表示已知总记录数,false表示未知 + */ + @Override + public boolean isKnowTotal() { + return total != null || getCurrentPage().isKnowTotal(); + } + + /** + * 创建新的游标分页实例,跳转到指定游标位置并指定每页大小 + *

新实例会继承当前实例的“总数是否已知”状态: + * - 若当前实例已知总数(isKnowTotal=true),新实例直接复用该总数; + * - 若当前实例未知总数,新实例total为null。

+ * + * @param cursorId 目标游标ID,作为新分页的起始位置 + * @param pageSize 新分页的每页大小,需大于0 + * @return 新的CursorPaging实例,继承当前实例的总数状态 + * @throws IllegalArgumentException 若pageSize≤0时抛出 + */ @Override public Paging query(K cursorId, int pageSize) { - return new CursorPaging<>(isKnowTotal() ? getTotal() : total, cursorId, pageSize, pagingQuery); + CursorPaging paging = new CursorPaging<>(cursorId, pageSize, pagingQuery); + paging.setTotal(isKnowTotal() ? getTotal() : total); + return paging; + } + + /** + * 手动设置总记录数 + *

设置后{@link #getTotal()}会优先返回该值,无需遍历计算,提升性能

+ * + * @param total 总记录数,可为null(表示恢复“总数未知”状态);非null时需≥0 + * @throws IllegalArgumentException 若total<0时抛出 + */ + public void setTotal(Long total) { + Assert.isTrue(total == null || total >= 0, "Total must be greater than or equal to 0"); + this.total = total; } } \ No newline at end of file diff --git a/core/src/main/java/run/soeasy/framework/core/page/OffsetPaging.java b/core/src/main/java/run/soeasy/framework/core/page/OffsetPaging.java index 347071684..01c24222c 100644 --- a/core/src/main/java/run/soeasy/framework/core/page/OffsetPaging.java +++ b/core/src/main/java/run/soeasy/framework/core/page/OffsetPaging.java @@ -7,6 +7,7 @@ import lombok.NonNull; import run.soeasy.framework.core.Assert; +import run.soeasy.framework.core.NumberUtils; import run.soeasy.framework.core.collection.Listable; /** @@ -47,10 +48,10 @@ public static long getPageNumber(long offset, long pageSize) { /** * 基于偏移量和内存列表创建OffsetPaging实例 * - * @param 分页元素类型 - * @param offset 偏移量,从0开始 - * @param pageSize 每页大小,需大于0 - * @param elements 原始数据列表,基于该列表进行内存分页 + * @param 分页元素类型 + * @param offset 偏移量,从0开始 + * @param pageSize 每页大小,需大于0 + * @param elements 原始数据列表,基于该列表进行内存分页 * @return OffsetPaging 内存分页后的实例 */ public static OffsetPaging of(long offset, int pageSize, List elements) { @@ -66,12 +67,12 @@ public static OffsetPaging of(long offset, int pageSize, List elements /** * 基于偏移量和指定总数创建OffsetPaging实例 * - * @param 分页查询结果类型(元素集合) - * @param 分页元素类型 - * @param offset 偏移量,从0开始 - * @param pageSize 每页大小,需大于0 + * @param 分页查询结果类型(元素集合) + * @param 分页元素类型 + * @param offset 偏移量,从0开始 + * @param pageSize 每页大小,需大于0 * @param offsetQuery 分页查询器:入参为偏移量、每页大小,返回元素集合Collection - * @param total 总记录数(可为null,表示未知总数) + * @param total 总记录数(可为null,表示未知总数) * @return OffsetPaging 分页实例 */ public static OffsetPaging of(long offset, int pageSize, @@ -117,12 +118,12 @@ public static OffsetPaging ofPageNumber(long pageNumber, int pageSize, Li /** * 基于页码和指定总数创建OffsetPaging实例 * - * @param 分页查询结果类型(元素集合) - * @param 分页元素类型 - * @param pageNumber 页码,从1开始 - * @param pageSize 每页大小,需大于0 + * @param 分页查询结果类型(元素集合) + * @param 分页元素类型 + * @param pageNumber 页码,从1开始 + * @param pageSize 每页大小,需大于0 * @param offsetQuery 分页查询器:入参为页码、每页大小,返回元素集合Collection - * @param total 总记录数(可为null,表示未知总数) + * @param total 总记录数(可为null,表示未知总数) * @return OffsetPaging 分页实例 */ public static OffsetPaging ofPageNumber(long pageNumber, int pageSize, @@ -165,20 +166,21 @@ public OffsetPaging(long offset, int pageSize, @NonNull PagingQuery> elementMapper, @NonNull Function totalMapper) { super(offset, pageSize, (cursorId, length) -> { + Assert.isTrue(offset >= 0, "Offset must be greater than or equal to 0"); T result = offsetQuery.query(cursorId, length); Listable elements = result == null ? null : elementMapper.apply(result); if (elements == null) { elements = Listable.empty(); } Number totalNumber = result == null ? null : totalMapper.apply(result); - Long total = totalNumber == null ? null : totalNumber.longValue(); + Long total = totalNumber == null ? null : NumberUtils.toLong(totalNumber); Long nextCursorId; if (total == null) { // 未知数量 nextCursorId = elements.hasElements() ? Math.addExact(cursorId, length) : null; } else { // 已知数量 - nextCursorId = (total - cursorId) > 1 ? Math.addExact(cursorId, length) : null; + nextCursorId = (total - cursorId) > length ? Math.addExact(cursorId, length) : null; } return new Cursor<>(cursorId, elements, nextCursorId, total); }); diff --git a/core/src/main/java/run/soeasy/framework/core/page/PagingQuery.java b/core/src/main/java/run/soeasy/framework/core/page/PagingQuery.java index d3fc5bac3..93796bfc1 100644 --- a/core/src/main/java/run/soeasy/framework/core/page/PagingQuery.java +++ b/core/src/main/java/run/soeasy/framework/core/page/PagingQuery.java @@ -16,7 +16,6 @@ public interface PagingQuery { * @param cursorId 起始游标位置,用于标识从何处开始查询数据 * 首次查询时可为null,表示从第一条数据开始 * @param pageSize 每页返回的记录数量上限 - * 0表示使用默认的每页大小(由具体实现决定) * @return 返回一个包含查询结果的分页对象 */ T query(S cursorId, int pageSize); From cd8ab300228bb704a6d83b24ef4a5ea07230f16e Mon Sep 17 00:00:00 2001 From: wcnnkh <416078075@qq.com> Date: Tue, 2 Dec 2025 20:49:50 +0800 Subject: [PATCH 08/16] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=88=86=E9=A1=B5?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/collection/ListableWrapper.java | 41 ++-- .../soeasy/framework/core/page/Cursor.java | 40 ---- .../framework/core/page/CursorPage.java | 43 ++++ .../framework/core/page/CursorPaging.java | 197 ++++++++++-------- .../framework/core/page/OffsetPaging.java | 141 +++++++------ .../run/soeasy/framework/core/page/Page.java | 17 ++ .../framework/core/page/PageIterator.java | 117 ++++++++--- .../soeasy/framework/core/page/Pageable.java | 81 +++---- .../framework/core/page/PageableWrapper.java | 31 +++ .../soeasy/framework/core/page/Paging.java | 67 +++--- .../framework/core/type/ClassUtils.java | 4 +- .../framework/core/page/OffsetPagingTest.java | 4 +- 12 files changed, 482 insertions(+), 301 deletions(-) delete mode 100644 core/src/main/java/run/soeasy/framework/core/page/Cursor.java create mode 100644 core/src/main/java/run/soeasy/framework/core/page/CursorPage.java create mode 100644 core/src/main/java/run/soeasy/framework/core/page/Page.java create mode 100644 core/src/main/java/run/soeasy/framework/core/page/PageableWrapper.java diff --git a/core/src/main/java/run/soeasy/framework/core/collection/ListableWrapper.java b/core/src/main/java/run/soeasy/framework/core/collection/ListableWrapper.java index 570a30321..c8c2fb758 100644 --- a/core/src/main/java/run/soeasy/framework/core/collection/ListableWrapper.java +++ b/core/src/main/java/run/soeasy/framework/core/collection/ListableWrapper.java @@ -12,27 +12,26 @@ * @see Listable * @see Wrapper */ +@FunctionalInterface public interface ListableWrapper> extends Listable, Wrapper { - - /** - * 获取被包装的元素集合。 - * 该方法代理调用源Listable的getElements()方法,返回其元素集合。 - * - * @return 被包装的元素集合实例 - */ - @Override - default Elements getElements() { - return getSource().getElements(); - } - /** - * 判断被包装的元素集合是否包含元素。 - * 该方法代理调用源Listable的hasElements()方法,返回其判断结果。 - * - * @return true表示元素集合为空,false表示包含元素 - */ - @Override - default boolean hasElements() { - return getSource().hasElements(); - } + /** + * 获取被包装的元素集合。 该方法代理调用源Listable的getElements()方法,返回其元素集合。 + * + * @return 被包装的元素集合实例 + */ + @Override + default Elements getElements() { + return getSource().getElements(); + } + + /** + * 判断被包装的元素集合是否包含元素。 该方法代理调用源Listable的hasElements()方法,返回其判断结果。 + * + * @return true表示元素集合为空,false表示包含元素 + */ + @Override + default boolean hasElements() { + return getSource().hasElements(); + } } \ No newline at end of file diff --git a/core/src/main/java/run/soeasy/framework/core/page/Cursor.java b/core/src/main/java/run/soeasy/framework/core/page/Cursor.java deleted file mode 100644 index 618bbf0ef..000000000 --- a/core/src/main/java/run/soeasy/framework/core/page/Cursor.java +++ /dev/null @@ -1,40 +0,0 @@ -package run.soeasy.framework.core.page; - -import java.io.Serializable; - -import lombok.Data; -import lombok.NonNull; -import run.soeasy.framework.core.Assert; -import run.soeasy.framework.core.collection.Listable; -import run.soeasy.framework.core.collection.ListableWrapper; - -/** - * 游标实现类 封装分页数据及游标信息,实现序列化以便跨进程传递 - * - * @author soeasy.run - * - * @param 游标ID的类型,需实现Serializable - * @param 分页数据项的类型 - */ -@Data -public class Cursor implements Pageable, ListableWrapper>, Serializable { - private static final long serialVersionUID = 1L; - - /** 当前页的游标ID,用于标识数据起始位置 */ - private final K cursorId; - /** 分页数据的源集合,不可为null */ - @NonNull - private final Listable source; - /** 下一页的游标ID,null表示无下一页 */ - private final K nextCursorId; - - private final Long total; - - public Cursor(K cursorId, @NonNull Listable source, K nextCursorId, Long total) { - Assert.isTrue(total == null || total >= 0, "Total must be greater than or equal to 0"); - this.cursorId = cursorId; - this.source = source; - this.nextCursorId = nextCursorId; - this.total = total; - } -} \ No newline at end of file diff --git a/core/src/main/java/run/soeasy/framework/core/page/CursorPage.java b/core/src/main/java/run/soeasy/framework/core/page/CursorPage.java new file mode 100644 index 000000000..71806ef1d --- /dev/null +++ b/core/src/main/java/run/soeasy/framework/core/page/CursorPage.java @@ -0,0 +1,43 @@ +package run.soeasy.framework.core.page; + +import lombok.Data; +import lombok.NonNull; +import run.soeasy.framework.core.Assert; +import run.soeasy.framework.core.collection.Listable; +import run.soeasy.framework.core.collection.ListableWrapper; + +/** + * 基于游标的分页实现类 封装分页数据及游标信息,用于高效处理大数据集的分页查询 + * + * @author soeasy.run + * @param 游标类型,用于标识分页起始位置 + * @param 分页数据项的类型 + */ +@Data +public class CursorPage implements Pageable, ListableWrapper> { + /** 当前页的游标,用于标识数据起始位置 */ + private final K currentCursor; + /** 分页数据的源集合,不可为null */ + @NonNull + private final Listable source; + /** 下一页的游标,null表示无下一页 */ + private final K nextCursor; + /** 总记录数,null表示未知总数 */ + private final Long totalCount; + + /** + * 基于游标的分页构造方法 + * + * @param currentCursor 当前页游标(首次请求可为null) + * @param source 分页数据集合(不可为null) + * @param nextCursor 下一页游标(无下一页则为null) + * @param totalCount 总记录数(未知则为null,需≥0) + */ + public CursorPage(K currentCursor, @NonNull Listable source, K nextCursor, Long totalCount) { + Assert.isTrue(totalCount == null || totalCount >= 0, "Total count must be greater than or equal to 0"); + this.currentCursor = currentCursor; + this.source = source; + this.nextCursor = nextCursor; + this.totalCount = totalCount; + } +} \ No newline at end of file diff --git a/core/src/main/java/run/soeasy/framework/core/page/CursorPaging.java b/core/src/main/java/run/soeasy/framework/core/page/CursorPaging.java index e13d91eab..6f5d243b1 100644 --- a/core/src/main/java/run/soeasy/framework/core/page/CursorPaging.java +++ b/core/src/main/java/run/soeasy/framework/core/page/CursorPaging.java @@ -7,91 +7,105 @@ /** * 基于游标的分页实现 使用懒加载机制,首次访问数据时执行实际查询 - *

核心特性: - * 1. 懒加载:当前页数据首次访问时才执行查询,避免无效数据库/远程调用; - * 2. 游标定位:通过游标ID(K类型)标识分页位置,而非传统偏移量; - * 3. 总数懒计算:总记录数优先取查询结果的已知总数,未知时遍历所有页累加计算; - * 4. 线程安全:双重检查锁定保证懒加载的线程安全性。

+ *

+ * 核心特性: 1. 懒加载:当前页数据首次访问时才执行查询,避免无效数据库/远程调用; 2. 游标定位:通过游标(K类型)标识分页位置,而非传统偏移量; + * 3. 总数懒计算:总记录数优先取查询结果的已知总数,未知时遍历所有页累加计算; 4. 线程安全:双重检查锁定保证懒加载的线程安全性。 + *

* * @author soeasy.run - * * @param 游标的类型,用于标识分页位置(如Long型偏移量、String型唯一标识等) * @param 分页内容的元素类型 */ public class CursorPaging implements Paging { - /** + /** * 当前页数据,使用双重检查锁定机制延迟初始化 - *

首次调用{@link #getCurrentPage()}时执行实际查询,查询后缓存结果

+ *

+ * 首次调用{@link #getCurrentPage()}时执行实际查询,查询后缓存结果 + *

*/ private volatile Pageable currentPage; - - /** - * 当前页的游标ID - *

作为分页查询的起始位置标识,由构造器传入,不可修改

- */ - private final K cursorId; - - /** + + /** + * 当前页的游标 + *

+ * 作为分页查询的起始位置标识,由构造器传入,不可修改 + *

+ */ + private final K cursor; + + /** * 每页大小 - *

表示单次查询返回的最大元素数量,构造时校验需大于0

+ *

+ * 表示单次查询返回的最大元素数量,构造时校验需严格大于0(不允许为0或负数) + *

*/ private final int pageSize; - - /** + + /** * 分页查询器,不可为null - *

封装实际的分页查询逻辑,入参为游标ID和每页大小,返回分页结果Pageable

+ *

+ * 封装实际的分页查询逻辑,入参为游标和每页大小,返回分页结果Pageable + *

*/ @NonNull private final PagingQuery> pagingQuery; - - /** + + /** * 总记录数,null表示总数未知,需遍历所有页计算 - *

若手动通过{@link #setTotal(Long)}设置,则优先使用该值;否则通过{@link #getTotal()}计算

+ *

+ * 若手动通过{@link #setTotalCount(Long)}设置,则优先使用该值;否则通过{@link #getTotalCount()}计算 + *

*/ - private Long total; + private Long totalCount; /** * 创建默认每页大小的游标分页实例(每页大小为{@link Integer#MAX_VALUE}) - *

适用于“一次性查询所有数据”的场景,本质是不分页查询

+ *

+ * 适用于“一次性查询所有数据”的场景,本质是不分页查询 + *

* - * @param cursorId 起始游标ID,作为分页查询的起始位置标识 + * @param cursor 起始游标,作为分页查询的起始位置标识 * @param pagingQuery 分页查询器,不可为null,封装实际查询逻辑 * @throws IllegalArgumentException 若pagingQuery为null时抛出(由@NonNull注解触发) */ - public CursorPaging(K cursorId, @NonNull PagingQuery> pagingQuery) { - this(cursorId, Integer.MAX_VALUE, pagingQuery); + public CursorPaging(K cursor, @NonNull PagingQuery> pagingQuery) { + this(cursor, Integer.MAX_VALUE, pagingQuery); } /** * 创建指定每页大小的分页实例 * - * @param cursorId 起始游标ID,作为分页查询的起始位置标识 - * @param pageSize 每页大小,需大于0 + * @param cursor 起始游标,作为分页查询的起始位置标识 + * @param pageSize 每页大小,必须>0(不允许为0或负数) * @param pagingQuery 分页查询器,不可为null,封装实际查询逻辑 * @throws IllegalArgumentException 若pageSize≤0或pagingQuery为null时抛出 */ - public CursorPaging(K cursorId, int pageSize, @NonNull PagingQuery> pagingQuery) { - Assert.isTrue(pageSize > 0, "PageSize must be greater than to 0"); - this.cursorId = cursorId; + public CursorPaging(K cursor, int pageSize, @NonNull PagingQuery> pagingQuery) { + Assert.isTrue(pageSize > 0, "Page size must be greater than 0, cannot be 0 or negative"); + this.cursor = cursor; this.pageSize = pageSize; this.pagingQuery = pagingQuery; } /** * 获取当前页数据,使用双重检查锁定实现懒加载 - *

首次调用时执行实际查询,并缓存结果;后续调用直接返回缓存的currentPage

- *

线程安全:通过volatile修饰currentPage + 同步代码块,保证多线程下仅执行一次查询

+ *

+ * 首次调用时执行实际查询,并缓存结果;后续调用直接返回缓存的currentPage + *

+ *

+ * 线程安全:通过volatile修饰currentPage + 同步代码块,保证多线程下仅执行一次查询 + *

* * @return 当前页的Pageable对象,永不返回null(无数据时返回空Pageable) */ - private Pageable getCurrentPage() { + public Pageable getCurrentPage() { if (currentPage == null) { synchronized (this) { if (currentPage == null) { - currentPage = pagingQuery.query(cursorId, pageSize); + currentPage = pagingQuery.query(cursor, pageSize); if (currentPage == null) { // 处理无数据情况 - currentPage = new Cursor<>(cursorId, Listable.empty(), null, null); + currentPage = new CursorPage<>(cursor, Listable.empty(), null, null); } } } @@ -100,18 +114,20 @@ private Pageable getCurrentPage() { } /** - * 获取当前页的游标ID + * 获取当前页的游标 * - * @return 当前页的游标ID,与构造器传入的起始游标ID一致,不可修改 + * @return 当前页的游标,与构造器传入的起始游标一致,不可修改 */ @Override - public final K getCursorId() { - return cursorId; + public final K getCurrentCursor() { + return cursor; } /** * 获取当前页的元素列表(懒加载) - *

首次调用时触发{@link #getCurrentPage()}执行实际查询,后续调用返回缓存数据

+ *

+ * 首次调用时触发{@link #getCurrentPage()}执行实际查询,后续调用返回缓存数据 + *

* * @return 当前页的元素集合Elements,永不返回null(无数据时返回空Elements) */ @@ -121,21 +137,28 @@ public final Elements getElements() { } /** - * 获取下一页的游标ID(懒加载) - *

首次调用时触发{@link #getCurrentPage()}执行实际查询,后续调用返回缓存结果

- *

返回null表示无下一页数据

+ * 获取下一页的游标(懒加载) + *

+ * 首次调用时触发{@link #getCurrentPage()}执行实际查询,后续调用返回缓存结果 + *

+ *

+ * 返回null表示无下一页数据 + *

* - * @return 下一页的游标ID,null表示无下一页 + * @return 下一页的游标,null表示无下一页 */ @Override - public final K getNextCursorId() { - return getCurrentPage().getNextCursorId(); + public final K getNextCursor() { + return getCurrentPage().getNextCursor(); } /** * 获取每页大小 + *

+ * 构造时已校验>0,返回值恒>0,不可修改 + *

* - * @return 每页大小,构造时校验需大于0,不可修改 + * @return 每页大小(恒>0) */ @Override public final int getPageSize() { @@ -144,29 +167,32 @@ public final int getPageSize() { /** * 获取总记录数(懒计算) - *

计算规则(优先级从高到低): - * 1. 若已通过{@link #setTotal(Long)}设置total,直接返回该值; - * 2. 若当前页查询结果包含已知总数(isKnowTotal=true),返回当前页的总数; - * 3. 遍历所有分页,累加每一页的元素数量得到总记录数(性能较低,仅适用于总数未知场景)。

- *

注意:遍历累加时使用{@link Math#addExact(long, long)},总数溢出会抛出ArithmeticException

+ *

+ * 计算规则(优先级从高到低): 1. 若已通过{@link #setTotalCount(Long)}设置totalCount,直接返回该值; 2. + * 若当前页查询结果包含已知总数(isKnownTotal=true),返回当前页的总数; 3. + * 遍历所有分页,累加每一页的元素数量得到总记录数(性能较低,仅适用于总数未知场景)。 + *

+ *

+ * 注意:遍历累加时使用{@link Math#addExact(long, long)},总数溢出会抛出ArithmeticException + *

* * @return 总记录数,≥0;若总数未知且遍历后无数据,返回0 * @throws ArithmeticException 当累加总数超出Long范围时抛出 */ @Override - public Long getTotal() { - if (total != null) { - return total; + public Long getTotalCount() { + if (totalCount != null) { + return totalCount; } - if (getCurrentPage().isKnowTotal()) { - return getCurrentPage().getTotal(); + if (getCurrentPage().isKnownTotal()) { + return getCurrentPage().getTotalCount(); } long total = 0; for (Paging paging : pages()) { - if (paging.isKnowTotal()) { - return paging.getTotal(); + if (paging.isKnownTotal()) { + return paging.getTotalCount(); } total = Math.addExact(paging.getElements().count(), total); } @@ -175,7 +201,9 @@ public Long getTotal() { /** * 判断是否存在下一页(懒加载) - *

首次调用时触发{@link #getCurrentPage()}执行实际查询,后续调用返回缓存结果

+ *

+ * 首次调用时触发{@link #getCurrentPage()}执行实际查询,后续调用返回缓存结果 + *

* * @return true表示存在下一页,false表示无下一页 */ @@ -186,45 +214,48 @@ public final boolean hasNextPage() { /** * 判断是否已知总记录数 - *

判断规则: - * 1. 若已手动设置total(非null),返回true; - * 2. 若当前页查询结果包含已知总数,返回true; - * 3. 其他情况返回false(需遍历计算总数)。

+ *

+ * 判断规则: 1. 若已手动设置totalCount(非null),返回true; 2. 若当前页查询结果包含已知总数,返回true; 3. + * 其他情况返回false(需遍历计算总数)。 + *

* * @return true表示已知总记录数,false表示未知 */ @Override - public boolean isKnowTotal() { - return total != null || getCurrentPage().isKnowTotal(); + public boolean isKnownTotal() { + return totalCount != null || getCurrentPage().isKnownTotal(); } /** * 创建新的游标分页实例,跳转到指定游标位置并指定每页大小 - *

新实例会继承当前实例的“总数是否已知”状态: - * - 若当前实例已知总数(isKnowTotal=true),新实例直接复用该总数; - * - 若当前实例未知总数,新实例total为null。

+ *

+ * 新实例会继承当前实例的“总数是否已知”状态: - 若当前实例已知总数(isKnownTotal=true),新实例直接复用该总数; - + * 若当前实例未知总数,新实例totalCount为null。 + *

* - * @param cursorId 目标游标ID,作为新分页的起始位置 - * @param pageSize 新分页的每页大小,需大于0 + * @param cursor 目标游标,作为新分页的起始位置 + * @param pageSize 新分页的每页大小,必须>0(不允许为0或负数) * @return 新的CursorPaging实例,继承当前实例的总数状态 * @throws IllegalArgumentException 若pageSize≤0时抛出 */ @Override - public Paging query(K cursorId, int pageSize) { - CursorPaging paging = new CursorPaging<>(cursorId, pageSize, pagingQuery); - paging.setTotal(isKnowTotal() ? getTotal() : total); + public Paging query(K cursor, int pageSize) { + CursorPaging paging = new CursorPaging<>(cursor, pageSize, pagingQuery); + paging.setTotalCount(isKnownTotal() ? getTotalCount() : totalCount); return paging; } /** * 手动设置总记录数 - *

设置后{@link #getTotal()}会优先返回该值,无需遍历计算,提升性能

+ *

+ * 设置后{@link #getTotalCount()}会优先返回该值,无需遍历计算,提升性能 + *

* - * @param total 总记录数,可为null(表示恢复“总数未知”状态);非null时需≥0 - * @throws IllegalArgumentException 若total<0时抛出 + * @param totalCount 总记录数,可为null(表示恢复“总数未知”状态);非null时需≥0 + * @throws IllegalArgumentException 若totalCount<0时抛出 */ - public void setTotal(Long total) { - Assert.isTrue(total == null || total >= 0, "Total must be greater than or equal to 0"); - this.total = total; + public void setTotalCount(Long totalCount) { + Assert.isTrue(totalCount == null || totalCount >= 0, "Total count must be greater than or equal to 0"); + this.totalCount = totalCount; } } \ No newline at end of file diff --git a/core/src/main/java/run/soeasy/framework/core/page/OffsetPaging.java b/core/src/main/java/run/soeasy/framework/core/page/OffsetPaging.java index 01c24222c..424b0a430 100644 --- a/core/src/main/java/run/soeasy/framework/core/page/OffsetPaging.java +++ b/core/src/main/java/run/soeasy/framework/core/page/OffsetPaging.java @@ -11,56 +11,62 @@ import run.soeasy.framework.core.collection.Listable; /** - * 使用偏移量进行分页 基于偏移量(offset)和每页大小(pageSize)实现的分页机制 + * 基于偏移量的分页实现 基于偏移量(offset)和每页大小(pageSize)实现的分页机制,继承游标分页体系(偏移量作为Long型游标) * * @author soeasy.run - * * @param 分页内容的元素类型 */ public class OffsetPaging extends CursorPaging { /** - * 获取偏移量 + * 计算偏移量(基于页码和每页大小) * - * @param pageNumber 页码,从1开始 - * @param pageSize 每页大小, 不能小于0 - * @return + * @param pageNumber 页码,必须≥1(从1开始) + * @param pageSize 每页大小,必须>0(不允许为0或负数) + * @return 偏移量(从0开始) + * @throws IllegalArgumentException 页码≤0或每页大小≤0时抛出 + * @throws ArithmeticException 数值计算溢出时抛出 */ public static long getOffset(long pageNumber, long pageSize) { - Assert.isTrue(pageNumber > 0, "PageNumber must be greater than to 0"); - Assert.isTrue(pageSize > 0, "PageSize must be greater than to 0"); + Assert.isTrue(pageNumber > 0, "Page number must be greater than 0, cannot be 0 or negative"); + Assert.isTrue(pageSize > 0, "Page size must be greater than 0, cannot be 0 or negative"); return Math.multiplyExact((pageNumber - 1), pageSize); } /** - * 获取页码 + * 计算页码(基于偏移量和每页大小) * - * @param offset 偏移量, 从0开始 - * @param pageSize 每页大小, 不能小于0 - * @return 从1开始 + * @param offset 偏移量,必须≥0(从0开始) + * @param pageSize 每页大小,必须>0(不允许为0或负数) + * @return 页码(从1开始) + * @throws IllegalArgumentException 偏移量<0或每页大小≤0时抛出 + * @throws ArithmeticException 数值计算溢出时抛出 */ public static long getPageNumber(long offset, long pageSize) { Assert.isTrue(offset >= 0, "Offset must be greater than or equal to 0"); - Assert.isTrue(pageSize > 0, "PageSize must be greater than to 0"); + Assert.isTrue(pageSize > 0, "Page size must be greater than 0, cannot be 0 or negative"); return Math.addExact(offset / pageSize, 1); } /** - * 基于偏移量和内存列表创建OffsetPaging实例 + * 基于偏移量和内存列表创建OffsetPaging实例(内存分页) * * @param 分页元素类型 - * @param offset 偏移量,从0开始 - * @param pageSize 每页大小,需大于0 - * @param elements 原始数据列表,基于该列表进行内存分页 + * @param offset 偏移量,必须≥0(从0开始) + * @param pageSize 每页大小,必须>0(不允许为0或负数) + * @param elements 原始数据列表,基于该列表进行内存分页(不可为null) * @return OffsetPaging 内存分页后的实例 + * @throws IllegalArgumentException 偏移量<0或每页大小≤0时抛出 */ public static OffsetPaging of(long offset, int pageSize, List elements) { - return of(offset, pageSize, (cursorId, length) -> { - int fromIndex = Math.toIntExact(cursorId); + Assert.notNull(elements, "Elements list cannot be null"); + return of(offset, pageSize, (cursor, length) -> { + int fromIndex = Math.toIntExact(cursor); if (fromIndex >= elements.size()) { return Collections.emptyList(); } - return elements.subList(fromIndex, Math.min(Math.addExact(fromIndex, length), elements.size())); + int toIndex = Math.min(Math.addExact(fromIndex, length), elements.size()); + return elements.subList(fromIndex, toIndex); }, Function.identity(), (e) -> elements.size()); } @@ -69,11 +75,12 @@ public static OffsetPaging of(long offset, int pageSize, List elements * * @param 分页查询结果类型(元素集合) * @param 分页元素类型 - * @param offset 偏移量,从0开始 - * @param pageSize 每页大小,需大于0 - * @param offsetQuery 分页查询器:入参为偏移量、每页大小,返回元素集合Collection + * @param offset 偏移量,必须≥0(从0开始) + * @param pageSize 每页大小,必须>0(不允许为0或负数) + * @param offsetQuery 分页查询器:入参为偏移量、每页大小,返回元素集合Collection(不可为null) * @param total 总记录数(可为null,表示未知总数) * @return OffsetPaging 分页实例 + * @throws IllegalArgumentException 偏移量<0或每页大小≤0时抛出 */ public static OffsetPaging of(long offset, int pageSize, @NonNull PagingQuery> offsetQuery, Long total) { @@ -85,12 +92,13 @@ public static OffsetPaging of(long offset, int pageSize, * * @param 分页查询的原始结果类型 * @param 分页元素类型 - * @param offset 偏移量,从0开始 - * @param pageSize 每页大小,需大于0 - * @param offsetQuery 分页查询器:入参为偏移量、每页大小,返回原始查询结果T + * @param offset 偏移量,必须≥0(从0开始) + * @param pageSize 每页大小,必须>0(不允许为0或负数) + * @param offsetQuery 分页查询器:入参为偏移量、每页大小,返回原始查询结果T(不可为null) * @param elementMapper 结果映射器:将原始查询结果T转换为分页元素集合Collection(不可为null) * @param totalMapper 总数映射器:将原始查询结果T转换为总记录数(Number类型,不可为null) * @return OffsetPaging 分页实例 + * @throws IllegalArgumentException 偏移量<0或每页大小≤0时抛出 */ public static OffsetPaging of(long offset, int pageSize, @NonNull PagingQuery offsetQuery, @@ -103,16 +111,17 @@ public static OffsetPaging of(long offset, int pageSize, } /** - * 基于页码和内存列表创建OffsetPaging实例 + * 基于页码和内存列表创建OffsetPaging实例(内存分页) * * @param 分页元素类型 - * @param pageNumber 页码,从1开始 - * @param pageSize 每页大小,需大于0 - * @param elements 原始数据列表,基于该列表进行内存分页 + * @param pageNumber 页码,必须≥1(从1开始) + * @param pageSize 每页大小,必须>0(不允许为0或负数) + * @param elements 原始数据列表,基于该列表进行内存分页(不可为null) * @return OffsetPaging 内存分页后的实例 + * @throws IllegalArgumentException 页码≤0或每页大小≤0时抛出 */ public static OffsetPaging ofPageNumber(long pageNumber, int pageSize, List elements) { - return of(getPageNumber(pageNumber, pageSize), pageSize, elements); + return of(getOffset(pageNumber, pageSize), pageSize, elements); } /** @@ -120,11 +129,12 @@ public static OffsetPaging ofPageNumber(long pageNumber, int pageSize, Li * * @param 分页查询结果类型(元素集合) * @param 分页元素类型 - * @param pageNumber 页码,从1开始 - * @param pageSize 每页大小,需大于0 - * @param offsetQuery 分页查询器:入参为页码、每页大小,返回元素集合Collection + * @param pageNumber 页码,必须≥1(从1开始) + * @param pageSize 每页大小,必须>0(不允许为0或负数) + * @param offsetQuery 分页查询器:入参为偏移量、每页大小,返回元素集合Collection(不可为null) * @param total 总记录数(可为null,表示未知总数) * @return OffsetPaging 分页实例 + * @throws IllegalArgumentException 页码≤0或每页大小≤0时抛出 */ public static OffsetPaging ofPageNumber(long pageNumber, int pageSize, @NonNull PagingQuery> offsetQuery, Long total) { @@ -136,12 +146,13 @@ public static OffsetPaging ofPageNumber(long pageNumber, int pageSize, * * @param 分页查询的原始结果类型 * @param 分页元素类型 - * @param pageNumber 页码,从1开始 - * @param pageSize 每页大小,需大于0 - * @param pageNumberQuery 分页查询器:入参为页码、每页大小,返回原始查询结果T + * @param pageNumber 页码,必须≥1(从1开始) + * @param pageSize 每页大小,必须>0(不允许为0或负数) + * @param pageNumberQuery 分页查询器:入参为页码、每页大小,返回原始查询结果T(不可为null) * @param elementMapper 结果映射器:将原始查询结果T转换为分页元素集合Collection(不可为null) * @param totalMapper 总数映射器:将原始查询结果T转换为总记录数(Number类型,不可为null) * @return OffsetPaging 分页实例 + * @throws IllegalArgumentException 页码≤0或每页大小≤0时抛出 */ public static OffsetPaging ofPageNumber(long pageNumber, int pageSize, @NonNull PagingQuery pageNumberQuery, @@ -156,50 +167,57 @@ public static OffsetPaging ofPageNumber(long pageNumber, int pageSize, * 通用分页构造器(支持自定义结果映射&总数映射) * * @param 分页查询的原始结果类型 - * @param offset 偏移量,从0开始 - * @param pageSize 每页大小,需大于0 - * @param offsetQuery 分页查询器:入参为偏移量、每页大小,返回原始查询结果T + * @param offset 偏移量,必须≥0(从0开始) + * @param pageSize 每页大小,必须>0(不允许为0或负数) + * @param offsetQuery 分页查询器:入参为偏移量、每页大小,返回原始查询结果T(不可为null) * @param elementMapper 结果映射器:将原始查询结果T转换为分页元素列表Listable(不可为null) * @param totalMapper 总数映射器:将原始查询结果T转换为总记录数(Number类型,不可为null) + * @throws IllegalArgumentException 偏移量<0或每页大小≤0时抛出 */ public OffsetPaging(long offset, int pageSize, @NonNull PagingQuery offsetQuery, @NonNull Function> elementMapper, @NonNull Function totalMapper) { - super(offset, pageSize, (cursorId, length) -> { + super(offset, pageSize, (cursor, length) -> { Assert.isTrue(offset >= 0, "Offset must be greater than or equal to 0"); - T result = offsetQuery.query(cursorId, length); - Listable elements = result == null ? null : elementMapper.apply(result); - if (elements == null) { - elements = Listable.empty(); - } + T result = offsetQuery.query(cursor, length); + + Listable elements = result == null ? Listable.empty() : elementMapper.apply(result); + elements = elements == null ? Listable.empty() : elements; + Number totalNumber = result == null ? null : totalMapper.apply(result); - Long total = totalNumber == null ? null : NumberUtils.toLong(totalNumber); - Long nextCursorId; - if (total == null) { - // 未知数量 - nextCursorId = elements.hasElements() ? Math.addExact(cursorId, length) : null; + Long totalCount = totalNumber == null ? null : NumberUtils.toLong(totalNumber); + + Long nextCursor; + if (totalCount == null) { + nextCursor = elements.hasElements() ? Math.addExact(cursor, length) : null; } else { - // 已知数量 - nextCursorId = (total - cursorId) > length ? Math.addExact(cursorId, length) : null; + nextCursor = (totalCount - cursor) > length ? Math.addExact(cursor, length) : null; } - return new Cursor<>(cursorId, elements, nextCursorId, total); + + return new CursorPage<>(cursor, elements, nextCursor, totalCount); }); } + @Override + public Page getCurrentPage() { + return new Page<>(super.getCurrentPage(), getPageNumber(), getPageSize()); + } + /** * 获取当前页码 * - * @return 当前页码,从1开始 + * @return 当前页码(从1开始) */ public final long getPageNumber() { - return getPageNumber(getCursorId(), getPageSize()); + return getPageNumber(getCurrentCursor(), getPageSize()); } /** - * 跳转到指定页码 + * 跳转到指定页码(复用当前每页大小) * - * @param pageNumber 目标页码,从1开始 + * @param pageNumber 目标页码,必须≥1(从1开始) * @return 新的OffsetPaging实例 + * @throws IllegalArgumentException 页码≤0时抛出 */ public final OffsetPaging jumpToPage(long pageNumber) { return jumpToPage(pageNumber, getPageSize()); @@ -208,12 +226,13 @@ public final OffsetPaging jumpToPage(long pageNumber) { /** * 跳转到指定页码并指定每页大小 * - * @param pageNumber 目标页码,从1开始 - * @param pageSize 每页大小 + * @param pageNumber 目标页码,必须≥1(从1开始) + * @param pageSize 每页大小,必须>0(不允许为0或负数) * @return 新的OffsetPaging实例 + * @throws IllegalArgumentException 页码≤0或每页大小≤0时抛出 */ public OffsetPaging jumpToPage(long pageNumber, int pageSize) { return new OffsetPaging<>(getOffset(pageNumber, pageSize), pageSize, this::query, Function.identity(), - (e) -> e.isKnowTotal() ? e.getTotal() : null); + (e) -> e.isKnownTotal() ? e.getTotalCount() : null); } } \ No newline at end of file diff --git a/core/src/main/java/run/soeasy/framework/core/page/Page.java b/core/src/main/java/run/soeasy/framework/core/page/Page.java new file mode 100644 index 000000000..8ecf3d944 --- /dev/null +++ b/core/src/main/java/run/soeasy/framework/core/page/Page.java @@ -0,0 +1,17 @@ +package run.soeasy.framework.core.page; + +import lombok.Data; +import lombok.NonNull; + +@Data +public class Page implements PageableWrapper> { + private final Pageable source; + private final long pageNumber; + private final int pageSize; + + public Page(@NonNull Pageable source, long pageNumber, int pageSize) { + this.source = source; + this.pageNumber = pageNumber; + this.pageSize = pageSize; + } +} diff --git a/core/src/main/java/run/soeasy/framework/core/page/PageIterator.java b/core/src/main/java/run/soeasy/framework/core/page/PageIterator.java index 3c5ee3711..4ee8a16ba 100644 --- a/core/src/main/java/run/soeasy/framework/core/page/PageIterator.java +++ b/core/src/main/java/run/soeasy/framework/core/page/PageIterator.java @@ -4,48 +4,117 @@ import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; -import java.util.function.Supplier; import lombok.Getter; import lombok.NonNull; -import lombok.RequiredArgsConstructor; +import run.soeasy.framework.core.Assert; import run.soeasy.framework.core.collection.Listable; -@RequiredArgsConstructor +/** + * 基于元素迭代器的分页迭代器 + *

+ * 将单元素迭代器(Iterator)拆分为按页划分的Pageable迭代器, + * 每次迭代返回包含指定数量元素的分页对象,分页位置通过偏移量(offset)标识 + *

+ * + * @param 分页元素的类型 + * @author soeasy.run + */ public class PageIterator implements Iterator> { + /** + * 每页大小,需大于0 + */ @Getter private final int pageSize; + + /** + * 原始元素迭代器,提供分页的基础数据来源,不可为null + */ @NonNull private final Iterator iterator; - private volatile int page = 0; - private volatile Supplier> supplier; + /** + * 下一页的页码(从1开始计数),用于调用OffsetPaging.getOffset计算偏移量 + */ + private int pageNumber = 1; + + /** + * 缓存下一页的分页对象,减少重复计算 + */ + private Pageable nextPageable; + + /** + * 创建分页迭代器 + * + * @param pageSize 每页大小,必须大于0 + * @param iterator 原始元素迭代器,不可为null + * @throws IllegalArgumentException 若pageSize≤0时抛出 + * @throws NullPointerException 若iterator为null时抛出 + */ + public PageIterator(int pageSize, @NonNull Iterator iterator) { + Assert.isTrue(pageSize > 0, "PageSize must be greater than 0"); + this.pageSize = pageSize; + this.iterator = iterator; + } + + /** + * 判断是否存在下一页数据 + *

+ * 首次调用时会预加载下一页的元素并缓存分页对象,后续调用直接返回缓存状态 + *

+ * + * @return true表示存在下一页,false表示无更多分页数据 + */ @Override public synchronized boolean hasNext() { - if (supplier == null && iterator.hasNext()) { - List list = new ArrayList<>(pageSize); - while (iterator.hasNext() && list.size() < pageSize) { - list.add(iterator.next()); - } - - long offset = page * pageSize; - Pageable pageable = new Cursor(offset, Listable.forCollection(list), offset + pageSize, - null); - supplier = () -> pageable; + // 已缓存下一页,直接返回存在 + if (nextPageable != null) { + return true; + } + // 原始迭代器无元素,无更多分页 + if (!iterator.hasNext()) { + return false; + } + + // 收集当前页的元素(最多pageSize个) + List currentPageElements = new ArrayList<>(pageSize); + while (iterator.hasNext() && currentPageElements.size() < pageSize) { + currentPageElements.add(iterator.next()); } - return supplier != null; + + // 计算当前页偏移量(复用框架内置工具方法,保证逻辑统一) + long offset = OffsetPaging.getOffset(pageNumber, pageSize); + // 页码自增,为下一页偏移量计算做准备 + pageNumber++; + + // 计算下一页游标ID,防护数值溢出并补充上下文 + Long nextCursorId = null; + try { + nextCursorId = Math.addExact(offset, pageSize); + } catch (ArithmeticException e) { + throw new ArithmeticException( + "Calculate next cursor id failed: overflow (offset=" + offset + ", pageSize=" + pageSize + ")"); + } + + // 构建分页对象并缓存 + nextPageable = new CursorPage<>(offset, Listable.forCollection(currentPageElements), nextCursorId, null); + return true; } + /** + * 获取下一页的分页对象 + * + * @return 下一页的Pageable实例 + * @throws NoSuchElementException 若不存在下一页时抛出 + */ @Override public synchronized Pageable next() { if (!hasNext()) { - throw new NoSuchElementException(); - } - try { - return supplier.get(); - } finally { - supplier = null; + throw new NoSuchElementException("No more pages available"); } + // 取出缓存的分页对象并清空缓存 + Pageable currentPage = nextPageable; + nextPageable = null; + return currentPage; } - -} +} \ No newline at end of file diff --git a/core/src/main/java/run/soeasy/framework/core/page/Pageable.java b/core/src/main/java/run/soeasy/framework/core/page/Pageable.java index 4001451b1..98741bccb 100644 --- a/core/src/main/java/run/soeasy/framework/core/page/Pageable.java +++ b/core/src/main/java/run/soeasy/framework/core/page/Pageable.java @@ -3,50 +3,55 @@ import run.soeasy.framework.core.collection.Listable; /** - * 可分页的对象 定义基于游标的分页机制,用于高效处理大数据集的分页查询 + * 基于游标的分页接口,定义高效处理大数据集分页查询的核心方法 * * @author soeasy.run - * - * @param 游标的类型,用于标识分页位置 - * @param 分页内容的元素类型 + * @param 游标类型(如 Long/String/复合标识),用于标识分页起始位置 + * @param 分页内容的元素类型(如业务实体类) */ public interface Pageable extends Listable { - /** - * 获取当前页的游标ID 游标ID用于标识当前分页位置,通常是上一页最后一个元素的唯一标识 - * - * @return 当前游标ID,首次请求时可为null - */ - K getCursorId(); + /** + * 获取当前页的游标(分页定位标识) + * 通常为上一页最后一个元素的唯一标识,用于定位当前分页的起始位置 + * + * @return 当前游标,首次请求时可为null + */ + K getCurrentCursor(); - /** - * 获取下一页的游标ID 用于标识下一页数据的起始位置 - * - * @return 下一页游标ID,若无下一页则返回null - */ - K getNextCursorId(); + /** + * 获取下一页的游标(分页定位标识) + * 用于标识下一页数据的起始位置 + * + * @return 下一页游标,若无下一页则返回null + */ + K getNextCursor(); - /** - * 判断是否存在下一页 基于{@link #getNextCursorId()}的返回值进行判断 - * - * @return 存在下一页返回true,否则返回false - */ - default boolean hasNextPage() { - return getNextCursorId() != null; - } + /** + * 判断是否存在下一页 + * 基于{@link #getNextCursor()}的返回值进行判断 + * + * @return 存在下一页返回true,否则返回false + */ + default boolean hasNextPage() { + return getNextCursor() != null; + } - /** - * 判断是否已知总数 某些场景下(如大数据集)可能无法高效获取总数,此时返回false 未知总数的分页在调用getTotal()时会使用代价较高的循环计数 - * - * @return 如果已知总数返回true,否则返回false - */ - default boolean isKnowTotal() { - return getTotal() != null; - } + /** + * 判断是否已知总记录数 + * 某些场景下(如大数据集)可能无法高效获取总数,此时返回false; + * 未知总数的分页在调用getTotalCount()时会使用代价较高的循环计数 + * + * @return 如果已知总数返回true,否则返回false + */ + default boolean isKnownTotal() { + return getTotalCount() != null; + } - /** - * 获取总记录数 如果isKnowTotal()返回false,此方法可能会触发全量数据遍历,性能开销较大 - * - * @return 总记录数 - */ - Long getTotal(); + /** + * 获取总记录数 + * 如果isKnownTotal()返回false,此方法可能会触发全量数据遍历,性能开销较大 + * + * @return 总记录数 + */ + Long getTotalCount(); } \ No newline at end of file diff --git a/core/src/main/java/run/soeasy/framework/core/page/PageableWrapper.java b/core/src/main/java/run/soeasy/framework/core/page/PageableWrapper.java new file mode 100644 index 000000000..6ec68ad1e --- /dev/null +++ b/core/src/main/java/run/soeasy/framework/core/page/PageableWrapper.java @@ -0,0 +1,31 @@ +package run.soeasy.framework.core.page; + +import run.soeasy.framework.core.collection.ListableWrapper; + +@FunctionalInterface +public interface PageableWrapper> extends Pageable, ListableWrapper { + @Override + default K getCurrentCursor() { + return getSource().getCurrentCursor(); + } + + @Override + default K getNextCursor() { + return getSource().getNextCursor(); + } + + @Override + default boolean hasNextPage() { + return getSource().hasNextPage(); + } + + @Override + default boolean isKnownTotal() { + return getSource().isKnownTotal(); + } + + @Override + default Long getTotalCount() { + return getSource().getTotalCount(); + } +} diff --git a/core/src/main/java/run/soeasy/framework/core/page/Paging.java b/core/src/main/java/run/soeasy/framework/core/page/Paging.java index d384f3a6f..4f9afded1 100644 --- a/core/src/main/java/run/soeasy/framework/core/page/Paging.java +++ b/core/src/main/java/run/soeasy/framework/core/page/Paging.java @@ -7,82 +7,89 @@ import run.soeasy.framework.core.collection.LinkedIterator; /** - * 分页接口 定义基于游标的分页查询机制,支持获取分页元数据和遍历所有页 + * 基于游标的分页接口 定义基于游标的分页查询机制,支持获取分页元数据、页跳转及全量页的延迟遍历 * * @author soeasy.run - * - * @param 游标的类型,用于标识分页位置 - * @param 分页内容的元素类型 + * @param 游标类型,用于标识分页起始位置(如Long/String/复合标识) + * @param 分页内容的元素类型(如业务实体类) */ public interface Paging extends Pageable, PagingQuery> { /** - * 获取总页数 + * 计算总页数(适配大数据量,避免数值溢出) + *

+ * 公式说明:(totalCount - 1) / pageSize + 1
+ * 优势:替代 (totalCount + pageSize - 1)/pageSize,避免大数相加导致的溢出,且结果等价(totalCount>0时) + *

* - * @param total 总记录数,需 ≥ 0 - * @param pageSize 每页大小,需 > 0 + * @param totalCount 总记录数,必须 ≥ 0 + * @param pageSize 每页大小,必须 > 0 * @return 总页数(≥ 0) - * @throws ArithmeticException 当计算过程中发生数值溢出时抛出 + * @throws IllegalArgumentException 当totalCount < 0 或 pageSize ≤ 0时抛出 + * @throws ArithmeticException 当计算过程中(如加法)发生数值溢出时抛出 */ - public static long getPages(long total, long pageSize) { - Assert.isTrue(total >= 0, "Total must be greater than or equal to 0"); + public static long getTotalPages(long totalCount, long pageSize) { + Assert.isTrue(totalCount >= 0, "Total count must be greater than or equal to 0"); Assert.isTrue(pageSize > 0, "PageSize must be greater than 0"); - if (total == 0) { + if (totalCount == 0) { return 0; } - // 3. 等价变形公式:(total - 1) / pageSize + 1(避免total + pageSize溢出) - // 原公式 (total + pageSize -1)/pageSize 等价于 (total-1)/pageSize + 1(total>0时) - // 优势:仅做减法/除法/加法,无大数相加,从根源避免溢出 - return Math.addExact(((total - 1) / pageSize), 1); + // 等价变形公式:(totalCount - 1) / pageSize + 1(避免totalCount + pageSize溢出) + return Math.addExact(((totalCount - 1) / pageSize), 1); } /** - * 获取总页数 + * 获取总页数(基于当前分页的总记录数和页大小计算) * * @return 总页数 - * @throws ArithmeticException 如果pageSize为0 + * @throws IllegalArgumentException 当总记录数<0 或 页大小≤0时抛出 + * @throws ArithmeticException 当计算过程中发生数值溢出时抛出 */ - default long getPages() { - return getPages(getTotal(), getPageSize()); + default long getTotalPages() { + Long total = getTotalCount(); + if (total == null) { + throw new UnsupportedOperationException("Unknown total number"); + } + return getTotalPages(total, getPageSize()); } /** * 获取每页数量 * - * @return 每页数量,如果返回0表示每页数量不确定 + * @return 每页数量 */ int getPageSize(); /** - * 获取下一页 + * 获取下一页的分页对象 * * @return 下一页的分页对象 - * @throws NoSuchElementException 如果没有下一页 + * @throws NoSuchElementException 如果没有下一页数据时抛出 */ default Paging nextPage() { if (!hasNextPage()) { throw new NoSuchElementException("There is no next page"); } - return jumpTo(getNextCursorId()); + return jumpTo(getNextCursor()); } /** - * 获取所有页的元素集合 支持流式遍历所有页,延迟加载后续页的数据 + * 获取所有页的元素集合(延迟加载) 支持流式遍历所有页,后续页数据仅在遍历到时分页查询,避免一次性加载全量数据(适配大数据集) * - * @return 包含所有页的元素集合 + * @return 包含所有页的延迟加载元素集合 */ default Elements> pages() { return Elements.of(() -> new LinkedIterator<>(this, Paging::hasNextPage, Paging::nextPage)); } /** - * 跳转到指定游标位置的页 + * 跳转到指定游标位置的分页 * - * @param cursorId 目标页的游标ID - * @return 指定位置的分页对象 + * @param cursor 目标页的游标(首次查询可为null) + * @return 指定游标位置的分页对象 */ - default Paging jumpTo(K cursorId) { - return query(cursorId, getPageSize()); + default Paging jumpTo(K cursor) { + return query(cursor, getPageSize()); } } \ No newline at end of file diff --git a/core/src/main/java/run/soeasy/framework/core/type/ClassUtils.java b/core/src/main/java/run/soeasy/framework/core/type/ClassUtils.java index 2d23fe941..5ff618648 100644 --- a/core/src/main/java/run/soeasy/framework/core/type/ClassUtils.java +++ b/core/src/main/java/run/soeasy/framework/core/type/ClassUtils.java @@ -17,7 +17,7 @@ import lombok.experimental.UtilityClass; import run.soeasy.framework.core.collection.ArrayUtils; import run.soeasy.framework.core.collection.Listable; -import run.soeasy.framework.core.page.Cursor; +import run.soeasy.framework.core.page.CursorPage; import run.soeasy.framework.core.page.CursorPaging; import run.soeasy.framework.core.page.Paging; @@ -291,7 +291,7 @@ public static Paging, Class> getInterfaces(@NonNull Class sourceC return new CursorPaging, Class>(sourceClass, (clazz, size) -> { Class[] interfaces = clazz.getInterfaces(); List> list = interfaces == null ? Collections.emptyList() : Arrays.asList(interfaces); - return new Cursor<>(clazz, Listable.forCollection(list), clazz.getSuperclass(), null); + return new CursorPage<>(clazz, Listable.forCollection(list), clazz.getSuperclass(), null); }); } diff --git a/core/src/test/java/run/soeasy/framework/core/page/OffsetPagingTest.java b/core/src/test/java/run/soeasy/framework/core/page/OffsetPagingTest.java index e5d524057..bbbd943ea 100644 --- a/core/src/test/java/run/soeasy/framework/core/page/OffsetPagingTest.java +++ b/core/src/test/java/run/soeasy/framework/core/page/OffsetPagingTest.java @@ -19,8 +19,8 @@ public void test() { for (int i = 1; i < count; i++) { OffsetPaging paginations = OffsetPaging.of(0, i, list); - Assert.assertTrue(paginations.jumpToPage(paginations.getPages() + 1).getElements().count() == 0); - Assert.assertTrue(!paginations.jumpToPage(paginations.getPages()).hasNextPage()); + Assert.assertTrue(paginations.jumpToPage(paginations.getTotalPages() + 1).getElements().count() == 0); + Assert.assertTrue(!paginations.jumpToPage(paginations.getTotalPages()).hasNextPage()); String[] leftArray = list.toArray(new String[0]); String[] rightArray = paginations.pages().flatMap((e) -> e.getElements()).toArray(String[]::new); Assert.assertArrayEquals(leftArray, rightArray); From 0d73c67ce6d555a3bae8db08aec3c118e8068fed Mon Sep 17 00:00:00 2001 From: wcnnkh <416078075@qq.com> Date: Wed, 10 Dec 2025 22:39:03 +0800 Subject: [PATCH 09/16] =?UTF-8?q?=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/run/soeasy/framework/aop/Aop.java | 20 +- .../DelegatedObjectExecutionInterceptor.java | 2 +- .../aop/ExecutionInterceptorRegistry.java | 3 +- .../framework/aop/ExecutionInterceptors.java | 10 +- .../soeasy/framework/aop/ProxyFactories.java | 3 +- .../soeasy/framework/beans/BeanFormat.java | 11 +- .../framework/beans/BeanInfoFactory.java | 8 +- .../framework/beans/BeanInfoMapping.java | 44 + .../soeasy/framework/beans/BeanMapper.java | 2 +- .../framework/beans/BeanPropertyTemplate.java | 71 - .../run/soeasy/framework/beans/BeanUtils.java | 30 +- .../beans/ConfigurableBeanInfoFactory.java | 2 + .../soeasy/framewrok/beans/BeanUtilsTest.java | 2 +- .../framework/codec/crypto/HmacMD5.java | 29 - .../framework/codec/crypto/HmacSHA1.java | 29 - .../framework/codec/format/CharsetCodec.java | 11 +- .../codec/format/KeyValueFormat.java | 117 +- .../soeasy/framework/core/StringUtils.java | 32 +- .../AbstractAnnotationPropertyMapping.java | 103 +- .../core/annotation/AnnotationProperties.java | 158 +- .../CustomizeAnnotationPropertyMapping.java | 17 +- .../annotation/MergedAnnotatedElement.java | 2 +- .../core/annotation/MergedAnnotation.java | 251 ++-- .../annotation/SynthesizedAnnotation.java | 7 +- .../framework/core/attribute/Attributes.java | 6 +- .../core/attribute/AttributesWrapper.java | 4 +- .../core/attribute/EditableAttributes.java | 4 +- .../core/attribute/SimpleAttributes.java | 8 +- .../collection/AbstractMultiValueMap.java | 103 -- .../core/collection/ArrayDictionary.java | 172 --- .../framework/core/collection/ArrayUtils.java | 9 +- .../core/collection/CacheableElements.java | 191 --- .../core/collection/CloseableIterator.java | 2 +- .../core/collection/CollectionElements.java | 73 - .../collection/CollectionElementsWrapper.java | 115 -- .../{factory => }/CollectionFactory.java | 2 +- .../core/collection/CollectionUtils.java | 1278 ++++++----------- .../core/collection/CollectionWrapper.java | 199 --- .../core/collection/ConvertedElements.java | 101 -- .../core/collection/ConvertedProvider.java | 131 -- .../collection/ConvertibleEnumeration.java | 84 -- .../core/collection/ConvertibleIterable.java | 68 - .../core/collection/ConvertibleIterator.java | 98 -- .../core/collection/DefaultMultiValueMap.java | 43 - .../framework/core/collection/Dictionary.java | 95 -- .../core/collection/DictionaryWrapper.java | 129 -- .../framework/core/collection/Elements.java | 366 ----- .../core/collection/ElementsWrapper.java | 261 ---- .../core/collection/EmptyElements.java | 150 -- .../core/collection/EmptyProvider.java | 55 - .../framework/core/collection/Enumerable.java | 26 - .../core/collection/EnumerableToIterable.java | 54 - .../collection/EnumerationToIterator.java | 62 +- .../collection/IterableElementsWrapper.java | 52 - .../core/collection/IterableProvider.java | 60 - .../core/collection/IterableToEnumerable.java | 51 - .../core/collection/IterableWrapper.java | 49 - .../core/collection/IterationIterator.java | 46 - .../collection/IteratorToEnumeration.java | 59 +- .../framework/core/collection/KeyValues.java | 38 - .../core/collection/KeyValuesWrapper.java | 42 - .../framework/core/collection/Keys.java | 28 - .../core/collection/KeysWrapper.java | 39 - .../core/collection/KnownSizeElements.java | 77 - .../core/collection/KnownSizeProvider.java | 55 - .../core/collection/LinkedMultiValueMap.java | 121 +- .../core/collection/ListElements.java | 42 - .../core/collection/ListElementsWrapper.java | 171 --- .../collection/{factory => }/ListFactory.java | 2 +- .../core/collection/ListWrapper.java | 192 --- .../framework/core/collection/Listable.java | 73 - .../core/collection/ListableWrapper.java | 37 - .../core/collection/MapDictionary.java | 229 --- .../collection/{factory => }/MapFactory.java | 2 +- .../framework/core/collection/MapWrapper.java | 280 ---- .../core/collection/MergedElements.java | 110 -- .../core/collection/MergedProvider.java | 77 - .../core/collection/MultiValueMap.java | 235 +-- .../core/collection/MultiValueMapWrapper.java | 116 -- .../core/collection/PreviousIterator.java | 25 + .../framework/core/collection/Provider.java | 136 -- .../core/collection/ProviderWrapper.java | 126 -- .../framework/core/collection/Reloadable.java | 42 - .../collection/ReloadableElementsWrapper.java | 111 -- .../framework/core/collection/Sequential.java | 69 - .../core/collection/SequentialIterator.java | 83 -- .../core/collection/SetElements.java | 44 - .../core/collection/SetElementsWrapper.java | 138 -- .../collection/{factory => }/SetFactory.java | 2 +- .../framework/core/collection/SetWrapper.java | 243 ---- .../StandardCollectionElements.java | 15 - .../collection/StandardIterableElements.java | 105 -- .../core/collection/StandardListElements.java | 45 - .../core/collection/StandardSetElements.java | 45 - .../StandardStreamableElements.java | 72 - .../framework/core/collection/Streamable.java | 521 ------- .../collection/StreamableElementsWrapper.java | 65 - .../core/collection/StreamableWrapper.java | 439 ------ .../core/collection/function/FlatMerger.java | 39 - .../core/collection/function/MapMerger.java | 55 - .../core/concurrent/LockableContainer.java | 988 +++++++------ .../core/convert/ConversionService.java | 8 +- .../core/convert/ConverterRegistry.java | 266 ++-- .../framework/core/convert/Converters.java | 104 +- .../support/ArrayToCollectionConverter.java | 2 +- .../CollectionToCollectionConverter.java | 2 +- .../convert/support/MapToMapConverter.java | 2 +- .../support/ObjectToCollectionConverter.java | 2 +- .../core/convert/value/TypedValue.java | 21 +- .../core/convert/value/TypedValueWrapper.java | 4 +- .../core/domain/CharSequenceTemplate.java | 379 ++--- .../framework/core/domain/JavaVersion.java | 6 +- .../framework/core/domain/JoinVersion.java | 234 +-- .../core/domain/KeyValueWrapper.java | 5 + .../framework/core/domain/NumberValue.java | 8 +- .../framework/core/domain/ParentDiscover.java | 8 +- .../soeasy/framework/core/domain/Range.java | 6 +- .../soeasy/framework/core/domain/Value.java | 8 +- .../framework/core/domain/ValueWrapper.java | 4 +- .../soeasy/framework/core/domain/Version.java | 4 +- .../core/exchange/AbstractChannel.java | 6 +- .../core/exchange/{event => }/BaseEvent.java | 2 +- .../framework/core/exchange/BatchChannel.java | 6 +- .../core/exchange/BatchDispatcher.java | 6 +- .../core/exchange/BatchEventDispatcher.java | 71 + .../core/exchange/BatchListenable.java | 6 +- .../core/exchange/BatchListenableChannel.java | 6 +- .../core/exchange/BatchListener.java | 6 +- .../core/exchange/BatchPublisher.java | 6 +- .../exchange/{event => }/ChangeEvent.java | 2 +- .../core/exchange/{event => }/ChangeType.java | 2 +- .../framework/core/exchange/Channel.java | 4 +- .../core/exchange/CollectionRegistry.java | 194 +++ .../CompletableToListenableFutureAdapter.java | 212 +++ .../core/exchange/CompositeOperation.java | 353 +++++ .../core/exchange/{future => }/Confirm.java | 18 +- .../core/exchange/DisposableDispatcher.java | 90 ++ .../core/exchange/EmptyRegistrations.java | 12 - .../core/exchange/EventDispatcher.java | 100 ++ .../core/exchange/FakeBatchChannel.java | 13 +- .../core/exchange/FakeBatchDispatcher.java | 2 +- .../core/exchange/FakeBatchListenable.java | 8 +- .../exchange/FakeBatchListenableChannel.java | 2 +- .../core/exchange/FakeBatchListener.java | 6 +- .../core/exchange/FakeBatchPublisher.java | 10 +- .../core/exchange/FakeSingleChannel.java | 10 +- .../core/exchange/FakeSingleDispatcher.java | 4 +- .../core/exchange/FakeSingleListenable.java | 6 +- .../exchange/FakeSingleListenableChannel.java | 4 +- .../core/exchange/FakeSingleListener.java | 6 +- .../core/exchange/FakeSinglePublisher.java | 8 +- .../core/exchange/IgnorePublisher.java | 4 +- .../core/exchange/KeyValueRegistry.java | 67 + .../{event => }/LifecycleDispatcher.java | 5 +- .../framework/core/exchange/Listenable.java | 2 +- .../core/exchange/ListenableFuture.java | 128 ++ .../core/exchange/ListenableFutureTask.java | 172 +++ .../{future => }/ListenableFutureWrapper.java | 25 +- .../core/exchange/ListenableOperation.java | 73 + .../exchange/ListenableOperationWrapper.java | 20 + .../core/exchange/ListenableStage.java | 192 +++ .../core/exchange/ListenableWrapper.java | 18 + .../framework/core/exchange/MapRegistry.java | 295 ++++ .../framework/core/exchange/Operation.java | 218 +++ .../core/exchange/OperationWrapper.java | 72 + .../core/exchange/{future => }/Promise.java | 2 +- .../framework/core/exchange/Publisher.java | 2 +- .../framework/core/exchange/Receipt.java | 78 - .../core/exchange/ReceiptWrapper.java | 63 - .../framework/core/exchange/Receipted.java | 118 -- .../framework/core/exchange/Receipts.java | 43 - .../framework/core/exchange/Registed.java | 76 - .../framework/core/exchange/Registration.java | 130 -- .../core/exchange/RegistrationWrapper.java | 66 - .../core/exchange/Registrations.java | 117 -- .../core/exchange/RegistrationsWrapper.java | 69 - .../framework/core/exchange/Registry.java | 80 ++ .../ScheduledListenableFuture.java | 2 +- .../ScheduledListenableFutureAdapter.java | 6 +- .../SettableListenableFuture.java | 6 +- .../soeasy/framework/core/exchange/Stage.java | 278 ++++ .../core/exchange/SuccessfullyRegistered.java | 81 -- .../exchange/container/AbstractContainer.java | 83 -- .../container/AbstractEntryRegistration.java | 36 - .../AbstractLifecycleRegistration.java | 114 -- .../AbstractPayloadRegistration.java | 66 - .../container/AtomicElementRegistration.java | 73 - .../container/AtomicEntryRegistration.java | 88 -- .../exchange/container/BatchRegistration.java | 121 -- .../core/exchange/container/Container.java | 99 -- .../exchange/container/ContainerWrapper.java | 118 -- .../DisposableKeyValueRegistration.java | 84 -- .../container/DisposableRegistration.java | 59 - .../container/ElementRegistration.java | 53 - .../container/ElementRegistrationWrapped.java | 70 - .../container/ElementRegistrationWrapper.java | 110 -- .../exchange/container/EntryRegistration.java | 18 - .../container/EntryRegistrationWrapped.java | 71 - .../container/EntryRegistrationWrapper.java | 85 -- .../container/InterceptableRegisration.java | 136 -- .../container/KeyValueRegistration.java | 34 - .../KeyValueRegistrationWrapped.java | 80 -- .../KeyValueRegistrationWrapper.java | 69 - .../exchange/container/KeyValueRegistry.java | 62 - .../container/LifecycleRegistration.java | 49 - .../LifecycleRegistrationWrapper.java | 48 - .../container/LimitableRegistration.java | 152 -- .../container/LimitableRegistrations.java | 93 -- .../exchange/container/MappedContainer.java | 86 -- .../container/MappedPayloadRegistration.java | 54 - .../exchange/container/MappedRegistry.java | 90 -- .../exchange/container/PayloadRegisted.java | 29 - .../container/PayloadRegistration.java | 109 -- .../container/PayloadRegistrationWrapped.java | 79 - .../container/PayloadRegistrationWrapper.java | 57 - .../container/RegistrationException.java | 23 - .../container/RegistrationWrapped.java | 161 --- .../core/exchange/container/Registry.java | 47 - .../exchange/container/RegistryWrapper.java | 63 - .../StandardPayloadRegistration.java | 68 - .../collection/ArrayListContainer.java | 63 - .../collection/CollectionContainer.java | 587 -------- .../container/collection/ListContainer.java | 216 --- .../container/collection/QueueContainer.java | 183 --- .../container/map/DefaultMapContainer.java | 56 - .../exchange/container/map/MapContainer.java | 757 ---------- .../container/map/MultiValueMapContainer.java | 523 ------- .../container/map/TreeMapContainer.java | 111 -- .../container/map/TreeSetContainer.java | 118 -- .../exchange/event/BatchEventDispatcher.java | 74 - .../exchange/event/DisposableDispatcher.java | 112 -- .../core/exchange/event/EventDispatcher.java | 109 -- .../CompletableToListenableFutureAdapter.java | 217 --- .../exchange/future/ListenableFuture.java | 99 -- .../exchange/future/ListenableFutureTask.java | 173 --- .../exchange/future/ListenableReceipt.java | 89 -- .../future/ListenableReceiptWrapper.java | 75 - .../future/ListenableRegistration.java | 81 -- .../future/ListenableRegistrationWrapper.java | 87 -- .../framework/core/exchange/future/Stage.java | 205 --- .../core/execute/ExecutableMetadata.java | 148 +- .../execute/ExecutableMetadataWrapper.java | 10 +- .../core/execute/ParameterTemplate.java | 40 - .../ConfigurableParameterNameDiscoverer.java | 94 +- .../ExecutableExceptionTypeDescriptors.java | 90 +- .../ExecutableParameterDescriptor.java | 2 +- .../reflect/ExecutableParameterMapping.java | 112 ++ .../reflect/ExecutableParameterTemplate.java | 169 --- .../execute/reflect/ReflectionExecutable.java | 23 +- .../core/execute/reflect/ReflectionField.java | 2 +- .../execute/reflect/ReflectionMethod.java | 8 +- .../execute/reflect/ReflectionProperty.java | 2 +- .../templates => mapping}/ChainMapper.java | 3 +- .../framework/core/mapping/DefaultMapper.java | 421 ++++++ .../FilterableMapper.java | 82 +- .../templates => mapping}/GenericMapper.java | 25 +- .../MappedMapper.java} | 45 +- .../templates => mapping}/Mapper.java | 3 +- .../templates => mapping}/MappingContext.java | 3 +- .../templates => mapping}/MappingFactory.java | 3 +- .../templates => mapping}/MappingFilter.java | 3 +- .../core/mapping/MappingProvider.java | 89 ++ .../StreamableMapper.java} | 98 +- .../templates => mapping}/ValueMapper.java | 3 +- .../property/ClassMemberTemplateFactory.java | 2 +- .../property/ClassMemberTemplateRegistry.java | 92 ++ .../property/Cloner.java | 2 +- .../property/IndexedPropertyMapping.java | 27 + .../core/mapping/property/MapMapping.java | 154 ++ .../property/MappedPropertyMapping.java | 27 + .../property/MultiMappedPropertyMapping.java | 29 + .../property/NamedAccessibleDescriptor.java | 4 +- .../core/mapping/property/ObjectMapping.java | 53 + .../property/ObjectPropertyAccessor.java | 2 +- .../property/ObjectTemplateFactory.java | 6 +- .../property/ObjectTemplateRegistry.java | 83 ++ .../property/Property.java | 2 +- .../property/PropertyAccessor.java | 2 +- .../property/PropertyDescriptor.java | 2 +- .../property/PropertyDescriptorWrapper.java | 2 +- .../core/mapping/property/PropertyMapper.java | 279 ++++ .../mapping/property/PropertyMapping.java | 161 +++ .../property/PropertyMappingFilter.java | 6 +- .../property/PropertyMappingPredicate.java | 85 ++ .../property/PropertyMappingWrapper.java | 86 ++ .../property/PropertyWrapper.java | 2 +- .../property/StreamablePropertyMapping.java | 22 + .../framework/core/page/CursorPage.java | 13 +- .../framework/core/page/CursorPaging.java | 22 +- .../framework/core/page/OffsetPaging.java | 16 +- .../framework/core/page/PageIterator.java | 4 +- .../soeasy/framework/core/page/Pageable.java | 81 +- .../framework/core/page/PageableWrapper.java | 10 +- .../soeasy/framework/core/page/Paging.java | 6 +- .../framework/core/spi/AndConfigured.java | 46 - .../soeasy/framework/core/spi/AndInclude.java | 89 -- .../core/spi/CompositeServiceDiscoverer.java | 34 + .../framework/core/spi/Configurable.java | 16 +- .../core/spi/ConfigurableProviderFactory.java | 46 - .../core/spi/ConfigurableServices.java | 379 ++--- .../soeasy/framework/core/spi/Configured.java | 77 - .../framework/core/spi/ConfiguredWrapper.java | 68 - .../core/spi/ConvertedConfigured.java | 88 -- .../framework/core/spi/ConvertedInclude.java | 88 -- .../soeasy/framework/core/spi/Include.java | 63 - .../framework/core/spi/IncludeWrapper.java | 69 - .../soeasy/framework/core/spi/Included.java | 82 -- .../soeasy/framework/core/spi/Includes.java | 83 -- .../framework/core/spi/NativeProvider.java | 88 -- .../framework/core/spi/ProviderFactory.java | 30 - .../framework/core/spi/ServiceContainer.java | 309 ---- .../framework/core/spi/ServiceDiscoverer.java | 84 ++ .../framework/core/spi/ServiceInjector.java | 6 +- .../framework/core/spi/ServiceInjectors.java | 45 +- .../soeasy/framework/core/spi/ServiceMap.java | 109 +- .../framework/core/spi/ServiceRegistry.java | 14 + .../soeasy/framework/core/spi/Services.java | 434 +----- .../framework/core/spi/SystemProperties.java | 318 ++-- .../core/spi/SystemProviderFactory.java | 84 -- .../core/spi/SystemServiceDiscoverer.java | 98 ++ .../framework/core/spi/TypeServiceMap.java | 24 + .../core/streaming/CachedStreamable.java | 167 +++ .../core/streaming/EmptyMapping.java | 9 + .../EmptyStreamable.java | 4 +- .../core/streaming/IndexedMapping.java | 20 + .../core/streaming/MappedMapping.java | 112 ++ .../framework/core/streaming/Mapping.java | 273 ++++ .../core/streaming/MappingWrapper.java | 82 ++ .../core/streaming/MergedStreamable.java | 71 + .../core/streaming/MultiMappedMapping.java | 115 ++ .../NoUniqueElementException.java | 2 +- .../core/streaming/PureMappedStreamable.java | 99 ++ .../framework/core/streaming/Streamable.java | 1056 ++++++++++++++ .../core/streaming/StreamableMapping.java | 27 + .../core/streaming/StreamableWrapper.java | 530 +++++++ .../core/streaming/TransformedStreamable.java | 31 + .../framework/core/streaming/ZipIterator.java | 189 +++ .../core/streaming/ZipStreamable.java | 107 ++ .../function/Filter.java | 12 +- .../core/streaming/function/FlatMerger.java | 34 + .../core/streaming/function/MapMerger.java | 52 + .../function/Merger.java | 2 +- .../function/PropertiesMerger.java | 11 +- .../function/Selector.java | 12 +- .../function/Weighted.java | 2 +- .../function/WeightedSource.java | 2 +- .../framework/core/time/TimeFormatter.java | 2 +- .../core/transform/TransformationService.java | 8 +- .../core/transform/TransformerRegistry.java | 30 +- .../core/transform/Transformers.java | 95 +- .../collection/MapEntryAccessor.java | 142 -- .../transform/collection/MapEntryMapping.java | 131 -- .../property/ArrayPropertyMapping.java | 58 - .../property/ArrayPropertyTemplate.java | 60 - .../property/ArrayTypedProperties.java | 57 - .../property/ClassMemberTemplateRegistry.java | 98 -- .../property/MapPropertyMapping.java | 58 - .../property/MapPropertyTemplate.java | 60 - .../property/MapTypedProperties.java | 57 - .../transform/property/ObjectProperties.java | 51 - .../property/ObjectTemplateRegistry.java | 95 -- .../transform/property/PropertyMapper.java | 268 ---- .../transform/property/PropertyMapping.java | 85 -- .../property/PropertyMappingPredicate.java | 83 -- .../property/PropertyMappingWrapper.java | 85 -- .../transform/property/PropertyTemplate.java | 143 -- .../property/PropertyTemplateProperties.java | 126 -- .../property/PropertyTemplateWrapper.java | 145 -- .../transform/property/TypedProperties.java | 60 - .../property/TypedPropertiesWrapper.java | 66 - .../transform/templates/ArrayMapping.java | 85 -- .../transform/templates/ArrayTemplate.java | 56 - .../templates/ArrayTemplateMapping.java | 83 -- .../transform/templates/DefaultMapper.java | 417 ------ .../core/transform/templates/MapMapping.java | 74 - .../core/transform/templates/MapTemplate.java | 71 - .../templates/MapTemplateMapping.java | 82 -- .../core/transform/templates/Mapping.java | 56 - .../transform/templates/MappingProvider.java | 89 -- .../transform/templates/MappingWrapper.java | 74 - .../core/transform/templates/Template.java | 89 -- .../transform/templates/TemplateMapping.java | 89 -- .../templates/TemplateMappingWrapper.java | 92 -- .../templates/TemplateProperties.java | 178 --- .../transform/templates/TemplateWrapper.java | 78 - .../core/transmittable/Inheriter.java | 77 - .../core/transmittable/InheriterBackup.java | 83 -- .../core/transmittable/InheriterCapture.java | 91 -- .../registry/AnyInheriterRegistry.java | 31 - .../registry/InheriterRegistry.java | 57 - .../registry/ThreadLocalInheriter.java | 75 - .../core/transmittable/wrapped/Captured.java | 78 - .../transmittable/wrapped/Inheritable.java | 71 - .../wrapped/InheritableExecutor.java | 79 - .../wrapped/InheritableExecutorService.java | 248 ---- .../InheritableScheduledExecutorService.java | 84 -- .../wrapped/WrappedCallable.java | 85 -- .../wrapped/WrappedRunnable.java | 77 - .../core/type/CachingClassMemberFactory.java | 10 +- .../core/type/ClassMemberFactory.java | 8 +- .../framework/core/type/ClassMembers.java | 226 ++- .../core/type/ClassMembersLoader.java | 403 +++--- .../framework/core/type/ClassUtils.java | 4 +- .../core/type/ConstructorInstanceFactory.java | 4 +- .../framework/core/type/ReflectionUtils.java | 10 +- .../type/SerializationConstructorFactory.java | 241 ++-- .../run/soeasy/framework/io/FileUtils.java | 12 +- .../soeasy/framework/io/ReaderFactory.java | 8 +- .../framework/io/ReaderFactoryWrapper.java | 4 +- .../ConfigurablePropertiesResolver.java | 113 -- .../resolver/DefaultPropertiesResolver.java | 164 --- .../io/resolver/PropertiesResolver.java | 72 - .../soeasy/framework/io/watch/PathPoller.java | 461 +++--- .../framework/io/watch/PathWatcher.java | 270 ++-- .../framework/io/watch/ResourcePoller.java | 10 +- .../framework/io/watch/WatchKeyPoller.java | 18 +- .../framework/io/watch/WatchKeyRegistry.java | 85 +- .../logging/ConfigurableLoggerFactory.java | 181 ++- .../framework/logging/LevelRegistry.java | 288 ++-- .../soeasy/framework/logging/LogManager.java | 12 +- .../framework/logging/LoggerRegistry.java | 8 +- .../framework/codec/crypto/HmacMD5Test.java | 22 - .../framework/codec/crypto/HmacSHA1Test.java | 22 - .../core/collections/CollectionUtilsTest.java | 2 +- .../core/collections/ElementsTest.java | 19 - .../framework/core/exchange/StageTest.java | 276 ++++ .../collection/ListContainerTest.java | 21 - .../invoke/reflect/ReflectionClonerTest.java | 2 +- .../core/invoke/reflect/ReflectionTest.java | 2 +- .../framework/core/page/OffsetPagingTest.java | 2 +- .../soeasy/framework/core/spi/SpiTest.java | 4 +- ...ceMapTest.java => TypeServiceMapTest.java} | 12 +- .../core/streaming/StreamableTest.java | 276 ++++ .../core/transmittable/InheriterTest.java | 41 - .../core/transmittable/ThreadLocalTest.java | 39 - .../framework/core/type/ClassUtilsTest.java | 2 +- .../framework/core/type/MembersTest.java | 4 +- .../jdbc/AbstractResultSetAccessor.java | 2 +- .../framework/jdbc/ResultSetWrapped.java | 8 +- .../jdbc/convert/ResultSetColumnAccessor.java | 2 +- .../jdbc/convert/ResultSetProperties.java | 194 ++- .../framework/messaging/EmptyMimeTypes.java | 42 - .../soeasy/framework/messaging/Headers.java | 334 +---- .../messaging/MediaTypeRegistry.java | 42 - .../framework/messaging/MediaTypes.java | 158 -- .../framework/messaging/MimeTypeList.java | 71 - .../messaging/MultiMediaTypeComparator.java | 31 + .../messaging/convert/MessageConverter.java | 16 +- .../convert/MessageConverterComparator.java | 15 +- .../messaging/convert/MessageConverters.java | 408 +++--- .../AbstractEntityMessageConverter.java | 1 - .../AbstractInputMessageConverter.java | 3 +- .../support/AbstractMessageConverter.java | 38 +- .../AbstractPropertyMessageConverter.java | 4 +- .../support/AbstractTextMessageConverter.java | 320 ++--- .../support/ByteArrayMessageConverter.java | 4 +- .../support/DefaultMessageConverters.java | 33 - .../support/DocumentMessageConverter.java | 5 +- .../convert/support/JsonMessageConverter.java | 7 +- .../support/QueryStringMessageConveter.java | 4 +- .../support/SerializableMessageConveter.java | 5 +- .../convert/support/TextMessageConverter.java | 4 +- 462 files changed, 12996 insertions(+), 27755 deletions(-) create mode 100644 beans/src/main/java/run/soeasy/framework/beans/BeanInfoMapping.java delete mode 100644 beans/src/main/java/run/soeasy/framework/beans/BeanPropertyTemplate.java delete mode 100644 core/src/main/java/run/soeasy/framework/codec/crypto/HmacMD5.java delete mode 100644 core/src/main/java/run/soeasy/framework/codec/crypto/HmacSHA1.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/AbstractMultiValueMap.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/ArrayDictionary.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/CacheableElements.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/CollectionElements.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/CollectionElementsWrapper.java rename core/src/main/java/run/soeasy/framework/core/collection/{factory => }/CollectionFactory.java (98%) delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/CollectionWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/ConvertedElements.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/ConvertedProvider.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/ConvertibleEnumeration.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/ConvertibleIterable.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/ConvertibleIterator.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/DefaultMultiValueMap.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/Dictionary.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/DictionaryWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/Elements.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/ElementsWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/EmptyElements.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/EmptyProvider.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/Enumerable.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/EnumerableToIterable.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/IterableElementsWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/IterableProvider.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/IterableToEnumerable.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/IterableWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/IterationIterator.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/KeyValues.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/KeyValuesWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/Keys.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/KeysWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/KnownSizeElements.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/KnownSizeProvider.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/ListElements.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/ListElementsWrapper.java rename core/src/main/java/run/soeasy/framework/core/collection/{factory => }/ListFactory.java (95%) delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/ListWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/Listable.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/ListableWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/MapDictionary.java rename core/src/main/java/run/soeasy/framework/core/collection/{factory => }/MapFactory.java (98%) delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/MapWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/MergedElements.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/MergedProvider.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/MultiValueMapWrapper.java create mode 100644 core/src/main/java/run/soeasy/framework/core/collection/PreviousIterator.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/Provider.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/ProviderWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/Reloadable.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/ReloadableElementsWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/Sequential.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/SequentialIterator.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/SetElements.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/SetElementsWrapper.java rename core/src/main/java/run/soeasy/framework/core/collection/{factory => }/SetFactory.java (97%) delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/SetWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/StandardCollectionElements.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/StandardIterableElements.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/StandardListElements.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/StandardSetElements.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/StandardStreamableElements.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/Streamable.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/StreamableElementsWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/StreamableWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/function/FlatMerger.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/collection/function/MapMerger.java rename core/src/main/java/run/soeasy/framework/core/exchange/{event => }/BaseEvent.java (98%) create mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/BatchEventDispatcher.java rename core/src/main/java/run/soeasy/framework/core/exchange/{event => }/ChangeEvent.java (99%) rename core/src/main/java/run/soeasy/framework/core/exchange/{event => }/ChangeType.java (97%) create mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/CollectionRegistry.java create mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/CompletableToListenableFutureAdapter.java create mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/CompositeOperation.java rename core/src/main/java/run/soeasy/framework/core/exchange/{future => }/Confirm.java (72%) create mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/DisposableDispatcher.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/EmptyRegistrations.java create mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/EventDispatcher.java create mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/KeyValueRegistry.java rename core/src/main/java/run/soeasy/framework/core/exchange/{event => }/LifecycleDispatcher.java (94%) create mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/ListenableFuture.java create mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/ListenableFutureTask.java rename core/src/main/java/run/soeasy/framework/core/exchange/{future => }/ListenableFutureWrapper.java (89%) create mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/ListenableOperation.java create mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/ListenableOperationWrapper.java create mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/ListenableStage.java create mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/ListenableWrapper.java create mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/MapRegistry.java create mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/Operation.java create mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/OperationWrapper.java rename core/src/main/java/run/soeasy/framework/core/exchange/{future => }/Promise.java (98%) delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/Receipt.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/ReceiptWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/Receipted.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/Receipts.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/Registed.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/Registration.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/RegistrationWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/Registrations.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/RegistrationsWrapper.java create mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/Registry.java rename core/src/main/java/run/soeasy/framework/core/exchange/{future => }/ScheduledListenableFuture.java (95%) rename core/src/main/java/run/soeasy/framework/core/exchange/{future => }/ScheduledListenableFutureAdapter.java (95%) rename core/src/main/java/run/soeasy/framework/core/exchange/{future => }/SettableListenableFuture.java (97%) create mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/Stage.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/SuccessfullyRegistered.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/AbstractContainer.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/AbstractEntryRegistration.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/AbstractLifecycleRegistration.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/AbstractPayloadRegistration.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/AtomicElementRegistration.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/AtomicEntryRegistration.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/BatchRegistration.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/Container.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/ContainerWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/DisposableKeyValueRegistration.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/DisposableRegistration.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/ElementRegistration.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/ElementRegistrationWrapped.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/ElementRegistrationWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/EntryRegistration.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/EntryRegistrationWrapped.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/EntryRegistrationWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/InterceptableRegisration.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/KeyValueRegistration.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/KeyValueRegistrationWrapped.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/KeyValueRegistrationWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/KeyValueRegistry.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/LifecycleRegistration.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/LifecycleRegistrationWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/LimitableRegistration.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/LimitableRegistrations.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/MappedContainer.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/MappedPayloadRegistration.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/MappedRegistry.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/PayloadRegisted.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/PayloadRegistration.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/PayloadRegistrationWrapped.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/PayloadRegistrationWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/RegistrationException.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/RegistrationWrapped.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/Registry.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/RegistryWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/StandardPayloadRegistration.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/collection/ArrayListContainer.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/collection/CollectionContainer.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/collection/ListContainer.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/collection/QueueContainer.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/map/DefaultMapContainer.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/map/MapContainer.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/map/MultiValueMapContainer.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/map/TreeMapContainer.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/container/map/TreeSetContainer.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/event/BatchEventDispatcher.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/event/DisposableDispatcher.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/event/EventDispatcher.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/future/CompletableToListenableFutureAdapter.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/future/ListenableFuture.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/future/ListenableFutureTask.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/future/ListenableReceipt.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/future/ListenableReceiptWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/future/ListenableRegistration.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/future/ListenableRegistrationWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/exchange/future/Stage.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/execute/ParameterTemplate.java create mode 100644 core/src/main/java/run/soeasy/framework/core/execute/reflect/ExecutableParameterMapping.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/execute/reflect/ExecutableParameterTemplate.java rename core/src/main/java/run/soeasy/framework/core/{transform/templates => mapping}/ChainMapper.java (97%) create mode 100644 core/src/main/java/run/soeasy/framework/core/mapping/DefaultMapper.java rename core/src/main/java/run/soeasy/framework/core/{transform/templates => mapping}/FilterableMapper.java (68%) rename core/src/main/java/run/soeasy/framework/core/{transform/templates => mapping}/GenericMapper.java (88%) rename core/src/main/java/run/soeasy/framework/core/{transform/templates/MapMapper.java => mapping/MappedMapper.java} (84%) rename core/src/main/java/run/soeasy/framework/core/{transform/templates => mapping}/Mapper.java (95%) rename core/src/main/java/run/soeasy/framework/core/{transform/templates => mapping}/MappingContext.java (97%) rename core/src/main/java/run/soeasy/framework/core/{transform/templates => mapping}/MappingFactory.java (96%) rename core/src/main/java/run/soeasy/framework/core/{transform/templates => mapping}/MappingFilter.java (95%) create mode 100644 core/src/main/java/run/soeasy/framework/core/mapping/MappingProvider.java rename core/src/main/java/run/soeasy/framework/core/{transform/templates/ArrayMapper.java => mapping/StreamableMapper.java} (65%) rename core/src/main/java/run/soeasy/framework/core/{transform/templates => mapping}/ValueMapper.java (97%) rename core/src/main/java/run/soeasy/framework/core/{transform => mapping}/property/ClassMemberTemplateFactory.java (97%) create mode 100644 core/src/main/java/run/soeasy/framework/core/mapping/property/ClassMemberTemplateRegistry.java rename core/src/main/java/run/soeasy/framework/core/{transform => mapping}/property/Cloner.java (99%) create mode 100644 core/src/main/java/run/soeasy/framework/core/mapping/property/IndexedPropertyMapping.java create mode 100644 core/src/main/java/run/soeasy/framework/core/mapping/property/MapMapping.java create mode 100644 core/src/main/java/run/soeasy/framework/core/mapping/property/MappedPropertyMapping.java create mode 100644 core/src/main/java/run/soeasy/framework/core/mapping/property/MultiMappedPropertyMapping.java rename core/src/main/java/run/soeasy/framework/core/{transform => mapping}/property/NamedAccessibleDescriptor.java (94%) create mode 100644 core/src/main/java/run/soeasy/framework/core/mapping/property/ObjectMapping.java rename core/src/main/java/run/soeasy/framework/core/{transform => mapping}/property/ObjectPropertyAccessor.java (98%) rename core/src/main/java/run/soeasy/framework/core/{transform => mapping}/property/ObjectTemplateFactory.java (94%) create mode 100644 core/src/main/java/run/soeasy/framework/core/mapping/property/ObjectTemplateRegistry.java rename core/src/main/java/run/soeasy/framework/core/{transform => mapping}/property/Property.java (98%) rename core/src/main/java/run/soeasy/framework/core/{transform => mapping}/property/PropertyAccessor.java (96%) rename core/src/main/java/run/soeasy/framework/core/{transform => mapping}/property/PropertyDescriptor.java (97%) rename core/src/main/java/run/soeasy/framework/core/{transform => mapping}/property/PropertyDescriptorWrapper.java (97%) create mode 100644 core/src/main/java/run/soeasy/framework/core/mapping/property/PropertyMapper.java create mode 100644 core/src/main/java/run/soeasy/framework/core/mapping/property/PropertyMapping.java rename core/src/main/java/run/soeasy/framework/core/{transform => mapping}/property/PropertyMappingFilter.java (91%) create mode 100644 core/src/main/java/run/soeasy/framework/core/mapping/property/PropertyMappingPredicate.java create mode 100644 core/src/main/java/run/soeasy/framework/core/mapping/property/PropertyMappingWrapper.java rename core/src/main/java/run/soeasy/framework/core/{transform => mapping}/property/PropertyWrapper.java (98%) create mode 100644 core/src/main/java/run/soeasy/framework/core/mapping/property/StreamablePropertyMapping.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/spi/AndConfigured.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/spi/AndInclude.java create mode 100644 core/src/main/java/run/soeasy/framework/core/spi/CompositeServiceDiscoverer.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/spi/ConfigurableProviderFactory.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/spi/Configured.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/spi/ConfiguredWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/spi/ConvertedConfigured.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/spi/ConvertedInclude.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/spi/Include.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/spi/IncludeWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/spi/Included.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/spi/Includes.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/spi/NativeProvider.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/spi/ProviderFactory.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/spi/ServiceContainer.java create mode 100644 core/src/main/java/run/soeasy/framework/core/spi/ServiceDiscoverer.java create mode 100644 core/src/main/java/run/soeasy/framework/core/spi/ServiceRegistry.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/spi/SystemProviderFactory.java create mode 100644 core/src/main/java/run/soeasy/framework/core/spi/SystemServiceDiscoverer.java create mode 100644 core/src/main/java/run/soeasy/framework/core/spi/TypeServiceMap.java create mode 100644 core/src/main/java/run/soeasy/framework/core/streaming/CachedStreamable.java create mode 100644 core/src/main/java/run/soeasy/framework/core/streaming/EmptyMapping.java rename core/src/main/java/run/soeasy/framework/core/{collection => streaming}/EmptyStreamable.java (87%) create mode 100644 core/src/main/java/run/soeasy/framework/core/streaming/IndexedMapping.java create mode 100644 core/src/main/java/run/soeasy/framework/core/streaming/MappedMapping.java create mode 100644 core/src/main/java/run/soeasy/framework/core/streaming/Mapping.java create mode 100644 core/src/main/java/run/soeasy/framework/core/streaming/MappingWrapper.java create mode 100644 core/src/main/java/run/soeasy/framework/core/streaming/MergedStreamable.java create mode 100644 core/src/main/java/run/soeasy/framework/core/streaming/MultiMappedMapping.java rename core/src/main/java/run/soeasy/framework/core/{collection => streaming}/NoUniqueElementException.java (95%) create mode 100644 core/src/main/java/run/soeasy/framework/core/streaming/PureMappedStreamable.java create mode 100644 core/src/main/java/run/soeasy/framework/core/streaming/Streamable.java create mode 100644 core/src/main/java/run/soeasy/framework/core/streaming/StreamableMapping.java create mode 100644 core/src/main/java/run/soeasy/framework/core/streaming/StreamableWrapper.java create mode 100644 core/src/main/java/run/soeasy/framework/core/streaming/TransformedStreamable.java create mode 100644 core/src/main/java/run/soeasy/framework/core/streaming/ZipIterator.java create mode 100644 core/src/main/java/run/soeasy/framework/core/streaming/ZipStreamable.java rename core/src/main/java/run/soeasy/framework/core/{collection => streaming}/function/Filter.java (89%) create mode 100644 core/src/main/java/run/soeasy/framework/core/streaming/function/FlatMerger.java create mode 100644 core/src/main/java/run/soeasy/framework/core/streaming/function/MapMerger.java rename core/src/main/java/run/soeasy/framework/core/{collection => streaming}/function/Merger.java (97%) rename core/src/main/java/run/soeasy/framework/core/{collection => streaming}/function/PropertiesMerger.java (83%) rename core/src/main/java/run/soeasy/framework/core/{collection => streaming}/function/Selector.java (79%) rename core/src/main/java/run/soeasy/framework/core/{collection => streaming}/function/Weighted.java (91%) rename core/src/main/java/run/soeasy/framework/core/{collection => streaming}/function/WeightedSource.java (95%) delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/collection/MapEntryAccessor.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/collection/MapEntryMapping.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/property/ArrayPropertyMapping.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/property/ArrayPropertyTemplate.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/property/ArrayTypedProperties.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/property/ClassMemberTemplateRegistry.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/property/MapPropertyMapping.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/property/MapPropertyTemplate.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/property/MapTypedProperties.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/property/ObjectProperties.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/property/ObjectTemplateRegistry.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/property/PropertyMapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/property/PropertyMapping.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/property/PropertyMappingPredicate.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/property/PropertyMappingWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/property/PropertyTemplate.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/property/PropertyTemplateProperties.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/property/PropertyTemplateWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/property/TypedProperties.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/property/TypedPropertiesWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/templates/ArrayMapping.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/templates/ArrayTemplate.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/templates/ArrayTemplateMapping.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/templates/DefaultMapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/templates/MapMapping.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/templates/MapTemplate.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/templates/MapTemplateMapping.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/templates/Mapping.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/templates/MappingProvider.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/templates/MappingWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/templates/Template.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/templates/TemplateMapping.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/templates/TemplateMappingWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/templates/TemplateProperties.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transform/templates/TemplateWrapper.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transmittable/Inheriter.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transmittable/InheriterBackup.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transmittable/InheriterCapture.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transmittable/registry/AnyInheriterRegistry.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transmittable/registry/InheriterRegistry.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transmittable/registry/ThreadLocalInheriter.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transmittable/wrapped/Captured.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transmittable/wrapped/Inheritable.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transmittable/wrapped/InheritableExecutor.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transmittable/wrapped/InheritableExecutorService.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transmittable/wrapped/InheritableScheduledExecutorService.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transmittable/wrapped/WrappedCallable.java delete mode 100644 core/src/main/java/run/soeasy/framework/core/transmittable/wrapped/WrappedRunnable.java delete mode 100644 core/src/main/java/run/soeasy/framework/io/resolver/ConfigurablePropertiesResolver.java delete mode 100644 core/src/main/java/run/soeasy/framework/io/resolver/DefaultPropertiesResolver.java delete mode 100644 core/src/main/java/run/soeasy/framework/io/resolver/PropertiesResolver.java delete mode 100644 core/src/test/java/run/soeasy/framework/codec/crypto/HmacMD5Test.java delete mode 100644 core/src/test/java/run/soeasy/framework/codec/crypto/HmacSHA1Test.java delete mode 100644 core/src/test/java/run/soeasy/framework/core/collections/ElementsTest.java create mode 100644 core/src/test/java/run/soeasy/framework/core/exchange/StageTest.java delete mode 100644 core/src/test/java/run/soeasy/framework/core/exchange/container/collection/ListContainerTest.java rename core/src/test/java/run/soeasy/framework/core/spi/{ServiceMapTest.java => TypeServiceMapTest.java} (50%) create mode 100644 core/src/test/java/run/soeasy/framework/core/streaming/StreamableTest.java delete mode 100644 core/src/test/java/run/soeasy/framework/core/transmittable/InheriterTest.java delete mode 100644 core/src/test/java/run/soeasy/framework/core/transmittable/ThreadLocalTest.java delete mode 100644 messaging/src/main/java/run/soeasy/framework/messaging/EmptyMimeTypes.java delete mode 100644 messaging/src/main/java/run/soeasy/framework/messaging/MediaTypeRegistry.java delete mode 100644 messaging/src/main/java/run/soeasy/framework/messaging/MediaTypes.java delete mode 100644 messaging/src/main/java/run/soeasy/framework/messaging/MimeTypeList.java create mode 100644 messaging/src/main/java/run/soeasy/framework/messaging/MultiMediaTypeComparator.java delete mode 100644 messaging/src/main/java/run/soeasy/framework/messaging/convert/support/DefaultMessageConverters.java diff --git a/aop/src/main/java/run/soeasy/framework/aop/Aop.java b/aop/src/main/java/run/soeasy/framework/aop/Aop.java index 5d42741da..f82ee17df 100644 --- a/aop/src/main/java/run/soeasy/framework/aop/Aop.java +++ b/aop/src/main/java/run/soeasy/framework/aop/Aop.java @@ -7,8 +7,8 @@ import run.soeasy.framework.core.RandomUtils; import run.soeasy.framework.core.StringUtils; import run.soeasy.framework.core.collection.ArrayUtils; -import run.soeasy.framework.core.collection.Elements; import run.soeasy.framework.core.execute.Execution; +import run.soeasy.framework.core.streaming.Streamable; /** * AOP核心组件,继承自{@link JdkProxyFactory},提供基于JDK动态代理的AOP功能实现, @@ -119,12 +119,12 @@ public Proxy getProxy(@NonNull Class sourceClass, Class[] interfaces, this.id); // 合并拦截器:标识拦截器 + 全局注册拦截器 + 自定义拦截器 - Elements executionInterceptors; + Streamable executionInterceptors; if (executionInterceptor == null) { - executionInterceptors = Elements.forArray(delegatedObjectExecutionInterceptor, + executionInterceptors = Streamable.array(delegatedObjectExecutionInterceptor, getExecutionInterceptorRegistry()); } else { - executionInterceptors = Elements.forArray(delegatedObjectExecutionInterceptor, + executionInterceptors = Streamable.array(delegatedObjectExecutionInterceptor, getExecutionInterceptorRegistry(), executionInterceptor); } @@ -177,12 +177,12 @@ public Execution getProxyFunction(@NonNull Execution execution, ExecutionInterce this.id); // 合并拦截器 - Elements executionInterceptors; + Streamable executionInterceptors; if (executionInterceptor == null) { - executionInterceptors = Elements.forArray(delegatedObjectExecutionInterceptor, + executionInterceptors = Streamable.array(delegatedObjectExecutionInterceptor, getExecutionInterceptorRegistry()); } else { - executionInterceptors = Elements.forArray(delegatedObjectExecutionInterceptor, + executionInterceptors = Streamable.array(delegatedObjectExecutionInterceptor, getExecutionInterceptorRegistry(), executionInterceptor); } @@ -225,11 +225,11 @@ public Proxy getProxy(@NonNull Class sourceClass, T source, Cla source); // 合并拦截器:切换目标拦截器 + 自定义拦截器 - Elements executionInterceptors; + Streamable executionInterceptors; if (executionInterceptor == null) { - executionInterceptors = Elements.forArray(switchableTargetExecutionInterceptor); + executionInterceptors = Streamable.array(switchableTargetExecutionInterceptor); } else { - executionInterceptors = Elements.forArray(switchableTargetExecutionInterceptor, executionInterceptor); + executionInterceptors = Streamable.array(switchableTargetExecutionInterceptor, executionInterceptor); } // 包装为拦截器集合并创建代理 diff --git a/aop/src/main/java/run/soeasy/framework/aop/DelegatedObjectExecutionInterceptor.java b/aop/src/main/java/run/soeasy/framework/aop/DelegatedObjectExecutionInterceptor.java index ac8d43c40..2c13db1f7 100644 --- a/aop/src/main/java/run/soeasy/framework/aop/DelegatedObjectExecutionInterceptor.java +++ b/aop/src/main/java/run/soeasy/framework/aop/DelegatedObjectExecutionInterceptor.java @@ -44,7 +44,7 @@ class DelegatedObjectExecutionInterceptor implements InvocationInterceptor, Seri @Override public Object intercept(@NonNull Invocation executor) throws Throwable { // 判断是否为获取代理容器ID的方法调用(方法名匹配且无参数) - if (executor.getMetadata().getParameterTemplate().isEmpty() + if (executor.getMetadata().getParameterMapping().isEmpty() && executor.getMetadata().getName().equals(DelegatedObject.PROXY_CONTAINER_ID_METHOD_NAME)) { return id; // 返回预设的代理容器ID } diff --git a/aop/src/main/java/run/soeasy/framework/aop/ExecutionInterceptorRegistry.java b/aop/src/main/java/run/soeasy/framework/aop/ExecutionInterceptorRegistry.java index e544ebfc0..a7c294ddf 100644 --- a/aop/src/main/java/run/soeasy/framework/aop/ExecutionInterceptorRegistry.java +++ b/aop/src/main/java/run/soeasy/framework/aop/ExecutionInterceptorRegistry.java @@ -3,6 +3,7 @@ import lombok.NonNull; import run.soeasy.framework.core.execute.Execution; import run.soeasy.framework.core.spi.ConfigurableServices; +import run.soeasy.framework.core.spi.ServiceComparator; /** * 执行拦截器注册中心,继承自{@link ConfigurableServices}并实现{@link ExecutionInterceptor}接口, @@ -30,7 +31,7 @@ public class ExecutionInterceptorRegistry extends ConfigurableServices该类持有一个拦截器元素集合({@link Elements}),在拦截执行时,通过创建{@link ExecutionInterceptorChain} + *

该类持有一个拦截器元素集合({@link Streamable}),在拦截执行时,通过创建{@link ExecutionInterceptorChain} * 将拦截器集合转换为拦截链,按迭代顺序依次执行每个拦截器,最终触发{@code nextChain}(目标执行逻辑), * 实现多拦截器的有序增强。 * * @author soeasy.run * @see ExecutionInterceptor * @see ExecutionInterceptorChain - * @see Elements + * @see Streamable */ @Data public class ExecutionInterceptors implements ExecutionInterceptor { @@ -25,7 +25,7 @@ public class ExecutionInterceptors implements ExecutionInterceptor { * 拦截器元素集合(非空),包含需要按序执行的拦截器 */ @NonNull - private final Elements executionInterceptors; + private final Streamable executionInterceptors; /** * 拦截链执行完毕后触发的下一个执行节点(通常为目标业务逻辑) @@ -47,7 +47,7 @@ public class ExecutionInterceptors implements ExecutionInterceptor { @Override public Object intercept(@NonNull Execution function) throws Throwable { // 创建拦截器链,关联当前拦截器集合与下一个执行节点 - ExecutionInterceptorChain chain = new ExecutionInterceptorChain(executionInterceptors.iterator(), nextChain); + ExecutionInterceptorChain chain = new ExecutionInterceptorChain(executionInterceptors.toList().iterator(), nextChain); // 执行拦截链 return chain.intercept(function); } diff --git a/aop/src/main/java/run/soeasy/framework/aop/ProxyFactories.java b/aop/src/main/java/run/soeasy/framework/aop/ProxyFactories.java index 9cfccd930..9d86539d7 100644 --- a/aop/src/main/java/run/soeasy/framework/aop/ProxyFactories.java +++ b/aop/src/main/java/run/soeasy/framework/aop/ProxyFactories.java @@ -2,6 +2,7 @@ import lombok.NonNull; import run.soeasy.framework.core.spi.ConfigurableServices; +import run.soeasy.framework.core.spi.ServiceComparator; import run.soeasy.framework.core.type.ClassUtils; /** @@ -23,7 +24,7 @@ public class ProxyFactories extends ConfigurableServices implement * 构造代理工厂集合,指定服务类为{@link ProxyFactory} */ public ProxyFactories() { - setServiceClass(ProxyFactory.class); + super(ServiceComparator.defaultServiceComparator()); } /** diff --git a/beans/src/main/java/run/soeasy/framework/beans/BeanFormat.java b/beans/src/main/java/run/soeasy/framework/beans/BeanFormat.java index 081c692d0..206253332 100644 --- a/beans/src/main/java/run/soeasy/framework/beans/BeanFormat.java +++ b/beans/src/main/java/run/soeasy/framework/beans/BeanFormat.java @@ -5,10 +5,7 @@ import lombok.Setter; import run.soeasy.framework.codec.Codec; import run.soeasy.framework.codec.format.KeyValueFormat; -import run.soeasy.framework.core.convert.value.TypedValueAccessor; import run.soeasy.framework.core.domain.KeyValue; -import run.soeasy.framework.core.transform.property.TypedProperties; -import run.soeasy.framework.core.transform.templates.Mapping; /** * 基于JavaBean的键值格式化器,继承自{@link KeyValueFormat},专门用于将JavaBean对象与键值对格式(如查询字符串、表单数据)进行互转, @@ -46,13 +43,7 @@ public BeanFormat(@NonNull CharSequence delimiter, @NonNull CharSequence connect // 注册Object类型的映射器:将任意Bean转换为键值对流 getKeyValueMapper().getMappingProvider().register(Object.class, (bean, type) -> { // 通过BeanMapper获取Bean的类型化属性集合 - TypedProperties typedProperties = BeanUtils.getProperties(bean, type); - - // 定义Bean到键值对流的映射逻辑 - Mapping mapping = () -> typedProperties.getElements() - .map(property -> KeyValue.of(property.getKey(), property.getValue())); - - return mapping; + return BeanUtils.getProperties(bean, type); }); } } \ No newline at end of file diff --git a/beans/src/main/java/run/soeasy/framework/beans/BeanInfoFactory.java b/beans/src/main/java/run/soeasy/framework/beans/BeanInfoFactory.java index 463a046b9..726a20743 100644 --- a/beans/src/main/java/run/soeasy/framework/beans/BeanInfoFactory.java +++ b/beans/src/main/java/run/soeasy/framework/beans/BeanInfoFactory.java @@ -4,8 +4,8 @@ import java.beans.IntrospectionException; import lombok.NonNull; -import run.soeasy.framework.core.transform.property.ObjectTemplateFactory; -import run.soeasy.framework.core.transform.property.PropertyTemplate; +import run.soeasy.framework.core.mapping.property.ObjectTemplateFactory; +import run.soeasy.framework.core.mapping.property.PropertyMapping; /** * Bean信息工厂接口,继承自{@link ObjectTemplateFactory},定义获取JavaBean信息({@link BeanInfo})和创建Bean属性模板的规范, @@ -46,7 +46,7 @@ public interface BeanInfoFactory extends ObjectTemplateFactory { * @return 针对该Bean类的{@link BeanPropertyTemplate}实例 */ @Override - default PropertyTemplate getObjectTemplate(Class objectClass) { - return new BeanPropertyTemplate(objectClass, this); + default PropertyMapping getObjectTemplate(Class objectClass) { + return new BeanInfoMapping(objectClass, this).standard(); } } \ No newline at end of file diff --git a/beans/src/main/java/run/soeasy/framework/beans/BeanInfoMapping.java b/beans/src/main/java/run/soeasy/framework/beans/BeanInfoMapping.java new file mode 100644 index 000000000..079745897 --- /dev/null +++ b/beans/src/main/java/run/soeasy/framework/beans/BeanInfoMapping.java @@ -0,0 +1,44 @@ +package run.soeasy.framework.beans; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.util.stream.Stream; + +import lombok.NonNull; +import run.soeasy.framework.core.mapping.property.PropertyMapping; +import run.soeasy.framework.core.mapping.property.StreamablePropertyMapping; +import run.soeasy.framework.core.streaming.Streamable; +import run.soeasy.framework.core.type.ReflectionUtils; + +public class BeanInfoMapping extends StreamablePropertyMapping { + + public BeanInfoMapping(@NonNull Streamable elements) { + super(elements); + } + + public BeanInfoMapping(@NonNull Class beanClass, @NonNull BeanInfo beanInfo) { + this(Streamable.array(beanInfo.getPropertyDescriptors()) + .map((descriptor) -> new BeanProperty(beanClass, descriptor))); + } + + protected BeanInfoMapping(@NonNull Class beanClass, @NonNull BeanInfoFactory beanInfoFactory) { + this(Streamable.of(() -> { + BeanInfo beanInfo; + try { + beanInfo = beanInfoFactory.getBeanInfo(beanClass); + } catch (IntrospectionException e) { + throw new FatalBeanException("Failed to obtain BeanInfo for class [" + beanClass + "]", e); + } + // 将属性描述符转换为BeanProperty,并过滤忽略的属性 + return Stream.of(beanInfo.getPropertyDescriptors()) + .map((descriptor) -> new BeanProperty(beanClass, descriptor)).iterator(); + })); + } + + public PropertyMapping standard() { + return new BeanInfoMapping(this.elements().filter((property) -> { + return property.getReadMethod() != null && property.getReadMethod().getSource() != null + && ReflectionUtils.isObjectMethod(property.getReadMethod().getSource()); + })).toMultiMapped(); + } +} diff --git a/beans/src/main/java/run/soeasy/framework/beans/BeanMapper.java b/beans/src/main/java/run/soeasy/framework/beans/BeanMapper.java index 90fbe73fb..90266b0d5 100644 --- a/beans/src/main/java/run/soeasy/framework/beans/BeanMapper.java +++ b/beans/src/main/java/run/soeasy/framework/beans/BeanMapper.java @@ -1,7 +1,7 @@ package run.soeasy.framework.beans; import lombok.Getter; -import run.soeasy.framework.core.transform.property.PropertyMapper; +import run.soeasy.framework.core.mapping.property.PropertyMapper; /** * Bean映射工具类,继承自{@link PropertyMapper},专门用于处理JavaBean之间的属性复制与转换, diff --git a/beans/src/main/java/run/soeasy/framework/beans/BeanPropertyTemplate.java b/beans/src/main/java/run/soeasy/framework/beans/BeanPropertyTemplate.java deleted file mode 100644 index f8619f0d8..000000000 --- a/beans/src/main/java/run/soeasy/framework/beans/BeanPropertyTemplate.java +++ /dev/null @@ -1,71 +0,0 @@ -package run.soeasy.framework.beans; - -import java.beans.BeanInfo; -import java.beans.IntrospectionException; -import java.util.stream.Stream; - -import lombok.NonNull; -import run.soeasy.framework.core.transform.property.MapPropertyTemplate; -import run.soeasy.framework.core.transform.property.PropertyTemplate; -import run.soeasy.framework.core.type.ReflectionUtils; - -/** - * 基于JavaBean的属性模板实现,继承自{@link MapPropertyTemplate},实现{@link PropertyTemplate}接口, - * 用于管理和访问JavaBean的属性信息(封装为{@link BeanProperty}),支持通过JavaBean内省机制获取属性元数据, - * 并过滤掉无需处理的基础属性(如Object类的方法对应的属性)。 - * - *

该类通过{@link BeanInfoFactory}获取JavaBean的{@link BeanInfo},将属性描述符转换为{@link BeanProperty}对象, - * 构建属性模板集合,适用于属性映射、数据转换、反射操作等场景。 - * - * @author soeasy.run - * @see MapPropertyTemplate - * @see PropertyTemplate - * @see BeanProperty - * @see BeanInfo - */ -class BeanPropertyTemplate extends MapPropertyTemplate> - implements PropertyTemplate { - - /** - * 构造Bean属性模板(基于指定的Bean类和Bean信息工厂) - * - *

通过{@link BeanInfoFactory}获取目标Bean类的{@link BeanInfo},解析其中的属性描述符并转换为{@link BeanProperty}, - * 同时过滤掉需要忽略的属性(如Object类自带方法对应的属性),完成属性模板的初始化。 - * - * @param beanClass 目标JavaBean的类对象(非空) - * @param beanInfoFactory 用于获取Bean信息的工厂(非空) - * @throws FatalBeanException 当获取BeanInfo失败时抛出(包装{@link IntrospectionException}) - */ - public BeanPropertyTemplate(@NonNull Class beanClass, BeanInfoFactory beanInfoFactory) { - super(() -> { - BeanInfo beanInfo; - try { - beanInfo = beanInfoFactory.getBeanInfo(beanClass); - } catch (IntrospectionException e) { - throw new FatalBeanException("Failed to obtain BeanInfo for class [" + beanClass + "]", e); - } - // 将属性描述符转换为BeanProperty,并过滤忽略的属性 - return Stream.of(beanInfo.getPropertyDescriptors()) - .map((descriptor) -> new BeanProperty(beanClass, descriptor)) - .filter((property) -> !isIgnoreProperty(property)) - .iterator(); - }, false); - } - - /** - * 判断是否为需要忽略的属性 - * - *

忽略规则:若属性的读方法(getter)是Object类中定义的方法(如{@link Object#getClass()}),则视为需要忽略的属性, - * 避免处理JavaBean继承自Object的基础属性。 - * - * @param property 待判断的Bean属性 - * @return 是需要忽略的属性则返回true,否则返回false - */ - private static boolean isIgnoreProperty(BeanProperty property) { - if (property.getReadMethod() != null && property.getReadMethod().getSource() != null - && ReflectionUtils.isObjectMethod(property.getReadMethod().getSource())) { - return true; - } - return false; - } -} \ No newline at end of file diff --git a/beans/src/main/java/run/soeasy/framework/beans/BeanUtils.java b/beans/src/main/java/run/soeasy/framework/beans/BeanUtils.java index 2ba45ffe4..410adedff 100644 --- a/beans/src/main/java/run/soeasy/framework/beans/BeanUtils.java +++ b/beans/src/main/java/run/soeasy/framework/beans/BeanUtils.java @@ -5,9 +5,9 @@ import lombok.NonNull; import lombok.experimental.UtilityClass; import run.soeasy.framework.core.convert.TypeDescriptor; -import run.soeasy.framework.core.transform.property.PropertyMappingFilter; -import run.soeasy.framework.core.transform.property.PropertyTemplate; -import run.soeasy.framework.core.transform.property.TypedProperties; +import run.soeasy.framework.core.mapping.property.PropertyAccessor; +import run.soeasy.framework.core.mapping.property.PropertyMapping; +import run.soeasy.framework.core.mapping.property.PropertyMappingFilter; /** * Bean 操作工具类:基于 {@link BeanMapper} 封装 Bean 属性核心操作,提供「属性复制、属性提取、属性模板获取」三大核心能力, @@ -17,17 +17,17 @@ * 1. 线程安全:通过双重检查锁单例模式初始化 {@link BeanMapper},保证多线程环境下实例唯一性; * 2. 灵活扩展:支持自定义 {@link PropertyMappingFilter} 过滤属性或自定义映射规则; * 3. 类型适配:支持显式指定源/目标类(应对多态场景)和自动推断类型(简化常规调用); - * 4. 元数据支持:提供属性模板({@link PropertyTemplate})和带类型信息的属性集合({@link TypedProperties})提取能力。 + * 4. 元数据支持:提供属性模板({@link PropertyMapping})和带类型信息的属性集合({@link PropertyMapping})提取能力。 *

* 依赖说明: * - 核心依赖:{@link BeanMapper}(属性映射核心实现,所有操作最终委托其执行); * - 扩展依赖:{@link PropertyMappingFilter}(属性映射过滤/自定义)、{@link TypeDescriptor}(类型描述); - * - 元数据依赖:{@link TypedProperties}(带类型的属性集合)、{@link PropertyTemplate}(属性模板)。 + * - 元数据依赖:{@link PropertyMapping}(带类型的属性集合)、{@link PropertyMapping}(属性模板)。 * * @author soeasy.run * @see BeanMapper 属性映射核心实现类(具体执行策略由其定义) - * @see TypedProperties 带类型信息的属性集合 - * @see PropertyTemplate Bean 属性元数据模板 + * @see PropertyMapping 带类型信息的属性集合 + * @see PropertyMapping Bean 属性元数据模板 */ @UtilityClass public class BeanUtils { @@ -103,11 +103,11 @@ public static BeanMapper getBeanMapper() { * 自动通过 Bean 实例生成 {@link TypeDescriptor},具体属性提取规则由 {@link BeanMapper} 实现类定义。 * * @param bean 待提取属性的 Bean 对象(非空,具体支持的对象类型由 {@link BeanMapper} 定义) - * @return 带类型信息的属性集合 {@link TypedProperties},具体返回格式由 {@link BeanMapper#getMapping} 定义 + * @return 带类型信息的属性集合 {@link PropertyMapping},具体返回格式由 {@link BeanMapper#getMapping} 定义 * @throws NullPointerException 若 bean 为 null - * @see TypedProperties 带类型信息的属性集合(支持类型安全的属性取值) + * @see PropertyMapping 带类型信息的属性集合(支持类型安全的属性取值) */ - public static TypedProperties getProperties(@NonNull Object bean) { + public static PropertyMapping getProperties(@NonNull Object bean) { return getProperties(bean, TypeDescriptor.forObject(bean)); } @@ -119,11 +119,11 @@ public static TypedProperties getProperties(@NonNull Object bean) { * * @param bean 待提取属性的 Bean 对象(具体支持的空值处理、对象类型由 {@link BeanMapper} 定义) * @param typeDescriptor 类型描述器(非空,用于 {@link BeanMapper} 精确解析属性元数据,支持泛型、参数化类型) - * @return 带类型信息的属性集合 {@link TypedProperties},具体返回格式由 {@link BeanMapper#getMapping} 定义 + * @return 带类型信息的属性集合 {@link PropertyMapping},具体返回格式由 {@link BeanMapper#getMapping} 定义 * @throws NullPointerException 若 typeDescriptor 为 null * @see TypeDescriptor 类型描述器(封装类类型、泛型信息等) */ - public static TypedProperties getProperties(Object bean, @NonNull TypeDescriptor typeDescriptor) { + public static PropertyMapping getProperties(Object bean, @NonNull TypeDescriptor typeDescriptor) { return getBeanMapper().getMapping(bean, typeDescriptor); } @@ -134,12 +134,12 @@ public static TypedProperties getProperties(Object bean, @NonNull TypeDescriptor * 具体元数据解析规则、缓存策略由 {@link BeanMapper} 实现类定义。 * * @param beanClass Bean 的类对象(非空,具体支持的类类型由 {@link BeanMapper} 定义) - * @return Bean 的属性模板 {@link PropertyTemplate},具体模板内容由 {@link BeanMapper#getObjectTemplate} 定义 + * @return Bean 的属性模板 {@link PropertyMapping},具体模板内容由 {@link BeanMapper#getObjectTemplate} 定义 * @throws NullPointerException 若 beanClass 为 null - * @see PropertyTemplate 属性模板(缓存 Bean 的属性元数据,提升重复操作性能) + * @see PropertyMapping 属性模板(缓存 Bean 的属性元数据,提升重复操作性能) * @see BeanProperty 单个属性的元数据(包含属性名、类型、getter/setter 等) */ - public static PropertyTemplate getTemplate(@NonNull Class beanClass) { + public static PropertyMapping getTemplate(@NonNull Class beanClass) { return getBeanMapper().getObjectTemplate(beanClass); } } \ No newline at end of file diff --git a/beans/src/main/java/run/soeasy/framework/beans/ConfigurableBeanInfoFactory.java b/beans/src/main/java/run/soeasy/framework/beans/ConfigurableBeanInfoFactory.java index c5c03a934..1198fc398 100644 --- a/beans/src/main/java/run/soeasy/framework/beans/ConfigurableBeanInfoFactory.java +++ b/beans/src/main/java/run/soeasy/framework/beans/ConfigurableBeanInfoFactory.java @@ -6,6 +6,7 @@ import lombok.NonNull; import run.soeasy.framework.core.spi.ConfigurableServices; +import run.soeasy.framework.core.spi.ServiceComparator; /** * 可配置的Bean信息工厂,继承自{@link ConfigurableServices}并实现{@link BeanInfoFactory}, @@ -26,6 +27,7 @@ class ConfigurableBeanInfoFactory extends ConfigurableServices * 用于后续通过SPI机制加载该接口的实现类。 */ ConfigurableBeanInfoFactory() { + super(ServiceComparator.defaultServiceComparator()); setServiceClass(BeanInfoFactory.class); } diff --git a/beans/src/test/java/run/soeasy/framewrok/beans/BeanUtilsTest.java b/beans/src/test/java/run/soeasy/framewrok/beans/BeanUtilsTest.java index da765ca66..82fde69c8 100644 --- a/beans/src/test/java/run/soeasy/framewrok/beans/BeanUtilsTest.java +++ b/beans/src/test/java/run/soeasy/framewrok/beans/BeanUtilsTest.java @@ -6,7 +6,7 @@ import lombok.Data; import run.soeasy.framework.beans.BeanUtils; -import run.soeasy.framework.core.transform.property.PropertyMappingFilter; +import run.soeasy.framework.core.mapping.property.PropertyMappingFilter; public class BeanUtilsTest { @Data diff --git a/core/src/main/java/run/soeasy/framework/codec/crypto/HmacMD5.java b/core/src/main/java/run/soeasy/framework/codec/crypto/HmacMD5.java deleted file mode 100644 index acbf346c4..000000000 --- a/core/src/main/java/run/soeasy/framework/codec/crypto/HmacMD5.java +++ /dev/null @@ -1,29 +0,0 @@ -package run.soeasy.framework.codec.crypto; - -/** - * HmacMD5消息认证码实现类,继承自{@link MAC},专门用于基于HmacMD5算法计算消息认证码, - * 封装了HmacMD5算法的初始化逻辑,简化使用HmacMD5进行数据完整性和真实性验证的流程。 - * - *

HmacMD5算法结合MD5哈希函数和密钥,生成128位(16字节)的消息认证码, - * 适用于对安全性要求不高但需要快速计算的消息认证场景(如 legacy 系统兼容、简单数据校验等)。 - * - * @author soeasy.run - * @see MAC - * @see javax.crypto.Mac - */ -public class HmacMD5 extends MAC { - - /** - * HmacMD5算法名称常量,对应标准加密算法中的"HmacMD5" - */ - public static final String ALGORITHM = "HmacMD5"; - - /** - * 构造HmacMD5编码器(基于密钥字节数组) - * - * @param secretKey 密钥字节数组(用于初始化HmacMD5算法) - */ - public HmacMD5(byte[] secretKey) { - super(ALGORITHM, secretKey); - } -} \ No newline at end of file diff --git a/core/src/main/java/run/soeasy/framework/codec/crypto/HmacSHA1.java b/core/src/main/java/run/soeasy/framework/codec/crypto/HmacSHA1.java deleted file mode 100644 index 3a9eb73ca..000000000 --- a/core/src/main/java/run/soeasy/framework/codec/crypto/HmacSHA1.java +++ /dev/null @@ -1,29 +0,0 @@ -package run.soeasy.framework.codec.crypto; - -/** - * HmacSHA1消息认证码实现类,继承自{@link MAC},专门用于基于HmacSHA1算法计算消息认证码, - * 封装了HmacSHA1算法的初始化逻辑,简化使用HmacSHA1进行数据完整性和真实性验证的流程。 - * - *

HmacSHA1算法结合SHA1哈希函数和密钥,生成160位(20字节)的消息认证码, - * 适用于需要中等安全强度的消息认证场景(如API接口签名、数据传输校验等)。 - * - * @author soeasy.run - * @see MAC - * @see javax.crypto.Mac - */ -public class HmacSHA1 extends MAC { - - /** - * HmacSHA1算法名称常量,对应标准加密算法中的"HmacSHA1" - */ - public static final String ALGORITHM = "HmacSHA1"; - - /** - * 构造HmacSHA1编码器(基于密钥字节数组) - * - * @param secretKey 密钥字节数组(用于初始化HmacSHA1算法,密钥长度推荐不小于160位) - */ - public HmacSHA1(byte[] secretKey) { - super(ALGORITHM, secretKey); - } -} \ No newline at end of file diff --git a/core/src/main/java/run/soeasy/framework/codec/format/CharsetCodec.java b/core/src/main/java/run/soeasy/framework/codec/format/CharsetCodec.java index 5a981549e..999087ffb 100644 --- a/core/src/main/java/run/soeasy/framework/codec/format/CharsetCodec.java +++ b/core/src/main/java/run/soeasy/framework/codec/format/CharsetCodec.java @@ -15,8 +15,7 @@ import run.soeasy.framework.codec.Encoder; import run.soeasy.framework.codec.binary.Gzip; import run.soeasy.framework.codec.binary.ToBinaryCodec; -import run.soeasy.framework.codec.crypto.HmacMD5; -import run.soeasy.framework.codec.crypto.HmacSHA1; +import run.soeasy.framework.codec.crypto.MAC; import run.soeasy.framework.io.IOUtils; /** @@ -117,12 +116,8 @@ public Codec gzip() { return gzip(HexCodec.DEFAULT); } - public Encoder toHmacMD5(String secretKey) { - return toEncoder(new HmacMD5(encode(secretKey)).toEncoder(HexCodec.DEFAULT)); - } - - public Encoder toHmacSHA1(String secretKey) { - return toEncoder(new HmacSHA1(encode(secretKey)).toEncoder(HexCodec.DEFAULT)); + public Encoder toMac(String algorithm, String secretKey) { + return toEncoder(new MAC(algorithm, encode(secretKey))); } public static CharsetCodec charset(Charset charset) { diff --git a/core/src/main/java/run/soeasy/framework/codec/format/KeyValueFormat.java b/core/src/main/java/run/soeasy/framework/codec/format/KeyValueFormat.java index 8b60ad83f..0076ddc7a 100644 --- a/core/src/main/java/run/soeasy/framework/codec/format/KeyValueFormat.java +++ b/core/src/main/java/run/soeasy/framework/codec/format/KeyValueFormat.java @@ -13,7 +13,6 @@ import run.soeasy.framework.codec.Codec; import run.soeasy.framework.codec.CodecException; import run.soeasy.framework.core.collection.ArrayUtils; -import run.soeasy.framework.core.collection.DefaultMultiValueMap; import run.soeasy.framework.core.collection.MultiValueMap; import run.soeasy.framework.core.convert.ConversionException; import run.soeasy.framework.core.convert.Converter; @@ -21,34 +20,38 @@ import run.soeasy.framework.core.convert.TypeDescriptor; import run.soeasy.framework.core.convert.strings.StringFormat; import run.soeasy.framework.core.convert.support.SystemConversionService; -import run.soeasy.framework.core.convert.value.TypedValueAccessor; import run.soeasy.framework.core.domain.KeyValue; import run.soeasy.framework.core.join.Joiner; import run.soeasy.framework.core.join.KeyValueSplitter; -import run.soeasy.framework.core.transform.collection.MapEntryMapping; -import run.soeasy.framework.core.transform.templates.DefaultMapper; -import run.soeasy.framework.core.transform.templates.Mapping; +import run.soeasy.framework.core.mapping.DefaultMapper; +import run.soeasy.framework.core.mapping.property.MapMapping; +import run.soeasy.framework.core.mapping.property.Property; +import run.soeasy.framework.core.mapping.property.PropertyAccessor; +import run.soeasy.framework.core.mapping.property.PropertyMapper; +import run.soeasy.framework.core.streaming.Mapping; import run.soeasy.framework.core.type.ResolvableType; /** * 键值对格式处理器,提供键值对数据的编解码、类型转换和流式处理能力,适用于配置解析、参数处理、数据格式化等场景。 * - *

核心功能: + *

+ * 核心功能: *

    - *
  • 将对象与键值对字符串相互转换(如"a=1&b=2"与Map的互转)
  • - *
  • 支持自定义分隔符(键值对之间)和连接符(键与值之间)
  • - *
  • 处理多值键(同一键对应多个值),适配{@link MultiValueMap}数据结构
  • - *
  • 集成类型转换服务,支持复杂对象与键值对的双向映射
  • - *
  • 提供流式读写能力,高效处理大文本或网络流数据
  • + *
  • 将对象与键值对字符串相互转换(如"a=1&b=2"与Map的互转)
  • + *
  • 支持自定义分隔符(键值对之间)和连接符(键与值之间)
  • + *
  • 处理多值键(同一键对应多个值),适配{@link Mapping}数据结构
  • + *
  • 集成类型转换服务,支持复杂对象与键值对的双向映射
  • + *
  • 提供流式读写能力,高效处理大文本或网络流数据
  • *
* - *

实现特性: + *

+ * 实现特性: *

    - *
  • 继承{@link KeyValueSplitter},复用键值对分割与拼接逻辑
  • - *
  • 实现{@link StringFormat},支持对象与字符串的格式化转换
  • - *
  • 实现{@link Codec},定义{@link MultiValueMap}与字符串的编解码规则
  • - *
  • 实现{@link ConverterAware},集成类型转换能力,支持复杂对象处理
  • - *
  • 通过{@link DefaultMapper}实现对象与键值对的映射转换
  • + *
  • 继承{@link KeyValueSplitter},复用键值对分割与拼接逻辑
  • + *
  • 实现{@link StringFormat},支持对象与字符串的格式化转换
  • + *
  • 实现{@link Codec},定义{@link MultiValueMap}与字符串的编解码规则
  • + *
  • 实现{@link ConverterAware},集成类型转换能力,支持复杂对象处理
  • + *
  • 通过{@link DefaultMapper}实现对象与键值对的映射转换
  • *
* * @author soeasy.run @@ -59,13 +62,12 @@ @Getter @Setter public class KeyValueFormat extends KeyValueSplitter - implements StringFormat, ConverterAware, Codec, String> { - + implements StringFormat, ConverterAware, Codec, String> { + /** - * 键值对映射器,负责对象与键值对结构的映射转换, - * 支持将任意对象转换为键值对集合,或将键值对集合转换为指定类型的对象。 + * 键值对映射器,负责对象与键值对结构的映射转换, 支持将任意对象转换为键值对集合,或将键值对集合转换为指定类型的对象。 */ - private final DefaultMapper> keyValueMapper = new DefaultMapper<>(); + private final PropertyMapper keyValueMapper = new PropertyMapper<>(); /** * 获取当前使用的类型转换器 @@ -99,22 +101,23 @@ public KeyValueFormat(@NonNull CharSequence delimiter, @NonNull CharSequence con super(delimiter, connector, keyCodec, valueCodec); setConverter(SystemConversionService.getInstance()); keyValueMapper.getMappingProvider().registerFactory(Map.class, - (map, type) -> new MapEntryMapping(map, type, getConverter())); + (map, type) -> new MapMapping(map, type, getConverter())); } /** * 将键值对元素拼接并追加到目标Appendable,支持多值键的处理 * - *

特殊处理: + *

+ * 特殊处理: *

    - *
  • 若值为{@link Iterable}(如List),则为每个元素生成一个键值对
  • - *
  • 若值为数组,则为每个数组元素生成一个键值对
  • - *
  • 普通值直接生成一个键值对
  • + *
  • 若值为{@link Iterable}(如List),则为每个元素生成一个键值对
  • + *
  • 若值为数组,则为每个数组元素生成一个键值对
  • + *
  • 普通值直接生成一个键值对
  • *
* * @param appendable 目标追加器(如StringBuilder、FileWriter) - * @param count 已处理的键值对数量,用于分隔符的添加(首个元素前不加分隔符) - * @param element 待处理的键值对元素 + * @param count 已处理的键值对数量,用于分隔符的添加(首个元素前不加分隔符) + * @param element 待处理的键值对元素 * @return 处理后的键值对总数量 * @throws IOException 当向Appendable写入数据失败时抛出 */ @@ -130,8 +133,9 @@ public long join(Appendable appendable, long count, KeyValue iterator = ArrayUtils.elements(value).stream().iterator(); + while (iterator.hasNext()) { + count += super.join(appendable, count, KeyValue.of(element.getKey(), iterator.next())); } return count; } @@ -141,24 +145,25 @@ public long join(Appendable appendable, long count, KeyValue处理流程: + *

+ * 处理流程: *

    - *
  1. 通过{@link #keyValueMapper}将对象转换为键值对集合
  2. - *
  3. 过滤出可读取的值,生成键值对迭代器
  4. - *
  5. 使用{@link Joiner}将键值对拼接为字符串并写入Appendable
  6. + *
  7. 通过{@link #keyValueMapper}将对象转换为键值对集合
  8. + *
  9. 过滤出可读取的值,生成键值对迭代器
  10. + *
  11. 使用{@link Joiner}将键值对拼接为字符串并写入Appendable
  12. *
* - * @param source 待格式化的对象(通常为Map或可转换为Map的类型) + * @param source 待格式化的对象(通常为Map或可转换为Map的类型) * @param sourceTypeDescriptor 源对象的类型描述符,用于精确转换 - * @param appendable 目标追加位置(如StringBuilder、FileWriter) - * @throws IOException 当向Appendable写入数据失败时抛出 + * @param appendable 目标追加位置(如StringBuilder、FileWriter) + * @throws IOException 当向Appendable写入数据失败时抛出 * @throws ConversionException 当对象转换为键值对失败时抛出 */ @Override public void to(Object source, TypeDescriptor sourceTypeDescriptor, Appendable appendable) throws ConversionException, IOException { - Mapping mapping = keyValueMapper.getMapping(source, sourceTypeDescriptor); - Iterator> iterator = mapping.getElements().stream() + Mapping mapping = keyValueMapper.getMapping(source, sourceTypeDescriptor); + Iterator> iterator = mapping.stream() .filter((e) -> e.getValue().isReadable()).map((e) -> KeyValue.of(e.getKey(), e.getValue().get())) .iterator(); Joiner.joinAll(appendable, iterator, this); @@ -167,18 +172,19 @@ public void to(Object source, TypeDescriptor sourceTypeDescriptor, Appendable ap /** * 从可读数据源(如Reader)读取键值对字符串并转换为指定类型的对象 * - *

处理流程: + *

+ * 处理流程: *

    - *
  1. 将可读数据源解析为键值对集合
  2. - *
  3. 转换为Map结构(键为String类型,值为String列表)支持多值键
  4. - *
  5. 通过{@link #keyValueMapper}将Map转换为目标类型的对象
  6. + *
  7. 将可读数据源解析为键值对集合
  8. + *
  9. 转换为Map结构(键为String类型,值为String列表)支持多值键
  10. + *
  11. 通过{@link #keyValueMapper}将Map转换为目标类型的对象
  12. *
* - * @param readable 可读数据源(如StringReader、FileReader) + * @param readable 可读数据源(如StringReader、FileReader) * @param targetTypeDescriptor 目标对象的类型描述符 * @return 转换后的目标类型对象 * @throws ConversionException 当键值对转换为目标类型失败时抛出 - * @throws IOException 当从Readable读取数据失败时抛出 + * @throws IOException 当从Readable读取数据失败时抛出 */ @Override public Object from(Readable readable, TypeDescriptor targetTypeDescriptor) throws ConversionException, IOException { @@ -192,25 +198,27 @@ public Object from(Readable readable, TypeDescriptor targetTypeDescriptor) throw /** * 将{@link MultiValueMap}编码为键值对字符串 * - *

默认实现通过类型转换完成,等价于将MultiValueMap转换为String类型。 + *

+ * 默认实现通过类型转换完成,等价于将MultiValueMap转换为String类型。 * * @param source 待编码的MultiValueMap * @return 编码后的键值对字符串 * @throws CodecException 当编码过程失败时抛出(如转换异常) */ @Override - public final String encode(MultiValueMap source) throws CodecException { + public final String encode(Mapping source) throws CodecException { return convert(source, String.class); } /** * 将键值对字符串解码为{@link MultiValueMap} * - *

处理流程: + *

+ * 处理流程: *

    - *
  1. 将字符串分割为键值对集合
  2. - *
  3. 按键分组,收集每个键对应的多个值(保持插入顺序)
  4. - *
  5. 转换为{@link DefaultMultiValueMap}返回
  6. + *
  7. 将字符串分割为键值对集合
  8. + *
  9. 按键分组,收集每个键对应的多个值(保持插入顺序)
  10. + *
  11. 转换为{@link Mapping}返回
  12. *
* * @param source 待解码的键值对字符串 @@ -218,13 +226,12 @@ public final String encode(MultiValueMap source) throws CodecExc * @throws CodecException 当解码过程失败时抛出(如分割异常) */ @Override - public final MultiValueMap decode(String source) throws CodecException { + public final Mapping decode(String source) throws CodecException { // 使用Stream流处理键值对,分组收集到LinkedHashMap保持顺序 Map> map = split(source).collect(Collectors.groupingBy(KeyValue::getKey, // 按键分组 LinkedHashMap::new, // 使用LinkedHashMap保持键的顺序 Collectors.mapping(KeyValue::getValue, Collectors.toList()) // 收集值列表 )); - return new DefaultMultiValueMap<>(map); + return Mapping.ofMultiMapped(map); } } - \ No newline at end of file diff --git a/core/src/main/java/run/soeasy/framework/core/StringUtils.java b/core/src/main/java/run/soeasy/framework/core/StringUtils.java index 94278bd8d..616e507ac 100644 --- a/core/src/main/java/run/soeasy/framework/core/StringUtils.java +++ b/core/src/main/java/run/soeasy/framework/core/StringUtils.java @@ -6,10 +6,10 @@ import java.util.StringTokenizer; import lombok.experimental.UtilityClass; -import run.soeasy.framework.core.collection.Elements; import run.soeasy.framework.core.domain.CharSequenceSplitIterator; import run.soeasy.framework.core.domain.CharSequenceTemplate; import run.soeasy.framework.core.domain.Range; +import run.soeasy.framework.core.streaming.Streamable; /** * 字符串工具类 提供字符串操作的各种实用方法,包括查找、替换、分割、比较等功能 @@ -792,7 +792,7 @@ public static int lastIndexOf(CharSequence source, int sourceOffset, int sourceC * @param filters 分隔符序列 * @return 分割后的元素集合 */ - public static Elements split(CharSequence charSequence, boolean trimTokens, + public static Streamable split(CharSequence charSequence, boolean trimTokens, boolean ignoreEmptyTokens, CharSequence... filters) { return split(charSequence, filters).map((s) -> trimTokens ? (s == null ? s : s.trim()) : s) .filter((s) -> (ignoreEmptyTokens ? StringUtils.isNotEmpty(s) : true)); @@ -805,9 +805,9 @@ public static Elements split(CharSequence charSequence, bo * @param filters 分隔符序列 * @return 分割后的元素集合 */ - public static Elements split(CharSequence charSequence, CharSequence... filters) { + public static Streamable split(CharSequence charSequence, CharSequence... filters) { if (charSequence == null) { - return Elements.empty(); + return Streamable.empty(); } return split(charSequence, 0, charSequence.length(), Arrays.asList(filters)); } @@ -819,10 +819,10 @@ public static Elements split(CharSequence charSequence, Ch * @param filters 分隔符集合 * @return 分割后的元素集合 */ - public static Elements split(CharSequence charSequence, + public static Streamable split(CharSequence charSequence, Collection filters) { if (charSequence == null) { - return Elements.empty(); + return Streamable.empty(); } return split(charSequence, 0, charSequence.length(), filters); } @@ -836,10 +836,10 @@ public static Elements split(CharSequence charSequence, * @param filters 分隔符集合 * @return 分割后的元素集合 */ - public static Elements split(CharSequence charSequence, int beginIndex, int endIndex, + public static Streamable split(CharSequence charSequence, int beginIndex, int endIndex, Collection filters) { if (StringUtils.isEmpty(charSequence)) { - return Elements.empty(); + return Streamable.empty(); } boolean find = false; @@ -851,9 +851,9 @@ public static Elements split(CharSequence charSequence, in } if (!find) { - return Elements.singleton(new CharSequenceTemplate(charSequence)); + return Streamable.singleton(new CharSequenceTemplate(charSequence)); } - return Elements.of(() -> new CharSequenceSplitIterator(charSequence, filters, beginIndex, endIndex)); + return Streamable.of(() -> new CharSequenceSplitIterator(charSequence, filters, beginIndex, endIndex)); } /** @@ -952,9 +952,9 @@ public static boolean startsWithIgnoreCase(String str, String prefix) { * @param delimiters 分隔符 * @return 分词后的元素集合 */ - public static Elements tokenize(String text, String delimiters) { + public static Streamable tokenize(String text, String delimiters) { if (StringUtils.isEmpty(text)) { - return Elements.empty(); + return Streamable.empty(); } return tokenize(new StringTokenizer(text, delimiters)); @@ -969,7 +969,7 @@ public static Elements tokenize(String text, String delimiters) { * @param ignoreEmptyTokens 是否忽略空标记 * @return 分词后的元素集合 */ - public static Elements tokenize(String str, String delimiters, boolean trimTokens, + public static Streamable tokenize(String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) { return tokenize(str, delimiters).map((s) -> trimTokens ? (s == null ? s : s.trim()) : s) .filter((s) -> (ignoreEmptyTokens ? StringUtils.isNotEmpty(s) : true)); @@ -981,12 +981,12 @@ public static Elements tokenize(String str, String delimiters, boolean t * @param tokenizer 字符串分词器 * @return 分词后的元素集合 */ - public static Elements tokenize(StringTokenizer tokenizer) { + public static Streamable tokenize(StringTokenizer tokenizer) { if (tokenizer == null) { - return Elements.empty(); + return Streamable.empty(); } - return Elements.of(() -> new Iterator() { + return Streamable.of(() -> new Iterator() { @Override public boolean hasNext() { return tokenizer.hasMoreTokens(); diff --git a/core/src/main/java/run/soeasy/framework/core/annotation/AbstractAnnotationPropertyMapping.java b/core/src/main/java/run/soeasy/framework/core/annotation/AbstractAnnotationPropertyMapping.java index 7f3442301..6f2fd67a1 100644 --- a/core/src/main/java/run/soeasy/framework/core/annotation/AbstractAnnotationPropertyMapping.java +++ b/core/src/main/java/run/soeasy/framework/core/annotation/AbstractAnnotationPropertyMapping.java @@ -7,24 +7,25 @@ /** * 注解属性映射抽象基类,实现{@link AnnotationProperties}接口, - * 提供注解属性映射的基础实现,包括hashCode和equals方法的默认实现, - * 是框架中构建具体注解属性映射器的抽象基类。 + * 提供注解属性映射的基础实现,包括hashCode和equals方法的默认实现, 是框架中构建具体注解属性映射器的抽象基类。 *

* 该类基于注解类型和属性元素实现了hashCode和equals方法,确保相同注解类型和属性值的实例相等, * 并提供了合理的哈希值计算方式,适用于需要统一处理注解属性映射的场景。 * - *

核心特性: + *

+ * 核心特性: *

    - *
  • 标准化相等性:基于注解类型和属性值实现equals方法
  • - *
  • 一致哈希值:基于注解类型和属性值计算hashCode
  • - *
  • 抽象模板:定义了注解属性映射的基本框架,子类只需实现具体属性获取逻辑
  • + *
  • 标准化相等性:基于注解类型和属性值实现equals方法
  • + *
  • 一致哈希值:基于注解类型和属性值计算hashCode
  • + *
  • 抽象模板:定义了注解属性映射的基本框架,子类只需实现具体属性获取逻辑
  • *
* - *

实现说明: + *

+ * 实现说明: *

    - *
  • equals方法:比较注解类型和所有属性值的相等性
  • - *
  • hashCode方法:结合注解类型和属性值计算哈希值
  • - *
  • 属性比较:使用ObjectUtils.equals进行深层值比较
  • + *
  • equals方法:比较注解类型和所有属性值的相等性
  • + *
  • hashCode方法:结合注解类型和属性值计算哈希值
  • + *
  • 属性比较:使用ObjectUtils.equals进行深层值比较
  • *
* * @author soeasy.run @@ -33,47 +34,47 @@ */ public abstract class AbstractAnnotationPropertyMapping implements AnnotationProperties { - /** - * 计算注解属性映射的哈希值 - *

- * 哈希值计算基于: - *

    - *
  1. 注解类型的哈希值
  2. - *
  3. 所有属性元素的哈希值(通过ArrayUtils.hashCode计算)
  4. - *
- * - * @return 注解属性映射的哈希值 - */ - @Override - public int hashCode() { - return getType().hashCode() + ArrayUtils.hashCode(getElements().toList()); - } + /** + * 计算注解属性映射的哈希值 + *

+ * 哈希值计算基于: + *

    + *
  1. 注解类型的哈希值
  2. + *
  3. 所有属性元素的哈希值(通过ArrayUtils.hashCode计算)
  4. + *
+ * + * @return 注解属性映射的哈希值 + */ + @Override + public int hashCode() { + return getType().hashCode() + ArrayUtils.hashCode(elements().toArray()); + } - /** - * 判断与其他对象的相等性 - *

- * 相等性判断基于: - *

    - *
  1. 对象引用相等
  2. - *
  3. 注解类型相同
  4. - *
  5. 所有属性值相同(使用ObjectUtils.equals进行深层比较)
  6. - *
- * - * @param obj 待比较的对象 - * @return 如果对象相等返回true,否则返回false - */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } + /** + * 判断与其他对象的相等性 + *

+ * 相等性判断基于: + *

    + *
  1. 对象引用相等
  2. + *
  3. 注解类型相同
  4. + *
  5. 所有属性值相同(使用ObjectUtils.equals进行深层比较)
  6. + *
+ * + * @param obj 待比较的对象 + * @return 如果对象相等返回true,否则返回false + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } - if (obj instanceof AnnotationProperties) { - AnnotationProperties annotationProperties = (AnnotationProperties) obj; - return getType() == annotationProperties.getType() && getElements() - .equals(annotationProperties.getElements(), (a, b) -> ObjectUtils.equals(a.getKey(), b.getKey()) - && ObjectUtils.equals(a.getValue().get(), b.getValue().get())); - } - return false; - } + if (obj instanceof AnnotationProperties) { + AnnotationProperties annotationProperties = (AnnotationProperties) obj; + return getType() == annotationProperties.getType() + && equalsInAnyOrder(annotationProperties, (a, b) -> ObjectUtils.equals(a.getKey(), b.getKey()) + && ObjectUtils.equals(a.getValue().get(), b.getValue().get())); + } + return false; + } } \ No newline at end of file diff --git a/core/src/main/java/run/soeasy/framework/core/annotation/AnnotationProperties.java b/core/src/main/java/run/soeasy/framework/core/annotation/AnnotationProperties.java index 6a57fa1a6..0c64f6311 100644 --- a/core/src/main/java/run/soeasy/framework/core/annotation/AnnotationProperties.java +++ b/core/src/main/java/run/soeasy/framework/core/annotation/AnnotationProperties.java @@ -7,107 +7,111 @@ import run.soeasy.framework.core.convert.Converter; import run.soeasy.framework.core.convert.TypeDescriptor; -import run.soeasy.framework.core.convert.value.TypedValue; -import run.soeasy.framework.core.transform.property.TypedProperties; +import run.soeasy.framework.core.mapping.property.PropertyAccessor; +import run.soeasy.framework.core.mapping.property.PropertyMapping; import run.soeasy.framework.core.type.ReflectionUtils; /** - * 注解属性接口,继承自{@link TypedProperties}和{@link InvocationHandler}, + * 注解属性接口,继承自{@link PropertyMapping}和{@link InvocationHandler}, * 提供注解属性的动态访问能力和合成动态注解的功能,是框架中处理动态注解的核心抽象。 *

* 该接口通过代理模式将属性操作转换为注解的方法调用,支持基于属性集合动态生成注解实例, * 适用于需要在运行时动态构造注解的场景,如测试框架、动态代理、注解处理器等。 * - *

核心特性: + *

+ * 核心特性: *

    - *
  • 属性-注解映射:将属性集合转换为注解的方法调用
  • - *
  • 动态合成:通过{@link #synthesize()}方法生成代理注解实例
  • - *
  • 类型安全:通过泛型约束确保合成注解的类型一致性
  • - *
  • 代理处理:实现InvocationHandler接口,处理注解方法的调用
  • + *
  • 属性-注解映射:将属性集合转换为注解的方法调用
  • + *
  • 动态合成:通过{@link #synthesize()}方法生成代理注解实例
  • + *
  • 类型安全:通过泛型约束确保合成注解的类型一致性
  • + *
  • 代理处理:实现InvocationHandler接口,处理注解方法的调用
  • *
* - *

泛型说明: + *

+ * 泛型说明: *

    - *
  • {@code A}:合成的注解类型,必须是{@link Annotation}的子类
  • + *
  • {@code A}:合成的注解类型,必须是{@link Annotation}的子类
  • *
* - *

使用场景: + *

+ * 使用场景: *

    - *
  • 动态注解生成:在运行时根据配置生成自定义注解
  • - *
  • 测试框架:动态构造测试所需的注解
  • - *
  • 代理增强:为代理对象添加动态注解
  • - *
  • 注解处理器:动态处理和生成注解实例
  • - *
  • 元编程:基于属性集合动态生成注解元数据
  • + *
  • 动态注解生成:在运行时根据配置生成自定义注解
  • + *
  • 测试框架:动态构造测试所需的注解
  • + *
  • 代理增强:为代理对象添加动态注解
  • + *
  • 注解处理器:动态处理和生成注解实例
  • + *
  • 元编程:基于属性集合动态生成注解元数据
  • *
* * @author soeasy.run - * @see TypedProperties + * @see PropertyMapping * @see InvocationHandler * @see Proxy */ -public interface AnnotationProperties
extends TypedProperties, InvocationHandler { +public interface AnnotationProperties + extends PropertyMapping, InvocationHandler { - /** - * 获取合成注解的类型 - * - * @return 注解的Class对象,如{@code MyAnnotation.class} - */ - Class getType(); + /** + * 获取合成注解的类型 + * + * @return 注解的Class对象,如{@code MyAnnotation.class} + */ + Class getType(); - /** - * 处理代理方法的调用(InvocationHandler实现) - *

- * 该方法实现了注解代理的核心逻辑,处理以下类型的调用: - *

    - *
  1. Object类方法:equals/hashCode/toString
  2. - *
  3. 注解元方法:annotationType()
  4. - *
  5. 注解属性方法:通过属性名称获取对应值
  6. - *
- * - * @param proxy 代理实例 - * @param method 被调用的方法 - * @param args 方法参数 - * @return 方法调用结果 - * @throws Throwable 调用过程中抛出的异常 - */ - @Override - default Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - if (ReflectionUtils.isEqualsMethod(method)) { - return equals(args[0]); - } - if (ReflectionUtils.isHashCodeMethod(method)) { - return hashCode(); - } - if (ReflectionUtils.isToStringMethod(method)) { - return toString(); - } + /** + * 处理代理方法的调用(InvocationHandler实现) + *

+ * 该方法实现了注解代理的核心逻辑,处理以下类型的调用: + *

    + *
  1. Object类方法:equals/hashCode/toString
  2. + *
  3. 注解元方法:annotationType()
  4. + *
  5. 注解属性方法:通过属性名称获取对应值
  6. + *
+ * + * @param proxy 代理实例 + * @param method 被调用的方法 + * @param args 方法参数 + * @return 方法调用结果 + * @throws Throwable 调用过程中抛出的异常 + */ + @Override + default Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (ReflectionUtils.isEqualsMethod(method)) { + return equals(args[0]); + } + if (ReflectionUtils.isHashCodeMethod(method)) { + return hashCode(); + } + if (ReflectionUtils.isToStringMethod(method)) { + return toString(); + } - if (method.getName().equals("annotationType") && method.getParameterCount() == 0) { - return getType(); - } + if (method.getName().equals("annotationType") && method.getParameterCount() == 0) { + return getType(); + } - if (hasKey(method.getName())) { - TypedValue typedValue = get(method.getName()); - return typedValue == null ? null - : typedValue.map(TypeDescriptor.forExecutableReturnType(method), Converter.assignable()).get(); - } - throw new IllegalArgumentException( - String.format("Method [%s] is unsupported for synthesized annotation type [%s]", method, getType())); - } + if (hasKey(method.getName())) { + return getValues(method.getName()).findFirst() + .map((typedValue) -> typedValue + .map(TypeDescriptor.forExecutableReturnType(method), Converter.assignable()).get()) + .orElse(null); + } + throw new IllegalArgumentException( + String.format("Method [%s] is unsupported for synthesized annotation type [%s]", method, getType())); + } - /** - * 合成动态注解实例 - *

- * 该方法使用Java动态代理生成实现指定注解类型的代理实例, - * 代理的方法调用将委托给当前{@link AnnotationProperties}实现, - * 从而实现基于属性集合的动态注解。 - * - * @return 动态合成的注解实例 - */ - @SuppressWarnings("unchecked") - default A synthesize() { - Class type = getType(); - ClassLoader classLoader = type.getClassLoader(); - return (A) Proxy.newProxyInstance(classLoader, new Class[] { type, SynthesizedAnnotation.class }, this); - } + /** + * 合成动态注解实例 + *

+ * 该方法使用Java动态代理生成实现指定注解类型的代理实例, 代理的方法调用将委托给当前{@link AnnotationProperties}实现, + * 从而实现基于属性集合的动态注解。 + * + * @return 动态合成的注解实例 + */ + @SuppressWarnings("unchecked") + default A synthesize() { + Class type = getType(); + ClassLoader classLoader = type.getClassLoader(); + return (A) Proxy.newProxyInstance(classLoader, new Class[] { type, SynthesizedAnnotation.class }, this); + } } \ No newline at end of file diff --git a/core/src/main/java/run/soeasy/framework/core/annotation/CustomizeAnnotationPropertyMapping.java b/core/src/main/java/run/soeasy/framework/core/annotation/CustomizeAnnotationPropertyMapping.java index 4d9a7808b..ad9343d5c 100644 --- a/core/src/main/java/run/soeasy/framework/core/annotation/CustomizeAnnotationPropertyMapping.java +++ b/core/src/main/java/run/soeasy/framework/core/annotation/CustomizeAnnotationPropertyMapping.java @@ -5,12 +5,13 @@ import lombok.Getter; import lombok.NonNull; import lombok.RequiredArgsConstructor; -import run.soeasy.framework.core.transform.property.TypedProperties; -import run.soeasy.framework.core.transform.property.TypedPropertiesWrapper; +import run.soeasy.framework.core.mapping.property.PropertyAccessor; +import run.soeasy.framework.core.mapping.property.PropertyMapping; +import run.soeasy.framework.core.mapping.property.PropertyMappingWrapper; /** * 自定义注解属性映射器,继承自{@link AbstractAnnotationPropertyMapping}, - * 实现{@link AnnotationProperties}和{@link TypedPropertiesWrapper}接口, + * 实现{@link AnnotationProperties}和{@link PropertyMappingWrapper}接口, * 用于将任意类型的属性包装为注解属性,支持自定义注解类型与属性的映射关系。 *

* 该类通过组合模式将外部属性源与注解类型绑定,实现注解属性的动态访问, @@ -18,7 +19,7 @@ * *

核心特性: *