diff --git a/source/org/jivesoftware/smack/XMPPConnection.java b/source/org/jivesoftware/smack/XMPPConnection.java index e6d56daff..b6b8dc523 100644 --- a/source/org/jivesoftware/smack/XMPPConnection.java +++ b/source/org/jivesoftware/smack/XMPPConnection.java @@ -53,16 +53,12 @@ package org.jivesoftware.smack; import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.debugger.*; import org.jivesoftware.smack.filter.*; -import org.jivesoftware.smack.util.StringUtils; -import javax.swing.*; +import java.lang.reflect.Constructor; import java.net.*; import java.io.*; -import java.awt.*; -import java.awt.datatransfer.Clipboard; -import java.awt.datatransfer.StringSelection; -import java.awt.event.*; /** * Creates a connection to a XMPP server. A simple use of this API might @@ -81,8 +77,6 @@ import java.awt.event.*; */ public class XMPPConnection { - private static final String NEWLINE = "\n"; - /** * Value that indicates whether debugging is enabled. When enabled, a debug * window will apear for each new connection that will contain the following @@ -103,9 +97,10 @@ public class XMPPConnection { try { DEBUG_ENABLED = Boolean.getBoolean("smack.debugEnabled"); } - catch (Exception e) { } + catch (Exception e) { + } } - private JFrame debugFrame = null; + private SmackDebugger debugger = null; String host; int port; @@ -166,12 +161,16 @@ public class XMPPConnection { this.socket = new Socket(host, port); } catch (UnknownHostException uhe) { - throw new XMPPException("Could not connect to " + host + ":" + port + ".", - new XMPPError(502), uhe); + throw new XMPPException( + "Could not connect to " + host + ":" + port + ".", + new XMPPError(504), + uhe); } catch (IOException ioe) { - throw new XMPPException("XMPPError connecting to " + host + ":" + port + ".", - new XMPPError(502), ioe); + throw new XMPPException( + "XMPPError connecting to " + host + ":" + port + ".", + new XMPPError(502), + ioe); } init(); } @@ -248,8 +247,7 @@ public class XMPPConnection { * to the serrver. */ public synchronized void login(String username, String password, String resource) - throws XMPPException - { + throws XMPPException { if (!isConnected()) { throw new IllegalStateException("Not connected to server."); } @@ -262,12 +260,12 @@ public class XMPPConnection { discoveryAuth.setType(IQ.Type.GET); discoveryAuth.setUsername(username); - PacketCollector collector = packetReader.createPacketCollector( - new PacketIDFilter(discoveryAuth.getPacketID())); + PacketCollector collector = + packetReader.createPacketCollector(new PacketIDFilter(discoveryAuth.getPacketID())); // Send the packet packetWriter.sendPacket(discoveryAuth); // Wait up to five seconds for a response from the server. - IQ response = (IQ)collector.nextResult(5000); + IQ response = (IQ) collector.nextResult(5000); if (response == null) { throw new XMPPException("No response from the server."); } @@ -276,7 +274,7 @@ public class XMPPConnection { throw new XMPPException(response.getError()); } // Otherwise, no error so continue processing. - Authentication authTypes = (Authentication)response; + Authentication authTypes = (Authentication) response; collector.cancel(); // Now, create the authentication packet we'll send to the server. @@ -300,7 +298,7 @@ public class XMPPConnection { // Send the packet. packetWriter.sendPacket(auth); // Wait up to five seconds for a response from the server. - response = (IQ)collector.nextResult(5000); + response = (IQ) collector.nextResult(5000); if (response == null) { throw new XMPPException("Authentication failed."); } @@ -334,11 +332,7 @@ public class XMPPConnection { // If debugging is enabled, change the the debug window title to include the // name we are now logged-in as. if (DEBUG_ENABLED) { - String title = "Smack Debug Window -- " + username + "@" + getHost() + ":" + getPort(); - if (resource != null) { - title += "/" + resource; - } - debugFrame.setTitle(title); + debugger.userHasLogged(user); } } @@ -363,12 +357,12 @@ public class XMPPConnection { // Create the authentication packet we'll send to the server. Authentication auth = new Authentication(); - PacketCollector collector = packetReader.createPacketCollector( - new PacketIDFilter(auth.getPacketID())); + PacketCollector collector = + packetReader.createPacketCollector(new PacketIDFilter(auth.getPacketID())); // Send the packet. packetWriter.sendPacket(auth); // Wait up to five seconds for a response from the server. - IQ response = (IQ)collector.nextResult(5000); + IQ response = (IQ) collector.nextResult(5000); if (response == null) { throw new XMPPException("Anonymous login failed."); } @@ -380,7 +374,7 @@ public class XMPPConnection { this.user = response.getTo(); } else { - this.user = this.host + "/" + ((Authentication)response).getResource(); + this.user = this.host + "/" + ((Authentication) response).getResource(); } // We're done with the collector, so explicitly cancel it. collector.cancel(); @@ -398,9 +392,7 @@ public class XMPPConnection { // If debugging is enabled, change the the debug window title to include the // name we are now logged-in as. if (DEBUG_ENABLED) { - String title = "Smack Debug Window -- " + getHost() + ":" + getPort(); - title += "/" + StringUtils.parseResource(user); - debugFrame.setTitle(title); + debugger.userHasLogged(user); } } @@ -427,7 +419,8 @@ public class XMPPConnection { try { Thread.sleep(500); } - catch (Exception e) { } + catch (Exception e) { + } elapsed += 500; } return roster; @@ -527,11 +520,13 @@ public class XMPPConnection { try { Thread.sleep(150); } - catch (Exception e) { } + catch (Exception e) { + } try { socket.close(); } - catch (Exception e) { } + catch (Exception e) { + } authenticated = false; connected = false; } @@ -574,6 +569,28 @@ public class XMPPConnection { packetReader.removePacketListener(packetListener); } + /** + * Registers a packet writer listener with this connection. The listener will be + * notified of every packet that this connection sends. + * + * @param packetWriterListener the packet writer listener to notify of sent packets. + */ + public void addPacketListener(PacketWriterListener packetWriterListener) { + if (!isConnected()) { + throw new IllegalStateException("Not connected to server."); + } + packetWriter.addPacketListener(packetWriterListener); + } + + /** + * Removes a packet writer listener from this connection. + * + * @param packetWriterListener the packet writer listener to remove. + */ + public void removePacketListener(PacketWriterListener packetWriterListener) { + packetWriter.removePacketListener(packetWriterListener); + } + /** * Creates a new packet collector for this connection. A packet filter determines * which packets will be accumulated by the collector. @@ -625,16 +642,45 @@ public class XMPPConnection { writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8")); } catch (IOException ioe) { - throw new XMPPException("XMPPError establishing connection with server.", - new XMPPError(502), ioe); + throw new XMPPException( + "XMPPError establishing connection with server.", + new XMPPError(502), + ioe); } // If debugging is enabled, we open a window and write out all network traffic. - // The method that creates the debug GUI returns PacketListener that we must add - // after the packet reader and writer are created. - PacketListener debugListener = null; if (DEBUG_ENABLED) { - debugListener = createDebug(); + + // Detect the debugger class to use. + String className = System.getProperty("smack.debuggerClass"); + Class debuggerClass = null; + try { + debuggerClass = Class.forName(className); + } + catch (Exception e) { + try { + debuggerClass = + Class.forName("org.jivesoftware.smackx.debugger.EnhancedDebugger"); + } + catch (Exception ex) { + debuggerClass = LiteDebugger.class; + } + } + // Create a new debugger instance. If an exception occurs then disable the debugging + // option + try { + Constructor constructor = + debuggerClass.getConstructor( + new Class[] { XMPPConnection.class, Writer.class, Reader.class }); + debugger = + (SmackDebugger) constructor.newInstance(new Object[] { this, writer, reader }); + reader = debugger.getReader(); + writer = debugger.getWriter(); + } + catch (Exception e) { + e.printStackTrace(); + DEBUG_ENABLED = false; + } } packetWriter = new PacketWriter(this); @@ -643,7 +689,10 @@ public class XMPPConnection { // If debugging is enabled, we should start the thread that will listen for // all packets and then log them. if (DEBUG_ENABLED) { - packetReader.addPacketListener(debugListener, null); + packetReader.addPacketListener(debugger.getListener(), null); + if (debugger.getWriterListener() != null) { + packetWriter.addPacketListener(debugger.getWriterListener()); + } } // Start the packet writer. This will open a XMPP stream to the server packetWriter.startup(); @@ -654,322 +703,4 @@ public class XMPPConnection { // Make note of the fact that we're now connected. connected = true; } - - /** - * Creates the debug process, which is a GUI window that displays XML traffic. - * This method must be called before the packet reader and writer are created because - * it wraps the reader and writer objects with special logging implementations. - * The method returns a PacketListner that must be added after the packet reader is - * created. - * - * @return a PacketListener used by the debugging process that must be added after the packet - * reader is created. - */ - private PacketListener createDebug() { - // Use the native look and feel. - try { - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - } - catch (Exception e) { - e.printStackTrace(); - } - - debugFrame = new JFrame("Smack Debug Window -- " + getHost() + ":" + getPort()); - - // We'll arrange the UI into four tabs. The first tab contains all data, the second - // client generated XML, the third server generated XML, and the fourth is packet - // data from the server as seen by Smack. - JTabbedPane tabbedPane = new JTabbedPane(); - - JPanel allPane = new JPanel(); - allPane.setLayout(new GridLayout(3, 1)); - tabbedPane.add("All", allPane); - - // Create UI elements for client generated XML traffic. - final JTextArea sentText1 = new JTextArea(); - final JTextArea sentText2 = new JTextArea(); - sentText1.setEditable(false); - sentText2.setEditable(false); - sentText1.setForeground(new Color(112, 3, 3)); - sentText2.setForeground(new Color(112, 3, 3)); - allPane.add(new JScrollPane(sentText1)); - tabbedPane.add("Sent", new JScrollPane(sentText2)); - - // Add pop-up menu. - JPopupMenu menu = new JPopupMenu(); - JMenuItem menuItem1 = new JMenuItem("Copy"); - menuItem1.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) - { - // Get the clipboard - Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); - // Set the sent text as the new content of the clipboard - clipboard.setContents(new StringSelection(sentText1.getText()), null); - } - }); - - JMenuItem menuItem2 = new JMenuItem("Clear"); - menuItem2.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) - { - sentText1.setText(""); - sentText2.setText(""); - } - }); - - // Add listener to the text area so the popup menu can come up. - MouseListener popupListener = new PopupListener(menu); - sentText1.addMouseListener(popupListener); - sentText2.addMouseListener(popupListener); - menu.add(menuItem1); - menu.add(menuItem2); - - // Create UI elements for server generated XML traffic. - final JTextArea receivedText1 = new JTextArea(); - final JTextArea receivedText2 = new JTextArea(); - receivedText1.setEditable(false); - receivedText2.setEditable(false); - receivedText1.setForeground(new Color(6, 76, 133)); - receivedText2.setForeground(new Color(6, 76, 133)); - allPane.add(new JScrollPane(receivedText1)); - tabbedPane.add("Received", new JScrollPane(receivedText2)); - - // Add pop-up menu. - menu = new JPopupMenu(); - menuItem1 = new JMenuItem("Copy"); - menuItem1.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) - { - // Get the clipboard - Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); - // Set the sent text as the new content of the clipboard - clipboard.setContents(new StringSelection(receivedText1.getText()), null); - } - }); - - menuItem2 = new JMenuItem("Clear"); - menuItem2.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) - { - receivedText1.setText(""); - receivedText2.setText(""); - } - }); - - // Add listener to the text area so the popup menu can come up. - popupListener = new PopupListener(menu); - receivedText1.addMouseListener(popupListener); - receivedText2.addMouseListener(popupListener); - menu.add(menuItem1); - menu.add(menuItem2); - - // Create UI elements for interpreted XML traffic. - final JTextArea interpretedText1 = new JTextArea(); - final JTextArea interpretedText2 = new JTextArea(); - interpretedText1.setEditable(false); - interpretedText2.setEditable(false); - interpretedText1.setForeground(new Color(1, 94, 35)); - interpretedText2.setForeground(new Color(1, 94, 35)); - allPane.add(new JScrollPane(interpretedText1)); - tabbedPane.add("Interpreted", new JScrollPane(interpretedText2)); - - // Add pop-up menu. - menu = new JPopupMenu(); - menuItem1 = new JMenuItem("Copy"); - menuItem1.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) - { - // Get the clipboard - Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); - // Set the sent text as the new content of the clipboard - clipboard.setContents(new StringSelection(interpretedText1.getText()), null); - } - }); - - menuItem2 = new JMenuItem("Clear"); - menuItem2.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) - { - interpretedText1.setText(""); - interpretedText2.setText(""); - } - }); - - // Add listener to the text area so the popup menu can come up. - popupListener = new PopupListener(menu); - interpretedText1.addMouseListener(popupListener); - interpretedText2.addMouseListener(popupListener); - menu.add(menuItem1); - menu.add(menuItem2); - - debugFrame.getContentPane().add(tabbedPane); - - debugFrame.setSize(550, 400); - debugFrame.setVisible(true); - - // Create a special Reader that wraps the main Reader and logs data to the GUI. - Reader debugReader = new Reader() { - - Reader myReader = reader; - - public int read(char cbuf[], int off, int len) throws IOException { - int count = myReader.read(cbuf, off, len); - if (count > 0) { - String str = new String(cbuf, off, count); - int index = str.lastIndexOf(">"); - if (index != -1) { - receivedText1.append(str.substring(0, index+1)); - receivedText2.append(str.substring(0, index+1)); - receivedText1.append(NEWLINE); - receivedText2.append(NEWLINE); - if (str.length() > index) { - receivedText1.append(str.substring(index+1)); - receivedText2.append(str.substring(index+1)); - } - } - else { - receivedText1.append(str); - receivedText2.append(str); - } - } - return count; - } - - public void close() throws IOException { - myReader.close(); - } - - public int read() throws IOException { - return myReader.read(); - } - - public int read(char cbuf[]) throws IOException { - return myReader.read(cbuf); - } - - public long skip(long n) throws IOException { - return myReader.skip(n); - } - - public boolean ready() throws IOException { - return myReader.ready(); - } - - public boolean markSupported() { - return myReader.markSupported(); - } - - public void mark(int readAheadLimit) throws IOException { - myReader.mark(readAheadLimit); - } - - public void reset() throws IOException { - myReader.reset(); - } - }; - - // Create a special Writer that wraps the main Writer and logs data to the GUI. - Writer debugWriter = new Writer() { - - Writer myWriter = writer; - - public void write(char cbuf[], int off, int len) throws IOException { - myWriter.write(cbuf, off, len); - String str = new String(cbuf, off, len); - sentText1.append(str); - sentText2.append(str); - if (str.endsWith(">")) { - sentText1.append(NEWLINE); - sentText2.append(NEWLINE); - } - } - - public void flush() throws IOException { - myWriter.flush(); - } - - public void close() throws IOException { - myWriter.close(); - } - - public void write(int c) throws IOException { - myWriter.write(c); - } - - public void write(char cbuf[]) throws IOException { - myWriter.write(cbuf); - String str = new String(cbuf); - sentText1.append(str); - sentText2.append(str); - if (str.endsWith(">")) { - sentText1.append(NEWLINE); - sentText2.append(NEWLINE); - } - } - - public void write(String str) throws IOException { - myWriter.write(str); - sentText1.append(str); - sentText2.append(str); - if (str.endsWith(">")) { - sentText1.append(NEWLINE); - sentText2.append(NEWLINE); - } - } - - public void write(String str, int off, int len) throws IOException { - myWriter.write(str, off, len); - str = str.substring(off, off + len); - sentText1.append(str); - sentText2.append(str); - if (str.endsWith(">")) { - sentText1.append(NEWLINE); - sentText2.append(NEWLINE); - } - } - }; - - // Assign the reader/writer objects to use the debug versions. The packet reader - // and writer will use the debug versions when they are created. - reader = debugReader; - writer = debugWriter; - - // Create a thread that will listen for all incoming packets and write them to - // the GUI. This is what we call "interpreted" packet data, since it's the packet - // data as Smack sees it and not as it's coming in as raw XML. - PacketListener debugListener = new PacketListener() { - public void processPacket(Packet packet) { - interpretedText1.append(packet.toXML()); - interpretedText2.append(packet.toXML()); - interpretedText1.append(NEWLINE); - interpretedText2.append(NEWLINE); - } - }; - return debugListener; - } - - /** - * Listens for debug window popup dialog events. - */ - private class PopupListener extends MouseAdapter { - JPopupMenu popup; - - PopupListener(JPopupMenu popupMenu) { - popup = popupMenu; - } - - public void mousePressed(MouseEvent e) { - maybeShowPopup(e); - } - - public void mouseReleased(MouseEvent e) { - maybeShowPopup(e); - } - - private void maybeShowPopup(MouseEvent e) { - if (e.isPopupTrigger()) { - popup.show(e.getComponent(), e.getX(), e.getY()); - } - } - } }