chore(jcef): 更新缓存日志文件的时间戳和进程ID

- 更新了多个JCEF缓存目录下的LOG文件时间戳
- 更新了chrome_debug.log中的时间戳和进程ID
- 更新了Extension State、GCM Store、Local Storage等目录的LOG文件
- 保持了原有的缓存数据结构和功能
- 修正了Session Storage目录中的时间戳信息
- 更新了shared_proto_db和Site Characteristics Database的元数据日志
This commit is contained in:
2026-01-02 17:49:28 +08:00
parent 8de2b0f2fe
commit 752850b936
37 changed files with 732 additions and 464 deletions

View File

@@ -1,3 +1,3 @@
#Current Loaded Language #Current Loaded Language
#Fri Jan 02 17:08:53 CST 2026 #Fri Jan 02 17:46:22 CST 2026
loadedLanguage=system\:zh_CN loadedLanguage=system\:zh_CN

Binary file not shown.

View File

@@ -1,3 +1,3 @@
2025/12/28-14:50:54.309 d58 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Extension State/MANIFEST-000001 2026/01/02-17:46:55.167 52c Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Extension State/MANIFEST-000001
2025/12/28-14:50:54.309 d58 Recovering log #3 2026/01/02-17:46:55.168 52c Recovering log #3
2025/12/28-14:50:54.312 d58 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Extension State/000003.log 2026/01/02-17:46:55.169 52c Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Extension State/000003.log

View File

@@ -1,3 +1,3 @@
2025/12/28-14:19:49.868 bcc Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Extension State/MANIFEST-000001 2025/12/28-14:50:54.309 d58 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Extension State/MANIFEST-000001
2025/12/28-14:19:49.869 bcc Recovering log #3 2025/12/28-14:50:54.309 d58 Recovering log #3
2025/12/28-14:19:49.881 bcc Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Extension State/000003.log 2025/12/28-14:50:54.312 d58 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Extension State/000003.log

View File

@@ -1,3 +1,3 @@
2025/12/28-14:50:57.787 24fc Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\GCM Store\Encryption/MANIFEST-000001 2026/01/02-17:46:58.729 3d98 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\GCM Store\Encryption/MANIFEST-000001
2025/12/28-14:50:57.787 24fc Recovering log #3 2026/01/02-17:46:58.730 3d98 Recovering log #3
2025/12/28-14:50:57.788 24fc Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\GCM Store\Encryption/000003.log 2026/01/02-17:46:58.730 3d98 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\GCM Store\Encryption/000003.log

View File

@@ -1,3 +1,3 @@
2025/12/28-14:19:54.454 4760 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\GCM Store\Encryption/MANIFEST-000001 2025/12/28-14:50:57.787 24fc Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\GCM Store\Encryption/MANIFEST-000001
2025/12/28-14:19:54.457 4760 Recovering log #3 2025/12/28-14:50:57.787 24fc Recovering log #3
2025/12/28-14:19:54.458 4760 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\GCM Store\Encryption/000003.log 2025/12/28-14:50:57.788 24fc Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\GCM Store\Encryption/000003.log

Binary file not shown.

View File

@@ -1,3 +1,3 @@
2025/12/28-14:50:54.386 2b54 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Local Storage\leveldb/MANIFEST-000001 2026/01/02-17:46:55.667 31c0 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Local Storage\leveldb/MANIFEST-000001
2025/12/28-14:50:54.399 2b54 Recovering log #44 2026/01/02-17:46:55.683 31c0 Recovering log #44
2025/12/28-14:50:54.404 2b54 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Local Storage\leveldb/000044.log 2026/01/02-17:46:55.687 31c0 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Local Storage\leveldb/000044.log

View File

@@ -1,3 +1,3 @@
2025/12/28-14:19:49.965 2d90 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Local Storage\leveldb/MANIFEST-000001 2025/12/28-14:50:54.386 2b54 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Local Storage\leveldb/MANIFEST-000001
2025/12/28-14:19:49.981 2d90 Recovering log #44 2025/12/28-14:50:54.399 2b54 Recovering log #44
2025/12/28-14:19:49.986 2d90 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Local Storage\leveldb/000044.log 2025/12/28-14:50:54.404 2b54 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Local Storage\leveldb/000044.log

View File

@@ -1 +1 @@
{"net":{"http_server_properties":{"broken_alternative_services":[{"anonymization":["JAAAAB0AAABodHRwczovL3VwZGF0ZS5nb29nbGVhcGlzLmNvbQAAAA==",false],"broken_count":10,"broken_until":"1767056398","host":"update.googleapis.com","port":443,"protocol_str":"quic"},{"anonymization":["DAAAAAcAAABmaWxlOi8vAA==",false],"broken_count":1,"broken_until":"1766904958","host":"fonts.googleapis.com","port":443,"protocol_str":"quic"}],"servers":[{"anonymization":["DAAAAAcAAABmaWxlOi8vAA==",false],"server":"https://code.jquery.com","supports_spdy":true},{"alternative_service":[{"advertised_alpns":["h3"],"expiration":"13413970258167864","port":443,"protocol_str":"quic"}],"anonymization":["JAAAAB0AAABodHRwczovL3VwZGF0ZS5nb29nbGVhcGlzLmNvbQAAAA==",false],"server":"https://update.googleapis.com","supports_spdy":true},{"alternative_service":[{"advertised_alpns":["h3"],"expiration":"13413970255009851","port":443,"protocol_str":"quic"}],"anonymization":["DAAAAAcAAABmaWxlOi8vAA==",false],"server":"https://fonts.googleapis.com","supports_spdy":true},{"anonymization":["DAAAAAcAAABmaWxlOi8vAA==",false],"server":"https://cdn.jsdelivr.net","supports_spdy":true}],"version":5},"network_qualities":{"CAESABiAgICA+P////8B":"4G"}}} {"net":{"http_server_properties":{"broken_alternative_services":[{"anonymization":["JAAAAB0AAABodHRwczovL3VwZGF0ZS5nb29nbGVhcGlzLmNvbQAAAA==",false],"broken_count":10,"host":"update.googleapis.com","port":443,"protocol_str":"quic"},{"anonymization":["DAAAAAcAAABmaWxlOi8vAA==",false],"broken_count":1,"host":"fonts.googleapis.com","port":443,"protocol_str":"quic"}],"servers":[{"anonymization":["DAAAAAcAAABmaWxlOi8vAA==",false],"server":"https://code.jquery.com","supports_spdy":true},{"anonymization":["DAAAAAcAAABmaWxlOi8vAA==",false],"server":"https://fonts.googleapis.com","supports_spdy":true},{"anonymization":["DAAAAAcAAABmaWxlOi8vAA==",false],"server":"https://cdn.jsdelivr.net","supports_spdy":true},{"alternative_service":[{"advertised_alpns":["h3"],"expiration":"13414412819106787","port":443,"protocol_str":"quic"}],"anonymization":["JAAAAB0AAABodHRwczovL3VwZGF0ZS5nb29nbGVhcGlzLmNvbQAAAA==",false],"server":"https://update.googleapis.com","supports_spdy":true}],"version":5},"network_qualities":{"CAESABiAgICA+P////8B":"4G"}}}

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,3 @@
2025/12/28-14:51:01.393 2160 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Session Storage/MANIFEST-000001 2026/01/02-17:48:16.445 31c0 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Session Storage/MANIFEST-000001
2025/12/28-14:51:01.395 2160 Recovering log #12 2026/01/02-17:48:16.446 31c0 Recovering log #12
2025/12/28-14:51:01.401 2160 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Session Storage/000012.log 2026/01/02-17:48:16.449 31c0 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Session Storage/000012.log

View File

@@ -1,3 +1,3 @@
2025/12/20-18:28:04.218 294 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Session Storage/MANIFEST-000001 2025/12/28-14:51:01.393 2160 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Session Storage/MANIFEST-000001
2025/12/20-18:28:04.219 294 Recovering log #12 2025/12/28-14:51:01.395 2160 Recovering log #12
2025/12/20-18:28:04.222 294 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Session Storage/000012.log 2025/12/28-14:51:01.401 2160 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Session Storage/000012.log

View File

@@ -1,3 +1,3 @@
2025/12/28-14:50:54.257 186c Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Site Characteristics Database/MANIFEST-000001 2026/01/02-17:46:55.084 3b7c Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Site Characteristics Database/MANIFEST-000001
2025/12/28-14:50:54.263 186c Recovering log #3 2026/01/02-17:46:55.114 3b7c Recovering log #3
2025/12/28-14:50:54.264 186c Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Site Characteristics Database/000003.log 2026/01/02-17:46:55.115 3b7c Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Site Characteristics Database/000003.log

View File

@@ -1,3 +1,3 @@
2025/12/28-14:19:49.782 193c Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Site Characteristics Database/MANIFEST-000001 2025/12/28-14:50:54.257 186c Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Site Characteristics Database/MANIFEST-000001
2025/12/28-14:19:49.813 193c Recovering log #3 2025/12/28-14:50:54.263 186c Recovering log #3
2025/12/28-14:19:49.814 193c Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Site Characteristics Database/000003.log 2025/12/28-14:50:54.264 186c Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Site Characteristics Database/000003.log

View File

