From c4a10214dbc4639aba333e2be86590f229af1877 Mon Sep 17 00:00:00 2001 From: tzdwindows 7 <3076584115@qq.com> Date: Fri, 2 May 2025 19:11:25 +0800 Subject: [PATCH] =?UTF-8?q?feat(core):=20=E6=B7=BB=E5=8A=A0=E9=AB=98?= =?UTF-8?q?=E7=BA=A7=E6=96=87=E4=BB=B6=E9=80=89=E6=8B=A9=E5=99=A8=E5=92=8C?= =?UTF-8?q?=E6=9A=97=E9=BB=91=E4=B8=BB=E9=A2=98=E8=81=8A=E5=A4=A9=E7=95=8C?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 AdvancedJFileChooser 类,实现记住上次访问目录的功能 - 添加 AIaToolbox_dark.html 文件,实现暗黑主题的聊天界面- 优化代码结构,提高可读性和可维护性 --- build.gradle | 10 + javascript/AIaToolbox_dark.html | 867 ++++++++++ javascript/AIaToolbox_light.html | 665 ++++++++ javascript/HtmlViewer.html | 462 ++++++ language/saved_language.properties | 2 +- .../innovators/box/AxisInnovatorsBox.java | 4 + .../java/com/axis/innovators/box/Main.java | 10 +- .../innovators/box/browser/CefAppManager.java | 1 + .../box/browser/MainApplication.java | 109 +- .../gui/JavaPseudocodeGenerator.java | 339 ++++ .../decompilation/gui/ModernJarViewer.java | 4 +- .../gui/TypeMismatchException.java | 7 + .../axis/innovators/box/gui/FridaWindow.java | 5 +- .../box/gui/JarApiProfilingWindow.java | 1469 +++++++++++++++++ .../axis/innovators/box/gui/MainWindow.java | 46 +- .../box/register/RegistrationTool.java | 16 + .../innovators/box/tools/FolderCreator.java | 10 + .../innovators/box/tools/RegisterTray.java | 233 +++ .../box/util/AdvancedJFileChooser.java | 67 + .../org/editing/CodeEditingComponent.java | 7 + src/main/java/org/editing/Main.java | 9 + src/main/resources/icons/class.png | Bin 0 -> 15834 bytes src/main/resources/icons/export.png | Bin 0 -> 6691 bytes src/main/resources/icons/import.png | Bin 0 -> 15072 bytes src/main/resources/icons/logo.ico | Bin 0 -> 4286 bytes src/main/resources/icons/method.png | Bin 0 -> 8965 bytes src/main/resources/icons/package.png | Bin 0 -> 3634 bytes .../JarApiViewer/JarApi_Viewer.png | Bin 0 -> 8073 bytes .../icons/programming/programming.png | Bin 0 -> 8245 bytes src/main/resources/icons/search.png | Bin 0 -> 8171 bytes src/main/resources/javascript/HtmlViewer.html | 462 ++++++ 31 files changed, 4726 insertions(+), 78 deletions(-) create mode 100644 javascript/AIaToolbox_dark.html create mode 100644 javascript/AIaToolbox_light.html create mode 100644 javascript/HtmlViewer.html create mode 100644 src/main/java/com/axis/innovators/box/decompilation/gui/JavaPseudocodeGenerator.java create mode 100644 src/main/java/com/axis/innovators/box/decompilation/gui/TypeMismatchException.java create mode 100644 src/main/java/com/axis/innovators/box/gui/JarApiProfilingWindow.java create mode 100644 src/main/java/com/axis/innovators/box/tools/RegisterTray.java create mode 100644 src/main/java/com/axis/innovators/box/util/AdvancedJFileChooser.java create mode 100644 src/main/java/org/editing/CodeEditingComponent.java create mode 100644 src/main/java/org/editing/Main.java create mode 100644 src/main/resources/icons/class.png create mode 100644 src/main/resources/icons/export.png create mode 100644 src/main/resources/icons/import.png create mode 100644 src/main/resources/icons/logo.ico create mode 100644 src/main/resources/icons/method.png create mode 100644 src/main/resources/icons/package.png create mode 100644 src/main/resources/icons/programming/JarApiViewer/JarApi_Viewer.png create mode 100644 src/main/resources/icons/programming/programming.png create mode 100644 src/main/resources/icons/search.png create mode 100644 src/main/resources/javascript/HtmlViewer.html 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 0000000000000000000000000000000000000000..673a167209417644d920e2467872ba891ce10a01 GIT binary patch literal 15834 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 WrPcN?5nxywNkvgZp+U|v;{O0wn9?}_ literal 0 HcmV?d00001 diff --git a/src/main/resources/icons/export.png b/src/main/resources/icons/export.png new file mode 100644 index 0000000000000000000000000000000000000000..6ff3df2132d7af04eb28f3fe36b7697959bf744e GIT binary patch literal 6691 zcmbVx_ghoX6Yfa}RltO%NRSXBy@)791OtRFDu|&Mk=}bpA_@eg1P~BW@JkU9LX*&& zbfgJNla7TTNXO8<=X39WaDO;w=Q+>ryR)-9@64WwyL(%kjpYIh003;dIyfV+?fvgz zVgTP{)3O|}f%q9|s{>_2$Q1zKoz=yu83$YcdCm6xl4d~92ASrVW%k0#5U!@`#1mD* zx?@cnxUS~UX2{iQc#PJAU6s|^efKf>9V$4%rgJ}Ch{gG`AvzU@-a0cVidt_e%W?R~ zrsTeWLdUbL4@rAD&V@zD1+9!&y-+B(F$rj0e^fg(^ @4&Ht;~V!)0>-3Zx_JcA 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/icons/import.png b/src/main/resources/icons/import.png new file mode 100644 index 0000000000000000000000000000000000000000..b6fad0f771065e04c50285d69a521c75b614c7c6 GIT binary patch literal 15072 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/icons/logo.ico b/src/main/resources/icons/logo.ico new file mode 100644 index 0000000000000000000000000000000000000000..3dd5b53d48a24f57c41d2e43167c870348b769b7 GIT binary patch literal 4286 zcmchZF>4e-7=}mClU!kvM(lDRf|Xz)7!1TF=s&Qq6(OJjD?6|7XGBv-iUT_}mV!+{ zK?MsTe;_DWBtZk3N|faBWoDrx6urt$QZ7Yh88u;>PgNj80VT(n*UOSdbS=FTqX^%PNTUj z&HV`V{rdJa2jRGmJ9?OnmxYWtFnJFW2@VI7xW)b^sJ-Tf|!@svoNz1MA4V?UMXY^m*K zNxOAjzL(7zF^>MnsPkLpE3qcpqPQ-*@tHi>5C?)7-if;O_vC9{cx@i2-V^Bj5|0FZ z*TfGo3H_^41D$E{Eb5^3Q;??%qAvXz`T8L6-xUvq=TCV~ycYDsH}P0}kI!#ZHbvF_ zKdG_BbH1bJ+XH`j92fM=K0Ai5s&h7;i<=^gH#Gu}YcyN(@mARXw(2(y{bqJo^vs#C z&1cmw57|`LCw_e4)Be#yGn-ph5)-l6hM0*xv#GAn8nTwGDQj#0tT}66jDfvjk8H!< zv4^SZ_DSxLL--@7$Srcr{>eRZ(Dsz%D7lIaxl0aL)#vVTZgHj-1ZRx1<`_7GoJGgL z8QqKh++DMSzSIaZe@FGXThQUI;m#q3p5QLB{=Djw&_9sh4Y4XZVpn(#cy5a|!5z(A zOvH!Qv$$o#1iZ_p`Tz}NKy4rL7 zWv$PCgnG4oPL0btjpmdzmm<`UTfaO%Qs-0E-t%=?(NDj-*zfO5577&u-3?)}PVjkD G2kk$2YddlP literal 0 HcmV?d00001 diff --git a/src/main/resources/icons/method.png b/src/main/resources/icons/method.png new file mode 100644 index 0000000000000000000000000000000000000000..80128d45d7ca65c9a48334346c21e33f7246695b GIT binary patch literal 8965 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/icons/package.png b/src/main/resources/icons/package.png new file mode 100644 index 0000000000000000000000000000000000000000..162b7ba9b12cd8f0f2b32d81a2284ab225159ab7 GIT binary patch literal 3634 zcmeHKS5#B$wqArFB#N67k*X0xlPH9yNJ*hcg0!fRAOac;f=ZPd0wIWk6cJ*iiwY=c z0A&M8-U5l%ir0IGV_zk*J4L|gpvXz%$VM!Locjn{4d z3-v?KD|e)5o+wd;eRvny^NFC>r)eO8vA3h`l_NZNWar}KU%cR6&1GF*T(nC^#JOj% zwXCy(?%riE{6GF)u8A;YOQ3DHa;z5?)K@t?YU%Z*+~KC$HsNM}LVVkpd0TI=bqhmS zl=rYb3F_e607VRE0kFAZKx$$b4`Cp2_~_Uy@WC7hISpNdEATU@1Q}PkJxTO}(@w7?8)A&o61&3BUwt)yUDGugNz5-#Q9q8$KGWQrZG64-nld?>l#;Q3#^29@t?C@&=!Rw;=kzSvM zpdxW?+L3OVmonMscpj36p3tqLXiYxF7#jccU!Avpi=c&8sW>Aka=aC-7rQ6o_s0cL z^Z2=GOO|b?+y_^LW)5<^I&scSC=gtUWn*T2<=R!KTyb2N`y`tbBAoRN{260nedw%iuXq`2h|Q2^|0&C5B_{OYca7!L~1_?zEA=v zo{ZYXINNleb+gEMdKt6_WmZ0){1XeQn2G@PHVvU4$q_sz;g zm}BYGe_g+9F2cU9)r(n}{?~VOXV5Mk#)MfU{$Cp>13`ZsoGvSLNybdQx9m+gMKV>= zD`GVu1M%K7)6ZvL@Vqs#k{MhvTfTd^H zDQ+6-?GC(Z27iL*K6|61B}$7anj4m0U0xPQ`ksEHM(OMC4-eA#^+h-D#nXL9@DTou z@~<~Y6$}Y=g{;>591D=DGx3`zjXtD=Ay9JOK0aeGJ2!!cXcbSY(Em7JFZO#VBT(%p zp>TLj-|~RiV)9IZyjJARz}wvKeUVKUeeOhG$^)cQ)p51M&E3jNUptI`-TukOOifz$ z{_+9?Tmq4niSwFXrUl&_8JOKE1}l|tktc8UELF7lJb5e4nrskr@{?ZFc%pQEkXHl_ zPs&|wj#(1zR#o+c`MSgYvcENCvbpW?`Sr57?I;nw_7}qLbQ_itJ5qn~7o{y;?5MRA z?_>@qD@?Suwwl|y4c*Uet}D$(F9kwG*sYA19OmC8;s#fem2dj9sQz*%tf1!fi~4;m zw=9KQn+QJqwUtBl~7vO6Jn0`el=y~nrg!`4MeF4ry3DxR*;r~5s}q5 zDCAu<$8on#>)s_Hw+=0(mp1v}u;+P(aNTYPhyJETv1WBhkbO3fDO6&*W}UlSz2En? zA2#Ouq!6e{2>YhYfQV$p8dUDEbegRO0tFvi$f+9;x{^L!OwaS_Q$RQQL)^0s=+wxF zZMtv;n_z=yp0hLwv}6b+xwB?@0d~QRUsy1`ex0z#Ez315GbSKEo}d?@!xF0>Q+7X) z*oGp8eFnR@7^dflCp?ImI>(zoCY-l2v24V(D~H<@PkSVzoj{A!WNAR`L-NdL*75R} ziqTbl=eo|oA4xm(;9ln8neL>dBvC$Z zFF%BOUf4}%hzjRoGVSY_)6{8PNNPiI5Wuf1CA$^@tJl(J+5W3(i)lyu} zei$4)$P5VyX(R_GlyTN9WRARW%$_W}t2?J<;m9mm&=ma`9UavYi^ZY_&%*8S&zDp2 zpR^6XPw?|Qm;%3qO3O|o#&41@2cR4=-{07@37XPS}ioxkc#C6%`Z0PR*hu+7l5pG$z7|dV+Aob0j4<$l%Q%VDBJ%S;Kmuf1CDDYIv zF7Nhkfw{^B`yI`)Ww9Od%@`}%IDGRB3|>J3t{L!g?!!L>yRm_qgH@LM1KQ_IGg+7^ z!?NSXRexqK1#ONQ?ifb(6OkN13a0hiQXY&d1ylRR6@ox<)R%1KN|2iTE^Ql_D_Fu^ zig*?~McKGun-&gFc!P0)f>E96x?uFrz=^Rk&wX+gLXEf|OCMOk^&BduGgHnWY+vRM z`{{zGI4Ltm6O;h*G54)L^3G(g4jYsJ7`mAX36ucYv|<@h z0{jDTi5VMgur~4x=dJxk?|XfRT0aC@DIZIE_C&^{pxZ^2*1CImg#_sMQwKtM_q-&d z_yIg2B!nA0`uCdsz0l}~A7WAETf)!EzxCWLjTB)&+y*Ge>yRFj6jkG0;9zi}2NBtV zZR1NPwuWi3X0K`y2}kTe{@!%!6SE2cFJmP(TEbB8k{3vMp}-QZ86}z%Bmz>Qte0}n zz`yEAZYD)U;~u=0{i*?Vhw)7*VmL6tWu zIsUwZdrKq|c@K__VgL4geG(K?U|%yW$Z~^XOeXYC%^H>Vn^f*6>&<=Sr%EeIV4D{Q zra$`)_qkE#i-WJa+|VPA=lh@r;mUAY12TNNrzj8g<}mzoQr_b)EEZELfKC)6mz0N8=xCG^M5_YIcB^AQ|G? z@w{tKB1UsD&c&s-NqWncVp*yICF_RA4Le}raj?Kf$$vZS_)*^i%+w=LSE)BTFQ;~6 z>j-_u)qxOxZts<5%N0cGLbDHLa43J>ewQ)r`>!KVT}k|7}~1%9#$Lt7L5(=nab?mi9g&NqF$n%*2( z@lI-(+EQH|?CgfP4^U*rF9NX=N2ebUOQ4VhX-sR3nuTx{{jwgF9ezj*urb z0yN}gPFhTukX+%I%A;HDDg5|4WRGLA28{JqAX*5?7h0^K@5B@jH-Q;F7Vpu&SFjTl z+IpFdp!2Ax-YQ`9uva$yXQ}b(S>bupp;@*9)hC(Q2o3w^(A>`*D0}#X6nQG*;X@0E z%##RSeD)ORbkjn zmkPj`DJ9^Q;Ua!qt2uWnj$F)s_T}Y>XeE`Em0ian{L1kM$Z|Cj#oSL9RZKGwag~U@p_gd3KAtZK#R@R}rok;V00Y@! zUC&38@K<>A=qI$wr_PdGabL3)YaM6n{mo&6&n!1Dzn0nYJb5btSPzelJ=`Pk>YbNf zElEFhPC={%%?V=sE&v|GU+H2dYMLopE?~Pkd9Luk|0b!G6r$XmxFmNbmy#a;v8tag zY?C}fl8F82Ixg+`rxF9xf_lRN>pR?qsC2%wiC=<(D=Exzf&{pPfD|KSeS3TEp_9|H zXoCXtpG!P>&*ZvuO@#jNoD2@#Q2RlRiVVC8%oqg~h1i@ddT`Lb^yhL8tFTxmEv9-F z@pdI^T}ik4;Il#kc6ERUmQ#9P*tmg~yV&j?vWUO41d$X7T+Jg9U&}ZcUNCZ%i)lg& zlSl!)oIa8SN>;d0mVXSOd-%eXpJ=rP(ihoX#nYGeUy zkQX0F2k7IcEHFyFhoK4a?%k=-e_z4`4`*wmTxbIx)D!m;4c0&QvaJNoWdaLU;+wn| z@JF`uhdb_?agO}juWn7qLdk6}xF*5|ni&Q=iCC_@p`l*?+1K#B9sss-N(PRxfr1jwR?7Y?rqvD^Lv>{XEgy>F zWNoQ475smsm%dZGOVhY}G1eTGD8cJXAs?VEn3$mbu;dO8KOXjt2Y`*vwWVXbz1m|e z7-_%PZwIA7m&7nmL+se@4HV&W=7csiLzMc1dp^S(dyN!fSwzB)evK;v5h<7ang#_u zV5h_7CjF}CPlh}F-kh}h?jvtIc|$Ws_sO&`3TZ~7o%iOWKm zl)!Mz)Ftw=jVlrOtmUcY<17tq_g7wKSzTEFfgu9*+*Mc}wk-hWOO2$0EhG}A*!n4Fbkf!Ncr?f4zg;nasP0)cQcc3eLl>5 z@R+RRHwbL&9?-G}_o!jjM}98&Tdwg|6#mA3=-=5X+#Oi1LDDsakqLGH6Oy=d%5~q( zDh9TzS3(2UAONMmj#|&44GdI zEc_~iY`+OC*i%r(K~&%Q7MXm@x?AKy3qmP~?MD%0#gtr~={euC>P9_E4ll646Zb;@ z{&w>jJ_!PAf*=%kgH2&NtdzLl=2~5i#C^?@_x-W_<@pE9INGMI)obL9VfFf3^Xl-x z)i;W>w5aVDD~i#pL9B2%>-gY(3J@%;#6Y^BVt$M14<`!$sRSR+$;LK8Dlbnw5V~7Q zjZEXp3@(`>+N58a>0i$bp~c{@M$|cuWs<`r?srq{n&1J zOfFVaTf37qX5}uTeG%D22RM)UZ1I)p{5lAbZfFHY3jzCLw{f%MMkOh!s^0yT9=ide zV`K2_b8D!#DE||g+%JV~%teMz2GYoF)7BScR=>IN{MHDc^%O^sN!WWAbfklkj~;x;811VzScrY(OkOV*}_0r54x)N$b|PGlZna zW*L_6&dBvL@|T3EJ8C!0Y}g8}1WVs#+p?usoO_dGR;=vM;?`V2n~2`gBV zo0|L;?ZQnm@zS-nW>wV}-YM`Q{M%v;-?H`g2CyvVwV+J z#KpoNRVE*wjbM`A)#Q4%u6;@XucsGT6V`HH6f~tG%71I7kv(nJu9lDhOQSiM|GK8p zwHeSm0*?+I1XmtMTY209nQ#0jCbb8)cBM?OU1PROqY-NOexRjLdUcE!tD8D3)yv@I zXkK3yERV9yUtf+T5&xD7wrL+)Qnyx|bpD82lQX>ep7~=1WHL}QFh@2KA66!54lHO2 zuIOY)#QBw{AgnbFt|pa~mfoNG#}w~lHi%I0=?S=vjg8fdo|SO7`ZaTG_umnsLw;~6 zB*^e!dEy3%IK|%|XW?-R&)aom9|dPEAB<3tUeq?FmT>Hdiykg8;2gP@+Hh{EE3aj& z`2*aqxE+T6gBZ-Rt6UtIVzt_B4)t##;#KXzvBV*hb79(<-iOtY$>qz-W`LX~)mD}8 zFEd_k3$m1ATIm_sp2Ym2*nNwLR|z0m+ld-n0<2@bWKnM8a2p-U&Uu0Yse8xk?r|KQv(l!i?z-p5`dVdE`76_Y6Es7~KfFjllY|!Tv4gQ7;tpvM;s$ zcw#Bq^_qvDCzQ3Sx;p!v(-($5IfAyNk?SQm{Xyv8Cd?%^Nf%RG+5{TY{v>2R{2H}m z+u5g(gu998j+-uDs~vpmF??y!g$Y$2EM~m;)gWhh@|eTPJ2eq8=FX`{j~-2#m0DIS zh2+QhZ!%q~;e+C?6SOzjmAl>OzA3&M7bv3I=o^sa_sPV>{2q|#Z?=?L=-IEz#Aq^(_z{bSScsB$}!A9`c)|=%- z{@R9y;OXRKxS{C81D?EcA<|KjZC)u0E`%QNTe?$|ge{97#v&n94WUAP;>HSN0r^C} z%H44f+q4<$+L41=60vYx$pypu-w1$AeNbUZPp~-QHBWeBQ`E)S3^OsAAk5D<*tf~c zQoC}=;Oza?p4*L>Jh6!<1iz)4Z{(Km~B#p zmbl-;ho0gNt&yV&F@>ZP6L3S)Tyz?r1m*+?!M!`0&Ahl@U^BW%){+T)bE;si6I_{; zrso)ZY6_~4BPGB(S2B#D*rH&FX?-F)stMy~KeNamWmZv^gbkB$z2uIBzC@2)r%r2E zhSrxg0Pw?`9LtbSF@HZlzt7oJE$zzMmJ$ml|F(A(HfZjbrH}Ci_1jqTptc>d{kH?` zW#43^W|X1&%TLy#vXas0(D+o+G*FeOD;N}u95hS$$CMNBmR9EpOaHV0J+DL2M*Yr0 z-Y;N47tM}oUkaTcr;)fnVpq1&bQ#X=Qg!1{z1ZhrNjeSRu!A@ zEBV%}DWc_hh$@hqg^3U!+tc(MK^B#nx z>3fLs|7uO0r?N;X04fSFUgOOWh{+-j<~zGSPU?E33{VVk3EaU~vjUMr=&3tP(QG z#)O2hwj4j}`>NS)chdAS=1;LDwGR)FmFwuopjfYPGBz~M;AD;Cf&i8}%D_ScwtLuZ zPjzH`L>ETKIo_L+5kql}j|{9lYzF-Y%6gc3B#Kp6uv}`rSo7{E1|)5Xn(fn^mm^Q% zgJMMNbp?r`G4O>mid{TFF0<`T%(JVcZJj60?kMdv_T|G2n&}e+C&f`6(Z|cc+IV=@ zBs9tAL*qu}-JTr%Cl1o^NM`AG1aQMP?SxXo(ka^v)aOs5-+2_8WM>}M#;8|xm^8nAcNkL$x2#m^UePPTOgXNlK3fC0D@^*+s`{|O&1 z#)46pck0e*^B}{WcfP%lBCZUdNSGM8%Co*BS8D5#3Wut4-ma?x8PHphh|!d#PnHKg zk#?QkZ=NB}zV^y{`m1-CYYPU*-q&W>7gn`ZwSn%j0)bBwxM6!*O*_q2T2bpw{_ysL z#)D|>U=l3H!Vt@pd>r=eB`obO zrMzCJ7bJ}cP8Z0Z9nEgkKZ%4ah7?T&rNZ`v<`4U$+ux=|(jfiXgjjS04`X#^wWC0| zcf%<4MC|@0V9yiD5QPi&K?80l7&jR|;J%2#X*s;r< zL`?5Jm-|0&Iu^9Aq@d9?rW(kN>A#ldL`mWtnNu!BPi17nn4Q!}-7@$#A%r(Fr- z^iSw;G%MgP{|*ZHAQu<>&J`=^nAx^fCS*SAOGPmi0*O$&r|S}vu&uoxa3+^Ywv2pykme?f;Q9z95?Z_t> zOOep<>^$PPO0OCcl#KEAd1uCwT;JU1J9~Rf>2hU4QxNJMR2EhA0W#g?i7E^a`hCu7 zX(_J~BR1-@Qw$6enq*9U&-t;sVK?CA-je zNSClMH#x9-7cUAM?IY${h&WGdP=+}TIgzBn zw<;LFMr;iI@&pSHxe;-YFOP?aZTyj*evgPi>Rh3WsO7-q>SLY>k*eJ0*hdQ2HJgTP9Y>l-T29CK5gAZK;_D}-Q72grcGV-SBx{i zi`g)y-2*kfagS>11jTXH8Z@8jSUW#o0Lu~z#a#GTn=^M4$>7oCw%pY zB_bAIk4>gHPYG0-KKQdsFGOm}zWtNY4AfzifHh|_2X`t~lUEUyBFn-luc}7C>?upQ z^~#={p7!gge_5DlERY|FW0&MJ%#_*U0Py*A_G9yk9j%-fRZ$__?A3RJL|-Th-@kn< zwd#$YZF6%7pa44iHHI<-QHm_McdV^#rf15ng`0UU#jw3UO*`|fPj~Mntq0A4=|Blg zpxlE0>v*^;7jjoC$9E^jBTDRF-4Xzm^)=imq2hP=eb5hUY-(zHrK###6DH~0=kZc0 zNP_D5zM_^#DZ4hqRn-Z}oYh~j!6#9K>mJ>cHzBGI9!ZX>d-$0>^QSnzdu$NjJ+S`5 zpU`fYl!VrT!KtKw@F|sjZevtthQS3ohs=I^#;(+))S@%oYK+Jut<1-%^?~S zx8x$yoM~OTQPvQ))u*^4>>jMRLjGTBTy7gG3J>J~3JNr4O9*Ij+`FYn7Wr>U4?jxONb^fFII?u~& zs$2$z7!z({HUE*0sAtdm)iBDv$0un2qPv?V$!Kp}WTMqG)Hj ze-%^WeZNtvKHzv+35m$Y7CFW1-hGNLLTVq?eQs!==RJGV!me}$id%T>zcKbl=*u6b zx#l#9yWQuQcK*FNIv{vd?yrRRZ5C~U5bm9MTmA%Bx2J-BQ?gFmt6vx4-q#(7*sKJB zAr?)pI0hUeCX-Gz(d6%=r-j+*A|5Ek3|RjBKUjZU-1dL@8z|V(bv}};`4#)=Dr}l?mXaVXG_R|yFo7Dy(HYbPw-rA zVg9nKL>hN6Jo$+MPi%&vqz+am=>aYmhI=6_CYzQdYTzrjRUe1=rqQ$$jttR~N*xVV zY8%!iNHIEH|0m7n!Q*){;?z`Hb+a%3SMZc~*6Wzh-(EX1i{Cy$Tt(|#S7qx0n+&w5 zxfr~c-lOkQr$?Bd1MSg_iT@fm2TPMvZs;!%)ea>e8>V|Fu?kVelR?*VLbJj*?~s_B zD`z%yZe2}A>p^bsm>?rB0@mCW#mvs$7#Y1zZ^M#8F4f{Bv@eAHVCE%e~fSPER zp(@8oF4jaw);>`~*xOWfEuN z*k%AhpV7&W&-XEF*ygvN`K8wyr0*{~_Va^CTa%wW_7&|)gNsAb?5Nn`@u!@7A->eN zRWRm4q&kkYy|EIss}L^k(ddTTZzs$j*Km#O{ zVlM8m-ZlIt$G=4B=w0x|P9ec}y54_d+oCDt_@d7GkUrX#8W)!>0B%U3&j+QKj^Tkw z;UXQU2iB%vEPSkBkXn$=J74M^Wf&DkyUOF@emw$@3MY6}R^=0XRaT}C0boMa-K>51 z*cJ6n$d>+Q+O@@3QmiCbN@EbY~K0!6` zyFa#``x7j19vw?uV``XSg@pHdFvu|68?+r>3efp_kEProYtcnnep%HA76L$QjO_O7 zkSnuz|Fd(pk5EN;bb@~Jm znEaP7FWdlJomgp-pJFk(0^*v4sImG3bxKC3B7U7t{vJ?4xDkMsS6E$9hx_@racqwV zX^N;G<8}5|&(F`V>oc2FLW(-y|F(35V)O|&wFQp_A|Jko3`PymxG+72>R<`j{Z9&8 z_o(w|Uoq!CQ87WUudSsuK3Hs3y*yi2&Wjht)ileH8ztp~1`0o;5oIc3YP*_TA7P(? zZfk3s204jxVR7dFo)d>$BIMiYy5?Jy@Z!Y}Emiqo*DwSStTc7|Z{yN--=;V)k=YOs z-##_%_zKege^%H9at`zd9o;XcJ#kWcFrI6cNF&4mpmQZhIrK!8FSgnehgrORb(oD@ z4X-YAA*HVGl(QK6(e>o#dSM!xD$$wx&es&PTPD!2Y2uFGs$7Tr$bi19N!*fj+&qCW zc)9GazKU10sR*N>(e8N2Dfem64^{%kFA#hMtby~VN3VR1B zic0#^NP@qExVj!%Efpi}sj;-k(;z(_d+MQ~?b;4=^%jPF4z=G0sbGINpET*=Yd)V9?Y+W59Fo#X(I8eplTm zA0+~@{ z-O#uMDxpgZJYl`aF^>c!8yb_&oT@)a89A8 zPn_)I1K=$=Q2+RiA32PHc{HHQzQ#7Vb7jm9NE($+!)#pwv>|qSID`Tmi=6PAn??PV zww57o9Ms8)Wug|hGc+R zg1Z)sJmIC-bH&^jZ4TycGV)dB4wU@Z^zCL3TQSto_bGt?VTI@H-Y5#Ut$vAmWX}=( zD?ZSNp3{DlK^cWah*{&2i@5KZ(r)dVXhFpE-%cXYd8*u`;;byMzy-~qELlHJS{ZV) zeD5*0Eto?VKvBmH0itpGB1C6)L-GWAF~4We_GPN7s$P(XnhB?$PK}O^wg!Y@NqGmXP_cEz0-MBx_z{|J4WYT{{6R}G6u`T9y%Vj`M zty4nnt}d?!ko3kO6_Y~-fQ{;)eT!EE%!G)}!eEy#Mc=>LWkdg!=I!jGy^$t5AeUz# z^t~XjpzLC}wciheLevLpJs_dMtUA}-TwK06*xCKiq~OvO01-@F-rC%p>)Jo10_->_ zl5PHCukEG4r-LS0 z!ZCG@LxFP#2M4-?4~uT`q-quMrHR{~<$;j1NduZYU*++nr|?A~8%EQYGajnwVF3xT zFe-XwI8qD&n{Ci%Mp;!M=TmV0nfYK1*^3(5hz}q}CQ>^ExC0Y4E?p7O?iP#EF!Qv) zd69iSbdh?>&(mOZbg-6^r(<0k;dI!3j;^_X;$|U(edT=z_+4c;@;TLk%Sih&?-X~2 zf7NUssZ~kn2&kYyEzhK{g|O6080UG2aR z#z?U#-4rH$tiK!oPh}i#Z(qT)!kY#X zw{{AAfvKFAI+(}OMucJTL0GR1_*YioXS~5cEwZ>ww_{+H5B{M-a1=yd8t@gSZ}6}m zuA_?}Bme23s}%ryVT&Nn6OYe6+1DJ%Q*dQK-TgzS8`|QbVMe;PZyMoNT&cFB_3uOb zvIG3zn_d0A#WS>5Bjz{V5d0dts7x^@dL#~)%Nvak=V0e)aM$L&G6 zW1|T`jr((b-p=5dY9VpUic8T`2Of(pDBMF<*#Q)~wT@mngbwgD8voFAP{(qXu@P{6 zO5CM%@(HlEwiaM?Gi{0ES7QjT?fob4=1x+p5A*$C%HB|DzL{6$U6|L`Kscsf8}dU( z?bI?x$wWA1MQX;uUKR3aWiX>9RZD=fM^8dLga+8fH3~bnS(d$)c-TF@HdaEC4ePJ? zd9<^$bJw);{tofc9%Xed=PKL&ky6gU@(Mj8BVly2r0{RgW?td%dDpBTk8?ZTEU6xG z;QURQlj&-y&zXNpk_1Rt=vHrUue@oM#lI168F{?V9auKuf!>QcQ8&Fe20sF~GCUa| zP=tVz#8pnY>497iH@6L|hR5HU`AugVHV;uaTk_DNTk}PqKYu>I`;BJuMz+t&;M#Pf z*V1R<8XLCXOM<;w{Nko;K_-S$B!Fg%{wk;E*B^z2BS(i}2_G2R3A;ypSY(IaoLj{q zU$=aIU!*IwOpgxvqrKnXX5;brU6+ZR2^m%FxnPK;XwhRfl$APqJJxPn8@`OvxiNOl z=#|PHpF`rabI~iCeWJ?E&CSK{-oGa$7g##nPD!lNZZ*6m*zP;4`#>*5TYF&m-8hPA zaU8_ch_zcxFor|sak{LORJ~2O$ENUY8gQObaZe7Y)1>e%6Jqx7N=lqstZw!Q>I*-R z(0`J|2KQbxBd#EaRnVPxBn`vvRK$J$7ELwoY9w8xd7|V4{M7mQZ)0kqqZ-D}+1b++ z3rlP&W%g`vAAzDucg^Nx6%<HSgImN#U3o3(`Ofx@QvM{ATkHi$%IeRXxbQKr#?FOp|Z(@OSz!m zu3J#^hAcL383I2l*1sZhpN4j_eX|&jT7@r~`N|glosLPuJ;@YM-!s!J%TJYN#O^eej8$ZDzu=GC5$uj+i5uPo*? z`5v}Y*yxVewsnQr&+a*(aJLsTi7SCPqW1`Ck#XNtQW}qO1MmfYx|I%0y@6@MBIsVpd1ilg~%EZbFPzYcsIT-_QJRf3sy%Mxc1 zz^@Q=P+eG%^QdEEniiXK=IP3$SC(qDhV_O6E&M6r@r=+yt#?HO?xC(h?oxjA`V~I$<@i-K2%zCPgP|S5*b@d*eKoC*ZIqQgR z@d2G}laG&2=`xBl|E(=Ijq4j9GV{eMLz2%rB{aEXZ~HE1!gnv$8ZEKECJ!GztTQ86 z9R@8DsVV7ec{5f_9Ih`9R%=H(Sp)XodA99TVOp3E20G8$uE6~G6DF{-2mzmff3mdd z%8P)6D7)phY>yXSFu$Ch-{k2!6U;E0_~r(`G4Z>d=w(I_GM;=)F@4@!;I*J zZG91sFC^#=U5uPP%haGzs!%}?yr-2teNxtTQ0MlcCgM)l{@s|Cy2oYGYHf*K(zmT8 z3r>FX_tw56M^lX*IaZ_w+`NwvNYi-D5gX@R=J6>0 zXLnm*ktZU~(!}>-AmnW=kx!@DWOC0 z(~+g>;GWBi_LT-oSyv7ba92%rJe_{#0=&N6-VK6ViLZ|=bAB|tXO6;Y8_U39!5|qq zHylmGR<3LtxJOCVkfTWww)uirn`QCtvt~5BdZyRZsl8%0non4@g2#fA4(&b?ca5{~ zI5wWlzQSZji}|I%RCu{M6Orjs5U zZm1}!EajA36s9A$o*$K%jFHqNH}bsaf%bj78o)`DwN`w4*&@YIky%-4mCr|aH`#Yr zzUYDM9lQ|_T)pe^!v6Cmo^sO~-kat_g% zbVlwH@(OhH(D0FJ?>IV@i=79*6cmP2V;HT(PWWH_>6zOt9cY5!Xu->|kjUs)5jlgo-SE&oVF5zp|PoyG9GLiNb@^j^$SicP}>6 zs5r!US%jhDV6f%Y&>$9I@#TIK43;wg!Kg9xjc%>`u^sOv?orc!!4yST^vD0nbHjR2 zOy2>nqE|{(p-7r$akWmS3PqK6Mu074oOrw%?flC~;g9UY5ij=3ME0HF7lvST z5l1;!W_+0XzI8dps949mEQy>902jiC)s=!wo=L+)t0b!XssFTrboTwmREM@2i&8z4 z(6@$Og^{Tl-_xOs24K(<|9sbFd}?m)bbWHtw0PCU9ZRd#lElP`%hqj_(? zVANiuyxypW?Ql(S!f1IQAJ}ZmJCAU5FF97RJeB;6F`2^MoKd)iD#s_O=iA6(dpPEh zH!qsIOdP)0BdAw%upRjr)Bg|>S||2S3NH7V)GG2xh=~EJm|E|C%+A}n^MF;I_*oMp zI9{NIxn#)~-DyCV9q*SBWk&HR@v**^kX_4%dplQKE1S^1*{cD9*wg>YD0sx_g^OMu z+dLZR%ZkL+*y!AN*O=1%K&vn@G0~HTm(lF46S>kLEK!`DgP@5xY>(SdBRmy==a|Os zWfc@{^^qu+8?cTTlG*XQKq$o}$fl85}zrA=nvA`<;|$=-co(|LKL z{Fu##ddF|dZ-g+>tO3yE@aJY8gf_pOgt!r3z~JSRL!}RpZPr{p0k>gZ3Hkl%tKqoD z*EZV8Q#XrRsR6|@uV4DS_JziAy}-}DLbiYGTwLbwmXJO-;M7~Ol|R%vY*IGwJ;yrRQ;-^ihj|0sAmidNWA z3EXxZOmE(7n4K5GA`Qm=SS%jdQ&H$99B{@wi74Cx*~f;=tZ*?gF%lIe<)rIZ?pOAiO^6{U(5%kikYWDL0MC_uIxc#u%UegCh4`Qr=rJ6Z7Mt6&jCAm z-Q@e`!YT93L5I7mQfp?I*|D)Y>zirE2%yzk1vZ7U&G+FGnwBYZzU682jEk+ue{dB? ztjbySBFk(j{zp^Ww;t+`AE;~78q-P6-Ee)~9Rg}do;GxXCk z-+U#f&S2IZl1ULJda6aFc1f5{aq0N%tjpd@Bl#?C{015Hx|opA$eYX6lTu|=J>A_a z1goZ9?##dH7>+-G<_rC!0|ntXUp`X)gi@L0uddTFa1^LGHw2@{o3`yI9sI?qSC#!G zRUF~+`Vo5{l587o_7u=T{4}c(*HpCvZU9#7#hww08A9(azn$ENd?dt>#01YYZl=AK zMIw7QvNW_=c@zpRWgXlH|9V}>B2s0nQwHHibq+9pgTbgY7kftbPn){zcWQ7q?lEg~ z@;r7ZxEm=FV58Odl9Ni!B3l_3O}IPKVyG6S_w8OtFo8}vY^o~qFrnh@A#%&vU?-gv z!rLj~QWY^zq`KutPVHHK?+&dfRl<);v1^iiACl>KLsABo@f=sL+I?C#e&>k%R4tN~Sv>YMu%wGW0WT^kT}7*} ziT1hh)q%(RbD;kis;Kjirem-oalyRs9PYkGY=k5^bWhtfWP&V-jKKe{?NcDZ1=Mw4 z*x7~tIg~p%et`h?1_IQ3@fVRAx9KJka>o)Gv5B{=pHe-=*zmG%o zYwrIFgwI$o8`7DJf5~*YXnBxou#mcW<{hqAdK2}IKp-GCH#e=|V&06sXo(E3f!e1C zR$6^5yOh1)Y(w?LeV-(9rl!t)aQV=VxW5AVWHZk7Knoq^pVMPpZo)AI=j7sYa7?k9 z7+#h?i6Hesz3!{1s~Lhi922hFk6vQ&_zccvoG16Ps<4h1_{2i*&6<8LEve#5p?>O< zfAYx&ci{hy0_0P%6GtXTO0YRezx8o}=EkV!&!0cr3C`V^e^xqee(xzcOzf}1whcow zi`lnZ0F81jD5(Y5`SI^!n>BnnooHZX7;H#+T^(t$NINan_ct}7h@fp&rO+f7WT@)| z5=oeyV z&jc~SYhOm6+_16Y_^F8w(iL!`46@}6FG79&Q~mSj&rQGLEG@(#^g&O$j8(pIp2Rm! z3td%36`3JLy#^(C^8l$ehJLf7M?6=#Y#hbC4wBZ%f2S}7l}7%KS=4sYE|*c4{k6|Q z4Vcj{0F4a!hs!(BT3u5GuEPTVBZ4KE5A*Z$!NCi*rn+V+=cM<;KjmUmv=A90>370S z*h$>mX*aGvO6hx*42qL~I!T8o!>JAuE>Fh?y zv?LiTA2e9;&OOk*>+fA4XQDs~Y)xj9gKa6gk|RxV8-g)|TUNpqzfUpm*NO3>9~ z6jrh}`q@a5=i=fj+57j;oxbkYVbFYyXskBX6HRm(sExF+wB#mkZ@*|rowJ+WlaGK7nsGG2M&s{}808Dc z$@zoH6hrC`{iM5Cs5~`qB#trL#_oz1+?<5kuAYF7a`Xbo$Z;O%s>nmWx2W8`D6 z0}}@J-lhK^9vjGtUg(9^#rslX0@rT=(GPf=TUwfe$kQ~8$|BB6w~ucp6FRPZ{>xzz z0nwlz5Gkq`?7vPz=%35zC^0o#UrD zR^z!S-0^D`Q3@tkX0XJ!atW!<46Bnbujhd6++w4+XBrOzzj%D$qelb5Y6KG#`C`jwbG0F~B z$As54{C1t)qryUij)z=Y6EmTUv>@Ha@ju4aKmQ1zJGwCau@L38znbbat`Uf9qhkN- zRG{z`m{qL_qb)7k!}TNY$z+0a$v@nrK+HD05X0Wj%d-!M+UIOEhttZvEWLLz5~M6; zDE<9ZKjSwBb|J!J7!YGc5uREG%@s=k{*o?~BokQu#OU-#-&y*guqMOMQ~89_^g78c@L&CA*mUKl=zT zdX=TyTgdQ0d6x$e)d=gj8(AjZMoXTM-T+)FsuA6=f_sNeSBkkMC@}yhZM$CRAfe(1 zaqL4ECi|`9Aq8Fj)4JJzvqA7q@W90t&J;c-WgL0tJ~H=HV` zeuEGUG0LphZG!gpxH`AS=E5hW2Hyh!G}ZLb1e9Ia{{e_rY~TO@ literal 0 HcmV?d00001 diff --git a/src/main/resources/icons/search.png b/src/main/resources/icons/search.png new file mode 100644 index 0000000000000000000000000000000000000000..75880b1b94d1e1525acfec6e642bf7f1d3d82f0d GIT binary patch literal 8171 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 + + + + Axis Innovators Pro + + + + + + + + + + + + + + + + +

+ +
+
+
+ +
+
+
+ +
+
+
+ + + + + \ No newline at end of file