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