1 /* -*- Mode: Java; tab-width: 4 -*-
3 * Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
5 * Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc.
6 * ("Apple") in consideration of your agreement to the following terms, and your
7 * use, installation, modification or redistribution of this Apple software
8 * constitutes acceptance of these terms. If you do not agree with these terms,
9 * please do not use, install, modify or redistribute this Apple software.
11 * In consideration of your agreement to abide by the following terms, and subject
12 * to these terms, Apple grants you a personal, non-exclusive license, under Apple's
13 * copyrights in this original Apple software (the "Apple Software"), to use,
14 * reproduce, modify and redistribute the Apple Software, with or without
15 * modifications, in source and/or binary forms; provided that if you redistribute
16 * the Apple Software in its entirety and without modifications, you must retain
17 * this notice and the following text and disclaimers in all such redistributions of
18 * the Apple Software. Neither the name, trademarks, service marks or logos of
19 * Apple Computer, Inc. may be used to endorse or promote products derived from the
20 * Apple Software without specific prior written permission from Apple. Except as
21 * expressly stated in this notice, no other rights or licenses, express or implied,
22 * are granted by Apple herein, including but not limited to any patent rights that
23 * may be infringed by your derivative works or by other works in which the Apple
24 * Software may be incorporated.
26 * The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO
27 * WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
28 * WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29 * PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
30 * COMBINATION WITH YOUR PRODUCTS.
32 * IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
33 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
34 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
35 * ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
36 * OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
37 * (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
38 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40 SimpleChat is a simple peer-to-peer chat program that demonstrates
41 DNS-SD registration, browsing, resolving and record-querying.
44 - implement better coloring algorithm
49 import java
.awt
.event
.*;
53 import javax
.swing
.event
.*;
54 import javax
.swing
.text
.*;
56 import com
.apple
.dnssd
.*;
59 class SimpleChat
implements ResolveListener
, RegisterListener
, QueryListener
,
60 ActionListener
, ItemListener
, Runnable
62 Document textDoc
; // Holds all the chat text
63 JTextField inputField
; // Holds a pending chat response
64 String ourName
; // name used to identify this user in chat
65 DNSSDService browser
; // object that actively browses for other chat clients
66 DNSSDService resolver
; // object that resolves other chat clients
67 DNSSDRegistration registration
; // object that maintains our connection advertisement
68 JComboBox targetPicker
; // Indicates who we're talking to
69 TargetListModel targetList
; // and its list model
70 JButton sendButton
; // Will send text in inputField to target
71 InetAddress buddyAddr
; // and address
72 int buddyPort
; // and port
73 DatagramPacket dataPacket
; // Inbound data packet
74 DatagramSocket outSocket
; // Outbound data socket
75 SimpleAttributeSet textAttribs
;
77 static final String kChatExampleRegType
= "_p2pchat._udp";
78 static final String kWireCharSet
= "ISO-8859-1";
80 public SimpleChat() throws Exception
82 JFrame frame
= new JFrame("SimpleChat");
83 frame
.addWindowListener(new WindowAdapter() {
84 public void windowClosing(WindowEvent e
) {System
.exit(0);}
87 ourName
= System
.getProperty( "user.name");
88 targetList
= new TargetListModel();
89 textAttribs
= new SimpleAttributeSet();
90 DatagramSocket inSocket
= new DatagramSocket();
91 dataPacket
= new DatagramPacket( new byte[ 4096], 4096);
92 outSocket
= new DatagramSocket();
94 this.setupSubPanes( frame
.getContentPane(), frame
.getRootPane());
96 frame
.setVisible(true);
97 inputField
.requestFocusInWindow();
99 browser
= DNSSD
.browse( 0, 0, kChatExampleRegType
, "", new SwingBrowseListener( targetList
));
101 registration
= DNSSD
.register( 0, 0, ourName
, kChatExampleRegType
, "", "", inSocket
.getLocalPort(), null, this);
103 new ListenerThread( this, inSocket
, dataPacket
).start();
106 protected void setupSubPanes( Container parent
, JRootPane rootPane
)
108 parent
.setLayout( new BoxLayout( parent
, BoxLayout
.Y_AXIS
));
110 JPanel textRow
= new JPanel();
111 textRow
.setLayout( new BoxLayout( textRow
, BoxLayout
.X_AXIS
));
112 textRow
.add( Box
.createRigidArea( new Dimension( 16, 0)));
113 JEditorPane textPane
= new JEditorPane( "text/html", "<BR>");
114 textPane
.setPreferredSize( new Dimension( 400, 300));
115 textPane
.setEditable( false);
116 JScrollPane textScroller
= new JScrollPane( textPane
);
117 textRow
.add( textScroller
);
118 textRow
.add( Box
.createRigidArea( new Dimension( 16, 0)));
119 textDoc
= textPane
.getDocument();
121 JPanel addressRow
= new JPanel();
122 addressRow
.setLayout( new BoxLayout( addressRow
, BoxLayout
.X_AXIS
));
123 targetPicker
= new JComboBox( targetList
);
124 targetPicker
.addItemListener( this);
125 addressRow
.add( Box
.createRigidArea( new Dimension( 16, 0)));
126 addressRow
.add( new JLabel( "Talk to: "));
127 addressRow
.add( targetPicker
);
128 addressRow
.add( Box
.createHorizontalGlue());
130 JPanel buttonRow
= new JPanel();
131 buttonRow
.setLayout( new BoxLayout( buttonRow
, BoxLayout
.X_AXIS
));
132 buttonRow
.add( Box
.createRigidArea( new Dimension( 16, 0)));
133 inputField
= new JTextField();
134 // prevent inputField from hijacking <Enter> key
135 inputField
.getKeymap().removeKeyStrokeBinding( KeyStroke
.getKeyStroke( KeyEvent
.VK_ENTER
, 0));
136 buttonRow
.add( inputField
);
137 sendButton
= new JButton( "Send");
138 buttonRow
.add( Box
.createRigidArea( new Dimension( 8, 0)));
139 buttonRow
.add( sendButton
);
140 buttonRow
.add( Box
.createRigidArea( new Dimension( 16, 0)));
141 rootPane
.setDefaultButton( sendButton
);
142 sendButton
.addActionListener( this);
143 sendButton
.setEnabled( false);
145 parent
.add( Box
.createRigidArea( new Dimension( 0, 16)));
146 parent
.add( textRow
);
147 parent
.add( Box
.createRigidArea( new Dimension( 0, 8)));
148 parent
.add( addressRow
);
149 parent
.add( Box
.createRigidArea( new Dimension( 0, 8)));
150 parent
.add( buttonRow
);
151 parent
.add( Box
.createRigidArea( new Dimension( 0, 16)));
154 public void serviceRegistered( DNSSDRegistration registration
, int flags
,
155 String serviceName
, String regType
, String domain
)
157 ourName
= serviceName
; // might have been renamed on collision
160 public void operationFailed( DNSSDService service
, int errorCode
)
162 System
.out
.println( "Service reported error " + String
.valueOf( errorCode
));
165 public void serviceResolved( DNSSDService resolver
, int flags
, int ifIndex
, String fullName
,
166 String hostName
, int port
, TXTRecord txtRecord
)
170 // Start a record query to obtain IP address from hostname
171 DNSSD
.queryRecord( 0, ifIndex
, hostName
, 1 /* ns_t_a */, 1 /* ns_c_in */,
172 new SwingQueryListener( this));
174 catch ( Exception e
) { terminateWithException( e
); }
178 public void queryAnswered( DNSSDService query
, int flags
, int ifIndex
, String fullName
,
179 int rrtype
, int rrclass
, byte[] rdata
, int ttl
)
182 buddyAddr
= InetAddress
.getByAddress( rdata
);
184 catch ( Exception e
) { terminateWithException( e
); }
185 sendButton
.setEnabled( true);
188 public void actionPerformed( ActionEvent e
) // invoked when Send button is hit
192 String sendString
= ourName
+ ": " + inputField
.getText();
193 byte[] sendData
= sendString
.getBytes( kWireCharSet
);
194 outSocket
.send( new DatagramPacket( sendData
, sendData
.length
, buddyAddr
, buddyPort
));
195 StyleConstants
.setForeground( textAttribs
, Color
.black
);
196 textDoc
.insertString( textDoc
.getLength(), inputField
.getText() + "\n", textAttribs
);
197 inputField
.setText( "");
199 catch ( Exception exception
) { terminateWithException( exception
); }
202 public void itemStateChanged( ItemEvent e
) // invoked when Target selection changes
204 sendButton
.setEnabled( false);
205 if ( e
.getStateChange() == ItemEvent
.SELECTED
)
208 TargetListElem sel
= (TargetListElem
) targetList
.getSelectedItem();
209 resolver
= DNSSD
.resolve( 0, sel
.fInt
, sel
.fServiceName
, sel
.fType
, sel
.fDomain
, this);
211 catch ( Exception exception
) { terminateWithException( exception
); }
215 public void run() // invoked on event thread when inbound packet arrives
219 String inMessage
= new String( dataPacket
.getData(), 0, dataPacket
.getLength(), kWireCharSet
);
220 StyleConstants
.setForeground( textAttribs
, this.getColorFor( dataPacket
.getData(), dataPacket
.getLength()));
221 textDoc
.insertString( textDoc
.getLength(), inMessage
+ "\n", textAttribs
);
223 catch ( Exception e
) { terminateWithException( e
); }
226 protected Color
getColorFor( byte[] chars
, int length
)
227 // Produce a mapping from a string to a color, suitable for text display
230 for ( int i
=0; i
< length
&& chars
[i
] != ':'; i
++)
231 rgb
= rgb ^
( (int) chars
[i
] << (i
%3+2) * 8);
232 return new Color( rgb
& 0x007F7FFF); // mask off high bits so it is a dark color
234 // for ( int i=0; i < length && chars[i] != ':'; i++)
238 protected static void terminateWithException( Exception e
)
244 public static void main(String s
[])
249 catch ( Exception e
) { terminateWithException( e
); }
257 public TargetListElem( String serviceName
, String domain
, String type
, int ifIndex
)
258 { fServiceName
= serviceName
; fDomain
= domain
; fType
= type
; fInt
= ifIndex
; }
260 public String
toString() { return fServiceName
; }
262 public String fServiceName
, fDomain
, fType
;
266 class TargetListModel
extends DefaultComboBoxModel
implements BrowseListener
268 /* The Browser invokes this callback when a service is discovered. */
269 public void serviceFound( DNSSDService browser
, int flags
, int ifIndex
,
270 String serviceName
, String regType
, String domain
)
272 TargetListElem match
= this.findMatching( serviceName
); // probably doesn't handle near-duplicates well.
275 this.addElement( new TargetListElem( serviceName
, domain
, regType
, ifIndex
));
278 /* The Browser invokes this callback when a service disappears. */
279 public void serviceLost( DNSSDService browser
, int flags
, int ifIndex
,
280 String serviceName
, String regType
, String domain
)
282 TargetListElem match
= this.findMatching( serviceName
); // probably doesn't handle near-duplicates well.
285 this.removeElement( match
);
288 /* The Browser invokes this callback when a service disappears. */
289 public void operationFailed( DNSSDService service
, int errorCode
)
291 System
.out
.println( "Service reported error " + String
.valueOf( errorCode
));
294 protected TargetListElem
findMatching( String match
)
296 for ( int i
= 0; i
< this.getSize(); i
++)
297 if ( match
.equals( this.getElementAt( i
).toString()))
298 return (TargetListElem
) this.getElementAt( i
);
305 // A ListenerThread runs its owner when datagram packet p appears on socket s.
306 class ListenerThread
extends Thread
308 public ListenerThread( Runnable owner
, DatagramSocket s
, DatagramPacket p
)
309 { fOwner
= owner
; fSocket
= s
; fPacket
= p
; }
317 fSocket
.receive( fPacket
);
318 SwingUtilities
.invokeAndWait( fOwner
); // process data on main thread
322 break; // terminate thread
327 protected Runnable fOwner
;
328 protected DatagramSocket fSocket
;
329 protected DatagramPacket fPacket
;