2 * Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved.
8 * This file contains Original Code and/or Modifications of Original Code
9 * as defined in and that are subject to the Apple Public Source License
10 * Version 2.0 (the 'License'). You may not use this file except in
11 * compliance with the License. Please obtain a copy of the License at
12 * http://www.opensource.apple.com/apsl/ and read it before using this
15 * The Original Code and all software distributed under the License are
16 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
17 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
18 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
20 * Please see the License for the specific language governing rights and
21 * limitations under the License.
23 * @APPLE_LICENSE_HEADER_END@
25 Change History (most recent first):
27 $Log: SimpleChat.java,v $
28 Revision 1.2 2004/04/30 21:53:35 rpantos
29 Change line endings for CVS.
31 Revision 1.1 2004/04/30 16:29:35 rpantos
34 SimpleChat is a simple peer-to-peer chat program that demonstrates
35 DNSSD registration, browsing, resolving and record-querying.
38 - remove TXTRecord tests
39 - remove diagnostic printf's
40 - implement better coloring algorithm
45 import java
.awt
.event
.*;
49 import javax
.swing
.event
.*;
50 import javax
.swing
.text
.*;
52 import com
.apple
.dnssd
.*;
55 class SimpleChat
implements ResolveListener
, RegisterListener
, QueryListener
,
56 ActionListener
, ItemListener
, Runnable
58 Document textDoc
; // Holds all the chat text
59 JTextField inputField
; // Holds a pending chat response
60 String ourName
; // name used to identify this user in chat
61 DNSSDService browser
; // object that actively browses for other chat clients
62 DNSSDService resolver
; // object that resolves other chat clients
63 DNSSDRegistration registration
; // object that maintains our connection advertisement
64 JComboBox targetPicker
; // Indicates who we're talking to
65 TargetListModel targetList
; // and its list model
66 JButton sendButton
; // Will send text in inputField to target
67 InetAddress buddyAddr
; // and address
68 int buddyPort
; // and port
69 DatagramPacket dataPacket
; // Inbound data packet
70 DatagramSocket outSocket
; // Outbound data socket
71 SimpleAttributeSet textAttribs
;
73 static final String kChatExampleRegType
= "_p2pchat._udp";
74 static final String kWireCharSet
= "ISO-8859-1";
76 public SimpleChat() throws Exception
78 JFrame frame
= new JFrame("SimpleChat");
79 frame
.addWindowListener(new WindowAdapter() {
80 public void windowClosing(WindowEvent e
) {System
.exit(0);}
83 ourName
= System
.getProperty( "user.name");
84 targetList
= new TargetListModel();
85 textAttribs
= new SimpleAttributeSet();
86 DatagramSocket inSocket
= new DatagramSocket();
87 dataPacket
= new DatagramPacket( new byte[ 4096], 4096);
88 outSocket
= new DatagramSocket();
90 this.setupSubPanes( frame
.getContentPane(), frame
.getRootPane());
92 frame
.setVisible(true);
93 inputField
.requestFocusInWindow();
95 browser
= DNSSD
.browse( 0, 0, kChatExampleRegType
, "", new SwingBrowseListener( targetList
));
97 TXTRecord tRec
= new TXTRecord();
98 tRec
.set( "name", "roger");
99 tRec
.set( "color", "blue");
101 registration
= DNSSD
.register( 0, 0, ourName
, kChatExampleRegType
, "", "", inSocket
.getLocalPort(), tRec
, 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
158 System
.out
.println( "ServiceRegisterCallback() invoked as " + serviceName
);
161 public void operationFailed( DNSSDService service
, int errorCode
)
163 System
.out
.println( "Service reported error " + String
.valueOf( errorCode
));
166 public void serviceResolved( DNSSDService resolver
, int flags
, int ifIndex
, String fullName
,
167 String hostName
, int port
, TXTRecord txtRecord
)
170 for ( int i
=0; null != ( a
= txtRecord
.getKey( i
)); i
++)
171 System
.out
.println( "attr/val " + String
.valueOf( i
) + ": " + a
+ "," + txtRecord
.getValueAsString( i
));
175 // Start a record query to obtain IP address from hostname
176 DNSSD
.queryRecord( 0, ifIndex
, hostName
, 1 /* ns_t_a */, 1 /* ns_c_in */,
177 new SwingQueryListener( this));
179 catch ( Exception e
) { terminateWithException( e
); }
183 public void queryAnswered( DNSSDService query
, int flags
, int ifIndex
, String fullName
,
184 int rrtype
, int rrclass
, byte[] rdata
, int ttl
)
187 buddyAddr
= InetAddress
.getByAddress( rdata
);
189 catch ( Exception e
) { terminateWithException( e
); }
190 sendButton
.setEnabled( true);
193 public void actionPerformed( ActionEvent e
) // invoked when Send button is hit
197 String sendString
= ourName
+ ": " + inputField
.getText();
198 byte[] sendData
= sendString
.getBytes( kWireCharSet
);
199 outSocket
.send( new DatagramPacket( sendData
, sendData
.length
, buddyAddr
, buddyPort
));
200 StyleConstants
.setForeground( textAttribs
, Color
.black
);
201 textDoc
.insertString( textDoc
.getLength(), inputField
.getText() + "\n", textAttribs
);
202 inputField
.setText( "");
204 catch ( Exception exception
) { terminateWithException( exception
); }
207 public void itemStateChanged( ItemEvent e
) // invoked when Target selection changes
209 sendButton
.setEnabled( false);
210 if ( e
.getStateChange() == ItemEvent
.SELECTED
)
213 TargetListElem sel
= (TargetListElem
) targetList
.getSelectedItem();
214 resolver
= DNSSD
.resolve( 0, sel
.fInt
, sel
.fServiceName
, sel
.fType
, sel
.fDomain
, this);
216 catch ( Exception exception
) { terminateWithException( exception
); }
220 public void run() // invoked on event thread when inbound packet arrives
224 String inMessage
= new String( dataPacket
.getData(), 0, dataPacket
.getLength(), kWireCharSet
);
225 StyleConstants
.setForeground( textAttribs
, this.getColorFor( dataPacket
.getData(), dataPacket
.getLength()));
226 textDoc
.insertString( textDoc
.getLength(), inMessage
+ "\n", textAttribs
);
228 catch ( Exception e
) { terminateWithException( e
); }
231 protected Color
getColorFor( byte[] chars
, int length
)
232 // Produce a mapping from a string to a color, suitable for text display
235 for ( int i
=0; i
< length
&& chars
[i
] != ':'; i
++)
236 rgb
= rgb ^
( (int) chars
[i
] << (i
%3+2) * 8);
237 return new Color( rgb
& 0x007F7FFF); // mask off high bits so it is a dark color
239 // for ( int i=0; i < length && chars[i] != ':'; i++)
243 protected static void terminateWithException( Exception e
)
249 public static void main(String s
[])
254 catch ( Exception e
) { terminateWithException( e
); }
262 public TargetListElem( String serviceName
, String domain
, String type
, int ifIndex
)
263 { fServiceName
= serviceName
; fDomain
= domain
; fType
= type
; fInt
= ifIndex
; }
265 public String
toString() { return fServiceName
; }
267 public String fServiceName
, fDomain
, fType
;
271 class TargetListModel
extends DefaultComboBoxModel
implements BrowseListener
273 /* The Browser invokes this callback when a service is discovered. */
274 public void serviceFound( DNSSDService browser
, int flags
, int ifIndex
,
275 String serviceName
, String regType
, String domain
)
277 TargetListElem match
= this.findMatching( serviceName
); // probably doesn't handle near-duplicates well.
280 this.addElement( new TargetListElem( serviceName
, domain
, regType
, ifIndex
));
283 /* The Browser invokes this callback when a service disappears. */
284 public void serviceLost( DNSSDService browser
, int flags
, int ifIndex
,
285 String serviceName
, String regType
, String domain
)
287 TargetListElem match
= this.findMatching( serviceName
); // probably doesn't handle near-duplicates well.
290 this.removeElement( match
);
293 /* The Browser invokes this callback when a service disappears. */
294 public void operationFailed( DNSSDService service
, int errorCode
)
296 System
.out
.println( "Service reported error " + String
.valueOf( errorCode
));
299 protected TargetListElem
findMatching( String match
)
301 for ( int i
= 0; i
< this.getSize(); i
++)
302 if ( match
.equals( this.getElementAt( i
).toString()))
303 return (TargetListElem
) this.getElementAt( i
);
310 // A ListenerThread runs its owner when datagram packet p appears on socket s.
311 class ListenerThread
extends Thread
313 public ListenerThread( Runnable owner
, DatagramSocket s
, DatagramPacket p
)
314 { fOwner
= owner
; fSocket
= s
; fPacket
= p
; }
322 fSocket
.receive( fPacket
);
323 SwingUtilities
.invokeAndWait( fOwner
); // process data on main thread
327 break; // terminate thread
332 protected Runnable fOwner
;
333 protected DatagramSocket fSocket
;
334 protected DatagramPacket fPacket
;