feat(plugin): 添加 Python 插件支持
- 新增 PluginPyLoader 类实现 Python 插件加载- 添加 PythonResult 类封装 Python执行结果 - 实现 RunPython 工具类用于执行 Python脚本 - 新增 PyLocalSide 类提供 Python 脚本调用本地方法的接口- 修改主程序启动逻辑,支持 Python 插件初始化 - 更新插件注册设置项,兼容 Python 插件
This commit is contained in:
4
.idea/misc.xml
generated
4
.idea/misc.xml
generated
@@ -1,3 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ASMPluginConfiguration">
|
||||
<asm skipDebug="false" skipFrames="false" skipCode="false" expandFrames="false" />
|
||||
@@ -7,6 +8,9 @@
|
||||
<asm skipDebug="true" skipFrames="true" skipCode="false" expandFrames="false" />
|
||||
<groovy codeStyle="LEGACY" />
|
||||
</component>
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.12 (NIGANMA)" />
|
||||
</component>
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="FindBugsConfigurable">
|
||||
<option name="make" value="true" />
|
||||
|
||||
17
build.gradle
17
build.gradle
@@ -15,10 +15,17 @@ group = 'com.axis.innovators.box'
|
||||
version = '0.0.1'
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
maven { url "https://maven.aliyun.com/repository/public" }
|
||||
maven { setUrl("https://maven.aliyun.com/repository/central") }
|
||||
maven { setUrl("https://maven.aliyun.com/repository/jcenter") }
|
||||
maven { setUrl("https://maven.aliyun.com/repository/google") }
|
||||
maven { setUrl("https://maven.aliyun.com/repository/gradle-plugin") }
|
||||
maven { setUrl("https://maven.aliyun.com/repository/public") }
|
||||
maven { setUrl("https://jitpack.io") }
|
||||
maven { setUrl("https://maven.aliyun.com/nexus/content/groups/public/") }
|
||||
maven { setUrl("https://maven.aliyun.com/nexus/content/repositories/jcenter") }
|
||||
gradlePluginPortal()
|
||||
google()
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -48,6 +55,10 @@ dependencies {
|
||||
implementation 'com.formdev:flatlaf:3.2.1' // FlatLaf核心
|
||||
implementation 'com.formdev:flatlaf-extras:3.2.1' // 扩展组件
|
||||
implementation 'com.formdev:flatlaf-intellij-themes:3.2.1' // 官方主题包
|
||||
|
||||
implementation 'org.python:jython-standalone:2.7.3'
|
||||
|
||||
implementation 'com.google.code.gson:gson:2.10.1'
|
||||
}
|
||||
|
||||
// 分离依赖项到 libs 目录
|
||||
|
||||
62
plug-in/python/Testing/main.py
Normal file
62
plug-in/python/Testing/main.py
Normal file
@@ -0,0 +1,62 @@
|
||||
"""
|
||||
工具模块初始化脚本
|
||||
功能:向Axis Innovators Box注册自定义工具类别和工具项
|
||||
作者:tzdwindows 7
|
||||
版本:1.1
|
||||
"""
|
||||
from com.axis.innovators.box.python import PyLocalSide
|
||||
from javax.swing import AbstractAction
|
||||
|
||||
class MyAction(AbstractAction):
|
||||
def actionPerformed(self, event):
|
||||
"""工具项点击事件处理"""
|
||||
print("[DEBUG] Tool item clicked! Event source:", event.getSource())
|
||||
|
||||
def onStartup():
|
||||
"""
|
||||
系统启动时自动执行的初始化逻辑
|
||||
功能:
|
||||
1. 创建工具类别
|
||||
2. 创建工具项并绑定动作
|
||||
3. 注册到系统全局工具集
|
||||
"""
|
||||
print('[INFO] 正在初始化自定义工具...')
|
||||
|
||||
# --------------------------
|
||||
# 创建工具类别(参数顺序:显示名称,图标资源名,描述)
|
||||
# --------------------------
|
||||
tool_category = PyLocalSide.getToolCategory(
|
||||
u"数据分析工具", # 显示名称(GUI可见)
|
||||
u"analytics_icon.png", # 图标文件名(需存在于资源目录)
|
||||
u"高级数据分析功能集合" # 悬停提示描述
|
||||
)
|
||||
|
||||
# --------------------------
|
||||
# 创建工具项(参数顺序:显示名称,图标,描述,ID,动作对象)
|
||||
# --------------------------
|
||||
tool_action = MyAction()
|
||||
tool_item = PyLocalSide.getToolItem(
|
||||
u"数据可视化", # 工具项显示名称
|
||||
u"chart_icon.png", # 工具项图标
|
||||
u"生成交互式数据图表", # 工具项描述
|
||||
1001, # 工具项唯一ID(需在配置中统一管理)
|
||||
tool_action # 点击触发的动作
|
||||
)
|
||||
tool_category.addTool(tool_item)
|
||||
|
||||
# --------------------------
|
||||
# 注册工具类别到系统(参数:类别对象,全局唯一注册名称)
|
||||
# --------------------------
|
||||
PyLocalSide.addToolCategory(
|
||||
tool_category,
|
||||
u"custom_module::data_analysis_tools" # 推荐命名规则:模块名::功能名
|
||||
)
|
||||
print('[SUCCESS] 工具类别注册成功')
|
||||
|
||||
if __name__ == '__main__':
|
||||
result = 0
|
||||
errorResult = ""
|
||||
|
||||
# 确保Jython运行时可以访问onStartup函数
|
||||
# 原理:将函数显式绑定到全局字典
|
||||
globals()['onStartup'] = onStartup
|
||||
13
plug-in/python/Testing/metadata.json
Normal file
13
plug-in/python/Testing/metadata.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"id": "testing",
|
||||
"name": "测试",
|
||||
"version": "0.0.1",
|
||||
"description": "测试插件",
|
||||
"author": "tzdwindows 7",
|
||||
"dependencies": [],
|
||||
|
||||
"_comment": {
|
||||
"warning": "本文件为插件元数据配置,修改后需重启应用生效",
|
||||
"path": "插件资源应放置在plugins/{id}/目录下"
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import com.axis.innovators.box.events.StartupEvent;
|
||||
import com.axis.innovators.box.gui.*;
|
||||
import com.axis.innovators.box.plugins.PluginDescriptor;
|
||||
import com.axis.innovators.box.plugins.PluginLoader;
|
||||
import com.axis.innovators.box.plugins.PluginPyLoader;
|
||||
import com.axis.innovators.box.register.RegistrationSettingsItem;
|
||||
import com.axis.innovators.box.register.RegistrationTool;
|
||||
import com.axis.innovators.box.register.RegistrationTopic;
|
||||
@@ -14,6 +15,7 @@ import com.axis.innovators.box.tools.ArgsParser;
|
||||
import com.axis.innovators.box.tools.LibraryLoad;
|
||||
import com.axis.innovators.box.tools.StateManager;
|
||||
import com.axis.innovators.box.tools.SystemInfoUtil;
|
||||
import com.axis.innovators.box.util.PythonResult;
|
||||
import com.formdev.flatlaf.FlatLightLaf;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@@ -24,7 +26,6 @@ import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -34,7 +35,7 @@ import java.util.Map;
|
||||
*/
|
||||
public class AxisInnovatorsBox {
|
||||
private static final Logger logger = LogManager.getLogger(AxisInnovatorsBox.class);
|
||||
private static final String VERSIONS = "0.0.1";
|
||||
private static final String VERSIONS = "0.0.2";
|
||||
private static final String[] AUTHOR = new String[]{
|
||||
"tzdwindows 7"
|
||||
};
|
||||
@@ -60,6 +61,7 @@ public class AxisInnovatorsBox {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
|
||||
static {
|
||||
try {
|
||||
LibraryLoad.loadLibrary("FridaNative");
|
||||
@@ -349,6 +351,7 @@ public class AxisInnovatorsBox {
|
||||
logger.info("Loaded plugins Started");
|
||||
main.progressBarManager.updateMainProgress(++main.completedTasks);
|
||||
PluginLoader.loadPlugins();
|
||||
PluginPyLoader.loadAllPlugins();
|
||||
logger.info("Loaded plugins End");
|
||||
|
||||
main.progressBarManager.close();
|
||||
@@ -357,6 +360,16 @@ public class AxisInnovatorsBox {
|
||||
try {
|
||||
main.ex = new MainWindow();
|
||||
GlobalEventBus.EVENT_BUS.post(new StartupEvent(main));
|
||||
PluginPyLoader.getLoadedPlugins().forEach((pluginId, pyPluginDescriptor) -> {
|
||||
if (pyPluginDescriptor.getLastResult().getInterpreter() != null){
|
||||
PythonResult result = pyPluginDescriptor.getLastResult();
|
||||
try {
|
||||
result.callFunction("onStartup");
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to call onStartup function", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
main.runWindow();
|
||||
} catch (Exception e) {
|
||||
logger.error("There was a problem starting the main thread", e);
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.apache.logging.log4j.Logger;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
@@ -35,11 +36,16 @@ public class LoadIcon {
|
||||
* @param size 图片大小
|
||||
* @return ImageIcon对象
|
||||
*/
|
||||
public static ImageIcon loadIcon(Class<?> clazz ,String filename, int size) {
|
||||
public static ImageIcon loadIcon(Class<?> clazz, String filename, int size) {
|
||||
try {
|
||||
if (filename.isEmpty()){
|
||||
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) {
|
||||
@@ -53,6 +59,16 @@ public class LoadIcon {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.axis.innovators.box.plugins;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class PluginMetadata {
|
||||
@SerializedName("id")
|
||||
private String id;
|
||||
|
||||
@SerializedName("name")
|
||||
private String name;
|
||||
|
||||
@SerializedName("version")
|
||||
private String version = "1.0.0";
|
||||
|
||||
@SerializedName("description")
|
||||
private String description = "";
|
||||
|
||||
@SerializedName("author")
|
||||
private String author = "Unknown";
|
||||
|
||||
@SerializedName("dependencies")
|
||||
private List<String> dependencies = new ArrayList<>();
|
||||
|
||||
public String getId() { return id; }
|
||||
public String getName() { return name; }
|
||||
public String getVersion() { return version; }
|
||||
public String getDescription() { return description; }
|
||||
public String getAuthor() { return author; }
|
||||
public List<String> getDependencies() { return dependencies; }
|
||||
|
||||
public boolean isValid() {
|
||||
return id != null && !id.isEmpty() &&
|
||||
name != null && !name.isEmpty() &&
|
||||
version != null && !version.isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
package com.axis.innovators.box.plugins;
|
||||
|
||||
import com.axis.innovators.box.AxisInnovatorsBox;
|
||||
import com.axis.innovators.box.tools.FolderCreator;
|
||||
import com.axis.innovators.box.util.PythonResult;
|
||||
import com.axis.innovators.box.util.RunPython;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author tzdwindows 7
|
||||
*/
|
||||
public class PluginPyLoader {
|
||||
public static final String PLUGIN_PATH = FolderCreator.getPyPluginFolder();
|
||||
private static final Logger LOGGER = LogManager.getLogger(PluginPyLoader.class);
|
||||
private static final Map<String, PyPluginDescriptor> loadedPlugins = new HashMap<>();
|
||||
private static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors();
|
||||
|
||||
/**
|
||||
* Python插件描述类
|
||||
*/
|
||||
public static class PyPluginDescriptor {
|
||||
private final String id;
|
||||
private final String name;
|
||||
private final Path scriptPath;
|
||||
private final Path pluginRoot;
|
||||
private boolean enabled;
|
||||
private PythonResult lastResult;
|
||||
|
||||
public PyPluginDescriptor(String id, String name, Path pluginRoot) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.pluginRoot = pluginRoot;
|
||||
this.scriptPath = pluginRoot.resolve("main.py");
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
// Getters
|
||||
public String getId() { return id; }
|
||||
public String getName() { return name; }
|
||||
public Path getScriptPath() { return scriptPath; }
|
||||
public boolean isEnabled() { return enabled; }
|
||||
public PythonResult getLastResult() { return lastResult; }
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载所有Python插件
|
||||
*/
|
||||
public static void loadAllPlugins() {
|
||||
if (PLUGIN_PATH == null || PLUGIN_PATH.isEmpty()) {
|
||||
LOGGER.warn("Python plugin path not configured");
|
||||
return;
|
||||
}
|
||||
|
||||
try (Stream<Path> pathStream = Files.walk(Path.of(PLUGIN_PATH), 3)) {
|
||||
List<Path> pluginPaths = pathStream
|
||||
.filter(path -> path.endsWith("main.py"))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
ExecutorService executor = Executors.newFixedThreadPool(CORE_POOL_SIZE);
|
||||
CountDownLatch latch = new CountDownLatch(pluginPaths.size());
|
||||
AtomicInteger progressCounter = new AtomicInteger(0);
|
||||
int total = pluginPaths.size();
|
||||
|
||||
for (Path path : pluginPaths) {
|
||||
executor.submit(() -> {
|
||||
try {
|
||||
loadPlugin(path);
|
||||
int currentProgress = progressCounter.incrementAndGet();
|
||||
updateProgress(currentProgress, total);
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
latch.await(5, TimeUnit.MINUTES);
|
||||
executor.shutdownNow();
|
||||
} catch (IOException | InterruptedException e) {
|
||||
LOGGER.error("Plugin loading interrupted", e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateProgress(int current, int total) {
|
||||
AxisInnovatorsBox.getMain().progressBarManager.updateSubProgress(
|
||||
"Loading Python Plugins",
|
||||
current,
|
||||
total
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载单个Python插件
|
||||
*/
|
||||
private static void loadPlugin(Path scriptPath) {
|
||||
try {
|
||||
Path pluginRoot = scriptPath.getParent();
|
||||
|
||||
PyPluginDescriptor descriptor = readPluginMetadata(pluginRoot);
|
||||
|
||||
List<String> dependencies = new ArrayList<>();
|
||||
dependencies.add(pluginRoot.toString());
|
||||
Path libsPath = pluginRoot.resolve("libs");
|
||||
if (Files.exists(libsPath)) {
|
||||
dependencies.add(libsPath.toString());
|
||||
}
|
||||
|
||||
PythonResult result = RunPython.executeWithJython(
|
||||
scriptPath.toString(),
|
||||
dependencies
|
||||
);
|
||||
|
||||
descriptor.lastResult = result;
|
||||
descriptor.enabled = result.getExitCode() == 0;
|
||||
|
||||
loadedPlugins.put(descriptor.id, descriptor);
|
||||
LOGGER.info("Loaded Python plugin: {} - {}", descriptor.id, descriptor.name);
|
||||
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to load plugin at {}", scriptPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取插件元数据(从metadata.json)
|
||||
*/
|
||||
private static PyPluginDescriptor readPluginMetadata(Path pluginRoot) throws IOException {
|
||||
String pluginId = pluginRoot.getFileName().toString();
|
||||
String pluginName = pluginId;
|
||||
Gson gson = new Gson();
|
||||
Path metaFile = pluginRoot.resolve("metadata.json");
|
||||
if (Files.exists(metaFile)) {
|
||||
try (Reader reader = Files.newBufferedReader(metaFile)) {
|
||||
PluginMetadata meta = gson.fromJson(reader, PluginMetadata.class);
|
||||
if (meta.getId() != null && !meta.getId().isEmpty()) {
|
||||
pluginId = meta.getId();
|
||||
}
|
||||
if (meta.getName() != null && !meta.getName().isEmpty()) {
|
||||
pluginName = meta.getName();
|
||||
}
|
||||
if (!meta.getDependencies().isEmpty()) {
|
||||
LOGGER.info("Plugin {} requires dependencies: {}", pluginId, meta.getDependencies());
|
||||
}
|
||||
} catch (JsonSyntaxException e) {
|
||||
LOGGER.error("Invalid JSON format in {}", metaFile, e);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Failed to read {}", metaFile, e);
|
||||
}
|
||||
}
|
||||
|
||||
return new PyPluginDescriptor(pluginId, pluginName, pluginRoot);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有已加载插件
|
||||
*/
|
||||
public static Map<String, PyPluginDescriptor> getLoadedPlugins() {
|
||||
return new HashMap<>(loadedPlugins);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新加载指定插件
|
||||
*/
|
||||
public static void reloadPlugin(String pluginId) {
|
||||
PyPluginDescriptor descriptor = loadedPlugins.get(pluginId);
|
||||
if (descriptor != null) {
|
||||
loadedPlugins.remove(pluginId);
|
||||
loadPlugin(descriptor.scriptPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.axis.innovators.box.python;
|
||||
|
||||
import com.axis.innovators.box.AxisInnovatorsBox;
|
||||
import com.axis.innovators.box.gui.MainWindow;
|
||||
import com.axis.innovators.box.register.LanguageManager;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
|
||||
/**
|
||||
* 在py脚本的程序调用本程序注册各种信息
|
||||
* @author tzdwindows 7
|
||||
*/
|
||||
public class PyLocalSide {
|
||||
public static void addLanguage(String languageName,
|
||||
String registeredName,
|
||||
String languageFileName) {
|
||||
LanguageManager.Language language = new LanguageManager.Language(
|
||||
languageName,
|
||||
registeredName,
|
||||
languageFileName
|
||||
);
|
||||
LanguageManager.addLanguage(language);
|
||||
}
|
||||
public static MainWindow.ToolCategory getToolCategory(String name,
|
||||
String icon,
|
||||
String description) {
|
||||
|
||||
return new MainWindow.ToolCategory(name, icon, description);
|
||||
}
|
||||
|
||||
public static Action getAction(Runnable runnable){
|
||||
return new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
runnable.run();
|
||||
}
|
||||
};
|
||||
}
|
||||
public static MainWindow.ToolItem getToolItem(String title,
|
||||
String icon,
|
||||
String description,
|
||||
int id,
|
||||
Action action){
|
||||
System.out.println(action);
|
||||
return new MainWindow.ToolItem(title, icon, description, id, action);
|
||||
}
|
||||
|
||||
public static void addToolCategory(MainWindow.ToolCategory category, String registeredName){
|
||||
AxisInnovatorsBox.getMain().
|
||||
getRegistrationTool().addToolCategory(category, registeredName);
|
||||
}
|
||||
}
|
||||
@@ -117,8 +117,6 @@ public class LanguageManager {
|
||||
return sb.toString().isEmpty() ? "无内容变更" : sb.toString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 加载语言
|
||||
* @param languageName 语言注册名
|
||||
|
||||
@@ -6,6 +6,8 @@ import com.axis.innovators.box.gui.LoadIcon;
|
||||
import com.axis.innovators.box.gui.WindowsJDialog;
|
||||
import com.axis.innovators.box.plugins.PluginDescriptor;
|
||||
import com.axis.innovators.box.plugins.PluginLoader;
|
||||
import com.axis.innovators.box.plugins.PluginPyLoader;
|
||||
import com.axis.innovators.box.util.PythonResult;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.tzd.lm.LM;
|
||||
@@ -50,15 +52,15 @@ public class RegistrationSettingsItem extends WindowsJDialog {
|
||||
registrationSettingsItem.addSettings(
|
||||
pluginPanel, language.getText("settings.1.title"),
|
||||
null, language.getText("settings.1.tip"), "system:settings_plugins_item"
|
||||
);
|
||||
);
|
||||
registrationSettingsItem.addSettings(
|
||||
generalPanel, language.getText("settings.2.title"),
|
||||
null, language.getText("settings.2.tip"), "system:settings_appearance_item"
|
||||
);
|
||||
);
|
||||
registrationSettingsItem.addSettings(
|
||||
aboutPanel, language.getText("settings.3.title"),
|
||||
null, language.getText("settings.3.tip"), "system:settings_information_item"
|
||||
);
|
||||
);
|
||||
registrationSettingsItem.addSettings(
|
||||
themePanel, language.getText("settings.4.title"),
|
||||
null, language.getText("settings.4.tip"), "system:settings_theme_item"
|
||||
@@ -253,13 +255,13 @@ public class RegistrationSettingsItem extends WindowsJDialog {
|
||||
UIManager.setLookAndFeel(theme);
|
||||
}
|
||||
JOptionPane.showMessageDialog(null, language.getText("settings.4.load_theme_success")
|
||||
+ themeName + language.getText("settings.4.load_theme_success.2"),
|
||||
+ themeName + language.getText("settings.4.load_theme_success.2"),
|
||||
language.getText("settings.4.load_theme_success.3"), JOptionPane.INFORMATION_MESSAGE);
|
||||
registrationTopic.setLoading(registeredName);
|
||||
themeList.repaint();
|
||||
} catch (Exception e) {
|
||||
JOptionPane.showMessageDialog(null, language.getText("settings.4.load_theme_error")
|
||||
+ e.getMessage(),
|
||||
+ e.getMessage(),
|
||||
language.getText("settings.4.load_theme_error.title"),
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
@@ -330,24 +332,40 @@ public class RegistrationSettingsItem extends WindowsJDialog {
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
|
||||
String[] columns = {
|
||||
language.getText("settings.1.columns.0"),
|
||||
language.getText("settings.1.columns.1"),
|
||||
language.getText("settings.1.columns.2"),
|
||||
language.getText("settings.1.columns.3")
|
||||
};
|
||||
|
||||
List<Object[]> pluginData = new ArrayList<>();
|
||||
|
||||
for (PluginDescriptor plugin : PluginLoader.getLoadedPlugins()) {
|
||||
pluginData.add(new Object[]{
|
||||
"Java",
|
||||
plugin.getName(),
|
||||
String.join(", ", plugin.getSupportedVersions()),
|
||||
plugin.getDescription()
|
||||
});
|
||||
}
|
||||
|
||||
for (PluginPyLoader.PyPluginDescriptor plugin : PluginPyLoader.getLoadedPlugins().values()) {
|
||||
pluginData.add(new Object[]{
|
||||
"Python",
|
||||
plugin.getName(),
|
||||
"N/A",
|
||||
plugin.getLastResult().getOutput()
|
||||
});
|
||||
}
|
||||
|
||||
DefaultTableModel model = new DefaultTableModel(columns, 0) {
|
||||
@Override
|
||||
public boolean isCellEditable(int row, int column) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
List<PluginDescriptor> plugins = PluginLoader.getLoadedPlugins();
|
||||
for (PluginDescriptor plugin : plugins) {
|
||||
model.addRow(new Object[]{
|
||||
plugin.getName(),
|
||||
String.join(", ", plugin.getSupportedVersions()),
|
||||
plugin.getDescription()
|
||||
});
|
||||
for (Object[] row : pluginData) {
|
||||
model.addRow(row);
|
||||
}
|
||||
|
||||
JTable table = new JTable(model);
|
||||
@@ -360,8 +378,21 @@ public class RegistrationSettingsItem extends WindowsJDialog {
|
||||
if (e.getClickCount() == 2) {
|
||||
int selectedRow = table.getSelectedRow();
|
||||
if (selectedRow >= 0) {
|
||||
PluginDescriptor selectedPlugin = plugins.get(selectedRow);
|
||||
showPluginDetails(selectedPlugin);
|
||||
String pluginType = (String) model.getValueAt(selectedRow, 0);
|
||||
String pluginId = "";
|
||||
|
||||
try {
|
||||
if ("Java".equals(pluginType)) {
|
||||
PluginDescriptor selectedPlugin = PluginLoader.getLoadedPlugins().get(selectedRow);
|
||||
showPluginDetails(selectedPlugin);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
logger.error("Failed to show plugin details [ID:{}]", pluginId, ex);
|
||||
JOptionPane.showMessageDialog(panel,
|
||||
"插件信息加载失败:" + ex.getMessage(),
|
||||
"系统错误",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -373,7 +404,6 @@ public class RegistrationSettingsItem extends WindowsJDialog {
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
private static void showPluginDetails(PluginDescriptor plugin) {
|
||||
JDialog dialog = new JDialog(AxisInnovatorsBox.getMain().getMainWindow(), "插件详细信息", true);
|
||||
dialog.setLayout(new BorderLayout());
|
||||
@@ -536,7 +566,7 @@ public class RegistrationSettingsItem extends WindowsJDialog {
|
||||
Object key = keys.nextElement();
|
||||
Object value = UIManager.get(key);
|
||||
if (value instanceof Font) {
|
||||
Font derived = font.deriveFont(((Font)value).getStyle());
|
||||
Font derived = font.deriveFont(((Font) value).getStyle());
|
||||
UIManager.put(key, derived);
|
||||
}
|
||||
}
|
||||
@@ -558,17 +588,18 @@ public class RegistrationSettingsItem extends WindowsJDialog {
|
||||
|
||||
/**
|
||||
* 注册设置项
|
||||
*
|
||||
* @param tabbedPanesSettings 设置页面
|
||||
* @param title 标题
|
||||
* @param icon 显示图标(可为null)
|
||||
* @param tip 描述
|
||||
* @param registeredName 注册名(必须是唯一的)
|
||||
* @param title 标题
|
||||
* @param icon 显示图标(可为null)
|
||||
* @param tip 描述
|
||||
* @param registeredName 注册名(必须是唯一的)
|
||||
*/
|
||||
private void addSettings(JPanel tabbedPanesSettings,
|
||||
String title,
|
||||
Icon icon,
|
||||
String tip,
|
||||
String registeredName) {
|
||||
String title,
|
||||
Icon icon,
|
||||
String tip,
|
||||
String registeredName) {
|
||||
if (mainWindow != null) {
|
||||
if (!mainWindow.isWindow()) {
|
||||
registrationItem(tabbedPanesSettings, title, icon, tip, registeredName);
|
||||
@@ -582,22 +613,23 @@ public class RegistrationSettingsItem extends WindowsJDialog {
|
||||
|
||||
/**
|
||||
* 注册设置项
|
||||
*
|
||||
* @param tabbedPanesSettings 设置页面
|
||||
* @param title 标题
|
||||
* @param icon 显示图标(可为null)
|
||||
* @param tip 描述
|
||||
* @param plugin 插件
|
||||
* @param registeredName 注册名(必须是唯一的)
|
||||
* @param title 标题
|
||||
* @param icon 显示图标(可为null)
|
||||
* @param tip 描述
|
||||
* @param plugin 插件
|
||||
* @param registeredName 注册名(必须是唯一的)
|
||||
*/
|
||||
public void addSettings(JPanel tabbedPanesSettings,
|
||||
String title,
|
||||
Icon icon,
|
||||
String tip,
|
||||
PluginDescriptor plugin,
|
||||
String registeredName) {
|
||||
String title,
|
||||
Icon icon,
|
||||
String tip,
|
||||
PluginDescriptor plugin,
|
||||
String registeredName) {
|
||||
if (mainWindow != null) {
|
||||
if (!mainWindow.isWindow()) {
|
||||
registrationItem(tabbedPanesSettings, title, icon, tip,plugin.getRegistrationName()
|
||||
registrationItem(tabbedPanesSettings, title, icon, tip, plugin.getRegistrationName()
|
||||
+ ":" +
|
||||
registeredName);
|
||||
} else {
|
||||
@@ -610,6 +642,7 @@ public class RegistrationSettingsItem extends WindowsJDialog {
|
||||
|
||||
/**
|
||||
* 通过 PluginDescriptor 查询整个插件装载的注册项
|
||||
*
|
||||
* @param plugin 插件描述符
|
||||
* @return List<RegistrationSettingsItem> 与指定插件相关的注册设置项列表
|
||||
*/
|
||||
@@ -637,6 +670,7 @@ public class RegistrationSettingsItem extends WindowsJDialog {
|
||||
|
||||
/**
|
||||
* 获取设置项
|
||||
*
|
||||
* @param id id
|
||||
* @return JPanel对象
|
||||
*/
|
||||
@@ -651,6 +685,7 @@ public class RegistrationSettingsItem extends WindowsJDialog {
|
||||
|
||||
/**
|
||||
* 通过注册名获取UUID
|
||||
*
|
||||
* @param registeredName 注册名
|
||||
* @return UUID
|
||||
*/
|
||||
@@ -665,6 +700,7 @@ public class RegistrationSettingsItem extends WindowsJDialog {
|
||||
|
||||
/**
|
||||
* 注册设置项
|
||||
*
|
||||
* @param registrationSettingsItem 注册设置项
|
||||
*/
|
||||
public void addRegistrationSettingsItem(RegistrationSettingsItem registrationSettingsItem) {
|
||||
@@ -677,6 +713,7 @@ public class RegistrationSettingsItem extends WindowsJDialog {
|
||||
|
||||
/**
|
||||
* 注册设置项
|
||||
*
|
||||
* @param tabbedPane JTabbedPane
|
||||
*/
|
||||
public void registration(JTabbedPane tabbedPane) {
|
||||
@@ -687,6 +724,7 @@ public class RegistrationSettingsItem extends WindowsJDialog {
|
||||
|
||||
/**
|
||||
* 获取所有注册设置项
|
||||
*
|
||||
* @return List<RegistrationSettingsItem>
|
||||
*/
|
||||
public static List<RegistrationSettingsItem> getRegistrationSettingsItemList() {
|
||||
|
||||
@@ -68,7 +68,7 @@ public class RegistrationTool {
|
||||
* 注册ToolCategory
|
||||
* @param toolCategory ToolCategory
|
||||
*/
|
||||
private boolean addToolCategory(MainWindow.ToolCategory toolCategory,
|
||||
public boolean addToolCategory(MainWindow.ToolCategory toolCategory,
|
||||
String registeredName) {
|
||||
if (!main.isWindow()) {
|
||||
if (registeredNameList.contains(registeredName)) {
|
||||
|
||||
@@ -42,6 +42,15 @@ public class FolderCreator {
|
||||
return folder;
|
||||
}
|
||||
|
||||
public static String getPyPluginFolder() {
|
||||
String folder = createFolder(PLUGIN_PATH + "\\python");
|
||||
if (folder == null) {
|
||||
logger.error("Plugin folder creation failed, please use administrator privileges to execute this procedure");
|
||||
return null;
|
||||
}
|
||||
return folder;
|
||||
}
|
||||
|
||||
public static String getModelFolder() {
|
||||
String folder = createFolder(MODEL_PATH);
|
||||
if (folder == null) {
|
||||
|
||||
121
src/main/java/com/axis/innovators/box/util/PythonResult.java
Normal file
121
src/main/java/com/axis/innovators/box/util/PythonResult.java
Normal file
@@ -0,0 +1,121 @@
|
||||
package com.axis.innovators.box.util;
|
||||
|
||||
import org.python.core.Py;
|
||||
import org.python.core.PyException;
|
||||
import org.python.util.PythonInterpreter;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 增强的Python脚本执行结果封装
|
||||
*/
|
||||
public final class PythonResult implements AutoCloseable {
|
||||
private final int exitCode;
|
||||
private final Object output;
|
||||
private final PythonInterpreter interpreter;
|
||||
|
||||
PythonResult(int exitCode, Object output, PythonInterpreter interpreter) {
|
||||
this.exitCode = exitCode;
|
||||
this.output = output;
|
||||
this.interpreter = interpreter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建成功结果
|
||||
*/
|
||||
public static PythonResult success(Object output, PythonInterpreter interpreter) {
|
||||
return new PythonResult(0, output, Objects.requireNonNull(interpreter));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建错误结果
|
||||
*/
|
||||
public static PythonResult error(int exitCode, String message) {
|
||||
return new PythonResult(exitCode, message, null);
|
||||
}
|
||||
|
||||
public static PythonResult of(int exitCode, Object output, PythonInterpreter interpreter) {
|
||||
return new PythonResult(exitCode, output, interpreter);
|
||||
}
|
||||
|
||||
public int getExitCode() {
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
public Object getOutput() {
|
||||
return output;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return exitCode == 0 && interpreter != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取解释器实例
|
||||
* @throws IllegalStateException 如果解释器不可用
|
||||
*/
|
||||
public PythonInterpreter getInterpreter() {
|
||||
if (interpreter == null) {
|
||||
throw new IllegalStateException("Python interpreter not available");
|
||||
}
|
||||
return interpreter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全调用Python函数
|
||||
* @param functionName 目标函数名称
|
||||
* @param parameters 函数参数(支持基本类型和字符串)
|
||||
* @return 函数执行结果
|
||||
* @throws IllegalStateException 如果解释器不可用
|
||||
* @throws RuntimeException 如果Python执行出错
|
||||
*/
|
||||
public Object callFunction(String functionName, Object... parameters) {
|
||||
PythonInterpreter pyInterp = this.getInterpreter();
|
||||
if (pyInterp == null) {
|
||||
throw new IllegalStateException("Interpreter not available");
|
||||
}
|
||||
|
||||
try {
|
||||
StringBuilder args = new StringBuilder();
|
||||
for (Object param : parameters) {
|
||||
if (param instanceof String) {
|
||||
args.append("\"").append(param.toString().replace("\"", "\\\"")).append("\",");
|
||||
} else if (param instanceof Number || param instanceof Boolean) {
|
||||
args.append(param).append(",");
|
||||
} else {
|
||||
args.append("\"").append(param.toString()).append("\",");
|
||||
}
|
||||
}
|
||||
|
||||
if (!args.isEmpty()) {
|
||||
args.deleteCharAt(args.length() - 1);
|
||||
}
|
||||
|
||||
String callExpr = functionName + "(" + args.toString() + ")";
|
||||
return pyInterp.eval(callExpr);
|
||||
} catch (PyException e) {
|
||||
throw new RuntimeException("Python error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return this.interpreter != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (interpreter != null) {
|
||||
interpreter.cleanup();
|
||||
interpreter.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(
|
||||
"PythonResult{exitCode=%d, output=%s, interpreter=%s}",
|
||||
exitCode,
|
||||
output,
|
||||
interpreter != null ? "available" : "null"
|
||||
);
|
||||
}
|
||||
}
|
||||
93
src/main/java/com/axis/innovators/box/util/RunPython.java
Normal file
93
src/main/java/com/axis/innovators/box/util/RunPython.java
Normal file
@@ -0,0 +1,93 @@
|
||||
package com.axis.innovators.box.util;
|
||||
|
||||
import org.python.core.PyException;
|
||||
import org.python.core.PySyntaxError;
|
||||
import org.python.util.PythonInterpreter;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Python解释器工具类
|
||||
* @author tzdwindows 7
|
||||
*/
|
||||
public class RunPython {
|
||||
private static final String AST_PROCESSOR =
|
||||
"def __process_code(raw_code):\n" +
|
||||
" try:\n" +
|
||||
" import ast, traceback\n" +
|
||||
" tree = ast.parse(raw_code)\n" +
|
||||
" for node in ast.walk(tree):\n" +
|
||||
" if isinstance(node, ast.FunctionDef):\n" +
|
||||
" node.returns = None\n" +
|
||||
" for arg in node.args.args:\n" +
|
||||
" arg.annotation = None\n" +
|
||||
" return compile(tree, '<string>', 'exec')\n" +
|
||||
" except Exception:\n" +
|
||||
" return compile(raw_code, '<string>', 'exec')\n";
|
||||
|
||||
// 线程局部存储优化
|
||||
private static final ThreadLocal<PythonInterpreter> PY_THREAD_LOCAL =
|
||||
ThreadLocal.withInitial(() -> {
|
||||
PythonInterpreter pi = new PythonInterpreter();
|
||||
pi.exec("from __future__ import print_function");
|
||||
pi.exec("import sys");
|
||||
pi.exec(AST_PROCESSOR);
|
||||
return pi;
|
||||
});
|
||||
|
||||
public static PythonResult executeWithJython(String scriptPath) {
|
||||
return executeWithJython(scriptPath, Collections.emptyList());
|
||||
}
|
||||
|
||||
public static Object callPythonFunction(PythonResult result,
|
||||
String functionName,
|
||||
Object... params) {
|
||||
if (result == null || !result.isValid()) {
|
||||
return "INVALID_INTERPRETER";
|
||||
}
|
||||
try {
|
||||
return result.callFunction(functionName, params);
|
||||
} catch (Exception e) {
|
||||
return "CALL_ERROR: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public static PythonResult executeWithJython(String scriptPath,
|
||||
List<String> dependencyPaths) {
|
||||
PythonInterpreter pi = PY_THREAD_LOCAL.get();
|
||||
try {
|
||||
pi.cleanup();
|
||||
pi.exec("sys.path = []");
|
||||
|
||||
pi.exec("import sys");
|
||||
pi.exec("reload(sys)");
|
||||
pi.exec("sys.setdefaultencoding('utf-8')");
|
||||
|
||||
dependencyPaths.stream()
|
||||
.map(dep -> new File(dep).getAbsolutePath().replace("\\", "/"))
|
||||
.forEach(path -> pi.exec("sys.path.append(r'" + path + "')"));
|
||||
|
||||
String code = Files.readString(Path.of(scriptPath), StandardCharsets.UTF_8);
|
||||
pi.set("raw_code", code);
|
||||
pi.exec("exec(__process_code(raw_code))");
|
||||
|
||||
return PythonResult.of(
|
||||
Optional.ofNullable(pi.get("result", Integer.class)).orElse(0),
|
||||
Optional.ofNullable(pi.get("errorResult", String.class)).orElse(""),
|
||||
pi
|
||||
);
|
||||
} catch (NoSuchFileException e) {
|
||||
return PythonResult.error(-2, "FILE_NOT_FOUND: " + scriptPath);
|
||||
} catch (IOException e) {
|
||||
return PythonResult.error(-3, "IO_ERROR: " + e.getMessage());
|
||||
} catch (PySyntaxError e) {
|
||||
String errorMsg = "SYNTAX_ERROR: " + e.value.toString();
|
||||
return PythonResult.error(-4, errorMsg);
|
||||
} catch (PyException e) {
|
||||
return PythonResult.error(-5, "RUNTIME_ERROR: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user