feat(plugin): 添加 Python 插件支持

- 新增 PluginPyLoader 类实现 Python 插件加载- 添加 PythonResult 类封装 Python执行结果
- 实现 RunPython 工具类用于执行 Python脚本
- 新增 PyLocalSide 类提供 Python 脚本调用本地方法的接口- 修改主程序启动逻辑,支持 Python 插件初始化
- 更新插件注册设置项,兼容 Python 插件
This commit is contained in:
tzdwindows 7
2025-02-22 15:53:32 +08:00
parent 701dfcfb47
commit ce996b73be
15 changed files with 707 additions and 46 deletions

4
.idea/misc.xml generated
View File

@@ -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" />

View File

@@ -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 目录

View 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

View File

@@ -0,0 +1,13 @@
{
"id": "testing",
"name": "测试",
"version": "0.0.1",
"description": "测试插件",
"author": "tzdwindows 7",
"dependencies": [],
"_comment": {
"warning": "本文件为插件元数据配置,修改后需重启应用生效",
"path": "插件资源应放置在plugins/{id}/目录下"
}
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -117,8 +117,6 @@ public class LanguageManager {
return sb.toString().isEmpty() ? "无内容变更" : sb.toString();
}
/**
* 加载语言
* @param languageName 语言注册名

View File

@@ -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() {

View File

@@ -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)) {

View File

@@ -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) {

View 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"
);
}
}

View 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());
}
}
}