diff --git a/src/main/java/core/packetproxy/gui/GUIHistoryContextMenuFactory.java b/src/main/java/core/packetproxy/gui/GUIHistoryContextMenuFactory.java deleted file mode 100644 index 2f842191..00000000 --- a/src/main/java/core/packetproxy/gui/GUIHistoryContextMenuFactory.java +++ /dev/null @@ -1,341 +0,0 @@ -/* - * Copyright 2025 DeNA Co., Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package packetproxy.gui; - -import static packetproxy.gui.HttpPacketClipboardKt.copyMethodUrlBody; -import static packetproxy.gui.HttpPacketClipboardKt.copyUrl; -import static packetproxy.util.Logging.errWithStackTrace; - -import java.awt.Color; -import java.awt.Toolkit; -import java.awt.datatransfer.Clipboard; -import java.awt.datatransfer.StringSelection; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.KeyEvent; -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import javax.swing.JFrame; -import javax.swing.JMenuItem; -import javax.swing.JOptionPane; -import javax.swing.JPopupMenu; -import javax.swing.JTable; -import javax.swing.KeyStroke; -import org.apache.commons.io.FileUtils; -import packetproxy.controller.ResendController; -import packetproxy.http.HeaderField; -import packetproxy.http.Http; -import packetproxy.model.Packet; -import packetproxy.model.Packets; - -/** - * Extracted popup menu builder and action wiring for GUIHistory. Reduces the - * size and responsibility in GUIHistory.java. - */ -public class GUIHistoryContextMenuFactory { - - private GUIHistoryContextMenuFactory() { - } - - public static class Handles { - private final JPopupMenu menu; - private final JMenuItem send; - private final JMenuItem sendToResender; - private final JMenuItem copy; - private final JMenuItem copyAll; - - Handles(JPopupMenu menu, JMenuItem send, JMenuItem sendToResender, JMenuItem copy, JMenuItem copyAll) { - this.menu = menu; - this.send = send; - this.sendToResender = sendToResender; - this.copy = copy; - this.copyAll = copyAll; - } - - public JPopupMenu getMenu() { - return menu; - } - - public JMenuItem getSend() { - return send; - } - - public JMenuItem getSendToResender() { - return sendToResender; - } - - public JMenuItem getCopy() { - return copy; - } - - public JMenuItem getCopyAll() { - return copyAll; - } - } - - public static Handles build(GUIHistory context, JFrame owner, JTable table, GUIPacket gui_packet, Packets packets, - TableCustomColorManager colorManager, Color packetColorGreen, Color packetColorBrown, - Color packetColorYellow) { - final int mask_key = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx(); - JPopupMenu menu = new JPopupMenu(); - - JMenuItem send = createMenuItem("send", KeyEvent.VK_S, KeyStroke.getKeyStroke(KeyEvent.VK_S, mask_key), e -> { - try { - int id = context.getSelectedPacketId(); - Packet packet = packets.query(id); - byte[] data = gui_packet.getData(); - if (packet == null) { - return; - } - ResendController.getInstance().resend(packet.getOneShotPacket(data)); - packet.setResend(); - packets.update(packet); - context.updateRequestOne(context.getSelectedPacketId()); - } catch (Exception ex) { - errWithStackTrace(ex); - } - }); - - JMenuItem sendToResender = createMenuItem("send to Resender", KeyEvent.VK_R, - KeyStroke.getKeyStroke(KeyEvent.VK_R, mask_key), e -> { - try { - Packet packet = gui_packet.getPacket(); - packet.setResend(); - packets.update(packet); - if (packet.getModifiedData().length == 0) { - GUIResender.getInstance().addResends(packet.getOneShotFromDecodedData()); - } else { - GUIResender.getInstance().addResends(packet.getOneShotFromModifiedData()); - } - context.updateRequestOne(context.getSelectedPacketId()); - } catch (Exception ex) { - errWithStackTrace(ex); - } - }); - - JMenuItem copyAll = createMenuItem("copy Method + URL + Body", KeyEvent.VK_M, - KeyStroke.getKeyStroke(KeyEvent.VK_M, mask_key), e -> { - try { - Packet packet = gui_packet.getPacket(); - copyMethodUrlBody(packet.getDecodedData(), packet); - } catch (Exception ex) { - errWithStackTrace(ex); - } - }); - - JMenuItem copy = createMenuItem("copy URL", KeyEvent.VK_Y, KeyStroke.getKeyStroke(KeyEvent.VK_Y, mask_key), - e -> { - try { - int id = context.getSelectedPacketId(); - Packet packet = packets.query(id); - copyUrl(packet.getDecodedData(), packet); - } catch (Exception ex) { - errWithStackTrace(ex); - } - }); - - JMenuItem bulkSender = createMenuItem("send to Bulk Sender", -1, null, e -> { - try { - Packet packet = gui_packet.getPacket(); - if (packet.getModifiedData().length == 0) { - GUIBulkSender.getInstance().add(packet.getOneShotFromDecodedData(), packet.getId()); - } else { - GUIBulkSender.getInstance().add(packet.getOneShotFromModifiedData(), packet.getId()); - } - } catch (Exception ex) { - errWithStackTrace(ex); - } - }); - - JMenuItem saveAll = createSaveMenuItem(owner, "save all data to file", "packet.dat", - () -> gui_packet.getPacket().getReceivedData()); - - JMenuItem saveHttpBody = createSaveMenuItem(owner, "save HTTP body to file", "body.dat", - () -> Http.create(gui_packet.getPacket().getDecodedData()).getBody()); - - JMenuItem addColorG = createColorMenuItem(table, packets, colorManager, packetColorGreen, "green", - "add color (green)"); - JMenuItem addColorB = createColorMenuItem(table, packets, colorManager, packetColorBrown, "brown", - "add color (brown)"); - JMenuItem addColorY = createColorMenuItem(table, packets, colorManager, packetColorYellow, "yellow", - "add color (yellow)"); - JMenuItem clearColor = createClearColorMenuItem(table, colorManager); - - JMenuItem delete_selected_items = createMenuItem("delete selected items", -1, null, e -> { - try { - int[] selected_rows = table.getSelectedRows(); - for (int i = 0; i < selected_rows.length; i++) { - int requestPacketId = (Integer) table.getValueAt(selected_rows[i], 0); - colorManager.clear(requestPacketId); - - // マージされた行の場合、レスポンスパケットも一緒に削除する(DB残留を防ぐ) - int responsePacketId = context.getResponsePacketIdForRequest(requestPacketId); - if (responsePacketId != -1) { - colorManager.clear(responsePacketId); - packets.delete(packets.query(responsePacketId)); - } - - packets.delete(packets.query(requestPacketId)); - } - context.updateAll(); - } catch (Exception ex) { - errWithStackTrace(ex); - } - }); - - JMenuItem delete_all = createMenuItem("delete all items", -1, null, e -> { - try { - for (int i = 0; i < table.getRowCount(); ++i) { - Integer id = (Integer) table.getValueAt(i, 0); - colorManager.clear(id); - } - packets.deleteAll(); - context.updateAll(); - } catch (Exception ex) { - errWithStackTrace(ex); - } - }); - - JMenuItem copyAsCurl = createMenuItem("copy as curl", -1, null, new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - try { - Http http = Http.create(gui_packet.getPacket().getDecodedData()); - List headerFields = http.getHeader().getFields(); - ArrayList commandList = new ArrayList<>(); - commandList.add("curl"); - String url = http.getURL(gui_packet.getPacket().getServerPort(), - gui_packet.getPacket().getUseSSL()); - commandList.add(String.format("'%s'", url)); - commandList.add("-X"); - commandList.add(http.getMethod()); - for (HeaderField hf : headerFields) { - commandList.add("-H"); - commandList.add(String.format("'%s: %s'", hf.getName(), hf.getValue())); - } - String body = new String(http.getBody()); - if (body.trim().length() > 0) { - commandList.add("--data"); - commandList.add(String.format("'%s'", body)); - } - commandList.add("--compressed"); - StringSelection command = new StringSelection(String.join(" ", commandList)); - Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); - clipboard.setContents(command, command); - } catch (Exception ex) { - errWithStackTrace(ex); - } - } - }); - - menu.add(send); - menu.add(sendToResender); - menu.add(copyAll); - menu.add(copy); - menu.add(bulkSender); - menu.add(saveAll); - menu.add(saveHttpBody); - menu.add(addColorG); - menu.add(addColorB); - menu.add(addColorY); - menu.add(clearColor); - menu.add(copyAsCurl); - menu.add(delete_selected_items); - menu.add(delete_all); - - return new Handles(menu, send, sendToResender, copy, copyAll); - } - - private static JMenuItem createMenuItem(String name, int key, KeyStroke hotkey, ActionListener l) { - JMenuItem out = new JMenuItem(name); - if (key >= 0) { - out.setMnemonic(key); - } - if (hotkey != null) { - out.setAccelerator(hotkey); - } - out.addActionListener(l); - return out; - } - - @FunctionalInterface - private interface ByteArraySupplier { - byte[] get() throws Exception; - } - - private static JMenuItem createSaveMenuItem(JFrame owner, String label, String defaultFileName, - ByteArraySupplier dataSupplier) { - return createMenuItem(label, -1, null, e -> { - WriteFileChooserWrapper filechooser = new WriteFileChooserWrapper(owner, "dat", defaultFileName); - filechooser.addFileChooserListener(new WriteFileChooserWrapper.FileChooserListener() { - @Override - public void onApproved(File file, String extension) { - try { - byte[] data = dataSupplier.get(); - FileUtils.writeByteArrayToFile(file, data); - JOptionPane.showMessageDialog(owner, String.format("%sに保存しました!", file.getPath())); - } catch (Exception ex) { - errWithStackTrace(ex); - JOptionPane.showMessageDialog(null, "データの保存に失敗しました。"); - } - } - - @Override - public void onCanceled() { - } - - @Override - public void onError() { - JOptionPane.showMessageDialog(null, "データの保存に失敗しました。"); - } - }); - filechooser.showSaveDialog(); - }); - } - - private static JMenuItem createColorMenuItem(JTable table, Packets packets, TableCustomColorManager colorManager, - Color awtColor, String dbColorName, String label) { - return createMenuItem(label, -1, null, e -> { - try { - int[] selected_rows = table.getSelectedRows(); - for (int i = 0; i < selected_rows.length; i++) { - Integer id = (Integer) table.getValueAt(selected_rows[i], 0); - colorManager.add(id, awtColor); - Packet packet = packets.query(id); - packet.setColor(dbColorName); - packets.update(packet); - } - } catch (Exception ex) { - errWithStackTrace(ex); - } - }); - } - - private static JMenuItem createClearColorMenuItem(JTable table, TableCustomColorManager colorManager) { - return createMenuItem("clear color", -1, null, e -> { - try { - int[] selected_rows = table.getSelectedRows(); - for (int i = 0; i < selected_rows.length; i++) { - Integer id = (Integer) table.getValueAt(selected_rows[i], 0); - colorManager.clear(id); - } - } catch (Exception ex) { - errWithStackTrace(ex); - } - }); - } -} diff --git a/src/main/kotlin/core/packetproxy/gui/GUIHistoryContextMenuFactory.kt b/src/main/kotlin/core/packetproxy/gui/GUIHistoryContextMenuFactory.kt new file mode 100644 index 00000000..9d2f7a84 --- /dev/null +++ b/src/main/kotlin/core/packetproxy/gui/GUIHistoryContextMenuFactory.kt @@ -0,0 +1,368 @@ +/* + * Copyright 2026 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package packetproxy.gui + +import java.awt.Color +import java.awt.Toolkit +import java.awt.datatransfer.StringSelection +import java.awt.event.ActionEvent +import java.awt.event.ActionListener +import java.awt.event.KeyEvent +import java.io.File +import javax.swing.JFrame +import javax.swing.JMenuItem +import javax.swing.JOptionPane +import javax.swing.JPopupMenu +import javax.swing.JTable +import javax.swing.KeyStroke +import org.apache.commons.io.FileUtils +import packetproxy.controller.ResendController +import packetproxy.http.Http +import packetproxy.model.Packets +import packetproxy.util.Logging.errWithStackTrace + +/** + * Extracted popup menu builder and action wiring for GUIHistory. Reduces the size and + * responsibility in GUIHistory.java. + */ +object GUIHistoryContextMenuFactory { + + class Handles( + val menu: JPopupMenu, + val send: JMenuItem, + val sendToResender: JMenuItem, + val copy: JMenuItem, + val copyAll: JMenuItem, + ) + + @JvmStatic + fun build( + context: GUIHistory, + owner: JFrame, + table: JTable, + guiPacket: GUIPacket, + packets: Packets, + colorManager: TableCustomColorManager, + packetColorGreen: Color, + packetColorBrown: Color, + packetColorYellow: Color, + ): Handles { + val maskKey = Toolkit.getDefaultToolkit().menuShortcutKeyMaskEx + val menu = JPopupMenu() + + val send = + createMenuItem("send", KeyEvent.VK_S, KeyStroke.getKeyStroke(KeyEvent.VK_S, maskKey)) { + try { + val id = context.selectedPacketId + val packet = packets.query(id) + val data = guiPacket.data + if (packet == null) { + return@createMenuItem + } + ResendController.getInstance().resend(packet.getOneShotPacket(data)) + packet.setResend() + packets.update(packet) + context.updateRequestOne(context.selectedPacketId) + } catch (ex: Exception) { + errWithStackTrace(ex) + } + } + + val sendToResender = + createMenuItem( + "send to Resender", + KeyEvent.VK_R, + KeyStroke.getKeyStroke(KeyEvent.VK_R, maskKey), + ) { + try { + val packet = guiPacket.packet + packet.setResend() + packets.update(packet) + if (packet.modifiedData.isEmpty()) { + GUIResender.getInstance().addResends(packet.getOneShotFromDecodedData()) + } else { + GUIResender.getInstance().addResends(packet.getOneShotFromModifiedData()) + } + context.updateRequestOne(context.selectedPacketId) + } catch (ex: Exception) { + errWithStackTrace(ex) + } + } + + val copyAll = + createMenuItem( + "copy Method + URL + Body", + KeyEvent.VK_M, + KeyStroke.getKeyStroke(KeyEvent.VK_M, maskKey), + ) { + try { + val packet = guiPacket.packet + copyMethodUrlBody(packet.decodedData, packet) + } catch (ex: Exception) { + errWithStackTrace(ex) + } + } + + val copy = + createMenuItem("copy URL", KeyEvent.VK_Y, KeyStroke.getKeyStroke(KeyEvent.VK_Y, maskKey)) { + try { + val id = context.selectedPacketId + val packet = packets.query(id) + copyUrl(packet.decodedData, packet) + } catch (ex: Exception) { + errWithStackTrace(ex) + } + } + + val bulkSender = + createMenuItem("send to Bulk Sender", -1, null) { + try { + val packet = guiPacket.packet + if (packet.modifiedData.isEmpty()) { + GUIBulkSender.getInstance().add(packet.getOneShotFromDecodedData(), packet.id) + } else { + GUIBulkSender.getInstance().add(packet.getOneShotFromModifiedData(), packet.id) + } + } catch (ex: Exception) { + errWithStackTrace(ex) + } + } + + val saveAll = + createSaveMenuItem(owner, "save all data to file", "packet.dat") { + guiPacket.packet.receivedData + } + + val saveHttpBody = + createSaveMenuItem(owner, "save HTTP body to file", "body.dat") { + Http.create(guiPacket.packet.decodedData).body + } + + val addColorG = + createColorMenuItem( + table, + packets, + colorManager, + packetColorGreen, + "green", + "add color (green)", + ) + val addColorB = + createColorMenuItem( + table, + packets, + colorManager, + packetColorBrown, + "brown", + "add color (brown)", + ) + val addColorY = + createColorMenuItem( + table, + packets, + colorManager, + packetColorYellow, + "yellow", + "add color (yellow)", + ) + val clearColor = createClearColorMenuItem(table, colorManager) + + val deleteSelectedItems = + createMenuItem("delete selected items", -1, null) { + try { + val selectedRows = table.selectedRows + for (i in selectedRows.indices) { + val requestPacketId = table.getValueAt(selectedRows[i], 0) as Int + colorManager.clear(requestPacketId) + + // マージされた行の場合、レスポンスパケットも一緒に削除する(DB残留を防ぐ) + val responsePacketId = context.getResponsePacketIdForRequest(requestPacketId) + if (responsePacketId != -1) { + colorManager.clear(responsePacketId) + packets.delete(packets.query(responsePacketId)) + } + + packets.delete(packets.query(requestPacketId)) + } + context.updateAll() + } catch (ex: Exception) { + errWithStackTrace(ex) + } + } + + val deleteAll = + createMenuItem("delete all items", -1, null) { + try { + for (i in 0 until table.rowCount) { + val id = table.getValueAt(i, 0) as Int + colorManager.clear(id) + } + packets.deleteAll() + context.updateAll() + } catch (ex: Exception) { + errWithStackTrace(ex) + } + } + + val copyAsCurl = + createMenuItem( + "copy as curl", + -1, + null, + ActionListener { + try { + val http = Http.create(guiPacket.packet.decodedData) + val headerFields = http.header.fields + val commandList = ArrayList() + commandList.add("curl") + val url = http.getURL(guiPacket.packet.serverPort, guiPacket.packet.useSSL) + commandList.add(String.format("'%s'", url)) + commandList.add("-X") + commandList.add(http.method) + for (hf in headerFields) { + commandList.add("-H") + commandList.add(String.format("'%s: %s'", hf.name, hf.value)) + } + val body = String(http.body) + if (body.trim().isNotEmpty()) { + commandList.add("--data") + commandList.add(String.format("'%s'", body)) + } + commandList.add("--compressed") + val command = StringSelection(commandList.joinToString(" ")) + val clipboard = Toolkit.getDefaultToolkit().systemClipboard + clipboard.setContents(command, command) + } catch (ex: Exception) { + errWithStackTrace(ex) + } + }, + ) + + menu.add(send) + menu.add(sendToResender) + menu.add(copyAll) + menu.add(copy) + menu.add(bulkSender) + menu.add(saveAll) + menu.add(saveHttpBody) + menu.add(addColorG) + menu.add(addColorB) + menu.add(addColorY) + menu.add(clearColor) + menu.add(copyAsCurl) + menu.add(deleteSelectedItems) + menu.add(deleteAll) + + return Handles(menu, send, sendToResender, copy, copyAll) + } + + private fun createMenuItem( + name: String, + key: Int, + hotkey: KeyStroke?, + listener: ActionListener, + ): JMenuItem { + val out = JMenuItem(name) + if (key >= 0) { + out.mnemonic = key + } + if (hotkey != null) { + out.accelerator = hotkey + } + out.addActionListener(listener) + return out + } + + private fun createMenuItem( + name: String, + key: Int, + hotkey: KeyStroke?, + listener: (ActionEvent) -> Unit, + ): JMenuItem = createMenuItem(name, key, hotkey, ActionListener(listener)) + + private fun createSaveMenuItem( + owner: JFrame, + label: String, + defaultFileName: String, + dataSupplier: () -> ByteArray, + ): JMenuItem { + return createMenuItem(label, -1, null) { + val filechooser = WriteFileChooserWrapper(owner, "dat", defaultFileName) + filechooser.addFileChooserListener( + object : WriteFileChooserWrapper.FileChooserListener { + override fun onApproved(file: File, extension: String) { + try { + val data = dataSupplier() + FileUtils.writeByteArrayToFile(file, data) + JOptionPane.showMessageDialog(owner, String.format("%sに保存しました!", file.path)) + } catch (ex: Exception) { + errWithStackTrace(ex) + JOptionPane.showMessageDialog(null, "データの保存に失敗しました。") + } + } + + override fun onCanceled() {} + + override fun onError() { + JOptionPane.showMessageDialog(null, "データの保存に失敗しました。") + } + } + ) + filechooser.showSaveDialog() + } + } + + private fun createColorMenuItem( + table: JTable, + packets: Packets, + colorManager: TableCustomColorManager, + awtColor: Color, + dbColorName: String, + label: String, + ): JMenuItem { + return createMenuItem(label, -1, null) { + try { + val selectedRows = table.selectedRows + for (i in selectedRows.indices) { + val id = table.getValueAt(selectedRows[i], 0) as Int + colorManager.add(id, awtColor) + val packet = packets.query(id) + packet.color = dbColorName + packets.update(packet) + } + } catch (ex: Exception) { + errWithStackTrace(ex) + } + } + } + + private fun createClearColorMenuItem( + table: JTable, + colorManager: TableCustomColorManager, + ): JMenuItem { + return createMenuItem("clear color", -1, null) { + try { + val selectedRows = table.selectedRows + for (i in selectedRows.indices) { + val id = table.getValueAt(selectedRows[i], 0) as Int + colorManager.clear(id) + } + } catch (ex: Exception) { + errWithStackTrace(ex) + } + } + } +}