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 org.jivesoftware.smack.SmackConfiguration; 021import org.jivesoftware.smack.provider.ProviderManager; 022 023import javax.swing.*; 024 025import java.awt.*; 026import java.awt.event.*; 027import java.net.URL; 028import java.util.ArrayList; 029import java.util.Collections; 030import java.util.Vector; 031 032/** 033 * The EnhancedDebuggerWindow is the main debug window that will show all the EnhancedDebuggers. 034 * For each connection to debug there will be an EnhancedDebugger that will be shown in the 035 * EnhancedDebuggerWindow.<p> 036 * <p/> 037 * This class also provides information about Smack like for example the Smack version and the 038 * installed providers. 039 * 040 * @author Gaston Dombiak 041 */ 042public class EnhancedDebuggerWindow { 043 044 private static EnhancedDebuggerWindow instance; 045 046 private static ImageIcon connectionCreatedIcon; 047 private static ImageIcon connectionActiveIcon; 048 private static ImageIcon connectionClosedIcon; 049 private static ImageIcon connectionClosedOnErrorIcon; 050 051 public static boolean PERSISTED_DEBUGGER = false; 052 /** 053 * Keeps the max number of rows to keep in the tables. A value less than 0 means that packets 054 * will never be removed. If you are planning to use this debugger in a 055 * production environment then you should set a lower value (e.g. 50) to prevent the debugger 056 * from consuming all the JVM memory. 057 */ 058 public static int MAX_TABLE_ROWS = 150; 059 060 { 061 URL url; 062 063 url = 064 Thread.currentThread().getContextClassLoader().getResource( 065 "images/trafficlight_off.png"); 066 if (url != null) { 067 connectionCreatedIcon = new ImageIcon(url); 068 } 069 url = 070 Thread.currentThread().getContextClassLoader().getResource( 071 "images/trafficlight_green.png"); 072 if (url != null) { 073 connectionActiveIcon = new ImageIcon(url); 074 } 075 url = 076 Thread.currentThread().getContextClassLoader().getResource( 077 "images/trafficlight_red.png"); 078 if (url != null) { 079 connectionClosedIcon = new ImageIcon(url); 080 } 081 url = Thread.currentThread().getContextClassLoader().getResource("images/warning.png"); 082 if (url != null) { 083 connectionClosedOnErrorIcon = new ImageIcon(url); 084 } 085 086 } 087 088 private JFrame frame = null; 089 private JTabbedPane tabbedPane = null; 090 private java.util.List<EnhancedDebugger> debuggers = new ArrayList<EnhancedDebugger>(); 091 092 private EnhancedDebuggerWindow() { 093 } 094 095 /** 096 * Returns the unique EnhancedDebuggerWindow instance available in the system. 097 * 098 * @return the unique EnhancedDebuggerWindow instance 099 */ 100 public static EnhancedDebuggerWindow getInstance() { 101 if (instance == null) { 102 instance = new EnhancedDebuggerWindow(); 103 } 104 return instance; 105 } 106 107 /** 108 * Adds the new specified debugger to the list of debuggers to show in the main window. 109 * 110 * @param debugger the new debugger to show in the debug window 111 */ 112 synchronized static void addDebugger(EnhancedDebugger debugger) { 113 getInstance().showNewDebugger(debugger); 114 } 115 116 /** 117 * Shows the new debugger in the debug window. 118 * 119 * @param debugger the new debugger to show 120 */ 121 private void showNewDebugger(EnhancedDebugger debugger) { 122 if (frame == null) { 123 createDebug(); 124 } 125 debugger.tabbedPane.setName("XMPPConnection_" + tabbedPane.getComponentCount()); 126 tabbedPane.add(debugger.tabbedPane, tabbedPane.getComponentCount() - 1); 127 tabbedPane.setIconAt(tabbedPane.indexOfComponent(debugger.tabbedPane), connectionCreatedIcon); 128 frame.setTitle( 129 "Smack Debug Window -- Total connections: " + (tabbedPane.getComponentCount() - 1)); 130 // Keep the added debugger for later access 131 debuggers.add(debugger); 132 } 133 134 /** 135 * Notification that a user has logged in to the server. A new title will be set 136 * to the tab of the given debugger. 137 * 138 * @param debugger the debugger whose connection logged in to the server 139 * @param user the user@host/resource that has just logged in 140 */ 141 synchronized static void userHasLogged(EnhancedDebugger debugger, String user) { 142 int index = getInstance().tabbedPane.indexOfComponent(debugger.tabbedPane); 143 getInstance().tabbedPane.setTitleAt( 144 index, 145 user); 146 getInstance().tabbedPane.setIconAt( 147 index, 148 connectionActiveIcon); 149 } 150 151 /** 152 * Notification that the connection was properly closed. 153 * 154 * @param debugger the debugger whose connection was properly closed. 155 */ 156 synchronized static void connectionClosed(EnhancedDebugger debugger) { 157 getInstance().tabbedPane.setIconAt( 158 getInstance().tabbedPane.indexOfComponent(debugger.tabbedPane), 159 connectionClosedIcon); 160 } 161 162 /** 163 * Notification that the connection was closed due to an exception. 164 * 165 * @param debugger the debugger whose connection was closed due to an exception. 166 * @param e the exception. 167 */ 168 synchronized static void connectionClosedOnError(EnhancedDebugger debugger, Exception e) { 169 int index = getInstance().tabbedPane.indexOfComponent(debugger.tabbedPane); 170 getInstance().tabbedPane.setToolTipTextAt( 171 index, 172 "XMPPConnection closed due to the exception: " + e.getMessage()); 173 getInstance().tabbedPane.setIconAt( 174 index, 175 connectionClosedOnErrorIcon); 176 } 177 178 synchronized static void connectionEstablished(EnhancedDebugger debugger) { 179 getInstance().tabbedPane.setIconAt( 180 getInstance().tabbedPane.indexOfComponent(debugger.tabbedPane), 181 connectionActiveIcon); 182 } 183 184 /** 185 * Creates the main debug window that provides information about Smack and also shows 186 * a tab panel for each connection that is being debugged. 187 */ 188 @SuppressWarnings({ "rawtypes", "unchecked" }) 189 private void createDebug() { 190 191 frame = new JFrame("Smack Debug Window"); 192 193 if (!PERSISTED_DEBUGGER) { 194 // Add listener for window closing event 195 frame.addWindowListener(new WindowAdapter() { 196 public void windowClosing(WindowEvent evt) { 197 rootWindowClosing(evt); 198 } 199 }); 200 } 201 202 // We'll arrange the UI into tabs. The last tab contains Smack's information. 203 // All the connection debugger tabs will be shown before the Smack info tab. 204 tabbedPane = new JTabbedPane(); 205 206 // Create the Smack info panel 207 JPanel informationPanel = new JPanel(); 208 informationPanel.setLayout(new BoxLayout(informationPanel, BoxLayout.Y_AXIS)); 209 210 // Add the Smack version label 211 JPanel versionPanel = new JPanel(); 212 versionPanel.setLayout(new BoxLayout(versionPanel, BoxLayout.X_AXIS)); 213 versionPanel.setMaximumSize(new Dimension(2000, 31)); 214 versionPanel.add(new JLabel(" Smack version: ")); 215 JFormattedTextField field = new JFormattedTextField(SmackConfiguration.getVersion()); 216 field.setEditable(false); 217 field.setBorder(null); 218 versionPanel.add(field); 219 informationPanel.add(versionPanel); 220 221 // Add the list of installed IQ Providers 222 JPanel iqProvidersPanel = new JPanel(); 223 iqProvidersPanel.setLayout(new GridLayout(1, 1)); 224 iqProvidersPanel.setBorder(BorderFactory.createTitledBorder("Installed IQ Providers")); 225 Vector<String> providers = new Vector<String>(); 226 for (Object provider : ProviderManager.getIQProviders()) { 227 if (provider.getClass() == Class.class) { 228 providers.add(((Class<?>) provider).getName()); 229 } 230 else { 231 providers.add(provider.getClass().getName()); 232 } 233 } 234 // Sort the collection of providers 235 Collections.sort(providers); 236 JList list = new JList(providers); 237 iqProvidersPanel.add(new JScrollPane(list)); 238 informationPanel.add(iqProvidersPanel); 239 240 // Add the list of installed Extension Providers 241 JPanel extensionProvidersPanel = new JPanel(); 242 extensionProvidersPanel.setLayout(new GridLayout(1, 1)); 243 extensionProvidersPanel.setBorder(BorderFactory.createTitledBorder("Installed Extension Providers")); 244 providers = new Vector<String>(); 245 for (Object provider : ProviderManager.getExtensionProviders()) { 246 if (provider.getClass() == Class.class) { 247 providers.add(((Class<?>) provider).getName()); 248 } 249 else { 250 providers.add(provider.getClass().getName()); 251 } 252 } 253 // Sort the collection of providers 254 Collections.sort(providers); 255 list = new JList(providers); 256 extensionProvidersPanel.add(new JScrollPane(list)); 257 informationPanel.add(extensionProvidersPanel); 258 259 tabbedPane.add("Smack Info", informationPanel); 260 261 // Add pop-up menu. 262 JPopupMenu menu = new JPopupMenu(); 263 // Add a menu item that allows to close the current selected tab 264 JMenuItem menuItem = new JMenuItem("Close"); 265 menuItem.addActionListener(new ActionListener() { 266 public void actionPerformed(ActionEvent e) { 267 // Remove the selected tab pane if it's not the Smack info pane 268 if (tabbedPane.getSelectedIndex() < tabbedPane.getComponentCount() - 1) { 269 int index = tabbedPane.getSelectedIndex(); 270 // Notify to the debugger to stop debugging 271 EnhancedDebugger debugger = debuggers.get(index); 272 debugger.cancel(); 273 // Remove the debugger from the root window 274 tabbedPane.remove(debugger.tabbedPane); 275 debuggers.remove(debugger); 276 // Update the root window title 277 frame.setTitle( 278 "Smack Debug Window -- Total connections: " 279 + (tabbedPane.getComponentCount() - 1)); 280 } 281 } 282 }); 283 menu.add(menuItem); 284 // Add a menu item that allows to close all the tabs that have their connections closed 285 menuItem = new JMenuItem("Close All Not Active"); 286 menuItem.addActionListener(new ActionListener() { 287 public void actionPerformed(ActionEvent e) { 288 ArrayList<EnhancedDebugger> debuggersToRemove = new ArrayList<EnhancedDebugger>(); 289 // Remove all the debuggers of which their connections are no longer valid 290 for (int index = 0; index < tabbedPane.getComponentCount() - 1; index++) { 291 EnhancedDebugger debugger = debuggers.get(index); 292 if (!debugger.isConnectionActive()) { 293 // Notify to the debugger to stop debugging 294 debugger.cancel(); 295 debuggersToRemove.add(debugger); 296 } 297 } 298 for (EnhancedDebugger debugger : debuggersToRemove) { 299 // Remove the debugger from the root window 300 tabbedPane.remove(debugger.tabbedPane); 301 debuggers.remove(debugger); 302 } 303 // Update the root window title 304 frame.setTitle( 305 "Smack Debug Window -- Total connections: " 306 + (tabbedPane.getComponentCount() - 1)); 307 } 308 }); 309 menu.add(menuItem); 310 // Add listener to the text area so the popup menu can come up. 311 tabbedPane.addMouseListener(new PopupListener(menu)); 312 313 frame.getContentPane().add(tabbedPane); 314 315 frame.setSize(650, 400); 316 317 if (!PERSISTED_DEBUGGER) { 318 frame.setVisible(true); 319 } 320 } 321 322 /** 323 * Notification that the root window is closing. Stop listening for received and 324 * transmitted packets in all the debugged connections. 325 * 326 * @param evt the event that indicates that the root window is closing 327 */ 328 public void rootWindowClosing(WindowEvent evt) { 329 // Notify to all the debuggers to stop debugging 330 for (EnhancedDebugger debugger : debuggers) { 331 debugger.cancel(); 332 } 333 // Release any reference to the debuggers 334 debuggers.removeAll(debuggers); 335 // Release the default instance 336 instance = null; 337 } 338 339 /** 340 * Listens for debug window popup dialog events. 341 */ 342 private class PopupListener extends MouseAdapter { 343 344 JPopupMenu popup; 345 346 PopupListener(JPopupMenu popupMenu) { 347 popup = popupMenu; 348 } 349 350 public void mousePressed(MouseEvent e) { 351 maybeShowPopup(e); 352 } 353 354 public void mouseReleased(MouseEvent e) { 355 maybeShowPopup(e); 356 } 357 358 private void maybeShowPopup(MouseEvent e) { 359 if (e.isPopupTrigger()) { 360 popup.show(e.getComponent(), e.getX(), e.getY()); 361 } 362 } 363 } 364 365 public void setVisible(boolean visible) { 366 if (frame != null) { 367 frame.setVisible(visible); 368 } 369 } 370 371 public boolean isVisible() { 372 return frame != null && frame.isVisible(); 373 } 374}