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