diff --git a/build.gradle b/build.gradle index 4697214..e7e3a11 100644 --- a/build.gradle +++ b/build.gradle @@ -145,6 +145,16 @@ dependencies { implementation 'jflac:jflac:1.3' // FLAC支持 implementation 'com.github.axet:TarsosDSP:2.4' + + // Eclipse 组件 + // https://mvnrepository.com/artifact/org.eclipse.swt/org.eclipse.swt.win32.win32.x86_64 + //implementation 'org.eclipse.swt:org.eclipse.swt.win32.win32.x86_64:4.3' + // + //// 基础 Eclipse 组件 + //implementation 'org.eclipse.platform:org.eclipse.jface:3.36.0' + //implementation 'org.eclipse.platform:org.eclipse.jface.text:3.27.0' // 使用兼容版本 + //implementation 'org.eclipse.platform:org.eclipse.core.runtime:3.33.0' + //implementation 'org.eclipse.platform:org.eclipse.equinox.common:3.20.0' } // 分离依赖项到 libs 目录 diff --git a/javascript/AIaToolbox_dark.html b/javascript/AIaToolbox_dark.html new file mode 100644 index 0000000..b4f28ec --- /dev/null +++ b/javascript/AIaToolbox_dark.html @@ -0,0 +1,867 @@ + + + + + + DeepSeek - 智能助手 + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/javascript/AIaToolbox_light.html b/javascript/AIaToolbox_light.html new file mode 100644 index 0000000..9d5ed0e --- /dev/null +++ b/javascript/AIaToolbox_light.html @@ -0,0 +1,665 @@ + + + + + + DeepSeek - 智能助手 + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/javascript/HtmlViewer.html b/javascript/HtmlViewer.html new file mode 100644 index 0000000..6ffb8a0 --- /dev/null +++ b/javascript/HtmlViewer.html @@ -0,0 +1,462 @@ + + + + + Axis Innovators Pro + + + + + + + + + + + + + + + + +
+ +
+
+
+ +
+
+
+ +
+
+
+ + + + + \ No newline at end of file diff --git a/language/saved_language.properties b/language/saved_language.properties index 90c8a26..c155509 100644 --- a/language/saved_language.properties +++ b/language/saved_language.properties @@ -1,3 +1,3 @@ #Current Loaded Language -#Sun Mar 02 19:35:49 CST 2025 +#Thu May 01 15:04:30 CST 2025 loadedLanguage=system\:zh_CN diff --git a/src/main/java/com/axis/innovators/box/AxisInnovatorsBox.java b/src/main/java/com/axis/innovators/box/AxisInnovatorsBox.java index f106032..7739d30 100644 --- a/src/main/java/com/axis/innovators/box/AxisInnovatorsBox.java +++ b/src/main/java/com/axis/innovators/box/AxisInnovatorsBox.java @@ -319,6 +319,10 @@ public class AxisInnovatorsBox { windowsJDialog.repaint(); } + //public void execute(Runnable runnable){ + // thread. + //} + /** * 重新加载窗口 */ diff --git a/src/main/java/com/axis/innovators/box/Main.java b/src/main/java/com/axis/innovators/box/Main.java index 24f02cd..de6caa9 100644 --- a/src/main/java/com/axis/innovators/box/Main.java +++ b/src/main/java/com/axis/innovators/box/Main.java @@ -1,16 +1,13 @@ package com.axis.innovators.box; +import com.axis.innovators.box.browser.MainApplication; import com.axis.innovators.box.decompilation.gui.ModernJarViewer; -import com.axis.innovators.box.events.GlobalEventBus; -import com.axis.innovators.box.events.OpenFileEvents; -import com.axis.innovators.box.gui.LoginWindow; import com.axis.innovators.box.tools.ArgsParser; import com.axis.innovators.box.tools.FolderCleaner; import com.axis.innovators.box.tools.FolderCreator; import com.axis.innovators.box.register.LanguageManager; import javax.swing.*; -import java.util.Arrays; import java.util.List; import java.util.Map; @@ -42,6 +39,11 @@ public class Main { }); return; } + + if (".html".equals(extension)){ + MainApplication.popupHTMLWindow(path); + return; + } } AxisInnovatorsBox.run(args); diff --git a/src/main/java/com/axis/innovators/box/browser/CefAppManager.java b/src/main/java/com/axis/innovators/box/browser/CefAppManager.java index 5bc7dd0..069bb94 100644 --- a/src/main/java/com/axis/innovators/box/browser/CefAppManager.java +++ b/src/main/java/com/axis/innovators/box/browser/CefAppManager.java @@ -56,6 +56,7 @@ public class CefAppManager { private static void disposeCefApp() { if (cefApp != null) { + cefApp.clearSchemeHandlerFactories(); cefApp.dispose(); cefApp = null; isInitialized = false; diff --git a/src/main/java/com/axis/innovators/box/browser/MainApplication.java b/src/main/java/com/axis/innovators/box/browser/MainApplication.java index 2a4fe7a..b317bda 100644 --- a/src/main/java/com/axis/innovators/box/browser/MainApplication.java +++ b/src/main/java/com/axis/innovators/box/browser/MainApplication.java @@ -1,5 +1,8 @@ package com.axis.innovators.box.browser; +import com.axis.innovators.box.tools.FolderCreator; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import org.cef.browser.CefBrowser; import org.cef.browser.CefFrame; import org.cef.browser.CefMessageRouter; @@ -8,9 +11,13 @@ import org.cef.handler.CefMessageRouterHandlerAdapter; import org.tzd.lm.LM; import javax.swing.*; +import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.List; import java.util.Objects; import java.util.concurrent.ExecutorService; @@ -27,46 +34,7 @@ public class MainApplication { private static long ctxHandle; private static boolean isSystem = true; public static void main(String[] args) { - LM.loadLibrary(LM.CUDA); - modelHandle = LM.llamaLoadModelFromFile(LM.DEEP_SEEK); - ctxHandle = LM.createContext(modelHandle); - - AtomicReference window = new AtomicReference<>(); - SwingUtilities.invokeLater(() -> { - WindowRegistry.getInstance().createNewChildWindow("main", builder -> - window.set(builder.title("Axis Innovators Box AI 工具箱") - .icon(new ImageIcon(Objects.requireNonNull(MainApplication.class.getClassLoader().getResource("icons/logo.png"))).getImage()) - .size(1280, 720) - .htmlPath(Objects.requireNonNull(MainApplication.class.getClassLoader().getResource("javascript/AIaToolbox.html")).getFile()) - .operationHandler(createOperationHandler()) - .build()) - ); - - CefMessageRouter msgRouter = window.get().getMsgRouter(); - if (msgRouter != null) { - msgRouter.addHandler(new CefMessageRouterHandlerAdapter() { - @Override - public boolean onQuery(CefBrowser browser, CefFrame frame, long queryId, - String request, boolean persistent, CefQueryCallback callback) { - // 处理浏览器请求 - handleBrowserQuery(browser, request, callback); - return true; - } - - @Override - public void onQueryCanceled(CefBrowser browser, CefFrame frame, long queryId) { - // 处理请求取消 - } - }, true); - } - }); - - // 关闭钩子 - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - LM.llamaFreeContext(ctxHandle); - LM.llamaFreeModel(modelHandle); - executor.shutdown(); - })); + //popupHTMLWindow(); } @@ -87,7 +55,7 @@ public class MainApplication { .parentFrame(parent) .icon(new ImageIcon(Objects.requireNonNull(MainApplication.class.getClassLoader().getResource("icons/logo.png"))).getImage()) .size(1280, 720) - .htmlPath(Objects.requireNonNull(MainApplication.class.getClassLoader().getResource("javascript/AIaToolbox_dark.html")).getFile()) + .htmlPath(FolderCreator.getJavaScriptFolder() + "\\" + "AIaToolbox_dark.html") .operationHandler(createOperationHandler()) .build()) ); @@ -110,13 +78,60 @@ public class MainApplication { }, true); } }); + } - // 关闭钩子 - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - LM.llamaFreeContext(ctxHandle); - LM.llamaFreeModel(modelHandle); - executor.shutdown(); - })); + /** + * 弹出html预览窗口 + */ + public static void popupHTMLWindow(String path) { + AtomicReference window = new AtomicReference<>(); + SwingUtilities.invokeLater(() -> { + WindowRegistry.getInstance().createNewWindow("main", builder -> + window.set(builder.title("Axis Innovators Box HTML查看器") + .icon(new ImageIcon(Objects.requireNonNull(MainApplication.class.getClassLoader().getResource("icons/logo.png"))).getImage()) + .size(1487, 836) + .htmlPath(FolderCreator.getJavaScriptFolder() + "\\" + "HtmlViewer.html") + .operationHandler(createOperationHandler()) + .build()) + ); + + CefMessageRouter msgRouter = window.get().getMsgRouter(); + if (msgRouter != null) { + msgRouter.addHandler(new CefMessageRouterHandlerAdapter() { + @Override + public boolean onQuery(CefBrowser browser, CefFrame frame, long queryId, + String request, boolean persistent, CefQueryCallback callback) { + try { + // 解析JSON请求 + JsonObject requestJson = JsonParser.parseString(request).getAsJsonObject(); + if (requestJson.has("type") && "loadInitialContent".equals(requestJson.get("type").getAsString())) { + + Path filePath = Paths.get(path); + + // 验证文件存在性 + if (!Files.exists(filePath)) { + callback.failure(404, "{\"code\":404,\"message\":\"文件未找到\"}"); + return true; + } + + // 读取文件内容 + String content = Files.readString(filePath, StandardCharsets.UTF_8); + callback.success(content); + return true; + } + } catch (Exception e) { + callback.failure(500, "{\"code\":500,\"message\":\"" + e.getMessage() + "\"}"); + } + return false; + } + + @Override + public void onQueryCanceled(CefBrowser browser, CefFrame frame, long queryId) { + // 处理请求取消 + } + }, true); + } + }); } private static void handleBrowserQuery(CefBrowser browser, String request, CefQueryCallback callback) { diff --git a/src/main/java/com/axis/innovators/box/decompilation/gui/JavaPseudocodeGenerator.java b/src/main/java/com/axis/innovators/box/decompilation/gui/JavaPseudocodeGenerator.java new file mode 100644 index 0000000..b158152 --- /dev/null +++ b/src/main/java/com/axis/innovators/box/decompilation/gui/JavaPseudocodeGenerator.java @@ -0,0 +1,339 @@ +package com.axis.innovators.box.decompilation.gui; + +import org.objectweb.asm.*; +import org.objectweb.asm.tree.*; +import org.objectweb.asm.tree.analysis.*; + +import java.lang.reflect.Field; +import java.util.*; + +public class JavaPseudocodeGenerator extends MethodVisitor { + private final StringBuilder output = new StringBuilder(); + private int indentLevel = 0; + private final Deque controlStack = new ArrayDeque<>(); + private final Map labelPositions = new HashMap<>(); + private int currentPosition = 0; + + // 类型推断相关数据结构 + private final Map localVarTypes = new HashMap<>(); + private final Analyzer analyzer; + private Frame[] frames; + private final Deque operandStack = new ArrayDeque<>(); + private final MethodNode methodNode; + + private static class TypeValue { + final Type type; + final String expression; + + TypeValue(Type type, String expression) { + this.type = type; + this.expression = expression; + } + + @Override + public String toString() { + return expression; + } + } + + private static class ControlStructure { + enum Type { LOOP, CONDITIONAL, TRY_CATCH } + + private final String header; + private final Type type; + private Label endLabel; + + ControlStructure(String header, Type type) { + this.header = header; + this.type = type; + } + + ControlStructure setEndLabel(Label endLabel) { + this.endLabel = endLabel; + return this; + } + + String getHeader() { return header; } + Label getEndLabel() { return endLabel; } + } + + public JavaPseudocodeGenerator(MethodNode methodNode) throws AnalyzerException { + super(Opcodes.ASM9); + this.methodNode = methodNode; + this.analyzer = new Analyzer<>(new BasicInterpreter()); + this.frames = analyzer.analyze(methodNode.name, methodNode); + initLabelPositions(methodNode); + analyzeLocalVariables(methodNode); + } + + private void initLabelPositions(MethodNode methodNode) { + labelPositions.clear(); + int pos = 0; + for (AbstractInsnNode insn : methodNode.instructions) { + if (insn instanceof LabelNode) { + Label label = ((LabelNode) insn).getLabel(); + labelPositions.put(label, pos); + } + pos++; + } + } + + private void analyzeLocalVariables(MethodNode methodNode) { + localVarTypes.clear(); + if (methodNode.localVariables != null) { + for (LocalVariableNode local : methodNode.localVariables) { + Type type = Type.getType(local.desc); + localVarTypes.put(local.index, type); + } + } + } + + public String getOutput() { + return output.toString(); + } + + @Override + public void visitCode() { + // 生成方法签名 + String access = getAccessModifier(methodNode.access); + Type returnType = Type.getReturnType(methodNode.desc); + Type[] argTypes = Type.getArgumentTypes(methodNode.desc); + + StringBuilder signature = new StringBuilder(); + signature.append(access) + .append(" ") + .append(returnType.getClassName()) + .append(" ") + .append(methodNode.name) + .append("("); + + List params = new ArrayList<>(); + for (int i = 0; i < argTypes.length; i++) { + params.add(argTypes[i].getClassName() + " arg" + i); + } + signature.append(String.join(", ", params)) + .append(") {"); + + addLine(signature.toString()); + indentLevel++; + } + + private String getAccessModifier(int access) { + List modifiers = new ArrayList<>(); + if ((access & Opcodes.ACC_PUBLIC) != 0) modifiers.add("public"); + if ((access & Opcodes.ACC_PRIVATE) != 0) modifiers.add("private"); + if ((access & Opcodes.ACC_PROTECTED) != 0) modifiers.add("protected"); + if ((access & Opcodes.ACC_STATIC) != 0) modifiers.add("static"); + return String.join(" ", modifiers); + } + + private void pushValue(Type type, String expr) { + operandStack.push(new TypeValue(type, expr)); + } + + private TypeValue popValue(Type expectedType) { + if (operandStack.isEmpty()) { + return new TypeValue(expectedType, "/* missing value */"); + } + TypeValue value = operandStack.pop(); + if (!value.type.equals(expectedType)) { + return new TypeValue(expectedType, "/* type error: " + value.expression + " */"); + } + return value; + } + + @Override + public void visitInsn(int opcode) { + switch (opcode) { + case Opcodes.RETURN: + addLine("return;"); + break; + case Opcodes.IRETURN: + addLine("return " + popValue(Type.INT_TYPE) + ";"); + break; + case Opcodes.ARETURN: + addLine("return " + popValue(Type.getType(Object.class)) + ";"); + break; + case Opcodes.ATHROW: + addLine("throw " + popValue(Type.getType(Throwable.class)) + ";"); + break; + case Opcodes.IADD: { + TypeValue val2 = popValue(Type.INT_TYPE); + TypeValue val1 = popValue(Type.INT_TYPE); + pushValue(Type.INT_TYPE, val1.expression + " + " + val2.expression); + break; + } + default: + addLine("// asm: " + OpcodesUtil.getOpcodeName(opcode)); + } + currentPosition++; + } + + @Override + public void visitVarInsn(int opcode, int var) { + Type type = localVarTypes.getOrDefault(var, Type.VOID_TYPE); + switch (opcode) { + case Opcodes.ILOAD: + pushValue(Type.INT_TYPE, "var" + var); + break; + case Opcodes.ALOAD: + pushValue(type, "var" + var); + break; + case Opcodes.ISTORE: + addLine("var" + var + " = " + popValue(Type.INT_TYPE) + ";"); + break; + case Opcodes.ASTORE: + addLine("var" + var + " = " + popValue(type) + ";"); + break; + default: + addLine("// asm: " + OpcodesUtil.getOpcodeName(opcode)); + } + currentPosition++; + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, + String descriptor, boolean isInterface) { + try { + Type returnType = Type.getReturnType(descriptor); + Type[] argTypes = Type.getArgumentTypes(descriptor); + + List args = new ArrayList<>(); + for (int i = argTypes.length - 1; i >= 0; i--) { + args.add(0, popValue(argTypes[i]).toString()); + } + + String methodCall; + if (opcode == Opcodes.INVOKESTATIC) { + methodCall = owner.replace('/', '.') + "." + name; + } else { + TypeValue target = popValue(Type.getType("L" + owner + ";")); + methodCall = target + "." + name; + } + + String callExpr = methodCall + "(" + String.join(", ", args) + ")"; + if (returnType != Type.VOID_TYPE) { + pushValue(returnType, callExpr); + } else { + addLine(callExpr + ";"); + } + } catch (Exception e) { + addLine("// asm: " + OpcodesUtil.getOpcodeName(opcode)); + } + currentPosition++; + } + + @Override + public void visitJumpInsn(int opcode, Label label) { + try { + TypeValue condition = popValue(Type.BOOLEAN_TYPE); + String operator = getConditionOperator(opcode); + + if (isLoopStart(label)) { + addLine("while (" + condition.expression + operator + ") {"); + indentLevel++; + controlStack.push(new ControlStructure("loop", ControlStructure.Type.LOOP).setEndLabel(label)); + } else { + addLine("if (" + condition.expression + operator + ") {"); + indentLevel++; + controlStack.push(new ControlStructure("if", ControlStructure.Type.CONDITIONAL).setEndLabel(label)); + } + } catch (Exception e) { + addLine("// asm: " + OpcodesUtil.getOpcodeName(opcode)); + } + currentPosition++; + } + + private String getConditionOperator(int opcode) { + switch (opcode) { + case Opcodes.IFEQ: return " == 0"; + case Opcodes.IFNE: return " != 0"; + case Opcodes.IFLT: return " < 0"; + case Opcodes.IFGE: return " >= 0"; + default: return ""; + } + } + + private boolean isLoopStart(Label label) { + int targetPos = getLabelPosition(label); + return targetPos != -1 && targetPos < currentPosition; + } + + private int getLabelPosition(Label label) { + return labelPositions.getOrDefault(label, -1); + } + + @Override + public void visitLabel(Label label) { + if (!controlStack.isEmpty()) { + ControlStructure structure = controlStack.peek(); + if (structure.getEndLabel() == null) { + structure.setEndLabel(label); + } else if (structure.getEndLabel().equals(label)) { + controlStack.pop(); + indentLevel--; + addLine("}"); + } + } + currentPosition++; + } + + @Override + public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { + addLine("try {"); + indentLevel++; + controlStack.push(new ControlStructure("try", ControlStructure.Type.TRY_CATCH).setEndLabel(end)); + currentPosition++; + } + + @Override + public void visitEnd() { + while (!controlStack.isEmpty()) { + indentLevel--; + addLine("}"); + controlStack.pop(); + } + indentLevel--; + addLine("}"); + } + + private void addLine(String line) { + output.append(" ".repeat(indentLevel)).append(line).append("\n"); + } + + private static class OpcodesUtil { + private static final Map OPCODE_NAMES = new HashMap<>(); + + static { + try { + for (Field field : Opcodes.class.getDeclaredFields()) { + if (field.getType() == int.class) { + OPCODE_NAMES.put(field.getInt(null), field.getName()); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + static String getOpcodeName(int opcode) { + return OPCODE_NAMES.getOrDefault(opcode, "UNKNOWN_OPCODE_" + opcode); + } + } + + public static void main(String[] args) throws Exception { + MethodNode methodNode = new MethodNode(Opcodes.ASM9, + Opcodes.ACC_PUBLIC, "add", + "(II)I", + null, + null); + methodNode.instructions.add(new VarInsnNode(Opcodes.ILOAD, 0)); + methodNode.instructions.add(new VarInsnNode(Opcodes.ILOAD, 1)); + methodNode.instructions.add(new InsnNode(Opcodes.IADD)); + methodNode.instructions.add(new InsnNode(Opcodes.IRETURN)); + + JavaPseudocodeGenerator generator = new JavaPseudocodeGenerator(methodNode); + methodNode.accept(generator); + System.out.println(generator.getOutput()); + } +} \ No newline at end of file diff --git a/src/main/java/com/axis/innovators/box/decompilation/gui/ModernJarViewer.java b/src/main/java/com/axis/innovators/box/decompilation/gui/ModernJarViewer.java index 8627a7e..40617eb 100644 --- a/src/main/java/com/axis/innovators/box/decompilation/gui/ModernJarViewer.java +++ b/src/main/java/com/axis/innovators/box/decompilation/gui/ModernJarViewer.java @@ -1,6 +1,7 @@ package com.axis.innovators.box.decompilation.gui; import com.axis.innovators.box.gui.LoadIcon; +import com.axis.innovators.box.util.AdvancedJFileChooser; import com.github.javaparser.JavaParser; import com.github.javaparser.ParseResult; import com.github.javaparser.Position; @@ -794,7 +795,7 @@ public class ModernJarViewer extends JFrame { /****/ private void openJarFile() { - JFileChooser chooser = new JFileChooser(); + AdvancedJFileChooser chooser = new AdvancedJFileChooser(); chooser.setFileFilter(new javax.swing.filechooser.FileFilter() { public boolean accept(File f) { return f.getName().endsWith(".jar") || f.isDirectory(); @@ -829,6 +830,7 @@ public class ModernJarViewer extends JFrame { String name = entry.getName(); // 忽略已被源码覆盖的class文件 if (name.endsWith(".class") && sourceFiles.contains(name)) continue; + addEntryToTree(root, name); } } catch (Exception ex) { diff --git a/src/main/java/com/axis/innovators/box/decompilation/gui/TypeMismatchException.java b/src/main/java/com/axis/innovators/box/decompilation/gui/TypeMismatchException.java new file mode 100644 index 0000000..c63c2da --- /dev/null +++ b/src/main/java/com/axis/innovators/box/decompilation/gui/TypeMismatchException.java @@ -0,0 +1,7 @@ +package com.axis.innovators.box.decompilation.gui; + +public class TypeMismatchException extends Exception{ + public TypeMismatchException(String s) { + super(s); + } +} diff --git a/src/main/java/com/axis/innovators/box/gui/FridaWindow.java b/src/main/java/com/axis/innovators/box/gui/FridaWindow.java index 1fb734c..26da815 100644 --- a/src/main/java/com/axis/innovators/box/gui/FridaWindow.java +++ b/src/main/java/com/axis/innovators/box/gui/FridaWindow.java @@ -1,6 +1,7 @@ package com.axis.innovators.box.gui; import com.axis.innovators.box.gui.renderer.DarculaCompletionCellRenderer; +import com.axis.innovators.box.util.AdvancedJFileChooser; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.fife.rsta.ac.LanguageSupport; @@ -479,7 +480,7 @@ public class FridaWindow extends WindowsJDialog { private void openFile() { - JFileChooser fileChooser = new JFileChooser(); + AdvancedJFileChooser fileChooser = new AdvancedJFileChooser(); fileChooser.setFileFilter(new javax.swing.filechooser.FileNameExtensionFilter("Frida脚本配置 (*.frida)", "frida")); int result = fileChooser.showOpenDialog(this); if (result == JFileChooser.APPROVE_OPTION) { @@ -503,7 +504,7 @@ public class FridaWindow extends WindowsJDialog { } private void saveFile() { - JFileChooser fileChooser = new JFileChooser(); + AdvancedJFileChooser fileChooser = new AdvancedJFileChooser(); fileChooser.setFileFilter(new javax.swing.filechooser.FileNameExtensionFilter("Frida脚本配置 (*.frida)", "frida")); int result = fileChooser.showSaveDialog(this); if (result == JFileChooser.APPROVE_OPTION) { diff --git a/src/main/java/com/axis/innovators/box/gui/JarApiProfilingWindow.java b/src/main/java/com/axis/innovators/box/gui/JarApiProfilingWindow.java new file mode 100644 index 0000000..f8d894b --- /dev/null +++ b/src/main/java/com/axis/innovators/box/gui/JarApiProfilingWindow.java @@ -0,0 +1,1469 @@ +package com.axis.innovators.box.gui; + +import com.axis.innovators.box.decompilation.gui.JavaPseudocodeGenerator; +import com.axis.innovators.box.util.AdvancedJFileChooser; +import com.formdev.flatlaf.FlatDarkLaf; +import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; +import org.fife.ui.rsyntaxtextarea.SyntaxConstants; +import org.fife.ui.rtextarea.RTextScrollPane; +import org.objectweb.asm.*; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.analysis.AnalyzerException; +import org.objectweb.asm.util.ASMifier; +import org.objectweb.asm.util.TraceClassVisitor; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.tree.*; +import javax.tools.*; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.awt.*; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.StringSelection; +import java.awt.event.*; +import java.io.*; +import java.net.URI; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; +import java.util.prefs.Preferences; +import java.util.stream.Collectors; + +public class JarApiProfilingWindow extends WindowsJDialog { + private List currentMethods = new ArrayList<>(); + private JTree classTree; + private DefaultTreeModel treeModel; + private JTextArea methodInfoArea; + private Map classMap = new HashMap<>(); + private JComboBox searchBox; + private RSyntaxTextArea asmEditor; + private JLabel statusBar; + private DefaultListModel methodListModel; + private ClassTreeRenderer treeCellRenderer; + // 图标资源 + private final ImageIcon IMPORT_ICON = loadIcon("/icons/import.png"); + private final ImageIcon EXPORT_ICON = loadIcon("/icons/export.png"); + private final ImageIcon SEARCH_ICON = loadIcon("/icons/search.png"); + private JList methodList; + + public JarApiProfilingWindow(Window owner) { + super((Frame) owner, "JAR API Profiler", true); + initUI(); + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + } + + public static void main(String[] args) { + SwingUtilities.invokeLater(() -> { + try { + UIManager.setLookAndFeel(new com.formdev.flatlaf.FlatDarculaLaf()); + } catch (Exception ex) { + ex.printStackTrace(); + } + JarApiProfilingWindow window = new JarApiProfilingWindow(null); + window.initUI(); + window.setVisible(true); + }); + } + + public void initUI() { + + // 初始化数据模型 + treeModel = new DefaultTreeModel(new DefaultMutableTreeNode("JAR 内容")); + methodListModel = new DefaultListModel<>(); + + // 设置窗口属性 + setMinimumSize(new Dimension(1200, 800)); + setPreferredSize(new Dimension(1200, 800)); + + // 创建菜单栏 + createMenuBar(); + + // 主界面布局 - 使用 BorderLayout 作为根布局 + JPanel mainPanel = new JPanel(new BorderLayout()); + + // 创建内容面板(使用分割面板) + JSplitPane contentSplitPane = createContentPane(); + mainPanel.add(contentSplitPane, BorderLayout.CENTER); + + // 状态栏 + mainPanel.add(createStatusBar(), BorderLayout.SOUTH); + + this.add(mainPanel); + pack(); + setLocationRelativeTo(null); + } + + private void refreshLayout() { + SwingUtilities.invokeLater(() -> { + classTree.updateUI(); + methodList.updateUI(); + asmEditor.updateUI(); + + revalidate(); + repaint(); + }); + } + + private JSplitPane createContentPane() { + JSplitPane mainSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); + mainSplit.setLeftComponent(createClassPanel()); + mainSplit.setRightComponent(createMethodPanel()); + mainSplit.setDividerLocation(300); + return mainSplit; + } + + private JComponent createClassPanel() { + JPanel panel = new JPanel(new BorderLayout()); + + // 搜索框 + JPanel searchPanel = new JPanel(new BorderLayout(5, 5)); + searchBox = new JComboBox<>(); + searchBox.setEditable(true); + + JButton searchBtn = new JButton(SEARCH_ICON); + searchBtn.addActionListener(e -> performSearch()); + searchPanel.add(new JLabel(SEARCH_ICON), BorderLayout.WEST); + searchPanel.add(searchBox, BorderLayout.CENTER); + searchPanel.add(searchBtn, BorderLayout.EAST); + + // 类树 + classTree = new JTree(treeModel); + classTree.setRootVisible(false); + classTree.setShowsRootHandles(true); + treeCellRenderer = new ClassTreeRenderer(classMap); + classTree.setCellRenderer(treeCellRenderer); + //classTree.setCellRenderer(new ClassTreeRenderer(classMap)); + classTree.addTreeSelectionListener(e -> { + TreePath newPath = e.getNewLeadSelectionPath(); + if (newPath == null) return; + + DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) newPath.getLastPathComponent(); + if (selectedNode.isRoot()) { + // 忽略根节点和一级包节点的选择 + return; + } + updateMethodList(); + }); + + //classTree.setSelectionModel(new DefaultTreeSelectionModel() { + // @Override + // public void removeSelectionPath(TreePath path) { + // if (path != null) super.removeSelectionPath(path); + // } +// + // @Override + // public void removeSelectionPaths(TreePath[] paths) { + // if (paths != null && paths.length > 0) { + // super.removeSelectionPaths(paths); + // } + // } + //}); + + classTree.addTreeSelectionListener(e -> { + TreePath newPath = e.getNewLeadSelectionPath(); + if (newPath == null) return; + + DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) newPath.getLastPathComponent(); + String className = getFullClassName(selectedNode); + + // 仅当选中类节点时更新方法列表 + if (classMap.containsKey(className)) { + updateMethodList(); + } else { + // 清空方法列表并显示提示 + methodListModel.clear(); + methodInfoArea.setText("请选择一个类节点以查看方法"); + asmEditor.setText(""); + } + }); + + classTree.addFocusListener(new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + // 保持选中状态 + classTree.requestFocusInWindow(); + } + }); + + JScrollPane scrollPane = new JScrollPane(classTree); + scrollPane.setMinimumSize(new Dimension(250, 400)); + scrollPane.setPreferredSize(new Dimension(300, 600)); + panel.add(scrollPane, BorderLayout.CENTER); + + panel.add(searchPanel, BorderLayout.NORTH); + panel.add(new JScrollPane(classTree), BorderLayout.CENTER); + panel.setBorder(BorderFactory.createTitledBorder("类结构")); + return panel; + } + + private void performSearch() { + String keyword = (String) searchBox.getEditor().getItem(); + treeCellRenderer.setSearchText(keyword); + classTree.repaint(); + + if (keyword == null || keyword.trim().isEmpty()) { + JOptionPane.showMessageDialog(this, "请输入搜索关键字"); + return; + } + + // 使用后台线程执行搜索 + new SwingWorker, Void>() { + @Override + protected List doInBackground() { + List results = new ArrayList<>(); + DefaultMutableTreeNode root = (DefaultMutableTreeNode) treeModel.getRoot(); + findNodes(root, keyword.trim().toLowerCase(), results); + return results; + } + + @Override + protected void done() { + try { + List results = get(); + if (results.isEmpty()) { + JOptionPane.showMessageDialog(JarApiProfilingWindow.this, + "未找到匹配项"); + return; + } + + // 显示搜索结果对话框 + showSearchResultsDialog(results); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + }.execute(); + } + + + private void findNodes(DefaultMutableTreeNode node, String keyword, List results) { + Enumeration children = node.children(); + while (children.hasMoreElements()) { + DefaultMutableTreeNode child = (DefaultMutableTreeNode) children.nextElement(); + + // 获取节点完整路径 + String fullPath = getFullClassName(child); + + // 匹配逻辑:显示文本或完整类名包含关键字 + boolean isMatch = child.getUserObject().toString().toLowerCase().contains(keyword) || + (classMap.containsKey(fullPath) && + fullPath.toLowerCase().contains(keyword)); + + if (isMatch) { + results.add(new TreePath(child.getPath())); + } + + // 递归搜索子节点 + if (!child.isLeaf()) { + findNodes(child, keyword, results); + } + } + } + + private void showSearchResultsDialog(List results) { + JDialog resultDialog = new JDialog(this, "搜索结果", false); + resultDialog.setLayout(new BorderLayout()); + + // 结果列表 + JList resultList = new JList<>(new AbstractListModel() { + @Override + public int getSize() { return results.size(); } + @Override + public TreePath getElementAt(int index) { return results.get(index); } + }); + + resultList.setCellRenderer(new DefaultListCellRenderer() { + @Override + public Component getListCellRendererComponent(JList list, Object value, + int index, boolean isSelected, boolean cellHasFocus) { + TreePath path = (TreePath) value; + DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent(); + + // 显示完整路径 + String text = getFullClassName(node) + " - " + node.getUserObject(); + return super.getListCellRendererComponent(list, text, index, isSelected, cellHasFocus); + } + }); + + resultList.addListSelectionListener(e -> { + if (!e.getValueIsAdjusting()) { + TreePath selected = resultList.getSelectedValue(); + classTree.setSelectionPath(selected); + classTree.scrollPathToVisible(selected); + } + }); + + // 分页控制 + JPanel controlPanel = new JPanel(); + JLabel countLabel = new JLabel("找到 " + results.size() + " 个结果"); + JButton prevBtn = new JButton("上一个"); + JButton nextBtn = new JButton("下一个"); + + prevBtn.addActionListener(e -> { + int index = resultList.getSelectedIndex() - 1; + if (index >= 0) { + resultList.setSelectedIndex(index); + } + }); + + nextBtn.addActionListener(e -> { + int index = resultList.getSelectedIndex() + 1; + if (index < results.size()) { + resultList.setSelectedIndex(index); + } + }); + + controlPanel.add(prevBtn); + controlPanel.add(nextBtn); + controlPanel.add(countLabel); + + resultDialog.add(new JScrollPane(resultList), BorderLayout.CENTER); + resultDialog.add(controlPanel, BorderLayout.SOUTH); + resultDialog.setSize(400, 300); + resultDialog.setLocationRelativeTo(this); + resultDialog.setVisible(true); + } + + + private JComponent createMethodPanel() { + JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); + + // 方法信息 + methodInfoArea = new JTextArea(); + //methodInfoArea.setEditable(false); + methodInfoArea.setLineWrap(true); // 启用自动换行 + methodInfoArea.setWrapStyleWord(true); // 按单词换行 + methodInfoArea.setComponentPopupMenu(createTextPopup()); + JScrollPane infoScroll = new JScrollPane(methodInfoArea); + infoScroll.setBorder(BorderFactory.createTitledBorder("方法详细信息")); + + KeyStroke copyKey = KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK); + methodInfoArea.getInputMap().put(copyKey, "copy"); + methodInfoArea.getActionMap().put("copy", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + methodInfoArea.copy(); + } + }); + + // 方法列表和编辑器 + JSplitPane bottomPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); + bottomPane.setTopComponent(createMethodList()); + bottomPane.setBottomComponent(createAsmEditor()); + bottomPane.setDividerLocation(150); + + splitPane.setTopComponent(infoScroll); + splitPane.setBottomComponent(bottomPane); + splitPane.setDividerLocation(250); + return splitPane; + } + + private JPopupMenu createTextPopup() { + JPopupMenu popup = new JPopupMenu(); + + // 全选菜单项 + JMenuItem selectAllItem = new JMenuItem("全选"); + selectAllItem.addActionListener(e -> methodInfoArea.selectAll()); + + // 复制菜单项(增强版) + JMenuItem copyItem = new JMenuItem("复制"); + copyItem.addActionListener(e -> { + if (!methodInfoArea.getSelectedText().isEmpty()) { + // 使用系统剪贴板 + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + StringSelection selection = new StringSelection(methodInfoArea.getSelectedText()); + clipboard.setContents(selection, null); + } else { + // 没有选择时自动复制全部内容 + String fullText = methodInfoArea.getText(); + StringSelection selection = new StringSelection(fullText); + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + clipboard.setContents(selection, null); + } + }); + + // 添加菜单项 + popup.add(selectAllItem); + popup.addSeparator(); + popup.add(copyItem); + + return popup; + } + + private JComponent createMethodList() { + methodList = new JList<>(methodListModel); + methodList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + methodList.addListSelectionListener(e -> { + if (!e.getValueIsAdjusting()) { + SwingUtilities.invokeLater(() -> { + if (methodList.getSelectedIndex() == -1 && + methodListModel.size() > 0) { + methodList.setSelectedIndex(0); + } else { + showSelectedMethod(); + } + }); + } + }); + + methodList.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { // 双击事件 + showJavaPseudocode(); + } + } + }); + + + + JScrollPane scrollPane = new JScrollPane(methodList); + scrollPane.setBorder(BorderFactory.createTitledBorder("方法")); + scrollPane.setPreferredSize(new Dimension(300, 150)); + return scrollPane; + } + + private void showJavaPseudocode() { + MethodNode method = getSelectedMethod(); + if (method == null) { + JOptionPane.showMessageDialog(this, "请先选择有效方法"); + return; + } + + try { + // 使用完整的生成器初始化(包含方法节点参数) + JavaPseudocodeGenerator generator = new JavaPseudocodeGenerator(method); + + // 创建带语法高亮的代码区 + RSyntaxTextArea codeArea = new RSyntaxTextArea(); + codeArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA); + codeArea.setText(generator.getOutput()); + codeArea.setEditable(false); + codeArea.setCaretPosition(0); + + // 创建带行号的滚动面板 + RTextScrollPane scrollPane = new RTextScrollPane(codeArea); + scrollPane.setLineNumbersEnabled(true); + + // 配置对话框 + JDialog dialog = new JDialog(this, "Java 伪代码 - " + method.name, true); + dialog.setLayout(new BorderLayout()); + dialog.add(scrollPane, BorderLayout.CENTER); + + // 添加工具栏 + JToolBar toolBar = new JToolBar(); + JButton copyButton = new JButton("复制代码"); + copyButton.addActionListener(e -> { + codeArea.selectAll(); + codeArea.copy(); + }); + toolBar.add(copyButton); + dialog.add(toolBar, BorderLayout.NORTH); + + // 设置对话框属性 + dialog.setSize(800, 600); + dialog.setLocationRelativeTo(this); + dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + dialog.setVisible(true); + + } catch (AnalyzerException ex) { + JOptionPane.showMessageDialog(this, + "分析方法时出现错误:\n" + ex.getMessage(), + "控制流分析错误", + JOptionPane.ERROR_MESSAGE); + } catch (Exception ex) { + JOptionPane.showMessageDialog(this, + "生成伪代码失败:\n" + ex.getMessage(), + "生成错误", + JOptionPane.ERROR_MESSAGE); + } + } + + private JComponent createAsmEditor() { + asmEditor = new RSyntaxTextArea(); + asmEditor.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA); + RTextScrollPane scrollPane = new RTextScrollPane(asmEditor); + scrollPane.setBorder(BorderFactory.createTitledBorder("ASM 代码")); + return scrollPane; + } + + + private void createMenuBar() { + JMenuBar menuBar = new JMenuBar(); + + // 文件菜单 + JMenu fileMenu = new JMenu("文件"); + JMenuItem importItem = new JMenuItem("导入 JAR", IMPORT_ICON); + JMenuItem exportItem = new JMenuItem("导出 JAR", EXPORT_ICON); + + importItem.addActionListener(this::handleImport); + exportItem.addActionListener(this::handleExport); + + fileMenu.add(importItem); + fileMenu.add(exportItem); + menuBar.add(fileMenu); + + setJMenuBar(menuBar); + + JMenu editMenu = new JMenu("编辑"); + JMenuItem annotateItem = new JMenuItem("添加注解"); + annotateItem.addActionListener(e -> addAnnotationToCurrentMethod()); + editMenu.add(annotateItem); + menuBar.add(editMenu); + } + + private void addAnnotationToCurrentMethod() { + int index = methodList.getSelectedIndex(); + if (index == -1) { + JOptionPane.showMessageDialog(this, "请先选择方法"); + return; + } + + MethodNode method = currentMethods.get(index); + + // 创建多行输入组件 + JTextArea textArea = new JTextArea(5, 30); + textArea.setLineWrap(true); + + // 填充已有注解(转换为可编辑格式) + if (method.visibleAnnotations != null && !method.visibleAnnotations.isEmpty()) { + String existingAnnotations = method.visibleAnnotations.stream() + .map(ann -> { + // 将Lcom/example/Annotation; 转换为 @com.example.Annotation + String desc = ann.desc; + return desc.startsWith("L") && desc.endsWith(";") ? + desc.substring(1, desc.length()-1).replace('/', '.') : + desc; + }) + .collect(Collectors.joining("\n")); + textArea.setText(existingAnnotations); + } + + JScrollPane scrollPane = new JScrollPane(textArea); + + // 显示带滚动条的多行输入对话框 + int result = JOptionPane.showConfirmDialog( + this, + scrollPane, + "编辑注解(每行一个注解)", + JOptionPane.OK_CANCEL_OPTION, + JOptionPane.PLAIN_MESSAGE + ); + + if (result != JOptionPane.OK_OPTION) return; + + // 处理输入内容 + String input = textArea.getText().trim(); + if (input.isEmpty()) return; + + // 清空现有注解 + if (method.visibleAnnotations != null) { + method.visibleAnnotations.clear(); + } else { + method.visibleAnnotations = new ArrayList<>(); + } + + // 按行分割并添加新注解 + Arrays.stream(input.split("\\r?\\n")) + .filter(line -> !line.trim().isEmpty()) + .forEach(line -> { + String processedLine = line.trim(); + + // 自动格式化(如果用户输入@开头则去掉) + if (processedLine.startsWith("@")) { + processedLine = processedLine.substring(1); + } + + // 转换为ASM需要的格式:L完整类名; + processedLine = processedLine.replace('.', '/'); + if (!processedLine.startsWith("L")) { + processedLine = "L" + processedLine; + } + if (!processedLine.endsWith(";")) { + processedLine += ";"; + } + + method.visibleAnnotations.add(new AnnotationNode(processedLine)); + }); + + // 更新classMap + TreePath classPath = classTree.getSelectionPath(); + String className = getFullClassName((DefaultMutableTreeNode) classPath.getLastPathComponent()); + ClassNode classNode = classMap.get(className); + + if (classNode != null) { + int methodIndex = classNode.methods.indexOf(method); + if (methodIndex != -1) { + // 创建方法副本以确保树节点刷新 + MethodNode newMethod = new MethodNode( + method.access, + method.name, + method.desc, + method.signature, + method.exceptions.toArray(new String[0]) + ); + newMethod.visibleAnnotations = method.visibleAnnotations; + newMethod.instructions = method.instructions; + newMethod.tryCatchBlocks = method.tryCatchBlocks; + + classNode.methods.set(methodIndex, newMethod); + currentMethods.set(index, newMethod); // 更新当前方法列表 + } + } + + // 刷新显示 + displayMethodInfo(currentMethods.get(index)); + JOptionPane.showMessageDialog(this, + "已成功更新 " + method.visibleAnnotations.size() + " 个注解"); + } + + private MethodNode getSelectedMethod() { + int index = methodList.getSelectedIndex(); + if (index >= 0 && index < currentMethods.size()) { + return currentMethods.get(index); + } + return null; + } + + + private void handleExport(ActionEvent e) { + AdvancedJFileChooser chooser = new AdvancedJFileChooser(); + if (chooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) { + new Thread(() -> { + try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(chooser.getSelectedFile()))) { + + // 1. 写入所有类文件 + for (Map.Entry entry : classMap.entrySet()) { + JarEntry je = new JarEntry(entry.getKey() + ".class"); + jos.putNextEntry(je); + + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); + entry.getValue().accept(cw); + jos.write(cw.toByteArray()); + + jos.closeEntry(); + } + + // 2. 写入注解元数据 + JarEntry metaEntry = new JarEntry("META-INF/annotations.xml"); + jos.putNextEntry(metaEntry); + + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); + Document doc = docBuilder.newDocument(); + + Element rootElement = doc.createElement("annotations"); + doc.appendChild(rootElement); + + for (ClassNode classNode : classMap.values()) { + for (MethodNode method : classNode.methods) { + if (method.visibleAnnotations != null && !method.visibleAnnotations.isEmpty()) { + Element methodElement = doc.createElement("method"); + methodElement.setAttribute("class", classNode.name); + methodElement.setAttribute("name", method.name); + methodElement.setAttribute("desc", method.desc); + + for (AnnotationNode ann : method.visibleAnnotations) { + Element annElement = doc.createElement("annotation"); + annElement.setTextContent(ann.desc); + methodElement.appendChild(annElement); + } + + rootElement.appendChild(methodElement); + } + } + } + + // 将XML写入JAR + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + Transformer transformer = transformerFactory.newTransformer(); + DOMSource source = new DOMSource(doc); + StreamResult result = new StreamResult(new OutputStreamWriter(jos, StandardCharsets.UTF_8)); + transformer.transform(source, result); + + jos.closeEntry(); + + JOptionPane.showMessageDialog(this, "JAR导出成功"); + } catch (Exception ex) { + JOptionPane.showMessageDialog(this, "导出失败: " + ex.getMessage()); + } + }).start(); + } + } + + + private void handleImport(ActionEvent e) { + AdvancedJFileChooser chooser = new AdvancedJFileChooser() { + @Override + protected JDialog createDialog(Component parent) throws HeadlessException { + JDialog dialog = super.createDialog(parent); + dialog.setIconImage(EXPORT_ICON.getImage()); // 设置对话框图标 + return dialog; + } + }; + chooser.setDialogTitle("选择要打开的Jar Api"); + chooser.setFileFilter(new FileNameExtensionFilter("JAR Files (*.jar)", "jar")); + + if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { + loadJarFile(chooser.getSelectedFile()); + } + } + + private void loadJarFile(File file) { + ProgressDialog progressDialog = new ProgressDialog(this); + List classEntries = new ArrayList<>(); + + SwingWorker worker = new SwingWorker() { + private DefaultMutableTreeNode root = new DefaultMutableTreeNode(file.getName()); + private int totalClasses; + private int processed; + private long lastUpdateTime; + + @Override + protected Void doInBackground() throws Exception { + // 第一阶段:快速扫描类文件 + try (JarFile jar = new JarFile(file)) { + jar.stream() + .filter(entry -> entry.getName().endsWith(".class")) + .forEach(classEntries::add); + totalClasses = classEntries.size(); + } + + // 第二阶段:并行处理类文件 + try (JarFile jar = new JarFile(file)) { + classEntries.parallelStream().forEach(entry -> { + if (isCancelled()) return; + + try (InputStream is = jar.getInputStream(entry)) { + ClassReader reader = new ClassReader(is); + ClassNode classNode = new ClassNode(); + reader.accept(classNode, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + + List methods = new ArrayList<>(); + for (MethodNode method : classNode.methods) { + methods.add(String.format("%s %s%s", + getAccessString(method.access), + method.name, + method.desc)); + } + + publish(new ClassProcessingResult( + classNode.name, + methods, + classNode + )); + + processed++; + updateProgress(); + } catch (Exception e) { + System.err.println("Error processing: " + entry.getName()); + } + }); + } + return null; + } + + private void updateProgress() { + int progress = (int) ((processed * 100.0) / totalClasses); + setProgress(progress); + } + + @Override + protected void process(List chunks) { + // 控制UI更新频率 (每秒最多20次) + if (System.currentTimeMillis() - lastUpdateTime < 50) return; + + SwingUtilities.invokeLater(() -> { + for (ClassProcessingResult result : chunks) { + processClassNode(result); + } + treeModel.nodeStructureChanged(root); + lastUpdateTime = System.currentTimeMillis(); + }); + } + + private void processClassNode(ClassProcessingResult result) { + String[] packages = result.className.split("/"); + DefaultMutableTreeNode parent = root; + + // 动态构建包结构 + for (int i=0; i + classNode.add(new DefaultMutableTreeNode(method))); + + parent.add(classNode); + classMap.put(result.className, result.classNode); + } + + @Override + protected void done() { + SwingUtilities.invokeLater(() -> { + progressDialog.dispose(); + if (isCancelled()) { + statusBar.setText("操作已取消"); + return; + } + + // 最终树结构更新 + treeModel.setRoot(root); + statusBar.setText("已加载 " + processed + " 个类"); + + // 异步展开节点 + new Thread(() -> expandAllNodes(classTree)).start(); + }); + } + }; + + // 配置进度更新 + worker.addPropertyChangeListener(evt -> { + if ("progress" == evt.getPropertyName()) { + progressDialog.updateProgress(worker.getProgress()); + } + if (evt.getPropertyName().equals("state")) { + if (evt.getNewValue() == SwingWorker.StateValue.STARTED) { + SwingUtilities.invokeLater(() -> { + classTree.setEnabled(false); + progressDialog.startProcessing(classEntries.size()); + progressDialog.setVisible(true); + }); + } else if (evt.getNewValue() == SwingWorker.StateValue.DONE) { + SwingUtilities.invokeLater(() -> classTree.setEnabled(true)); + } + } + }); + + progressDialog.getCancelButton().addActionListener(e -> { + worker.cancel(true); + SwingUtilities.invokeLater(() -> + statusBar.setText("取消中...")); + }); + + worker.execute(); + + try (JarFile jar = new JarFile(file)) { + // 加载注解元数据 + JarEntry metaEntry = jar.getJarEntry("META-INF/annotations.xml"); + if (metaEntry != null) { + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + Document doc = dBuilder.parse(jar.getInputStream(metaEntry)); + + NodeList methods = doc.getElementsByTagName("method"); + for (int i = 0; i < methods.getLength(); i++) { + Element methodElement = (Element) methods.item(i); + String className = methodElement.getAttribute("class"); + String methodName = methodElement.getAttribute("name"); + String methodDesc = methodElement.getAttribute("desc"); + + ClassNode classNode = classMap.get(className); + if (classNode != null) { + for (MethodNode method : classNode.methods) { + if (method.name.equals(methodName) && method.desc.equals(methodDesc)) { + NodeList annotations = methodElement.getElementsByTagName("annotation"); + for (int j = 0; j < annotations.getLength(); j++) { + String annDesc = annotations.item(j).getTextContent(); + AnnotationNode ann = new AnnotationNode(annDesc); + if (method.visibleAnnotations == null) { + method.visibleAnnotations = new ArrayList<>(); + } + method.visibleAnnotations.add(ann); + } + break; + } + } + } + } + } + } catch (Exception ex) { + System.err.println("加载注解失败: " + ex.getMessage()); + } + } + + private static class ClassProcessingResult { + final String className; + final List methods; + final ClassNode classNode; + + ClassProcessingResult(String className, List methods, ClassNode classNode) { + this.className = className; + this.methods = methods; + this.classNode = classNode; + } + } + + + private void expandAllNodes(JTree tree) { + DefaultMutableTreeNode root = (DefaultMutableTreeNode) treeModel.getRoot(); + Enumeration nodes = root.breadthFirstEnumeration(); + + while (nodes.hasMoreElements()) { + DefaultMutableTreeNode node = (DefaultMutableTreeNode) nodes.nextElement(); + if (node.getChildCount() > 0) { + SwingUtilities.invokeLater(() -> { + TreePath path = new TreePath(node.getPath()); + tree.expandPath(path); + }); + try { + Thread.sleep(10); // 控制展开速度 + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + } + + + private DefaultMutableTreeNode getSelectedClassNode() { + TreePath path = classTree.getSelectionPath(); + if (path != null) { + DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent(); + // 确保选择的是类节点(有方法子节点) + if (!node.isLeaf() && node.getChildCount() > 0) { + return node; + } + } + return null; + } + + private void showMethodASM(MethodNode method) { + try { + // 方法1:直接生成方法代码(需要完整类上下文) + ClassWriter cw = new ClassWriter(0); + TraceClassVisitor tracer = new TraceClassVisitor( + cw, + new ASMifier(), + new PrintWriter(new StringWriter()) + ); + MethodVisitor mv = tracer.visitMethod( + method.access, + method.name, + method.desc, + method.signature, + method.exceptions.toArray(new String[0]) + ); + method.accept(mv); + mv.visitEnd(); + + // 方法2:通过临时类生成(推荐) + StringWriter sw = new StringWriter(); + ClassNode tempClass = new ClassNode(); + tempClass.name = "GeneratedClass"; + tempClass.methods.add(method); + tempClass.accept(new TraceClassVisitor( + null, + new ASMifier(), + new PrintWriter(sw) + )); + asmEditor.setText(sw.toString()); + } catch (Exception e) { + SwingUtilities.invokeLater(() -> + JOptionPane.showMessageDialog(this, + "ASM生成错误: " + e.getMessage()) + ); + } + } + + private void showSelectedMethod() { + int index = methodList.getSelectedIndex(); + if (index == -1 || index >= currentMethods.size()) return; + + MethodNode method = currentMethods.get(index); + displayMethodInfo(method); + showMethodASM(method); + } + + + private String getFullClassName(DefaultMutableTreeNode node) { + LinkedList pathSegments = new LinkedList<>(); + TreeNode currentNode = node; + + // 向上遍历到根节点(不含) + while (currentNode != null && !((DefaultMutableTreeNode)currentNode).isRoot()) { + pathSegments.addFirst(((DefaultMutableTreeNode)currentNode).getUserObject().toString()); + currentNode = currentNode.getParent(); + } + return String.join("/", pathSegments); + } + + + private void displayMethodInfo(MethodNode method) { + // 主要信息表格 + String formatTemplate = + "╔══════════════════════════════════════════════╗\n" + + "║ %-16s: %-34s ║\n" + + "║ %-16s: %-34s ║\n" + + "║ %-16s: %-34s ║\n" + + "║ %-16s: %-34s ║\n" + + "║ %-16s: %-34s ║\n" + + "║ %-16s: %-34s ║\n" + + "║ %-16s: %-34s ║\n" + + "║ %-16s: %-34s ║\n" + + "╚══════════════════════════════════════════════╝\n" + + "✨ 注解信息 ✨\n" + + "▔▔▔▔▔▔▔▔▔▔▔▔▔\n" + + "%s"; // 单独处理注解信息 + + String annotations = highlightAnnotations(getAnnotations(method)); + + String info = String.format( + formatTemplate, + "方法名称", limitWidth(method.name, 30), + "访问权限", limitWidth(getEnhancedAccessString(method.access), 30), + "方法描述符", limitWidth(method.desc, 30), + "泛型签名", limitWidth(method.signature != null ? method.signature : "无", 30), + "参数类型", limitWidth(getParameters(method), 30), + "返回类型", limitWidth(getReturnType(method.desc), 30), + "异常列表", limitWidth(getExceptions(method), 30), + "字节码特性", limitWidth(getBytecodeFeatures(method), 30), + annotations // 最后一个参数对应%s + ); + + methodInfoArea.setText(info); + } + + // 高亮注解的辅助方法 + private String highlightAnnotations(String annotations) { + if (annotations == null || annotations.isEmpty()) { + return "⚠ 无注解信息"; + } + return "⦿ " + annotations.replaceAll(", ", "\n⦿ "); + } + + // 修改后的字符串截断方法(支持换行) + private String limitWidth(String text, int maxWidth) { + if (text == null) return ""; + + return text.length() > maxWidth ? + text.substring(0, maxWidth - 3) + "..." : + text; + } + + + // 新增辅助方法 + private String getExceptions(MethodNode method) { + return method.exceptions != null && !method.exceptions.isEmpty() ? + String.join(", ", method.exceptions) : "无"; + } + + private String getBytecodeFeatures(MethodNode method) { + List features = new ArrayList<>(); + + // 栈和局部变量信息 + features.add("栈深度: " + method.maxStack); + features.add("局部变量: " + method.maxLocals); + + // 特殊修饰符 + if ((method.access & Opcodes.ACC_SYNCHRONIZED) != 0) { + features.add("同步方法"); + } + if ((method.access & Opcodes.ACC_BRIDGE) != 0) { + features.add("桥接方法"); + } + if ((method.access & Opcodes.ACC_VARARGS) != 0) { + features.add("可变参数"); + } + + // 方法参数元数据 + if (method.parameters != null && !method.parameters.isEmpty()) { + features.add("参数名元数据"); + } + + return String.join(" | ", features); + } + + private String getEnhancedAccessString(int access) { + List modifiers = new ArrayList<>(); + + // 基础访问修饰符 + if ((access & Opcodes.ACC_PUBLIC) != 0) modifiers.add("public"); + if ((access & Opcodes.ACC_PRIVATE) != 0) modifiers.add("private"); + if ((access & Opcodes.ACC_PROTECTED) != 0) modifiers.add("protected"); + if ((access & Opcodes.ACC_STATIC) != 0) modifiers.add("static"); + if ((access & Opcodes.ACC_FINAL) != 0) modifiers.add("final"); + + // 特殊修饰符 + if ((access & Opcodes.ACC_SYNCHRONIZED) != 0) modifiers.add("synchronized"); + if ((access & Opcodes.ACC_BRIDGE) != 0) modifiers.add("bridge"); + if ((access & Opcodes.ACC_VARARGS) != 0) modifiers.add("varargs"); + if ((access & Opcodes.ACC_NATIVE) != 0) modifiers.add("native"); + if ((access & Opcodes.ACC_ABSTRACT) != 0) modifiers.add("abstract"); + if ((access & Opcodes.ACC_STRICT) != 0) modifiers.add("strictfp"); + + return String.join(" + ", modifiers); + } + + private void processClassEntry(JarFile jar, JarEntry entry, DefaultMutableTreeNode root) + throws IOException { + try (InputStream is = jar.getInputStream(entry)) { + ClassReader reader = new ClassReader(is); + ClassNode classNode = new ClassNode(); + + // 优化点1:减少解析深度提升性能 + reader.accept(classNode, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + + // 优化点2:在EDT线程执行UI操作 + SwingUtilities.invokeLater(() -> { + String[] packages = classNode.name.split("/"); + DefaultMutableTreeNode parentNode = root; + for (int i = 0; i < packages.length - 1; i++) { + final int index = i; + parentNode = findOrCreatePackageNode(parentNode, packages[index]); + } + DefaultMutableTreeNode classTreeNode = new DefaultMutableTreeNode( + packages[packages.length - 1].replace(".class", "") + ); + List methodNodes = new ArrayList<>(); + for (MethodNode method : classNode.methods) { + String methodText = String.format("%s %s%s", + getAccessString(method.access), + method.name, + method.desc + ); + methodNodes.add(new DefaultMutableTreeNode(methodText)); + } + methodNodes.forEach(classTreeNode::add); + parentNode.add(classTreeNode); + synchronized (classMap) { + classMap.put(classNode.name, classNode); + } + treeModel.nodeStructureChanged(parentNode); + }); + } + } + + + private DefaultMutableTreeNode findOrCreatePackageNode(DefaultMutableTreeNode parent, String name) { + Enumeration children = parent.children(); + while (children.hasMoreElements()) { + DefaultMutableTreeNode child = (DefaultMutableTreeNode) children.nextElement(); + if (child.getUserObject().equals(name)) { + return child; + } + } + + DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(name); + parent.add(newNode); + return newNode; + } + + private void updateMethodList() { + + SwingUtilities.invokeLater(() -> { + TreePath selectedPath = classTree.getSelectionPath(); + if (selectedPath == null) return; + + DefaultMutableTreeNode node = (DefaultMutableTreeNode) selectedPath.getLastPathComponent(); + String className = getFullClassName(node); + + // 验证是否为有效类节点 + if (!classMap.containsKey(className)) { + return; + } + + ClassNode cn = classMap.get(className); + methodListModel.clear(); + currentMethods.clear(); + + if (cn != null && cn.methods != null) { + cn.methods.forEach(m -> { + String methodInfo = String.format("%s %s%s", + getAccessString(m.access), + m.name, + m.desc); + methodListModel.addElement(methodInfo); + currentMethods.add(m); + }); + + if (!methodListModel.isEmpty()) { + methodList.setSelectedIndex(0); + } else { + methodInfoArea.setText("该类没有方法"); + asmEditor.setText(""); + } + } + }); + } + + private boolean isValidClassNode(DefaultMutableTreeNode node) { + if (node == null || node.isRoot()) return false; + + String className = getFullClassName(node); + // 双重验证:存在于classMap且包含方法子节点 + return classMap.containsKey(className) && + node.getChildCount() > 0 && + isMethodNode((DefaultMutableTreeNode) node.getFirstChild()); + } + + private boolean isMethodNode(DefaultMutableTreeNode node) { + return node.getUserObject().toString().contains("("); + } + + private boolean isClassNode(DefaultMutableTreeNode node) { + String className = getFullClassName(node); + return classMap.containsKey(className) && + node.getChildCount() > 0 && + ((DefaultMutableTreeNode)node.getFirstChild()).getUserObject().toString().contains("("); + } + + private String getAccessString(int access) { + List modifiers = new ArrayList<>(); + if ((access & Opcodes.ACC_PUBLIC) != 0) modifiers.add("public"); + if ((access & Opcodes.ACC_PRIVATE) != 0) modifiers.add("private"); + if ((access & Opcodes.ACC_PROTECTED) != 0) modifiers.add("protected"); + if ((access & Opcodes.ACC_STATIC) != 0) modifiers.add("static"); + if ((access & Opcodes.ACC_FINAL) != 0) modifiers.add("final"); + if ((access & Opcodes.ACC_SYNCHRONIZED) != 0) modifiers.add("synchronized"); + return String.join(" ", modifiers); + } + + private ImageIcon loadIcon(String path) { + try { + // 获取原始图标 + URL imgUrl = getClass().getResource(path); + if (imgUrl == null) return new ImageIcon(); + ImageIcon originalIcon = new ImageIcon(imgUrl); + + // 设置目标尺寸(示例调整为24x24,可根据需要修改) + int targetWidth = 24; + int targetHeight = 24; + + // 等比例缩放 + Image scaledImage = originalIcon.getImage() + .getScaledInstance(targetWidth, targetHeight, Image.SCALE_SMOOTH); + + return new ImageIcon(scaledImage); + } catch (Exception e) { + return new ImageIcon(); // 返回空图标 + } + } + + private String getParameters(MethodNode method) { + org.objectweb.asm.Type[] argTypes = org.objectweb.asm.Type.getArgumentTypes(method.desc); + return Arrays.stream(argTypes) + .map(t -> t.getClassName().replace('$', '.')) + .collect(Collectors.joining(", ")); + } + + private String getReturnType(String desc) { + return org.objectweb.asm.Type.getReturnType(desc).getClassName(); + } + + private String getAnnotations(MethodNode method) { + if (method.visibleAnnotations == null) return "None"; + return method.visibleAnnotations.stream() + .map(a -> "@" + a.desc.replace("L", "").replace(";", "")) + .collect(Collectors.joining(", ")); + } + + private JPanel createStatusBar() { + statusBar = new JLabel(" 已完成"); + statusBar.setBorder(BorderFactory.createEtchedBorder()); + JPanel statusPanel = new JPanel(new BorderLayout()); + statusPanel.add(statusBar); + return statusPanel; + } + + private String getParentClassName(DefaultMutableTreeNode methodNode) { + TreeNode parent = methodNode.getParent(); + if (parent instanceof DefaultMutableTreeNode) { + Object userObject = ((DefaultMutableTreeNode) parent).getUserObject(); + if (userObject instanceof String) { + return (String) userObject; + } + } + return null; + } + + private static class ClassTreeRenderer extends DefaultTreeCellRenderer { + private final ImageIcon packageIcon = new ImageIcon("icons/package.png"); + private final ImageIcon classIcon = new ImageIcon("icons/class.png"); + private final ImageIcon methodIcon = new ImageIcon("icons/method.png"); + private final Map classMapRef; + private String searchText; + + public ClassTreeRenderer(Map classMap) { + this.classMapRef = classMap; + } + + @Override + public Component getTreeCellRendererComponent(JTree tree, Object value, + boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { + super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus); + DefaultMutableTreeNode node = (DefaultMutableTreeNode) value; + + String text = node.getUserObject().toString(); + + // 高亮匹配文本 + if (searchText != null && !searchText.isEmpty() && + text.toLowerCase().contains(searchText.toLowerCase())) { + setForeground(Color.RED); + setFont(getFont().deriveFont(Font.BOLD)); + } else { + setForeground(tree.getForeground()); + setFont(tree.getFont()); + } + + // 获取完整类路径 + LinkedList pathSegments = new LinkedList<>(); + TreeNode currentNode = node; + + while (currentNode != null && !((DefaultMutableTreeNode)currentNode).isRoot()) { + pathSegments.addFirst(((DefaultMutableTreeNode)currentNode).getUserObject().toString()); + currentNode = currentNode.getParent(); + } + + String className = String.join("/", pathSegments); + + // 判断逻辑 + if (node.isRoot()) { + setIcon(null); + } else if (classMapRef.containsKey(className)) { + setIcon(classIcon); + } else if (isMethodNode(node)) { + setIcon(methodIcon); + } else { + setIcon(packageIcon); + } + + //System.out.println("ClassMap contains " + className + ": " + classMapRef.containsKey(className)); + return this; + } + + private boolean isClassNode(DefaultMutableTreeNode node) { + return node.getParent() != null && + node.getParent() instanceof DefaultMutableTreeNode && + ((DefaultMutableTreeNode) node.getParent()).getUserObject() instanceof String && + !((String) node.getUserObject()).contains("("); + } + + private boolean isMethodNode(DefaultMutableTreeNode node) { + return node.getUserObject().toString().contains("("); + } + + public void setSearchText(String text) { + this.searchText = text; + } + } + + private static class HighlightRenderer extends JLabel implements ListCellRenderer { + private String searchText; + + public Component getListCellRendererComponent(JList list, + String value, int index, boolean isSelected, boolean cellHasFocus) { + setText(value); + if (searchText != null && value.contains(searchText)) { + setForeground(Color.RED); + setFont(getFont().deriveFont(Font.BOLD)); + } else { + setForeground(list.getForeground()); + setFont(list.getFont()); + } + return this; + } + + public void setSearchText(String text) { + this.searchText = text; + } + } + + // 进度弹窗类 + class ProgressDialog extends JDialog { + private JProgressBar progressBar; + private JLabel statusLabel; + private JLabel timeLabel; + private long startTime; + private int totalFiles; + private JButton cancelButton; + + public ProgressDialog(JarApiProfilingWindow owner) { + super(owner, "Processing JAR", true); + initUI(); + } + + private void initUI() { + setLayout(new BorderLayout()); + setSize(400, 150); + setLocationRelativeTo(getOwner()); + + JPanel contentPanel = new JPanel(new BorderLayout(10, 10)); + contentPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + + progressBar = new JProgressBar(0, 100); + statusLabel = new JLabel("Preparing..."); + timeLabel = new JLabel(" "); + + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + cancelButton = new JButton("Cancel"); + buttonPanel.add(cancelButton); + + contentPanel.add(statusLabel, BorderLayout.NORTH); + contentPanel.add(progressBar, BorderLayout.CENTER); + contentPanel.add(timeLabel, BorderLayout.SOUTH); + + add(contentPanel, BorderLayout.CENTER); + add(buttonPanel, BorderLayout.SOUTH); + } + + public void startProcessing(int total) { + this.totalFiles = total; + this.startTime = System.currentTimeMillis(); + progressBar.setValue(0); + updateTime(); + } + + public void updateProgress(int processed) { + int progress = (int) ((processed * 100.0) / totalFiles); + progressBar.setValue(progress); + statusLabel.setText(String.format("Processed: %d/%d", processed, totalFiles)); + updateTime(); + } + + private void updateTime() { + long elapsed = System.currentTimeMillis() - startTime; + String elapsedTime = formatTime(elapsed); + + if (progressBar.getValue() > 0) { + long estimatedTotal = (elapsed * totalFiles) / progressBar.getValue(); + long remaining = estimatedTotal - elapsed; + timeLabel.setText(String.format("Elapsed: %s | Remaining: %s", + elapsedTime, formatTime(remaining))); + } else { + timeLabel.setText("Elapsed: " + elapsedTime); + } + } + + private String formatTime(long millis) { + return String.format("%02d:%02d", + TimeUnit.MILLISECONDS.toMinutes(millis), + TimeUnit.MILLISECONDS.toSeconds(millis) - + TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis))); + } + + public JButton getCancelButton() { + return cancelButton; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/axis/innovators/box/gui/MainWindow.java b/src/main/java/com/axis/innovators/box/gui/MainWindow.java index 6fccccf..496fb3b 100644 --- a/src/main/java/com/axis/innovators/box/gui/MainWindow.java +++ b/src/main/java/com/axis/innovators/box/gui/MainWindow.java @@ -39,7 +39,7 @@ public class MainWindow extends JFrame { private final boolean isBackground = true; private final boolean isBlur = true; private SystemTray systemTray; - private TrayIcon trayIcon; + //private TrayIcon trayIcon; public MainWindow() { setIconImage(LoadIcon.loadIcon("logo.png", 32).getImage()); @@ -123,17 +123,17 @@ public class MainWindow extends JFrame { popup.add(exitItem); // 5. 创建托盘图标 - trayIcon = new TrayIcon(roundedImage, "轴创工具箱", popup); - trayIcon.setImageAutoSize(true); + //trayIcon = new TrayIcon(roundedImage, "轴创工具箱", popup); + //trayIcon.setImageAutoSize(true); // 6. 添加事件监听 - addTrayEventListeners(); + //addTrayEventListeners(); - try { - systemTray.add(trayIcon); - } catch (AWTException ex) { - logger.error("添加系统托盘图标失败", ex); - } + //try { + // systemTray.add(trayIcon); + //} catch (AWTException ex) { + // logger.error("添加系统托盘图标失败", ex); + //} } // 基础菜单项创建方法(解决方法不存在问题) @@ -161,20 +161,20 @@ public class MainWindow extends JFrame { } // 添加事件监听 - private void addTrayEventListeners() { - // 双击恢复窗口 - trayIcon.addActionListener(e -> setVisible(true)); - - // 右键菜单触发兼容处理 - trayIcon.addMouseListener(new MouseAdapter() { - @Override - public void mouseReleased(MouseEvent e) { - if (e.isPopupTrigger()) { - trayIcon.getPopupMenu().show(e.getComponent(), e.getX(), e.getY()); - } - } - }); - } + //private void addTrayEventListeners() { + // // 双击恢复窗口 + // trayIcon.addActionListener(e -> setVisible(true)); +// + // // 右键菜单触发兼容处理 + // trayIcon.addMouseListener(new MouseAdapter() { + // @Override + // public void mouseReleased(MouseEvent e) { + // if (e.isPopupTrigger()) { + // trayIcon.getPopupMenu().show(e.getComponent(), e.getX(), e.getY()); + // } + // } + // }); + //} // 初始化中文字体(在main方法或构造函数中调用) private void initChineseFont() { diff --git a/src/main/java/com/axis/innovators/box/register/RegistrationTool.java b/src/main/java/com/axis/innovators/box/register/RegistrationTool.java index b638631..c8a67fb 100644 --- a/src/main/java/com/axis/innovators/box/register/RegistrationTool.java +++ b/src/main/java/com/axis/innovators/box/register/RegistrationTool.java @@ -3,6 +3,7 @@ package com.axis.innovators.box.register; import com.axis.innovators.box.AxisInnovatorsBox; import com.axis.innovators.box.browser.MainApplication; import com.axis.innovators.box.gui.FridaWindow; +import com.axis.innovators.box.gui.JarApiProfilingWindow; import com.axis.innovators.box.gui.LocalWindow; import com.axis.innovators.box.gui.MainWindow; import com.axis.innovators.box.plugins.PluginDescriptor; @@ -47,6 +48,20 @@ public class RegistrationTool { } })); + MainWindow.ToolCategory programmingToolsCategory = new MainWindow.ToolCategory("编程工具", + "programming/programming.png", + "编程工具"); + programmingToolsCategory.addTool(new MainWindow.ToolItem("JarApi查看器", "programming/JarApiViewer/JarApi_Viewer.png", + "查看Jar内的方法以及其注解" + + "\n作者:tzdwindows 7", ++id, new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + Window owner = SwingUtilities.windowForComponent((Component) e.getSource()); + JarApiProfilingWindow jarApiProfilingWindow = new JarApiProfilingWindow(owner); + main.popupWindow(jarApiProfilingWindow); + } + })); + MainWindow.ToolCategory aICategory = new MainWindow.ToolCategory("AI工具", "ai/ai.png", "人工智能/大语言模型"); @@ -75,6 +90,7 @@ public class RegistrationTool { addToolCategory(debugCategory, "system:debugTools"); addToolCategory(aICategory,"system:fridaTools"); + addToolCategory(programmingToolsCategory, "system:programmingTools"); } /** diff --git a/src/main/java/com/axis/innovators/box/tools/FolderCreator.java b/src/main/java/com/axis/innovators/box/tools/FolderCreator.java index 2336da9..60b8cc6 100644 --- a/src/main/java/com/axis/innovators/box/tools/FolderCreator.java +++ b/src/main/java/com/axis/innovators/box/tools/FolderCreator.java @@ -17,6 +17,7 @@ public class FolderCreator { public static final String LOGS_PATH = "logs"; public static final String LANGUAGE_PATH = "language"; public static final String CONFIGURATION_PATH = "state"; + public static final String JAVA_SCRIPT_PATH = "javascript"; public static String getConfigurationFolder() { String folder = createFolder(CONFIGURATION_PATH); if (folder == null) { @@ -25,6 +26,15 @@ public class FolderCreator { } return folder; } + + public static String getJavaScriptFolder() { + String folder = createFolder(JAVA_SCRIPT_PATH); + if (folder == null) { + logger.error("Plugin folder creation failed, please use administrator privileges to execute this procedure"); + return null; + } + return folder; + } public static String getLanguageFolder() { String folder = createFolder(LANGUAGE_PATH); if (folder == null) { diff --git a/src/main/java/com/axis/innovators/box/tools/RegisterTray.java b/src/main/java/com/axis/innovators/box/tools/RegisterTray.java new file mode 100644 index 0000000..98b8533 --- /dev/null +++ b/src/main/java/com/axis/innovators/box/tools/RegisterTray.java @@ -0,0 +1,233 @@ +package com.axis.innovators.box.tools; + +import java.util.ArrayList; +import java.util.List; + +/** + * Windows系统托盘管理器 + *

提供系统托盘图标的管理功能,支持菜单操作和事件回调

+ * + * @author tzdwindows + * @version 1.0 + */ +public class RegisterTray { + + static { + LibraryLoad.loadLibrary("RegisterTray"); + } + + /** + * 托盘菜单项构建器(流畅接口) + */ + public static class MenuBuilder { + private final List items = new ArrayList<>(); + + /** + * 添加菜单项 + * @param id 菜单项唯一标识符(需大于0) + * @param label 菜单显示文本 + * @param onClick 点击事件处理器 + * @return 当前构建器实例 + */ + public MenuBuilder addItem(int id, String label, MenuItemClickListener onClick) { + items.add(new Item( + id, + label, + "", "", "", + (Event) combinedId -> { + int itemId = (int)(combinedId >> 32); + onClick.onClick(itemId); + } + )); + return this; + } + + /** + * 构建菜单项列表 + * @return 可用于注册的菜单项集合 + */ + public List build() { + return new ArrayList<>(items); + } + } + + /** + * 托盘配置器(流畅接口) + */ + public static class TrayConfig { + private String title = "Application"; + private String iconPath = ""; + private String tooltip = ""; + private List menuItems = new ArrayList<>(); + private TrayClickListener clickListener = id -> {}; + + /** + * 设置托盘标题(必选) + * @param title 显示在托盘区域的标题 + * @return 当前配置实例 + */ + public TrayConfig title(String title) { + this.title = title; + return this; + } + + /** + * 设置托盘图标(可选) + * @param iconPath ICO图标的绝对路径 + * @return 当前配置实例 + */ + public TrayConfig icon(String iconPath) { + this.iconPath = iconPath; + return this; + } + + /** + * 设置悬停提示信息(可选) + * @param tooltip 鼠标悬停时显示的文本 + * @return 当前配置实例 + */ + public TrayConfig tooltip(String tooltip) { + this.tooltip = tooltip; + return this; + } + + /** + * 设置右键菜单(可选) + * @param menuItems 通过MenuBuilder构建的菜单项列表 + * @return 当前配置实例 + */ + public TrayConfig menu(List menuItems) { + this.menuItems = new ArrayList<>(menuItems); + return this; + } + + /** + * 设置托盘点击事件(可选) + * @param listener 点击事件监听器 + * @return 当前配置实例 + */ + public TrayConfig onClick(TrayClickListener listener) { + this.clickListener = listener; + return this; + } + + /** + * 完成配置并注册托盘 + * @return 注册成功的托盘ID + * @throws TrayException 注册失败时抛出 + */ + public long register() throws TrayException { + try { + return RegisterTray.register( + title, + menuItems, + iconPath, + tooltip, + clickListener::onClick + ); + } catch (Exception e) { + throw new TrayException("托盘注册失败", e); + } + } + } + + /** + * 托盘点击事件监听器 + */ + @FunctionalInterface + public interface TrayClickListener { + /** + * 当托盘图标被点击时触发 + * @param trayId 托盘实例的唯一标识符 + */ + void onClick(long trayId); + } + + /** + * 菜单项点击事件监听器 + */ + @FunctionalInterface + public interface MenuItemClickListener { + /** + * 当菜单项被点击时触发 + * @param itemId 菜单项的唯一标识符 + */ + void onClick(int itemId); + } + + /** + * 托盘操作异常 + */ + public static class TrayException extends Exception { + public TrayException(String message, Throwable cause) { + super(message, cause); + } + } + + public static native long register(String name, List value, + String icon, String description, Event event); + public static native void unregister(long id); + + public interface Event { + void onClick(long id); + } + + public static class Item { + private final long id; + private final String name; + private final String value; + private final String icon; + private final String description; + private final Event event; + + /** + * @param id 菜单项唯一ID + * @param name 显示名称 + * @param value 保留字段 + * @param icon 保留字段 + * @param description 保留字段 + * @param event 点击事件处理器 + */ + public Item(int id, String name, String value, + String icon, String description, Event event) { + this.id = id; + this.name = name; + this.value = value; + this.icon = icon; + this.description = description; + this.event = event; + } + } + + public static void main(String[] args) { + try { + // 构建菜单 + List menuItems = new MenuBuilder() + .addItem(1, "显示日志", itemId -> + System.out.println("打开日志,菜单项ID: " + itemId)) + .addItem(2, "退出系统", itemId -> System.exit(0)) + .build(); + + // 配置并注册托盘 + long trayId = new TrayConfig() + .title("数据监控系统") + .icon("C:/icons/app.ico") + .tooltip("双击查看实时数据") + .menu(menuItems) + .onClick(id -> + System.out.println("托盘被点击,ID: " + id)) + .register(); + + // 程序主循环 + while (true) { + Thread.sleep(1000); + } + } catch (RegisterTray.TrayException | InterruptedException e) { + e.printStackTrace(); + } + } + + //static { + // System.loadLibrary("RegisterTray"); + //} +} \ No newline at end of file diff --git a/src/main/java/com/axis/innovators/box/util/AdvancedJFileChooser.java b/src/main/java/com/axis/innovators/box/util/AdvancedJFileChooser.java new file mode 100644 index 0000000..f8e6549 --- /dev/null +++ b/src/main/java/com/axis/innovators/box/util/AdvancedJFileChooser.java @@ -0,0 +1,67 @@ +package com.axis.innovators.box.util; + +import com.axis.innovators.box.gui.JarApiProfilingWindow; +import com.formdev.flatlaf.FlatLaf; +import javax.swing.*; +import java.awt.*; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.util.prefs.Preferences; + +/** + * @author tzdwindows 7 + */ +public class AdvancedJFileChooser extends JFileChooser { + private static final String PREF_KEY = "LAST_DIRECTORY"; + private static final File FALLBACK_DIR = new File(System.getProperty("user.home")); + + public AdvancedJFileChooser() { + super(loadValidDirectory()); + setFileSelectionMode(FILES_AND_DIRECTORIES); + } + + @Override + public int showDialog(Component parent, String approveButtonText) { + int result = super.showDialog(parent, approveButtonText); + if (result == APPROVE_OPTION) { + persistDirectory(getSelectedPath()); + } + return result; + } + + private String getSelectedPath() { + File selected = getSelectedFile(); + return (selected != null && selected.isDirectory()) ? + selected.getAbsolutePath() : + getCurrentDirectory().getAbsolutePath(); + } + + private static File loadValidDirectory() { + try { + Preferences prefs = Preferences.userNodeForPackage(JarApiProfilingWindow.class); + String path = prefs.get(PREF_KEY, FALLBACK_DIR.getAbsolutePath()); + + File dir = new File(path).getCanonicalFile(); + return dir.isDirectory() && Files.isReadable(dir.toPath()) ? + dir : + FALLBACK_DIR; + + } catch (IOException | InvalidPathException | SecurityException e) { + return FALLBACK_DIR; + } + } + + private void persistDirectory(String path) { + try { + File dir = new File(path).getCanonicalFile(); + if (dir.isDirectory() && Files.isWritable(dir.toPath())) { + Preferences prefs = Preferences.userNodeForPackage(JarApiProfilingWindow.class); + prefs.put(PREF_KEY, dir.getAbsolutePath()); + } + } catch (IOException | InvalidPathException | SecurityException e) { + System.err.println("无法保存目录: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/editing/CodeEditingComponent.java b/src/main/java/org/editing/CodeEditingComponent.java new file mode 100644 index 0000000..55f4e0c --- /dev/null +++ b/src/main/java/org/editing/CodeEditingComponent.java @@ -0,0 +1,7 @@ +package org.editing; + +import javax.swing.*; + +public class CodeEditingComponent extends JTextPane { + +} \ No newline at end of file diff --git a/src/main/java/org/editing/Main.java b/src/main/java/org/editing/Main.java new file mode 100644 index 0000000..5025055 --- /dev/null +++ b/src/main/java/org/editing/Main.java @@ -0,0 +1,9 @@ +package org.editing; + +import java.io.File; + +public class Main { + + public static void main(String[] args) { + } +} diff --git a/src/main/resources/icons/class.png b/src/main/resources/icons/class.png new file mode 100644 index 0000000..673a167 Binary files /dev/null and b/src/main/resources/icons/class.png differ diff --git a/src/main/resources/icons/export.png b/src/main/resources/icons/export.png new file mode 100644 index 0000000..6ff3df2 Binary files /dev/null and b/src/main/resources/icons/export.png differ diff --git a/src/main/resources/icons/import.png b/src/main/resources/icons/import.png new file mode 100644 index 0000000..b6fad0f Binary files /dev/null and b/src/main/resources/icons/import.png differ diff --git a/src/main/resources/icons/logo.ico b/src/main/resources/icons/logo.ico new file mode 100644 index 0000000..3dd5b53 Binary files /dev/null and b/src/main/resources/icons/logo.ico differ diff --git a/src/main/resources/icons/method.png b/src/main/resources/icons/method.png new file mode 100644 index 0000000..80128d4 Binary files /dev/null and b/src/main/resources/icons/method.png differ diff --git a/src/main/resources/icons/package.png b/src/main/resources/icons/package.png new file mode 100644 index 0000000..162b7ba Binary files /dev/null and b/src/main/resources/icons/package.png differ diff --git a/src/main/resources/icons/programming/JarApiViewer/JarApi_Viewer.png b/src/main/resources/icons/programming/JarApiViewer/JarApi_Viewer.png new file mode 100644 index 0000000..ec5dc8f Binary files /dev/null and b/src/main/resources/icons/programming/JarApiViewer/JarApi_Viewer.png differ diff --git a/src/main/resources/icons/programming/programming.png b/src/main/resources/icons/programming/programming.png new file mode 100644 index 0000000..e052812 Binary files /dev/null and b/src/main/resources/icons/programming/programming.png differ diff --git a/src/main/resources/icons/search.png b/src/main/resources/icons/search.png new file mode 100644 index 0000000..75880b1 Binary files /dev/null and b/src/main/resources/icons/search.png differ diff --git a/src/main/resources/javascript/HtmlViewer.html b/src/main/resources/javascript/HtmlViewer.html new file mode 100644 index 0000000..6ffb8a0 --- /dev/null +++ b/src/main/resources/javascript/HtmlViewer.html @@ -0,0 +1,462 @@ + + + + + Axis Innovators Pro + + + + + + + + + + + + + + + + +
+ +
+
+
+ +
+
+
+ +
+
+
+ + + + + \ No newline at end of file