This commit is contained in:
@@ -5,14 +5,17 @@ import java.io.StringWriter;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 通用工具
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2021-02-13 11:39
|
||||
*/
|
||||
public interface TimiJava {
|
||||
|
||||
/** 全 0 UUID */
|
||||
String ZERO_UUID = "00000000-0000-0000-0000-000000000000";
|
||||
|
||||
/**
|
||||
@@ -69,41 +72,87 @@ public interface TimiJava {
|
||||
return !isEmpty(object);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为空时取默认值
|
||||
*
|
||||
* @param obj 判空对象
|
||||
* @param defaultObj 默认对象
|
||||
* @return 最终值
|
||||
* @param <T> 对象类型
|
||||
*/
|
||||
static <T> T defaultIfNull(T obj, T defaultObj) {
|
||||
return firstNotNull(obj, defaultObj);
|
||||
return Objects.requireNonNullElse(obj, defaultObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为空时取默认值
|
||||
*
|
||||
* @param obj 判空对象
|
||||
* @param defaultObj 默认对象
|
||||
* @return 最终值
|
||||
* @param <T> 对象类型
|
||||
*/
|
||||
static <T> T defaultIfEmpty(T obj, T defaultObj) {
|
||||
return firstNotEmpty(obj, defaultObj);
|
||||
if (isEmpty(obj)) {
|
||||
return defaultObj;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取第一个非空对象
|
||||
*
|
||||
* @param objects 对象列表
|
||||
* @return 最终值
|
||||
* @param <T> 对象类型
|
||||
*/
|
||||
@SafeVarargs
|
||||
static <T> T firstNotNull(T... objects) {
|
||||
for (int i = 0; i < objects.length; i++) {
|
||||
if (objects[i] != null) {
|
||||
return objects[i];
|
||||
for (T object : objects) {
|
||||
if (object != null) {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取第一个非空对象
|
||||
*
|
||||
* @param objects 对象列表
|
||||
* @return 最终值
|
||||
* @param <T> 对象类型
|
||||
*/
|
||||
@SafeVarargs
|
||||
static <T> T firstNotEmpty(T... objects) {
|
||||
for (int i = 0; i < objects.length; i++) {
|
||||
if (TimiJava.isNotEmpty(objects[i])) {
|
||||
return objects[i];
|
||||
for (T object : objects) {
|
||||
if (TimiJava.isNotEmpty(object)) {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static String toString(Exception e) {
|
||||
/**
|
||||
* 打印异常
|
||||
*
|
||||
* @param e 异常
|
||||
* @return 异常文本
|
||||
*/
|
||||
static String serializeThrowable(Throwable e) {
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw);
|
||||
e.printStackTrace(pw);
|
||||
return sw.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 空安全迭代器,使 for 迭代可以入参为空
|
||||
*
|
||||
* @param iterable 可迭代对象
|
||||
* @return 安全迭代对象
|
||||
* @param <T> 迭代对象类型
|
||||
*/
|
||||
static <T> Iterable<T> safeIterable(Iterable<T> iterable) {
|
||||
return defaultIfNull(iterable, Collections::emptyIterator);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
package com.imyeyu.java.bean;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 分页
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2025-07-25 11:04
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class BasePage {
|
||||
|
||||
/** 下标 */
|
||||
@@ -12,35 +21,13 @@ public class BasePage {
|
||||
/** 数据量 */
|
||||
protected long size = 16;
|
||||
|
||||
public BasePage() {
|
||||
}
|
||||
|
||||
public BasePage(int index, long size) {
|
||||
this.index = index;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
/** 上一页 */
|
||||
public void prev() {
|
||||
index--;
|
||||
}
|
||||
|
||||
/** 下一页 */
|
||||
public void next() {
|
||||
index++;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public void setIndex(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setSize(long size) {
|
||||
this.size = size;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package com.imyeyu.java.bean;
|
||||
|
||||
import com.imyeyu.java.TimiJava;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 分页结果
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2025-07-25 11:04
|
||||
*/
|
||||
@Data
|
||||
public class BasePageResult<T> {
|
||||
|
||||
/** 总数据量 */
|
||||
@@ -16,6 +20,7 @@ public class BasePageResult<T> {
|
||||
/** 总页数 */
|
||||
protected int pages;
|
||||
|
||||
/** 分页数据列表 */
|
||||
protected List<T> list;
|
||||
|
||||
/**
|
||||
@@ -36,15 +41,6 @@ public class BasePageResult<T> {
|
||||
return !isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取总数据量
|
||||
*
|
||||
* @return 总数据量
|
||||
*/
|
||||
public long getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置总数据量
|
||||
*
|
||||
@@ -57,18 +53,15 @@ public class BasePageResult<T> {
|
||||
}
|
||||
}
|
||||
|
||||
public List<T> getList() {
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置分页数据列表
|
||||
*
|
||||
* @param list 分页数据列表
|
||||
*/
|
||||
public void setList(List<T> list) {
|
||||
this.list = list;
|
||||
if (TimiJava.isNotEmpty(list)) {
|
||||
pages = (int) Math.ceil(1D * total / list.size());
|
||||
}
|
||||
}
|
||||
|
||||
public int getPages() {
|
||||
return pages;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,11 @@ public interface Callback {
|
||||
/** 执行程序 */
|
||||
void handler() throws RuntimeException;
|
||||
|
||||
/**
|
||||
* 非空时执行回调
|
||||
*
|
||||
* @param callback 回调
|
||||
*/
|
||||
static void handle(Callback callback) {
|
||||
if (callback == null) {
|
||||
return;
|
||||
|
||||
@@ -21,7 +21,9 @@ public interface CallbackArg<T> {
|
||||
/**
|
||||
* 非空时执行回调
|
||||
*
|
||||
* @param t 参数
|
||||
* @param callbackArg 回调
|
||||
* @param <T> 入参类型
|
||||
*/
|
||||
static <T> void handle(T t, CallbackArg<T> callbackArg) {
|
||||
if (callbackArg == null) {
|
||||
|
||||
@@ -23,7 +23,10 @@ public interface CallbackArgReturn<T, R> {
|
||||
/**
|
||||
* 非空时执行回调
|
||||
*
|
||||
* @param t 参数
|
||||
* @param callbackArgReturn 回调
|
||||
* @param <T> 入参类型
|
||||
* @param <R> 返回类型
|
||||
*/
|
||||
static <T, R> R handle(T t, CallbackArgReturn<T, R> callbackArgReturn) {
|
||||
if (callbackArgReturn == null) {
|
||||
|
||||
@@ -22,6 +22,7 @@ public interface CallbackReturn<R> {
|
||||
* 非空时执行回调
|
||||
*
|
||||
* @param callbackReturn 回调
|
||||
* @param <R> 返回类型
|
||||
*/
|
||||
static <R> R handle(CallbackReturn<R> callbackReturn) {
|
||||
if (callbackReturn == null) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.imyeyu.java.bean;
|
||||
|
||||
import com.imyeyu.java.ref.Ref;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 多语言
|
||||
@@ -8,9 +9,12 @@ import com.imyeyu.java.ref.Ref;
|
||||
* @author 夜雨
|
||||
* @since 2022-02-23 11:25
|
||||
*/
|
||||
@Data
|
||||
public class Language {
|
||||
|
||||
/**
|
||||
* 支持语言枚举
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2025-12-05 14:31
|
||||
*/
|
||||
@@ -67,68 +71,4 @@ public class Language {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public String getZhCN() {
|
||||
return zhCN;
|
||||
}
|
||||
|
||||
public void setZhCN(String zhCN) {
|
||||
this.zhCN = zhCN;
|
||||
}
|
||||
|
||||
public String getZhTW() {
|
||||
return zhTW;
|
||||
}
|
||||
|
||||
public void setZhTW(String zhTW) {
|
||||
this.zhTW = zhTW;
|
||||
}
|
||||
|
||||
public String getEnUS() {
|
||||
return enUS;
|
||||
}
|
||||
|
||||
public void setEnUS(String enUS) {
|
||||
this.enUS = enUS;
|
||||
}
|
||||
|
||||
public String getRuRU() {
|
||||
return ruRU;
|
||||
}
|
||||
|
||||
public void setRuRU(String ruRU) {
|
||||
this.ruRU = ruRU;
|
||||
}
|
||||
|
||||
public String getKoKR() {
|
||||
return koKR;
|
||||
}
|
||||
|
||||
public void setKoKR(String koKR) {
|
||||
this.koKR = koKR;
|
||||
}
|
||||
|
||||
public String getJaJP() {
|
||||
return jaJP;
|
||||
}
|
||||
|
||||
public void setJaJP(String jaJP) {
|
||||
this.jaJP = jaJP;
|
||||
}
|
||||
|
||||
public String getDeDE() {
|
||||
return deDE;
|
||||
}
|
||||
|
||||
public void setDeDE(String deDE) {
|
||||
this.deDE = deDE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,18 +3,52 @@ package com.imyeyu.java.bean;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 多语言映射接口
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2024-04-03 11:27
|
||||
*/
|
||||
public interface LanguageMapping {
|
||||
|
||||
/**
|
||||
* 添加映射
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
*/
|
||||
void add(String key, String value);
|
||||
|
||||
/**
|
||||
* 是否存在映射
|
||||
*
|
||||
* @param key 键
|
||||
* @return true 为存在
|
||||
*/
|
||||
boolean has(String key);
|
||||
|
||||
/**
|
||||
* 获取映射值
|
||||
*
|
||||
* @param key 键
|
||||
* @return 值
|
||||
*/
|
||||
String text(String key);
|
||||
|
||||
/**
|
||||
* 获取映射值,不存在时使用默认值
|
||||
*
|
||||
* @param key 键
|
||||
* @param def 默认值
|
||||
* @return 值
|
||||
*/
|
||||
String text(String key, String def);
|
||||
|
||||
/**
|
||||
* 获取映射值并插入参数
|
||||
*
|
||||
* @param key 键
|
||||
* @param argsMap 参数列表
|
||||
* @return 值
|
||||
*/
|
||||
String textArgs(String key, Map<String, Object> argsMap);
|
||||
}
|
||||
|
||||
@@ -3,26 +3,79 @@ package com.imyeyu.java.bean;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 多语言消息接口
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2024-04-01 10:28
|
||||
*/
|
||||
public interface LanguageMsgMapping<T> {
|
||||
|
||||
/**
|
||||
* 设置消息
|
||||
*
|
||||
* @param msg 消息
|
||||
* @return 原对象
|
||||
*/
|
||||
T msg(String msg);
|
||||
|
||||
/**
|
||||
* 设置消息多语言键
|
||||
*
|
||||
* @param msgKey 键
|
||||
* @return 原对象
|
||||
*/
|
||||
T msgKey(String msgKey);
|
||||
|
||||
/**
|
||||
* 设置消息多语言键并附加插值参数
|
||||
*
|
||||
* @param msgKey 键
|
||||
* @param msgArgs 插值参数
|
||||
* @return 原对象
|
||||
*/
|
||||
T msgKey(String msgKey, Map<String, Object> msgArgs);
|
||||
|
||||
/**
|
||||
* 设置消息
|
||||
*
|
||||
* @param msg 消息
|
||||
*/
|
||||
void setMsg(String msg);
|
||||
|
||||
/**
|
||||
* 获取消息
|
||||
*
|
||||
* @return 消息
|
||||
*/
|
||||
String getMsg();
|
||||
|
||||
/**
|
||||
* 设置消息多语言键
|
||||
*
|
||||
* @param msgKey 键
|
||||
*/
|
||||
void setMsgKey(String msgKey);
|
||||
|
||||
|
||||
/**
|
||||
* 设置消息多语言键并附加插值参数列表
|
||||
*
|
||||
* @param msgKey 键
|
||||
* @param msgArgs 插值参数
|
||||
*/
|
||||
void setMsgKey(String msgKey, Map<String, Object> msgArgs);
|
||||
|
||||
/**
|
||||
* 获取消息多语言键
|
||||
*
|
||||
* @return 键
|
||||
*/
|
||||
String getMsgKey();
|
||||
|
||||
/**
|
||||
* 获取附加插值参数
|
||||
*
|
||||
* @return 附加插值参数列表
|
||||
*/
|
||||
Map<String, Object> getMsgArgs();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package com.imyeyu.java.bean.timi;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 通用代码(基于 HTTP 代码扩展)
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2021-05-21 14:32
|
||||
*/
|
||||
@Getter
|
||||
public enum TimiCode {
|
||||
|
||||
// ---------- 200 正常 ----------
|
||||
@@ -74,6 +77,7 @@ public enum TimiCode {
|
||||
/** 服务繁忙 */
|
||||
ERROR_SERVICE_BUSY(50300);
|
||||
|
||||
/** 代码 */
|
||||
final Integer value;
|
||||
|
||||
TimiCode(Integer value) {
|
||||
@@ -108,15 +112,6 @@ public enum TimiCode {
|
||||
return new TimiResponse<>(this).msg(toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取代码
|
||||
*
|
||||
* @return 代码
|
||||
*/
|
||||
public Integer getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据代码返回对象
|
||||
*
|
||||
@@ -125,9 +120,9 @@ public enum TimiCode {
|
||||
*/
|
||||
public static TimiCode fromCode(int code) {
|
||||
TimiCode[] codes = values();
|
||||
for (int i = 0; i < codes.length; i++) {
|
||||
if (codes[i].getValue() == code) {
|
||||
return codes[i];
|
||||
for (TimiCode timiCode : codes) {
|
||||
if (timiCode.getValue() == code) {
|
||||
return timiCode;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.imyeyu.java.bean.timi;
|
||||
|
||||
import com.imyeyu.java.bean.LanguageMsgMapping;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -13,6 +14,7 @@ import java.util.Map;
|
||||
public class TimiError extends AssertionError implements LanguageMsgMapping<TimiError> {
|
||||
|
||||
/** 代码 */
|
||||
@Getter
|
||||
protected final TimiCode code;
|
||||
|
||||
protected transient String msgKey;
|
||||
@@ -35,15 +37,6 @@ public class TimiError extends AssertionError implements LanguageMsgMapping<Timi
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取代码
|
||||
*
|
||||
* @return 代码
|
||||
*/
|
||||
public TimiCode getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public TimiResponse<?> toResponse() {
|
||||
return new TimiResponse<>(code).msg(getMessage()).msgKey(msgKey, msgArgs);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.imyeyu.java.bean.timi;
|
||||
import com.imyeyu.java.TimiJava;
|
||||
import com.imyeyu.java.bean.CallbackReturn;
|
||||
import com.imyeyu.java.bean.LanguageMsgMapping;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -15,6 +16,7 @@ import java.util.Map;
|
||||
public class TimiException extends RuntimeException implements LanguageMsgMapping<TimiException> {
|
||||
|
||||
/** 代码 */
|
||||
@Getter
|
||||
protected final TimiCode code;
|
||||
|
||||
protected transient String msgKey;
|
||||
@@ -48,15 +50,6 @@ public class TimiException extends RuntimeException implements LanguageMsgMappin
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取代码
|
||||
*
|
||||
* @return 代码
|
||||
*/
|
||||
public TimiCode getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public TimiResponse<?> toResponse() {
|
||||
return new TimiResponse<>(code).msg(getMessage()).msgKey(msgKey, msgArgs);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package com.imyeyu.java.bean.timi;
|
||||
|
||||
import com.imyeyu.java.bean.LanguageMsgMapping;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
@@ -11,9 +14,12 @@ import java.util.Map;
|
||||
* @author 夜雨
|
||||
* @since 2021-07-01 20:18
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
public class TimiResponse<T> implements Serializable, LanguageMsgMapping<TimiResponse<T>> {
|
||||
|
||||
/** 代码 */
|
||||
/** 响应代码 */
|
||||
@Setter
|
||||
@Getter
|
||||
protected Integer code;
|
||||
|
||||
/** 消息 */
|
||||
@@ -24,12 +30,10 @@ public class TimiResponse<T> implements Serializable, LanguageMsgMapping<TimiRes
|
||||
protected transient Map<String, Object> msgArgs;
|
||||
|
||||
/** 数据体 */
|
||||
@Setter
|
||||
@Getter
|
||||
protected T data;
|
||||
|
||||
/** 默认构造器 */
|
||||
public TimiResponse() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造器
|
||||
*
|
||||
@@ -85,42 +89,6 @@ public class TimiResponse<T> implements Serializable, LanguageMsgMapping<TimiRes
|
||||
return code.toException(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取代码
|
||||
*
|
||||
* @return 代码
|
||||
*/
|
||||
public Integer getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置代码
|
||||
*
|
||||
* @param code 代码
|
||||
*/
|
||||
public void setCode(Integer code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据体
|
||||
*
|
||||
* @return 数据体
|
||||
*/
|
||||
public T getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置数据体
|
||||
*
|
||||
* @param data 数据体
|
||||
*/
|
||||
public void setData(T data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加消息(避免和泛型字符串冲突)
|
||||
*
|
||||
|
||||
@@ -3,16 +3,33 @@ package com.imyeyu.java.obs;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 值变更监听器
|
||||
*
|
||||
* @param <T> 监听值类型
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 15:15
|
||||
*/
|
||||
public interface ChangeListener<T> {
|
||||
|
||||
/**
|
||||
* 处理值变更事件
|
||||
*
|
||||
* @param from 变更前值
|
||||
* @param to 变更后值
|
||||
*/
|
||||
void handler(T from, T to);
|
||||
|
||||
/**
|
||||
* 通知全部监听器处理本次变更
|
||||
*
|
||||
* @param listenerList 监听器列表
|
||||
* @param from 变更前值
|
||||
* @param to 变更后值
|
||||
* @param <T> 监听值类型
|
||||
*/
|
||||
static <T> void notifyListener(List<ChangeListener<T>> listenerList, T from, T to) {
|
||||
for (int i = 0; i < listenerList.size(); i++) {
|
||||
listenerList.get(i).handler(from, to);
|
||||
for (ChangeListener<T> listener : listenerList) {
|
||||
listener.handler(from, to);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,27 +3,49 @@ package com.imyeyu.java.obs;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 集合元素变更监听器
|
||||
*
|
||||
* @param <E> 元素类型
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 15:59
|
||||
*/
|
||||
public interface CollectionChangeListener<E> {
|
||||
|
||||
/**
|
||||
* 集合变更类型
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 19:47
|
||||
*/
|
||||
enum ChangeType {
|
||||
|
||||
/** 新增元素 */
|
||||
ADD,
|
||||
|
||||
/** 删除元素 */
|
||||
REMOVE
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理集合元素变更
|
||||
*
|
||||
* @param type 变更类型
|
||||
* @param e 本次变更元素
|
||||
* @throws RuntimeException 监听器处理失败时抛出
|
||||
*/
|
||||
void handler(ChangeType type, E e) throws RuntimeException;
|
||||
|
||||
/**
|
||||
* 通知全部集合监听器处理本次变更
|
||||
*
|
||||
* @param listenerList 监听器列表
|
||||
* @param type 变更类型
|
||||
* @param element 本次变更元素
|
||||
* @param <E> 元素类型
|
||||
*/
|
||||
static <E> void notifyListener(List<CollectionChangeListener<E>> listenerList, ChangeType type, E element) {
|
||||
for (int i = 0; i < listenerList.size(); i++) {
|
||||
listenerList.get(i).handler(type, element);
|
||||
for (CollectionChangeListener<E> listener : listenerList) {
|
||||
listener.handler(type, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,32 +3,56 @@ package com.imyeyu.java.obs;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 映射变更监听器
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 18:01
|
||||
*/
|
||||
public interface MapChangeListener<K, V> {
|
||||
|
||||
/**
|
||||
*
|
||||
* 映射变更类型
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 23:51
|
||||
*/
|
||||
enum ChangeType {
|
||||
|
||||
/** 新增键值对 */
|
||||
ADD,
|
||||
|
||||
/** 更新既有键值对 */
|
||||
UPDATE,
|
||||
|
||||
/** 删除键值对 */
|
||||
REMOVE
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理映射变更
|
||||
*
|
||||
* @param type 变更类型
|
||||
* @param key 变更键
|
||||
* @param value 变更值
|
||||
*/
|
||||
void handler(ChangeType type, K key, V value);
|
||||
|
||||
/**
|
||||
* 通知全部映射监听器处理本次变更
|
||||
*
|
||||
* @param listenerList 监听器列表
|
||||
* @param type 变更类型
|
||||
* @param key 变更键
|
||||
* @param value 变更值
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
static <K, V> void notifyListener(List<MapChangeListener<K, V>> listenerList, ChangeType type, Object key, V value) {
|
||||
for (int i = 0; i < listenerList.size(); i++) {
|
||||
listenerList.get(i).handler(type, (K) key, value);
|
||||
for (MapChangeListener<K, V> kvMapChangeListener : listenerList) {
|
||||
kvMapChangeListener.handler(type, (K) key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,39 @@
|
||||
package com.imyeyu.java.obs;
|
||||
|
||||
/**
|
||||
* 可观察对象接口
|
||||
*
|
||||
* @param <T> 可观察值类型
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 15:07
|
||||
*/
|
||||
public interface Observable<T> {
|
||||
|
||||
/**
|
||||
* 获取当前值
|
||||
*
|
||||
* @return 当前值
|
||||
*/
|
||||
T get();
|
||||
|
||||
/**
|
||||
* 设置当前值
|
||||
*
|
||||
* @param value 新值
|
||||
*/
|
||||
void set(T value);
|
||||
|
||||
/**
|
||||
* 添加值变更监听器
|
||||
*
|
||||
* @param changeListener 值变更监听器
|
||||
*/
|
||||
void addListener(ChangeListener<T> changeListener);
|
||||
|
||||
/**
|
||||
* 移除值变更监听器
|
||||
*
|
||||
* @param changeListener 值变更监听器
|
||||
*/
|
||||
void removeListener(ChangeListener<T> changeListener);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
package com.imyeyu.java.obs;
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 可观察布尔值
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 18:43
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
public class ObservableBoolean extends ObservableObject<Boolean> {
|
||||
|
||||
public ObservableBoolean() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定初始值创建可观察布尔值
|
||||
*
|
||||
* @param value 初始值
|
||||
*/
|
||||
public ObservableBoolean(Boolean value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
package com.imyeyu.java.obs;
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 可观察双精度值
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 18:43
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
public class ObservableDouble extends ObservableObject<Double> {
|
||||
|
||||
public ObservableDouble() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定数值创建可观察双精度值
|
||||
*
|
||||
* @param value 初始值
|
||||
*/
|
||||
public ObservableDouble(Number value) {
|
||||
super(value.doubleValue());
|
||||
}
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
package com.imyeyu.java.obs;
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 可观察浮点值
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 18:43
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
public class ObservableFloat extends ObservableObject<Float> {
|
||||
|
||||
public ObservableFloat() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定初始值创建可观察浮点值
|
||||
*
|
||||
* @param value 初始值
|
||||
*/
|
||||
public ObservableFloat(Float value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
package com.imyeyu.java.obs;
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 可观察整型值
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 15:06
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
public class ObservableInteger extends ObservableObject<Integer> {
|
||||
|
||||
public ObservableInteger() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定初始值创建可观察整型值
|
||||
*
|
||||
* @param value 初始值
|
||||
*/
|
||||
public ObservableInteger(Integer value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
@@ -7,28 +7,56 @@ import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* 可观察列表,支持监听元素新增和删除事件
|
||||
*
|
||||
* @param <E> 元素类型
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 17:51
|
||||
*/
|
||||
public class ObservableList<E> extends ArrayList<E> {
|
||||
|
||||
/** 集合变更监听器列表 */
|
||||
private final List<CollectionChangeListener<E>> changeListenerList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 创建空的可观察列表
|
||||
*/
|
||||
public ObservableList() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定集合创建可观察列表
|
||||
*
|
||||
* @param c 初始化集合
|
||||
*/
|
||||
public ObservableList(Collection<? extends E> c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加集合变更监听器
|
||||
*
|
||||
* @param listener 集合变更监听器
|
||||
*/
|
||||
public void addChangeListener(CollectionChangeListener<E> listener) {
|
||||
changeListenerList.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除集合变更监听器
|
||||
*
|
||||
* @param listener 集合变更监听器
|
||||
*/
|
||||
public void removeChangeListener(CollectionChangeListener<E> listener) {
|
||||
changeListenerList.add(listener);
|
||||
changeListenerList.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增元素并通知监听器
|
||||
*
|
||||
* @param e 待新增元素
|
||||
* @return true 表示列表发生变化
|
||||
*/
|
||||
@Override
|
||||
public boolean add(E e) {
|
||||
boolean result = super.add(e);
|
||||
@@ -38,12 +66,24 @@ public class ObservableList<E> extends ArrayList<E> {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在指定下标新增元素并通知监听器
|
||||
*
|
||||
* @param index 插入下标
|
||||
* @param element 待新增元素
|
||||
*/
|
||||
@Override
|
||||
public void add(int index, E element) {
|
||||
super.add(index, element);
|
||||
CollectionChangeListener.notifyListener(changeListenerList, CollectionChangeListener.ChangeType.ADD, element);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量新增元素并逐个通知监听器
|
||||
*
|
||||
* @param c 待新增元素集合
|
||||
* @return true 表示列表发生变化
|
||||
*/
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends E> c) {
|
||||
boolean result = super.addAll(c);
|
||||
@@ -55,6 +95,13 @@ public class ObservableList<E> extends ArrayList<E> {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从指定下标开始批量新增元素并逐个通知监听器
|
||||
*
|
||||
* @param index 起始下标
|
||||
* @param c 待新增元素集合
|
||||
* @return true 表示列表发生变化
|
||||
*/
|
||||
@Override
|
||||
public boolean addAll(int index, Collection<? extends E> c) {
|
||||
boolean result = super.addAll(index, c);
|
||||
@@ -66,6 +113,12 @@ public class ObservableList<E> extends ArrayList<E> {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定下标元素并通知监听器
|
||||
*
|
||||
* @param index 待删除元素下标
|
||||
* @return 被删除元素
|
||||
*/
|
||||
@Override
|
||||
public E remove(int index) {
|
||||
E removed = super.remove(index);
|
||||
@@ -75,11 +128,23 @@ public class ObservableList<E> extends ArrayList<E> {
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定元素并通知监听器
|
||||
*
|
||||
* @param o 待删除元素
|
||||
* @return true 表示删除成功
|
||||
*/
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
return remove(indexOf(o)) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除元素并逐个通知监听器
|
||||
*
|
||||
* @param c 待删除元素集合
|
||||
* @return true 表示列表发生变化
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
@@ -92,6 +157,12 @@ public class ObservableList<E> extends ArrayList<E> {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按条件删除元素
|
||||
*
|
||||
* @param filter 删除条件
|
||||
* @return true 表示至少删除一个元素
|
||||
*/
|
||||
@Override
|
||||
public boolean removeIf(Predicate<? super E> filter) {
|
||||
boolean removed = false;
|
||||
@@ -106,6 +177,13 @@ public class ObservableList<E> extends ArrayList<E> {
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取子列表的可观察副本
|
||||
*
|
||||
* @param fromIndex 起始下标(含)
|
||||
* @param toIndex 结束下标(不含)
|
||||
* @return 子列表副本
|
||||
*/
|
||||
@Override
|
||||
public ObservableList<E> subList(int fromIndex, int toIndex) {
|
||||
List<E> subList = super.subList(fromIndex, toIndex);
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
package com.imyeyu.java.obs;
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 可观察长整型值
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 18:43
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
public class ObservableLong extends ObservableObject<Long> {
|
||||
|
||||
public ObservableLong() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定初始值创建可观察长整型值
|
||||
*
|
||||
* @param value 初始值
|
||||
*/
|
||||
public ObservableLong(Long value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
@@ -6,21 +6,43 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 可观察映射,支持监听键值对新增、更新和删除事件
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 18:32
|
||||
*/
|
||||
public class ObservableMap<K, V> extends HashMap<K, V> {
|
||||
|
||||
/** 映射变更监听器列表 */
|
||||
private final List<MapChangeListener<K, V>> changeListenerList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 添加映射变更监听器
|
||||
*
|
||||
* @param listener 映射变更监听器
|
||||
*/
|
||||
public void addChangeListener(MapChangeListener<K, V> listener) {
|
||||
changeListenerList.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除映射变更监听器
|
||||
*
|
||||
* @param listener 映射变更监听器
|
||||
*/
|
||||
public void removeChangeListener(MapChangeListener<K, V> listener) {
|
||||
changeListenerList.add(listener);
|
||||
changeListenerList.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入键值对并在必要时通知监听器
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return 旧值,不存在则为 null
|
||||
*/
|
||||
@Override
|
||||
public V put(K key, V value) {
|
||||
V v = super.put(key, value);
|
||||
@@ -32,6 +54,11 @@ public class ObservableMap<K, V> extends HashMap<K, V> {
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量写入键值对
|
||||
*
|
||||
* @param m 待写入映射
|
||||
*/
|
||||
@Override
|
||||
public void putAll(Map<? extends K, ? extends V> m) {
|
||||
for (Map.Entry<? extends K, ? extends V> item : m.entrySet()) {
|
||||
@@ -39,6 +66,12 @@ public class ObservableMap<K, V> extends HashMap<K, V> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定键并通知监听器
|
||||
*
|
||||
* @param key 键
|
||||
* @return 被删除值,不存在则为 null
|
||||
*/
|
||||
@Override
|
||||
public V remove(Object key) {
|
||||
V removed = super.remove(key);
|
||||
@@ -48,6 +81,13 @@ public class ObservableMap<K, V> extends HashMap<K, V> {
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅当键值都匹配时删除并通知监听器
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return true 表示删除成功
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public boolean remove(Object key, Object value) {
|
||||
|
||||
@@ -1,30 +1,42 @@
|
||||
package com.imyeyu.java.obs;
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 可观察对象抽象基类
|
||||
*
|
||||
* @param <T> 可观察值类型
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 15:22
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
public abstract class ObservableObject<T> implements Observable<T> {
|
||||
|
||||
/** 值变更监听器列表 */
|
||||
private final List<ChangeListener<T>> changeListenerList = new ArrayList<>();
|
||||
|
||||
/** 当前值 */
|
||||
private T value;
|
||||
|
||||
public ObservableObject() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定初始值创建可观察对象
|
||||
*
|
||||
* @param value 初始值
|
||||
*/
|
||||
public ObservableObject(T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public final T get() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public final void set(T toValue) {
|
||||
final T from = this.value;
|
||||
@@ -35,11 +47,13 @@ public abstract class ObservableObject<T> implements Observable<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void addListener(ChangeListener<T> changeListener) {
|
||||
changeListenerList.add(changeListener);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void removeListener(ChangeListener<T> changeListener) {
|
||||
changeListenerList.remove(changeListener);
|
||||
|
||||
@@ -8,21 +8,41 @@ import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* 可观察集合,支持监听元素新增和删除事件
|
||||
*
|
||||
* @param <E> 元素类型
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 18:29
|
||||
*/
|
||||
public class ObservableSet<E> extends HashSet<E> {
|
||||
|
||||
/** 集合变更监听器列表 */
|
||||
private final List<CollectionChangeListener<E>> changeListenerList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 添加集合变更监听器
|
||||
*
|
||||
* @param listener 集合变更监听器
|
||||
*/
|
||||
public void addChangeListener(CollectionChangeListener<E> listener) {
|
||||
changeListenerList.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除集合变更监听器
|
||||
*
|
||||
* @param listener 集合变更监听器
|
||||
*/
|
||||
public void removeChangeListener(CollectionChangeListener<E> listener) {
|
||||
changeListenerList.add(listener);
|
||||
changeListenerList.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增元素并通知监听器
|
||||
*
|
||||
* @param e 待新增元素
|
||||
* @return true 表示集合发生变化
|
||||
*/
|
||||
@Override
|
||||
public boolean add(E e) {
|
||||
boolean result = super.add(e);
|
||||
@@ -32,6 +52,12 @@ public class ObservableSet<E> extends HashSet<E> {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量新增元素并逐个通知监听器
|
||||
*
|
||||
* @param c 待新增元素集合
|
||||
* @return true 表示集合发生变化
|
||||
*/
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends E> c) {
|
||||
boolean result = super.addAll(c);
|
||||
@@ -43,6 +69,12 @@ public class ObservableSet<E> extends HashSet<E> {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定元素并通知监听器
|
||||
*
|
||||
* @param o 待删除元素
|
||||
* @return true 表示删除成功
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
@@ -53,6 +85,12 @@ public class ObservableSet<E> extends HashSet<E> {
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除元素并逐个通知监听器
|
||||
*
|
||||
* @param c 待删除元素集合
|
||||
* @return true 表示集合发生变化
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
@@ -65,6 +103,12 @@ public class ObservableSet<E> extends HashSet<E> {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按条件删除元素
|
||||
*
|
||||
* @param filter 删除条件
|
||||
* @return true 表示至少删除一个元素
|
||||
*/
|
||||
@Override
|
||||
public boolean removeIf(Predicate<? super E> filter) {
|
||||
boolean removed = false;
|
||||
|
||||
@@ -1,27 +1,48 @@
|
||||
package com.imyeyu.java.obs;
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 可观察字符串值
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 18:43
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
public class ObservableString extends ObservableObject<String> {
|
||||
|
||||
public ObservableString() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定初始值创建可观察字符串值
|
||||
*
|
||||
* @param value 初始值
|
||||
*/
|
||||
public ObservableString(String value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前字符串是否为空或全空白
|
||||
*
|
||||
* @return true 表示空串或全空白
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return get().isEmpty() || get().trim().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前字符串是否非空且非全空白
|
||||
*
|
||||
* @return true 表示非空且包含非空白字符
|
||||
*/
|
||||
public boolean isNotEmpty() {
|
||||
return !isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前字符串值
|
||||
*
|
||||
* @return 当前字符串值
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return get();
|
||||
|
||||
@@ -7,7 +7,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 反射相关
|
||||
* 反射工具类,提供字段与方法查找、字段读写、字符串转枚举等能力
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2023-05-04 15:05
|
||||
@@ -15,9 +15,9 @@ import java.util.List;
|
||||
public class Ref {
|
||||
|
||||
/**
|
||||
* 获取类字段列表
|
||||
* 获取指定类声明的字段列表(不包含父类)
|
||||
*
|
||||
* @param clazz 类
|
||||
* @param clazz 类型对象
|
||||
* @return 字段列表
|
||||
*/
|
||||
public static List<Field> listFields(Class<?> clazz) {
|
||||
@@ -25,9 +25,9 @@ public class Ref {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类字段列表(包括父类)
|
||||
* 获取指定类及其父类声明的字段列表(直到 {@link Object} 之前)
|
||||
*
|
||||
* @param clazz 类
|
||||
* @param clazz 类型对象
|
||||
* @return 字段列表
|
||||
*/
|
||||
public static List<Field> listAllFields(Class<?> clazz) {
|
||||
@@ -40,20 +40,21 @@ public class Ref {
|
||||
}
|
||||
|
||||
/**
|
||||
* 将外部键名转换为 Java 字段名
|
||||
*
|
||||
* @param keyName
|
||||
* @return
|
||||
* @param keyName 外部键名,支持中划线、下划线和空格分隔
|
||||
* @return 驼峰命名字段名
|
||||
*/
|
||||
public static String getFieldName(String keyName) {
|
||||
String[] splits = {"-", "_", " "};
|
||||
StringBuilder full = new StringBuilder(keyName.substring(0, 1).toUpperCase() + keyName.substring(1));
|
||||
for (int i = 0; i < splits.length; i++) {
|
||||
if (keyName.contains(splits[i])) {
|
||||
// 存在分隔符
|
||||
for (String split : splits) {
|
||||
if (keyName.contains(split)) {
|
||||
// 存在分隔符时,先按分隔符拆词,再按驼峰拼接
|
||||
full.setLength(0);
|
||||
String[] word = keyName.split(splits[i]);
|
||||
for (int j = 0; j < word.length; j++) {
|
||||
full.append(word[j].substring(0, 1).toUpperCase()).append(word[j].substring(1));
|
||||
String[] word = keyName.split(split);
|
||||
for (String s : word) {
|
||||
full.append(s.substring(0, 1).toUpperCase()).append(s.substring(1));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -62,11 +63,12 @@ public class Ref {
|
||||
}
|
||||
|
||||
/**
|
||||
* 反射获取对象字段,包括父级类,直至 {@link Object},如果都不存在则返回 null
|
||||
* 反射获取对象字段,包括父级类,直至 {@link Object},如果都不存在则抛出异常
|
||||
*
|
||||
* @param clazz 类
|
||||
* @param fieldName 字段名
|
||||
* @return 字段
|
||||
* @return 字段对象
|
||||
* @throws NullPointerException 找不到字段时抛出异常
|
||||
*/
|
||||
public static Field getField(Class<?> clazz, String fieldName) {
|
||||
do {
|
||||
@@ -78,7 +80,7 @@ public class Ref {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
} while (clazz != Object.class);
|
||||
throw new NullPointerException(String.format("not found field: %s in %s", fieldName, clazz));
|
||||
throw new NullPointerException("not found field: %s in %s".formatted(fieldName, clazz));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,7 +107,7 @@ public class Ref {
|
||||
* @param <T> 返回类型
|
||||
* @return 字段值
|
||||
* @throws IllegalAccessException 反射访问失败
|
||||
* @throws NullPointerException 向上反射直至 {@link Object} 也找不到该字段
|
||||
* @throws NullPointerException 字段对象为空
|
||||
*/
|
||||
public static <T> T getFieldValue(Object object, Field field, Class<? extends T> toClass) throws IllegalAccessException, NullPointerException {
|
||||
if (field == null) {
|
||||
@@ -120,7 +122,7 @@ public class Ref {
|
||||
*
|
||||
* @param objectClass 类
|
||||
* @param fieldName 字段名
|
||||
* @return 字段
|
||||
* @return 字段对象,不存在时返回 null
|
||||
*/
|
||||
public static Field getClassField(Class<?> objectClass, String fieldName) {
|
||||
try {
|
||||
@@ -142,7 +144,7 @@ public class Ref {
|
||||
* @param <T> 值类型
|
||||
* @return 字段值
|
||||
* @throws IllegalAccessException 反射访问失败
|
||||
* @throws NoSuchFieldException 字段不存在
|
||||
* @throws NoSuchFieldException 字段不存在
|
||||
*/
|
||||
public static <T> T getClassFieldValue(Object object, Class<?> objectClass, String fieldName, Class<T> toClass) throws IllegalAccessException, NoSuchFieldException {
|
||||
Field field = getClassField(objectClass, fieldName);
|
||||
@@ -160,16 +162,19 @@ public class Ref {
|
||||
* @param fieldName 字段名
|
||||
* @param value 字段值
|
||||
* @throws IllegalAccessException 反射访问失败
|
||||
* @throws NoSuchFieldException 向上反射直至 {@link Object} 也找不到该字段
|
||||
*/
|
||||
public static void setFieldValue(Object object, String fieldName, Object value) throws IllegalAccessException, NoSuchFieldException {
|
||||
Field field = getField(object.getClass(), fieldName);
|
||||
if (field == null) {
|
||||
throw new NoSuchFieldException("not found " + fieldName + " field in " + object.getClass().getSimpleName());
|
||||
}
|
||||
setFieldValue(object, field, value);
|
||||
public static void setFieldValue(Object object, String fieldName, Object value) throws IllegalAccessException {
|
||||
setFieldValue(object, getField(object.getClass(), fieldName), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 反射设置对象字段值
|
||||
*
|
||||
* @param object 对象
|
||||
* @param field 字段对象
|
||||
* @param value 字段值
|
||||
* @throws IllegalAccessException 反射访问失败
|
||||
*/
|
||||
public static void setFieldValue(Object object, Field field, Object value) throws IllegalAccessException {
|
||||
field.setAccessible(true);
|
||||
field.set(object, value);
|
||||
@@ -200,7 +205,8 @@ public class Ref {
|
||||
* @param clazz 类
|
||||
* @param methodName 方法名
|
||||
* @param parameterTypes 可选参
|
||||
* @return 方法对象
|
||||
* @return 方法对象,不存在时返回 null
|
||||
* @throws NullPointerException 类对象为空
|
||||
*/
|
||||
public static Method getMethod(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
|
||||
if (clazz == null) {
|
||||
@@ -224,7 +230,8 @@ public class Ref {
|
||||
* @param clazz 枚举类
|
||||
* @param string 字符串
|
||||
* @param <T> 泛型
|
||||
* @return 泛型
|
||||
* @return 匹配到的枚举值,找不到或输入为空时返回 null
|
||||
* @throws IllegalArgumentException 传入类型不是枚举类型
|
||||
*/
|
||||
public static <T extends Enum<?>> T toType(Class<T> clazz, String string) {
|
||||
if (string == null) {
|
||||
@@ -234,31 +241,32 @@ public class Ref {
|
||||
if (ts == null) {
|
||||
throw new IllegalArgumentException(clazz.getName() + " is not an enum type");
|
||||
}
|
||||
for (int i = 0; i < ts.length; i++) {
|
||||
if (ts[i].name().equalsIgnoreCase(string)) {
|
||||
return ts[i];
|
||||
for (T t : ts) {
|
||||
if (t.name().equalsIgnoreCase(string)) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建字段反射元信息对象
|
||||
*
|
||||
*
|
||||
* @param owner
|
||||
* @param keyName
|
||||
* @return
|
||||
* @param owner 字段所属类型
|
||||
* @param keyName 字段键名
|
||||
* @return 字段反射元信息
|
||||
*/
|
||||
public static RefField field(Class<?> owner, String keyName) {
|
||||
return new RefField(owner, keyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过无参构造器创建实例
|
||||
*
|
||||
* @param type
|
||||
* @return
|
||||
* @param <T>
|
||||
* @throws Exception
|
||||
* @param type 类型对象
|
||||
* @param <T> 泛型类型
|
||||
* @return 新实例
|
||||
* @throws Exception 反射创建实例异常
|
||||
*/
|
||||
public static <T> T newInstance(Class<T> type) throws Exception {
|
||||
return type.getDeclaredConstructor().newInstance();
|
||||
|
||||
@@ -1,21 +1,39 @@
|
||||
package com.imyeyu.java.ref;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
*
|
||||
* 字段反射元信息,包含字段类型以及对应的 getter/setter 方法
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2024-04-26 15:46
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class RefField {
|
||||
|
||||
/** 字段类型 */
|
||||
private Class<?> type;
|
||||
|
||||
/** 字段对象 */
|
||||
private Field field;
|
||||
|
||||
/** 字段 setter 方法 */
|
||||
private Method setter;
|
||||
|
||||
/** 字段 getter 方法 */
|
||||
private Method getter;
|
||||
|
||||
/**
|
||||
* 根据拥有者类型和键名构建字段反射元信息
|
||||
*
|
||||
* @param owner 字段所属类型
|
||||
* @param keyName 字段键名,支持中划线、下划线和空格形式
|
||||
*/
|
||||
RefField(Class<?> owner, String keyName) {
|
||||
String fieldName = Ref.getFieldName(keyName);
|
||||
String fieldNameUpper = String.valueOf(fieldName.charAt(0)).toUpperCase() + fieldName.substring(1);
|
||||
@@ -38,40 +56,7 @@ public class RefField {
|
||||
setterName = "set" + fieldNameUpper;
|
||||
getterName = "get" + fieldNameUpper;
|
||||
}
|
||||
|
||||
setter = Ref.getMethod(owner, setterName, field.getType());
|
||||
getter = Ref.getMethod(owner, getterName);
|
||||
}
|
||||
|
||||
public Class<?> getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(Class<?> type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Field getField() {
|
||||
return field;
|
||||
}
|
||||
|
||||
public void setField(Field field) {
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
public Method getSetter() {
|
||||
return setter;
|
||||
}
|
||||
|
||||
public void setSetter(Method setter) {
|
||||
this.setter = setter;
|
||||
}
|
||||
|
||||
public Method getGetter() {
|
||||
return getter;
|
||||
}
|
||||
|
||||
public void setGetter(Method getter) {
|
||||
this.getter = getter;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,15 @@ package com.imyeyu.java.thread;
|
||||
|
||||
import com.imyeyu.java.bean.Callback;
|
||||
import com.imyeyu.java.bean.CallbackArg;
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 异步可重试执行器
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2025-11-06 17:45
|
||||
@@ -24,16 +29,30 @@ public class AsyncRetryExecutor {
|
||||
/** true 为守护进程 */
|
||||
private static final boolean DEFAULT_DAEMON = false;
|
||||
|
||||
/** 实际执行重试逻辑的线程 */
|
||||
private final Thread thread;
|
||||
|
||||
/** true 为执行成功 */
|
||||
@Getter
|
||||
private volatile boolean isSuccess = false;
|
||||
|
||||
/** true 为处于运行状态 */
|
||||
@Getter
|
||||
private volatile boolean isRunning = false;
|
||||
|
||||
private AsyncRetryExecutor(Builder builder) {
|
||||
/**
|
||||
* 根据构建器参数创建异步重试执行器
|
||||
*
|
||||
* @param task 构建器对象
|
||||
*/
|
||||
private AsyncRetryExecutor(Task task) {
|
||||
TimiException.required(task, "not found task");
|
||||
TimiException.required(task.getCallback(), "not found task.callback");
|
||||
thread = new Thread(() -> {
|
||||
int retryCount = 0;
|
||||
while (isRunning && !isSuccess && (builder.maxRetry < 0 || retryCount <= builder.maxRetry)) {
|
||||
while (isRunning && !isSuccess && (task.maxRetry < 0 || retryCount <= task.maxRetry)) {
|
||||
try {
|
||||
builder.callback.handler();
|
||||
task.callback.handler();
|
||||
isSuccess = true;
|
||||
} catch (Exception e) {
|
||||
retryCount++;
|
||||
@@ -41,22 +60,22 @@ public class AsyncRetryExecutor {
|
||||
// 中断
|
||||
break;
|
||||
}
|
||||
if (0 < builder.maxRetry && builder.maxRetry < retryCount) {
|
||||
if (0 < task.maxRetry && task.maxRetry < retryCount) {
|
||||
// 超过重试次数
|
||||
builder.onRetryExhausted.handler(e);
|
||||
task.onRetryExhausted.handler(e);
|
||||
break;
|
||||
}
|
||||
// 重试
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep(builder.retryInterval);
|
||||
TimeUnit.MILLISECONDS.sleep(task.retryInterval);
|
||||
} catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, builder.threadNamePrefix + Thread.currentThread().threadId());
|
||||
thread.setDaemon(builder.daemon);
|
||||
}, task.threadNamePrefix + Thread.currentThread().threadId());
|
||||
thread.setDaemon(task.daemon);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,64 +84,64 @@ public class AsyncRetryExecutor {
|
||||
* @author 夜雨
|
||||
* @since 2025-11-06 23:37
|
||||
*/
|
||||
public static class Builder {
|
||||
@Data
|
||||
@Builder
|
||||
public static class Task {
|
||||
|
||||
// 必需参数
|
||||
private final Callback callback;
|
||||
/** 必填回调方法 */
|
||||
private Callback callback;
|
||||
|
||||
// 可选参数(带默认值)
|
||||
/** 最大重试次数,-1 为无限重试 */
|
||||
private int maxRetry = DEFAULT_MAX_RETRY;
|
||||
|
||||
/** 重试间隔毫秒数 */
|
||||
private long retryInterval = DEFAULT_RETRY_INTERVAL;
|
||||
|
||||
/** 线程名前缀 */
|
||||
private String threadNamePrefix = DEFAULT_THREAD_NAME_PREFIX;
|
||||
|
||||
/** 是否设置为守护线程 */
|
||||
private boolean daemon = DEFAULT_DAEMON;
|
||||
|
||||
/** 重试耗尽时的异常回调 */
|
||||
private CallbackArg<Exception> onRetryExhausted;
|
||||
|
||||
public Builder(Callback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
public Builder maxRetry(int maxRetry) {
|
||||
this.maxRetry = maxRetry;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder retryInterval(long retryInterval) {
|
||||
this.retryInterval = retryInterval;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder threadNamePrefix(String prefix) {
|
||||
this.threadNamePrefix = prefix;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder daemon(boolean daemon) {
|
||||
this.daemon = daemon;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder onRetryExhausted(CallbackArg<Exception> onRetryExhausted) {
|
||||
this.onRetryExhausted = onRetryExhausted;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AsyncRetryExecutor build() {
|
||||
return new AsyncRetryExecutor(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用默认参数创建执行器
|
||||
*
|
||||
* @param callback 执行回调
|
||||
* @return 执行器实例
|
||||
*/
|
||||
public static AsyncRetryExecutor create(Callback callback) {
|
||||
return new Builder(callback).build();
|
||||
return new AsyncRetryExecutor(Task.builder().callback(callback).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定最大重试次数创建执行器
|
||||
*
|
||||
* @param callback 执行回调
|
||||
* @param maxRetry 最大重试次数
|
||||
* @return 执行器实例
|
||||
*/
|
||||
public static AsyncRetryExecutor create(Callback callback, int maxRetry) {
|
||||
return new Builder(callback).maxRetry(maxRetry).build();
|
||||
return new AsyncRetryExecutor(Task.builder().callback(callback).maxRetry(maxRetry).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定重试间隔创建执行器
|
||||
*
|
||||
* @param callback 执行回调
|
||||
* @param interval 重试间隔毫秒数
|
||||
* @return 执行器实例
|
||||
*/
|
||||
public static AsyncRetryExecutor create(Callback callback, long interval) {
|
||||
return new Builder(callback).retryInterval(interval).build();
|
||||
return new AsyncRetryExecutor(Task.builder().callback(callback).retryInterval(interval).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动异步执行
|
||||
*/
|
||||
public void start() {
|
||||
if (!isRunning) {
|
||||
isRunning = true;
|
||||
@@ -131,18 +150,13 @@ public class AsyncRetryExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 中断执行并停止后续重试
|
||||
*/
|
||||
public void interrupt() {
|
||||
isRunning = false;
|
||||
if (thread.isAlive() && !thread.isInterrupted()) {
|
||||
thread.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return isSuccess;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return isRunning;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user