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