FileDocCategorySizeDatePackage
TicTacToe.javaAPI DocExample7673Sat Jul 09 14:57:14 BST 2005None

TicTacToe.java

import java.util.HashMap;
import java.nio.*;
import java.nio.channels.*;
import java.net.InetSocketAddress;
import javax.swing.*;
import javax.swing.event.*;
import com.apple.dnssd.*;

// Our TicTacToe object does the following:
// 1. It's a JFrame window. It's a DNSSD BrowseListener so it
//    gets add and remove events to tell it what to show in the window,
//    and a ListSelectionListener so it knows what the user clicked.
// 2. It listens for incoming connections. It opens a listening TCP
//    socket and advertises the listening TCP socket with Bonjour.
//    It's our RegisterListener, so that it knows our advertised name:
//     - To display it in the window title bar
//     - To exclude it from the list of discovered peers on the network

// To safely call Swing routines to update the user interface,
// we have to call them from the Swing event-dispatching thread.
// To do this, we make little Runnable objects where necessary
// and pass them to SwingUtilities.invokeAndWait(). This makes
// their run() method execute on event-dispatching thread where
// it can safely make the calls it needs. For more details, see:
// <http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html>

public class TicTacToe extends JFrame implements Runnable,
	RegisterListener, BrowseListener, ListSelectionListener
	{
	public static void main(String[] args)
		{
		Runnable runOnSwingThread = new Runnable()
			{ public void run() { new TicTacToe(); } };
		try { SwingUtilities.invokeAndWait(runOnSwingThread); }
		catch (Exception e) { e.printStackTrace(); }
		}

	public static final String ServiceType = "_tic-tac-toe-ex._tcp";
	public String myName;
	public HashMap activeGames;
	private DefaultListModel gameList;
	private JList players;
	private ServerSocketChannel listentingChannel;
	private int listentingPort;

	// NOTE: Because a TicTacToe is a JFrame, the caller MUST be running
	// on the event-dispatching thread before trying to create one.
	public TicTacToe()
		{
		super("Tic-Tac-Toe");
		try
			{
			// 1. Make the browsing window, and start browsing
			activeGames = new HashMap();
			gameList = new DefaultListModel();
			players = new JList(gameList);
			players.addListSelectionListener(this);
			getContentPane().add(new JScrollPane(players));
			setSize(200, 300);
			setDefaultCloseOperation(EXIT_ON_CLOSE);
			setVisible(true);
			DNSSD.browse(ServiceType, this);
			
			// 2. Make listening socket and advertise it
			listentingChannel = ServerSocketChannel.open();
			listentingChannel.socket().bind(new InetSocketAddress(0));
			listentingPort = listentingChannel.socket().getLocalPort();
			setTitle(listentingPort + " registering");
			DNSSD.register(null, ServiceType, listentingPort, this);
	
			// 3. If we sit here and hog the event-dispatching thread
			// the whole UI will freeze up, so instead we create a new
			// background thread to receive incoming connection requests.
			new Thread(this).start();
			}
		catch (Exception e) { e.printStackTrace(); }
		}

	public void operationFailed(DNSSDService service, int errorCode)
		{
		System.out.println("Bonjour operation failed " + errorCode);
		System.exit(-1);
		}

	// If our name changes while we're running, we update window title.
	// In the event that we're registering in multiple domains (Wide-Area
	// Bonjour) we'll use the local (mDNS) name for display purposes.
	public void serviceRegistered(DNSSDRegistration sd, int flags,
		String serviceName, String regType, String domain)
		{
		if (!domain.equalsIgnoreCase("local.")) return;
		myName = serviceName;
		Runnable r = new Runnable()
			{ public void run() { setTitle(listentingPort + " " + myName); } };
		try { SwingUtilities.invokeAndWait(r); }
		catch (Exception e) { e.printStackTrace(); }
		}

	// Our serviceFound and serviceLost callbacks just make Adder and
	// Remover objects that safely run on the event-dispatching thread
	// so they can modify the user interface
	public void serviceFound(DNSSDService browser, int flags, int ind,
						String name, String type, String domain)
		{
		if (name.equals(myName)) return;	// Don't add ourselves to the list
		DiscoveredInstance x = new DiscoveredInstance(ind, name, domain);
		try { SwingUtilities.invokeAndWait(new Adder(x)); }
		catch (Exception e) { e.printStackTrace(); }
		}

	public void serviceLost(DNSSDService browser, int flags, int ind,
		String name, String regType, String domain)
		{
		DiscoveredInstance x = new DiscoveredInstance(ind, name, domain);
		try { SwingUtilities.invokeAndWait(new Remover(x)); }
		catch (Exception e) { e.printStackTrace(); }
		}

	// The Adder and Remover classes update the list of discovered instances
	private class Adder implements Runnable
		{
		private DiscoveredInstance add;
		public Adder(DiscoveredInstance a) { add = a; }
		public void run() { gameList.addElement(add); }
		}

	private class Remover implements Runnable
		{
		private DiscoveredInstance rmv;
		public Remover(DiscoveredInstance r) { rmv = r; }
		public void run()
			{
			String name = rmv.toString();
			for (int i = 0; i < gameList.size(); i++)
				{
				if (gameList.getElementAt(i).toString().equals(name))
					{ gameList.removeElementAt(i); return; }
				}
			}
		}

	// When the user clicks in our list, if we already have a
	// GameBoard we bring it to the front, otherwise we make
	// a new GameBoard and initiate a new outgoing connection.
	public void valueChanged(ListSelectionEvent event)
		{
		int selected = players.getSelectedIndex();
		if (selected != -1)
			{
			DiscoveredInstance x =
				(DiscoveredInstance)players.getSelectedValue();
			GameBoard game = (GameBoard)activeGames.get(x.toString());
			if (game != null) game.toFront();
			else x.resolve(new GameBoard(this, x.toString(), null));
			}
		}

	// When we receive an incoming connection, GameReceiver reads the
	// peer name from the connection and then makes a new GameBoard for it.
	private class GameReceiver implements Runnable
		{
		private SocketChannel sc;
		public GameReceiver(SocketChannel s) { sc = s; }
		public void run()
			{
			try
				{
				ByteBuffer buffer = ByteBuffer.allocate(4 + 128);
				CharBuffer charBuffer = buffer.asCharBuffer();
				sc.read(buffer);
				int length = buffer.getInt(0);
				char[] c = new char[length];
				charBuffer.position(2);
				charBuffer.get(c, 0, length);
				String serviceName = new String(c);
				GameBoard game = new GameBoard(TicTacToe.this, serviceName, sc);
				}
			catch (Exception e) { e.printStackTrace(); }
			}
		}

	// Our run() method just sits and waits for incoming connections
	// and hands each one off to a new thread to handle it.
	public void run()
		{
		try
			{
			while (true)
				{
				SocketChannel sc = listentingChannel.accept();
				if (sc != null) new Thread(new GameReceiver(sc)).start();
				}
			}
		catch (Exception e) { e.printStackTrace(); }
		}
	
	// Our inner class DiscoveredInstance has two special properties
	// It has a custom toString() method to display discovered
	// instances the way we want them to appear, and a resolve()
	// method, which asks it to resolve the named service it represents
	// and pass the result to the specified ResolveListener
	public class DiscoveredInstance
		{
		private int ind;
		private String name, domain;
		
		public DiscoveredInstance(int i, String n, String d)
			{ ind = i; name = n; domain = d; }
		
		public String toString()
			{
			String i = DNSSD.getNameForIfIndex(ind);
			return(i + " " + name + " (" + domain + ")");
			}

		public void resolve(ResolveListener x)
			{
			try { DNSSD.resolve(0, ind, name, ServiceType, domain, x); }
			catch (DNSSDException e) { e.printStackTrace(); }
			}
		}
	}