001/** 002 * 003 * Copyright 2003-2007 Jive Software. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.jivesoftware.smackx.debugger; 019 020import java.awt.BorderLayout; 021import java.awt.Color; 022import java.awt.GridBagConstraints; 023import java.awt.GridBagLayout; 024import java.awt.GridLayout; 025import java.awt.Insets; 026import java.awt.Toolkit; 027import java.awt.datatransfer.Clipboard; 028import java.awt.datatransfer.StringSelection; 029import java.awt.event.ActionEvent; 030import java.awt.event.ActionListener; 031import java.awt.event.MouseAdapter; 032import java.awt.event.MouseEvent; 033import java.io.Reader; 034import java.io.StringReader; 035import java.io.StringWriter; 036import java.io.Writer; 037import java.net.URL; 038import java.text.SimpleDateFormat; 039import java.util.Date; 040import java.util.logging.Level; 041import java.util.logging.Logger; 042 043import javax.swing.AbstractAction; 044import javax.swing.BorderFactory; 045import javax.swing.Icon; 046import javax.swing.ImageIcon; 047import javax.swing.JButton; 048import javax.swing.JFormattedTextField; 049import javax.swing.JLabel; 050import javax.swing.JMenuItem; 051import javax.swing.JPanel; 052import javax.swing.JPopupMenu; 053import javax.swing.JScrollPane; 054import javax.swing.JSplitPane; 055import javax.swing.JTabbedPane; 056import javax.swing.JTable; 057import javax.swing.JTextArea; 058import javax.swing.ListSelectionModel; 059import javax.swing.SwingUtilities; 060import javax.swing.event.ListSelectionEvent; 061import javax.swing.event.ListSelectionListener; 062import javax.swing.table.DefaultTableModel; 063import javax.swing.text.BadLocationException; 064import javax.xml.transform.OutputKeys; 065import javax.xml.transform.Transformer; 066import javax.xml.transform.TransformerConfigurationException; 067import javax.xml.transform.TransformerException; 068import javax.xml.transform.TransformerFactory; 069import javax.xml.transform.stream.StreamResult; 070import javax.xml.transform.stream.StreamSource; 071 072import org.jivesoftware.smack.AbstractConnectionListener; 073import org.jivesoftware.smack.AbstractXMPPConnection; 074import org.jivesoftware.smack.ConnectionListener; 075import org.jivesoftware.smack.ReconnectionListener; 076import org.jivesoftware.smack.ReconnectionManager; 077import org.jivesoftware.smack.SmackException.NotConnectedException; 078import org.jivesoftware.smack.StanzaListener; 079import org.jivesoftware.smack.XMPPConnection; 080import org.jivesoftware.smack.debugger.SmackDebugger; 081import org.jivesoftware.smack.packet.IQ; 082import org.jivesoftware.smack.packet.Message; 083import org.jivesoftware.smack.packet.Presence; 084import org.jivesoftware.smack.packet.Stanza; 085import org.jivesoftware.smack.util.ObservableReader; 086import org.jivesoftware.smack.util.ObservableWriter; 087import org.jivesoftware.smack.util.ReaderListener; 088import org.jivesoftware.smack.util.StringUtils; 089import org.jivesoftware.smack.util.WriterListener; 090 091import org.jxmpp.jid.EntityFullJid; 092import org.jxmpp.jid.Jid; 093 094/** 095 * The EnhancedDebugger is a debugger that allows to debug sent, received and interpreted messages 096 * but also provides the ability to send ad-hoc messages composed by the user.<p> 097 * <p/> 098 * A new EnhancedDebugger will be created for each connection to debug. All the EnhancedDebuggers 099 * will be shown in the same debug window provided by the class EnhancedDebuggerWindow. 100 * 101 * @author Gaston Dombiak 102 */ 103public class EnhancedDebugger implements SmackDebugger { 104 105 private static final Logger LOGGER = Logger.getLogger(EnhancedDebugger.class.getName()); 106 107 private static final String NEWLINE = "\n"; 108 109 private static ImageIcon packetReceivedIcon; 110 private static ImageIcon packetSentIcon; 111 private static ImageIcon presencePacketIcon; 112 private static ImageIcon iqPacketIcon; 113 private static ImageIcon messagePacketIcon; 114 private static ImageIcon unknownPacketTypeIcon; 115 116 { 117 URL url; 118 // Load the image icons 119 url = 120 Thread.currentThread().getContextClassLoader().getResource("images/nav_left_blue.png"); 121 if (url != null) { 122 packetReceivedIcon = new ImageIcon(url); 123 } 124 url = 125 Thread.currentThread().getContextClassLoader().getResource("images/nav_right_red.png"); 126 if (url != null) { 127 packetSentIcon = new ImageIcon(url); 128 } 129 url = 130 Thread.currentThread().getContextClassLoader().getResource("images/photo_portrait.png"); 131 if (url != null) { 132 presencePacketIcon = new ImageIcon(url); 133 } 134 url = 135 Thread.currentThread().getContextClassLoader().getResource( 136 "images/question_and_answer.png"); 137 if (url != null) { 138 iqPacketIcon = new ImageIcon(url); 139 } 140 url = Thread.currentThread().getContextClassLoader().getResource("images/message.png"); 141 if (url != null) { 142 messagePacketIcon = new ImageIcon(url); 143 } 144 url = Thread.currentThread().getContextClassLoader().getResource("images/unknown.png"); 145 if (url != null) { 146 unknownPacketTypeIcon = new ImageIcon(url); 147 } 148 } 149 150 private DefaultTableModel messagesTable = null; 151 private JTextArea messageTextArea = null; 152 private JFormattedTextField userField = null; 153 private JFormattedTextField statusField = null; 154 155 private XMPPConnection connection = null; 156 157 private StanzaListener packetReaderListener = null; 158 private StanzaListener packetWriterListener = null; 159 private ConnectionListener connListener = null; 160 private final ReconnectionListener reconnectionListener; 161 162 private Writer writer; 163 private Reader reader; 164 private ReaderListener readerListener; 165 private WriterListener writerListener; 166 167 private Date creationTime = new Date(); 168 169 // Statistics variables 170 private DefaultTableModel statisticsTable = null; 171 private int sentPackets = 0; 172 private int receivedPackets = 0; 173 private int sentIQPackets = 0; 174 private int receivedIQPackets = 0; 175 private int sentMessagePackets = 0; 176 private int receivedMessagePackets = 0; 177 private int sentPresencePackets = 0; 178 private int receivedPresencePackets = 0; 179 private int sentOtherPackets = 0; 180 private int receivedOtherPackets = 0; 181 182 JTabbedPane tabbedPane; 183 184 public EnhancedDebugger(XMPPConnection connection, Writer writer, Reader reader) { 185 this.connection = connection; 186 this.writer = writer; 187 this.reader = reader; 188 createDebug(); 189 EnhancedDebuggerWindow.addDebugger(this); 190 191 reconnectionListener = new ReconnectionListener() { 192 @Override 193 public void reconnectingIn(final int seconds) { 194 SwingUtilities.invokeLater(new Runnable() { 195 @Override 196 public void run() { 197 statusField.setValue("Attempt to reconnect in " + seconds + " seconds"); 198 } 199 }); 200 } 201 202 @Override 203 public void reconnectionFailed(Exception e) { 204 SwingUtilities.invokeLater(new Runnable() { 205 @Override 206 public void run() { 207 statusField.setValue("Reconnection failed"); 208 } 209 }); 210 } 211 }; 212 213 if (connection instanceof AbstractXMPPConnection) { 214 AbstractXMPPConnection abstractXmppConnection = (AbstractXMPPConnection) connection; 215 ReconnectionManager.getInstanceFor(abstractXmppConnection).addReconnectionListener(reconnectionListener); 216 } else { 217 LOGGER.info("The connection instance " + connection 218 + " is not an instance of AbstractXMPPConnection, thus we can not install the ReconnectionListener"); 219 } 220 } 221 222 /** 223 * Creates the debug process, which is a GUI window that displays XML traffic. 224 */ 225 private void createDebug() { 226 // We'll arrange the UI into six tabs. The first tab contains all data, the second 227 // client generated XML, the third server generated XML, the fourth allows to send 228 // ad-hoc messages and the fifth contains connection information. 229 tabbedPane = new JTabbedPane(); 230 231 // Add the All Packets, Sent, Received and Interpreted panels 232 addBasicPanels(); 233 234 // Add the panel to send ad-hoc messages 235 addAdhocPacketPanel(); 236 237 // Add the connection information panel 238 addInformationPanel(); 239 240 // Create a thread that will listen for all incoming packets and write them to 241 // the GUI. This is what we call "interpreted" packet data, since it's the packet 242 // data as Smack sees it and not as it's coming in as raw XML. 243 packetReaderListener = new StanzaListener() { 244 SimpleDateFormat dateFormatter = new SimpleDateFormat("HH:mm:ss:SS"); 245 246 @Override 247 public void processStanza(final Stanza packet) { 248 SwingUtilities.invokeLater(new Runnable() { 249 @Override 250 public void run() { 251 addReadPacketToTable(dateFormatter, packet); 252 } 253 }); 254 255 } 256 }; 257 258 // Create a thread that will listen for all outgoing packets and write them to 259 // the GUI. 260 packetWriterListener = new StanzaListener() { 261 SimpleDateFormat dateFormatter = new SimpleDateFormat("HH:mm:ss:SS"); 262 263 @Override 264 public void processStanza(final Stanza packet) { 265 SwingUtilities.invokeLater(new Runnable() { 266 @Override 267 public void run() { 268 addSentPacketToTable(dateFormatter, packet); 269 } 270 }); 271 272 } 273 }; 274 275 // Create a thread that will listen for any connection closed event 276 connListener = new AbstractConnectionListener() { 277 @Override 278 public void connectionClosed() { 279 SwingUtilities.invokeLater(new Runnable() { 280 @Override 281 public void run() { 282 statusField.setValue("Closed"); 283 EnhancedDebuggerWindow.connectionClosed(EnhancedDebugger.this); 284 } 285 }); 286 287 } 288 289 @Override 290 public void connectionClosedOnError(final Exception e) { 291 SwingUtilities.invokeLater(new Runnable() { 292 @Override 293 public void run() { 294 statusField.setValue("Closed due to an exception"); 295 EnhancedDebuggerWindow.connectionClosedOnError(EnhancedDebugger.this, e); 296 } 297 }); 298 299 } 300 }; 301 302 } 303 304 private void addBasicPanels() { 305 JSplitPane allPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); 306 allPane.setOneTouchExpandable(true); 307 308 messagesTable = 309 new DefaultTableModel( 310 new Object[] {"Hide", "Timestamp", "", "", "Message", "Id", "Type", "To", "From"}, 311 0) { 312 // CHECKSTYLE:OFF 313 private static final long serialVersionUID = 8136121224474217264L; 314 @Override 315 public boolean isCellEditable(int rowIndex, int mColIndex) { 316 // CHECKSTYLE:ON 317 return false; 318 } 319 320 @Override 321 public Class<?> getColumnClass(int columnIndex) { 322 if (columnIndex == 2 || columnIndex == 3) { 323 return Icon.class; 324 } 325 return super.getColumnClass(columnIndex); 326 } 327 328 }; 329 JTable table = new JTable(messagesTable); 330 // Allow only single a selection 331 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 332 // Hide the first column 333 table.getColumnModel().getColumn(0).setMaxWidth(0); 334 table.getColumnModel().getColumn(0).setMinWidth(0); 335 table.getTableHeader().getColumnModel().getColumn(0).setMaxWidth(0); 336 table.getTableHeader().getColumnModel().getColumn(0).setMinWidth(0); 337 // Set the column "timestamp" size 338 table.getColumnModel().getColumn(1).setMaxWidth(300); 339 table.getColumnModel().getColumn(1).setPreferredWidth(90); 340 // Set the column "direction" icon size 341 table.getColumnModel().getColumn(2).setMaxWidth(50); 342 table.getColumnModel().getColumn(2).setPreferredWidth(30); 343 // Set the column "packet type" icon size 344 table.getColumnModel().getColumn(3).setMaxWidth(50); 345 table.getColumnModel().getColumn(3).setPreferredWidth(30); 346 // Set the column "Id" size 347 table.getColumnModel().getColumn(5).setMaxWidth(100); 348 table.getColumnModel().getColumn(5).setPreferredWidth(55); 349 // Set the column "type" size 350 table.getColumnModel().getColumn(6).setMaxWidth(200); 351 table.getColumnModel().getColumn(6).setPreferredWidth(50); 352 // Set the column "to" size 353 table.getColumnModel().getColumn(7).setMaxWidth(300); 354 table.getColumnModel().getColumn(7).setPreferredWidth(90); 355 // Set the column "from" size 356 table.getColumnModel().getColumn(8).setMaxWidth(300); 357 table.getColumnModel().getColumn(8).setPreferredWidth(90); 358 // Create a table listener that listen for row selection events 359 SelectionListener selectionListener = new SelectionListener(table); 360 table.getSelectionModel().addListSelectionListener(selectionListener); 361 table.getColumnModel().getSelectionModel().addListSelectionListener(selectionListener); 362 allPane.setTopComponent(new JScrollPane(table)); 363 messageTextArea = new JTextArea(); 364 messageTextArea.setEditable(false); 365 // Add pop-up menu. 366 JPopupMenu menu = new JPopupMenu(); 367 JMenuItem menuItem1 = new JMenuItem("Copy"); 368 menuItem1.addActionListener(new ActionListener() { 369 @Override 370 public void actionPerformed(ActionEvent e) { 371 // Get the clipboard 372 Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); 373 // Set the sent text as the new content of the clipboard 374 clipboard.setContents(new StringSelection(messageTextArea.getText()), null); 375 } 376 }); 377 menu.add(menuItem1); 378 // Add listener to the text area so the popup menu can come up. 379 messageTextArea.addMouseListener(new PopupListener(menu)); 380 // CHECKSTYLE:OFF 381 JPanel sublayout = new JPanel(new BorderLayout()); 382 sublayout.add(new JScrollPane(messageTextArea), BorderLayout.CENTER); 383 384 JButton clearb = new JButton("Clear All Packets"); 385 386 clearb.addActionListener(new AbstractAction() { 387 private static final long serialVersionUID = -8576045822764763613L; 388 389 @Override 390 public void actionPerformed(ActionEvent e) { 391 messagesTable.setRowCount(0); 392 } 393 }); 394 // CHECKSTYLE:ON 395 396 sublayout.add(clearb, BorderLayout.NORTH); 397 allPane.setBottomComponent(sublayout); 398 399 allPane.setDividerLocation(150); 400 401 tabbedPane.add("All Packets", allPane); 402 tabbedPane.setToolTipTextAt(0, "Sent and received packets processed by Smack"); 403 404 // Create UI elements for client generated XML traffic. 405 final JTextArea sentText = new JTextArea(); 406 sentText.setWrapStyleWord(true); 407 sentText.setLineWrap(true); 408 sentText.setEditable(false); 409 sentText.setForeground(new Color(112, 3, 3)); 410 tabbedPane.add("Raw Sent Packets", new JScrollPane(sentText)); 411 tabbedPane.setToolTipTextAt(1, "Raw text of the sent packets"); 412 413 // Add pop-up menu. 414 menu = new JPopupMenu(); 415 menuItem1 = new JMenuItem("Copy"); 416 menuItem1.addActionListener(new ActionListener() { 417 @Override 418 public void actionPerformed(ActionEvent e) { 419 // Get the clipboard 420 Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); 421 // Set the sent text as the new content of the clipboard 422 clipboard.setContents(new StringSelection(sentText.getText()), null); 423 } 424 }); 425 426 JMenuItem menuItem2 = new JMenuItem("Clear"); 427 menuItem2.addActionListener(new ActionListener() { 428 @Override 429 public void actionPerformed(ActionEvent e) { 430 sentText.setText(""); 431 } 432 }); 433 434 // Add listener to the text area so the popup menu can come up. 435 sentText.addMouseListener(new PopupListener(menu)); 436 menu.add(menuItem1); 437 menu.add(menuItem2); 438 439 // Create UI elements for server generated XML traffic. 440 final JTextArea receivedText = new JTextArea(); 441 receivedText.setWrapStyleWord(true); 442 receivedText.setLineWrap(true); 443 receivedText.setEditable(false); 444 receivedText.setForeground(new Color(6, 76, 133)); 445 tabbedPane.add("Raw Received Packets", new JScrollPane(receivedText)); 446 tabbedPane.setToolTipTextAt( 447 2, 448 "Raw text of the received packets before Smack process them"); 449 450 // Add pop-up menu. 451 menu = new JPopupMenu(); 452 menuItem1 = new JMenuItem("Copy"); 453 menuItem1.addActionListener(new ActionListener() { 454 @Override 455 public void actionPerformed(ActionEvent e) { 456 // Get the clipboard 457 Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); 458 // Set the sent text as the new content of the clipboard 459 clipboard.setContents(new StringSelection(receivedText.getText()), null); 460 } 461 }); 462 463 menuItem2 = new JMenuItem("Clear"); 464 menuItem2.addActionListener(new ActionListener() { 465 @Override 466 public void actionPerformed(ActionEvent e) { 467 receivedText.setText(""); 468 } 469 }); 470 471 // Add listener to the text area so the popup menu can come up. 472 receivedText.addMouseListener(new PopupListener(menu)); 473 menu.add(menuItem1); 474 menu.add(menuItem2); 475 476 // Create a special Reader that wraps the main Reader and logs data to the GUI. 477 ObservableReader debugReader = new ObservableReader(reader); 478 readerListener = new ReaderListener() { 479 @Override 480 public void read(final String str) { 481 SwingUtilities.invokeLater(new Runnable() { 482 @Override 483 public void run() { 484 if (EnhancedDebuggerWindow.PERSISTED_DEBUGGER && 485 !EnhancedDebuggerWindow.getInstance().isVisible()) { 486 // Do not add content if the parent is not visible 487 return; 488 } 489 490 int index = str.lastIndexOf(">"); 491 if (index != -1) { 492 if (receivedText.getLineCount() >= EnhancedDebuggerWindow.MAX_TABLE_ROWS) 493 { 494 try { 495 receivedText.replaceRange("", 0, receivedText.getLineEndOffset(0)); 496 } 497 catch (BadLocationException e) { 498 LOGGER.log(Level.SEVERE, "Error with line offset, MAX_TABLE_ROWS is set too low: " + EnhancedDebuggerWindow.MAX_TABLE_ROWS, e); 499 } 500 } 501 receivedText.append(str.substring(0, index + 1)); 502 receivedText.append(NEWLINE); 503 if (str.length() > index) { 504 receivedText.append(str.substring(index + 1)); 505 } 506 } 507 else { 508 receivedText.append(str); 509 } 510 } 511 }); 512 } 513 }; 514 debugReader.addReaderListener(readerListener); 515 516 // Create a special Writer that wraps the main Writer and logs data to the GUI. 517 ObservableWriter debugWriter = new ObservableWriter(writer); 518 writerListener = new WriterListener() { 519 @Override 520 public void write(final String str) { 521 SwingUtilities.invokeLater(new Runnable() { 522 @Override 523 public void run() { 524 if (EnhancedDebuggerWindow.PERSISTED_DEBUGGER && 525 !EnhancedDebuggerWindow.getInstance().isVisible()) { 526 // Do not add content if the parent is not visible 527 return; 528 } 529 530 if (sentText.getLineCount() >= EnhancedDebuggerWindow.MAX_TABLE_ROWS) { 531 try { 532 sentText.replaceRange("", 0, sentText.getLineEndOffset(0)); 533 } 534 catch (BadLocationException e) { 535 LOGGER.log(Level.SEVERE, "Error with line offset, MAX_TABLE_ROWS is set too low: " + EnhancedDebuggerWindow.MAX_TABLE_ROWS, e); 536 } 537 } 538 539 sentText.append(str); 540 if (str.endsWith(">")) { 541 sentText.append(NEWLINE); 542 } 543 } 544 }); 545 546 547 } 548 }; 549 debugWriter.addWriterListener(writerListener); 550 551 // Assign the reader/writer objects to use the debug versions. The packet reader 552 // and writer will use the debug versions when they are created. 553 reader = debugReader; 554 writer = debugWriter; 555 556 } 557 558 private void addAdhocPacketPanel() { 559 // Create UI elements for sending ad-hoc messages. 560 final JTextArea adhocMessages = new JTextArea(); 561 adhocMessages.setEditable(true); 562 adhocMessages.setForeground(new Color(1, 94, 35)); 563 tabbedPane.add("Ad-hoc message", new JScrollPane(adhocMessages)); 564 tabbedPane.setToolTipTextAt(3, "Panel that allows you to send adhoc packets"); 565 566 // Add pop-up menu. 567 JPopupMenu menu = new JPopupMenu(); 568 JMenuItem menuItem = new JMenuItem("Message"); 569 menuItem.addActionListener(new ActionListener() { 570 @Override 571 public void actionPerformed(ActionEvent e) { 572 adhocMessages.setText( 573 "<message to=\"\" id=\"" 574 + StringUtils.randomString(5) 575 + "-X\"><body></body></message>"); 576 } 577 }); 578 menu.add(menuItem); 579 580 menuItem = new JMenuItem("IQ Get"); 581 menuItem.addActionListener(new ActionListener() { 582 @Override 583 public void actionPerformed(ActionEvent e) { 584 adhocMessages.setText( 585 "<iq type=\"get\" to=\"\" id=\"" 586 + StringUtils.randomString(5) 587 + "-X\"><query xmlns=\"\"></query></iq>"); 588 } 589 }); 590 menu.add(menuItem); 591 592 menuItem = new JMenuItem("IQ Set"); 593 menuItem.addActionListener(new ActionListener() { 594 @Override 595 public void actionPerformed(ActionEvent e) { 596 adhocMessages.setText( 597 "<iq type=\"set\" to=\"\" id=\"" 598 + StringUtils.randomString(5) 599 + "-X\"><query xmlns=\"\"></query></iq>"); 600 } 601 }); 602 menu.add(menuItem); 603 604 menuItem = new JMenuItem("Presence"); 605 menuItem.addActionListener(new ActionListener() { 606 @Override 607 public void actionPerformed(ActionEvent e) { 608 adhocMessages.setText( 609 "<presence to=\"\" id=\"" + StringUtils.randomString(5) + "-X\"/>"); 610 } 611 }); 612 menu.add(menuItem); 613 menu.addSeparator(); 614 615 menuItem = new JMenuItem("Send"); 616 menuItem.addActionListener(new ActionListener() { 617 @Override 618 public void actionPerformed(ActionEvent e) { 619 if (!"".equals(adhocMessages.getText())) { 620 AdHocPacket packetToSend = new AdHocPacket(adhocMessages.getText()); 621 try { 622 connection.sendStanza(packetToSend); 623 } 624 catch (InterruptedException | NotConnectedException e1) { 625 LOGGER.log(Level.WARNING, "exception", e); 626 } 627 } 628 } 629 }); 630 menu.add(menuItem); 631 632 menuItem = new JMenuItem("Clear"); 633 menuItem.addActionListener(new ActionListener() { 634 @Override 635 public void actionPerformed(ActionEvent e) { 636 adhocMessages.setText(null); 637 } 638 }); 639 menu.add(menuItem); 640 641 // Add listener to the text area so the popup menu can come up. 642 adhocMessages.addMouseListener(new PopupListener(menu)); 643 } 644 645 private void addInformationPanel() { 646 // Create UI elements for connection information. 647 JPanel informationPanel = new JPanel(); 648 informationPanel.setLayout(new BorderLayout()); 649 650 // Add the Host information 651 JPanel connPanel = new JPanel(); 652 connPanel.setLayout(new GridBagLayout()); 653 connPanel.setBorder(BorderFactory.createTitledBorder("XMPPConnection information")); 654 655 JLabel label = new JLabel("Host: "); 656 label.setMinimumSize(new java.awt.Dimension(150, 14)); 657 label.setMaximumSize(new java.awt.Dimension(150, 14)); 658 connPanel.add( 659 label, 660 new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, 21, 0, new Insets(0, 0, 0, 0), 0, 0)); 661 JFormattedTextField field = new JFormattedTextField(connection.getXMPPServiceDomain()); 662 field.setMinimumSize(new java.awt.Dimension(150, 20)); 663 field.setMaximumSize(new java.awt.Dimension(150, 20)); 664 field.setEditable(false); 665 field.setBorder(null); 666 connPanel.add( 667 field, 668 new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, 10, 2, new Insets(0, 0, 0, 0), 0, 0)); 669 670 // Add the Port information 671 label = new JLabel("Port: "); 672 label.setMinimumSize(new java.awt.Dimension(150, 14)); 673 label.setMaximumSize(new java.awt.Dimension(150, 14)); 674 connPanel.add( 675 label, 676 new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, 21, 0, new Insets(0, 0, 0, 0), 0, 0)); 677 field = new JFormattedTextField(connection.getPort()); 678 field.setMinimumSize(new java.awt.Dimension(150, 20)); 679 field.setMaximumSize(new java.awt.Dimension(150, 20)); 680 field.setEditable(false); 681 field.setBorder(null); 682 connPanel.add( 683 field, 684 new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, 10, 2, new Insets(0, 0, 0, 0), 0, 0)); 685 686 // Add the connection's User information 687 label = new JLabel("User: "); 688 label.setMinimumSize(new java.awt.Dimension(150, 14)); 689 label.setMaximumSize(new java.awt.Dimension(150, 14)); 690 connPanel.add( 691 label, 692 new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, 21, 0, new Insets(0, 0, 0, 0), 0, 0)); 693 userField = new JFormattedTextField(); 694 userField.setMinimumSize(new java.awt.Dimension(150, 20)); 695 userField.setMaximumSize(new java.awt.Dimension(150, 20)); 696 userField.setEditable(false); 697 userField.setBorder(null); 698 connPanel.add( 699 userField, 700 new GridBagConstraints(1, 2, 1, 1, 0.0, 0.0, 10, 2, new Insets(0, 0, 0, 0), 0, 0)); 701 702 // Add the connection's creationTime information 703 label = new JLabel("Creation time: "); 704 label.setMinimumSize(new java.awt.Dimension(150, 14)); 705 label.setMaximumSize(new java.awt.Dimension(150, 14)); 706 connPanel.add( 707 label, 708 new GridBagConstraints(0, 3, 1, 1, 0.0, 0.0, 21, 0, new Insets(0, 0, 0, 0), 0, 0)); 709 field = new JFormattedTextField(new SimpleDateFormat("yyyy.MM.dd HH:mm:ss:SS")); 710 field.setMinimumSize(new java.awt.Dimension(150, 20)); 711 field.setMaximumSize(new java.awt.Dimension(150, 20)); 712 field.setValue(creationTime); 713 field.setEditable(false); 714 field.setBorder(null); 715 connPanel.add( 716 field, 717 new GridBagConstraints(1, 3, 1, 1, 0.0, 0.0, 10, 2, new Insets(0, 0, 0, 0), 0, 0)); 718 719 // Add the connection's creationTime information 720 label = new JLabel("Status: "); 721 label.setMinimumSize(new java.awt.Dimension(150, 14)); 722 label.setMaximumSize(new java.awt.Dimension(150, 14)); 723 connPanel.add( 724 label, 725 new GridBagConstraints(0, 4, 1, 1, 0.0, 0.0, 21, 0, new Insets(0, 0, 0, 0), 0, 0)); 726 statusField = new JFormattedTextField(); 727 statusField.setMinimumSize(new java.awt.Dimension(150, 20)); 728 statusField.setMaximumSize(new java.awt.Dimension(150, 20)); 729 statusField.setValue("Active"); 730 statusField.setEditable(false); 731 statusField.setBorder(null); 732 connPanel.add( 733 statusField, 734 new GridBagConstraints(1, 4, 1, 1, 0.0, 0.0, 10, 2, new Insets(0, 0, 0, 0), 0, 0)); 735 // Add the connection panel to the information panel 736 informationPanel.add(connPanel, BorderLayout.NORTH); 737 738 // Add the Number of sent packets information 739 JPanel packetsPanel = new JPanel(); 740 packetsPanel.setLayout(new GridLayout(1, 1)); 741 packetsPanel.setBorder(BorderFactory.createTitledBorder("Transmitted Packets")); 742 743 statisticsTable = 744 new DefaultTableModel(new Object[][] { {"IQ", 0, 0}, {"Message", 0, 0}, 745 {"Presence", 0, 0}, {"Other", 0, 0}, {"Total", 0, 0}}, 746 new Object[] {"Type", "Received", "Sent"}) { 747 // CHECKSTYLE:OFF 748 private static final long serialVersionUID = -6793886085109589269L; 749 @Override 750 public boolean isCellEditable(int rowIndex, int mColIndex) { 751 // CHECKSTYLE:ON 752 return false; 753 } 754 }; 755 JTable table = new JTable(statisticsTable); 756 // Allow only single a selection 757 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 758 packetsPanel.add(new JScrollPane(table)); 759 760 // Add the packets panel to the information panel 761 informationPanel.add(packetsPanel, BorderLayout.CENTER); 762 763 tabbedPane.add("Information", new JScrollPane(informationPanel)); 764 tabbedPane.setToolTipTextAt(4, "Information and statistics about the debugged connection"); 765 } 766 767 @Override 768 public Reader newConnectionReader(Reader newReader) { 769 ((ObservableReader) reader).removeReaderListener(readerListener); 770 ObservableReader debugReader = new ObservableReader(newReader); 771 debugReader.addReaderListener(readerListener); 772 reader = debugReader; 773 return reader; 774 } 775 776 @Override 777 public Writer newConnectionWriter(Writer newWriter) { 778 ((ObservableWriter) writer).removeWriterListener(writerListener); 779 ObservableWriter debugWriter = new ObservableWriter(newWriter); 780 debugWriter.addWriterListener(writerListener); 781 writer = debugWriter; 782 return writer; 783 } 784 785 @Override 786 public void userHasLogged(final EntityFullJid user) { 787 final EnhancedDebugger debugger = this; 788 SwingUtilities.invokeLater(new Runnable() { 789 @Override 790 public void run() { 791 userField.setText(user.toString()); 792 EnhancedDebuggerWindow.userHasLogged(debugger, user.toString()); 793 // Add the connection listener to the connection so that the debugger can be notified 794 // whenever the connection is closed. 795 connection.addConnectionListener(connListener); 796 } 797 }); 798 799 } 800 801 @Override 802 public Reader getReader() { 803 return reader; 804 } 805 806 @Override 807 public Writer getWriter() { 808 return writer; 809 } 810 811 @Override 812 public StanzaListener getReaderListener() { 813 return packetReaderListener; 814 } 815 816 @Override 817 public StanzaListener getWriterListener() { 818 return packetWriterListener; 819 } 820 821 /** 822 * Updates the statistics table 823 */ 824 private void updateStatistics() { 825 statisticsTable.setValueAt(Integer.valueOf(receivedIQPackets), 0, 1); 826 statisticsTable.setValueAt(Integer.valueOf(sentIQPackets), 0, 2); 827 828 statisticsTable.setValueAt(Integer.valueOf(receivedMessagePackets), 1, 1); 829 statisticsTable.setValueAt(Integer.valueOf(sentMessagePackets), 1, 2); 830 831 statisticsTable.setValueAt(Integer.valueOf(receivedPresencePackets), 2, 1); 832 statisticsTable.setValueAt(Integer.valueOf(sentPresencePackets), 2, 2); 833 834 statisticsTable.setValueAt(Integer.valueOf(receivedOtherPackets), 3, 1); 835 statisticsTable.setValueAt(Integer.valueOf(sentOtherPackets), 3, 2); 836 837 statisticsTable.setValueAt(Integer.valueOf(receivedPackets), 4, 1); 838 statisticsTable.setValueAt(Integer.valueOf(sentPackets), 4, 2); 839 } 840 841 /** 842 * Adds the received stanza(/packet) detail to the messages table. 843 * 844 * @param dateFormatter the SimpleDateFormat to use to format Dates 845 * @param packet the read stanza(/packet) to add to the table 846 */ 847 private void addReadPacketToTable(final SimpleDateFormat dateFormatter, final Stanza packet) { 848 SwingUtilities.invokeLater(new Runnable() { 849 @Override 850 public void run() { 851 String messageType; 852 Jid from = packet.getFrom(); 853 String type = ""; 854 Icon packetTypeIcon; 855 receivedPackets++; 856 if (packet instanceof IQ) { 857 packetTypeIcon = iqPacketIcon; 858 messageType = "IQ Received (class=" + packet.getClass().getName() + ")"; 859 type = ((IQ) packet).getType().toString(); 860 receivedIQPackets++; 861 } 862 else if (packet instanceof Message) { 863 packetTypeIcon = messagePacketIcon; 864 messageType = "Message Received"; 865 type = ((Message) packet).getType().toString(); 866 receivedMessagePackets++; 867 } 868 else if (packet instanceof Presence) { 869 packetTypeIcon = presencePacketIcon; 870 messageType = "Presence Received"; 871 type = ((Presence) packet).getType().toString(); 872 receivedPresencePackets++; 873 } 874 else { 875 packetTypeIcon = unknownPacketTypeIcon; 876 messageType = packet.getClass().getName() + " Received"; 877 receivedOtherPackets++; 878 } 879 880 // Check if we need to remove old rows from the table to keep memory consumption low 881 if (EnhancedDebuggerWindow.MAX_TABLE_ROWS > 0 && 882 messagesTable.getRowCount() >= EnhancedDebuggerWindow.MAX_TABLE_ROWS) { 883 messagesTable.removeRow(0); 884 } 885 886 messagesTable.addRow( 887 new Object[] { 888 formatXML(packet.toXML().toString()), 889 dateFormatter.format(new Date()), 890 packetReceivedIcon, 891 packetTypeIcon, 892 messageType, 893 packet.getStanzaId(), 894 type, 895 "", 896 from}); 897 // Update the statistics table 898 updateStatistics(); 899 } 900 }); 901 } 902 903 /** 904 * Adds the sent stanza(/packet) detail to the messages table. 905 * 906 * @param dateFormatter the SimpleDateFormat to use to format Dates 907 * @param packet the sent stanza(/packet) to add to the table 908 */ 909 private void addSentPacketToTable(final SimpleDateFormat dateFormatter, final Stanza packet) { 910 SwingUtilities.invokeLater(new Runnable() { 911 @Override 912 public void run() { 913 String messageType; 914 Jid to = packet.getTo(); 915 String type = ""; 916 Icon packetTypeIcon; 917 sentPackets++; 918 if (packet instanceof IQ) { 919 packetTypeIcon = iqPacketIcon; 920 messageType = "IQ Sent (class=" + packet.getClass().getName() + ")"; 921 type = ((IQ) packet).getType().toString(); 922 sentIQPackets++; 923 } 924 else if (packet instanceof Message) { 925 packetTypeIcon = messagePacketIcon; 926 messageType = "Message Sent"; 927 type = ((Message) packet).getType().toString(); 928 sentMessagePackets++; 929 } 930 else if (packet instanceof Presence) { 931 packetTypeIcon = presencePacketIcon; 932 messageType = "Presence Sent"; 933 type = ((Presence) packet).getType().toString(); 934 sentPresencePackets++; 935 } 936 else { 937 packetTypeIcon = unknownPacketTypeIcon; 938 messageType = packet.getClass().getName() + " Sent"; 939 sentOtherPackets++; 940 } 941 942 // Check if we need to remove old rows from the table to keep memory consumption low 943 if (EnhancedDebuggerWindow.MAX_TABLE_ROWS > 0 && 944 messagesTable.getRowCount() >= EnhancedDebuggerWindow.MAX_TABLE_ROWS) { 945 messagesTable.removeRow(0); 946 } 947 948 messagesTable.addRow( 949 new Object[] { 950 formatXML(packet.toXML().toString()), 951 dateFormatter.format(new Date()), 952 packetSentIcon, 953 packetTypeIcon, 954 messageType, 955 packet.getStanzaId(), 956 type, 957 to, 958 ""}); 959 960 // Update the statistics table 961 updateStatistics(); 962 } 963 }); 964 } 965 966 private String formatXML(String str) { 967 try { 968 // Use a Transformer for output 969 TransformerFactory tFactory = TransformerFactory.newInstance(); 970 // Surround this setting in a try/catch for compatibility with Java 1.4. This setting is required 971 // for Java 1.5 972 try { 973 tFactory.setAttribute("indent-number", 2); 974 } 975 catch (IllegalArgumentException e) { 976 // Ignore 977 } 978 Transformer transformer = tFactory.newTransformer(); 979 transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); 980 transformer.setOutputProperty(OutputKeys.INDENT, "yes"); 981 transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); 982 983 // Transform the requested string into a nice formatted XML string 984 StreamSource source = new StreamSource(new StringReader(str)); 985 StringWriter sw = new StringWriter(); 986 StreamResult result = new StreamResult(sw); 987 transformer.transform(source, result); 988 return sw.toString(); 989 990 } 991 catch (TransformerConfigurationException tce) { 992 LOGGER.log(Level.SEVERE, "Transformer Factory error", tce); 993 } 994 catch (TransformerException te) { 995 LOGGER.log(Level.SEVERE, "Transformation error", te); 996 } 997 return str; 998 } 999 1000 /** 1001 * Returns true if the debugger's connection with the server is up and running. 1002 * 1003 * @return true if the connection with the server is active. 1004 */ 1005 boolean isConnectionActive() { 1006 return connection.isConnected(); 1007 } 1008 1009 /** 1010 * Stops debugging the connection. Removes any listener on the connection. 1011 */ 1012 void cancel() { 1013 connection.removeConnectionListener(connListener); 1014 connection.removeAsyncStanzaListener(packetReaderListener); 1015 connection.removePacketSendingListener(packetWriterListener); 1016 ((ObservableReader) reader).removeReaderListener(readerListener); 1017 ((ObservableWriter) writer).removeWriterListener(writerListener); 1018 messagesTable = null; 1019 } 1020 1021 /** 1022 * An ad-hoc stanza(/packet) is like any regular stanza(/packet) but with the exception that it's intention is 1023 * to be used only <b>to send packets</b>.<p> 1024 * <p/> 1025 * The whole text to send must be passed to the constructor. This implies that the client of 1026 * this class is responsible for sending a valid text to the constructor. 1027 */ 1028 private static class AdHocPacket extends Stanza { 1029 1030 private final String text; 1031 1032 /** 1033 * Create a new AdHocPacket with the text to send. The passed text must be a valid text to 1034 * send to the server, no validation will be done on the passed text. 1035 * 1036 * @param text the whole text of the stanza(/packet) to send 1037 */ 1038 public AdHocPacket(String text) { 1039 this.text = text; 1040 } 1041 1042 @Override 1043 public String toXML() { 1044 return text; 1045 } 1046 1047 @Override 1048 public String toString() { 1049 return toXML(); 1050 } 1051 1052 } 1053 1054 /** 1055 * Listens for debug window popup dialog events. 1056 */ 1057 private static class PopupListener extends MouseAdapter { 1058 1059 JPopupMenu popup; 1060 1061 PopupListener(JPopupMenu popupMenu) { 1062 popup = popupMenu; 1063 } 1064 1065 @Override 1066 public void mousePressed(MouseEvent e) { 1067 maybeShowPopup(e); 1068 } 1069 1070 @Override 1071 public void mouseReleased(MouseEvent e) { 1072 maybeShowPopup(e); 1073 } 1074 1075 private void maybeShowPopup(MouseEvent e) { 1076 if (e.isPopupTrigger()) { 1077 popup.show(e.getComponent(), e.getX(), e.getY()); 1078 } 1079 } 1080 } 1081 1082 private class SelectionListener implements ListSelectionListener { 1083 1084 JTable table; 1085 1086 // It is necessary to keep the table since it is not possible 1087 // to determine the table from the event's source 1088 SelectionListener(JTable table) { 1089 this.table = table; 1090 } 1091 1092 @Override 1093 public void valueChanged(ListSelectionEvent e) { 1094 if (table.getSelectedRow() == -1) { 1095 // Clear the messageTextArea since there is none packet selected 1096 messageTextArea.setText(null); 1097 } 1098 else { 1099 // Set the detail of the packet in the messageTextArea 1100 messageTextArea.setText( 1101 (String) table.getModel().getValueAt(table.getSelectedRow(), 0)); 1102 // Scroll up to the top 1103 messageTextArea.setCaretPosition(0); 1104 } 1105 } 1106 } 1107}