v0.0.3
All checks were successful
CI/CD / build-deploy (pull_request) Successful in 17s

This commit is contained in:
Timi
2026-04-27 23:49:37 +08:00
parent 3d8168c726
commit fecf693578
38 changed files with 1058 additions and 562 deletions

41
pom.xml
View File

@@ -6,14 +6,14 @@
<groupId>com.imyeyu.java</groupId>
<artifactId>timi-java</artifactId>
<version>0.0.2</version>
<version>0.0.3</version>
<packaging>jar</packaging>
<properties>
<maven.test.skip>true</maven.test.skip>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.test.skip>true</maven.test.skip>
</properties>
<build>
@@ -28,10 +28,42 @@
<artifactId>maven-source-plugin</artifactId>
<version>3.3.1</version>
</plugin>
<plugin>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
<version>1.18.20.0</version>
<configuration>
<sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>
<outputDirectory>${project.build.directory}/delombok</outputDirectory>
<addOutputDirectory>false</addOutputDirectory>
<encoding>UTF-8</encoding>
</configuration>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>delombok</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.11.2</version>
<configuration>
<sourcepath>${project.build.directory}/delombok</sourcepath>
<encoding>UTF-8</encoding>
<charset>UTF-8</charset>
<docencoding>UTF-8</docencoding>
</configuration>
</plugin>
</plugins>
</build>
@@ -57,6 +89,11 @@
</repositories>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.40</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -11,6 +11,11 @@ public interface Callback {
/** 执行程序 */
void handler() throws RuntimeException;
/**
* 非空时执行回调
*
* @param callback 回调
*/
static void handle(Callback callback) {
if (callback == null) {
return;

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -22,6 +22,7 @@ public interface CallbackReturn<R> {
* 非空时执行回调
*
* @param callbackReturn 回调
* @param <R> 返回类型
*/
static <R> R handle(CallbackReturn<R> callbackReturn) {
if (callbackReturn == null) {

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;
}
/**
* 追加消息(避免和泛型字符串冲突)
*

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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());
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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;

View File

@@ -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();

View File

@@ -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 {
@@ -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();

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -1,14 +1,23 @@
package test;
package com.imyeyu.java;
import com.imyeyu.java.thread.AsyncRetryExecutor;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* 线程工具相关测试
*
* @author 夜雨
* @since 2025-11-06 23:59
*/
public class TestThread {
public class AsyncRetryExecutorTest {
/**
* 验证异步重试执行器在成功后状态为成功
*
* @throws InterruptedException 等待异步完成时中断异常
*/
@Test
public void testAsyncRetryExecutor() throws InterruptedException {
Object lock = new Object();
@@ -25,11 +34,9 @@ public class TestThread {
}
});
executor.start();
synchronized (lock) {
lock.wait();
}
assert executor.isSuccess();
assertTrue(executor.isSuccess());
}
}

View File

@@ -1,4 +1,4 @@
package test;
package com.imyeyu.java;
import com.imyeyu.java.obs.ObservableList;
import com.imyeyu.java.obs.ObservableMap;
@@ -7,12 +7,18 @@ import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
/**
* 可观察对象相关测试
*
* @author 夜雨
* @since 2024-09-01 23:26
*/
public class TestObs {
public class ObservableTest {
/** 验证可观察字符串赋值后值会更新 */
@Test
public void testString() {
ObservableString obsString = new ObservableString("test1");
@@ -20,9 +26,10 @@ public class TestObs {
obsString.addListener((from, to) -> System.out.printf("from %s to %s%n", from, to));
obsString.set("test2");
System.out.println("now value = " + obsString);
assert obsString.get().equals("test2");
assertEquals("test2", obsString.get());
}
/** 验证可观察列表在多次增删后的最终状态 */
@Test
public void testList() {
ObservableList<String> list = new ObservableList<>();
@@ -40,8 +47,10 @@ public class TestObs {
list.removeAll(List.of("test3", "test4"));
list.removeIf(item -> item.equals("test1")); // 移除 0, 1, 2, 3, 4
System.out.println(list);
assertEquals(List.of("test5", "test7", "test8"), list);
}
/** 验证可观察映射在新增、更新与删除后的最终状态 */
@Test
public void testMap() {
ObservableMap<String, String> map = new ObservableMap<>();
@@ -56,5 +65,11 @@ public class TestObs {
map.remove("t1");
map.remove("t2", "test2");
System.out.println(map);
assertEquals(3, map.size());
assertEquals("test3", map.get("t3"));
assertEquals("test4", map.get("t4"));
assertEquals("test5", map.get("t5"));
assertFalse(map.containsKey("t1"));
assertFalse(map.containsKey("t2"));
}
}

View File

@@ -0,0 +1,109 @@
package com.imyeyu.java;
import com.imyeyu.java.ref.Ref;
import com.imyeyu.java.ref.RefField;
import lombok.Data;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/**
* 反射工具相关测试
*
* @author 夜雨
* @since 2024-04-26 11:05
*/
@Data
public class RefTest {
/** 布尔字段,使用 is 前缀 */
private boolean isBoy;
/** 布尔字段,不使用 is 前缀 */
private boolean boy;
/** 普通字符串字段 */
private String fieldName;
/** 验证字段名标准化转换规则 */
@Test
public void test() {
String t1 = Ref.getFieldName("is-boy");
assertEquals("isBoy", t1);
String t2 = Ref.getFieldName("is_boy");
assertEquals("isBoy", t2);
String t3 = Ref.getFieldName("is boy");
assertEquals("isBoy", t3);
String t4 = Ref.getFieldName("isBoy");
assertEquals("isBoy", t4);
String t5 = Ref.getFieldName("boy");
assertEquals("boy", t5);
String t6 = Ref.getFieldName("fieldName");
assertEquals("fieldName", t6);
String t7 = Ref.getFieldName("field name");
assertEquals("fieldName", t7);
String t8 = Ref.getFieldName("field-Name");
assertEquals("fieldName", t8);
String t9 = Ref.getFieldName("field_Name");
assertEquals("fieldName", t9);
}
/** 验证布尔字段 is-boy 能映射到标准 getter/setter */
@Test
public void refBooleanField() {
RefField refField = Ref.field(RefTest.class, "is-boy");
assertNotNull(refField.getSetter());
assertEquals("setBoy", refField.getSetter().getName());
assertNotNull(refField.getGetter());
assertEquals("isBoy", refField.getGetter().getName());
}
/** 验证布尔字段 boy 能映射到标准 getter/setter */
@Test
public void refBoolean2Field() {
RefField refField = Ref.field(RefTest.class, "boy");
assertNotNull(refField.getSetter());
assertEquals("setBoy", refField.getSetter().getName());
assertNotNull(refField.getGetter());
assertEquals("isBoy", refField.getGetter().getName());
}
/** 验证字段名 fieldName 能映射到标准 getter/setter */
@Test
public void refStringField() {
RefField refField = Ref.field(RefTest.class, "fieldName");
assertNotNull(refField.getSetter());
assertEquals("setFieldName", refField.getSetter().getName());
assertNotNull(refField.getGetter());
assertEquals("getFieldName", refField.getGetter().getName());
}
/** 验证字段名 field-name 能映射到标准 getter/setter */
@Test
public void refString2Field() {
RefField refField = Ref.field(RefTest.class, "field-name");
assertNotNull(refField.getSetter());
assertEquals("setFieldName", refField.getSetter().getName());
assertNotNull(refField.getGetter());
assertEquals("getFieldName", refField.getGetter().getName());
}
/** 验证字段名 field_name 能映射到标准 getter/setter */
@Test
public void refString3Field() {
RefField refField = Ref.field(RefTest.class, "field_name");
assertNotNull(refField.getSetter());
assertEquals("setFieldName", refField.getSetter().getName());
assertNotNull(refField.getGetter());
assertEquals("getFieldName", refField.getGetter().getName());
}
/** 验证字段名 field name 能映射到标准 getter/setter */
@Test
public void refString4Field() {
RefField refField = Ref.field(RefTest.class, "field name");
assertNotNull(refField.getSetter());
assertEquals("setFieldName", refField.getSetter().getName());
assertNotNull(refField.getGetter());
assertEquals("getFieldName", refField.getGetter().getName());
}
}

View File

@@ -0,0 +1,87 @@
package com.imyeyu.java;
import org.junit.jupiter.api.Test;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertIterableEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* {@link TimiJava} 静态工具方法测试
*
* @author 夜雨
* @since 2026-04-27 10:00
*/
public class TimiJavaTest {
/** 验证 isEmpty 与 isNotEmpty 的主要判空分支 */
@Test
public void testEmptyCheck() {
assertTrue(TimiJava.isEmpty(null));
assertTrue(TimiJava.isEmpty(""));
assertTrue(TimiJava.isEmpty(" "));
assertTrue(TimiJava.isEmpty(Collections.emptyList()));
assertTrue(TimiJava.isEmpty(Collections.emptyMap()));
assertFalse(TimiJava.isEmpty("value"));
assertTrue(TimiJava.isNotEmpty("value"));
}
/** 验证 defaultIfNull 与 defaultIfEmpty 返回默认值逻辑 */
@Test
public void testDefaultValue() {
assertEquals("default", TimiJava.defaultIfNull(null, "default"));
assertEquals("value", TimiJava.defaultIfNull("value", "default"));
assertEquals("default", TimiJava.defaultIfEmpty(" ", "default"));
assertEquals("value", TimiJava.defaultIfEmpty("value", "default"));
}
/** 验证 firstNotNull 与 firstNotEmpty 的首个命中逻辑 */
@Test
public void testFirstValue() {
assertEquals("v1", TimiJava.firstNotNull(null, "v1", "v2"));
assertNull(TimiJava.firstNotNull(null, null));
assertEquals("v1", TimiJava.firstNotEmpty("", " ", "v1", "v2"));
assertNull(TimiJava.firstNotEmpty("", " ", null));
}
/** 验证异常序列化结果包含异常类型与消息 */
@Test
public void testSerializeThrowable() {
String text = TimiJava.serializeThrowable(new IllegalArgumentException("bad"));
assertTrue(text.contains(IllegalArgumentException.class.getName()));
assertTrue(text.contains("bad"));
}
/** 验证 safeIterable 在空值与非空场景下都可安全迭代 */
@Test
public void testSafeIterable() {
Iterable<String> empty = TimiJava.safeIterable(null);
assertNotNull(empty);
assertFalse(empty.iterator().hasNext());
Iterable<String> values = TimiJava.safeIterable(List.of("a", "b"));
assertIterableEquals(List.of("a", "b"), values);
}
/** 验证 ZERO_UUID 常量值 */
@Test
public void testZeroUuid() {
assertEquals("00000000-0000-0000-0000-000000000000", TimiJava.ZERO_UUID);
}
/** 验证数组和映射判空路径 */
@Test
public void testArrayAndMapEmptyCheck() {
assertTrue(TimiJava.isEmpty(new String[]{}));
assertFalse(TimiJava.isEmpty(new String[]{"v"}));
assertTrue(TimiJava.isEmpty(Map.of()));
assertFalse(TimiJava.isEmpty(Map.of("k", "v")));
}
}

View File

@@ -1,96 +0,0 @@
package test;
import com.imyeyu.java.ref.Ref;
import com.imyeyu.java.ref.RefField;
import org.junit.jupiter.api.Test;
/**
* @author 夜雨
* @since 2024-04-26 11:05
*/
public class TestRef {
private boolean isBoy;
private boolean boy;
private String fieldName;
@Test
public void test() {
String t1 = Ref.getFieldName("is-boy");
assert t1.equals("isBoy");
String t2 = Ref.getFieldName("is_boy");
assert t2.equals("isBoy");
String t3 = Ref.getFieldName("is boy");
assert t3.equals("isBoy");
String t4 = Ref.getFieldName("isBoy");
assert t4.equals("isBoy");
String t5 = Ref.getFieldName("boy");
assert t5.equals("boy");
String t6 = Ref.getFieldName("fieldName");
assert t6.equals("fieldName");
String t7 = Ref.getFieldName("field name");
assert t7.equals("fieldName");
String t8 = Ref.getFieldName("field-Name");
assert t8.equals("fieldName");
String t9 = Ref.getFieldName("field_Name");
assert t9.equals("fieldName");
}
@Test
public void refBooleanField() {
RefField refField = Ref.field(TestRef.class, "is-boy");
assert refField.getSetter() != null && refField.getSetter().getName().equals("setBoy");
assert refField.getGetter() != null && refField.getGetter().getName().equals("isBoy");
}
@Test
public void refBoolean2Field() {
RefField refField = Ref.field(TestRef.class, "boy");
assert refField.getSetter() != null && refField.getSetter().getName().equals("setBoy");
assert refField.getGetter() != null && refField.getGetter().getName().equals("isBoy");
}
@Test
public void refStringField() {
RefField refField = Ref.field(TestRef.class, "fieldName");
assert refField.getSetter() != null && refField.getSetter().getName().equals("setFieldName");
assert refField.getGetter() != null && refField.getGetter().getName().equals("getFieldName");
}
@Test
public void refString2Field() {
RefField refField = Ref.field(TestRef.class, "field-name");
assert refField.getSetter() != null && refField.getSetter().getName().equals("setFieldName");
assert refField.getGetter() != null && refField.getGetter().getName().equals("getFieldName");
}
@Test
public void refString3Field() {
RefField refField = Ref.field(TestRef.class, "field_name");
assert refField.getSetter() != null && refField.getSetter().getName().equals("setFieldName");
assert refField.getGetter() != null && refField.getGetter().getName().equals("getFieldName");
}
@Test
public void refString4Field() {
RefField refField = Ref.field(TestRef.class, "field name");
assert refField.getSetter() != null && refField.getSetter().getName().equals("setFieldName");
assert refField.getGetter() != null && refField.getGetter().getName().equals("getFieldName");
}
public boolean isBoy() {
return isBoy;
}
public void setBoy(boolean boy) {
isBoy = boy;
}
public String getFieldName() {
return fieldName;
}
public void setFieldName(String fieldName) {
this.fieldName = fieldName;
}
}