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.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 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.getXMPPServiceDomain() + ":" + 085 connection.getPort()); 086 087 // Add listener for window closing event 088 frame.addWindowListener(new WindowAdapter() { 089 @Override 090 public void windowClosing(WindowEvent evt) { 091 rootWindowClosing(evt); 092 } 093 }); 094 095 // We'll arrange the UI into four tabs. The first tab contains all data, the second 096 // client generated XML, the third server generated XML, and the fourth is packet 097 // data from the server as seen by Smack. 098 JTabbedPane tabbedPane = new JTabbedPane(); 099 100 JPanel allPane = new JPanel(); 101 allPane.setLayout(new GridLayout(3, 1)); 102 tabbedPane.add("All", allPane); 103 104 // Create UI elements for client generated XML traffic. 105 final JTextArea sentText1 = new JTextArea(); 106 final JTextArea sentText2 = new JTextArea(); 107 sentText1.setEditable(false); 108 sentText2.setEditable(false); 109 sentText1.setForeground(new Color(112, 3, 3)); 110 sentText2.setForeground(new Color(112, 3, 3)); 111 allPane.add(new JScrollPane(sentText1)); 112 tabbedPane.add("Sent", new JScrollPane(sentText2)); 113 114 // Add pop-up menu. 115 JPopupMenu menu = new JPopupMenu(); 116 JMenuItem menuItem1 = new JMenuItem("Copy"); 117 menuItem1.addActionListener(new ActionListener() { 118 @Override 119 public void actionPerformed(ActionEvent e) { 120 // Get the clipboard 121 Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); 122 // Set the sent text as the new content of the clipboard 123 clipboard.setContents(new StringSelection(sentText1.getText()), null); 124 } 125 }); 126 127 JMenuItem menuItem2 = new JMenuItem("Clear"); 128 menuItem2.addActionListener(new ActionListener() { 129 @Override 130 public void actionPerformed(ActionEvent e) { 131 sentText1.setText(""); 132 sentText2.setText(""); 133 } 134 }); 135 136 // Add listener to the text area so the popup menu can come up. 137 MouseListener popupListener = new PopupListener(menu); 138 sentText1.addMouseListener(popupListener); 139 sentText2.addMouseListener(popupListener); 140 menu.add(menuItem1); 141 menu.add(menuItem2); 142 143 // Create UI elements for server generated XML traffic. 144 final JTextArea receivedText1 = new JTextArea(); 145 final JTextArea receivedText2 = new JTextArea(); 146 receivedText1.setEditable(false); 147 receivedText2.setEditable(false); 148 receivedText1.setForeground(new Color(6, 76, 133)); 149 receivedText2.setForeground(new Color(6, 76, 133)); 150 allPane.add(new JScrollPane(receivedText1)); 151 tabbedPane.add("Received", new JScrollPane(receivedText2)); 152 153 // Add pop-up menu. 154 menu = new JPopupMenu(); 155 menuItem1 = new JMenuItem("Copy"); 156 menuItem1.addActionListener(new ActionListener() { 157 @Override 158 public void actionPerformed(ActionEvent e) { 159 // Get the clipboard 160 Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); 161 // Set the sent text as the new content of the clipboard 162 clipboard.setContents(new StringSelection(receivedText1.getText()), null); 163 } 164 }); 165 166 menuItem2 = new JMenuItem("Clear"); 167 menuItem2.addActionListener(new ActionListener() { 168 @Override 169 public void actionPerformed(ActionEvent e) { 170 receivedText1.setText(""); 171 receivedText2.setText(""); 172 } 173 }); 174 175 // Add listener to the text area so the popup menu can come up. 176 popupListener = new PopupListener(menu); 177 receivedText1.addMouseListener(popupListener); 178 receivedText2.addMouseListener(popupListener); 179 menu.add(menuItem1); 180 menu.add(menuItem2); 181 182 // Create UI elements for interpreted XML traffic. 183 final JTextArea interpretedText1 = new JTextArea(); 184 final JTextArea interpretedText2 = new JTextArea(); 185 interpretedText1.setEditable(false); 186 interpretedText2.setEditable(false); 187 interpretedText1.setForeground(new Color(1, 94, 35)); 188 interpretedText2.setForeground(new Color(1, 94, 35)); 189 allPane.add(new JScrollPane(interpretedText1)); 190 tabbedPane.add("Interpreted", new JScrollPane(interpretedText2)); 191 192 // Add pop-up menu. 193 menu = new JPopupMenu(); 194 menuItem1 = new JMenuItem("Copy"); 195 menuItem1.addActionListener(new ActionListener() { 196 @Override 197 public void actionPerformed(ActionEvent e) { 198 // Get the clipboard 199 Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); 200 // Set the sent text as the new content of the clipboard 201 clipboard.setContents(new StringSelection(interpretedText1.getText()), null); 202 } 203 }); 204 205 menuItem2 = new JMenuItem("Clear"); 206 menuItem2.addActionListener(new ActionListener() { 207 @Override 208 public void actionPerformed(ActionEvent e) { 209 interpretedText1.setText(""); 210 interpretedText2.setText(""); 211 } 212 }); 213 214 // Add listener to the text area so the popup menu can come up. 215 popupListener = new PopupListener(menu); 216 interpretedText1.addMouseListener(popupListener); 217 interpretedText2.addMouseListener(popupListener); 218 menu.add(menuItem1); 219 menu.add(menuItem2); 220 221 frame.getContentPane().add(tabbedPane); 222 223 frame.setSize(550, 400); 224 frame.setVisible(true); 225 226 // Create a special Reader that wraps the main Reader and logs data to the GUI. 227 ObservableReader debugReader = new ObservableReader(reader); 228 readerListener = new ReaderListener() { 229 @Override 230 public void read(String str) { 231 int index = str.lastIndexOf(">"); 232 if (index != -1) { 233 receivedText1.append(str.substring(0, index + 1)); 234 receivedText2.append(str.substring(0, index + 1)); 235 receivedText1.append(NEWLINE); 236 receivedText2.append(NEWLINE); 237 if (str.length() > index) { 238 receivedText1.append(str.substring(index + 1)); 239 receivedText2.append(str.substring(index + 1)); 240 } 241 } 242 else { 243 receivedText1.append(str); 244 receivedText2.append(str); 245 } 246 } 247 }; 248 debugReader.addReaderListener(readerListener); 249 250 // Create a special Writer that wraps the main Writer and logs data to the GUI. 251 ObservableWriter debugWriter = new ObservableWriter(writer); 252 writerListener = new WriterListener() { 253 @Override 254 public void write(String str) { 255 sentText1.append(str); 256 sentText2.append(str); 257 if (str.endsWith(">")) { 258 sentText1.append(NEWLINE); 259 sentText2.append(NEWLINE); 260 } 261 } 262 }; 263 debugWriter.addWriterListener(writerListener); 264 265 // Assign the reader/writer objects to use the debug versions. The packet reader 266 // and writer will use the debug versions when they are created. 267 reader = debugReader; 268 writer = debugWriter; 269 270 // Create a thread that will listen for all incoming packets and write them to 271 // the GUI. This is what we call "interpreted" packet data, since it's the packet 272 // data as Smack sees it and not as it's coming in as raw XML. 273 listener = new StanzaListener() { 274 @Override 275 public void processStanza(Stanza packet) { 276 interpretedText1.append(packet.toXML().toString()); 277 interpretedText2.append(packet.toXML().toString()); 278 interpretedText1.append(NEWLINE); 279 interpretedText2.append(NEWLINE); 280 } 281 }; 282 } 283 284 /** 285 * Notification that the root window is closing. Stop listening for received and 286 * transmitted packets. 287 * 288 * @param evt the event that indicates that the root window is closing 289 */ 290 public void rootWindowClosing(WindowEvent evt) { 291 connection.removeAsyncStanzaListener(listener); 292 ((ObservableReader)reader).removeReaderListener(readerListener); 293 ((ObservableWriter)writer).removeWriterListener(writerListener); 294 } 295 296 /** 297 * Listens for debug window popup dialog events. 298 */ 299 private static class PopupListener extends MouseAdapter { 300 JPopupMenu popup; 301 302 PopupListener(JPopupMenu popupMenu) { 303 popup = popupMenu; 304 } 305 306 @Override 307 public void mousePressed(MouseEvent e) { 308 maybeShowPopup(e); 309 } 310 311 @Override 312 public void mouseReleased(MouseEvent e) { 313 maybeShowPopup(e); 314 } 315 316 private void maybeShowPopup(MouseEvent e) { 317 if (e.isPopupTrigger()) { 318 popup.show(e.getComponent(), e.getX(), e.getY()); 319 } 320 } 321 } 322 323 @Override 324 public Reader newConnectionReader(Reader newReader) { 325 ((ObservableReader)reader).removeReaderListener(readerListener); 326 ObservableReader debugReader = new ObservableReader(newReader); 327 debugReader.addReaderListener(readerListener); 328 reader = debugReader; 329 return reader; 330 } 331 332 @Override 333 public Writer newConnectionWriter(Writer newWriter) { 334 ((ObservableWriter)writer).removeWriterListener(writerListener); 335 ObservableWriter debugWriter = new ObservableWriter(newWriter); 336 debugWriter.addWriterListener(writerListener); 337 writer = debugWriter; 338 return writer; 339 } 340 341 @Override 342 public void userHasLogged(EntityFullJid user) { 343 String title = 344 "Smack Debug Window -- " 345 + user; 346 frame.setTitle(title); 347 } 348 349 @Override 350 public Reader getReader() { 351 return reader; 352 } 353 354 @Override 355 public Writer getWriter() { 356 return writer; 357 } 358 359 @Override 360 public StanzaListener getReaderListener() { 361 return listener; 362 } 363 364 @Override 365 public StanzaListener getWriterListener() { 366 return null; 367 } 368}