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:
@@ -839,7 +839,7 @@ public class AxisInnovatorsBox {
|
||||
UIManager.getSystemLookAndFeelClassName(),
|
||||
LanguageManager.getLoadedLanguages().getText("default_theme.system.topicName"),
|
||||
LanguageManager.getLoadedLanguages().getText("default_theme.default.tip"),
|
||||
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
|
||||
LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
|
||||
"system:default_theme",
|
||||
isDarkMode
|
||||
);
|
||||
@@ -849,7 +849,7 @@ public class AxisInnovatorsBox {
|
||||
"javax.swing.plaf.metal.MetalLookAndFeel",
|
||||
LanguageManager.getLoadedLanguages().getText("metal_theme.system.topicName"),
|
||||
LanguageManager.getLoadedLanguages().getText("metal_theme.default.tip"),
|
||||
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
|
||||
LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
|
||||
"system:metal_theme",
|
||||
false
|
||||
);
|
||||
@@ -859,7 +859,7 @@ public class AxisInnovatorsBox {
|
||||
"com.sun.java.swing.plaf.motif.MotifLookAndFeel",
|
||||
LanguageManager.getLoadedLanguages().getText("motif_theme.system.topicName"),
|
||||
LanguageManager.getLoadedLanguages().getText("motif_theme.default.tip"),
|
||||
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
|
||||
LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
|
||||
"system:motif_theme",
|
||||
false
|
||||
);
|
||||
@@ -870,7 +870,7 @@ public class AxisInnovatorsBox {
|
||||
new com.formdev.flatlaf.FlatLightLaf(),
|
||||
LanguageManager.getLoadedLanguages().getText("flatLight_theme.system.topicName"),
|
||||
LanguageManager.getLoadedLanguages().getText("flatLight_theme.default.tip"),
|
||||
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
|
||||
LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
|
||||
"system:flatLight_theme",
|
||||
false
|
||||
);
|
||||
@@ -880,7 +880,7 @@ public class AxisInnovatorsBox {
|
||||
new com.formdev.flatlaf.FlatDarkLaf(),
|
||||
LanguageManager.getLoadedLanguages().getText("flatDark_theme.system.topicName"),
|
||||
LanguageManager.getLoadedLanguages().getText("flatDark_theme.default.tip"),
|
||||
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
|
||||
LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
|
||||
"system:flatDark_theme",
|
||||
true
|
||||
);
|
||||
@@ -890,7 +890,7 @@ public class AxisInnovatorsBox {
|
||||
new com.formdev.flatlaf.FlatIntelliJLaf(),
|
||||
LanguageManager.getLoadedLanguages().getText("flatIntelliJ_theme.system.topicName"),
|
||||
LanguageManager.getLoadedLanguages().getText("flatIntelliJ_theme.default.tip"),
|
||||
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
|
||||
LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
|
||||
"system:flatIntelliJ_theme",
|
||||
false
|
||||
);
|
||||
@@ -900,7 +900,7 @@ public class AxisInnovatorsBox {
|
||||
new com.formdev.flatlaf.FlatDarculaLaf(),
|
||||
LanguageManager.getLoadedLanguages().getText("flatDarcula_theme.system.topicName"),
|
||||
LanguageManager.getLoadedLanguages().getText("flatDarcula_theme.default.tip"),
|
||||
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
|
||||
LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
|
||||
"system:flatDarcula_theme",
|
||||
true
|
||||
);
|
||||
@@ -910,7 +910,7 @@ public class AxisInnovatorsBox {
|
||||
new FlatMacLightLaf(),
|
||||
LanguageManager.getLoadedLanguages().getText("flatMacLight_theme.system.topicName"),
|
||||
LanguageManager.getLoadedLanguages().getText("flatMacLight_theme.default.tip"),
|
||||
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
|
||||
LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
|
||||
"system:flatMacLight_theme",
|
||||
false
|
||||
);
|
||||
@@ -920,7 +920,7 @@ public class AxisInnovatorsBox {
|
||||
new FlatMacDarkLaf(),
|
||||
LanguageManager.getLoadedLanguages().getText("flatMacDark_theme.system.topicName"),
|
||||
LanguageManager.getLoadedLanguages().getText("flatMacDark_theme.default.tip"),
|
||||
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
|
||||
LoadIcon.loadSystemIcon(MainWindow.class, "logo.png", 64),
|
||||
"system:flatMacDark_theme",
|
||||
true
|
||||
);
|
||||
@@ -929,7 +929,7 @@ public class AxisInnovatorsBox {
|
||||
new MaterialLookAndFeel(new JMarsDarkTheme()),
|
||||
LanguageManager.getLoadedLanguages().getText("mars_dark_theme.system.topicName"),
|
||||
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",
|
||||
true
|
||||
);
|
||||
@@ -938,7 +938,7 @@ public class AxisInnovatorsBox {
|
||||
new MaterialLookAndFeel(new MaterialLiteTheme()),
|
||||
LanguageManager.getLoadedLanguages().getText("material_lite_theme.system.topicName"),
|
||||
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",
|
||||
false
|
||||
);
|
||||
@@ -947,7 +947,7 @@ public class AxisInnovatorsBox {
|
||||
new MaterialLookAndFeel(new MaterialOceanicTheme()),
|
||||
LanguageManager.getLoadedLanguages().getText("material_oceanic_theme.system.topicName"),
|
||||
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",
|
||||
true
|
||||
);
|
||||
|
||||
@@ -1,31 +1,35 @@
|
||||
package com.axis.innovators.box.plugins;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
/**
|
||||
* 自定义类加载器,装载CorePlugins
|
||||
* @author tzdwindows 7
|
||||
* 优化:增加缓冲读取,优化集合初始化
|
||||
*/
|
||||
public class BoxClassLoader extends URLClassLoader {
|
||||
private static final List<IClassTransformer> CLASS_TRANSFORMS = new CopyOnWriteArrayList<>();
|
||||
private static final List<String> CLASS_BLACKLIST = new ArrayList<>();
|
||||
private static final List<String> CLASS_LOADING_LIST = Collections.synchronizedList(new ArrayList<>());
|
||||
// 使用 CopyOnWriteArrayList 适合读多写少的场景
|
||||
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<>();
|
||||
|
||||
static {
|
||||
Collections.addAll(CLASS_BLACKLIST,
|
||||
// 批量添加,减少扩容开销
|
||||
List<String> blacklist = java.util.Arrays.asList(
|
||||
"java.", "javax.", "sun.", "com.sun.", "jdk.",
|
||||
"org.xml.", "org.w3c.", "org.apache.",
|
||||
"javax.management.", "javax.swing."
|
||||
, "javafx.","org.jnativehook.","com.dustinredmond."
|
||||
"javax.management.", "javax.swing.",
|
||||
"javafx.", "org.jnativehook.", "com.dustinredmond."
|
||||
);
|
||||
CLASS_BLACKLIST.addAll(blacklist);
|
||||
}
|
||||
|
||||
public BoxClassLoader(ClassLoader parent) {
|
||||
@@ -60,7 +64,7 @@ public class BoxClassLoader extends URLClassLoader {
|
||||
resolveClass(c);
|
||||
}
|
||||
return c;
|
||||
} catch (ClassNotFoundException e) {
|
||||
} catch (ClassNotFoundException | SecurityException e) {
|
||||
return super.loadClass(name, resolve);
|
||||
}
|
||||
}
|
||||
@@ -68,27 +72,30 @@ public class BoxClassLoader extends URLClassLoader {
|
||||
|
||||
@Override
|
||||
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||||
synchronized (getClassLoadingLock(name)) {
|
||||
if (CLASS_LOADING_LIST.contains(name)) {
|
||||
throw new ClassCircularityError(name + " circular loading detected");
|
||||
}
|
||||
// 移除 synchronized (getClassLoadingLock(name)),因为 loadClass 已经锁住了
|
||||
// 防止循环依赖
|
||||
if (CLASS_LOADING_LIST.contains(name)) {
|
||||
throw new ClassCircularityError(name + " circular loading detected");
|
||||
}
|
||||
|
||||
CLASS_LOADING_LIST.add(name);
|
||||
try {
|
||||
byte[] clazzByte = getClassBytes(name);
|
||||
CLASS_LOADING_LIST.add(name);
|
||||
try {
|
||||
byte[] clazzByte = getClassBytes(name);
|
||||
// 只有当存在转换器时才遍历
|
||||
if (!CLASS_TRANSFORMS.isEmpty()) {
|
||||
for (IClassTransformer transformer : CLASS_TRANSFORMS) {
|
||||
byte[] transformed = transformer.transform(name, transformer.getClass().getName(), clazzByte);
|
||||
if (transformed != null) clazzByte = transformed;
|
||||
}
|
||||
|
||||
Class<?> clazz = defineClass(name, clazzByte, 0, clazzByte.length);
|
||||
CLASS_LOADING_LIST_OBJECT.add(clazz);
|
||||
return clazz;
|
||||
} catch (IOException e) {
|
||||
throw new ClassNotFoundException("Class byte loading failed", e);
|
||||
} finally {
|
||||
CLASS_LOADING_LIST.remove(name);
|
||||
}
|
||||
|
||||
Class<?> clazz = defineClass(name, clazzByte, 0, clazzByte.length);
|
||||
CLASS_LOADING_LIST_OBJECT.add(clazz);
|
||||
return clazz;
|
||||
} catch (IOException e) {
|
||||
throw new ClassNotFoundException("Class byte loading failed: " + name, e);
|
||||
} finally {
|
||||
CLASS_LOADING_LIST.remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,12 +103,17 @@ public class BoxClassLoader extends URLClassLoader {
|
||||
String path = className.replace('.', '/') + ".class";
|
||||
try (InputStream is = getResourceAsStream(path)) {
|
||||
if (is == null) throw new ClassNotFoundException(className);
|
||||
return is.readAllBytes();
|
||||
try (BufferedInputStream bis = new BufferedInputStream(is)) {
|
||||
return bis.readAllBytes();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package com.axis.innovators.box.plugins;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 插件描述符
|
||||
* @author tzdwindows 7
|
||||
*/
|
||||
public class PluginDescriptor {
|
||||
@@ -15,21 +17,93 @@ public class PluginDescriptor {
|
||||
private String transformers;
|
||||
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
|
||||
|
||||
public String getId() { return id; }
|
||||
public void setId(String id) { this.id = id; }
|
||||
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
|
||||
public List<String> getSupportedVersions() { return supportedVersions; }
|
||||
public void setSupportedVersions(List<String> supportedVersions) { this.supportedVersions = supportedVersions; }
|
||||
|
||||
public String getIcon() { return icon; }
|
||||
public void setIcon(String icon) { this.icon = icon; }
|
||||
|
||||
public String getDescription() { return description; }
|
||||
public void setDescription(String description) { this.description = description; }
|
||||
|
||||
public Object getInstance() { return instance; }
|
||||
public void setInstance(Object instance) { this.instance = instance; }
|
||||
public String getTransformers() {return transformers;}
|
||||
public void setTransformers(String transformers) {this.transformers = transformers;}
|
||||
public String getRegistrationName() {return registrationName;}
|
||||
public void setRegistrationName(String registrationName) {this.registrationName = registrationName;}
|
||||
}
|
||||
|
||||
public String getTransformers() { return transformers; }
|
||||
public void setTransformers(String transformers) { this.transformers = transformers; }
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -6,96 +6,185 @@ import com.axis.innovators.box.tools.FolderCreator;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
/**
|
||||
* 插件加载器
|
||||
* @author tzdwindows 7
|
||||
* 插件加载器 - 性能优化版 (修复 SystemClassLoader 启动崩溃问题)
|
||||
*/
|
||||
public class PluginLoader {
|
||||
private static Logger logger;
|
||||
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<String> corePluginMainClass = new ArrayList<>();
|
||||
private static final List<String> pluginRegisteredName = new ArrayList<>();
|
||||
public static void loadPlugins() throws IOException, PluginLoadingError {
|
||||
logger = LogManager.getLogger(PluginLoader.class);
|
||||
File pluginDir = null;
|
||||
if (PLUGIN_PATH != null) {
|
||||
pluginDir = new File(PLUGIN_PATH);
|
||||
}
|
||||
File[] jars = null;
|
||||
if (pluginDir != null) {
|
||||
jars = pluginDir.listFiles((dir, name) -> name.toLowerCase().endsWith(".jar"));
|
||||
}
|
||||
// 移除 static 初始化,防止在 SystemClassLoader 构造期间触发 Log4j 初始化
|
||||
private static volatile Logger logger;
|
||||
|
||||
if (jars == null) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < jars.length; i++) {
|
||||
processJarFile(jars[i],false);
|
||||
AxisInnovatorsBox.getMain().progressBarManager.updateSubProgress(
|
||||
"Loading Plugin " + i,
|
||||
i,
|
||||
jars.length);
|
||||
public static final String PLUGIN_PATH = FolderCreator.getPluginFolder();
|
||||
|
||||
private static final List<PluginDescriptor> loadedPlugins = new CopyOnWriteArrayList<>();
|
||||
private static final List<IClassTransformer> transformers = new CopyOnWriteArrayList<>();
|
||||
private static final List<String> corePluginMainClass = new CopyOnWriteArrayList<>();
|
||||
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);
|
||||
return logger;
|
||||
} catch (Throwable t) {
|
||||
// 捕获 BootstrapMethodError 或 IllegalStateException
|
||||
// 说明此时 SystemClassLoader 正在构造中,不能使用 Log4j
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void processJarFile(File jarFile, boolean isCorePlugin) throws IOException, PluginLoadingError {
|
||||
private static void logInfo(String msg, Object... args) {
|
||||
Logger l = getLogger();
|
||||
if (l != null) {
|
||||
l.info(msg, args);
|
||||
} else {
|
||||
// Fallback: 在 Log4j 准备好之前使用 System.out
|
||||
System.out.println("[PluginLoader-INFO] " + formatFallback(msg, args));
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
"Loading Plugin " + current, current, total);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void processJarFile(File jarFile, boolean isCorePlugin) throws IOException {
|
||||
try (JarFile jar = new JarFile(jarFile)) {
|
||||
// Check for CorePlugin in MANIFEST.MF
|
||||
if (isCorePlugin) {
|
||||
Attributes attributes = jar.getManifest().getMainAttributes();
|
||||
String corePluginClass = attributes.getValue("CorePlugin");
|
||||
if (corePluginClass != null) {
|
||||
processCorePlugin(jarFile, corePluginClass);
|
||||
}
|
||||
} else {
|
||||
JarEntry pluginFile = jar.getJarEntry("plug-in.box");
|
||||
if (pluginFile != null) {
|
||||
processWithManifest(jar, pluginFile, jarFile);
|
||||
} else {
|
||||
processWithAnnotations(jar, jarFile);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
JarEntry pluginFile = jar.getJarEntry("plug-in.box");
|
||||
PluginDescriptor descriptor = null;
|
||||
|
||||
if (pluginFile != null) {
|
||||
descriptor = processWithManifest(jar, pluginFile, jarFile);
|
||||
} else {
|
||||
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 {
|
||||
File pluginDir = null;
|
||||
if (PLUGIN_PATH != null) {
|
||||
pluginDir = new File(PLUGIN_PATH);
|
||||
}
|
||||
File[] jars = null;
|
||||
if (pluginDir != null) {
|
||||
jars = pluginDir.listFiles((dir, name) -> name.toLowerCase().endsWith(".jar"));
|
||||
}
|
||||
if (jars == null) {
|
||||
return;
|
||||
}
|
||||
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) return;
|
||||
|
||||
for (File jar : jars) {
|
||||
try {
|
||||
processJarFile(jar, true);
|
||||
} catch (PluginLoadingError e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
processJarFile(jar, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,124 +199,185 @@ public class PluginLoader {
|
||||
registerTransformers(corePlugin, urlClassLoader);
|
||||
}
|
||||
} 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) {
|
||||
//if (corePlugin.getMainClass() != null) {
|
||||
if (corePlugin.getMainClass() != null) {
|
||||
corePluginMainClass.add(corePlugin.getMainClass());
|
||||
String[] transformerClasses = corePlugin.getASMTransformerClass();
|
||||
if (transformerClasses != null) {
|
||||
for (String transformerClass : transformerClasses) {
|
||||
try {
|
||||
Class<?> transformerClazz = classLoader.loadClass(transformerClass);
|
||||
if (IClassTransformer.class.isAssignableFrom(transformerClazz)) {
|
||||
IClassTransformer transformer = (IClassTransformer) transformerClazz.getDeclaredConstructor().newInstance();
|
||||
transformers.add(transformer);
|
||||
BoxClassLoader.addClassTransformer(transformer);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to register transformer: {}", transformerClass, e);
|
||||
}
|
||||
String[] transformerClasses = corePlugin.getASMTransformerClass();
|
||||
if (transformerClasses != null) {
|
||||
for (String transformerClass : transformerClasses) {
|
||||
try {
|
||||
Class<?> transformerClazz = classLoader.loadClass(transformerClass);
|
||||
if (IClassTransformer.class.isAssignableFrom(transformerClazz)) {
|
||||
IClassTransformer transformer = (IClassTransformer) transformerClazz.getDeclaredConstructor().newInstance();
|
||||
transformers.add(transformer);
|
||||
BoxClassLoader.addClassTransformer(transformer);
|
||||
}
|
||||
} catch (Exception 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 {
|
||||
Properties props = new Properties();
|
||||
try (InputStream is = jar.getInputStream(entry)) {
|
||||
props.load(is);
|
||||
} catch (IOException e) {
|
||||
logger.error("Failed to load plugin from jar: {}", jarFile.getName(), e);
|
||||
return;
|
||||
}
|
||||
|
||||
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.setName(props.getProperty("name"));
|
||||
descriptor.setDescription(props.getProperty("description"));
|
||||
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");
|
||||
verifyRegisteredNameValid(descriptor, registrationName);
|
||||
|
||||
logger.info("Loaded plugin: {}", descriptor.getName());
|
||||
loadMainClass(props.getProperty("mainClass"), jarFile, descriptor);
|
||||
logInfo("Loaded plugin via manifest: {}", descriptor.getName());
|
||||
loadMainClass(props.getProperty("mainClass"), descriptor);
|
||||
|
||||
Attributes attributes = jar.getManifest().getMainAttributes();
|
||||
String corePluginClass = attributes.getValue("CorePlugin");
|
||||
descriptor.setTransformers(corePluginClass);
|
||||
loadPluginLanguages(jarFile, descriptor);
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
private static void verifyRegisteredNameValid(PluginDescriptor descriptor, String registrationName) throws PluginLoadingError {
|
||||
if (registrationName != null && !registrationName.isEmpty()
|
||||
&& !pluginRegisteredName.contains(registrationName)) {
|
||||
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");
|
||||
if (registrationName == null || registrationName.trim().isEmpty()) {
|
||||
throw new PluginLoadingError("Invalid registration name for plugin: " + descriptor.getName());
|
||||
}
|
||||
|
||||
synchronized (pluginRegisteredName) {
|
||||
if (!pluginRegisteredName.add(registrationName)) {
|
||||
throw new PluginLoadingError("Duplicate registered name '" + registrationName + "' in plugin " + descriptor.getName());
|
||||
}
|
||||
}
|
||||
descriptor.setRegistrationName(registrationName);
|
||||
}
|
||||
|
||||
private static void processWithAnnotations(JarFile jar, File jarFile) throws PluginLoadingError {
|
||||
URLClassLoader classLoader = createClassLoader(jarFile);
|
||||
private static PluginDescriptor processWithAnnotations(JarFile jar, File jarFile) throws PluginLoadingError {
|
||||
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();
|
||||
while (entries.hasMoreElements()) {
|
||||
JarEntry entry = entries.nextElement();
|
||||
if (!entry.isDirectory() && entry.getName().endsWith(".class")) {
|
||||
String className = entry.getName()
|
||||
.replace("/", ".")
|
||||
.replace(".class", "");
|
||||
|
||||
try {
|
||||
if (className.equals("module-info")) continue;
|
||||
|
||||
Class<?> clazz = classLoader.loadClass(className);
|
||||
PluginMeta meta = clazz.getAnnotation(PluginMeta.class);
|
||||
|
||||
if (meta != null) {
|
||||
PluginDescriptor descriptor = new PluginDescriptor();
|
||||
descriptor.setSourceLocation(jarFile.toURI().toURL());
|
||||
|
||||
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) {
|
||||
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();
|
||||
if (entry.getName().endsWith(".class")) {
|
||||
if (classLoader != null) {
|
||||
processClassEntry(entry, jar,classLoader,jarFile);
|
||||
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 loadPluginLanguages(File jarFile, PluginDescriptor plugin) {
|
||||
try (JarFile jar = new JarFile(jarFile)) {
|
||||
Enumeration<JarEntry> entries = jar.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
JarEntry entry = entries.nextElement();
|
||||
if (entry.getName().startsWith("lang/") &&
|
||||
entry.getName().endsWith(".properties")) {
|
||||
processLanguageFile(jar, entry, plugin);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("Failed to load language files for plugin: {}", plugin.getName(), e);
|
||||
}
|
||||
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) {
|
||||
String fileName = entry.getName().substring(entry.getName().lastIndexOf("/") + 1);
|
||||
String[] parts = fileName.split("[_.]");
|
||||
if (parts.length < 3) {
|
||||
return;
|
||||
}
|
||||
String langCode = parts[parts.length-1].replace(".properties", "");
|
||||
if (parts.length < 3) return;
|
||||
|
||||
String langCode = parts[parts.length - 1].replace(".properties", "");
|
||||
String langName = plugin.getName() + " " + langCode.toUpperCase();
|
||||
String registeredName = plugin.getRegistrationName() + "_" + langCode;
|
||||
|
||||
try (InputStream is = jar.getInputStream(entry)) {
|
||||
Properties properties2 = new Properties();
|
||||
properties2.load(new InputStreamReader(is, StandardCharsets.UTF_8));
|
||||
|
||||
LanguageManager.Language language = new LanguageManager.Language(
|
||||
langName,
|
||||
registeredName,
|
||||
null
|
||||
langName, registeredName, null
|
||||
) {
|
||||
@Override
|
||||
public void loadLanguageFile(String languageFile) {
|
||||
@@ -235,97 +385,42 @@ public class PluginLoader {
|
||||
}
|
||||
};
|
||||
LanguageManager.addLanguage(language);
|
||||
logger.info("Loaded plugin language: {} ({})", langName, registeredName);
|
||||
logDebug("Loaded plugin language: {} ({})", langName, registeredName);
|
||||
} 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) {
|
||||
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) {
|
||||
private static void loadMainClass(String mainClassName, PluginDescriptor descriptor) {
|
||||
if (mainClassName == null || mainClassName.isEmpty()) {
|
||||
logger.error("Invalid main class name: {}", mainClassName);
|
||||
return;
|
||||
}
|
||||
|
||||
try (URLClassLoader classLoader = new URLClassLoader(
|
||||
new URL[]{jarFile.toURI().toURL()},
|
||||
PluginLoader.class.getClassLoader())
|
||||
) {
|
||||
ClassLoader classLoader = descriptor.getClassLoader();
|
||||
if (classLoader == null) {
|
||||
logError("Plugin ClassLoader is missing for {}", descriptor.getName());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Class<?> mainClass = classLoader.loadClass(mainClassName);
|
||||
Object instance = mainClass.getDeclaredConstructor().newInstance();
|
||||
descriptor.setInstance(instance);
|
||||
|
||||
try {
|
||||
Field pluginInstance = mainClass.getDeclaredField("INSTANCE");
|
||||
pluginInstance.setAccessible(true);
|
||||
pluginInstance.set(null, descriptor);
|
||||
} catch (NoSuchFieldException | IllegalArgumentException
|
||||
| SecurityException | IllegalAccessException e) {
|
||||
logger.warn("Failed to set plugin instance: {}", mainClassName, e);
|
||||
}
|
||||
injectInstanceField(mainClass, instance, descriptor);
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to load main class: {}", mainClassName, e);
|
||||
logError("Failed to load main class: {}", mainClassName, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void injectInstanceField(Class<?> clazz, Object instance, PluginDescriptor descriptor) {
|
||||
try {
|
||||
Field pluginInstance = clazz.getDeclaredField("INSTANCE");
|
||||
pluginInstance.setAccessible(true);
|
||||
pluginInstance.set(null, descriptor);
|
||||
} catch (NoSuchFieldException e) {
|
||||
// 忽略
|
||||
} catch (Exception e) {
|
||||
logWarn("Failed to set plugin instance field for {}", clazz.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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.tools.FolderCreator;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
@@ -27,14 +28,54 @@ public class LanguageManager {
|
||||
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 要添加的语言对象
|
||||
* 处理规则:
|
||||
* 1. 注册名相同时保留原有基础信息
|
||||
* 2. 相同键值的新内容覆盖旧内容
|
||||
* 3. 详细记录新增、更新的键值数量
|
||||
* 4. 提供关键示例用于调试
|
||||
*/
|
||||
public static void addLanguage(Language language) {
|
||||
for (Language existing : LANGUAGES) {
|
||||
@@ -61,19 +102,21 @@ public class LanguageManager {
|
||||
existing.properties.setProperty(key, newValue);
|
||||
}
|
||||
|
||||
String logDetail = buildMergeLogDetails(added, updated, sampleAddedKeys, sampleUpdatedKeys);
|
||||
|
||||
logger.info("【语言合并报告】\n" +
|
||||
"▌合并目标:{} ({})\n" +
|
||||
"▌变更统计:新增 {} 条 / 更新 {} 条\n" +
|
||||
"▌当前总量:{} 条\n" +
|
||||
"▌关键示例:\n{}",
|
||||
existing.getLanguageName(),
|
||||
existing.getRegisteredName(),
|
||||
added,
|
||||
updated,
|
||||
existing.properties.size(),
|
||||
logDetail);
|
||||
// 仅当有变更时才构建日志,减少刷屏(可选优化)
|
||||
if (added > 0 || updated > 0) {
|
||||
String logDetail = buildMergeLogDetails(added, updated, sampleAddedKeys, sampleUpdatedKeys);
|
||||
logger.info("【语言合并报告】\n" +
|
||||
"▌合并来源:{} -> {}\n" +
|
||||
"▌变更统计:新增 {} 条 / 更新 {} 条\n" +
|
||||
"▌当前总量:{} 条\n" +
|
||||
"▌关键示例:\n{}",
|
||||
language.getLanguageName(),
|
||||
existing.getRegisteredName(),
|
||||
added,
|
||||
updated,
|
||||
existing.properties.size(),
|
||||
logDetail);
|
||||
}
|
||||
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
@@ -122,54 +165,43 @@ public class LanguageManager {
|
||||
*/
|
||||
public static void loadLanguage(String languageName) {
|
||||
loadedLanguages = LanguageManager.getLanguage(languageName);
|
||||
saveCurrentLanguageToFile();
|
||||
if (loadedLanguages != null) {
|
||||
saveCurrentLanguageToFile();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将当前加载的语言保存到配置文件中
|
||||
*/
|
||||
private static void saveCurrentLanguageToFile() {
|
||||
if (loadedLanguages == null) return;
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("loadedLanguage", loadedLanguages.getRegisteredName());
|
||||
|
||||
try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(SAVED_LANGUAGE_FILE), StandardCharsets.UTF_8)) {
|
||||
properties.store(writer, "Current Loaded Language");
|
||||
} catch (IOException e) {
|
||||
System.err.println("Failed to save current language to file: " + SAVED_LANGUAGE_FILE);
|
||||
e.printStackTrace();
|
||||
logger.error("Failed to save current language to file: {}", SAVED_LANGUAGE_FILE, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从配置文件中加载保存的语言
|
||||
*/
|
||||
public static void loadSavedLanguage() {
|
||||
File file = new File(SAVED_LANGUAGE_FILE);
|
||||
if (!file.exists()) return;
|
||||
|
||||
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);
|
||||
String loadedLanguageName = properties.getProperty("loadedLanguage");
|
||||
if (loadedLanguageName != null) {
|
||||
loadedLanguages = LanguageManager.getLanguage(loadedLanguageName);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.err.println("Failed to load saved language from file: " + SAVED_LANGUAGE_FILE);
|
||||
e.printStackTrace();
|
||||
logger.error("Failed to load saved language from file: {}", SAVED_LANGUAGE_FILE, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已加载的语言
|
||||
* @return 已加载的语言
|
||||
*/
|
||||
public static Language getLoadedLanguages() {
|
||||
return loadedLanguages;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取语言
|
||||
* @param languageName 语言注册名,或者语言名称
|
||||
* @return 语言对象
|
||||
*/
|
||||
public static Language getLanguage(String languageName) {
|
||||
for (Language language : LANGUAGES) {
|
||||
if (language.getRegisteredName().equals(languageName)) {
|
||||
@@ -185,14 +217,14 @@ public class LanguageManager {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有语言
|
||||
* @return 所有语言
|
||||
*/
|
||||
public static List<Language> getLanguages() {
|
||||
return LANGUAGES;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Inner Class Language
|
||||
// ==========================================
|
||||
|
||||
public static class Language {
|
||||
private final String languageName;
|
||||
private final String registeredName;
|
||||
@@ -210,14 +242,19 @@ public class LanguageManager {
|
||||
this.languageFile = LANGUAGE_PATH + "\\" + languageFileName + ".properties";
|
||||
}
|
||||
this.properties = new Properties();
|
||||
|
||||
// 默认构造时加载文件
|
||||
loadLanguageFile(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)) {
|
||||
properties.load(reader);
|
||||
} catch (IOException e) {
|
||||
@@ -227,10 +264,19 @@ public class LanguageManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定键的文本
|
||||
* @param key 键
|
||||
* @return 对应的文本,如果键不存在则返回 null
|
||||
* 新增:直接从输入流加载配置(供插件使用)
|
||||
* @param inputStream 资源流
|
||||
*/
|
||||
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) {
|
||||
if (!properties.containsKey(key)) {
|
||||
return key;
|
||||
@@ -238,9 +284,6 @@ public class LanguageManager {
|
||||
return properties.getProperty(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加文本
|
||||
*/
|
||||
public void addText(String key, String value) {
|
||||
properties.setProperty(key, value);
|
||||
}
|
||||
@@ -257,4 +300,4 @@ public class LanguageManager {
|
||||
return new File(languageFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -465,7 +465,7 @@ public class RegistrationSettingsItem extends WindowsJDialog {
|
||||
|
||||
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);
|
||||
iconPanel.add(iconLabel);
|
||||
mainPanel.add(iconPanel, BorderLayout.WEST);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.axis.innovators.box.window;
|
||||
|
||||
import com.axis.innovators.box.plugins.PluginDescriptor;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
@@ -11,67 +12,180 @@ import java.net.URL;
|
||||
|
||||
/**
|
||||
* 负责加载图片
|
||||
* <p>
|
||||
* 更新说明:已适配 PluginDescriptor。
|
||||
* </p>
|
||||
* @author tzdwindows 7
|
||||
*/
|
||||
public class LoadIcon {
|
||||
private static final Logger logger = LogManager.getLogger(LoadIcon.class);
|
||||
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 size 图片大小
|
||||
* @return ImageIcon对象
|
||||
*/
|
||||
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 width 宽度
|
||||
* @param height 高度
|
||||
* @return ImageIcon对象
|
||||
*/
|
||||
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 width 目标宽度
|
||||
* @param height 目标高度
|
||||
* @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 {
|
||||
if (filename == null || filename.isEmpty()) {
|
||||
return createPlaceholderIcon(width, height);
|
||||
}
|
||||
|
||||
Image image;
|
||||
// 1. 处理绝对路径
|
||||
if (new File(filename).isAbsolute()) {
|
||||
image = new ImageIcon(filename).getImage();
|
||||
} else {
|
||||
// 2. 处理资源路径
|
||||
String fullPath = ICON_PATH + filename;
|
||||
URL imgUrl = clazz.getResource(fullPath);
|
||||
if (imgUrl == null) {
|
||||
// 尝试不带 /icons/ 路径直接加载 (兼容 loadIcon0 逻辑)
|
||||
imgUrl = clazz.getResource(filename);
|
||||
}
|
||||
|
||||
if (imgUrl == null) {
|
||||
logger.warn("Resource not found: {}", filename);
|
||||
return createPlaceholderIcon(width, height);
|
||||
}
|
||||
image = new ImageIcon(imgUrl).getImage();
|
||||
// 1. 处理绝对路径 (不依赖 ClassLoader)
|
||||
File file = new File(filename);
|
||||
if (file.isAbsolute() && file.exists()) {
|
||||
Image image = new ImageIcon(filename).getImage();
|
||||
return new ImageIcon(image.getScaledInstance(width, height, Image.SCALE_SMOOTH));
|
||||
}
|
||||
|
||||
// 2. 处理资源路径
|
||||
URL imgUrl = null;
|
||||
|
||||
// 尝试 A: /icons/ + filename (标准化路径)
|
||||
String fullPath = ICON_PATH + filename;
|
||||
// 去掉开头的 / 因为 ClassLoader.getResource 不以 / 开头
|
||||
if (fullPath.startsWith("/")) fullPath = fullPath.substring(1);
|
||||
|
||||
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) {
|
||||
logger.warn("Resource not found: {}", filename);
|
||||
return createPlaceholderIcon(width, height);
|
||||
}
|
||||
|
||||
Image image = new ImageIcon(imgUrl).getImage();
|
||||
// 3. 执行高质量缩放
|
||||
return new ImageIcon(image.getScaledInstance(width, height, Image.SCALE_SMOOTH));
|
||||
|
||||
@@ -88,82 +202,14 @@ public class LoadIcon {
|
||||
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
||||
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.setColor(Color.RED);
|
||||
g2d.drawRect(0, 0, width - 1, height - 1);
|
||||
|
||||
g2d.dispose();
|
||||
return new ImageIcon(img);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载图片
|
||||
* @param clazz resources包所在的jar
|
||||
* @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();
|
||||
return new ImageIcon(img);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
# Auto-generated build information
|
||||
version=0.0.1
|
||||
buildTimestamp=2026-01-02T17:08:37.387878
|
||||
buildTimestamp=2026-01-02T17:46:06.8226378
|
||||
buildSystem=WINDOWS
|
||||
|
||||
Reference in New Issue
Block a user