@@ -1,3 +1,3 @@
2025/12/28-14:50:54.253 37d4 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Sync Data\LevelDB/MANIFEST-000001 2026/01/02-17:46:55.071 52c Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Sync Data\LevelDB/MANIFEST-000001
2025/12/28-14:50:54.263 37d4 Recovering log #3 2026/01/02-17:46:55.115 52c Recovering log #3
2025/12/28-14:50:54.264 37d4 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Sync Data\LevelDB/000003.log 2026/01/02-17:46:55.115 52c Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Sync Data\LevelDB/000003.log

View File

@@ -1,3 +1,3 @@
2025/12/28-14:19:49.766 bcc Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Sync Data\LevelDB/MANIFEST-000001 2025/12/28-14:50:54.253 37d4 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Sync Data\LevelDB/MANIFEST-000001
2025/12/28-14:19:49.813 bcc Recovering log #3 2025/12/28-14:50:54.263 37d4 Recovering log #3
2025/12/28-14:19:49.814 bcc Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Sync Data\LevelDB/000003.log 2025/12/28-14:50:54.264 37d4 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\Sync Data\LevelDB/000003.log

View File

@@ -1,3 +1,3 @@
2025/12/28-14:50:54.299 22b4 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db/MANIFEST-000001 2026/01/02-17:46:55.164 109c Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db/MANIFEST-000001
2025/12/28-14:50:54.299 22b4 Recovering log #19 2026/01/02-17:46:55.164 109c Recovering log #19
2025/12/28-14:50:54.301 22b4 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db/000019.log 2026/01/02-17:46:55.169 109c Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db/000019.log

View File

@@ -1,3 +1,3 @@
2025/12/28-14:19:49.860 6ac Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db/MANIFEST-000001 2025/12/28-14:50:54.299 22b4 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db/MANIFEST-000001
2025/12/28-14:19:49.861 6ac Recovering log #19 2025/12/28-14:50:54.299 22b4 Recovering log #19
2025/12/28-14:19:49.866 6ac Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db/000019.log 2025/12/28-14:50:54.301 22b4 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db/000019.log

View File

@@ -1,3 +1,3 @@
2025/12/28-14:50:54.290 22b4 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db\metadata/MANIFEST-000001 2026/01/02-17:46:55.156 109c Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db\metadata/MANIFEST-000001
2025/12/28-14:50:54.291 22b4 Recovering log #3 2026/01/02-17:46:55.156 109c Recovering log #3
2025/12/28-14:50:54.291 22b4 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db\metadata/000003.log 2026/01/02-17:46:55.157 109c Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db\metadata/000003.log

View File

@@ -1,3 +1,3 @@
2025/12/28-14:19:49.851 6ac Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db\metadata/MANIFEST-000001 2025/12/28-14:50:54.290 22b4 Reusing MANIFEST C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db\metadata/MANIFEST-000001
2025/12/28-14:19:49.852 6ac Recovering log #3 2025/12/28-14:50:54.291 22b4 Recovering log #3
2025/12/28-14:19:49.854 6ac Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db\metadata/000003.log 2025/12/28-14:50:54.291 22b4 Reusing old log C:\Users\Administrator\MCreatorWorkspaces\AxisInnovatorsBox\library\jcef\cache\Default\shared_proto_db\metadata/000003.log

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"user_experience_metrics.stability.exited_cleanly":false,"variations_crash_streak":594} {"user_experience_metrics.stability.exited_cleanly":false,"variations_crash_streak":595}

View File

@@ -1,6 +1,4 @@
[12284:16860:1228/145054.244:WARNING:account_consistency_mode_manager.cc(77)] Desktop Identity Consistency cannot be enabled as no OAuth client ID and client secret have been configured. [4944:21460:0102/174655.052:WARNING:account_consistency_mode_manager.cc(77)] Desktop Identity Consistency cannot be enabled as no OAuth client ID and client secret have been configured.
[12284:16860:1228/145054.280:WARNING:extension_service.cc(2065)] Found external version of extension ncennffkjdiamlpmcbajkmaiiiddgioothat is older than current version. Current version is: 3.52.14. New version is: 3.52.5. Keeping current version. [4944:21460:0102/174655.144:WARNING:extension_service.cc(2065)] Found external version of extension ncennffkjdiamlpmcbajkmaiiiddgioothat is older than current version. Current version is: 3.52.14. New version is: 3.52.5. Keeping current version.
[8880:16484:1228/145054.563:WARNING:viz_main_impl.cc(85)] VizNullHypothesis is disabled (not a warning) [5764:9508:0102/174655.913:WARNING:viz_main_impl.cc(85)] VizNullHypothesis is disabled (not a warning)
[13676:18520:1228/145254.442:WARNING:viz_main_impl.cc(85)] VizNullHypothesis is disabled (not a warning) [5060:21232:0102/174855.188:WARNING:viz_main_impl.cc(85)] VizNullHypothesis is disabled (not a warning)
[14320:9004:1228/145354.607:ERROR:gpu_blocklist.cc(71)] Unable to get gpu adapter
[12284:16860:1228/145354.607:ERROR:service_client.cc(36)] Unexpected on_device_model service disconnect: The device's GPU is not supported.

Binary file not shown.

Binary file not shown.

View File

@@ -1,6 +1,6 @@
#FileLock #FileLock
#Sun Dec 28 14:52:28 CST 2025 #Fri Jan 02 17:46:53 CST 2026
hostName=192.168.116.1 hostName=192.168.116.1
id=19b63bad12a8483d76995f9c5100715a8b474ee84cc id=19b7e1a4b3ed8755d1cb7723021bb5b3ae83bb80d46
method=file method=file
server=192.168.116.1\:64976 server=192.168.116.1\:64658

View File

@@ -839,7 +839,7 @@ public class AxisInnovatorsBox {
UIManager.getSystemLookAndFeelClassName(), UIManager.getSystemLookAndFeelClassName(),
LanguageManager.getLoadedLanguages().getText("default_theme.system.topicName"), LanguageManager.getLoadedLanguages().getText("default_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("default_theme.default.tip"), LanguageManager.getLoadedLanguages().getText("default_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64), LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
"system:default_theme", "system:default_theme",
isDarkMode isDarkMode
); );
@@ -849,7 +849,7 @@ public class AxisInnovatorsBox {
"javax.swing.plaf.metal.MetalLookAndFeel", "javax.swing.plaf.metal.MetalLookAndFeel",
LanguageManager.getLoadedLanguages().getText("metal_theme.system.topicName"), LanguageManager.getLoadedLanguages().getText("metal_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("metal_theme.default.tip"), LanguageManager.getLoadedLanguages().getText("metal_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64), LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
"system:metal_theme", "system:metal_theme",
false false
); );
@@ -859,7 +859,7 @@ public class AxisInnovatorsBox {
"com.sun.java.swing.plaf.motif.MotifLookAndFeel", "com.sun.java.swing.plaf.motif.MotifLookAndFeel",
LanguageManager.getLoadedLanguages().getText("motif_theme.system.topicName"), LanguageManager.getLoadedLanguages().getText("motif_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("motif_theme.default.tip"), LanguageManager.getLoadedLanguages().getText("motif_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64), LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
"system:motif_theme", "system:motif_theme",
false false
); );
@@ -870,7 +870,7 @@ public class AxisInnovatorsBox {
new com.formdev.flatlaf.FlatLightLaf(), new com.formdev.flatlaf.FlatLightLaf(),
LanguageManager.getLoadedLanguages().getText("flatLight_theme.system.topicName"), LanguageManager.getLoadedLanguages().getText("flatLight_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("flatLight_theme.default.tip"), LanguageManager.getLoadedLanguages().getText("flatLight_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64), LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
"system:flatLight_theme", "system:flatLight_theme",
false false
); );
@@ -880,7 +880,7 @@ public class AxisInnovatorsBox {
new com.formdev.flatlaf.FlatDarkLaf(), new com.formdev.flatlaf.FlatDarkLaf(),
LanguageManager.getLoadedLanguages().getText("flatDark_theme.system.topicName"), LanguageManager.getLoadedLanguages().getText("flatDark_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("flatDark_theme.default.tip"), LanguageManager.getLoadedLanguages().getText("flatDark_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64), LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
"system:flatDark_theme", "system:flatDark_theme",
true true
); );
@@ -890,7 +890,7 @@ public class AxisInnovatorsBox {
new com.formdev.flatlaf.FlatIntelliJLaf(), new com.formdev.flatlaf.FlatIntelliJLaf(),
LanguageManager.getLoadedLanguages().getText("flatIntelliJ_theme.system.topicName"), LanguageManager.getLoadedLanguages().getText("flatIntelliJ_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("flatIntelliJ_theme.default.tip"), LanguageManager.getLoadedLanguages().getText("flatIntelliJ_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64), LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
"system:flatIntelliJ_theme", "system:flatIntelliJ_theme",
false false
); );
@@ -900,7 +900,7 @@ public class AxisInnovatorsBox {
new com.formdev.flatlaf.FlatDarculaLaf(), new com.formdev.flatlaf.FlatDarculaLaf(),
LanguageManager.getLoadedLanguages().getText("flatDarcula_theme.system.topicName"), LanguageManager.getLoadedLanguages().getText("flatDarcula_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("flatDarcula_theme.default.tip"), LanguageManager.getLoadedLanguages().getText("flatDarcula_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64), LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
"system:flatDarcula_theme", "system:flatDarcula_theme",
true true
); );
@@ -910,7 +910,7 @@ public class AxisInnovatorsBox {
new FlatMacLightLaf(), new FlatMacLightLaf(),
LanguageManager.getLoadedLanguages().getText("flatMacLight_theme.system.topicName"), LanguageManager.getLoadedLanguages().getText("flatMacLight_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("flatMacLight_theme.default.tip"), LanguageManager.getLoadedLanguages().getText("flatMacLight_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64), LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
"system:flatMacLight_theme", "system:flatMacLight_theme",
false false
); );
@@ -920,7 +920,7 @@ public class AxisInnovatorsBox {
new FlatMacDarkLaf(), new FlatMacDarkLaf(),
LanguageManager.getLoadedLanguages().getText("flatMacDark_theme.system.topicName"), LanguageManager.getLoadedLanguages().getText("flatMacDark_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("flatMacDark_theme.default.tip"), LanguageManager.getLoadedLanguages().getText("flatMacDark_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64), LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
"system:flatMacDark_theme", "system:flatMacDark_theme",
true true
); );
@@ -929,7 +929,7 @@ public class AxisInnovatorsBox {
new MaterialLookAndFeel(new JMarsDarkTheme()), new MaterialLookAndFeel(new JMarsDarkTheme()),
LanguageManager.getLoadedLanguages().getText("mars_dark_theme.system.topicName"), LanguageManager.getLoadedLanguages().getText("mars_dark_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("mars_dark_theme.default.tip"), LanguageManager.getLoadedLanguages().getText("mars_dark_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64), LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
"system:mars_dark_theme", "system:mars_dark_theme",
true true
); );
@@ -938,7 +938,7 @@ public class AxisInnovatorsBox {
new MaterialLookAndFeel(new MaterialLiteTheme()), new MaterialLookAndFeel(new MaterialLiteTheme()),
LanguageManager.getLoadedLanguages().getText("material_lite_theme.system.topicName"), LanguageManager.getLoadedLanguages().getText("material_lite_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("material_lite_theme.default.tip"), LanguageManager.getLoadedLanguages().getText("material_lite_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64), LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
"system:material_lite_theme", "system:material_lite_theme",
false false
); );
@@ -947,7 +947,7 @@ public class AxisInnovatorsBox {
new MaterialLookAndFeel(new MaterialOceanicTheme()), new MaterialLookAndFeel(new MaterialOceanicTheme()),
LanguageManager.getLoadedLanguages().getText("material_oceanic_theme.system.topicName"), LanguageManager.getLoadedLanguages().getText("material_oceanic_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("material_oceanic_theme.default.tip"), LanguageManager.getLoadedLanguages().getText("material_oceanic_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64), LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
"system:material_oceanic_theme", "system:material_oceanic_theme",
true true
); );

