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