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