View File

@@ -1,31 +1,35 @@
package com.axis.innovators.box.plugins; package com.axis.innovators.box.plugins;
import java.io.BufferedInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
/** /**
* 自定义类加载器装载CorePlugins * 自定义类加载器装载CorePlugins
* @author tzdwindows 7 * 优化:增加缓冲读取,优化集合初始化
*/ */
public class BoxClassLoader extends URLClassLoader { public class BoxClassLoader extends URLClassLoader {
private static final List<IClassTransformer> CLASS_TRANSFORMS = new CopyOnWriteArrayList<>(); private static final List<IClassTransformer> CLASS_TRANSFORMS = new CopyOnWriteArrayList<>();
private static final List<String> CLASS_BLACKLIST = new ArrayList<>(); // 使用 CopyOnWriteArrayList 适合读多写少的场景
private static final List<String> CLASS_LOADING_LIST = Collections.synchronizedList(new ArrayList<>()); private static final List<String> CLASS_BLACKLIST = new CopyOnWriteArrayList<>();
// Loading list 仅用于防止循环依赖,普通同步 List 即可
private static final List<String> CLASS_LOADING_LIST = Collections.synchronizedList(new java.util.ArrayList<>(50));
private static final List<Class<?>> CLASS_LOADING_LIST_OBJECT = new CopyOnWriteArrayList<>(); private static final List<Class<?>> CLASS_LOADING_LIST_OBJECT = new CopyOnWriteArrayList<>();
static { static {
Collections.addAll(CLASS_BLACKLIST, // 批量添加,减少扩容开销
List<String> blacklist = java.util.Arrays.asList(
"java.", "javax.", "sun.", "com.sun.", "jdk.", "java.", "javax.", "sun.", "com.sun.", "jdk.",
"org.xml.", "org.w3c.", "org.apache.", "org.xml.", "org.w3c.", "org.apache.",
"javax.management.", "javax.swing." "javax.management.", "javax.swing.",
, "javafx.","org.jnativehook.","com.dustinredmond." "javafx.", "org.jnativehook.", "com.dustinredmond."
); );
CLASS_BLACKLIST.addAll(blacklist);
} }
public BoxClassLoader(ClassLoader parent) { public BoxClassLoader(ClassLoader parent) {
@@ -60,7 +64,7 @@ public class BoxClassLoader extends URLClassLoader {
resolveClass(c); resolveClass(c);
} }
return c; return c;
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException | SecurityException e) {
return super.loadClass(name, resolve); return super.loadClass(name, resolve);
} }
} }
@@ -68,7 +72,8 @@ public class BoxClassLoader extends URLClassLoader {
@Override @Override
protected Class<?> findClass(String name) throws ClassNotFoundException { protected Class<?> findClass(String name) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) { // 移除 synchronized (getClassLoadingLock(name)),因为 loadClass 已经锁住了
// 防止循环依赖
if (CLASS_LOADING_LIST.contains(name)) { if (CLASS_LOADING_LIST.contains(name)) {
throw new ClassCircularityError(name + " circular loading detected"); throw new ClassCircularityError(name + " circular loading detected");
} }
@@ -76,32 +81,39 @@ public class BoxClassLoader extends URLClassLoader {
CLASS_LOADING_LIST.add(name); CLASS_LOADING_LIST.add(name);
try { try {
byte[] clazzByte = getClassBytes(name); byte[] clazzByte = getClassBytes(name);
// 只有当存在转换器时才遍历
if (!CLASS_TRANSFORMS.isEmpty()) {
for (IClassTransformer transformer : CLASS_TRANSFORMS) { for (IClassTransformer transformer : CLASS_TRANSFORMS) {
byte[] transformed = transformer.transform(name, transformer.getClass().getName(), clazzByte); byte[] transformed = transformer.transform(name, transformer.getClass().getName(), clazzByte);
if (transformed != null) clazzByte = transformed; if (transformed != null) clazzByte = transformed;
} }
}
Class<?> clazz = defineClass(name, clazzByte, 0, clazzByte.length); Class<?> clazz = defineClass(name, clazzByte, 0, clazzByte.length);
CLASS_LOADING_LIST_OBJECT.add(clazz); CLASS_LOADING_LIST_OBJECT.add(clazz);
return clazz; return clazz;
} catch (IOException e) { } catch (IOException e) {
throw new ClassNotFoundException("Class byte loading failed", e); throw new ClassNotFoundException("Class byte loading failed: " + name, e);
} finally { } finally {
CLASS_LOADING_LIST.remove(name); CLASS_LOADING_LIST.remove(name);
} }
} }
}
private byte[] getClassBytes(String className) throws IOException, ClassNotFoundException { private byte[] getClassBytes(String className) throws IOException, ClassNotFoundException {
String path = className.replace('.', '/') + ".class"; String path = className.replace('.', '/') + ".class";
try (InputStream is = getResourceAsStream(path)) { try (InputStream is = getResourceAsStream(path)) {
if (is == null) throw new ClassNotFoundException(className); if (is == null) throw new ClassNotFoundException(className);
return is.readAllBytes(); try (BufferedInputStream bis = new BufferedInputStream(is)) {
return bis.readAllBytes();
}
} }
} }
private boolean isBlacklisted(String className) { private boolean isBlacklisted(String className) {
return CLASS_BLACKLIST.stream().anyMatch(className::startsWith); for (String s : CLASS_BLACKLIST) {
if (className.startsWith(s)) return true;
}
return false;
} }
public static void addClassTransformer(IClassTransformer transformer) { public static void addClassTransformer(IClassTransformer transformer) {

View File

@@ -1,8 +1,10 @@
package com.axis.innovators.box.plugins; package com.axis.innovators.box.plugins;
import java.net.URL;
import java.util.List; import java.util.List;
/** /**
* 插件描述符
* @author tzdwindows 7 * @author tzdwindows 7
*/ */
public class PluginDescriptor { public class PluginDescriptor {
@@ -15,21 +17,93 @@ public class PluginDescriptor {
private String transformers; private String transformers;
private String registrationName; private String registrationName;
/**
* 核心字段:插件的类加载器
* 保存这个是为了以后调用 classLoader.getResource("images/logo.png")
* 从而准确地从该插件的 JAR 包中获取资源,互不干扰。
*/
private ClassLoader classLoader;
/**
* 核心字段:插件 JAR 包的物理 URL
* 格式通常为: file:/C:/path/to/plugin.jar
* 用于构建 jar:file: 协议的 URL (常用于 JavaFX Image 或 FXML 加载)
*/
private URL sourceLocation;
// Getters and Setters // Getters and Setters
public String getId() { return id; } public String getId() { return id; }
public void setId(String id) { this.id = id; } public void setId(String id) { this.id = id; }
public String getName() { return name; } public String getName() { return name; }
public void setName(String name) { this.name = name; } public void setName(String name) { this.name = name; }
public List<String> getSupportedVersions() { return supportedVersions; } public List<String> getSupportedVersions() { return supportedVersions; }
public void setSupportedVersions(List<String> supportedVersions) { this.supportedVersions = supportedVersions; } public void setSupportedVersions(List<String> supportedVersions) { this.supportedVersions = supportedVersions; }
public String getIcon() { return icon; } public String getIcon() { return icon; }
public void setIcon(String icon) { this.icon = icon; } public void setIcon(String icon) { this.icon = icon; }
public String getDescription() { return description; } public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; } public void setDescription(String description) { this.description = description; }
public Object getInstance() { return instance; } public Object getInstance() { return instance; }
public void setInstance(Object instance) { this.instance = instance; } public void setInstance(Object instance) { this.instance = instance; }
public String getTransformers() {return transformers;}
public void setTransformers(String transformers) {this.transformers = transformers;} public String getTransformers() { return transformers; }
public String getRegistrationName() {return registrationName;} public void setTransformers(String transformers) { this.transformers = transformers; }
public void setRegistrationName(String registrationName) {this.registrationName = registrationName;}
public String getRegistrationName() { return registrationName; }
public void setRegistrationName(String registrationName) { this.registrationName = registrationName; }
/**
* 获取用于加载此插件资源的 ClassLoader
*/
public ClassLoader getClassLoader() {
return classLoader;
}
public void setClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
/**
* 获取插件 JAR 文件的物理路径 URL
*/
public URL getSourceLocation() {
return sourceLocation;
}
public void setSourceLocation(URL sourceLocation) {
this.sourceLocation = sourceLocation;
}
// --- 辅助方法:便捷加载资源 ---
/**
* 这是一个便捷方法,用于直接从当前插件的 Resources 中获取资源 URL。
* 哪怕多个插件有同名文件(如 config.png使用此方法也只会加载当前插件内的那个。
*
* @param path 资源路径,例如 "assets/icon.png" (不需要以 / 开头)
* @return 资源的完整 URL如果找不到则返回 null
*/
public URL getResource(String path) {
if (classLoader != null) {
return classLoader.getResource(path);
}
return null;
}
/**
* 获取资源的输入流(用于读取配置文件等)
* @param path 资源路径
* @return 输入流
*/
public java.io.InputStream getResourceAsStream(String path) {
if (classLoader != null) {
return classLoader.getResourceAsStream(path);
}
return null;
}
} }

View File

@@ -6,96 +6,185 @@ import com.axis.innovators.box.tools.FolderCreator;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import java.io.File; import java.io.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.jar.Attributes; import java.util.jar.Attributes;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
import java.util.jar.JarFile; import java.util.jar.JarFile;
/** /**
* 插件加载器 * 插件加载器 - 性能优化版 (修复 SystemClassLoader 启动崩溃问题)
* @author tzdwindows 7
*/ */
public class PluginLoader { public class PluginLoader {
private static Logger logger; // 移除 static 初始化,防止在 SystemClassLoader 构造期间触发 Log4j 初始化
private static volatile Logger logger;
public static final String PLUGIN_PATH = FolderCreator.getPluginFolder(); public static final String PLUGIN_PATH = FolderCreator.getPluginFolder();
private static final List<PluginDescriptor> loadedPlugins = new ArrayList<>();
private static final List<IClassTransformer> transformers = new ArrayList<>(); private static final List<PluginDescriptor> loadedPlugins = new CopyOnWriteArrayList<>();
private static final List<String> corePluginMainClass = new ArrayList<>(); private static final List<IClassTransformer> transformers = new CopyOnWriteArrayList<>();
private static final List<String> pluginRegisteredName = new ArrayList<>(); private static final List<String> corePluginMainClass = new CopyOnWriteArrayList<>();
public static void loadPlugins() throws IOException, PluginLoadingError { private static final Set<String> pluginRegisteredName = Collections.synchronizedSet(new HashSet<>());
// =========================================================
// 安全日志相关方法 (Safe Logger)
// =========================================================
/**
* 获取 Logger如果 Log4j 尚未准备好(例如在 SystemClassLoader 初始化期间),则返回 null
*/
private static Logger getLogger() {
if (logger != null) return logger;
try {
// 尝试初始化 Log4j
logger = LogManager.getLogger(PluginLoader.class); logger = LogManager.getLogger(PluginLoader.class);
File pluginDir = null; return logger;
if (PLUGIN_PATH != null) { } catch (Throwable t) {
pluginDir = new File(PLUGIN_PATH); // 捕获 BootstrapMethodError 或 IllegalStateException
// 说明此时 SystemClassLoader 正在构造中,不能使用 Log4j
return null;
} }
File[] jars = null;
if (pluginDir != null) {
jars = pluginDir.listFiles((dir, name) -> name.toLowerCase().endsWith(".jar"));
} }
if (jars == null) { private static void logInfo(String msg, Object... args) {
return; Logger l = getLogger();
if (l != null) {
l.info(msg, args);
} else {
// Fallback: 在 Log4j 准备好之前使用 System.out
System.out.println("[PluginLoader-INFO] " + formatFallback(msg, args));
} }
for (int i = 0; i < jars.length; i++) { }
processJarFile(jars[i],false);
private static void logError(String msg, Object... args) {
Logger l = getLogger();
if (l != null) {
l.error(msg, args);
} else {
System.err.println("[PluginLoader-ERROR] " + formatFallback(msg, args));
// 打印异常堆栈
for (Object arg : args) {
if (arg instanceof Throwable) {
((Throwable) arg).printStackTrace();
}
}
}
}
private static void logWarn(String msg, Object... args) {
Logger l = getLogger();
if (l != null) {
l.warn(msg, args);
} else {
System.out.println("[PluginLoader-WARN] " + formatFallback(msg, args));
}
}
private static void logDebug(String msg, Object... args) {
Logger l = getLogger();
if (l != null) {
l.debug(msg, args);
} else {
// Debug 级别在 fallback 模式下通常不打印,或者可以打印到 System.out
// System.out.println("[PluginLoader-DEBUG] " + formatFallback(msg, args));
}
}
// 简单的字符串格式化,用于 fallback 模式
private static String formatFallback(String msg, Object... args) {
if (args == null || args.length == 0) return msg;
try {
// 简单替换 {},不完美但够用
String result = msg;
for (Object arg : args) {
if (arg instanceof Throwable) continue; // 异常单独处理
result = result.replaceFirst("\\{\\}", String.valueOf(arg));
}
return result;
} catch (Exception e) {
return msg;
}
}
// =========================================================
// 业务逻辑
// =========================================================
public static void loadPlugins() throws IOException, PluginLoadingError {
File pluginDir = (PLUGIN_PATH != null) ? new File(PLUGIN_PATH) : null;
if (pluginDir == null || !pluginDir.exists()) return;
File[] jars = pluginDir.listFiles((dir, name) -> name.toLowerCase().endsWith(".jar"));
if (jars == null || jars.length == 0) return;
logInfo("Found {} jars, starting parallel loading...", jars.length);
AtomicInteger loadedCount = new AtomicInteger(0);
int total = jars.length;
Arrays.stream(jars).parallel().forEach(jarFile -> {
try {
processJarFile(jarFile, false);
} catch (Exception e) {
logError("Failed to load plugin jar: " + jarFile.getName(), e);
} finally {
int current = loadedCount.incrementAndGet();
if (AxisInnovatorsBox.getMain() != null && AxisInnovatorsBox.getMain().progressBarManager != null) {
try {
AxisInnovatorsBox.getMain().progressBarManager.updateSubProgress( AxisInnovatorsBox.getMain().progressBarManager.updateSubProgress(
"Loading Plugin " + i, "Loading Plugin " + current, current, total);
i, } catch (Exception ignored) {}
jars.length);
} }
} }
});
}
private static void processJarFile(File jarFile, boolean isCorePlugin) throws IOException, PluginLoadingError { private static void processJarFile(File jarFile, boolean isCorePlugin) throws IOException {
try (JarFile jar = new JarFile(jarFile)) { try (JarFile jar = new JarFile(jarFile)) {
// Check for CorePlugin in MANIFEST.MF
if (isCorePlugin) { if (isCorePlugin) {
Attributes attributes = jar.getManifest().getMainAttributes(); Attributes attributes = jar.getManifest().getMainAttributes();
String corePluginClass = attributes.getValue("CorePlugin"); String corePluginClass = attributes.getValue("CorePlugin");
if (corePluginClass != null) { if (corePluginClass != null) {
processCorePlugin(jarFile, corePluginClass); processCorePlugin(jarFile, corePluginClass);
} }
} else { return;
}
JarEntry pluginFile = jar.getJarEntry("plug-in.box"); JarEntry pluginFile = jar.getJarEntry("plug-in.box");
PluginDescriptor descriptor = null;
if (pluginFile != null) { if (pluginFile != null) {
processWithManifest(jar, pluginFile, jarFile); descriptor = processWithManifest(jar, pluginFile, jarFile);
} else { } else {
processWithAnnotations(jar, jarFile); descriptor = processWithAnnotations(jar, jarFile);
} }
if (descriptor != null) {
loadPluginLanguagesInternal(jar, descriptor);
Attributes attributes = jar.getManifest().getMainAttributes();
String corePluginClass = attributes.getValue("CorePlugin");
descriptor.setTransformers(corePluginClass);
} }
} catch (PluginLoadingError e) {
logError("Plugin logic error in {}: {}", jarFile.getName(), e.getMessage());
} }
} }
/**
* 加载核心插件
* @throws IOException 插件加载失败
*/
public static void loadCorePlugin() throws IOException { public static void loadCorePlugin() throws IOException {
File pluginDir = null; File pluginDir = (PLUGIN_PATH != null) ? new File(PLUGIN_PATH) : null;
if (PLUGIN_PATH != null) { if (pluginDir == null || !pluginDir.exists()) return;
pluginDir = new File(PLUGIN_PATH);
} File[] jars = pluginDir.listFiles((dir, name) -> name.toLowerCase().endsWith(".jar"));
File[] jars = null; if (jars == null) return;
if (pluginDir != null) {
jars = pluginDir.listFiles((dir, name) -> name.toLowerCase().endsWith(".jar"));
}
if (jars == null) {
return;
}
for (File jar : jars) { for (File jar : jars) {
try {
processJarFile(jar, true); processJarFile(jar, true);
} catch (PluginLoadingError e) {
throw new RuntimeException(e);
}
} }
} }
@@ -110,13 +199,14 @@ public class PluginLoader {
registerTransformers(corePlugin, urlClassLoader); registerTransformers(corePlugin, urlClassLoader);
} }
} catch (Exception e) { } catch (Exception e) {
logger.error("Failed to load core plugin: {}", corePluginClass, e); logError("Failed to load core plugin: {}", corePluginClass, e);
} }
} }
private static void registerTransformers(LoadingCorePlugin corePlugin, ClassLoader classLoader) { private static void registerTransformers(LoadingCorePlugin corePlugin, ClassLoader classLoader) {
//if (corePlugin.getMainClass() != null) { if (corePlugin.getMainClass() != null) {
corePluginMainClass.add(corePlugin.getMainClass()); corePluginMainClass.add(corePlugin.getMainClass());
}
String[] transformerClasses = corePlugin.getASMTransformerClass(); String[] transformerClasses = corePlugin.getASMTransformerClass();
if (transformerClasses != null) { if (transformerClasses != null) {
for (String transformerClass : transformerClasses) { for (String transformerClass : transformerClasses) {
@@ -128,106 +218,166 @@ public class PluginLoader {
BoxClassLoader.addClassTransformer(transformer); BoxClassLoader.addClassTransformer(transformer);
} }
} catch (Exception e) { } catch (Exception e) {
logger.error("Failed to register transformer: {}", transformerClass, e); logError("Failed to register transformer: {}", transformerClass, e);
} }
} }
} }
//} else {
// logger.error("Failed to load core plugin: {}", corePlugin.getClass().getName());
//}
} }
private static void processWithManifest(JarFile jar, JarEntry entry, File jarFile) private static PluginDescriptor processWithManifest(JarFile jar, JarEntry entry, File jarFile)
throws IOException, PluginLoadingError { throws IOException, PluginLoadingError {
Properties props = new Properties(); Properties props = new Properties();
try (InputStream is = jar.getInputStream(entry)) { try (InputStream is = jar.getInputStream(entry)) {
props.load(is); props.load(is);
} catch (IOException e) {
logger.error("Failed to load plugin from jar: {}", jarFile.getName(), e);
return;
} }
PluginDescriptor descriptor = new PluginDescriptor(); PluginDescriptor descriptor = new PluginDescriptor();
URL jarUrl = jarFile.toURI().toURL();
descriptor.setSourceLocation(jarUrl);
URLClassLoader pluginClassLoader = new URLClassLoader(
new URL[]{jarUrl},
PluginLoader.class.getClassLoader()
);
descriptor.setClassLoader(pluginClassLoader);
descriptor.setId(props.getProperty("id")); descriptor.setId(props.getProperty("id"));
descriptor.setName(props.getProperty("name")); descriptor.setName(props.getProperty("name"));
descriptor.setDescription(props.getProperty("description")); descriptor.setDescription(props.getProperty("description"));
descriptor.setIcon(props.getProperty("icon")); descriptor.setIcon(props.getProperty("icon"));
descriptor.setSupportedVersions(
Arrays.asList(props.getProperty("supportedVersions").split(",")) String versions = props.getProperty("supportedVersions");
); if (versions != null) {
descriptor.setSupportedVersions(Arrays.asList(versions.split(",")));
}
String registrationName = props.getProperty("registrationName"); String registrationName = props.getProperty("registrationName");
verifyRegisteredNameValid(descriptor, registrationName); verifyRegisteredNameValid(descriptor, registrationName);
logger.info("Loaded plugin: {}", descriptor.getName()); logInfo("Loaded plugin via manifest: {}", descriptor.getName());
loadMainClass(props.getProperty("mainClass"), jarFile, descriptor); loadMainClass(props.getProperty("mainClass"), descriptor);
Attributes attributes = jar.getManifest().getMainAttributes(); return descriptor;
String corePluginClass = attributes.getValue("CorePlugin");
descriptor.setTransformers(corePluginClass);
loadPluginLanguages(jarFile, descriptor);
} }
private static void verifyRegisteredNameValid(PluginDescriptor descriptor, String registrationName) throws PluginLoadingError { private static void verifyRegisteredNameValid(PluginDescriptor descriptor, String registrationName) throws PluginLoadingError {
if (registrationName != null && !registrationName.isEmpty() if (registrationName == null || registrationName.trim().isEmpty()) {
&& !pluginRegisteredName.contains(registrationName)) { throw new PluginLoadingError("Invalid registration name for plugin: " + descriptor.getName());
pluginRegisteredName.add(registrationName);
} else {
if (registrationName == null || registrationName.isEmpty()) {
throw new PluginLoadingError("Invalid registration name");
}
throw new PluginLoadingError("When the \" "
+ descriptor.getName()
+ "\" plugin is loaded, this plugin contains the same registered name as the previously loaded plugin");
} }
synchronized (pluginRegisteredName) {
if (!pluginRegisteredName.add(registrationName)) {
throw new PluginLoadingError("Duplicate registered name '" + registrationName + "' in plugin " + descriptor.getName());
}
}
descriptor.setRegistrationName(registrationName); descriptor.setRegistrationName(registrationName);
} }
private static void processWithAnnotations(JarFile jar, File jarFile) throws PluginLoadingError { private static PluginDescriptor processWithAnnotations(JarFile jar, File jarFile) throws PluginLoadingError {
URLClassLoader classLoader = createClassLoader(jarFile); PluginDescriptor foundDescriptor = null;
URLClassLoader classLoader = null;
boolean keepClassLoaderOpen = false;
try {
classLoader = new URLClassLoader(
new URL[]{jarFile.toURI().toURL()},
PluginLoader.class.getClassLoader());
Enumeration<JarEntry> entries = jar.entries(); Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) { while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement(); JarEntry entry = entries.nextElement();
if (entry.getName().endsWith(".class")) { if (!entry.isDirectory() && entry.getName().endsWith(".class")) {
if (classLoader != null) { String className = entry.getName()
processClassEntry(entry, jar,classLoader,jarFile); .replace("/", ".")
.replace(".class", "");
} try {
} if (className.equals("module-info")) continue;
}
}
private static void loadPluginLanguages(File jarFile, PluginDescriptor plugin) { Class<?> clazz = classLoader.loadClass(className);
try (JarFile jar = new JarFile(jarFile)) { PluginMeta meta = clazz.getAnnotation(PluginMeta.class);
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) { if (meta != null) {
JarEntry entry = entries.nextElement(); PluginDescriptor descriptor = new PluginDescriptor();
if (entry.getName().startsWith("lang/") && descriptor.setSourceLocation(jarFile.toURI().toURL());
entry.getName().endsWith(".properties")) {
processLanguageFile(jar, entry, plugin); descriptor.setClassLoader(classLoader);
keepClassLoaderOpen = true;
descriptor.setId(meta.id());
descriptor.setName(meta.name());
descriptor.setDescription(meta.description());
descriptor.setIcon(meta.icon());
descriptor.setSupportedVersions(Arrays.asList(meta.supportedVersions()));
verifyRegisteredNameValid(descriptor, meta.registeredName());
Object instance = clazz.getDeclaredConstructor().newInstance();
descriptor.setInstance(instance);
injectInstanceField(clazz, instance, descriptor);
loadedPlugins.add(descriptor);
logInfo("Loaded plugin via annotation: {}", descriptor.getName());
foundDescriptor = descriptor;
break;
}
} catch (Throwable e) {
// 忽略
}
} }
} }
} catch (IOException e) { } catch (IOException e) {
logger.error("Failed to load language files for plugin: {}", plugin.getName(), e); logError("Error scanning jar for annotations: " + jarFile.getName(), e);
} finally {
if (!keepClassLoaderOpen && classLoader != null) {
try {
classLoader.close();
} catch (IOException e) {
logWarn("Failed to close unused classloader", e);
} }
} }
}
return foundDescriptor;
}
private static void loadPluginLanguagesInternal(JarFile jar, PluginDescriptor plugin) {
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String entryName = entry.getName();
if (entryName.startsWith("lang/") && entryName.endsWith(".properties")) {
processLanguageFile(jar, entry, plugin);
}
else if (entryName.startsWith("assets/lang/") && entryName.endsWith(".properties")) {
processAssetsLanguageFile(plugin, entryName);
}
}
}
private static void processAssetsLanguageFile(PluginDescriptor plugin, String entryName) {
String fileName = entryName.substring(entryName.lastIndexOf("/") + 1);
String langCode = fileName.replace(".properties", "");
String targetRegisteredName = "system:" + langCode;
LanguageManager.registerPluginLanguage(plugin, entryName, targetRegisteredName);
}
private static void processLanguageFile(JarFile jar, JarEntry entry, PluginDescriptor plugin) { private static void processLanguageFile(JarFile jar, JarEntry entry, PluginDescriptor plugin) {
String fileName = entry.getName().substring(entry.getName().lastIndexOf("/") + 1); String fileName = entry.getName().substring(entry.getName().lastIndexOf("/") + 1);
String[] parts = fileName.split("[_.]"); String[] parts = fileName.split("[_.]");
if (parts.length < 3) { if (parts.length < 3) return;
return;
} String langCode = parts[parts.length - 1].replace(".properties", "");
String langCode = parts[parts.length-1].replace(".properties", "");
String langName = plugin.getName() + " " + langCode.toUpperCase(); String langName = plugin.getName() + " " + langCode.toUpperCase();
String registeredName = plugin.getRegistrationName() + "_" + langCode; String registeredName = plugin.getRegistrationName() + "_" + langCode;
try (InputStream is = jar.getInputStream(entry)) { try (InputStream is = jar.getInputStream(entry)) {
Properties properties2 = new Properties(); Properties properties2 = new Properties();
properties2.load(new InputStreamReader(is, StandardCharsets.UTF_8)); properties2.load(new InputStreamReader(is, StandardCharsets.UTF_8));
LanguageManager.Language language = new LanguageManager.Language( LanguageManager.Language language = new LanguageManager.Language(
langName, langName, registeredName, null
registeredName,
null
) { ) {
@Override @Override
public void loadLanguageFile(String languageFile) { public void loadLanguageFile(String languageFile) {
@@ -235,97 +385,42 @@ public class PluginLoader {
} }
}; };
LanguageManager.addLanguage(language); LanguageManager.addLanguage(language);
logger.info("Loaded plugin language: {} ({})", langName, registeredName); logDebug("Loaded plugin language: {} ({})", langName, registeredName);
} catch (IOException e) { } catch (IOException e) {
logger.error("Failed to load language file: {}", entry.getName(), e); logError("Failed to load language file: {}", entry.getName(), e);
} }
} }
private static URLClassLoader createClassLoader(File jarFile) { private static void loadMainClass(String mainClassName, PluginDescriptor descriptor) {
try {
return new URLClassLoader(
new URL[]{jarFile.toURI().toURL()},
PluginLoader.class.getClassLoader()
);
} catch (MalformedURLException e) {
logger.error("Error creating URLClassLoader", e);
}
return null;
}
private static void processClassEntry(JarEntry entry,JarFile jar,
URLClassLoader classLoader,
File jarFile) throws PluginLoadingError {
String className = entry.getName()
.replace("/", ".")
.replace(".class", "");
try {
Class<?> clazz = classLoader.loadClass(className);
PluginMeta meta = clazz.getAnnotation(PluginMeta.class);
if (meta != null) {
PluginDescriptor descriptor = new PluginDescriptor();
descriptor.setId(meta.id());
descriptor.setName(meta.name());
descriptor.setDescription(meta.description());
descriptor.setIcon(meta.icon());
descriptor.setSupportedVersions(Arrays.asList(meta.supportedVersions()));
String registrationName = meta.registeredName();
verifyRegisteredNameValid(descriptor, registrationName);
try {
Object instance = clazz.getDeclaredConstructor().newInstance();
descriptor.setInstance(instance);
try {
Field pluginInstance = instance.getClass().getDeclaredField("INSTANCE");
pluginInstance.setAccessible(true);
pluginInstance.set(null, descriptor);
} catch (NoSuchFieldException | IllegalArgumentException
| SecurityException | IllegalAccessException e) {
logger.warn("Failed to set plugin instance: {}", instance.getClass(), e);
}
loadedPlugins.add(descriptor);
Attributes attributes = jar.getManifest().getMainAttributes();
String corePluginClass = attributes.getValue("CorePlugin");
descriptor.setTransformers(corePluginClass);
loadPluginLanguages(jarFile, descriptor);
logger.info("Loaded plugin: {}", descriptor.getName());
} catch (Exception e) {
logger.error("Failed to instantiate plugin class: {}", className, e);
}
}
} catch (ClassNotFoundException | NoClassDefFoundError e) {
logger.error("Error loading class: {}", className, e);
}
}
private static void loadMainClass(String mainClassName, File jarFile, PluginDescriptor descriptor) {
if (mainClassName == null || mainClassName.isEmpty()) { if (mainClassName == null || mainClassName.isEmpty()) {
logger.error("Invalid main class name: {}", mainClassName);
return; return;
} }
try (URLClassLoader classLoader = new URLClassLoader( ClassLoader classLoader = descriptor.getClassLoader();
new URL[]{jarFile.toURI().toURL()}, if (classLoader == null) {
PluginLoader.class.getClassLoader()) logError("Plugin ClassLoader is missing for {}", descriptor.getName());
) { return;
}
try {
Class<?> mainClass = classLoader.loadClass(mainClassName); Class<?> mainClass = classLoader.loadClass(mainClassName);
Object instance = mainClass.getDeclaredConstructor().newInstance(); Object instance = mainClass.getDeclaredConstructor().newInstance();
descriptor.setInstance(instance); descriptor.setInstance(instance);
injectInstanceField(mainClass, instance, descriptor);
} catch (Exception e) {
logError("Failed to load main class: {}", mainClassName, e);
}
}
private static void injectInstanceField(Class<?> clazz, Object instance, PluginDescriptor descriptor) {
try { try {
Field pluginInstance = mainClass.getDeclaredField("INSTANCE"); Field pluginInstance = clazz.getDeclaredField("INSTANCE");
pluginInstance.setAccessible(true); pluginInstance.setAccessible(true);
pluginInstance.set(null, descriptor); pluginInstance.set(null, descriptor);
} catch (NoSuchFieldException | IllegalArgumentException } catch (NoSuchFieldException e) {
| SecurityException | IllegalAccessException e) { // 忽略
logger.warn("Failed to set plugin instance: {}", mainClassName, e);
}
} catch (Exception e) { } catch (Exception e) {
logger.error("Failed to load main class: {}", mainClassName, e); logWarn("Failed to set plugin instance field for {}", clazz.getName(), e);
} }
} }

View File

@@ -1,5 +1,6 @@
package com.axis.innovators.box.register; package com.axis.innovators.box.register;
import com.axis.innovators.box.plugins.PluginDescriptor;
import com.axis.innovators.box.plugins.PluginLoader; import com.axis.innovators.box.plugins.PluginLoader;
import com.axis.innovators.box.tools.FolderCreator; import com.axis.innovators.box.tools.FolderCreator;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
@@ -27,14 +28,54 @@ public class LanguageManager {
LANGUAGES.add(new Language("日本語", "system:ja_JP", "sys_ja_JP")); LANGUAGES.add(new Language("日本語", "system:ja_JP", "sys_ja_JP"));
} }
/**
* 插件专用:从插件 Jar 包资源中快速注册/合并语言
* <p>
* 示例用法:
* LanguageManager.registerPluginLanguage(plugin, "assets/lang/en_US.properties", "system:en_US");
* </p>
*
* @param plugin 插件描述符(用于获取 ClassLoader
* @param resourcePath 资源在 Jar 包中的路径 (例如 "assets/lang/zh_CN.properties")
* @param targetRegisteredName 目标语言的注册名 (例如 "system:zh_CN"),将合并到此语言中
*/
public static void registerPluginLanguage(PluginDescriptor plugin, String resourcePath, String targetRegisteredName) {
if (plugin == null || resourcePath == null || targetRegisteredName == null) {
return;
}
try (InputStream is = plugin.getResourceAsStream(resourcePath)) {
if (is == null) {
logger.warn("无法在插件 [{}] 中找到语言资源: {}", plugin.getName(), resourcePath);
return;
}
// 创建一个临时的 Language 对象,重写加载逻辑以避免读取磁盘文件
Language pluginLang = new Language(
plugin.getName() + " Resource", // 临时名称,不重要
targetRegisteredName, // 关键:注册名必须与现有语言一致才能合并
null
) {
@Override
public void loadLanguageFile(String ignored) {
// 覆盖父类行为:不从磁盘加载,防止 FileNotFoundException
}
};
// 手动从流中加载属性
pluginLang.loadFromInputStream(is);
// 复用现有的合并逻辑,这样可以保持统一的日志记录
addLanguage(pluginLang);
} catch (IOException e) {
logger.error("加载插件语言资源失败: {} - {}", plugin.getName(), resourcePath, e);
}
}
/** /**
* 添加/合并语言资源 * 添加/合并语言资源
* @param language 要添加的语言对象 * @param language 要添加的语言对象
* 处理规则:
* 1. 注册名相同时保留原有基础信息
* 2. 相同键值的新内容覆盖旧内容
* 3. 详细记录新增、更新的键值数量
* 4. 提供关键示例用于调试
*/ */
public static void addLanguage(Language language) { public static void addLanguage(Language language) {
for (Language existing : LANGUAGES) { for (Language existing : LANGUAGES) {
@@ -61,19 +102,21 @@ public class LanguageManager {
existing.properties.setProperty(key, newValue); existing.properties.setProperty(key, newValue);
} }
// 仅当有变更时才构建日志,减少刷屏(可选优化)
if (added > 0 || updated > 0) {
String logDetail = buildMergeLogDetails(added, updated, sampleAddedKeys, sampleUpdatedKeys); String logDetail = buildMergeLogDetails(added, updated, sampleAddedKeys, sampleUpdatedKeys);
logger.info("【语言合并报告】\n" + logger.info("【语言合并报告】\n" +
"▌合并目标{} ({})\n" + "▌合并来源{} -> {}\n" +
"▌变更统计:新增 {} 条 / 更新 {} 条\n" + "▌变更统计:新增 {} 条 / 更新 {} 条\n" +
"▌当前总量:{} 条\n" + "▌当前总量:{} 条\n" +
"▌关键示例:\n{}", "▌关键示例:\n{}",
existing.getLanguageName(), language.getLanguageName(),
existing.getRegisteredName(), existing.getRegisteredName(),
added, added,
updated, updated,
existing.properties.size(), existing.properties.size(),
logDetail); logDetail);
}
return; return;
} catch (Exception e) { } catch (Exception e) {
@@ -122,54 +165,43 @@ public class LanguageManager {
*/ */
public static void loadLanguage(String languageName) { public static void loadLanguage(String languageName) {
loadedLanguages = LanguageManager.getLanguage(languageName); loadedLanguages = LanguageManager.getLanguage(languageName);
if (loadedLanguages != null) {
saveCurrentLanguageToFile(); saveCurrentLanguageToFile();
} }
}
/**
* 将当前加载的语言保存到配置文件中
*/
private static void saveCurrentLanguageToFile() { private static void saveCurrentLanguageToFile() {
if (loadedLanguages == null) return;
Properties properties = new Properties(); Properties properties = new Properties();
properties.setProperty("loadedLanguage", loadedLanguages.getRegisteredName()); properties.setProperty("loadedLanguage", loadedLanguages.getRegisteredName());
try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(SAVED_LANGUAGE_FILE), StandardCharsets.UTF_8)) { try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(SAVED_LANGUAGE_FILE), StandardCharsets.UTF_8)) {
properties.store(writer, "Current Loaded Language"); properties.store(writer, "Current Loaded Language");
} catch (IOException e) { } catch (IOException e) {
System.err.println("Failed to save current language to file: " + SAVED_LANGUAGE_FILE); logger.error("Failed to save current language to file: {}", SAVED_LANGUAGE_FILE, e);
e.printStackTrace();
} }
} }
/**
* 从配置文件中加载保存的语言
*/
public static void loadSavedLanguage() { public static void loadSavedLanguage() {
File file = new File(SAVED_LANGUAGE_FILE);
if (!file.exists()) return;
Properties properties = new Properties(); Properties properties = new Properties();
try (InputStreamReader reader = new InputStreamReader(new FileInputStream(SAVED_LANGUAGE_FILE), StandardCharsets.UTF_8)) { try (InputStreamReader reader = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)) {
properties.load(reader); properties.load(reader);
String loadedLanguageName = properties.getProperty("loadedLanguage"); String loadedLanguageName = properties.getProperty("loadedLanguage");
if (loadedLanguageName != null) { if (loadedLanguageName != null) {
loadedLanguages = LanguageManager.getLanguage(loadedLanguageName); loadedLanguages = LanguageManager.getLanguage(loadedLanguageName);
} }
} catch (IOException e) { } catch (IOException e) {
System.err.println("Failed to load saved language from file: " + SAVED_LANGUAGE_FILE); logger.error("Failed to load saved language from file: {}", SAVED_LANGUAGE_FILE, e);
e.printStackTrace();
} }
} }
/**
* 获取已加载的语言
* @return 已加载的语言
*/
public static Language getLoadedLanguages() { public static Language getLoadedLanguages() {
return loadedLanguages; return loadedLanguages;
} }
/**
* 获取语言
* @param languageName 语言注册名,或者语言名称
* @return 语言对象
*/
public static Language getLanguage(String languageName) { public static Language getLanguage(String languageName) {
for (Language language : LANGUAGES) { for (Language language : LANGUAGES) {
if (language.getRegisteredName().equals(languageName)) { if (language.getRegisteredName().equals(languageName)) {
@@ -185,14 +217,14 @@ public class LanguageManager {
return null; return null;
} }
/**
* 获取所有语言
* @return 所有语言
*/
public static List<Language> getLanguages() { public static List<Language> getLanguages() {
return LANGUAGES; return LANGUAGES;
} }
// ==========================================
// Inner Class Language
// ==========================================
public static class Language { public static class Language {
private final String languageName; private final String languageName;
private final String registeredName; private final String registeredName;
@@ -210,14 +242,19 @@ public class LanguageManager {
this.languageFile = LANGUAGE_PATH + "\\" + languageFileName + ".properties"; this.languageFile = LANGUAGE_PATH + "\\" + languageFileName + ".properties";
} }
this.properties = new Properties(); this.properties = new Properties();
// 默认构造时加载文件
loadLanguageFile(languageFile); loadLanguageFile(languageFile);
} }
/** /**
* 加载语言文件 * 从磁盘加载语言文件
*/ */
public void loadLanguageFile(String languageFile) { public void loadLanguageFile(String languageFile) {
try (InputStreamReader reader = new InputStreamReader(new FileInputStream(languageFile), File file = new File(languageFile);
if (!file.exists()) return; // 容错处理
try (InputStreamReader reader = new InputStreamReader(new FileInputStream(file),
StandardCharsets.UTF_8)) { StandardCharsets.UTF_8)) {
properties.load(reader); properties.load(reader);
} catch (IOException e) { } catch (IOException e) {
@@ -227,10 +264,19 @@ public class LanguageManager {
} }
/** /**
* 获取指定键的文本 * 新增:直接从输入流加载配置(供插件使用)
* @param key 键 * @param inputStream 资源流
* @return 对应的文本,如果键不存在则返回 null
*/ */
public void loadFromInputStream(InputStream inputStream) {
if (inputStream == null) return;
try (InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
properties.load(reader);
} catch (IOException e) {
System.err.println("Failed to load language from stream for " + registeredName);
e.printStackTrace();
}
}
public String getText(String key) { public String getText(String key) {
if (!properties.containsKey(key)) { if (!properties.containsKey(key)) {
return key; return key;
@@ -238,9 +284,6 @@ public class LanguageManager {
return properties.getProperty(key); return properties.getProperty(key);
} }
/**
* 添加文本
*/
public void addText(String key, String value) { public void addText(String key, String value) {
properties.setProperty(key, value); properties.setProperty(key, value);
} }

View File

@@ -465,7 +465,7 @@ public class RegistrationSettingsItem extends WindowsJDialog {
JPanel iconPanel = new JPanel(); JPanel iconPanel = new JPanel();
ImageIcon icon = LoadIcon.loadIcon(plugin.getInstance().getClass(), plugin.getIcon(), 64); ImageIcon icon = LoadIcon.loadIcon(plugin, plugin.getIcon(), 64);
JLabel iconLabel = new JLabel(icon); JLabel iconLabel = new JLabel(icon);
iconPanel.add(iconLabel); iconPanel.add(iconLabel);
mainPanel.add(iconPanel, BorderLayout.WEST); mainPanel.add(iconPanel, BorderLayout.WEST);

View File

@@ -1,5 +1,6 @@
package com.axis.innovators.box.window; package com.axis.innovators.box.window;
import com.axis.innovators.box.plugins.PluginDescriptor;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@@ -11,67 +12,180 @@ import java.net.URL;
/** /**
* 负责加载图片 * 负责加载图片
* <p>
* 更新说明:已适配 PluginDescriptor。
* </p>
* @author tzdwindows 7 * @author tzdwindows 7
*/ */
public class LoadIcon { public class LoadIcon {
private static final Logger logger = LogManager.getLogger(LoadIcon.class); private static final Logger logger = LogManager.getLogger(LoadIcon.class);
private static final String ICON_PATH = "/icons/"; private static final String ICON_PATH = "/icons/";
// ==========================================
// Public API - 面向 PluginDescriptor
// ==========================================
/** /**
* 加载图片 * 从插件资源中加载正方形图标
* @param plugin 插件描述符
* @param filename 图片文件名 (相对于插件 classpath 的 /icons/ 或根路径)
* @param size 宽高
* @return ImageIcon
*/
public static ImageIcon loadIcon(PluginDescriptor plugin, String filename, int size) {
return loadIcon(plugin, filename, size, size);
}
/**
* 从插件资源中加载指定宽高的图标
* @param plugin 插件描述符
* @param filename 图片文件名
* @param width 宽
* @param height 高
* @return ImageIcon
*/
public static ImageIcon loadIcon(PluginDescriptor plugin, String filename, int width, int height) {
ClassLoader loader = plugin.getClassLoader();
if (loader == null) {
logger.warn("Plugin {} has no ClassLoader, returning placeholder.", plugin.getName());
return createPlaceholderIcon(width, height);
}
return loadIconInternal(loader, filename, width, height);
}
// ==========================================
// Public API - 面向主程序 (默认使用 LoadIcon.class)
// ==========================================
/**
* 加载主程序图标
* @param filename 图片名 * @param filename 图片名
* @param size 图片大小 * @param size 图片大小
* @return ImageIcon对象 * @return ImageIcon对象
*/ */
public static ImageIcon loadIcon(String filename, int size) { public static ImageIcon loadIcon(String filename, int size) {
return loadIcon(LoadIcon.class, filename, size); return loadIconInternal(LoadIcon.class.getClassLoader(), filename, size, size);
} }
/** /**
* 加载指定宽高的图片(适用于背景图) * 加载主程序指定宽高的图片
* @param filename 图片名 * @param filename 图片名
* @param width 宽度 * @param width 宽度
* @param height 高度 * @param height 高度
* @return ImageIcon对象 * @return ImageIcon对象
*/ */
public static ImageIcon loadIcon(String filename, int width, int height) { public static ImageIcon loadIcon(String filename, int width, int height) {
return loadIcon(LoadIcon.class, filename, width, height); return loadIconInternal(LoadIcon.class.getClassLoader(), filename, width, height);
} }
/** /**
* 加载指定宽高的图片(核心构造体) * 加载系统包下的图片
* @param clazz resources包所在的jar * @param clazz resources包所在的jar
* @param filename 图片名
* @param size 图片大小
* @return ImageIcon对象
*/
public static ImageIcon loadSystemIcon(Class<?> clazz, String filename, int size) {
return loadIconInternal(clazz.getClassLoader(), filename, size, size);
}
// ==========================================
// Package-Private API - 限制访问 (不支持 PluginDescriptor 的旧方法)
// ==========================================
/**
* 加载指定宽高的图片(仅限当前包访问)
* @param clazz resources包所在的jar类
* @param filename 图片名 * @param filename 图片名
* @param width 目标宽度 * @param width 目标宽度
* @param height 目标高度 * @param height 目标高度
* @return ImageIcon对象 * @return ImageIcon对象
*/ */
public static ImageIcon loadIcon(Class<?> clazz, String filename, int width, int height) { static ImageIcon loadIcon(Class<?> clazz, String filename, int width, int height) {
return loadIconInternal(clazz.getClassLoader(), filename, width, height);
}
/**
* 加载图片(仅限当前包访问)
* @param clazz resources包所在的jar类
* @param filename 图片名
* @param size 图片大小
* @return ImageIcon对象
*/
static ImageIcon loadIcon(Class<?> clazz, String filename, int size) {
return loadIconInternal(clazz.getClassLoader(), filename, size, size);
}
/**
* 加载图片,另一个版本(仅限当前包访问)
* 直接加载 filename不尝试拼接 ICON_PATH
*/
static ImageIcon loadIcon0(Class<?> clazz, String filename, int size) {
try {
if (filename == null || filename.isEmpty()) {
return createPlaceholderIcon(size, size);
}
URL imgUrl = clazz.getClassLoader().getResource(filename); // 使用 ClassLoader 加载
if (imgUrl == null) {
// 回退尝试 class.getResource (处理相对路径差异)
imgUrl = clazz.getResource(filename);
}
if (imgUrl == null) {
return createPlaceholderIcon(size, size);
}
Image image = new ImageIcon(imgUrl).getImage();
return new ImageIcon(image.getScaledInstance(size, size, Image.SCALE_SMOOTH));
} catch (Exception e) {
logger.error("Failed to load icon0 '{}'", filename, e);
return createPlaceholderIcon(size, size);
}
}
// ==========================================
// Internal Core Logic
// ==========================================
/**
* 统一的核心加载逻辑
*/
private static ImageIcon loadIconInternal(ClassLoader classLoader, String filename, int width, int height) {
try { try {
if (filename == null || filename.isEmpty()) { if (filename == null || filename.isEmpty()) {
return createPlaceholderIcon(width, height); return createPlaceholderIcon(width, height);
} }
Image image; // 1. 处理绝对路径 (不依赖 ClassLoader)
// 1. 处理绝对路径 File file = new File(filename);
if (new File(filename).isAbsolute()) { if (file.isAbsolute() && file.exists()) {
image = new ImageIcon(filename).getImage(); Image image = new ImageIcon(filename).getImage();
} else { return new ImageIcon(image.getScaledInstance(width, height, Image.SCALE_SMOOTH));
}
// 2. 处理资源路径 // 2. 处理资源路径
URL imgUrl = null;
// 尝试 A: /icons/ + filename (标准化路径)
String fullPath = ICON_PATH + filename; String fullPath = ICON_PATH + filename;
URL imgUrl = clazz.getResource(fullPath); // 去掉开头的 / 因为 ClassLoader.getResource 不以 / 开头
if (imgUrl == null) { if (fullPath.startsWith("/")) fullPath = fullPath.substring(1);
// 尝试不带 /icons/ 路径直接加载 (兼容 loadIcon0 逻辑)
imgUrl = clazz.getResource(filename); if (classLoader != null) {
imgUrl = classLoader.getResource(fullPath);
}
// 尝试 B: 直接使用 filename (如果不带 path 或已经在根目录下)
if (imgUrl == null && classLoader != null) {
String cleanName = filename.startsWith("/") ? filename.substring(1) : filename;
imgUrl = classLoader.getResource(cleanName);
} }
if (imgUrl == null) { if (imgUrl == null) {
logger.warn("Resource not found: {}", filename); logger.warn("Resource not found: {}", filename);
return createPlaceholderIcon(width, height); return createPlaceholderIcon(width, height);
} }
image = new ImageIcon(imgUrl).getImage();
}
Image image = new ImageIcon(imgUrl).getImage();
// 3. 执行高质量缩放 // 3. 执行高质量缩放
return new ImageIcon(image.getScaledInstance(width, height, Image.SCALE_SMOOTH)); return new ImageIcon(image.getScaledInstance(width, height, Image.SCALE_SMOOTH));
@@ -88,81 +202,13 @@ public class LoadIcon {
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics(); Graphics2D g2d = img.createGraphics();
// 使用更符合现代深色主题的占位颜色 // 使用更符合现代深色主题的占位颜色
g2d.setColor(new Color(30, 30, 30)); g2d.setColor(new Color(30, 30, 30, 128)); // 半透明深色
g2d.fillRect(0, 0, width, height); g2d.fillRect(0, 0, width, height);
g2d.dispose();
return new ImageIcon(img);
}
/** // 画个边框表示丢失
* 加载图片 g2d.setColor(Color.RED);
* @param clazz resources包所在的jar g2d.drawRect(0, 0, width - 1, height - 1);
* @param filename 图片名
* @param size 图片大小
* @return ImageIcon对象
*/
public static ImageIcon loadIcon(Class<?> clazz, String filename, int size) {
try {
if (filename.isEmpty()) {
return createPlaceholderIcon(size);
}
if (new File(filename).isAbsolute()) {
return loadAbsolutePathIcon(filename, size);
}
String fullPath = ICON_PATH + filename;
URL imgUrl = clazz.getResource(fullPath);
if (imgUrl == null) {
return createPlaceholderIcon(size);
}
Image image = new ImageIcon(imgUrl).getImage();
return new ImageIcon(image.getScaledInstance(size, size, Image.SCALE_SMOOTH));
} catch (Exception e) {
logger.error("Failed to load icon image path '{}'", filename, e);
return createPlaceholderIcon(size);
}
}
private static ImageIcon loadAbsolutePathIcon(String absolutePath, int size) {
try {
Image image = new ImageIcon(absolutePath).getImage();
return new ImageIcon(image.getScaledInstance(size, size, Image.SCALE_SMOOTH));
} catch (Exception e) {
logger.error("Failed to load absolute path icon '{}'", absolutePath, e);
return createPlaceholderIcon(size);
}
}
/**
* 加载图片,另一个版本
* @param clazz resources包所在的jar
* @param filename 图片名
* @param size 图片大小
* @return ImageIcon对象
*/
public static ImageIcon loadIcon0(Class<?> clazz ,String filename, int size) {
try {
if (filename.isEmpty()){
return createPlaceholderIcon(size);
}
URL imgUrl = clazz.getResource(filename);
if (imgUrl == null) {
return createPlaceholderIcon(size);
}
Image image = new ImageIcon(imgUrl).getImage();
return new ImageIcon(image.getScaledInstance(size, size, Image.SCALE_SMOOTH));
} catch (Exception e) {
logger.error("Failed to load icon image path '{}'", filename, e);
return createPlaceholderIcon(size);
}
}
private static ImageIcon createPlaceholderIcon(int size) {
BufferedImage img = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
g2d.setColor(Color.LIGHT_GRAY);
g2d.fillRect(0, 0, size, size);
g2d.dispose(); g2d.dispose();
return new ImageIcon(img); return new ImageIcon(img);
} }

View File

@@ -1,4 +1,4 @@
# Auto-generated build information # Auto-generated build information
version=0.0.1 version=0.0.1
buildTimestamp=2026-01-02T17:08:37.387878 buildTimestamp=2026-01-02T17:46:06.8226378
buildSystem=WINDOWS buildSystem=WINDOWS