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