From e422d31b8572f8ab107a24cd9c496bf2f80d9bf0 Mon Sep 17 00:00:00 2001 From: tzdwindows 7 <3076584115@qq.com> Date: Sun, 23 Feb 2025 13:31:14 +0800 Subject: [PATCH] =?UTF-8?q?feat(box):=20=E7=8E=B0=E4=BB=A3=E5=8C=96?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=95=8C=E9=9D=A2=E5=B9=B6=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=20jar=20=E6=96=87=E4=BB=B6=E9=A2=84=E8=A7=88=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 使用 FlatDarculaLaf 样式库替换默认样式 - 添加 jar 文件预览功能,使用 CFR 进行反编译 - 更新 build.gradle 文件,添加新依赖项 - 新增 CFROutputSinkFactory 和 JarClassFileSource 类 - 修改主程序启动逻辑,支持 jar 文件预览 --- build.gradle | 25 +- plug-in/Template-1.0-SNAPSHOT.jar | Bin 0 -> 4537 bytes plug-in/python/Testing/main.py | 30 +- .../innovators/box/AxisInnovatorsBox.java | 21 +- .../java/com/axis/innovators/box/Main.java | 3 + .../gui/CFROutputSinkFactory.java | 51 + .../decompilation/gui/JarClassFileSource.java | 47 + .../decompilation/gui/ModernJarViewer.java | 1000 +++++++++++++++++ .../com/axis/innovators/box/gui/LoadIcon.java | 2 +- .../box/plugins/BoxClassLoader.java | 1 + .../axis/innovators/box/tools/ArgsParser.java | 11 +- src/main/resources/modern-dark.css | 45 + 12 files changed, 1225 insertions(+), 11 deletions(-) create mode 100644 plug-in/Template-1.0-SNAPSHOT.jar create mode 100644 src/main/java/com/axis/innovators/box/decompilation/gui/CFROutputSinkFactory.java create mode 100644 src/main/java/com/axis/innovators/box/decompilation/gui/JarClassFileSource.java create mode 100644 src/main/java/com/axis/innovators/box/decompilation/gui/ModernJarViewer.java create mode 100644 src/main/resources/modern-dark.css diff --git a/build.gradle b/build.gradle index 5c1c68e..a9dafea 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ plugins { id 'java' id 'application' id 'edu.sc.seis.launch4j' version '2.5.4' + id 'org.openjfx.javafxplugin' version '0.1.0' } // JDK 版本检查 @@ -58,7 +59,23 @@ dependencies { implementation 'org.python:jython-standalone:2.7.3' + implementation 'org.fxmisc.richtext:richtextfx:0.11.0' // 更新后的richtextfx + implementation 'org.bitbucket.mstrobel:procyon-core:0.5.36' // 使用JitPack版本 + implementation 'org.bitbucket.mstrobel:procyon-compilertools:0.5.36' + + implementation 'com.fifesoft:rsyntaxtextarea:3.3.0' + implementation 'org.apache.commons:commons-compress:1.23.0' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2' + implementation 'org.controlsfx:controlsfx:11.1.2' // 现代化UI组件 + implementation 'com.dlsc.formsfx:formsfx-core:11.6.0' // 表单组件 + implementation 'net.sourceforge.plantuml:plantuml:8059' // UML支持(可选) + implementation 'com.google.code.gson:gson:2.10.1' + + implementation 'org.openjfx:javafx-controls:21' + implementation 'org.benf:cfr:0.152' + + implementation 'com.github.javaparser:javaparser-core:3.25.1' } // 分离依赖项到 libs 目录 @@ -108,6 +125,11 @@ tasks.withType(JavaExec).configureEach { ] } +javafx { + version = "21" + modules = [ 'javafx.controls', 'javafx.fxml' ] +} + // 单独打包文档 task packageOpenSourceDocs(type: Jar) { archiveClassifier = 'docs' @@ -122,7 +144,8 @@ application { // 确保运行时参数生效 applicationDefaultJvmArgs = [ "-Djava.system.class.loader=com.axis.innovators.box.plugins.BoxClassLoader", - "-Dloader.library.path=$buildDir/libs/libs" + "-Dloader.library.path=$buildDir/libs/libs", + '-Dfile.encoding=UTF-8' ] } diff --git a/plug-in/Template-1.0-SNAPSHOT.jar b/plug-in/Template-1.0-SNAPSHOT.jar new file mode 100644 index 0000000000000000000000000000000000000000..94d65c2a065501158691e27b984a6406e51aca39 GIT binary patch literal 4537 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 literal 0 HcmV?d00001 diff --git a/plug-in/python/Testing/main.py b/plug-in/python/Testing/main.py index ecbb55d..8a5f2ad 100644 --- a/plug-in/python/Testing/main.py +++ b/plug-in/python/Testing/main.py @@ -5,13 +5,41 @@ 版本:1.1 """ from com.axis.innovators.box.python import PyLocalSide -from javax.swing import AbstractAction + +from javax.swing import AbstractAction, JFrame,JDialog, JLabel, JButton, JPanel, JOptionPane +from java.awt import BorderLayout, Dimension +from java.awt.event import ActionListener class MyAction(AbstractAction): def actionPerformed(self, event): """工具项点击事件处理""" print("[DEBUG] Tool item clicked! Event source:", event.getSource()) + # 创建 JDialog 作为子窗口 + parent_frame = event.getSource().getTopLevelAncestor() # 获取父窗口 + dialog = JDialog(parent_frame, u"工具面板", True) # True 表示模态对话框 + dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE) # 关闭时释放资源 + dialog.setSize(300, 200) + dialog.setLocationRelativeTo(parent_frame) # 居中于父窗口 + + # 创建面板并设置布局 + panel = JPanel() + panel.layout = BorderLayout() # 使用边界布局 + + # 添加标签 + label = JLabel(u"这是工具项的子面板") + panel.add(label, BorderLayout.CENTER) + + # 添加关闭按钮 + close_btn = JButton(u"关闭") + close_btn.addActionListener(lambda e: dialog.dispose()) # 点击关闭窗口 + panel.add(close_btn, BorderLayout.SOUTH) + + # 将面板添加到窗口 + dialog.add(panel) + dialog.visible = True # 显示窗口 + + def onStartup(): """ 系统启动时自动执行的初始化逻辑 diff --git a/src/main/java/com/axis/innovators/box/AxisInnovatorsBox.java b/src/main/java/com/axis/innovators/box/AxisInnovatorsBox.java index 62c6eb5..1d7bf8a 100644 --- a/src/main/java/com/axis/innovators/box/AxisInnovatorsBox.java +++ b/src/main/java/com/axis/innovators/box/AxisInnovatorsBox.java @@ -1,5 +1,6 @@ package com.axis.innovators.box; +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.events.StartupEvent; @@ -26,6 +27,7 @@ import java.awt.*; import java.awt.event.ActionEvent; import java.io.*; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; @@ -334,11 +336,24 @@ public class AxisInnovatorsBox { try { main.initLog4j2(); main.setTopic(); - List> validFiles = ArgsParser.parseArgs(args); - for (Map fileInfo : validFiles) { - OpenFileEvents openFileEvents = new OpenFileEvents(fileInfo.get("path"), fileInfo.get("extension")); + String extension = fileInfo.get("extension"); + String path = fileInfo.get("path"); + if (".jar".equals(extension)){ + SwingUtilities.invokeLater(() -> { + try { + UIManager.setLookAndFeel(new com.formdev.flatlaf.FlatDarculaLaf()); + } catch (Exception ex) { + ex.printStackTrace(); + } + ModernJarViewer viewer = new ModernJarViewer(null, path); + main.popupWindow(viewer); + }); + main.progressBarManager.close(); + return; + } + OpenFileEvents openFileEvents = new OpenFileEvents(path, extension); GlobalEventBus.EVENT_BUS.post(openFileEvents); if (!openFileEvents.isContinue()) { return; diff --git a/src/main/java/com/axis/innovators/box/Main.java b/src/main/java/com/axis/innovators/box/Main.java index 6d09fc5..e33a86b 100644 --- a/src/main/java/com/axis/innovators/box/Main.java +++ b/src/main/java/com/axis/innovators/box/Main.java @@ -4,6 +4,8 @@ import com.axis.innovators.box.tools.FolderCleaner; import com.axis.innovators.box.tools.FolderCreator; import com.axis.innovators.box.register.LanguageManager; +import java.util.Arrays; + /** * @author tzdwindows 7 */ @@ -16,6 +18,7 @@ public class Main { LanguageManager.loadLanguage("system:zh_CN"); } + System.out.println(Arrays.toString(args)); AxisInnovatorsBox.run(args); } } diff --git a/src/main/java/com/axis/innovators/box/decompilation/gui/CFROutputSinkFactory.java b/src/main/java/com/axis/innovators/box/decompilation/gui/CFROutputSinkFactory.java new file mode 100644 index 0000000..92f3df8 --- /dev/null +++ b/src/main/java/com/axis/innovators/box/decompilation/gui/CFROutputSinkFactory.java @@ -0,0 +1,51 @@ +package com.axis.innovators.box.decompilation.gui; + +import org.benf.cfr.reader.api.OutputSinkFactory; +import org.benf.cfr.reader.api.SinkReturns.Decompiled; +import org.benf.cfr.reader.api.SinkReturns.ExceptionMessage; +import org.benf.cfr.reader.api.SinkReturns; + +import javax.swing.*; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class CFROutputSinkFactory implements OutputSinkFactory { + private JTextArea codeArea; + private StringBuilder output = new StringBuilder(); + + public CFROutputSinkFactory(JTextArea codeArea) { + this.codeArea = codeArea; + } + + public CFROutputSinkFactory() { + } + + @Override + public List getSupportedSinks(SinkType sgetOutputinkType, Collection collection) { + return Collections.singletonList(SinkClass.DECOMPILED); + } + + + public String getOutput() { + return output.toString(); + } + + @Override + public Sink getSink(SinkType sinkType, SinkClass sinkClass) { + if (sinkClass == SinkClass.DECOMPILED) { + return (Sink) new Sink() { + @Override + public void write(Decompiled decompiled) { + if (codeArea != null) { + SwingUtilities.invokeLater(() -> + codeArea.setText(decompiled.getJava()) + ); + } + output.append(decompiled.getJava()).append("\n"); + } + }; + } + return null; + } +} diff --git a/src/main/java/com/axis/innovators/box/decompilation/gui/JarClassFileSource.java b/src/main/java/com/axis/innovators/box/decompilation/gui/JarClassFileSource.java new file mode 100644 index 0000000..3f16d74 --- /dev/null +++ b/src/main/java/com/axis/innovators/box/decompilation/gui/JarClassFileSource.java @@ -0,0 +1,47 @@ +package com.axis.innovators.box.decompilation.gui; + +import org.benf.cfr.reader.api.ClassFileSource; +import org.benf.cfr.reader.bytecode.analysis.parse.utils.Pair; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.Collections; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +public class JarClassFileSource implements ClassFileSource { + private final JarFile jarFile; + + public JarClassFileSource(JarFile jarFile) { + this.jarFile = jarFile; + } + + @Override + public void informAnalysisRelativePathDetail(String usePath, String classFilePath) { + } + + @Override + public Collection addJar(String jarPath) { + return Collections.emptyList(); + } + + @Override + public String getPossiblyRenamedPath(String path) { + return path; + } + + @Override + public Pair getClassFileContent(String path) throws IOException { + JarEntry entry = jarFile.getJarEntry(path); + if (entry == null) { + return null; + } + + try (InputStream is = jarFile.getInputStream(entry)) { + byte[] bytes = new byte[(int) entry.getSize()]; + is.read(bytes); + return Pair.make(bytes, path); + } + } +} 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 new file mode 100644 index 0000000..bd9ad35 --- /dev/null +++ b/src/main/java/com/axis/innovators/box/decompilation/gui/ModernJarViewer.java @@ -0,0 +1,1000 @@ +package com.axis.innovators.box.decompilation.gui; + +import com.axis.innovators.box.gui.LoadIcon; +import com.axis.innovators.box.gui.WindowsJDialog; +import com.github.javaparser.JavaParser; +import com.github.javaparser.ParseResult; +import com.github.javaparser.Position; +import com.github.javaparser.ast.CompilationUnit; +import org.apache.commons.compress.utils.IOUtils; +import org.benf.cfr.reader.api.CfrDriver; +import org.fife.ui.rsyntaxtextarea.*; +import org.fife.ui.rtextarea.RTextScrollPane; +import com.github.javaparser.ast.expr.MethodCallExpr; + +import com.github.javaparser.ast.body.MethodDeclaration; + +import java.awt.event.*; +import java.util.concurrent.ConcurrentHashMap; + +import javax.swing.*; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.text.BadLocationException; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreePath; +import java.awt.*; +import java.io.*; +import java.net.URI; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author tzdwindows 7 + */ +public class ModernJarViewer extends WindowsJDialog { + private final DefaultListModel globalSearchResults = new DefaultListModel<>(); + private JTree fileTree; + private JTabbedPane contentPane; + private RSyntaxTextArea codeArea; + private JLabel imageLabel; + private File currentJarFile; + private final Map methodIndex = new ConcurrentHashMap<>(); + private MethodLink currentHoveredMethod; + private JavaParser javaParser = new JavaParser(); + private DefaultMutableTreeNode root; + + public ModernJarViewer(Frame owner) { + super(owner, "Jar反编译工具", true); + initComponents(); + } + + /****/ + public ModernJarViewer(Frame owner, String jarFile) { + super(owner, "Jar反编译工具", true); + initComponents(); + loadJar(new File(jarFile)); + } + + /****/ + private void initComponents() { + setSize(1280, 800); + setLayout(new BorderLayout()); + + setIconImage(LoadIcon.loadIcon("logo.png", 32).getImage()); + + // 初始化菜单 + JMenuBar menuBar = new JMenuBar(); + JMenu fileMenu = new JMenu("File"); + JMenuItem openItem = new JMenuItem("Open"); + openItem.addActionListener(e -> openJarFile()); + fileMenu.add(openItem); + menuBar.add(fileMenu); + setJMenuBar(menuBar); + + // 初始化文件树 + root = new DefaultMutableTreeNode("Root"); + fileTree = new JTree(new DefaultTreeModel(root)); + fileTree.addTreeSelectionListener(new TreeSelectionHandler()); + JScrollPane treeScroll = new JScrollPane(fileTree); + + // 初始化代码编辑器 + codeArea = new RSyntaxTextArea(); + codeArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA); + codeArea.setCodeFoldingEnabled(true); + codeArea.setAntiAliasingEnabled(true); + codeArea.setFont(new Font("Consolas", Font.PLAIN, 14)); + codeArea.setEditable(false); + + JPopupMenu popupMenu = createRightClickMenu(); + codeArea.setComponentPopupMenu(popupMenu); + + codeArea.setHyperlinksEnabled(true); + codeArea.addHyperlinkListener(e -> { + if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + handleHyperlinkClick(e.getURL()); + } + }); + + codeArea.setToolTipSupplier((textArea, mouseEvent) -> { + // 优先检查方法链接 + currentHoveredMethod = findMethodUnderCursor(mouseEvent.getPoint()); + if (currentHoveredMethod != null) { + return buildMethodTooltip(currentHoveredMethod); + } + + // 回退到Javadoc检查 + String javadoc = getJavadocAtCursor(); + return javadoc != null ? formatJavadoc(javadoc) : null; + }); + + codeArea.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + handleMethodClick(e); + } + }); + + JTextPane popupTextPane = new JTextPane(); + popupTextPane.setContentType("text/html"); + popupTextPane.setEditable(false); + ToolTipManager.sharedInstance().registerComponent(codeArea); + codeArea.setToolTipSupplier((textArea, mouseEvent) -> { + String javadoc = getJavadocAtCursor(); + if (javadoc != null) { + popupTextPane.setText(formatJavadoc(javadoc)); + return String.valueOf(popupTextPane); + } + return null; + }); + + codeArea.setBackground(new Color(0x1E1F22)); + //codeArea.setForeground(new Color(0xBBBBBB)); + codeArea.setSelectionColor(new Color(0x214283)); + codeArea.setCurrentLineHighlightColor(new Color(0x323232)); + + + // 使用 RTextScrollPane 支持代码折叠 + RTextScrollPane codeScrollPane = new RTextScrollPane(codeArea); + codeScrollPane.setLineNumbersEnabled(true); + codeScrollPane.setFoldIndicatorEnabled(true); + + // 初始化图片预览 + imageLabel = new JLabel(); + JScrollPane imageScrollPane = new JScrollPane(imageLabel); + + contentPane = new JTabbedPane(); + contentPane.addTab("Code", codeScrollPane); + contentPane.addTab("Image", imageScrollPane); + codeArea.setCodeFoldingEnabled(true); + + Font ideaFont = new Font("JetBrains Mono", Font.PLAIN, 13); + if (!Arrays.asList(GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames()).contains("JetBrains Mono")) { + ideaFont = new Font("Consolas", Font.PLAIN, 14); + } + codeArea.setFont(ideaFont); + + SyntaxScheme scheme = new SyntaxScheme(true); + scheme = configureIDEATheme(scheme); + codeArea.setSyntaxScheme(scheme); + + JSplitPane splitPane = new JSplitPane( + JSplitPane.HORIZONTAL_SPLIT, + treeScroll, + contentPane + ); + splitPane.setDividerLocation(300); + add(splitPane, BorderLayout.CENTER); + setupKeyBindings(); + JList globalSearchList = new JList<>(globalSearchResults); + globalSearchList.setCellRenderer(new SearchResultRenderer()); + } + + private JPopupMenu createRightClickMenu() { + JPopupMenu popupMenu = new JPopupMenu(); + + JMenuItem gotoMethodItem = new JMenuItem("跳转到方法定义"); + gotoMethodItem.addActionListener(e -> { + Point mousePosition = codeArea.getMousePosition(); + if (mousePosition != null) { + MethodLink link = findMethodUnderCursor(mousePosition); + if (link != null) { + navigateToMethod(link); + } else { + JOptionPane.showMessageDialog(this, "未找到方法定义", "提示", JOptionPane.INFORMATION_MESSAGE); + } + } + }); + popupMenu.add(gotoMethodItem); + + return popupMenu; + } + + private String decompileClass(JarEntry entry) { + try (JarFile jar = new JarFile(currentJarFile)) { + CFROutputSinkFactory sinkFactory = new CFROutputSinkFactory(); + CfrDriver driver = new CfrDriver.Builder() + .withClassFileSource(new JarClassFileSource(jar)) + .withOutputSink(sinkFactory) + .build(); + + driver.analyse(Collections.singletonList(entry.getName())); + return sinkFactory.getOutput(); + } catch (Exception e) { + e.printStackTrace(); + return "// Decompilation failed\n"; + } + } + + private static class SearchResultRenderer extends DefaultListCellRenderer { + public Component getListCellRendererComponent(JList list, Object value, int index, + boolean isSelected, boolean cellHasFocus) { + SearchResult result = (SearchResult) value; + JLabel label = (JLabel) super.getListCellRendererComponent( + list, value, index, isSelected, cellHasFocus); + label.setText("" + result.filePath + ": " + result.matchText + ""); + return label; + } + } + + private void setupKeyBindings() { + // 本地文件搜索 + KeyStroke ctrlF = KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_DOWN_MASK); + codeArea.getInputMap().put(ctrlF, "searchLocal"); + codeArea.getActionMap().put("searchLocal", new AbstractAction() { + public void actionPerformed(ActionEvent e) { + showLocalSearchDialog(); + } + }); + + // 全局搜索 + KeyStroke ctrlShiftF = KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK); + codeArea.getInputMap().put(ctrlShiftF, "searchGlobal"); + codeArea.getActionMap().put("searchGlobal", new AbstractAction() { + public void actionPerformed(ActionEvent e) { + showGlobalSearchDialog(); + } + }); + } + + private void showLocalSearchDialog() { + JDialog searchDialog = new JDialog(this, "查找", false); + JTextField searchField = new JTextField(20); + JButton nextButton = new JButton("下一个"); + JButton prevButton = new JButton("上一个"); + + nextButton.addActionListener(e -> searchInText(searchField.getText(), true)); + prevButton.addActionListener(e -> searchInText(searchField.getText(), false)); + + JPanel panel = new JPanel(); + panel.add(searchField); + panel.add(prevButton); + panel.add(nextButton); + + searchDialog.add(panel); + searchDialog.pack(); + searchDialog.setLocationRelativeTo(this); + searchDialog.setVisible(true); + } + + private void searchInText(String pattern, boolean forward) { + String text = codeArea.getText(); + int caret = codeArea.getCaretPosition(); + + if (forward) { + int index = text.indexOf(pattern, caret); + if (index != -1) { + codeArea.select(index, index + pattern.length()); + } + } else { + int index = text.lastIndexOf(pattern, caret - pattern.length()); + if (index != -1) { + codeArea.select(index, index + pattern.length()); + } + } + } + + private void showGlobalSearchDialog() { + JDialog globalDialog = new JDialog(this, "全局搜索", false); + JTextField searchField = new JTextField(30); + JButton searchButton = new JButton("搜索"); + DefaultListModel listModel = new DefaultListModel<>(); + JList resultList = new JList<>(listModel); + + // 添加搜索按钮的监听器 + searchButton.addActionListener(e -> { + // 在后台线程中执行搜索操作 + new SwingWorker() { + @Override + protected Void doInBackground() throws Exception { + searchInJar(searchField.getText(), listModel); // 耗时的搜索操作 + return null; + } + + @Override + protected void done() { + // 搜索完成后更新 UI + JOptionPane.showMessageDialog(globalDialog, "搜索完成!"); + } + }.execute(); + }); + + // 添加结果列表的监听器 + resultList.addListSelectionListener(e -> { + SearchResult result = resultList.getSelectedValue(); + if (result != null) { + navigateToSearchResult(result); // 跳转到搜索结果 + } + }); + + // 设置布局 + JPanel panel = new JPanel(new BorderLayout()); + panel.add(searchField, BorderLayout.NORTH); + panel.add(new JScrollPane(resultList), BorderLayout.CENTER); + panel.add(searchButton, BorderLayout.SOUTH); + + globalDialog.add(panel); + globalDialog.setSize(500, 400); + globalDialog.setLocationRelativeTo(this); + globalDialog.setVisible(true); // 显示对话框 + } + + private void navigateToSearchResult(SearchResult result) { + expandTreeToClass(result.filePath); + + SwingUtilities.invokeLater(() -> { + try { + codeArea.setCaretPosition(result.position); + codeArea.moveCaretPosition(result.position + result.matchText.length()); + Rectangle rect = codeArea.modelToView(result.position); + codeArea.scrollRectToVisible(rect); + codeArea.requestFocus(); + } catch (BadLocationException e) { + e.printStackTrace(); + } + }); + } + + private void searchInJar(String pattern, DefaultListModel listModel) { + try (JarFile jar = new JarFile(currentJarFile)) { + Enumeration entries = jar.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + if (entry.isDirectory()) continue; + + String content = ""; + // 区分处理源码和class文件 + if (entry.getName().endsWith(".java")) { + try (InputStream is = jar.getInputStream(entry)) { + content = new String(IOUtils.toByteArray(is), StandardCharsets.UTF_8); + } + } else if (entry.getName().endsWith(".class")) { + content = decompileClass(entry); + } + if (content.contains(pattern)) { + int index = content.indexOf(pattern); + while (index >= 0) { + int finalIndex = index; + SwingUtilities.invokeLater(() -> { + listModel.addElement(new SearchResult( + entry.getName(), + finalIndex, + pattern + )); + }); + index = content.indexOf(pattern, index + 1); + } + } + } + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + private String getEntryContent(JarEntry entry) { + try (JarFile jar = new JarFile(currentJarFile); + InputStream is = jar.getInputStream(entry)) { + if (isTextFile(entry.getName())) { + return new String(IOUtils.toByteArray(is), StandardCharsets.UTF_8); + } + } catch (Exception e) { + e.printStackTrace(); + } + return ""; + } + + private static class SearchResult { + final String filePath; + final int position; + final String matchText; + + SearchResult(String path, int pos, String text) { + filePath = path; + position = pos; + matchText = text; + } + + public String toString() { + return filePath + ": " + matchText; + } + } + + private MethodLink findMethodUnderCursor(Point point) { + try { + // 修复坐标转换方法名 + int offset = codeArea.viewToModel(point); + + // 获取当前行的Token列表 + int line = codeArea.getLineOfOffset(offset); + Token token = codeArea.getTokenListForLine(line); + + // 遍历Tokens找到精确位置 + while (token != null && token.isPaintable()) { + if (offset >= token.getOffset() && offset <= token.getEndOffset()) { + if (token.getType() == Token.IDENTIFIER) { + return resolveMethodCall(token, offset); + } + break; + } + token = token.getNextToken(); + } + } catch (BadLocationException e) { + e.printStackTrace(); + } + return null; + } + + private MethodLink resolveMethodCall(Token token, int offset) { + try { + String code = codeArea.getText(); + ParseResult parseResult = javaParser.parse(code); + + if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) { + CompilationUnit cu = parseResult.getResult().get(); + Position range2 = new Position(offset, offset); + return cu.findAll(MethodCallExpr.class).stream() + .filter(mce -> mce.getRange() + .map(range -> range.contains(range2)) + .orElse(false) + ) + .findFirst() + .map(mce -> { + try { + String className = mce.resolve().getClassName(); + String methodName = mce.getNameAsString(); + String signature = mce.resolve().getSignature(); + return new MethodLink(className, methodName, signature); + } catch (Exception e) { + return new MethodLink("Unknown", mce.getNameAsString(), ""); + } + }) + .orElse(null); + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + private MethodLink resolveMethodCall(MethodCallExpr mce) { + String methodName = mce.getNameAsString(); + String className = mce.resolve().getClassName(); // 需要处理解析异常 + String signature = mce.resolve().getSignature(); + return new MethodLink(className, methodName, signature); + } + + private String buildMethodTooltip(MethodLink link) { + MethodInfo info = methodIndex.get(link.getFullSignature()); + if (info == null) return null; + + return "
" + + "" + info.className + "" + + "." + info.methodName + "" + + "" + info.parameters + "
" + + "
" + + (info.javadoc != null ? info.javadoc : "No documentation available") + + "
"; + } + + private void handleMethodClick(MouseEvent e) { + MethodLink link = findMethodUnderCursor(e.getPoint()); + if (link == null) return; + + // 支持左键/中键跳转 + if (SwingUtilities.isLeftMouseButton(e) || + SwingUtilities.isMiddleMouseButton(e)) { + navigateToMethod(link); + } + } + + private void navigateToMethod(MethodLink link) { + // 转换为内部路径格式 + String sourcePath = link.className.replace('.', '/') + ".java"; + String classPath = link.className.replace('.', '/') + ".class"; + if (entryExistsInJar(sourcePath)) { + expandTreeToClass(sourcePath); + loadAndNavigateInSource(sourcePath, link.methodName); + } else { + expandTreeToClass(classPath); + SwingUtilities.invokeLater(() -> { + String code = codeArea.getText(); + String pattern = Pattern.quote(link.methodName + link.signature); + Matcher matcher = Pattern.compile(pattern).matcher(code); + if (matcher.find()) { + codeArea.setCaretPosition(matcher.start()); + codeArea.moveCaretPosition(matcher.end()); + codeArea.scrollRectToVisible(codeArea.getVisibleRect()); + } + }); + } + } + + private boolean entryExistsInJar(String path) { + try (JarFile jar = new JarFile(currentJarFile)) { + return jar.getEntry(path) != null; + } catch (Exception e) { + return false; + } + } + + private void loadAndNavigateInSource(String sourcePath, String methodName) { + try (JarFile jar = new JarFile(currentJarFile)) { + JarEntry entry = jar.getJarEntry(sourcePath); + try (InputStream is = jar.getInputStream(entry)) { + String sourceCode = new String(IOUtils.toByteArray(is), StandardCharsets.UTF_8); + codeArea.setText(sourceCode); + + // 在源码中定位方法 + Pattern pattern = Pattern.compile("\\b" + methodName + "\\s*\\("); + Matcher matcher = pattern.matcher(sourceCode); + if (matcher.find()) { + codeArea.setCaretPosition(matcher.start()); + codeArea.moveCaretPosition(matcher.end()); + } + } + } catch (Exception ex) { + showError("源码加载错误", ex.getMessage()); + } + } + + private void expandTreeToClass(String classPath) { + DefaultMutableTreeNode root = (DefaultMutableTreeNode) fileTree.getModel().getRoot(); + String[] pathSegments = classPath.split("/"); + + // 递归查找节点 + DefaultMutableTreeNode targetNode = findNode(root, pathSegments, 0); + + if (targetNode != null) { + TreePath path = new TreePath(targetNode.getPath()); + fileTree.expandPath(path); + fileTree.setSelectionPath(path); + fileTree.scrollPathToVisible(path); + } + } + + private DefaultMutableTreeNode findNode(DefaultMutableTreeNode parent, String[] pathSegments, int depth) { + if (depth >= pathSegments.length) { + return parent; + } + + String target = pathSegments[depth].toLowerCase(); + Enumeration children = parent.children(); + while (children.hasMoreElements()) { + DefaultMutableTreeNode child = (DefaultMutableTreeNode) children.nextElement(); + String nodeName = child.getUserObject().toString().toLowerCase(); + if (nodeName.startsWith(target)) { + DefaultMutableTreeNode found = findNode(child, pathSegments, depth + 1); + if (found != null) return found; + } + } + return null; + } + + private static class MethodLink { + final String className; + final String methodName; + final String signature; + + MethodLink(String className, String methodName, String signature) { + this.className = className; + this.methodName = methodName; + this.signature = signature; + } + + String getFullSignature() { + return className + "#" + methodName + signature; + } + } + + + /** + * 处理超链接点击事件 + * + * @param url 点击的链接 URL + */ + private void handleHyperlinkClick(URL url) { + if (url == null) return; + + try { + String href = url.toString(); + if (href.startsWith("#")) { + // 处理内部链接(如 {@link #methodName}) + String target = href.substring(1); // 去掉 # 符号 + navigateToMethod(target); // 跳转到目标方法 + } else { + // 处理外部链接(如 {@link java.lang.String}) + Desktop.getDesktop().browse(new URI(href)); + } + } catch (Exception e) { + e.printStackTrace(); + JOptionPane.showMessageDialog(this, "无法打开链接: " + url, "错误", JOptionPane.ERROR_MESSAGE); + } + } + + /** + * 跳转到指定方法 + * + * @param methodName 方法名称 + */ + private void navigateToMethod(String methodName) { + String text = codeArea.getText(); + int index = text.indexOf(methodName + "("); // 查找方法定义 + + if (index >= 0) { + codeArea.setCaretPosition(index); // 移动光标到方法定义处 + codeArea.requestFocusInWindow(); // 聚焦编辑器 + } else { + JOptionPane.showMessageDialog(this, "未找到方法: " + methodName, "提示", JOptionPane.INFORMATION_MESSAGE); + } + } + + /** + * 获取光标位置的 Javadoc 注释 + * + * @return Javadoc 注释内容,如果光标不在 Javadoc 上则返回 null + */ + private String getJavadocAtCursor() { + try { + int offset = codeArea.getCaretPosition(); // 获取光标位置 + Token token = codeArea.getTokenListForLine(codeArea.getCaretLineNumber()); + + // 遍历当前行的 Token,查找 Javadoc 注释 + while (token != null && token.isPaintable()) { + if (token.getType() == Token.COMMENT_DOCUMENTATION) { + // 检查光标是否在 Javadoc 范围内 + if (offset >= token.getOffset() && offset <= token.getEndOffset()) { + return token.getLexeme(); // 返回 Javadoc 内容 + } + } + token = token.getNextToken(); + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; // 未找到 Javadoc + } + + /** + * 格式化 Javadoc 注释为 HTML + * + * @param rawJavadoc 原始 Javadoc 注释 + * @return 格式化后的 HTML 内容 + */ + private String formatJavadoc(String rawJavadoc) { + // 替换 Javadoc 标签为高亮样式 + String formatted = rawJavadoc + .replace("@param", "@param") + .replace("@return", "@return") + .replace("@throws", "@throws") + .replace("@see", "@see") + .replace("{@link", "") + .replace("{@code", "") + .replace("\n", "
"); // 换行符替换为 HTML 换行 + + // 包裹 HTML 结构 + return "" + + formatted + + ""; + } + + private SyntaxScheme configureIDEATheme(SyntaxScheme scheme) { + // 基础颜色配置 + Color background = new Color(0x2B2B2B); + Color foreground = new Color(0xE0E0E0); + + // 设置全局默认样式 + Style defaultStyle = scheme.getStyle(Token.NULL); + defaultStyle.foreground = foreground; + defaultStyle.background = background; + + // IDEA 风格关键颜色配置 + setTokenStyle(scheme, Token.RESERVED_WORD, 0xCC7832); + setTokenStyle(scheme, Token.SEPARATOR, 0x4EC9B0); + setTokenStyle(scheme, Token.OPERATOR, 0xFFD700); + setTokenStyle(scheme, Token.IDENTIFIER, 0xE0E0E0); + setTokenStyle(scheme, Token.LITERAL_STRING_DOUBLE_QUOTE, 0x6A8759); + setTokenStyle(scheme, Token.LITERAL_NUMBER_DECIMAL_INT, 0x6897BB); + setTokenStyle(scheme, Token.COMMENT_EOL, 0x6A8759); + setTokenStyle(scheme, Token.COMMENT_MULTILINE, 0x6A8759); + setTokenStyle(scheme, Token.COMMENT_DOCUMENTATION, 0x629755); + setTokenStyle(scheme, Token.ANNOTATION, 0xBBB529); + setTokenStyle(scheme, Token.FUNCTION, 0xFFC66D); + setTokenStyle(scheme, Token.DATA_TYPE, 0xE8BF6A); + + // ======== 跨语言通用配置 ======== + // XML/HTML 标签(解决 颜色问题) + setTokenStyle(scheme, Token.MARKUP_TAG_NAME, 0xE8BF6A); + setTokenStyle(scheme, Token.MARKUP_TAG_DELIMITER, 0xCC7832); + setTokenStyle(scheme, Token.MARKUP_ENTITY_REFERENCE, 0x6897BB); + + // 正则表达式 + setTokenStyle(scheme, Token.REGEX, 0xD25252); + + // ======== 增强Java配置 ======== + setTokenStyle(scheme, Token.RESERVED_WORD_2, 0xCC7832); + setTokenStyle(scheme, Token.ANNOTATION, 0xBBB529); + + + // 设置当前行高亮 + codeArea.setHighlightCurrentLine(true); + codeArea.setCurrentLineHighlightColor(new Color(0x323232)); + + return scheme; + } + + + private void setTokenStyle(SyntaxScheme scheme, int tokenType, int rgb) { + Style style = scheme.getStyle(tokenType); + style.foreground = new Color(rgb); + // 如果需要可以设置粗体/斜体 + // style.fontFlags = Font.BOLD; + } + + /****/ + private void openJarFile() { + JFileChooser chooser = new JFileChooser(); + chooser.setFileFilter(new javax.swing.filechooser.FileFilter() { + public boolean accept(File f) { + return f.getName().endsWith(".jar") || f.isDirectory(); + } + public String getDescription() { return "JAR Files (*.jar)"; } + }); + + if(chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { + loadJar(chooser.getSelectedFile()); + } + } + + /****/ + private void loadJar(File jarFile) { + currentJarFile = jarFile; + try (JarFile jar = new JarFile(jarFile)) { + // 新增源码文件优先标记 + Set sourceFiles = new HashSet<>(); + + Enumeration entries = jar.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String name = entry.getName(); + if (name.endsWith(".java")) { + sourceFiles.add(name.replace(".java", ".class")); + } + } + + entries = jar.entries(); // 重新遍历 + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String name = entry.getName(); + // 忽略已被源码覆盖的class文件 + if (name.endsWith(".class") && sourceFiles.contains(name)) continue; + addEntryToTree(root, name); + } + } catch (Exception ex) { + JOptionPane.showMessageDialog(this, + "Error loading JAR: " + ex.getMessage(), + "Error", + JOptionPane.ERROR_MESSAGE + ); + } + } + + /****/ + private void addEntryToTree(DefaultMutableTreeNode parent, String path) { + String[] segments = path.split("/"); + DefaultMutableTreeNode current = parent; + + for (String segment : segments) { + DefaultMutableTreeNode child = findChild(current, segment); + if (child == null) { + child = new DefaultMutableTreeNode(segment); + current.add(child); + } + current = child; + } + } + + /****/ + private DefaultMutableTreeNode findChild(DefaultMutableTreeNode parent, String name) { + Enumeration children = parent.children(); + while (children.hasMoreElements()) { + DefaultMutableTreeNode child = (DefaultMutableTreeNode) children.nextElement(); + if (child.getUserObject().equals(name)) { + return child; + } + } + return null; + } + + /****/ + private class TreeSelectionHandler implements TreeSelectionListener { + public void valueChanged(TreeSelectionEvent e) { + TreePath path = fileTree.getSelectionPath(); + if (path != null) { + String entryPath = buildEntryPath(path); + handleSelection(entryPath); + } + } + + private String buildEntryPath(TreePath path) { + StringBuilder sb = new StringBuilder(); + Object[] nodes = path.getPath(); + for (int i=1; i { + codeArea.setText(sourceCode); + codeArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA); + codeArea.revalidate(); + }); + contentPane.setSelectedIndex(0); + } + } else if (entryPath.toLowerCase().endsWith(".xml")) { + loadTextFile(jar, entry, SyntaxConstants.SYNTAX_STYLE_XML); + } else if (isTextFile(entryPath)) { + loadTextFile(jar, entry, SyntaxConstants.SYNTAX_STYLE_NONE); + } else if (isImageFile(entryPath)) { + showImage(entry); + } + } catch (Exception ex) { + showError("Selection Error", ex.getMessage()); + } + } + + private void buildMethodIndex(String entryPath) { + try { + String code = ""; + if (entryPath.endsWith(".java")) { + // 直接读取源码 + try (InputStream is = new JarFile(currentJarFile).getInputStream(new JarEntry(entryPath))) { + code = new String(IOUtils.toByteArray(is), StandardCharsets.UTF_8); + } + } else { + code = codeArea.getText(); // 反编译内容 + } + ParseResult parseResult = javaParser.parse(code); + parseResult.ifSuccessful(cu -> { + cu.findAll(MethodDeclaration.class).forEach(md -> { + + MethodInfo info = new MethodInfo(); + info.className = entryPath.replace("/", ".") + .replace(".java", "") + .replace(".class", ""); + + info.methodName = md.getNameAsString(); + info.parameters = md.getParameters().toString(); + info.javadoc = md.getJavadoc().map(d -> d.getDescription().toText()).orElse(null); + + methodIndex.put(info.getSignature(), info); + }); + }); + } catch (Exception e) { + e.printStackTrace(); + } + } + + //++ 方法信息类 + private static class MethodInfo { + String className; + String methodName; + String parameters; + String javadoc; + + String getSignature() { + return className + "#" + methodName + parameters; + } + } + + private void loadTextFile(JarFile jar, JarEntry entry, String syntaxStyle) throws IOException { + try (InputStream is = jar.getInputStream(entry)) { + String content = new String(IOUtils.toByteArray(is), StandardCharsets.UTF_8); + SwingUtilities.invokeLater(() -> { + codeArea.setText(content); + codeArea.setSyntaxEditingStyle(syntaxStyle); + codeArea.revalidate(); + + if (entry.getName().toUpperCase().endsWith("MANIFEST.MF")) { + codeArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_PROPERTIES_FILE); + } + }); + } + contentPane.setSelectedIndex(0); + } + + private void decompileWithCFR(JarEntry entry) { + try (JarFile jar = new JarFile(currentJarFile)) { + CfrDriver driver = new CfrDriver.Builder() + .withClassFileSource(new JarClassFileSource(jar)) + .withOutputSink(new CFROutputSinkFactory(codeArea)) + .build(); + + driver.analyse(Collections.singletonList( + entry.getName() + )); + + SwingUtilities.invokeLater(() -> { + codeArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA); + codeArea.revalidate(); + }); + contentPane.setSelectedIndex(0); + } catch (Exception ex) { + showError("CFR Decompilation Error", ex.getMessage()); + } + } + + /****/ + private void showImage(JarEntry entry) { + try (InputStream is = new JarFile(currentJarFile).getInputStream(entry)) { + ImageIcon icon = new ImageIcon(IOUtils.toByteArray(is)); + imageLabel.setIcon(icon); + contentPane.setSelectedIndex(1); + } catch (Exception ex) { + showError("Image Error", ex.getMessage()); + } + } + + /****/ + private boolean isImageFile(String name) { + String[] exts = {".png", ".jpg", ".gif"}; + for (String ext : exts) { + if (name.toLowerCase().endsWith(ext)) return true; + } + return false; + } + + /****/ + private void showError(String title, String message) { + JOptionPane.showMessageDialog( + this, + message, + title, + JOptionPane.ERROR_MESSAGE + ); + } + + /****/ + public static void main(String[] args) { + SwingUtilities.invokeLater(() -> { + try { + // 使用Darcula主题(需添加FlatLaf依赖) + UIManager.setLookAndFeel(new com.formdev.flatlaf.FlatDarculaLaf()); + } catch (Exception ex) { + ex.printStackTrace(); + } + ModernJarViewer viewer = new ModernJarViewer(null); + viewer.setVisible(true); + }); + } +} \ No newline at end of file diff --git a/src/main/java/com/axis/innovators/box/gui/LoadIcon.java b/src/main/java/com/axis/innovators/box/gui/LoadIcon.java index d1942b6..4b1f559 100644 --- a/src/main/java/com/axis/innovators/box/gui/LoadIcon.java +++ b/src/main/java/com/axis/innovators/box/gui/LoadIcon.java @@ -25,7 +25,7 @@ public class LoadIcon { * @param size 图片大小 * @return ImageIcon对象 */ - static ImageIcon loadIcon(String filename, int size) { + public static ImageIcon loadIcon(String filename, int size) { return loadIcon(LoadIcon.class, filename, size); } diff --git a/src/main/java/com/axis/innovators/box/plugins/BoxClassLoader.java b/src/main/java/com/axis/innovators/box/plugins/BoxClassLoader.java index 02d4932..a2201b6 100644 --- a/src/main/java/com/axis/innovators/box/plugins/BoxClassLoader.java +++ b/src/main/java/com/axis/innovators/box/plugins/BoxClassLoader.java @@ -26,6 +26,7 @@ public class BoxClassLoader extends URLClassLoader { "java.", "javax.", "sun.", "com.sun.", "jdk.", "org.xml.", "org.w3c.", "org.apache.", "javax.management.", "javax.swing." + , "javafx." ); } diff --git a/src/main/java/com/axis/innovators/box/tools/ArgsParser.java b/src/main/java/com/axis/innovators/box/tools/ArgsParser.java index d958a12..8d2dfde 100644 --- a/src/main/java/com/axis/innovators/box/tools/ArgsParser.java +++ b/src/main/java/com/axis/innovators/box/tools/ArgsParser.java @@ -64,11 +64,12 @@ public class ArgsParser { public static void main(String[] args) { // 模拟 main 方法的 args 参数 String[] testArgs = { - "C:\\Program Files\\test.txt", // 合法文件 - "C:\\invalid_file.exe", // 不存在的文件 - "D:\\Documents\\report.pdf", // 合法文件 - "not_a_path", // 无效路径 - "C:\\Windows\\system32" // 目录,不是文件 + "C:\\Program Files\\test.txt", + "C:\\invalid_file.exe", + "D:\\Documents\\report.pdf", + "not_a_path", + "C:\\Windows\\system32" , + "C:\\Users\\Administrator\\MCreatorWorkspaces\\AxisInnovatorsBox\\build.gradle" }; // 解析 args 参数 diff --git a/src/main/resources/modern-dark.css b/src/main/resources/modern-dark.css new file mode 100644 index 0000000..7521e06 --- /dev/null +++ b/src/main/resources/modern-dark.css @@ -0,0 +1,45 @@ +.root { + -fx-base: #2D2D2D; + -fx-background: #1E1E1E; + -fx-control-inner-background: derive(-fx-base, 20%); + -fx-accent: #45A1FF; +} + +.menu-bar { + -fx-background-color: linear-gradient(to bottom, #3C3C3C, #2D2D2D); +} + +.tree-view { + -fx-background-color: -fx-base; + -fx-border-color: derive(-fx-base, -10%); +} + +.tree-cell { + -fx-text-fill: #DCDCDC; + -fx-font-size: 14px; +} + +.tree-cell:selected { + -fx-background-color: #45A1FF; + -fx-text-fill: white; +} + +.code-area { + -fx-font-family: "JetBrains Mono"; + -fx-font-size: 14px; + -fx-highlight-fill: #264F78; +} + +.tab-pane { + -fx-background-color: derive(-fx-base, 10%); +} + +.tab { + -fx-background-color: linear-gradient(to bottom, #3C3C3C, #2D2D2D); + -fx-text-fill: #DCDCDC; +} + +.tab:selected { + -fx-background-color: #45A1FF; + -fx-text-fill: white; +} \ No newline at end of file