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}