{
8 |
9 | void throwsAccept(T t) throws Throwable;
10 |
11 | @SneakyThrows
12 | @Override
13 | default void accept(T t) {
14 | throwsAccept(t);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/honoka-utils/src/main/java/de/honoka/sdk/util/basic/ThrowsRunnable.java:
--------------------------------------------------------------------------------
1 | package de.honoka.sdk.util.basic;
2 |
3 | import lombok.SneakyThrows;
4 |
5 | public interface ThrowsRunnable extends Runnable {
6 |
7 | void throwsRun() throws Throwable;
8 |
9 | @SneakyThrows
10 | @Override
11 | default void run() {
12 | throwsRun();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/honoka-utils/src/main/java/de/honoka/sdk/util/basic/javadoc/NotThreadSafe.java:
--------------------------------------------------------------------------------
1 | package de.honoka.sdk.util.basic.javadoc;
2 |
3 | import java.lang.annotation.*;
4 |
5 | /**
6 | * 标识一个元素不是线程安全的。
7 | *
8 | * 如果注解在类上,表示这个类中的所有方法都不是线程安全的。类中的任何一个方法在被一个线程调用时,
9 | * 另一个线程不可以同时调用同一个方法或任何一个其他方法,即使有方法被单独用{@link ThreadSafe}注解
10 | * 所标识,也仅表示该方法本身是线程安全的,不表示该方法和非线程安全的方法可以被同时调用。
11 | *
12 | * 如果注解在方法上,表示这个方法不是线程安全的。单个方法不可以被多个线程同时调用。
13 | */
14 | @Documented
15 | @Target({ ElementType.TYPE, ElementType.METHOD })
16 | @Retention(RetentionPolicy.SOURCE)
17 | public @interface NotThreadSafe {}
18 |
--------------------------------------------------------------------------------
/honoka-utils/src/main/java/de/honoka/sdk/util/basic/javadoc/ThreadSafe.java:
--------------------------------------------------------------------------------
1 | package de.honoka.sdk.util.basic.javadoc;
2 |
3 | import java.lang.annotation.*;
4 |
5 | /**
6 | * 标识一个元素是线程安全的。
7 | *
8 | * 如果注解在类上,表示这个类中的所有方法都是线程安全的。类中的任何一个方法在被一个线程调用时,
9 | * 另一个线程可以同时调用同一个方法或任何一个其他方法,除非某个方法被单独用{@link NotThreadSafe}
10 | * 注解所标识,如某些私有方法或包级私有方法。
11 | *
12 | * 如果注解在方法上,表示这个方法是线程安全的。单个方法可以被多个线程同时调用。
13 | */
14 | @Documented
15 | @Target({ ElementType.TYPE, ElementType.METHOD })
16 | @Retention(RetentionPolicy.SOURCE)
17 | public @interface ThreadSafe {}
18 |
--------------------------------------------------------------------------------
/honoka-utils/src/main/java/de/honoka/sdk/util/concurrent/LockUtils.java:
--------------------------------------------------------------------------------
1 | package de.honoka.sdk.util.concurrent;
2 |
3 | import de.honoka.sdk.util.basic.CodeUtils;
4 |
5 | import java.util.Iterator;
6 | import java.util.concurrent.Callable;
7 |
8 | public class LockUtils {
9 |
10 | public static T synchronizedItems(Iterable> iterable, Callable callable) {
11 | return synchronizedItems(iterable.iterator(), callable);
12 | }
13 |
14 | private static T synchronizedItems(Iterator> iterator, Callable callable) {
15 | Object obj = null;
16 | while(obj == null && iterator.hasNext()) {
17 | obj = iterator.next();
18 | }
19 | if(obj != null) {
20 | //noinspection SynchronizationOnLocalVariableOrMethodParameter
21 | synchronized(obj) {
22 | return synchronizedItems(iterator, callable);
23 | }
24 | } else {
25 | try {
26 | return callable.call();
27 | } catch(Throwable t) {
28 | CodeUtils.sneakyThrows(t);
29 | return null;
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/honoka-utils/src/main/java/de/honoka/sdk/util/concurrent/NewThreadFirstQueue.java:
--------------------------------------------------------------------------------
1 | package de.honoka.sdk.util.concurrent;
2 |
3 | import lombok.AccessLevel;
4 | import lombok.Getter;
5 | import lombok.Setter;
6 | import org.jetbrains.annotations.NotNull;
7 |
8 | import java.util.concurrent.LinkedBlockingQueue;
9 | import java.util.concurrent.ThreadPoolExecutor;
10 |
11 | /**
12 | * 用于支持{@link ThreadPoolExecutor}在线程数未达到最大线程数时创建新线程的任务队列。
13 | *
14 | *
15 | * 本类来源于Dubbo(
17 | * https://github.com/apache/dubbo/blob/3.3/dubbo-common/src/main/java/org/apache/dubbo/common
18 | * /threadpool/support/eager/TaskQueue.java
19 | * ),并进行了一些修改。
20 | *
21 | */
22 | @Setter(AccessLevel.PACKAGE)
23 | @Getter
24 | public class NewThreadFirstQueue extends LinkedBlockingQueue {
25 |
26 | private ThreadPoolExecutor executor;
27 |
28 | public NewThreadFirstQueue(int capacity) {
29 | super(capacity);
30 | }
31 |
32 | @Override
33 | public boolean offer(@NotNull R runnable) {
34 | synchronized(this) {
35 | //have free worker. put task into queue to let the worker deal with task.
36 | if(executor.getActiveCount() + size() < executor.getPoolSize()) {
37 | return super.offer(runnable);
38 | }
39 | //return false to let executor create new worker.
40 | if(executor.getPoolSize() < executor.getMaximumPoolSize()) {
41 | return false;
42 | }
43 | }
44 | //poolSize >= max
45 | return super.offer(runnable);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/honoka-utils/src/main/java/de/honoka/sdk/util/concurrent/ThreadPoolUtils.java:
--------------------------------------------------------------------------------
1 | package de.honoka.sdk.util.concurrent;
2 |
3 | import java.util.concurrent.*;
4 |
5 | public class ThreadPoolUtils {
6 |
7 | private static final RejectedExecutionHandler defaultRejectedExecutionHandler =
8 | new ThreadPoolExecutor.AbortPolicy();
9 |
10 | public static ScheduledThreadPoolExecutor newScheduledPool(
11 | int coreSize, RejectedExecutionHandler rejectedExecutionHandler
12 | ) {
13 | if(rejectedExecutionHandler == null) {
14 | rejectedExecutionHandler = defaultRejectedExecutionHandler;
15 | }
16 | ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(coreSize, rejectedExecutionHandler);
17 | /*
18 | * 任务取消时将定时任务的待执行单元从队列中删除,默认是false。在默认情况下,如果直接取消任务,
19 | * 并不会从队列中删除此任务的待执行单元。
20 | */
21 | executor.setRemoveOnCancelPolicy(true);
22 | //shutdown被调用后是否还执行队列中的延迟任务
23 | executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
24 | //shutdown被调用后是否继续执行正在执行的任务
25 | executor.setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
26 | return executor;
27 | }
28 |
29 | /**
30 | * 使用{@link NewThreadFirstQueue}创建线程池
31 | */
32 | public static ThreadPoolExecutor newEagerThreadPool(
33 | int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit
34 | ) {
35 | return newEagerThreadPool(
36 | corePoolSize, maximumPoolSize, keepAliveTime, unit, Integer.MAX_VALUE
37 | );
38 | }
39 |
40 | public static ThreadPoolExecutor newEagerThreadPool(
41 | int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
42 | int queueCapacity
43 | ) {
44 | return newEagerThreadPool(
45 | corePoolSize, maximumPoolSize, keepAliveTime, unit, queueCapacity,
46 | Executors.defaultThreadFactory(), defaultRejectedExecutionHandler
47 | );
48 | }
49 |
50 | public static ThreadPoolExecutor newEagerThreadPool(
51 | int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
52 | int queueCapacity, ThreadFactory threadFactory
53 | ) {
54 | return newEagerThreadPool(
55 | corePoolSize, maximumPoolSize, keepAliveTime, unit, queueCapacity,
56 | threadFactory, defaultRejectedExecutionHandler
57 | );
58 | }
59 |
60 | public static ThreadPoolExecutor newEagerThreadPool(
61 | int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
62 | int queueCapacity, RejectedExecutionHandler handler
63 | ) {
64 | return newEagerThreadPool(
65 | corePoolSize, maximumPoolSize, keepAliveTime, unit, queueCapacity,
66 | Executors.defaultThreadFactory(), handler
67 | );
68 | }
69 |
70 | public static ThreadPoolExecutor newEagerThreadPool(
71 | int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
72 | int queueCapacity, ThreadFactory threadFactory, RejectedExecutionHandler handler
73 | ) {
74 | NewThreadFirstQueue queue = new NewThreadFirstQueue<>(queueCapacity);
75 | ThreadPoolExecutor executor = new ThreadPoolExecutor(
76 | corePoolSize, maximumPoolSize, keepAliveTime, unit, queue,
77 | threadFactory, handler
78 | );
79 | queue.setExecutor(executor);
80 | return executor;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/honoka-utils/src/main/java/de/honoka/sdk/util/file/AbstractEnvironmentPathUtils.java:
--------------------------------------------------------------------------------
1 | package de.honoka.sdk.util.file;
2 |
3 | import java.io.File;
4 | import java.nio.file.Paths;
5 | import java.util.Objects;
6 |
7 | public abstract class AbstractEnvironmentPathUtils {
8 |
9 | public enum BuildTool {
10 |
11 | MAVEN, GRADLE
12 | }
13 |
14 | private final BuildTool buildTool;
15 |
16 | public AbstractEnvironmentPathUtils(BuildTool buildTool) {
17 | this.buildTool = buildTool;
18 | }
19 |
20 | public String getDataDirPathOfApp() {
21 | String mainClasspath = FileUtils.getMainClasspath();
22 | if(FileUtils.isAppRunningInJar()) return mainClasspath;
23 | switch(buildTool) {
24 | //大括号用于防止在不同的case块当中,由于变量名相同而产生的冲突
25 | case MAVEN: {
26 | File classesDir = Paths.get(mainClasspath, "..").normalize().toFile();
27 | if(!Objects.equals(classesDir.getName(), "classes")) {
28 | throw new RuntimeException("Not normal maven classes directory: " + classesDir.getAbsolutePath());
29 | }
30 | return Paths.get(classesDir.getAbsolutePath(), "../data").normalize().toString();
31 | }
32 | case GRADLE: {
33 | File classesDir = Paths.get(mainClasspath, "../..").normalize().toFile();
34 | if(!Objects.equals(classesDir.getName(), "classes")) {
35 | throw new RuntimeException("Not normal gradle classes directory: " + classesDir.getAbsolutePath());
36 | }
37 | return Paths.get(classesDir.getAbsolutePath(), "../data").normalize().toString();
38 | }
39 | default:
40 | throw new RuntimeException("Unknown build tool: " + buildTool);
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/honoka-utils/src/main/java/de/honoka/sdk/util/file/FileUtils.java:
--------------------------------------------------------------------------------
1 | package de.honoka.sdk.util.file;
2 |
3 | import cn.hutool.core.collection.CollUtil;
4 | import cn.hutool.core.io.FileUtil;
5 | import cn.hutool.core.io.IoUtil;
6 | import lombok.SneakyThrows;
7 |
8 | import java.io.File;
9 | import java.net.URL;
10 | import java.net.URLDecoder;
11 | import java.nio.charset.StandardCharsets;
12 | import java.nio.file.Files;
13 | import java.nio.file.LinkOption;
14 | import java.nio.file.Path;
15 | import java.nio.file.Paths;
16 | import java.nio.file.attribute.BasicFileAttributeView;
17 | import java.nio.file.attribute.BasicFileAttributes;
18 | import java.util.Date;
19 | import java.util.List;
20 | import java.util.Locale;
21 | import java.util.Objects;
22 | import java.util.stream.Collectors;
23 |
24 | public class FileUtils {
25 |
26 | private static volatile String MAIN_CLASSPATH;
27 |
28 | /**
29 | * 检查当前运行的jar包外部是否含有指定的资源文件,若有则忽略此资源,若没有
30 | * 则从jar包中指定的相对路径处,提取此资源复制到jar包外部相同的相对路径处
31 | *
32 | * @param clazz 要提取资源的jar包中的某个类,用于基于此类进行相对路径的定位
33 | * @param paths 要提取的资源相对于clazz类所在路径的相对路径,以及要提取的
34 | * 资源所存放的位置相对于当前运行的jar包的相对路径
35 | * @return 指定的资源当中是否有某些资源原本不在jar包外部
36 | */
37 | @SneakyThrows
38 | public static boolean copyResourceIfNotExists(Class> clazz, String... paths) {
39 | if(!isAppRunningInJar()) return false;
40 | boolean result = false;
41 | String mainClasspath = getMainClasspath();
42 | for(String path : paths) {
43 | URL url = clazz.getResource(path);
44 | if(url == null) continue;
45 | File file = new File(Paths.get(mainClasspath, path).toString());
46 | if(file.exists()) continue;
47 | //指定的资源不存在
48 | result = true;
49 | FileUtil.touch(file);
50 | FileUtil.writeFromStream(url.openStream(), file);
51 | }
52 | return result;
53 | }
54 |
55 | public static Date getCreateTime(String filePath) {
56 | return getCreateTime(new File(filePath));
57 | }
58 |
59 | public static Date getCreateTime(File file) {
60 | try {
61 | Path path = Paths.get(file.getAbsolutePath());
62 | BasicFileAttributeView basicView = Files.getFileAttributeView(
63 | path, BasicFileAttributeView.class,
64 | LinkOption.NOFOLLOW_LINKS);
65 | BasicFileAttributes attr = basicView.readAttributes();
66 | return new Date(attr.creationTime().toMillis());
67 | } catch(Exception e) {
68 | e.printStackTrace();
69 | return new Date(file.lastModified());
70 | }
71 | }
72 |
73 | public static boolean isAppRunningInJar() {
74 | URL rootResourceUrl = Thread.currentThread().getContextClassLoader().getResource("");
75 | if(rootResourceUrl == null) {
76 | throw new RuntimeException("Failed to get root resource");
77 | }
78 | return Objects.equals(rootResourceUrl.getProtocol().toLowerCase(Locale.ROOT), "jar");
79 | }
80 |
81 | /**
82 | * 获取当前运行环境的主classpath的绝对路径
83 | *
84 | * 当Java应用程序在jar包中被运行时,此路径为jar包所在目录的路径。在IDE中直接运行时,此路径为
85 | * 项目构建目录中的java源代码编译输出路径(如Maven中为“[项目目录]/target/classes”)。
86 | */
87 | @SneakyThrows
88 | public static String getMainClasspath() {
89 | if(MAIN_CLASSPATH != null) return MAIN_CLASSPATH;
90 | URL rootResourceUrl = Thread.currentThread().getContextClassLoader().getResource("");
91 | if(isAppRunningInJar()) {
92 | String path = Objects.requireNonNull(rootResourceUrl).getPath();
93 | String pathEndSymbol;
94 | if(path.startsWith("file:/")) {
95 | pathEndSymbol = ".jar!/";
96 | } else if(path.startsWith("nested:/")) {
97 | pathEndSymbol = ".jar/!";
98 | } else {
99 | throw new RuntimeException("Root resource path is invalid: " + path);
100 | }
101 | int lowercaseSymbolIndex = path.indexOf(pathEndSymbol);
102 | int uppercaseSymbolIndex = path.indexOf(pathEndSymbol.toUpperCase(Locale.ROOT));
103 | List symbolIndexes = CollUtil.newHashSet(lowercaseSymbolIndex, uppercaseSymbolIndex)
104 | .stream()
105 | .filter(it -> it != -1)
106 | .sorted()
107 | .collect(Collectors.toList());
108 | if(symbolIndexes.isEmpty()) {
109 | throw new RuntimeException("Root resource path is invalid: " + path);
110 | }
111 | int pathStartIndex = 0;
112 | if(path.startsWith("file:/")) {
113 | pathStartIndex = 6;
114 | } else if(path.startsWith("nested:/")) {
115 | pathStartIndex = 8;
116 | }
117 | path = path.substring(pathStartIndex, symbolIndexes.get(0) + 4);
118 | path = path.substring(0, path.lastIndexOf("/"));
119 | path = URLDecoder.decode(path, StandardCharsets.UTF_8.name());
120 | String result = Paths.get(path).normalize().toString();
121 | String osName = System.getProperty("os.name").toLowerCase(Locale.ROOT);
122 | if(!osName.contains("windows") && !result.startsWith("/")) {
123 | result = "/" + result;
124 | }
125 | File dir = new File(result);
126 | if(!dir.exists() || !dir.isDirectory()) {
127 | throw new RuntimeException("Calculated main classpath is invalid: " + result);
128 | }
129 | MAIN_CLASSPATH = result;
130 | } else {
131 | MAIN_CLASSPATH = new File(Objects.requireNonNull(rootResourceUrl).toURI()).getAbsolutePath();
132 | }
133 | return MAIN_CLASSPATH;
134 | }
135 |
136 | @SneakyThrows
137 | public static void checkOrMkdirs(File... dirs) {
138 | for(File dir : dirs) {
139 | if(!dir.exists()) dir.mkdirs();
140 | }
141 | }
142 |
143 | /**
144 | * 检查必要的文件是否存在,不存在则创建
145 | */
146 | @SneakyThrows
147 | public static void checkOrTouch(File... files) {
148 | for(File f : files) {
149 | FileUtil.touch(f);
150 | }
151 | }
152 |
153 | @SneakyThrows
154 | public static String fetchUrlResourceAndToString(URL url) {
155 | return new String(IoUtil.readBytes(url.openStream()));
156 | }
157 |
158 | public static String toUriPath(String filePath) {
159 | String uriPath = new File(filePath).toURI().toASCIIString();
160 | if(uriPath.startsWith("file:/") && !uriPath.startsWith("file:///")) {
161 | uriPath = uriPath.replaceFirst("file:/", "file:///");
162 | }
163 | return uriPath;
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/honoka-utils/src/main/java/de/honoka/sdk/util/file/csv/CsvTable.java:
--------------------------------------------------------------------------------
1 | package de.honoka.sdk.util.file.csv;
2 |
3 | import cn.hutool.core.io.FileUtil;
4 | import cn.hutool.core.io.IoUtil;
5 | import cn.hutool.core.util.StrUtil;
6 | import de.honoka.sdk.util.basic.ActionUtils;
7 | import de.honoka.sdk.util.various.ReflectUtils;
8 | import lombok.Getter;
9 | import lombok.SneakyThrows;
10 | import org.jetbrains.annotations.NotNull;
11 |
12 | import java.io.File;
13 | import java.io.InputStream;
14 | import java.lang.reflect.Field;
15 | import java.net.URL;
16 | import java.nio.charset.StandardCharsets;
17 | import java.util.*;
18 |
19 | /**
20 | * 将csv格式的表格加载为便于使用的对象
21 | */
22 | public class CsvTable implements Iterable {
23 |
24 | @Getter
25 | private final List