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