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