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">
|
<project version="4">
|
||||||
<component name="ASMPluginConfiguration">
|
<component name="ASMPluginConfiguration">
|
||||||
<asm skipDebug="false" skipFrames="false" skipCode="false" expandFrames="false" />
|
<asm skipDebug="false" skipFrames="false" skipCode="false" expandFrames="false" />
|
||||||
@@ -7,6 +8,9 @@
|
|||||||
<asm skipDebug="true" skipFrames="true" skipCode="false" expandFrames="false" />
|
<asm skipDebug="true" skipFrames="true" skipCode="false" expandFrames="false" />
|
||||||
<groovy codeStyle="LEGACY" />
|
<groovy codeStyle="LEGACY" />
|
||||||
</component>
|
</component>
|
||||||
|
<component name="Black">
|
||||||
|
<option name="sdkName" value="Python 3.12 (NIGANMA)" />
|
||||||
|
</component>
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="FindBugsConfigurable">
|
<component name="FindBugsConfigurable">
|
||||||
<option name="make" value="true" />
|
<option name="make" value="true" />
|
||||||
|
|||||||
17
build.gradle
17
build.gradle
@@ -15,10 +15,17 @@ group = 'com.axis.innovators.box'
|
|||||||
version = '0.0.1'
|
version = '0.0.1'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
maven { setUrl("https://maven.aliyun.com/repository/central") }
|
||||||
maven { url "https://maven.aliyun.com/repository/public" }
|
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()
|
mavenCentral()
|
||||||
jcenter()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@@ -48,6 +55,10 @@ dependencies {
|
|||||||
implementation 'com.formdev:flatlaf:3.2.1' // FlatLaf核心
|
implementation 'com.formdev:flatlaf:3.2.1' // FlatLaf核心
|
||||||
implementation 'com.formdev:flatlaf-extras:3.2.1' // 扩展组件
|
implementation 'com.formdev:flatlaf-extras:3.2.1' // 扩展组件
|
||||||
implementation 'com.formdev:flatlaf-intellij-themes: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 目录
|
// 分离依赖项到 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.gui.*;
|
||||||
import com.axis.innovators.box.plugins.PluginDescriptor;
|
import com.axis.innovators.box.plugins.PluginDescriptor;
|
||||||
import com.axis.innovators.box.plugins.PluginLoader;
|
import com.axis.innovators.box.plugins.PluginLoader;
|
||||||
|
import com.axis.innovators.box.plugins.PluginPyLoader;
|
||||||
import com.axis.innovators.box.register.RegistrationSettingsItem;
|
import com.axis.innovators.box.register.RegistrationSettingsItem;
|
||||||
import com.axis.innovators.box.register.RegistrationTool;
|
import com.axis.innovators.box.register.RegistrationTool;
|
||||||
import com.axis.innovators.box.register.RegistrationTopic;
|
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.LibraryLoad;
|
||||||
import com.axis.innovators.box.tools.StateManager;
|
import com.axis.innovators.box.tools.StateManager;
|
||||||
import com.axis.innovators.box.tools.SystemInfoUtil;
|
import com.axis.innovators.box.tools.SystemInfoUtil;
|
||||||
|
import com.axis.innovators.box.util.PythonResult;
|
||||||
import com.formdev.flatlaf.FlatLightLaf;
|
import com.formdev.flatlaf.FlatLightLaf;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
@@ -24,7 +26,6 @@ import java.awt.*;
|
|||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -34,7 +35,7 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
public class AxisInnovatorsBox {
|
public class AxisInnovatorsBox {
|
||||||
private static final Logger logger = LogManager.getLogger(AxisInnovatorsBox.class);
|
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[]{
|
private static final String[] AUTHOR = new String[]{
|
||||||
"tzdwindows 7"
|
"tzdwindows 7"
|
||||||
};
|
};
|
||||||
@@ -60,6 +61,7 @@ public class AxisInnovatorsBox {
|
|||||||
this.args = args;
|
this.args = args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
try {
|
try {
|
||||||
LibraryLoad.loadLibrary("FridaNative");
|
LibraryLoad.loadLibrary("FridaNative");
|
||||||
@@ -349,6 +351,7 @@ public class AxisInnovatorsBox {
|
|||||||
logger.info("Loaded plugins Started");
|
logger.info("Loaded plugins Started");
|
||||||
main.progressBarManager.updateMainProgress(++main.completedTasks);
|
main.progressBarManager.updateMainProgress(++main.completedTasks);
|
||||||
PluginLoader.loadPlugins();
|
PluginLoader.loadPlugins();
|
||||||
|
PluginPyLoader.loadAllPlugins();
|
||||||
logger.info("Loaded plugins End");
|
logger.info("Loaded plugins End");
|
||||||
|
|
||||||
main.progressBarManager.close();
|
main.progressBarManager.close();
|
||||||
@@ -357,6 +360,16 @@ public class AxisInnovatorsBox {
|
|||||||
try {
|
try {
|
||||||
main.ex = new MainWindow();
|
main.ex = new MainWindow();
|
||||||
GlobalEventBus.EVENT_BUS.post(new StartupEvent(main));
|
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();
|
main.runWindow();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("There was a problem starting the main thread", 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 javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.File;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,11 +36,16 @@ public class LoadIcon {
|
|||||||
* @param size 图片大小
|
* @param size 图片大小
|
||||||
* @return ImageIcon对象
|
* @return ImageIcon对象
|
||||||
*/
|
*/
|
||||||
public static ImageIcon loadIcon(Class<?> clazz ,String filename, int size) {
|
public static ImageIcon loadIcon(Class<?> clazz, String filename, int size) {
|
||||||
try {
|
try {
|
||||||
if (filename.isEmpty()){
|
if (filename.isEmpty()) {
|
||||||
return createPlaceholderIcon(size);
|
return createPlaceholderIcon(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (new File(filename).isAbsolute()) {
|
||||||
|
return loadAbsolutePathIcon(filename, size);
|
||||||
|
}
|
||||||
|
|
||||||
String fullPath = ICON_PATH + filename;
|
String fullPath = ICON_PATH + filename;
|
||||||
URL imgUrl = clazz.getResource(fullPath);
|
URL imgUrl = clazz.getResource(fullPath);
|
||||||
if (imgUrl == null) {
|
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
|
* @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();
|
return sb.toString().isEmpty() ? "无内容变更" : sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载语言
|
* 加载语言
|
||||||
* @param languageName 语言注册名
|
* @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.gui.WindowsJDialog;
|
||||||
import com.axis.innovators.box.plugins.PluginDescriptor;
|
import com.axis.innovators.box.plugins.PluginDescriptor;
|
||||||
import com.axis.innovators.box.plugins.PluginLoader;
|
import com.axis.innovators.box.plugins.PluginLoader;
|
||||||
|
import com.axis.innovators.box.plugins.PluginPyLoader;
|
||||||
|
import com.axis.innovators.box.util.PythonResult;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.tzd.lm.LM;
|
import org.tzd.lm.LM;
|
||||||
@@ -50,15 +52,15 @@ public class RegistrationSettingsItem extends WindowsJDialog {
|
|||||||
registrationSettingsItem.addSettings(
|
registrationSettingsItem.addSettings(
|
||||||
pluginPanel, language.getText("settings.1.title"),
|
pluginPanel, language.getText("settings.1.title"),
|
||||||
null, language.getText("settings.1.tip"), "system:settings_plugins_item"
|
null, language.getText("settings.1.tip"), "system:settings_plugins_item"
|
||||||
);
|
);
|
||||||
registrationSettingsItem.addSettings(
|
registrationSettingsItem.addSettings(
|
||||||
generalPanel, language.getText("settings.2.title"),
|
generalPanel, language.getText("settings.2.title"),
|
||||||
null, language.getText("settings.2.tip"), "system:settings_appearance_item"
|
null, language.getText("settings.2.tip"), "system:settings_appearance_item"
|
||||||
);
|
);
|
||||||
registrationSettingsItem.addSettings(
|
registrationSettingsItem.addSettings(
|
||||||
aboutPanel, language.getText("settings.3.title"),
|
aboutPanel, language.getText("settings.3.title"),
|
||||||
null, language.getText("settings.3.tip"), "system:settings_information_item"
|
null, language.getText("settings.3.tip"), "system:settings_information_item"
|
||||||
);
|
);
|
||||||
registrationSettingsItem.addSettings(
|
registrationSettingsItem.addSettings(
|
||||||
themePanel, language.getText("settings.4.title"),
|
themePanel, language.getText("settings.4.title"),
|
||||||
null, language.getText("settings.4.tip"), "system:settings_theme_item"
|
null, language.getText("settings.4.tip"), "system:settings_theme_item"
|
||||||
@@ -253,13 +255,13 @@ public class RegistrationSettingsItem extends WindowsJDialog {
|
|||||||
UIManager.setLookAndFeel(theme);
|
UIManager.setLookAndFeel(theme);
|
||||||
}
|
}
|
||||||
JOptionPane.showMessageDialog(null, language.getText("settings.4.load_theme_success")
|
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);
|
language.getText("settings.4.load_theme_success.3"), JOptionPane.INFORMATION_MESSAGE);
|
||||||
registrationTopic.setLoading(registeredName);
|
registrationTopic.setLoading(registeredName);
|
||||||
themeList.repaint();
|
themeList.repaint();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
JOptionPane.showMessageDialog(null, language.getText("settings.4.load_theme_error")
|
JOptionPane.showMessageDialog(null, language.getText("settings.4.load_theme_error")
|
||||||
+ e.getMessage(),
|
+ e.getMessage(),
|
||||||
language.getText("settings.4.load_theme_error.title"),
|
language.getText("settings.4.load_theme_error.title"),
|
||||||
JOptionPane.ERROR_MESSAGE);
|
JOptionPane.ERROR_MESSAGE);
|
||||||
}
|
}
|
||||||
@@ -330,24 +332,40 @@ public class RegistrationSettingsItem extends WindowsJDialog {
|
|||||||
JPanel panel = new JPanel(new BorderLayout());
|
JPanel panel = new JPanel(new BorderLayout());
|
||||||
|
|
||||||
String[] columns = {
|
String[] columns = {
|
||||||
|
language.getText("settings.1.columns.0"),
|
||||||
language.getText("settings.1.columns.1"),
|
language.getText("settings.1.columns.1"),
|
||||||
language.getText("settings.1.columns.2"),
|
language.getText("settings.1.columns.2"),
|
||||||
language.getText("settings.1.columns.3")
|
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) {
|
DefaultTableModel model = new DefaultTableModel(columns, 0) {
|
||||||
@Override
|
@Override
|
||||||
public boolean isCellEditable(int row, int column) {
|
public boolean isCellEditable(int row, int column) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
for (Object[] row : pluginData) {
|
||||||
List<PluginDescriptor> plugins = PluginLoader.getLoadedPlugins();
|
model.addRow(row);
|
||||||
for (PluginDescriptor plugin : plugins) {
|
|
||||||
model.addRow(new Object[]{
|
|
||||||
plugin.getName(),
|
|
||||||
String.join(", ", plugin.getSupportedVersions()),
|
|
||||||
plugin.getDescription()
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
JTable table = new JTable(model);
|
JTable table = new JTable(model);
|
||||||
@@ -360,8 +378,21 @@ public class RegistrationSettingsItem extends WindowsJDialog {
|
|||||||
if (e.getClickCount() == 2) {
|
if (e.getClickCount() == 2) {
|
||||||
int selectedRow = table.getSelectedRow();
|
int selectedRow = table.getSelectedRow();
|
||||||
if (selectedRow >= 0) {
|
if (selectedRow >= 0) {
|
||||||
PluginDescriptor selectedPlugin = plugins.get(selectedRow);
|
String pluginType = (String) model.getValueAt(selectedRow, 0);
|
||||||
showPluginDetails(selectedPlugin);
|
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;
|
return panel;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void showPluginDetails(PluginDescriptor plugin) {
|
private static void showPluginDetails(PluginDescriptor plugin) {
|
||||||
JDialog dialog = new JDialog(AxisInnovatorsBox.getMain().getMainWindow(), "插件详细信息", true);
|
JDialog dialog = new JDialog(AxisInnovatorsBox.getMain().getMainWindow(), "插件详细信息", true);
|
||||||
dialog.setLayout(new BorderLayout());
|
dialog.setLayout(new BorderLayout());
|
||||||
@@ -536,7 +566,7 @@ public class RegistrationSettingsItem extends WindowsJDialog {
|
|||||||
Object key = keys.nextElement();
|
Object key = keys.nextElement();
|
||||||
Object value = UIManager.get(key);
|
Object value = UIManager.get(key);
|
||||||
if (value instanceof Font) {
|
if (value instanceof Font) {
|
||||||
Font derived = font.deriveFont(((Font)value).getStyle());
|
Font derived = font.deriveFont(((Font) value).getStyle());
|
||||||
UIManager.put(key, derived);
|
UIManager.put(key, derived);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -558,17 +588,18 @@ public class RegistrationSettingsItem extends WindowsJDialog {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 注册设置项
|
* 注册设置项
|
||||||
|
*
|
||||||
* @param tabbedPanesSettings 设置页面
|
* @param tabbedPanesSettings 设置页面
|
||||||
* @param title 标题
|
* @param title 标题
|
||||||
* @param icon 显示图标(可为null)
|
* @param icon 显示图标(可为null)
|
||||||
* @param tip 描述
|
* @param tip 描述
|
||||||
* @param registeredName 注册名(必须是唯一的)
|
* @param registeredName 注册名(必须是唯一的)
|
||||||
*/
|
*/
|
||||||
private void addSettings(JPanel tabbedPanesSettings,
|
private void addSettings(JPanel tabbedPanesSettings,
|
||||||
String title,
|
String title,
|
||||||
Icon icon,
|
Icon icon,
|
||||||
String tip,
|
String tip,
|
||||||
String registeredName) {
|
String registeredName) {
|
||||||
if (mainWindow != null) {
|
if (mainWindow != null) {
|
||||||
if (!mainWindow.isWindow()) {
|
if (!mainWindow.isWindow()) {
|
||||||
registrationItem(tabbedPanesSettings, title, icon, tip, registeredName);
|
registrationItem(tabbedPanesSettings, title, icon, tip, registeredName);
|
||||||
@@ -582,22 +613,23 @@ public class RegistrationSettingsItem extends WindowsJDialog {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 注册设置项
|
* 注册设置项
|
||||||
|
*
|
||||||
* @param tabbedPanesSettings 设置页面
|
* @param tabbedPanesSettings 设置页面
|
||||||
* @param title 标题
|
* @param title 标题
|
||||||
* @param icon 显示图标(可为null)
|
* @param icon 显示图标(可为null)
|
||||||
* @param tip 描述
|
* @param tip 描述
|
||||||
* @param plugin 插件
|
* @param plugin 插件
|
||||||
* @param registeredName 注册名(必须是唯一的)
|
* @param registeredName 注册名(必须是唯一的)
|
||||||
*/
|
*/
|
||||||
public void addSettings(JPanel tabbedPanesSettings,
|
public void addSettings(JPanel tabbedPanesSettings,
|
||||||
String title,
|
String title,
|
||||||
Icon icon,
|
Icon icon,
|
||||||
String tip,
|
String tip,
|
||||||
PluginDescriptor plugin,
|
PluginDescriptor plugin,
|
||||||
String registeredName) {
|
String registeredName) {
|
||||||
if (mainWindow != null) {
|
if (mainWindow != null) {
|
||||||
if (!mainWindow.isWindow()) {
|
if (!mainWindow.isWindow()) {
|
||||||
registrationItem(tabbedPanesSettings, title, icon, tip,plugin.getRegistrationName()
|
registrationItem(tabbedPanesSettings, title, icon, tip, plugin.getRegistrationName()
|
||||||
+ ":" +
|
+ ":" +
|
||||||
registeredName);
|
registeredName);
|
||||||
} else {
|
} else {
|
||||||
@@ -610,6 +642,7 @@ public class RegistrationSettingsItem extends WindowsJDialog {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 通过 PluginDescriptor 查询整个插件装载的注册项
|
* 通过 PluginDescriptor 查询整个插件装载的注册项
|
||||||
|
*
|
||||||
* @param plugin 插件描述符
|
* @param plugin 插件描述符
|
||||||
* @return List<RegistrationSettingsItem> 与指定插件相关的注册设置项列表
|
* @return List<RegistrationSettingsItem> 与指定插件相关的注册设置项列表
|
||||||
*/
|
*/
|
||||||
@@ -637,6 +670,7 @@ public class RegistrationSettingsItem extends WindowsJDialog {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取设置项
|
* 获取设置项
|
||||||
|
*
|
||||||
* @param id id
|
* @param id id
|
||||||
* @return JPanel对象
|
* @return JPanel对象
|
||||||
*/
|
*/
|
||||||
@@ -651,6 +685,7 @@ public class RegistrationSettingsItem extends WindowsJDialog {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 通过注册名获取UUID
|
* 通过注册名获取UUID
|
||||||
|
*
|
||||||
* @param registeredName 注册名
|
* @param registeredName 注册名
|
||||||
* @return UUID
|
* @return UUID
|
||||||
*/
|
*/
|
||||||
@@ -665,6 +700,7 @@ public class RegistrationSettingsItem extends WindowsJDialog {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 注册设置项
|
* 注册设置项
|
||||||
|
*
|
||||||
* @param registrationSettingsItem 注册设置项
|
* @param registrationSettingsItem 注册设置项
|
||||||
*/
|
*/
|
||||||
public void addRegistrationSettingsItem(RegistrationSettingsItem registrationSettingsItem) {
|
public void addRegistrationSettingsItem(RegistrationSettingsItem registrationSettingsItem) {
|
||||||
@@ -677,6 +713,7 @@ public class RegistrationSettingsItem extends WindowsJDialog {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 注册设置项
|
* 注册设置项
|
||||||
|
*
|
||||||
* @param tabbedPane JTabbedPane
|
* @param tabbedPane JTabbedPane
|
||||||
*/
|
*/
|
||||||
public void registration(JTabbedPane tabbedPane) {
|
public void registration(JTabbedPane tabbedPane) {
|
||||||
@@ -687,6 +724,7 @@ public class RegistrationSettingsItem extends WindowsJDialog {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有注册设置项
|
* 获取所有注册设置项
|
||||||
|
*
|
||||||
* @return List<RegistrationSettingsItem>
|
* @return List<RegistrationSettingsItem>
|
||||||
*/
|
*/
|
||||||
public static List<RegistrationSettingsItem> getRegistrationSettingsItemList() {
|
public static List<RegistrationSettingsItem> getRegistrationSettingsItemList() {
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ public class RegistrationTool {
|
|||||||
* 注册ToolCategory
|
* 注册ToolCategory
|
||||||
* @param toolCategory ToolCategory
|
* @param toolCategory ToolCategory
|
||||||
*/
|
*/
|
||||||
private boolean addToolCategory(MainWindow.ToolCategory toolCategory,
|
public boolean addToolCategory(MainWindow.ToolCategory toolCategory,
|
||||||
String registeredName) {
|
String registeredName) {
|
||||||
if (!main.isWindow()) {
|
if (!main.isWindow()) {
|
||||||
if (registeredNameList.contains(registeredName)) {
|
if (registeredNameList.contains(registeredName)) {
|
||||||
|
|||||||
@@ -42,6 +42,15 @@ public class FolderCreator {
|
|||||||
return folder;
|
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() {
|
public static String getModelFolder() {
|
||||||
String folder = createFolder(MODEL_PATH);
|
String folder = createFolder(MODEL_PATH);
|
||||||
if (folder == null) {
|
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