Initial project
This commit is contained in:
128
.gitignore
vendored
128
.gitignore
vendored
@ -1,98 +1,38 @@
|
|||||||
# ---> JetBrains
|
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
|
||||||
|
|
||||||
# User-specific stuff
|
|
||||||
.idea/**/workspace.xml
|
|
||||||
.idea/**/tasks.xml
|
|
||||||
.idea/**/usage.statistics.xml
|
|
||||||
.idea/**/dictionaries
|
|
||||||
.idea/**/shelf
|
|
||||||
|
|
||||||
# AWS User-specific
|
|
||||||
.idea/**/aws.xml
|
|
||||||
|
|
||||||
# Generated files
|
|
||||||
.idea/**/contentModel.xml
|
|
||||||
|
|
||||||
# Sensitive or high-churn files
|
|
||||||
.idea/**/dataSources/
|
|
||||||
.idea/**/dataSources.ids
|
|
||||||
.idea/**/dataSources.local.xml
|
|
||||||
.idea/**/sqlDataSources.xml
|
|
||||||
.idea/**/dynamic.xml
|
|
||||||
.idea/**/uiDesigner.xml
|
|
||||||
.idea/**/dbnavigator.xml
|
|
||||||
|
|
||||||
# Gradle
|
|
||||||
.idea/**/gradle.xml
|
|
||||||
.idea/**/libraries
|
|
||||||
|
|
||||||
# Gradle and Maven with auto-import
|
|
||||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
|
||||||
# since they will be recreated, and may cause churn. Uncomment if using
|
|
||||||
# auto-import.
|
|
||||||
# .idea/artifacts
|
|
||||||
# .idea/compiler.xml
|
|
||||||
# .idea/jarRepositories.xml
|
|
||||||
# .idea/modules.xml
|
|
||||||
# .idea/*.iml
|
|
||||||
# .idea/modules
|
|
||||||
# *.iml
|
|
||||||
# *.ipr
|
|
||||||
|
|
||||||
# CMake
|
|
||||||
cmake-build-*/
|
|
||||||
|
|
||||||
# Mongo Explorer plugin
|
|
||||||
.idea/**/mongoSettings.xml
|
|
||||||
|
|
||||||
# File-based project format
|
|
||||||
*.iws
|
|
||||||
|
|
||||||
# IntelliJ
|
|
||||||
out/
|
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
|
||||||
.idea_modules/
|
|
||||||
|
|
||||||
# JIRA plugin
|
|
||||||
atlassian-ide-plugin.xml
|
|
||||||
|
|
||||||
# Cursive Clojure plugin
|
|
||||||
.idea/replstate.xml
|
|
||||||
|
|
||||||
# SonarLint plugin
|
|
||||||
.idea/sonarlint/
|
|
||||||
|
|
||||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
||||||
com_crashlytics_export_strings.xml
|
|
||||||
crashlytics.properties
|
|
||||||
crashlytics-build.properties
|
|
||||||
fabric.properties
|
|
||||||
|
|
||||||
# Editor-based Rest Client
|
|
||||||
.idea/httpRequests
|
|
||||||
|
|
||||||
# Android studio 3.1+ serialized cache file
|
|
||||||
.idea/caches/build_file_checksums.ser
|
|
||||||
|
|
||||||
# ---> Maven
|
|
||||||
target/
|
target/
|
||||||
pom.xml.tag
|
!.mvn/wrapper/maven-wrapper.jar
|
||||||
pom.xml.releaseBackup
|
!**/src/main/**/target/
|
||||||
pom.xml.versionsBackup
|
!**/src/test/**/target/
|
||||||
pom.xml.next
|
|
||||||
release.properties
|
|
||||||
dependency-reduced-pom.xml
|
|
||||||
buildNumber.properties
|
|
||||||
.mvn/timing.properties
|
|
||||||
# https://github.com/takari/maven-wrapper#usage-without-binary-jar
|
|
||||||
.mvn/wrapper/maven-wrapper.jar
|
|
||||||
|
|
||||||
# Eclipse m2e generated files
|
### IntelliJ IDEA ###
|
||||||
# Eclipse Core
|
.idea/modules.xml
|
||||||
.project
|
.idea/jarRepositories.xml
|
||||||
# JDT-specific (Eclipse Java Development Tools)
|
.idea/compiler.xml
|
||||||
|
.idea/libraries/
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
### Eclipse ###
|
||||||
|
.apt_generated
|
||||||
.classpath
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
build/
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
### Mac OS ###
|
||||||
|
.DS_Store
|
||||||
3
.idea/.gitignore
generated
vendored
Normal file
3
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
27
.idea/CopilotChatHistory.xml
generated
Normal file
27
.idea/CopilotChatHistory.xml
generated
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CopilotChatHistory">
|
||||||
|
<option name="conversations">
|
||||||
|
<list>
|
||||||
|
<Conversation>
|
||||||
|
<option name="createTime" value="1751250353639" />
|
||||||
|
<option name="id" value="0197bea7bde779279917cb804e5ee88c" />
|
||||||
|
<option name="title" value="新对话 2025年6月30日 10:25:53" />
|
||||||
|
<option name="updateTime" value="1751250353639" />
|
||||||
|
</Conversation>
|
||||||
|
<Conversation>
|
||||||
|
<option name="createTime" value="1747188768702" />
|
||||||
|
<option name="id" value="0196cc90dfbe78c5920c82d7d8a91d52" />
|
||||||
|
<option name="title" value="新对话 2025年5月14日 10:12:48" />
|
||||||
|
<option name="updateTime" value="1747188768702" />
|
||||||
|
</Conversation>
|
||||||
|
<Conversation>
|
||||||
|
<option name="createTime" value="1747138499903" />
|
||||||
|
<option name="id" value="0196c991d53f7af28f5401a5deaf4fc6" />
|
||||||
|
<option name="title" value="新对话 2025年5月13日 20:14:59" />
|
||||||
|
<option name="updateTime" value="1747138499903" />
|
||||||
|
</Conversation>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
7
.idea/encodings.xml
generated
Normal file
7
.idea/encodings.xml
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding">
|
||||||
|
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
14
.idea/misc.xml
generated
Normal file
14
.idea/misc.xml
generated
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
|
<component name="MavenProjectsManager">
|
||||||
|
<option name="originalFiles">
|
||||||
|
<list>
|
||||||
|
<option value="$PROJECT_DIR$/pom.xml" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" project-jdk-name="openjdk-21" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
124
.idea/uiDesigner.xml
generated
Normal file
124
.idea/uiDesigner.xml
generated
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Palette2">
|
||||||
|
<group name="Swing">
|
||||||
|
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
|
||||||
|
</item>
|
||||||
|
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
|
||||||
|
<initial-values>
|
||||||
|
<property name="text" value="Button" />
|
||||||
|
</initial-values>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
|
||||||
|
<initial-values>
|
||||||
|
<property name="text" value="RadioButton" />
|
||||||
|
</initial-values>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
|
||||||
|
<initial-values>
|
||||||
|
<property name="text" value="CheckBox" />
|
||||||
|
</initial-values>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
|
||||||
|
<initial-values>
|
||||||
|
<property name="text" value="Label" />
|
||||||
|
</initial-values>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||||
|
<preferred-size width="150" height="-1" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||||
|
<preferred-size width="150" height="-1" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||||
|
<preferred-size width="150" height="-1" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||||
|
<preferred-size width="150" height="50" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||||
|
<preferred-size width="150" height="50" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||||
|
<preferred-size width="150" height="50" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||||
|
<preferred-size width="150" height="50" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
|
||||||
|
<preferred-size width="150" height="50" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||||
|
<preferred-size width="150" height="50" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
|
||||||
|
<preferred-size width="200" height="200" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
|
||||||
|
<preferred-size width="200" height="200" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
|
||||||
|
<preferred-size width="-1" height="20" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
|
||||||
|
</item>
|
||||||
|
</group>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
37
pom.xml
Normal file
37
pom.xml
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>com.imyeyu.utils</groupId>
|
||||||
|
<artifactId>timi-utils</artifactId>
|
||||||
|
<version>0.0.1</version>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.imyeyu.java</groupId>
|
||||||
|
<artifactId>timi-java</artifactId>
|
||||||
|
<version>0.0.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<version>5.10.2</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.34</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
250
src/main/java/com/imyeyu/utils/AsciiTable.java
Normal file
250
src/main/java/com/imyeyu/utils/AsciiTable.java
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
package com.imyeyu.utils;
|
||||||
|
|
||||||
|
import com.imyeyu.java.bean.CallbackArgReturn;
|
||||||
|
import com.imyeyu.java.bean.timi.TimiCode;
|
||||||
|
import com.imyeyu.java.bean.timi.TimiException;
|
||||||
|
import com.imyeyu.java.ref.Ref;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2024-12-18 10:10
|
||||||
|
*/
|
||||||
|
public class AsciiTable<T> {
|
||||||
|
|
||||||
|
/** 制表符水平边框 */
|
||||||
|
private static final String BORDER_H = "─";
|
||||||
|
|
||||||
|
/** 制表符垂直边框 */
|
||||||
|
private static final String BORDER_V = "│";
|
||||||
|
|
||||||
|
/** 制表符左上边框 */
|
||||||
|
private static final String BORDER_TL = "┌";
|
||||||
|
|
||||||
|
/** 制表符上边框 */
|
||||||
|
private static final String BORDER_T = "┬";
|
||||||
|
|
||||||
|
/** 制表符右上边框 */
|
||||||
|
private static final String BORDER_TR = "┐";
|
||||||
|
|
||||||
|
/** 制表符左边框 */
|
||||||
|
private static final String BORDER_L = "├";
|
||||||
|
|
||||||
|
/** 制表符十字边框 */
|
||||||
|
private static final String BORDER_P = "┼";
|
||||||
|
|
||||||
|
/** 制表符右边框 */
|
||||||
|
private static final String BORDER_R = "┤";
|
||||||
|
|
||||||
|
/** 制表符左下边框 */
|
||||||
|
private static final String BORDER_BL = "└";
|
||||||
|
|
||||||
|
/** 制表符下边框 */
|
||||||
|
private static final String BORDER_B = "┴";
|
||||||
|
|
||||||
|
/** 制表符右下边框 */
|
||||||
|
private static final String BORDER_BR = "┘";
|
||||||
|
|
||||||
|
private final List<ColMapping<T>> colMappingList = new ArrayList<>();
|
||||||
|
|
||||||
|
private final List<Row> rowList = new ArrayList<>();
|
||||||
|
|
||||||
|
public String render(List<T> dataList) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
{
|
||||||
|
// 表头
|
||||||
|
Row header = new Row();
|
||||||
|
for (int i = 0; i < colMappingList.size(); i++) {
|
||||||
|
Row.Col col = new Row.Col();
|
||||||
|
col.text = colMappingList.get(i).name;
|
||||||
|
header.colList.add(col);
|
||||||
|
}
|
||||||
|
rowList.add(header);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// 数据
|
||||||
|
for (int i = 0; i < dataList.size(); i++) {
|
||||||
|
Row row = new Row();
|
||||||
|
for (int j = 0; j < colMappingList.size(); j++) {
|
||||||
|
Row.Col col = new Row.Col();
|
||||||
|
Object objValue;
|
||||||
|
|
||||||
|
ColMapping<T> tColMapping = colMappingList.get(j);
|
||||||
|
if (tColMapping.itemCallback == null) {
|
||||||
|
objValue = Ref.getFieldValue(dataList.get(i), tColMapping.field, Object.class);
|
||||||
|
} else {
|
||||||
|
objValue = tColMapping.itemCallback.handler(dataList.get(i));
|
||||||
|
}
|
||||||
|
if (tColMapping.valueCallback != null) {
|
||||||
|
objValue = tColMapping.valueCallback.handler(objValue.toString());
|
||||||
|
}
|
||||||
|
col.text = objValue.toString();
|
||||||
|
row.colList.add(col);
|
||||||
|
}
|
||||||
|
rowList.add(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 对齐渲染
|
||||||
|
Map<Integer, Integer> colMaxSize = new HashMap<>();
|
||||||
|
for (int i = 0; i < rowList.size(); i++) {
|
||||||
|
List<Row.Col> colList = rowList.get(i).colList;
|
||||||
|
for (int j = 0; j < colList.size(); j++) {
|
||||||
|
String colText = colList.get(j).text;
|
||||||
|
if (!colMaxSize.containsKey(j)) {
|
||||||
|
colMaxSize.put(j, colText.length());
|
||||||
|
}
|
||||||
|
colMaxSize.put(j, Math.max(colMaxSize.get(j), colText.length()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < rowList.size(); i++) {
|
||||||
|
List<Row.Col> colList = rowList.get(i).colList;
|
||||||
|
if (i == 0) {
|
||||||
|
// 顶边
|
||||||
|
sb.append(BORDER_TL);
|
||||||
|
for (int j = 0; j < colList.size(); j++) {
|
||||||
|
sb.append(BORDER_H.repeat(colMaxSize.get(j)));
|
||||||
|
if (j == colList.size() - 1) {
|
||||||
|
sb.append(BORDER_TR);
|
||||||
|
} else {
|
||||||
|
sb.append(BORDER_T);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.append('\n');
|
||||||
|
}
|
||||||
|
// 数据
|
||||||
|
for (int j = 0; j < colList.size(); j++) {
|
||||||
|
String text = colList.get(j).text;
|
||||||
|
if (j == 0) {
|
||||||
|
sb.append(BORDER_V);
|
||||||
|
}
|
||||||
|
sb.append(Text.paddedSpaceEnd(text, colMaxSize.get(j))).append(BORDER_V);
|
||||||
|
}
|
||||||
|
sb.append('\n');
|
||||||
|
if (i != rowList.size() - 1) {
|
||||||
|
// 数据分割
|
||||||
|
for (int j = 0; j < colList.size(); j++) {
|
||||||
|
if (j == 0) {
|
||||||
|
sb.append(BORDER_L);
|
||||||
|
}
|
||||||
|
sb.append(BORDER_H.repeat(colMaxSize.get(j)));
|
||||||
|
if (j == colList.size() - 1) {
|
||||||
|
sb.append(BORDER_R);
|
||||||
|
} else {
|
||||||
|
sb.append(BORDER_P);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.append('\n');
|
||||||
|
} else {
|
||||||
|
// 底边
|
||||||
|
sb.append(BORDER_BL);
|
||||||
|
for (int j = 0; j < colList.size(); j++) {
|
||||||
|
sb.append(BORDER_H.repeat(colMaxSize.get(j)));
|
||||||
|
if (j == colList.size() - 1) {
|
||||||
|
sb.append(BORDER_BR);
|
||||||
|
} else {
|
||||||
|
sb.append(BORDER_B);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new TimiException(TimiCode.ERROR, e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addHeader(String field) {
|
||||||
|
addHeader(field.substring(0, 1).toUpperCase() + field.substring(1), field);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addHeader(String name, String field) {
|
||||||
|
TimiException.required(field, "not found field");
|
||||||
|
|
||||||
|
ColMapping<T> colMapping = new ColMapping<>();
|
||||||
|
colMapping.name = name;
|
||||||
|
colMapping.field = field;
|
||||||
|
colMappingList.add(colMapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addHeader(ColMapping<T> colMapping) {
|
||||||
|
colMappingList.add(colMapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2024-12-18 10:16
|
||||||
|
*/
|
||||||
|
public static class ColMapping<T> extends Row.Col {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private String field;
|
||||||
|
|
||||||
|
private CallbackArgReturn<T, String> itemCallback;
|
||||||
|
|
||||||
|
private CallbackArgReturn<String, String> valueCallback;
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getField() {
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setField(String field) {
|
||||||
|
this.field = field;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CallbackArgReturn<T, String> getItemCallback() {
|
||||||
|
return itemCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setItemCallback(CallbackArgReturn<T, String> itemCallback) {
|
||||||
|
this.itemCallback = itemCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CallbackArgReturn<String, String> getValueCallback() {
|
||||||
|
return valueCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValueCallback(CallbackArgReturn<String, String> valueCallback) {
|
||||||
|
this.valueCallback = valueCallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2024-12-18 10:10
|
||||||
|
*/
|
||||||
|
private static class Row {
|
||||||
|
|
||||||
|
List<Col> colList = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2024-12-18 10:10
|
||||||
|
*/
|
||||||
|
private static class Col {
|
||||||
|
|
||||||
|
String text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
204
src/main/java/com/imyeyu/utils/Calc.java
Normal file
204
src/main/java/com/imyeyu/utils/Calc.java
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
package com.imyeyu.utils;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数学计算扩展
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @version 2023-03-20 10:28
|
||||||
|
*/
|
||||||
|
public class Calc {
|
||||||
|
|
||||||
|
private static final SecureRandom RANDOM = new SecureRandom();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为数字
|
||||||
|
*
|
||||||
|
* @param data 字符串
|
||||||
|
* @return 为 true 是表示是数字
|
||||||
|
*/
|
||||||
|
public static boolean isNumber(String data) {
|
||||||
|
try {
|
||||||
|
Double.parseDouble(data);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向上取整返回整型
|
||||||
|
*
|
||||||
|
* @param v 数值
|
||||||
|
* @return 取整结果
|
||||||
|
*/
|
||||||
|
public static int ceil(double v) {
|
||||||
|
return (int) Math.ceil(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向下取整返回整型
|
||||||
|
*
|
||||||
|
* @param v 数值
|
||||||
|
* @return 取整结果
|
||||||
|
*/
|
||||||
|
public static int floor(double v) {
|
||||||
|
return (int) Math.floor(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 四舍五入返回整型
|
||||||
|
*
|
||||||
|
* @param v 数值
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public static int round(double v) {
|
||||||
|
return (int) Math.round(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double round(double v, int scale) {
|
||||||
|
return round(v, scale, RoundingMode.DOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double round(double v, int scale, RoundingMode mode) {
|
||||||
|
BigDecimal bd = new BigDecimal(Double.toString(v));
|
||||||
|
bd = bd.setScale(scale, mode);
|
||||||
|
return bd.doubleValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 四舍五入返回长整型
|
||||||
|
*
|
||||||
|
* @param v 数值
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public static long roundLong(double v) {
|
||||||
|
return Math.round(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 范围内取随机值 [min, max]
|
||||||
|
*
|
||||||
|
* @param min 最小值
|
||||||
|
* @param max 最大值
|
||||||
|
* @return 随机值
|
||||||
|
*/
|
||||||
|
public static int random(int min, int max) {
|
||||||
|
if (max < min) {
|
||||||
|
throw new IllegalArgumentException("min must less than max");
|
||||||
|
}
|
||||||
|
return RANDOM.nextInt(max + 1 - min) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 范围内取随机值 [min, max]
|
||||||
|
*
|
||||||
|
* @param min 最小值
|
||||||
|
* @param max 最大值
|
||||||
|
* @return 随机值
|
||||||
|
*/
|
||||||
|
public static long random(long min, long max) {
|
||||||
|
if (max < min) {
|
||||||
|
throw new IllegalArgumentException("min must less than max");
|
||||||
|
}
|
||||||
|
return RANDOM.nextLong(max + 1 - min) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 范围内取随机值 (min, max)
|
||||||
|
*
|
||||||
|
* @param min 最小值
|
||||||
|
* @param max 最大值
|
||||||
|
* @return 随机值
|
||||||
|
*/
|
||||||
|
public static float random(float min, float max) {
|
||||||
|
if (max < min) {
|
||||||
|
throw new IllegalArgumentException("min must less than max");
|
||||||
|
}
|
||||||
|
return RANDOM.nextFloat(max - min) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 范围内取随机值 (min, max)
|
||||||
|
*
|
||||||
|
* @param min 最小值
|
||||||
|
* @param max 最大值
|
||||||
|
* @return 随机值
|
||||||
|
*/
|
||||||
|
public static double random(double min, double max) {
|
||||||
|
if (max < min) {
|
||||||
|
throw new IllegalArgumentException("min must less than max");
|
||||||
|
}
|
||||||
|
return RANDOM.nextDouble(max - min) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean randomBoolean() {
|
||||||
|
return random(0, 1) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算两数差值
|
||||||
|
*
|
||||||
|
* @param v0 第一个数
|
||||||
|
* @param v1 第二个数
|
||||||
|
* @return 差值
|
||||||
|
* @param <T> 类型
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <T extends Number> T between(T v0, T v1) {
|
||||||
|
if (v0 instanceof BigDecimal bd0 && v1 instanceof BigDecimal bd1) {
|
||||||
|
return (T) (Double) bd0.subtract(bd1).abs().doubleValue();
|
||||||
|
}
|
||||||
|
return (T) (Double) Math.abs(v0.doubleValue() - v1.doubleValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算一个数字是否在区间内
|
||||||
|
*
|
||||||
|
* @param min 区间最小值
|
||||||
|
* @param max 区间最大值
|
||||||
|
* @param number 判定值
|
||||||
|
* @return true 时,number 在 min 和 max 之间
|
||||||
|
*/
|
||||||
|
public static boolean in(double min, double max, double number) {
|
||||||
|
if (max < min) {
|
||||||
|
throw new IllegalArgumentException("min must less than max");
|
||||||
|
}
|
||||||
|
return min <= number && number <= max;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为正整数
|
||||||
|
*
|
||||||
|
* @param num 数字
|
||||||
|
* @return true 为正整数
|
||||||
|
*/
|
||||||
|
public static boolean isN1(Long num) {
|
||||||
|
if (num == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return 0 < num;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安全限制数值范围,当入参值在最小值和最大值之间时返回原值,否则返回最小或最大值
|
||||||
|
*
|
||||||
|
* @param min 最小值
|
||||||
|
* @param value 入参值
|
||||||
|
* @param max 最大值
|
||||||
|
* @return 限制结果
|
||||||
|
*/
|
||||||
|
public static Number range(Number min, Number value, Number max) {
|
||||||
|
if (value.doubleValue() < min.doubleValue()) {
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
if (max.doubleValue() < value.doubleValue()) {
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
355
src/main/java/com/imyeyu/utils/Collect.java
Normal file
355
src/main/java/com/imyeyu/utils/Collect.java
Normal file
@ -0,0 +1,355 @@
|
|||||||
|
package com.imyeyu.utils;
|
||||||
|
|
||||||
|
import com.imyeyu.java.TimiJava;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.ObjectInputStream;
|
||||||
|
import java.io.ObjectOutputStream;
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.BitSet;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author 夜雨
|
||||||
|
* @version 2023-08-07 11:50
|
||||||
|
*/
|
||||||
|
public class Collect {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数组元素移除
|
||||||
|
*
|
||||||
|
* @param array 源数组
|
||||||
|
* @param items 移除项
|
||||||
|
* @param <T> 数组数据类型
|
||||||
|
* @return 移除结果数组
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <T> T[] arrayRemove(T[] array, T... items) {
|
||||||
|
if (array == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (TimiJava.isEmpty(array) || TimiJava.isEmpty(items)) {
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
Map<T, Integer> occurrences = new HashMap<>(array.length);
|
||||||
|
for (T v : items) {
|
||||||
|
occurrences.merge(v, 1, Integer::sum);
|
||||||
|
}
|
||||||
|
BitSet toRemove = new BitSet();
|
||||||
|
for (int i = 0; i < array.length; i++) {
|
||||||
|
T key = array[i];
|
||||||
|
Integer count = occurrences.get(key);
|
||||||
|
if (count != null) {
|
||||||
|
occurrences.put(key, count - 1);
|
||||||
|
if (occurrences.get(key) == 0) {
|
||||||
|
occurrences.remove(key);
|
||||||
|
}
|
||||||
|
toRemove.set(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int srcLength = array.length;
|
||||||
|
int removals = toRemove.cardinality();
|
||||||
|
T[] result = (T[]) Array.newInstance(array.getClass().getComponentType(), srcLength - removals);
|
||||||
|
int srcIndex = 0;
|
||||||
|
int destIndex = 0;
|
||||||
|
int count;
|
||||||
|
int set;
|
||||||
|
while ((set = toRemove.nextSetBit(srcIndex)) != -1) {
|
||||||
|
count = set - srcIndex;
|
||||||
|
if (count > 0) {
|
||||||
|
System.arraycopy(array, srcIndex, result, destIndex, count);
|
||||||
|
destIndex += count;
|
||||||
|
}
|
||||||
|
srcIndex = toRemove.nextClearBit(set);
|
||||||
|
}
|
||||||
|
count = srcLength - srcIndex;
|
||||||
|
if (count > 0) {
|
||||||
|
System.arraycopy(array, srcIndex, result, destIndex, count);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取出哈希表的键作为列表
|
||||||
|
*
|
||||||
|
* @param map 哈希表
|
||||||
|
* @param <K> 键泛型
|
||||||
|
* @param <V> 值泛型
|
||||||
|
* @return 以哈希表键为类型的列表
|
||||||
|
*/
|
||||||
|
public static <K, V> List<K> mapKeys(Map<K, V> map) {
|
||||||
|
if ((map != null) && (!map.isEmpty())) {
|
||||||
|
return new ArrayList<>(map.keySet());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机哈希表
|
||||||
|
*
|
||||||
|
* @param map 哈希表
|
||||||
|
* @param limit 数量限制
|
||||||
|
* @param <K> 键泛型
|
||||||
|
* @param <V> 值泛型
|
||||||
|
* @return 随机结果
|
||||||
|
*/
|
||||||
|
public static <K, V> Map<K, V> randomMap(Map<K, V> map, int limit) {
|
||||||
|
Map<K, V> result = new LinkedHashMap<>();
|
||||||
|
List<K> list = mapKeys(map);
|
||||||
|
list.sort((lhs, rhs) -> {
|
||||||
|
int r1 = (int) (Math.random() * 10 + lhs.hashCode());
|
||||||
|
int r2 = (int) (Math.random() * 10 + rhs.hashCode());
|
||||||
|
return r1 - r2;
|
||||||
|
});
|
||||||
|
for (int i = 0, l = list.size(); i < l; i++) {
|
||||||
|
if (result.size() < limit) {
|
||||||
|
result.put(list.get(i), map.get(list.get(i)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据键排序哈希表
|
||||||
|
*
|
||||||
|
* @param map 哈希表
|
||||||
|
* @param comparator 比较器
|
||||||
|
* @param <K> 键泛型
|
||||||
|
* @param <V> 值泛型
|
||||||
|
* @return 排序结果
|
||||||
|
*/
|
||||||
|
public static <K, V> Map<K, V> sortMap(Map<K, V> map, Comparator<K> comparator) {
|
||||||
|
if (map == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (map.isEmpty()) {
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
Map<K, V> r = new TreeMap<>(comparator);
|
||||||
|
r.putAll(map);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据数字值排序 Map(正序)
|
||||||
|
*
|
||||||
|
* @param map 哈希表
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @return 排序结果列表
|
||||||
|
*/
|
||||||
|
public static <K> LinkedHashMap<K, Number> sortMapByNumberValueASC(Map<K, Number> map) {
|
||||||
|
return sortMapByNumberValue(map, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据数字值排序 Map(倒序)
|
||||||
|
*
|
||||||
|
* @param map 哈希表
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @return 排序结果列表
|
||||||
|
*/
|
||||||
|
public static <K> LinkedHashMap<K, Number> sortMapByNumberValueDESC(Map<K, Number> map) {
|
||||||
|
return sortMapByNumberValue(map, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据数字值排序 Map
|
||||||
|
*
|
||||||
|
* @param map 哈希表
|
||||||
|
* @param isASC true 为正序
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @return 排序结果列表
|
||||||
|
*/
|
||||||
|
public static <K> LinkedHashMap<K, Number> sortMapByNumberValue(Map<K, Number> map, boolean isASC) {
|
||||||
|
return sortMapByValue(map, (o1, o2) -> {
|
||||||
|
if (isASC) {
|
||||||
|
return Double.compare(o1.getValue().doubleValue(), o2.getValue().doubleValue());
|
||||||
|
} else {
|
||||||
|
return Double.compare(o2.getValue().doubleValue(), o1.getValue().doubleValue());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据值排序 Map
|
||||||
|
*
|
||||||
|
* @param map 哈希表
|
||||||
|
* @param comparator 比较器
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <V> 值类型
|
||||||
|
* @return 排序结果列表
|
||||||
|
*/
|
||||||
|
public static <K, V> LinkedHashMap<K, V> sortMapByValue(Map<K, V> map, Comparator<Map.Entry<K, V>> comparator) {
|
||||||
|
List<Map.Entry<K, V>> list = new ArrayList<>(map.entrySet());
|
||||||
|
list.sort(comparator);
|
||||||
|
|
||||||
|
LinkedHashMap<K, V> result = new LinkedHashMap<>();
|
||||||
|
for (Map.Entry<K, V> entry : list) {
|
||||||
|
result.put(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据字符串键排序哈希表
|
||||||
|
*
|
||||||
|
* @param map 哈希表
|
||||||
|
* @param <V> 值泛型
|
||||||
|
* @return 排序结果
|
||||||
|
*/
|
||||||
|
public static <V> Map<String, V> sortMapByStringKeyASC(Map<String, V> map) {
|
||||||
|
return sortMap(map, String::compareTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据数字键排序哈希表
|
||||||
|
*
|
||||||
|
* @param map 哈希表
|
||||||
|
* @param <V> 值泛型
|
||||||
|
* @return 排序结果
|
||||||
|
*/
|
||||||
|
public static <V> Map<Number, V> sortMapByNumberKeyASC(Map<Number, V> map) {
|
||||||
|
return sortMap(map, Comparator.comparingDouble(Number::doubleValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据数字键排序哈希表
|
||||||
|
*
|
||||||
|
* @param map 哈希表
|
||||||
|
* @param <V> 值泛型
|
||||||
|
* @return 排序结果
|
||||||
|
*/
|
||||||
|
public static <V> Map<Number, V> sortMapByNumberKeyDESC(Map<Number, V> map) {
|
||||||
|
return sortMap(map, Comparator.comparingDouble(Number::doubleValue).reversed());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安全地根据键移除哈希表的对象
|
||||||
|
*
|
||||||
|
* @param map 哈希表
|
||||||
|
* @param key 键
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <V> 值类型
|
||||||
|
* @return 被移除值
|
||||||
|
*/
|
||||||
|
public static <K, V> V removeByKey(Map<K, V> map, K key) {
|
||||||
|
Iterator<K> iterator = map.keySet().iterator();
|
||||||
|
K k;
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
k = iterator.next();
|
||||||
|
if (key.equals(k)) {
|
||||||
|
V v = map.get(k);
|
||||||
|
iterator.remove();
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安全地根据一些键移除哈希表的对象
|
||||||
|
*
|
||||||
|
* @param map 哈希表
|
||||||
|
* @param keys 键
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <V> 值类型
|
||||||
|
* @return 被移除值列表
|
||||||
|
*/
|
||||||
|
@SafeVarargs
|
||||||
|
public static <K, V> List<V> removeByKeys(Map<K, V> map, K... keys) {
|
||||||
|
Set<K> keysList = Set.of(keys);
|
||||||
|
List<V> removes = new ArrayList<>();
|
||||||
|
Iterator<K> iterator = map.keySet().iterator();
|
||||||
|
K k;
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
k = iterator.next();
|
||||||
|
if (keysList.contains(k)) {
|
||||||
|
removes.add(map.get(k));
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return removes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安全地根据值从哈希表移除数据
|
||||||
|
*
|
||||||
|
* @param map 哈希表
|
||||||
|
* @param value 值
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <V> 值类型
|
||||||
|
* @return 被移除的键
|
||||||
|
*/
|
||||||
|
public static <K, V> K removeByValue(Map<K, V> map, V value) {
|
||||||
|
Iterator<K> iterator = map.keySet().iterator();
|
||||||
|
K k;
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
k = iterator.next();
|
||||||
|
if (map.get(k).equals(value)) {
|
||||||
|
iterator.remove();
|
||||||
|
return k;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安全地根据一些值从哈希表移除数据
|
||||||
|
*
|
||||||
|
* @param map 哈希表
|
||||||
|
* @param values 值
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <V> 值类型
|
||||||
|
* @return 被移除的键列表
|
||||||
|
*/
|
||||||
|
@SafeVarargs
|
||||||
|
public static <K, V> List<K> removeByValues(Map<K, V> map, V... values) {
|
||||||
|
Set<V> valuesList = Set.of(values);
|
||||||
|
List<K> removes = new ArrayList<>();
|
||||||
|
Iterator<K> iterator = map.keySet().iterator();
|
||||||
|
K k;
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
k = iterator.next();
|
||||||
|
if (valuesList.contains(map.get(k))) {
|
||||||
|
iterator.remove();
|
||||||
|
removes.add(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return removes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 深克隆列表(通过序列化)
|
||||||
|
*
|
||||||
|
* @param list 列表
|
||||||
|
* @param <T> 数据类型
|
||||||
|
* @return 克隆列表
|
||||||
|
* @throws Exception 克隆异常
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <T> List<T> deepCopyList(List<T> list) throws Exception {
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
ObjectOutputStream oos = new ObjectOutputStream(baos);
|
||||||
|
oos.writeObject(list);
|
||||||
|
|
||||||
|
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
|
||||||
|
ObjectInputStream in = new ObjectInputStream(bais);
|
||||||
|
List<T> result = (List<T>) in.readObject();
|
||||||
|
|
||||||
|
in.close();
|
||||||
|
bais.close();
|
||||||
|
oos.close();
|
||||||
|
baos.close();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
105
src/main/java/com/imyeyu/utils/Decoder.java
Normal file
105
src/main/java/com/imyeyu/utils/Decoder.java
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
package com.imyeyu.utils;
|
||||||
|
|
||||||
|
import com.imyeyu.java.bean.timi.TimiCode;
|
||||||
|
import com.imyeyu.java.bean.timi.TimiException;
|
||||||
|
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编码操作
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @version 2021-02-13 10:59
|
||||||
|
*/
|
||||||
|
public class Decoder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解码 Unicode 字符串
|
||||||
|
*
|
||||||
|
* @param data Unicode 字符串
|
||||||
|
* @return 解码结果
|
||||||
|
*/
|
||||||
|
public static String unicode(String data) {
|
||||||
|
StringWriter out = new StringWriter(data.length());
|
||||||
|
|
||||||
|
StringBuilder unicode = new StringBuilder(4);
|
||||||
|
boolean hadSlash = false;
|
||||||
|
boolean inUnicode = false;
|
||||||
|
for (int i = 0, l = data.length(); i < l; i++) {
|
||||||
|
char c = data.charAt(i);
|
||||||
|
if (inUnicode) {
|
||||||
|
unicode.append(c);
|
||||||
|
if (unicode.length() == 4) {
|
||||||
|
try {
|
||||||
|
out.write((char) Integer.parseInt(unicode.toString(), 16));
|
||||||
|
unicode.setLength(0);
|
||||||
|
inUnicode = false;
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
throw new TimiException(TimiCode.ERROR, "Unable to parse unicode value: " + unicode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (hadSlash) {
|
||||||
|
hadSlash = false;
|
||||||
|
switch (c) {
|
||||||
|
case '\\' -> out.write('\\');
|
||||||
|
case '\'' -> out.write('\'');
|
||||||
|
case '\"' -> out.write('"');
|
||||||
|
case 'r' -> out.write('\r');
|
||||||
|
case 'f' -> out.write('\f');
|
||||||
|
case 't' -> out.write('\t');
|
||||||
|
case 'n' -> out.write('\n');
|
||||||
|
case 'b' -> out.write('\b');
|
||||||
|
case 'u' -> inUnicode = true;
|
||||||
|
default -> out.write(c);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
} else if (c == '\\') {
|
||||||
|
hadSlash = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
out.write(c);
|
||||||
|
}
|
||||||
|
if (hadSlash) {
|
||||||
|
out.write('\\');
|
||||||
|
}
|
||||||
|
return out.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解码 Base64 字符串
|
||||||
|
*
|
||||||
|
* @param data Base64 字符串
|
||||||
|
* @return 解码结果
|
||||||
|
*/
|
||||||
|
public static byte[] base64(String data) {
|
||||||
|
return Base64.getDecoder().decode(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解码 Base64 字符串
|
||||||
|
*
|
||||||
|
* @param data Base64 字符串
|
||||||
|
* @return 解码结果
|
||||||
|
*/
|
||||||
|
public static String base64String(String data) {
|
||||||
|
return new String(base64(data), StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解码 URL 链接
|
||||||
|
*
|
||||||
|
* @param url 已编码的 URL 链接
|
||||||
|
* @return 解码结果
|
||||||
|
*/
|
||||||
|
public static String url(String url) {
|
||||||
|
if (url == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return URLDecoder.decode(url, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
}
|
||||||
83
src/main/java/com/imyeyu/utils/Digest.java
Normal file
83
src/main/java/com/imyeyu/utils/Digest.java
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package com.imyeyu.utils;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author 夜雨
|
||||||
|
* @version 2023-08-07 14:27
|
||||||
|
*/
|
||||||
|
public class Digest {
|
||||||
|
|
||||||
|
public static String md5(String data) throws NoSuchAlgorithmException {
|
||||||
|
return md5(data, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String md5(String data, Charset charset) throws NoSuchAlgorithmException {
|
||||||
|
return md5(data.getBytes(charset));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String md5(byte[] data) throws NoSuchAlgorithmException {
|
||||||
|
if (data == null || data.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
MessageDigest md5 = MessageDigest.getInstance("MD5");
|
||||||
|
md5.update(data);
|
||||||
|
byte[] bytes = md5.digest();
|
||||||
|
char[] chars = new char[bytes.length * 2];
|
||||||
|
for (int i = 0, j = 0; i < bytes.length; i++) {
|
||||||
|
chars[j++] = Text.HEX_DIGITS_LOWER[bytes[i] >>> 4 & 0xF];
|
||||||
|
chars[j++] = Text.HEX_DIGITS_LOWER[bytes[i] & 0xF];
|
||||||
|
}
|
||||||
|
return new String(chars);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String sha1(String data) throws NoSuchAlgorithmException {
|
||||||
|
return sha1(data, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String sha1(String data, Charset charset) throws NoSuchAlgorithmException {
|
||||||
|
return sha1(data.getBytes(charset));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String sha1(byte[] bytes) throws NoSuchAlgorithmException {
|
||||||
|
MessageDigest sha = MessageDigest.getInstance("SHA");
|
||||||
|
sha.update(bytes);
|
||||||
|
return Text.byteToHex(sha.digest());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String sha256(String data) throws NoSuchAlgorithmException {
|
||||||
|
return sha256(data, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String sha256(String data, Charset charset) throws NoSuchAlgorithmException {
|
||||||
|
return sha256(data.getBytes(charset));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String sha256(byte[] bytes) throws NoSuchAlgorithmException {
|
||||||
|
MessageDigest sha = MessageDigest.getInstance("SHA-256");
|
||||||
|
sha.update(bytes);
|
||||||
|
return Text.byteToHex(sha.digest());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String sha512(String data) throws NoSuchAlgorithmException {
|
||||||
|
return sha512(data, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String sha512(String data, Charset charset) throws NoSuchAlgorithmException {
|
||||||
|
return sha512(data.getBytes(charset));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String sha512(byte[] bytes) throws NoSuchAlgorithmException {
|
||||||
|
MessageDigest sha = MessageDigest.getInstance("SHA-512");
|
||||||
|
BigInteger number = new BigInteger(1, sha.digest(bytes));
|
||||||
|
StringBuilder result = new StringBuilder(number.toString(16));
|
||||||
|
while (result.length() < 64) {
|
||||||
|
result.insert(0, "0");
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
162
src/main/java/com/imyeyu/utils/Encoder.java
Normal file
162
src/main/java/com/imyeyu/utils/Encoder.java
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
package com.imyeyu.utils;
|
||||||
|
|
||||||
|
import com.imyeyu.java.TimiJava;
|
||||||
|
import com.imyeyu.java.bean.timi.TimiCode;
|
||||||
|
import com.imyeyu.java.bean.timi.TimiException;
|
||||||
|
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.net.IDN;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编码操作
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @version 2021-02-13 10:59
|
||||||
|
*/
|
||||||
|
public class Encoder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unicode 编码全角字符
|
||||||
|
*
|
||||||
|
* @param data 字符串
|
||||||
|
* @return 编码结果
|
||||||
|
*/
|
||||||
|
public static String unicode(String data) {
|
||||||
|
StringWriter out = new StringWriter(data.length() * 2);
|
||||||
|
for (int i = 0, l = data.length(); i < l; i++) {
|
||||||
|
char c = data.charAt(i);
|
||||||
|
String cHex = Integer.toHexString(c).toUpperCase(Locale.ENGLISH);
|
||||||
|
|
||||||
|
if (0xFFF < c) {
|
||||||
|
out.write("\\u" + cHex);
|
||||||
|
} else if (0xFF < c) {
|
||||||
|
out.write("\\u0" + cHex);
|
||||||
|
} else if (0x7F < c) {
|
||||||
|
out.write("\\u00" + cHex);
|
||||||
|
} else if (c < 32) {
|
||||||
|
switch (c) {
|
||||||
|
case '\b' -> {
|
||||||
|
out.write('\\');
|
||||||
|
out.write('b');
|
||||||
|
}
|
||||||
|
case '\n' -> {
|
||||||
|
out.write('\\');
|
||||||
|
out.write('n');
|
||||||
|
}
|
||||||
|
case '\t' -> {
|
||||||
|
out.write('\\');
|
||||||
|
out.write('t');
|
||||||
|
}
|
||||||
|
case '\f' -> {
|
||||||
|
out.write('\\');
|
||||||
|
out.write('f');
|
||||||
|
}
|
||||||
|
case '\r' -> {
|
||||||
|
out.write('\\');
|
||||||
|
out.write('r');
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
if (0xF < c) {
|
||||||
|
out.write("\\u00" + cHex);
|
||||||
|
} else {
|
||||||
|
out.write("\\u000" + cHex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (c) {
|
||||||
|
case '\'' -> {
|
||||||
|
out.write('\\');
|
||||||
|
out.write('\'');
|
||||||
|
}
|
||||||
|
case '"' -> {
|
||||||
|
out.write('\\');
|
||||||
|
out.write('"');
|
||||||
|
}
|
||||||
|
case '\\' -> {
|
||||||
|
out.write('\\');
|
||||||
|
out.write('\\');
|
||||||
|
}
|
||||||
|
case '/' -> {
|
||||||
|
out.write('\\');
|
||||||
|
out.write('/');
|
||||||
|
}
|
||||||
|
default -> out.write(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base64 编码字符串
|
||||||
|
*
|
||||||
|
* @param data 字符串
|
||||||
|
* @return 编码结果
|
||||||
|
*/
|
||||||
|
public static String base64(String data) {
|
||||||
|
return base64(data.getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base64 编码字节数据
|
||||||
|
*
|
||||||
|
* @param bytes 字节数据
|
||||||
|
* @return 编码结果
|
||||||
|
*/
|
||||||
|
public static String base64(byte[] bytes) {
|
||||||
|
return Base64.getEncoder().encodeToString(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编码 URL 参数值。不要传整个链接,此方法只用于<u>参数值</u>的编码
|
||||||
|
*
|
||||||
|
* @param data URL 参数
|
||||||
|
* @return 编码结果
|
||||||
|
*/
|
||||||
|
public static String urlArg(String data) {
|
||||||
|
return URLEncoder.encode(data, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编码 URL 参数
|
||||||
|
*
|
||||||
|
* @param args URL 参数表
|
||||||
|
* @return 编码结果
|
||||||
|
*/
|
||||||
|
public static String urlArgs(Map<String, Object> args) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (Map.Entry<String, Object> param : args.entrySet()) {
|
||||||
|
sb.append('&').append(param.getKey()).append('=').append(urlArg(param.getValue().toString()));
|
||||||
|
}
|
||||||
|
return sb.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编码 URL 链接,半角 "+" 号会使用 "%2B"
|
||||||
|
*
|
||||||
|
* @param urlStr URL 链接
|
||||||
|
* @return 编码结果
|
||||||
|
*/
|
||||||
|
public static String url(String urlStr) {
|
||||||
|
if (TimiJava.isEmpty(urlStr)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
URL url = new URL(urlStr);
|
||||||
|
URI uri = new URI(url.getProtocol(), url.getUserInfo(), IDN.toASCII(url.getHost()), url.getPort(), url.getPath(), url.getQuery(), url.getRef());
|
||||||
|
return uri.toASCIIString().replace("+", "%2B");
|
||||||
|
} catch (MalformedURLException | URISyntaxException e) {
|
||||||
|
throw new TimiException(TimiCode.ERROR, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
261
src/main/java/com/imyeyu/utils/OS.java
Normal file
261
src/main/java/com/imyeyu/utils/OS.java
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
package com.imyeyu.utils;
|
||||||
|
|
||||||
|
import com.sun.management.OperatingSystemMXBean;
|
||||||
|
|
||||||
|
import java.awt.Desktop;
|
||||||
|
import java.awt.Toolkit;
|
||||||
|
import java.awt.datatransfer.DataFlavor;
|
||||||
|
import java.awt.datatransfer.StringSelection;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.lang.management.ManagementFactory;
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author 夜雨
|
||||||
|
* @version 2023-08-07 11:46
|
||||||
|
*/
|
||||||
|
public class OS {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2021-02-13 10:15
|
||||||
|
*/
|
||||||
|
public interface FileSystem {
|
||||||
|
|
||||||
|
/** 文件系统路径分隔符 FileSystem.separator */
|
||||||
|
String SEP = File.separator;
|
||||||
|
|
||||||
|
/** 文件名排序,优先文件和文件夹,次级名称 */
|
||||||
|
Comparator<File> COMPARATOR_FILE_NAME = (f1, f2) -> {
|
||||||
|
if (f1.isDirectory() && f2.isFile()) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
if (f1.isFile() && f2.isDirectory()) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return f1.getName().compareToIgnoreCase(f2.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统平台
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @version 2024-06-22 14:41
|
||||||
|
*/
|
||||||
|
public enum Platform {
|
||||||
|
|
||||||
|
WINDOWS,
|
||||||
|
|
||||||
|
LINUX,
|
||||||
|
|
||||||
|
MAC
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 运行时系统 */
|
||||||
|
public static final String NAME = System.getProperty("os.name");
|
||||||
|
|
||||||
|
/** true 为 Windows 系统 */
|
||||||
|
public static final boolean IS_WINDOWS = NAME.toLowerCase().contains("win");
|
||||||
|
|
||||||
|
/** true 为 Mac OSX 系统 */
|
||||||
|
public static final boolean IS_OSX = NAME.toLowerCase().contains("mac os x");
|
||||||
|
|
||||||
|
/** true 为 UNIX 系统 */
|
||||||
|
public static final boolean IS_UNIX = NAME.contains("nix") || NAME.contains("nux") || NAME.contains("mac");
|
||||||
|
|
||||||
|
/** 当前系统平台 */
|
||||||
|
public static final Platform PLATFORM = IS_WINDOWS ? Platform.WINDOWS : (IS_OSX ? Platform.MAC : Platform.LINUX);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 不处理异常执行命令
|
||||||
|
*
|
||||||
|
* @param command 命令
|
||||||
|
*/
|
||||||
|
public static void run(String command) {
|
||||||
|
try {
|
||||||
|
Runtime.getRuntime().exec(new String[] {command});
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 终止程序时执行命令(主线程结束后)
|
||||||
|
*
|
||||||
|
* @param command 命令
|
||||||
|
*/
|
||||||
|
public static void runAfterShutdown(String command) {
|
||||||
|
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||||
|
try {
|
||||||
|
new ProcessBuilder(command).start();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return 系统内存大小(单位:字节) */
|
||||||
|
public static Long getSystemMemorySize() {
|
||||||
|
return ((OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean()).getTotalMemorySize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Windows 系统禁用的字符 */
|
||||||
|
public static final Character[] INVALID_WINDOWS_SPECIFIC_CHARS = {'"', '*', ':', '<', '>', '?', '\\', '|', '/', 0x7F};
|
||||||
|
|
||||||
|
/** Unix 系统禁用的字符 */
|
||||||
|
public static final Character[] INVALID_UNIX_SPECIFIC_CHARS = {'\000'};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件名规则验证
|
||||||
|
*
|
||||||
|
* @param fileName 文件名
|
||||||
|
* @return true 为有效的
|
||||||
|
*/
|
||||||
|
public static boolean isValidFileName(String fileName) {
|
||||||
|
if (fileName == null || fileName.isEmpty() || 255 < fileName.length()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Character[] chars;
|
||||||
|
if (IS_WINDOWS) {
|
||||||
|
chars = INVALID_WINDOWS_SPECIFIC_CHARS;
|
||||||
|
} else if (IS_UNIX) {
|
||||||
|
chars = INVALID_UNIX_SPECIFIC_CHARS;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < chars.length; i++) {
|
||||||
|
if (fileName.contains(chars[i].toString())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用系统资源管理器打开位置
|
||||||
|
*
|
||||||
|
* @param dir 文件
|
||||||
|
*/
|
||||||
|
public static void showInExplorer(File dir) {
|
||||||
|
if (dir == null || !dir.exists()) {
|
||||||
|
throw new IllegalArgumentException("dir is not found");
|
||||||
|
}
|
||||||
|
if (IS_WINDOWS) {
|
||||||
|
if (dir.isFile()) {
|
||||||
|
dir = dir.getParentFile();
|
||||||
|
}
|
||||||
|
run("explorer " + dir.getAbsolutePath() + FileSystem.SEP);
|
||||||
|
} else {
|
||||||
|
Desktop.getDesktop().browseFileDirectory(dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用系统资源管理器打开文件位置并选中
|
||||||
|
*
|
||||||
|
* @param files 文件列表
|
||||||
|
*/
|
||||||
|
public static void showAndSelectInExplorer(File... files) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < files.length; i++) {
|
||||||
|
sb.append('"').append(files[i].getAbsolutePath()).append('"').append(',');
|
||||||
|
}
|
||||||
|
if (IS_WINDOWS) {
|
||||||
|
run("explorer /select," + sb.substring(0, sb.length() - 1));
|
||||||
|
} else {
|
||||||
|
Desktop.getDesktop().browseFileDirectory(files[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查某程序的某进程是否在运行(Windows 方法)
|
||||||
|
*
|
||||||
|
* @param appName 程序名
|
||||||
|
* @param processName 进程名
|
||||||
|
* @param excludeProcessName 排除名称
|
||||||
|
* @return true 为正在运行
|
||||||
|
* @throws Exception 异常
|
||||||
|
*/
|
||||||
|
public static boolean findProcess4Similarity(String appName, String processName, String... excludeProcessName) throws Exception {
|
||||||
|
return findProcess(appName, processName, true, .8F, excludeProcessName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查某程序的某进程是否在运行(Windows 方法)
|
||||||
|
*
|
||||||
|
* @param appName 程序名
|
||||||
|
* @param processName 进程名
|
||||||
|
* @param similarity true 为启用相似度搜索
|
||||||
|
* @param similarityRate 相似度达到多少判定为 true
|
||||||
|
* @param excludeProcessName 排除名称
|
||||||
|
* @return true 为正在运行
|
||||||
|
* @throws Exception 异常
|
||||||
|
*/
|
||||||
|
public static boolean findProcess(String appName, String processName, boolean similarity, float similarityRate, String... excludeProcessName) throws Exception {
|
||||||
|
Process proc = Runtime.getRuntime().exec("tasklist -v -fi " + '"' + "imagename eq " + appName + '"');
|
||||||
|
BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream()));
|
||||||
|
|
||||||
|
int titleStart = -1; // 线程名起始字符
|
||||||
|
String line;
|
||||||
|
whi1e:
|
||||||
|
while ((line = br.readLine()) != null) {
|
||||||
|
if (titleStart == -1) {
|
||||||
|
if (line.startsWith("===")) {
|
||||||
|
titleStart = line.lastIndexOf(" ");
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (line.startsWith(appName)) {
|
||||||
|
// 排除名
|
||||||
|
if (excludeProcessName != null) {
|
||||||
|
for (int i = 0; i < excludeProcessName.length; i++) {
|
||||||
|
if (line.contains(excludeProcessName[i])) {
|
||||||
|
continue whi1e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 相似度匹配
|
||||||
|
if (similarity && titleStart < line.length()) {
|
||||||
|
String title = line.substring(titleStart).trim();
|
||||||
|
if (!title.equals("")) {
|
||||||
|
if (similarityRate < Text.similarityRatio(processName, title)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return line.contains(processName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
br.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置字符串到剪切板(复制)
|
||||||
|
*
|
||||||
|
* @param s 字符串
|
||||||
|
*/
|
||||||
|
public static void setIntoClipboard(String s) {
|
||||||
|
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(s), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取剪切版的字符串(粘贴)
|
||||||
|
*
|
||||||
|
* @return 剪切板字符串,如果剪切板没有字符串将返回空的字符串
|
||||||
|
*/
|
||||||
|
public static String getIntoClipboard() {
|
||||||
|
try {
|
||||||
|
return Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null).getTransferData(DataFlavor.stringFlavor).toString();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
128
src/main/java/com/imyeyu/utils/StringInterpolator.java
Normal file
128
src/main/java/com/imyeyu/utils/StringInterpolator.java
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
package com.imyeyu.utils;
|
||||||
|
|
||||||
|
import com.imyeyu.java.TimiJava;
|
||||||
|
import com.imyeyu.java.bean.CallbackArgReturn;
|
||||||
|
import com.imyeyu.java.ref.Ref;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字符串插值器
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @version 2022-12-01 00:41
|
||||||
|
*/
|
||||||
|
public class StringInterpolator {
|
||||||
|
|
||||||
|
/** ${} 插值正则 */
|
||||||
|
public static final String DOLLAR_OBJ = "\\$\\{(.+?)\\}";
|
||||||
|
|
||||||
|
/** {} 插值正则 */
|
||||||
|
public static final String SIMPLE_OBJ = "\\{(.+?)\\}";
|
||||||
|
|
||||||
|
/** 正则匹配器 */
|
||||||
|
private final Pattern pattern;
|
||||||
|
|
||||||
|
/** 为 true 时允许安全插值空,变量可以插值 null 而不抛出异常 */
|
||||||
|
private boolean nullable = false;
|
||||||
|
|
||||||
|
/** 过滤器 */
|
||||||
|
private Map<String, CallbackArgReturn<String, String>> filterMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认构造
|
||||||
|
*
|
||||||
|
* @param regex 插槽正则
|
||||||
|
*/
|
||||||
|
public StringInterpolator(String regex) {
|
||||||
|
this.pattern = Pattern.compile(regex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注入变量
|
||||||
|
*
|
||||||
|
* @param string 字符串
|
||||||
|
* @param argsMap 变量表
|
||||||
|
* @return 插值结果
|
||||||
|
*/
|
||||||
|
public String inject(String string, Map<String, Object> argsMap) {
|
||||||
|
return pattern.matcher(string).replaceAll(result -> {
|
||||||
|
String group = result.group(1);
|
||||||
|
String key = group.trim();
|
||||||
|
String[] filters = null;
|
||||||
|
if (group.contains("|")) {
|
||||||
|
// 过滤器
|
||||||
|
String[] groups = group.split("\\|");
|
||||||
|
key = groups[0].trim();
|
||||||
|
filters = new String[groups.length - 1];
|
||||||
|
System.arraycopy(groups, 1, filters, 0, filters.length);
|
||||||
|
}
|
||||||
|
Object value = argsMap.get(key);
|
||||||
|
if (key.contains(".")) {
|
||||||
|
// 反射获取
|
||||||
|
try {
|
||||||
|
String[] deepKey = key.split("\\.");
|
||||||
|
value = argsMap.get(deepKey[0]);
|
||||||
|
for (int i = 1; i < deepKey.length; i++) {
|
||||||
|
value = Ref.getFieldValue(value, deepKey[i], Object.class);
|
||||||
|
}
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new RuntimeException("ref field file: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (TimiJava.isNotEmpty(filterMap) && TimiJava.isNotEmpty(filters)) {
|
||||||
|
// 过滤
|
||||||
|
for (int i = 0; i < filters.length; i++) {
|
||||||
|
CallbackArgReturn<String, String> filter = filterMap.get(filters[i].trim());
|
||||||
|
if (filter == null) {
|
||||||
|
throw new NullPointerException("not found %s filter for %s".formatted(filters[i], key));
|
||||||
|
}
|
||||||
|
value = filter.handler((String) value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (value == null) {
|
||||||
|
if (!nullable) {
|
||||||
|
throw new NullPointerException("null pointer exception for arg: " + key);
|
||||||
|
}
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
return String.valueOf(value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void putFilter(String name, CallbackArgReturn<String, String> callback) {
|
||||||
|
if (filterMap == null) {
|
||||||
|
filterMap = new HashMap<>();
|
||||||
|
}
|
||||||
|
filterMap.put(name, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void putAllFilter(Map<String, CallbackArgReturn<String, String>> filterMap) {
|
||||||
|
if (this.filterMap == null) {
|
||||||
|
this.filterMap = new HashMap<>();
|
||||||
|
}
|
||||||
|
this.filterMap.putAll(filterMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeFilter(String name) {
|
||||||
|
if (TimiJava.isNotEmpty(filterMap)) {
|
||||||
|
filterMap.remove(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearFilter() {
|
||||||
|
if (TimiJava.isNotEmpty(filterMap)) {
|
||||||
|
filterMap.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNullable(boolean nullable) {
|
||||||
|
this.nullable = nullable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNullable() {
|
||||||
|
return nullable;
|
||||||
|
}
|
||||||
|
}
|
||||||
463
src/main/java/com/imyeyu/utils/Text.java
Normal file
463
src/main/java/com/imyeyu/utils/Text.java
Normal file
@ -0,0 +1,463 @@
|
|||||||
|
package com.imyeyu.utils;
|
||||||
|
|
||||||
|
import com.imyeyu.java.TimiJava;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author 夜雨
|
||||||
|
* @version 2023-08-07 11:58
|
||||||
|
*/
|
||||||
|
public class Text {
|
||||||
|
|
||||||
|
/** 十六进制小写 */
|
||||||
|
public static char[] HEX_DIGITS_LOWER = "0123456789abcdef".toCharArray();
|
||||||
|
|
||||||
|
/** 十六进制大写 */
|
||||||
|
public static char[] HEX_DIGITS_UPPER = "0123456789ABCDEF".toCharArray();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字节数据转 16 进制字符串
|
||||||
|
*
|
||||||
|
* @param bytes 字节数据
|
||||||
|
* @return 16 进制字符串
|
||||||
|
*/
|
||||||
|
public static String byteToHex(byte[] bytes) {
|
||||||
|
final int l = bytes.length;
|
||||||
|
final char[] c = new char[l << 1];
|
||||||
|
for (int i = 0, j = 0; i < l; i++) {
|
||||||
|
c[j++] = Text.HEX_DIGITS_LOWER[(0xF0 & bytes[i]) >>> 4];
|
||||||
|
c[j++] = Text.HEX_DIGITS_LOWER[0x0F & bytes[i]];
|
||||||
|
}
|
||||||
|
return new String(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 16 进制字符串转字节数据
|
||||||
|
*
|
||||||
|
* @param hex 16 进制字符串
|
||||||
|
* @return 字节数据
|
||||||
|
* @throws UnsupportedEncodingException 不支持的编码
|
||||||
|
*/
|
||||||
|
public static byte[] hexToByte(String hex) throws UnsupportedEncodingException {
|
||||||
|
final char[] c = hex.toCharArray();
|
||||||
|
final byte[] b = new byte[c.length >> 1];
|
||||||
|
|
||||||
|
final int len = c.length;
|
||||||
|
if ((len & 0x01) != 0) {
|
||||||
|
throw new UnsupportedEncodingException("Odd number of characters.");
|
||||||
|
}
|
||||||
|
|
||||||
|
final int outLen = len >> 1;
|
||||||
|
if (c.length < outLen) {
|
||||||
|
throw new UnsupportedEncodingException("Output array is not large enough to accommodate decoded data.");
|
||||||
|
}
|
||||||
|
for (int i = 0, j = 0; j < len; i++) {
|
||||||
|
int f = toDigit(c[j], j) << 4;
|
||||||
|
j++;
|
||||||
|
f = f | toDigit(c[j], j);
|
||||||
|
j++;
|
||||||
|
b[i] = (byte) (f & 0xFF);
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int toDigit(final char ch, final int index) throws UnsupportedEncodingException {
|
||||||
|
final int digit = Character.digit(ch, 16);
|
||||||
|
if (digit == -1) {
|
||||||
|
throw new UnsupportedEncodingException("Illegal hexadecimal character " + ch + " at index " + index);
|
||||||
|
}
|
||||||
|
return digit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为半角字符
|
||||||
|
*
|
||||||
|
* @param c 字符
|
||||||
|
* @return 为 true 是表示是半角字符
|
||||||
|
*/
|
||||||
|
public static boolean isHalfChar(char c) {
|
||||||
|
return (int) c < 129;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字符串加双引号
|
||||||
|
*
|
||||||
|
* @param text 字符串内容
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public static String quote(String text) {
|
||||||
|
return '"' + text + '"';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 前补零(最终长度 2 字符)
|
||||||
|
*
|
||||||
|
* @param number 数值
|
||||||
|
* @return 补零字符串
|
||||||
|
*/
|
||||||
|
public static String zero(Number number) {
|
||||||
|
return zero(2, number);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String paddedSpaceStart(String str, int totalWidth) {
|
||||||
|
return String.format("%" + totalWidth + "s", str);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String paddedSpaceEnd(String str, int totalWidth) {
|
||||||
|
return String.format("%-" + totalWidth + "s", str);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 前补零
|
||||||
|
*
|
||||||
|
* @param l 最终长度
|
||||||
|
* @param number 数值
|
||||||
|
* @return 补零字符串
|
||||||
|
*/
|
||||||
|
public static String zero(int l, Number number) {
|
||||||
|
return String.format("%0" + l + "d", number);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 正则表达式测试
|
||||||
|
*
|
||||||
|
* @param reg 正则
|
||||||
|
* @param value 文本
|
||||||
|
* @return true 为匹配
|
||||||
|
*/
|
||||||
|
public static boolean testReg(String reg, String value) {
|
||||||
|
return Pattern.compile(reg).matcher(value).matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 驼峰转下划线
|
||||||
|
*
|
||||||
|
* @param camelCaseStr 驼峰字符串
|
||||||
|
* @return 下划线字符串
|
||||||
|
*/
|
||||||
|
public static String camelCase2underscore(String camelCaseStr) {
|
||||||
|
return camelCaseStr.replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下划线转驼峰
|
||||||
|
*
|
||||||
|
* @param underscoreName 下划线字符串
|
||||||
|
* @return 驼峰字符串
|
||||||
|
*/
|
||||||
|
public static String underscore2camelCase(String underscoreName) {
|
||||||
|
if (TimiJava.isEmpty(underscoreName)) {
|
||||||
|
return underscoreName;
|
||||||
|
}
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
boolean flag = false;
|
||||||
|
for (int i = 0; i < underscoreName.length(); i++) {
|
||||||
|
char c = underscoreName.charAt(i);
|
||||||
|
if ('_' == c) {
|
||||||
|
flag = true;
|
||||||
|
} else {
|
||||||
|
if (flag) {
|
||||||
|
result.append(Character.toUpperCase(c));
|
||||||
|
flag = false;
|
||||||
|
} else {
|
||||||
|
result.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 与多个字符串进行与比较
|
||||||
|
*
|
||||||
|
* @param string 比较字符串
|
||||||
|
* @param other 其他字符串
|
||||||
|
* @return true 时全部其他字符串和比较字符串一致
|
||||||
|
*/
|
||||||
|
public static boolean eqAnd(String string, String... other) {
|
||||||
|
for (int i = 0; i < other.length; i++) {
|
||||||
|
if (!string.equals(other[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 与多个字符串进行或比较
|
||||||
|
*
|
||||||
|
* @param string 比较字符串
|
||||||
|
* @param other 其他字符串
|
||||||
|
* @return true 时其他字符串存在和比较字符串一致
|
||||||
|
*/
|
||||||
|
public static boolean eqOr(String string, String... other) {
|
||||||
|
for (int i = 0; i < other.length; i++) {
|
||||||
|
if (!string.equals(other[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 与多个字符串进行忽略大小写的与比较
|
||||||
|
*
|
||||||
|
* @param string 比较字符串
|
||||||
|
* @param other 其他字符串
|
||||||
|
* @return true 时全部其他字符串和比较字符串一致
|
||||||
|
*/
|
||||||
|
public static boolean eqIgnoreCaseAnd(String string, String... other) {
|
||||||
|
for (int i = 0; i < other.length; i++) {
|
||||||
|
if (!string.equalsIgnoreCase(other[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 与多个字符串进行忽略大小写的或比较
|
||||||
|
*
|
||||||
|
* @param string 比较字符串
|
||||||
|
* @param other 其他字符串
|
||||||
|
* @return true 时其他字符串存在和比较字符串一致
|
||||||
|
*/
|
||||||
|
public static boolean eqIgnoreCaseOr(String string, String... other) {
|
||||||
|
for (int i = 0; i < other.length; i++) {
|
||||||
|
if (string.equalsIgnoreCase(other[i])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 与多个字符串进行忽略大小写包含关系
|
||||||
|
*
|
||||||
|
* @param string 原字符串
|
||||||
|
* @param other 其他字符串
|
||||||
|
* @return true 为 string 中至少含有一个 other 的忽略大小写的字符段
|
||||||
|
*/
|
||||||
|
public static boolean containsIgnoreCase(String string, String... other) {
|
||||||
|
String stringUpper = string.toUpperCase();
|
||||||
|
String stringLower = string.toLowerCase();
|
||||||
|
for (int i = 0; i < other.length; i++) {
|
||||||
|
if (stringLower.contains(other[i].toLowerCase()) || stringUpper.contains(other[i].toUpperCase())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String randomString(int length) {
|
||||||
|
return randomString("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String randomString(String pool, int length) {
|
||||||
|
SecureRandom r = new SecureRandom();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
sb.append(pool.charAt(r.nextInt(pool.length() - 1)));
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 较短的临时 UUID,如果使用在庞大的数据里,很可能会发生重复
|
||||||
|
*
|
||||||
|
* @return 完整 UUID 前 8 位
|
||||||
|
*/
|
||||||
|
public static String tempUUID() {
|
||||||
|
return UUID.randomUUID().toString().substring(0, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算字符串相似度(编辑距离算法)
|
||||||
|
*
|
||||||
|
* @param source 需比较的字符串
|
||||||
|
* @param target 被比较的字符串
|
||||||
|
* @param isIgnore 为 true 时忽略大小写
|
||||||
|
* @return 相似度 [0, 1]
|
||||||
|
*/
|
||||||
|
private static float levenshteinDistance(String source, String target, boolean isIgnore) {
|
||||||
|
int[][] d;
|
||||||
|
int n = source.length(), m = target.length(), i, j, temp;
|
||||||
|
char charS, charT;
|
||||||
|
|
||||||
|
if (n == 0) {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
if (m == 0) {
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
d = new int[n + 1][m + 1];
|
||||||
|
for (i = 0; i <= n; i++) {
|
||||||
|
d[i][0] = i;
|
||||||
|
}
|
||||||
|
for (j = 0; j <= m; j++) {
|
||||||
|
d[0][j] = j;
|
||||||
|
}
|
||||||
|
for (i = 1; i <= n; i++) {
|
||||||
|
charS = source.charAt(i - 1);
|
||||||
|
for (j = 1; j <= m; j++) {
|
||||||
|
charT = target.charAt(j - 1);
|
||||||
|
if (isIgnore) {
|
||||||
|
if (charS == charT || charS == charT + 32 || charS + 32 == charT) {
|
||||||
|
temp = 0;
|
||||||
|
} else {
|
||||||
|
temp = 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (charS == charT) {
|
||||||
|
temp = 0;
|
||||||
|
} else {
|
||||||
|
temp = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d[i][j] = min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return d[n][m];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 三数求最小
|
||||||
|
*
|
||||||
|
* @param one 值一
|
||||||
|
* @param two 值二
|
||||||
|
* @param three 值三
|
||||||
|
* @return 最小值
|
||||||
|
*/
|
||||||
|
public static int min(int one, int two, int three) {
|
||||||
|
return (one = Math.min(one, two)) < three ? one : three;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量相似度比较字符串(忽略大小写),返回相似度比较列表倒叙结果,较为相似的排最前
|
||||||
|
*
|
||||||
|
* @param sources 需比较的字符串列表
|
||||||
|
* @param target 被比较的字符串
|
||||||
|
* @return 比较结果列表
|
||||||
|
*/
|
||||||
|
public static LinkedHashMap<String, Number> similarityRatioList(Collection<String> sources, String target) {
|
||||||
|
return similarityRatioList(sources, target, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量相似度比较字符串,返回相似度比较列表倒叙结果,较为相似的排最前
|
||||||
|
*
|
||||||
|
* @param sources 需比较的字符串列表
|
||||||
|
* @param target 被比较的字符串
|
||||||
|
* @param isIgnoreCase true 为忽略大小写
|
||||||
|
* @return 比较结果列表
|
||||||
|
*/
|
||||||
|
public static LinkedHashMap<String, Number> similarityRatioList(Collection<String> sources, String target, boolean isIgnoreCase) {
|
||||||
|
Map<String, Number> items = new HashMap<>();
|
||||||
|
for (String source : sources) {
|
||||||
|
items.put(source, 0);
|
||||||
|
}
|
||||||
|
items.replaceAll((k, v) -> similarityRatio(k, target, isIgnoreCase));
|
||||||
|
return Collect.sortMapByNumberValueDESC(items);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 求字符串相似度,忽略大小写
|
||||||
|
*
|
||||||
|
* @param source 需比较的字符串
|
||||||
|
* @param target 被比较的字符串
|
||||||
|
* @return 相似度 [0, 1]
|
||||||
|
*/
|
||||||
|
public static float similarityRatio(String source, String target) {
|
||||||
|
return similarityRatio(source, target, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 求字符串相似度
|
||||||
|
*
|
||||||
|
* @param source 需比较的字符串
|
||||||
|
* @param target 被比较的字符串
|
||||||
|
* @param isIgnoreCase true 为忽略大小写
|
||||||
|
* @return 相似度 [0, 1]
|
||||||
|
*/
|
||||||
|
public static float similarityRatio(String source, String target, boolean isIgnoreCase) {
|
||||||
|
float ret;
|
||||||
|
final int max = Math.max(source.length(), target.length());
|
||||||
|
if (max == 0) {
|
||||||
|
ret = 1;
|
||||||
|
} else {
|
||||||
|
ret = 1 - levenshteinDistance(source, target, isIgnoreCase) / max;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检验字符串是否为 json 数据,不校验是否有错误
|
||||||
|
*
|
||||||
|
* @param s 字符串
|
||||||
|
* @return true 为是 JSON 数据
|
||||||
|
*/
|
||||||
|
public static boolean isJson(String s) {
|
||||||
|
return isJsonObject(s) || isJsonArray(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检验字符串是否为 json 对象,不校验是否有错误
|
||||||
|
*
|
||||||
|
* @param s 字符串
|
||||||
|
* @return true 为是 JSON 对象
|
||||||
|
*/
|
||||||
|
public static boolean isJsonObject(String s) {
|
||||||
|
return s.startsWith("{") && s.endsWith("}");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检验字符串是否为 json 数组,不校验是否有错误
|
||||||
|
*
|
||||||
|
* @param s 字符串
|
||||||
|
* @return true 为是 JSON 数组
|
||||||
|
*/
|
||||||
|
public static boolean isJsonArray(String s) {
|
||||||
|
return s.startsWith("[") && s.endsWith("]");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字符串替换,不需要正则的情况下
|
||||||
|
*
|
||||||
|
* @param string 字符串
|
||||||
|
* @param from 被替换字符
|
||||||
|
* @param to 替换字符串
|
||||||
|
* @return 替换结果
|
||||||
|
*/
|
||||||
|
public static String replaceAll(String string, char from, String to) {
|
||||||
|
return replaceAll(new StringBuilder(string), from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字符串替换,不需要正则的情况下
|
||||||
|
*
|
||||||
|
* @param sb 字符构造器
|
||||||
|
* @param from 被替换字符
|
||||||
|
* @param to 替换字符串
|
||||||
|
* @return 替换结果
|
||||||
|
*/
|
||||||
|
public static String replaceAll(StringBuilder sb, char from, String to) {
|
||||||
|
for (int i = 0; i < sb.length(); i++) {
|
||||||
|
if (sb.charAt(i) == from) {
|
||||||
|
sb.replace(i, ++i, to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
417
src/main/java/com/imyeyu/utils/Time.java
Normal file
417
src/main/java/com/imyeyu/utils/Time.java
Normal file
@ -0,0 +1,417 @@
|
|||||||
|
package com.imyeyu.utils;
|
||||||
|
|
||||||
|
import com.imyeyu.java.TimiJava;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 时间转换相关
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @version 2021-06-10 20:07
|
||||||
|
*/
|
||||||
|
public class Time {
|
||||||
|
|
||||||
|
// ---------- 格式化 ----------
|
||||||
|
|
||||||
|
/** 格式化 yy */
|
||||||
|
public static final SimpleDateFormat year = new SimpleDateFormat("yy");
|
||||||
|
|
||||||
|
/** 格式化 yyyy */
|
||||||
|
public static final SimpleDateFormat yearFull = new SimpleDateFormat("yyyy");
|
||||||
|
|
||||||
|
/** 格式化 M */
|
||||||
|
public static final SimpleDateFormat month = new SimpleDateFormat("M");
|
||||||
|
|
||||||
|
/** 格式化 MM */
|
||||||
|
public static final SimpleDateFormat monthFull = new SimpleDateFormat("MM");
|
||||||
|
|
||||||
|
/** 格式化 d */
|
||||||
|
public static final SimpleDateFormat day = new SimpleDateFormat("d");
|
||||||
|
|
||||||
|
/** 格式化 dd */
|
||||||
|
public static final SimpleDateFormat dayFull = new SimpleDateFormat("dd");
|
||||||
|
|
||||||
|
/** 格式化 HH */
|
||||||
|
public static final SimpleDateFormat hour = new SimpleDateFormat("HH");
|
||||||
|
|
||||||
|
/** 格式化 mm */
|
||||||
|
public static final SimpleDateFormat minute = new SimpleDateFormat("mm");
|
||||||
|
|
||||||
|
/** 格式化 ss */
|
||||||
|
public static final SimpleDateFormat second = new SimpleDateFormat("ss");
|
||||||
|
|
||||||
|
/** 格式化 hh:mm */
|
||||||
|
public static final SimpleDateFormat hhmm = new SimpleDateFormat("HH:mm");
|
||||||
|
|
||||||
|
/** 格式化 mm:ss */
|
||||||
|
public static final SimpleDateFormat mmss = new SimpleDateFormat("mm:ss");
|
||||||
|
|
||||||
|
/** 格式化 yyyyMMdd */
|
||||||
|
public static final SimpleDateFormat ymd = new SimpleDateFormat("yyyyMMdd");
|
||||||
|
|
||||||
|
/** 格式化 HH:mm:ss.SSS */
|
||||||
|
public static final SimpleDateFormat log = new SimpleDateFormat("[HH:mm:ss.SSS]");
|
||||||
|
|
||||||
|
/** 格式化 HH:mm:ss.SSS */
|
||||||
|
public static final SimpleDateFormat longLog = new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss.SSS]");
|
||||||
|
|
||||||
|
/** 格式化 yyyy-MM-dd */
|
||||||
|
public static final SimpleDateFormat date = new SimpleDateFormat("yyyy-MM-dd");
|
||||||
|
|
||||||
|
/** 格式化 HH:mm:ss */
|
||||||
|
public static final SimpleDateFormat time = new SimpleDateFormat("HH:mm:ss");
|
||||||
|
|
||||||
|
/** 格式化 yyyy-MM-dd HH:mm:ss */
|
||||||
|
public static final SimpleDateFormat dateTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
|
|
||||||
|
/** 格式化 yyyy-MM-dd'T'HH:mm:ss */
|
||||||
|
public static final SimpleDateFormat dateTimeT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
|
||||||
|
|
||||||
|
// ---------- 长整型时间戳 ----------
|
||||||
|
|
||||||
|
/** 1 秒时间戳 */
|
||||||
|
public static final long S = 1000;
|
||||||
|
|
||||||
|
/** 1 分钟时间戳 */
|
||||||
|
public static final long M = S * 60;
|
||||||
|
|
||||||
|
/** 1 小时时间戳 */
|
||||||
|
public static final long H = M * 60;
|
||||||
|
|
||||||
|
/** 1 天时间戳 */
|
||||||
|
public static final long D = H * 24;
|
||||||
|
|
||||||
|
// ---------- 整型时间戳 ----------
|
||||||
|
|
||||||
|
/** 1 秒时间戳(整型) */
|
||||||
|
public static final int SI = 1000;
|
||||||
|
|
||||||
|
/** 1 分钟时间戳(整型) */
|
||||||
|
public static final int MI = SI * 60;
|
||||||
|
|
||||||
|
/** 1 小时时间戳(整型) */
|
||||||
|
public static final int HI = MI * 60;
|
||||||
|
|
||||||
|
/** 1 天时间戳(整型) */
|
||||||
|
public static final int DI = HI * 24;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算两个时间戳精确的日期时间差
|
||||||
|
*
|
||||||
|
* @param begin 开始时间戳
|
||||||
|
* @param end 结束时间戳
|
||||||
|
* @return 时差
|
||||||
|
*/
|
||||||
|
public static Between between(long begin, long end) {
|
||||||
|
if (end < begin) {
|
||||||
|
throw new IllegalArgumentException("end time must greater than begin time:" + end + " < " + begin);
|
||||||
|
}
|
||||||
|
LocalDateTime ldtBegin = toLocalDateTime(begin);
|
||||||
|
LocalDateTime ldtEnd = toLocalDateTime(end);
|
||||||
|
|
||||||
|
Between between = new Between();
|
||||||
|
between.year = (int) ChronoUnit.YEARS.between(ldtBegin, ldtEnd);
|
||||||
|
between.month = (int) ChronoUnit.MONTHS.between(ldtBegin, ldtEnd) % 12;
|
||||||
|
between.day = (int) ChronoUnit.DAYS.between(ldtBegin.plusMonths(between.year * 12L + between.month), ldtEnd);
|
||||||
|
between.hour = (int) (ChronoUnit.HOURS.between(ldtBegin, ldtEnd) - ChronoUnit.DAYS.between(ldtBegin, ldtEnd) * 24);
|
||||||
|
between.minute = (int) ChronoUnit.MINUTES.between(ldtBegin, ldtEnd) % 60;
|
||||||
|
between.second = (int) ChronoUnit.SECONDS.between(ldtBegin, ldtEnd) % 60;
|
||||||
|
between.millis = (int) ((end - begin) % 1000);
|
||||||
|
return between;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取此刻毫秒
|
||||||
|
*
|
||||||
|
* @return 毫秒
|
||||||
|
*/
|
||||||
|
public static long now() {
|
||||||
|
return System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前时间 yyyy-MM-dd HH:mm:ss
|
||||||
|
*
|
||||||
|
* @return 当前时间
|
||||||
|
*/
|
||||||
|
public static String nowString() {
|
||||||
|
return toDateTime(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取昨天零时时间戳
|
||||||
|
*
|
||||||
|
* @return 昨天零时时间戳
|
||||||
|
*/
|
||||||
|
public static long yesterday() {
|
||||||
|
return today() - D;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取今天零时时间戳
|
||||||
|
*
|
||||||
|
* @return 今天零时时间戳
|
||||||
|
*/
|
||||||
|
public static long today() {
|
||||||
|
long now = now();
|
||||||
|
final long H8 = H * 8;
|
||||||
|
return ((now + H8 - (now + H8) % (H * 24)) - H8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取明天零时时间戳
|
||||||
|
*
|
||||||
|
* @return 明天零时时间戳
|
||||||
|
*/
|
||||||
|
public static long tomorrow() {
|
||||||
|
return today() + D;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转义为日期 yyyy-MM-dd
|
||||||
|
*
|
||||||
|
* @param unixTime 时间戳
|
||||||
|
* @return 日期字符串
|
||||||
|
*/
|
||||||
|
public static String toDate(long unixTime) {
|
||||||
|
return date.format(unixTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转义为时间 HH:mm:ss
|
||||||
|
*
|
||||||
|
* @param unixTime 时间戳
|
||||||
|
* @return 时间字符串
|
||||||
|
*/
|
||||||
|
public static String toTime(long unixTime) {
|
||||||
|
return time.format(unixTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转义为日期时间 yyyy-MM-dd HH:mm:ss
|
||||||
|
*
|
||||||
|
* @param unixTime 时间戳
|
||||||
|
* @return 日期时间字符串
|
||||||
|
*/
|
||||||
|
public static String toDateTime(long unixTime) {
|
||||||
|
return dateTime.format(unixTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转义为日期时间 yyyy-MM-dd HH:mm:ss
|
||||||
|
*
|
||||||
|
* @param date 时间对象
|
||||||
|
* @return 日期时间字符串
|
||||||
|
*/
|
||||||
|
public static String toDateTime(Date date) {
|
||||||
|
return dateTime.format(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 本地时间对象转时间戳(本地时区)
|
||||||
|
*
|
||||||
|
* @param date 本地日期对象
|
||||||
|
* @return 时间戳
|
||||||
|
*/
|
||||||
|
public static Long fromLocalDate(LocalDate date) {
|
||||||
|
if (date == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return date.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 本地时间对象转时间戳(本地时区)
|
||||||
|
*
|
||||||
|
* @param dateTime 本地日期对象
|
||||||
|
* @return 时间戳
|
||||||
|
*/
|
||||||
|
public static Long fromLocalDateTime(LocalDateTime dateTime) {
|
||||||
|
if (dateTime == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return dateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 时间戳转本地日期(本地时区)
|
||||||
|
*
|
||||||
|
* @param unixTime 时间戳
|
||||||
|
* @return 本地日期对象
|
||||||
|
*/
|
||||||
|
public static LocalDateTime toLocalDateTime(Long unixTime) {
|
||||||
|
if (unixTime == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Instant.ofEpochMilli(unixTime).atZone(ZoneId.systemDefault()).toLocalDateTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 媒体时间操作
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @version 2023-01-24 01:10
|
||||||
|
*/
|
||||||
|
public static class Media {
|
||||||
|
|
||||||
|
private static final Pattern PATTERN = Pattern.compile("(\\d*):?([0-5]\\d):([0-5]\\d)\\.?(\\d{1,3})?");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析字符串为毫秒,匹配 999:59:59.999 格式,兼容 999:59:59
|
||||||
|
*
|
||||||
|
* @param time 时间字符串
|
||||||
|
* @return 毫秒
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static long fromString(String time) {
|
||||||
|
if (TimiJava.isEmpty(time)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
Matcher matcher = PATTERN.matcher(time);
|
||||||
|
if (matcher.find()) {
|
||||||
|
long h = 0;
|
||||||
|
if (TimiJava.isNotEmpty(matcher.group(1))) {
|
||||||
|
h = Long.parseLong(matcher.group(1));
|
||||||
|
}
|
||||||
|
long m = Long.parseLong(matcher.group(2));
|
||||||
|
long s = Long.parseLong(matcher.group(3));
|
||||||
|
long ms = 0;
|
||||||
|
if (TimiJava.isNotEmpty(matcher.group(4))) {
|
||||||
|
String gms = matcher.group(4);
|
||||||
|
ms = Long.parseLong(gms);
|
||||||
|
if (gms.length() == 2) {
|
||||||
|
ms *= 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return h * Time.H + m * Time.M + s * Time.S + ms;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 秒转媒体时间,00:00:00
|
||||||
|
*
|
||||||
|
* @param second 秒
|
||||||
|
* @return 时间字符串
|
||||||
|
*/
|
||||||
|
public static String toString(double second) {
|
||||||
|
return toString((int) second);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 秒转媒体时间,00:00:00
|
||||||
|
*
|
||||||
|
* @param second 秒
|
||||||
|
* @return 时间字符串
|
||||||
|
*/
|
||||||
|
public static String toString(int second) {
|
||||||
|
int h = second / 60 / 60;
|
||||||
|
if (0 < h) {
|
||||||
|
return String.format("%d:%02d:%02d", h, second / 60 - h * 60, second % 60);
|
||||||
|
} else {
|
||||||
|
return String.format("%02d:%02d", second / 60, second % 60);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 毫秒转媒体时间,音视频播放时间,00:00:00.000
|
||||||
|
*
|
||||||
|
* @param millis 毫秒
|
||||||
|
* @return 时间字符串
|
||||||
|
*/
|
||||||
|
public static String toString(long millis) {
|
||||||
|
long s = millis / 1000;
|
||||||
|
long h = s / 60 / 60;
|
||||||
|
return String.format("%d:%02d:%02d.%03d", h, s / 60 - h * 60, s % 60, millis % 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 时差
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @version 2022-10-12 14:46
|
||||||
|
*/
|
||||||
|
public static class Between {
|
||||||
|
|
||||||
|
int year;
|
||||||
|
int month;
|
||||||
|
int day;
|
||||||
|
int hour;
|
||||||
|
int minute;
|
||||||
|
int second;
|
||||||
|
int millis;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取年数
|
||||||
|
*
|
||||||
|
* @return 年数
|
||||||
|
*/
|
||||||
|
public int getYear() {
|
||||||
|
return year;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取月数
|
||||||
|
*
|
||||||
|
* @return 月数
|
||||||
|
*/
|
||||||
|
public int getMonth() {
|
||||||
|
return month;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取天数
|
||||||
|
*
|
||||||
|
* @return 天数
|
||||||
|
*/
|
||||||
|
public int getDay() {
|
||||||
|
return day;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取小时
|
||||||
|
*
|
||||||
|
* @return 小时
|
||||||
|
*/
|
||||||
|
public int getHour() {
|
||||||
|
return hour;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取分钟
|
||||||
|
*
|
||||||
|
* @return 分钟
|
||||||
|
*/
|
||||||
|
public int getMinute() {
|
||||||
|
return minute;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取秒
|
||||||
|
*
|
||||||
|
* @return 秒
|
||||||
|
*/
|
||||||
|
public int getSecond() {
|
||||||
|
return second;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取毫秒
|
||||||
|
*
|
||||||
|
* @return 毫秒
|
||||||
|
*/
|
||||||
|
public int getMillis() {
|
||||||
|
return millis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
40
src/test/java/test/TestAsciiTable.java
Normal file
40
src/test/java/test/TestAsciiTable.java
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package test;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import com.imyeyu.utils.AsciiTable;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2024-12-18 12:47
|
||||||
|
*/
|
||||||
|
public class TestAsciiTable {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTable() throws Exception {
|
||||||
|
List<Item> dataList = new ArrayList<>();
|
||||||
|
dataList.add(new Item("r1 f1 value", "r1 f2 value"));
|
||||||
|
dataList.add(new Item("r2 f1 value", "r2 f2 value"));
|
||||||
|
dataList.add(new Item("r3 f1 value", "r3 f2 value333333"));
|
||||||
|
dataList.add(new Item("r4 f1 value", "r4 f2 value"));
|
||||||
|
|
||||||
|
AsciiTable<Item> table = new AsciiTable<>();
|
||||||
|
table.addHeader("f1 bbbbbbbbbbbbbbbbbbbbbbbbbbbb", "f1");
|
||||||
|
table.addHeader("f2");
|
||||||
|
String result = table.render(dataList);
|
||||||
|
System.out.println(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class Item {
|
||||||
|
|
||||||
|
String f1;
|
||||||
|
|
||||||
|
String f2;
|
||||||
|
}
|
||||||
|
}
|
||||||
47
src/test/java/test/TestText.java
Normal file
47
src/test/java/test/TestText.java
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package test;
|
||||||
|
|
||||||
|
import com.imyeyu.utils.StringInterpolator;
|
||||||
|
import com.imyeyu.utils.Text;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2024-09-04 17:50
|
||||||
|
*/
|
||||||
|
public class TestText {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStringInterpolator() {
|
||||||
|
String template = "test ${item.name} deep arg";
|
||||||
|
|
||||||
|
StringInterpolator interpolator = new StringInterpolator(StringInterpolator.DOLLAR_OBJ);
|
||||||
|
|
||||||
|
Map<String, Object> argsMap = new HashMap<>();
|
||||||
|
Item item = new Item();
|
||||||
|
item.name = "deepName";
|
||||||
|
argsMap.put("item", item);
|
||||||
|
|
||||||
|
System.out.println(interpolator.inject(template, argsMap));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCamelToUnderline() {
|
||||||
|
String str = "";
|
||||||
|
String result = "";
|
||||||
|
assert Text.camelCase2underscore(str).equals(result);
|
||||||
|
str = "hello";
|
||||||
|
result = "hello";
|
||||||
|
assert Text.camelCase2underscore(str).equals(result);
|
||||||
|
str = "helloWorldTest";
|
||||||
|
result = "hello_world_test";
|
||||||
|
assert Text.camelCase2underscore(str).equals(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Item {
|
||||||
|
|
||||||
|
String name;
|
||||||
|
}
|
||||||
|
}
|
||||||
8
src/test/java/test/TestTime.java
Normal file
8
src/test/java/test/TestTime.java
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2024-12-19 22:10
|
||||||
|
*/
|
||||||
|
public class TestTime {
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user