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}