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