001/**
002 *
003 * Copyright 2003-2007 Jive Software.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.jivesoftware.smackx.debugger;
019
020import java.awt.Color;
021import java.awt.GridLayout;
022import java.awt.Toolkit;
023import java.awt.datatransfer.Clipboard;
024import java.awt.datatransfer.StringSelection;
025import java.awt.event.ActionEvent;
026import java.awt.event.ActionListener;
027import java.awt.event.MouseAdapter;
028import java.awt.event.MouseEvent;
029import java.awt.event.MouseListener;
030import java.awt.event.WindowAdapter;
031import java.awt.event.WindowEvent;
032import java.io.Reader;
033import java.io.Writer;
034
035import javax.swing.JFrame;
036import javax.swing.JMenuItem;
037import javax.swing.JPanel;
038import javax.swing.JPopupMenu;
039import javax.swing.JScrollPane;
040import javax.swing.JTabbedPane;
041import javax.swing.JTextArea;
042
043import org.jivesoftware.smack.StanzaListener;
044import org.jivesoftware.smack.XMPPConnection;
045import org.jivesoftware.smack.debugger.SmackDebugger;
046import org.jivesoftware.smack.packet.Stanza;
047import org.jivesoftware.smack.util.ObservableReader;
048import org.jivesoftware.smack.util.ObservableWriter;
049import org.jivesoftware.smack.util.ReaderListener;
050import org.jivesoftware.smack.util.WriterListener;
051import org.jxmpp.util.XmppStringUtils;
052
053/**
054 * The LiteDebugger is a very simple debugger that allows to debug sent, received and 
055 * interpreted messages.
056 * 
057 * @author Gaston Dombiak
058 */
059public class LiteDebugger implements SmackDebugger {
060
061    private static final String NEWLINE = "\n";
062
063    private JFrame frame = null;
064    private XMPPConnection connection = null;
065
066    private StanzaListener listener = null;
067
068    private Writer writer;
069    private Reader reader;
070    private ReaderListener readerListener;
071    private WriterListener writerListener;
072
073    public LiteDebugger(XMPPConnection connection, Writer writer, Reader reader) {
074        this.connection = connection;
075        this.writer = writer;
076        this.reader = reader;
077        createDebug();
078    }
079
080    /**
081     * Creates the debug process, which is a GUI window that displays XML traffic.
082     */
083    private void createDebug() {
084        frame = new JFrame("Smack Debug Window -- " + connection.getServiceName() + ":" +
085                connection.getPort());
086
087        // Add listener for window closing event 
088        frame.addWindowListener(new WindowAdapter() {
089            public void windowClosing(WindowEvent evt) {
090                rootWindowClosing(evt);
091            }
092        });
093
094        // We'll arrange the UI into four tabs. The first tab contains all data, the second
095        // client generated XML, the third server generated XML, and the fourth is packet
096        // data from the server as seen by Smack.
097        JTabbedPane tabbedPane = new JTabbedPane();
098
099        JPanel allPane = new JPanel();
100        allPane.setLayout(new GridLayout(3, 1));
101        tabbedPane.add("All", allPane);
102
103        // Create UI elements for client generated XML traffic.
104        final JTextArea sentText1 = new JTextArea();
105        final JTextArea sentText2 = new JTextArea();
106        sentText1.setEditable(false);
107        sentText2.setEditable(false);
108        sentText1.setForeground(new Color(112, 3, 3));
109        sentText2.setForeground(new Color(112, 3, 3));
110        allPane.add(new JScrollPane(sentText1));
111        tabbedPane.add("Sent", new JScrollPane(sentText2));
112
113        // Add pop-up menu.
114        JPopupMenu menu = new JPopupMenu();
115        JMenuItem menuItem1 = new JMenuItem("Copy");
116        menuItem1.addActionListener(new ActionListener() {
117            public void actionPerformed(ActionEvent e) {
118                // Get the clipboard
119                Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
120                // Set the sent text as the new content of the clipboard
121                clipboard.setContents(new StringSelection(sentText1.getText()), null);
122            }
123        });
124
125        JMenuItem menuItem2 = new JMenuItem("Clear");
126        menuItem2.addActionListener(new ActionListener() {
127            public void actionPerformed(ActionEvent e) {
128                sentText1.setText("");
129                sentText2.setText("");
130            }
131        });
132
133        // Add listener to the text area so the popup menu can come up.
134        MouseListener popupListener = new PopupListener(menu);
135        sentText1.addMouseListener(popupListener);
136        sentText2.addMouseListener(popupListener);
137        menu.add(menuItem1);
138        menu.add(menuItem2);
139
140        // Create UI elements for server generated XML traffic.
141        final JTextArea receivedText1 = new JTextArea();
142        final JTextArea receivedText2 = new JTextArea();
143        receivedText1.setEditable(false);
144        receivedText2.setEditable(false);
145        receivedText1.setForeground(new Color(6, 76, 133));
146        receivedText2.setForeground(new Color(6, 76, 133));
147        allPane.add(new JScrollPane(receivedText1));
148        tabbedPane.add("Received", new JScrollPane(receivedText2));
149
150        // Add pop-up menu.
151        menu = new JPopupMenu();
152        menuItem1 = new JMenuItem("Copy");
153        menuItem1.addActionListener(new ActionListener() {
154            public void actionPerformed(ActionEvent e) {
155                // Get the clipboard
156                Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
157                // Set the sent text as the new content of the clipboard
158                clipboard.setContents(new StringSelection(receivedText1.getText()), null);
159            }
160        });
161
162        menuItem2 = new JMenuItem("Clear");
163        menuItem2.addActionListener(new ActionListener() {
164            public void actionPerformed(ActionEvent e) {
165                receivedText1.setText("");
166                receivedText2.setText("");
167            }
168        });
169
170        // Add listener to the text area so the popup menu can come up.
171        popupListener = new PopupListener(menu);
172        receivedText1.addMouseListener(popupListener);
173        receivedText2.addMouseListener(popupListener);
174        menu.add(menuItem1);
175        menu.add(menuItem2);
176
177        // Create UI elements for interpreted XML traffic.
178        final JTextArea interpretedText1 = new JTextArea();
179        final JTextArea interpretedText2 = new JTextArea();
180        interpretedText1.setEditable(false);
181        interpretedText2.setEditable(false);
182        interpretedText1.setForeground(new Color(1, 94, 35));
183        interpretedText2.setForeground(new Color(1, 94, 35));
184        allPane.add(new JScrollPane(interpretedText1));
185        tabbedPane.add("Interpreted", new JScrollPane(interpretedText2));
186
187        // Add pop-up menu.
188        menu = new JPopupMenu();
189        menuItem1 = new JMenuItem("Copy");
190        menuItem1.addActionListener(new ActionListener() {
191            public void actionPerformed(ActionEvent e) {
192                // Get the clipboard
193                Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
194                // Set the sent text as the new content of the clipboard
195                clipboard.setContents(new StringSelection(interpretedText1.getText()), null);
196            }
197        });
198
199        menuItem2 = new JMenuItem("Clear");
200        menuItem2.addActionListener(new ActionListener() {
201            public void actionPerformed(ActionEvent e) {
202                interpretedText1.setText("");
203                interpretedText2.setText("");
204            }
205        });
206
207        // Add listener to the text area so the popup menu can come up.
208        popupListener = new PopupListener(menu);
209        interpretedText1.addMouseListener(popupListener);
210        interpretedText2.addMouseListener(popupListener);
211        menu.add(menuItem1);
212        menu.add(menuItem2);
213
214        frame.getContentPane().add(tabbedPane);
215
216        frame.setSize(550, 400);
217        frame.setVisible(true);
218
219        // Create a special Reader that wraps the main Reader and logs data to the GUI.
220        ObservableReader debugReader = new ObservableReader(reader);
221        readerListener = new ReaderListener() {
222                    public void read(String str) {
223                        int index = str.lastIndexOf(">");
224                        if (index != -1) {
225                            receivedText1.append(str.substring(0, index + 1));
226                            receivedText2.append(str.substring(0, index + 1));
227                            receivedText1.append(NEWLINE);
228                            receivedText2.append(NEWLINE);
229                            if (str.length() > index) {
230                                receivedText1.append(str.substring(index + 1));
231                                receivedText2.append(str.substring(index + 1));
232                            }
233                        }
234                        else {
235                            receivedText1.append(str);
236                            receivedText2.append(str);
237                        }
238                    }
239                };
240        debugReader.addReaderListener(readerListener);
241
242        // Create a special Writer that wraps the main Writer and logs data to the GUI.
243        ObservableWriter debugWriter = new ObservableWriter(writer);
244        writerListener = new WriterListener() {
245                    public void write(String str) {
246                        sentText1.append(str);
247                        sentText2.append(str);
248                        if (str.endsWith(">")) {
249                            sentText1.append(NEWLINE);
250                            sentText2.append(NEWLINE);
251                        }
252                    }
253                };
254        debugWriter.addWriterListener(writerListener);
255
256        // Assign the reader/writer objects to use the debug versions. The packet reader
257        // and writer will use the debug versions when they are created.
258        reader = debugReader;
259        writer = debugWriter;
260
261        // Create a thread that will listen for all incoming packets and write them to
262        // the GUI. This is what we call "interpreted" packet data, since it's the packet
263        // data as Smack sees it and not as it's coming in as raw XML.
264        listener = new StanzaListener() {
265            public void processPacket(Stanza packet) {
266                interpretedText1.append(packet.toXML().toString());
267                interpretedText2.append(packet.toXML().toString());
268                interpretedText1.append(NEWLINE);
269                interpretedText2.append(NEWLINE);
270            }
271        };
272    }
273
274    /**
275     * Notification that the root window is closing. Stop listening for received and 
276     * transmitted packets.
277     * 
278     * @param evt the event that indicates that the root window is closing 
279     */
280    public void rootWindowClosing(WindowEvent evt) {
281        connection.removeAsyncStanzaListener(listener);
282        ((ObservableReader)reader).removeReaderListener(readerListener);
283        ((ObservableWriter)writer).removeWriterListener(writerListener);
284    }
285
286    /**
287     * Listens for debug window popup dialog events.
288     */
289    private class PopupListener extends MouseAdapter {
290        JPopupMenu popup;
291
292        PopupListener(JPopupMenu popupMenu) {
293            popup = popupMenu;
294        }
295
296        public void mousePressed(MouseEvent e) {
297            maybeShowPopup(e);
298        }
299
300        public void mouseReleased(MouseEvent e) {
301            maybeShowPopup(e);
302        }
303
304        private void maybeShowPopup(MouseEvent e) {
305            if (e.isPopupTrigger()) {
306                popup.show(e.getComponent(), e.getX(), e.getY());
307            }
308        }
309    }
310
311    public Reader newConnectionReader(Reader newReader) {
312        ((ObservableReader)reader).removeReaderListener(readerListener);
313        ObservableReader debugReader = new ObservableReader(newReader);
314        debugReader.addReaderListener(readerListener);
315        reader = debugReader;
316        return reader;
317    }
318
319    public Writer newConnectionWriter(Writer newWriter) {
320        ((ObservableWriter)writer).removeWriterListener(writerListener);
321        ObservableWriter debugWriter = new ObservableWriter(newWriter);
322        debugWriter.addWriterListener(writerListener);
323        writer = debugWriter;
324        return writer;
325    }
326
327    public void userHasLogged(String user) {
328        boolean isAnonymous = "".equals(XmppStringUtils.parseLocalpart(user));
329        String title =
330            "Smack Debug Window -- "
331                + (isAnonymous ? "" : XmppStringUtils.parseBareJid(user))
332                + "@"
333                + connection.getServiceName()
334                + ":"
335                + connection.getPort();
336        title += "/" + XmppStringUtils.parseResource(user);
337        frame.setTitle(title);
338    }
339
340    public Reader getReader() {
341        return reader;
342    }
343
344    public Writer getWriter() {
345        return writer;
346    }
347
348    public StanzaListener getReaderListener() {
349        return listener;
350    }
351
352    public StanzaListener getWriterListener() {
353        return null;
354    }
355}