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}