content: /* -*- Mode: Java; tab-width: 4 -*-
   *
   * Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
   *
   * Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple Computer, Inc.
   * ("Apple") in consideration of your agreement to the following terms, and your
   * use, installation, modification or redistribution of this Apple software
   * constitutes acceptance of these terms.  If you do not agree with these terms,
   * please do not use, install, modify or redistribute this Apple software.
   *
   * In consideration of your agreement to abide by the following terms, and subject
   * to these terms, Apple grants you a personal, non-exclusive license, under Apple's
   * copyrights in this original Apple software (the "Apple Software"), to use,
   * reproduce, modify and redistribute the Apple Software, with or without
   * modifications, in source and/or binary forms; provided that if you redistribute
   * the Apple Software in its entirety and without modifications, you must retain
   * this notice and the following text and disclaimers in all such redistributions of
   * the Apple Software.  Neither the name, trademarks, service marks or logos of
   * Apple Computer, Inc. may be used to endorse or promote products derived from the
   * Apple Software without specific prior written permission from Apple.  Except as
   * expressly stated in this notice, no other rights or licenses, express or implied,
   * are granted by Apple herein, including but not limited to any patent rights that
   * may be infringed by your derivative works or by other works in which the Apple
   * Software may be incorporated.
   *
   * The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO
   * WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
   * WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
   * PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
   * COMBINATION WITH YOUR PRODUCTS.
   *
   * IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
   * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
   * ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
   * OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
   * (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
  	BrowserApp demonstrates how to use DNS-SD to browse for and resolve services.
  
  	To do:
  	- display resolved TXTRecord
   */
  
  
  import java.awt.*;
  import java.awt.event.*;
  import java.util.*;
  import java.text.*;
  import javax.swing.*;
  import javax.swing.event.*;
  
  import com.apple.dnssd.*;
  
  
  class	BrowserApp implements ListSelectionListener, ResolveListener, Runnable
  {
  	static BrowserApp	app;
  	JFrame				frame;
  	DomainListModel		domainList;
  	BrowserListModel	servicesList, serviceList;
  	JList				domainPane, servicesPane, servicePane;
  	DNSSDService		servicesBrowser, serviceBrowser, domainBrowser;
  	JLabel				hostLabel, portLabel;
  	String				hostNameForUpdate;
  	int					portForUpdate;
  
  	public		BrowserApp()
  	{
  		frame = new JFrame("DNS-SD Service Browser");
  		frame.addWindowListener(new WindowAdapter() {
  			public void windowClosing(WindowEvent e) {System.exit(0);}
  		});
  
  		domainList = new DomainListModel();
  		servicesList = new ServicesBrowserListModel();
  		serviceList = new BrowserListModel();
  
  		try {
  			domainBrowser = DNSSD.enumerateDomains( DNSSD.BROWSE_DOMAINS, 0, domainList);
  
  			servicesBrowser = DNSSD.browse( 0, 0, "_services._dns-sd._udp.", "", servicesList);
  			serviceBrowser = null;
  		}
  		catch ( Exception ex) { terminateWithException( ex); }
  
  		this.setupSubPanes( frame.getContentPane());
  		frame.pack();
  		frame.setVisible(true);
  	}
  
  	protected void	setupSubPanes( Container parent)
  	{
  		parent.setLayout( new BoxLayout( parent, BoxLayout.Y_AXIS));
  
  		JPanel browserRow = new JPanel();
  		browserRow.setLayout( new BoxLayout( browserRow, BoxLayout.X_AXIS));
  		domainPane = new JList( domainList);
  		domainPane.addListSelectionListener( this);
  		JScrollPane domainScroller = new JScrollPane( domainPane, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
  		browserRow.add( domainScroller);
  		servicesPane = new JList( servicesList);
  		servicesPane.addListSelectionListener( this);
  		JScrollPane servicesScroller = new JScrollPane( servicesPane, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
  		browserRow.add( servicesScroller);
  		servicePane = new JList( serviceList);
  		servicePane.addListSelectionListener( this);
  		JScrollPane serviceScroller = new JScrollPane( servicePane, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
  		browserRow.add( serviceScroller);
  
  /*
  		JPanel buttonRow = new JPanel();
  		buttonRow.setLayout( new BoxLayout( buttonRow, BoxLayout.X_AXIS));
  		buttonRow.add( Box.createHorizontalGlue());
  		JButton connectButton = new JButton( "Don't Connect");
  		buttonRow.add( connectButton);
  		buttonRow.add( Box.createRigidArea( new Dimension( 16, 0)));
  */
  
  		JPanel labelRow = new JPanel();
  		labelRow.setLayout( new BoxLayout( labelRow, BoxLayout.X_AXIS));
  		labelRow.add( new JLabel( "  Host: "));
  		hostLabel = new JLabel();
  		labelRow.add( hostLabel);
  		labelRow.add( Box.createRigidArea( new Dimension( 32, 0)));
  		labelRow.add( new JLabel( "Port: "));
  		portLabel = new JLabel();
  		labelRow.add( portLabel);
  		labelRow.add( Box.createHorizontalGlue());
  
  		parent.add( browserRow);
  		parent.add( Box.createRigidArea( new Dimension( 0, 8)));
  		parent.add( labelRow);
  //		parent.add( buttonRow);
  		parent.add( Box.createRigidArea( new Dimension( 0, 16)));
  	}
  
  	public void valueChanged( ListSelectionEvent e)
  	{
  		try {
  			if ( e.getSource() == domainPane && !e.getValueIsAdjusting())
  			{
  				int		newSel = domainPane.getSelectedIndex();
  				if ( -1 != newSel)
  				{
  					if ( serviceBrowser != null)
  						serviceBrowser.stop();
  					serviceList.removeAllElements();
  					servicesBrowser = DNSSD.browse( 0, 0, "_services._dns-sd._udp.", "", servicesList);
  				}
  			}
  			else if ( e.getSource() == servicesPane && !e.getValueIsAdjusting())
  			{
  				int		newSel = servicesPane.getSelectedIndex();
  				if ( serviceBrowser != null)
  					serviceBrowser.stop();
  				serviceList.removeAllElements();
  				if ( -1 != newSel)
  					serviceBrowser = DNSSD.browse( 0, 0, servicesList.getNthRegType( newSel), "", serviceList);
  			}
  			else if ( e.getSource() == servicePane && !e.getValueIsAdjusting())
  			{
  				int		newSel = servicePane.getSelectedIndex();
  
  				hostLabel.setText( "");
  				portLabel.setText( "");
  
  				if ( -1 != newSel)
  				{
  					DNSSD.resolve( 0, serviceList.getNthInterface( newSel), 
  										serviceList.getNthServiceName( newSel), 
  										serviceList.getNthRegType( newSel), 
  										serviceList.getNthDomain( newSel), 
  										this);
  				}
  			}
  		}
  		catch ( Exception ex) { terminateWithException( ex); }
  	}
  
  	public void run()
  	{
  		hostLabel.setText( hostNameForUpdate);
  		portLabel.setText( String.valueOf( portForUpdate));		
  	}
  
  	public void	serviceResolved( DNSSDService resolver, int flags, int ifIndex, String fullName, 
  								String hostName, int port, TXTRecord txtRecord)
  	{
  		// We want to update GUI on the AWT event dispatching thread, but we can't stop
  		// the resolve from that thread, since stop() is synchronized with this callback.
  		// So, we stop the resolve on this thread, then invokeAndWait on the AWT event thread.
  
  		resolver.stop();
  
  		hostNameForUpdate = hostName;
  		portForUpdate = port;
  
  		try {
  			SwingUtilities.invokeAndWait(this);
  		}
  		catch ( Exception e)
  		{
  			e.printStackTrace();
  		}
  	}
  
  	public void	operationFailed( DNSSDService service, int errorCode)
  	{
  		service.stop();
  		// handle failure here
  	}
  
  	protected static void	terminateWithException( Exception e)
  	{
  		e.printStackTrace();
  		System.exit( -1);
  	}
  
      public static void main(String s[]) 
      {
  		app = new BrowserApp();
      }
  }
  
  
  class	BrowserListModel extends DefaultListModel implements BrowseListener, Runnable
  {
  	public		BrowserListModel()
  	{
  		addCache = new Vector();
  		removeCache = new Vector();
  	}
  
  	/* The Browser invokes this callback when a service is discovered. */
  	public void	serviceFound( DNSSDService browser, int flags, int ifIndex, 
  							String serviceName, String regType, String domain)
  	{
  		addCache.add( new BrowserListElem( serviceName, domain, regType, ifIndex));
  		if ( ( flags & DNSSD.MORE_COMING) == 0)
  			this.scheduleOnEventThread();
  	}
  
  	public void	serviceLost( DNSSDService browser, int flags, int ifIndex,
  							String serviceName, String regType, String domain)
  	{
  		removeCache.add( serviceName);
  		if ( ( flags & DNSSD.MORE_COMING) == 0)
  			this.scheduleOnEventThread();
  	}
  
  	public void	run()
  	{
  		while ( removeCache.size() > 0)
  		{
  			String	serviceName = (String) removeCache.remove( removeCache.size() - 1);
  			int		matchInd = this.findMatching( serviceName);	// probably doesn't handle near-duplicates well.
  			if ( matchInd != -1)
  				this.removeElementAt( matchInd);
  		}
  		while ( addCache.size() > 0)
  		{
  			BrowserListElem	elem = (BrowserListElem) addCache.remove( addCache.size() - 1);
  			if ( -1 == this.findMatching( elem.fServiceName))	// probably doesn't handle near-duplicates well.
  				this.addInSortOrder( elem);
  		}
  	}
  
  	public void	operationFailed( DNSSDService service, int errorCode)
  	{
  		// handle failure here
  	}
  
  	/* The list contains BrowserListElem's */
  	class	BrowserListElem
  	{
  		public	BrowserListElem( String serviceName, String domain, String type, int ifIndex)
  		{ fServiceName = serviceName; fDomain = domain; fType = type; fInt = ifIndex; }
  		
  		public String	toString() { return fServiceName; }
  		
  		public String	fServiceName, fDomain, fType;
  		public int		fInt;
  	}
  
  	public String	getNthServiceName( int n)
  	{
  		BrowserListElem sel = (BrowserListElem) this.get( n);
  		return sel.fServiceName;
  	}
  
  	public String	getNthRegType( int n)
  	{
  		BrowserListElem sel = (BrowserListElem) this.get( n);
  		return sel.fType;
  	}
  
  	public String	getNthDomain( int n)
  	{
  		BrowserListElem sel = (BrowserListElem) this.get( n);
  		return sel.fDomain;
  	}
  
  	public int		getNthInterface( int n)
  	{
  		BrowserListElem sel = (BrowserListElem) this.get( n);
  		return sel.fInt;
  	}
  
  	protected void	addInSortOrder( Object obj)
  	{
  		int	i;
  		for ( i = 0; i < this.size(); i++)
  			if ( sCollator.compare( obj.toString(), this.getElementAt( i).toString()) < 0)
  				break;
  		this.add( i, obj);
  	}
  
  	protected int	findMatching( String match)
  	{
  		for ( int i = 0; i < this.size(); i++)
  			if ( match.equals( this.getElementAt( i).toString()))
  				return i;
  		return -1;
  	}
  
  	protected void	scheduleOnEventThread()
  	{
  		try {
  			SwingUtilities.invokeAndWait( this);
  		}
  		catch ( Exception e)
  		{
  			e.printStackTrace();
  		}
  	}		
  
  	protected Vector	removeCache;	// list of serviceNames to remove
  	protected Vector	addCache;		// list of BrowserListElem's to add
  
  	protected static Collator	sCollator;
  
  	static	// Initialize our static variables
  	{
  		sCollator = Collator.getInstance();
  		sCollator.setStrength( Collator.PRIMARY);
  	}
  }
  
  
  class	ServicesBrowserListModel extends BrowserListModel
  {
  	/* The Browser invokes this callback when a service is discovered. */
  	public void	serviceFound( DNSSDService browser, int flags, int ifIndex, 
  							String serviceName, String regType, String domain)
  	// Overridden to stuff serviceName into regType and make serviceName human-readable.
  	{
  		regType = serviceName + ( regType.startsWith( "_udp.") ? "._udp." : "._tcp.");
  		super.serviceFound( browser, flags, ifIndex, this.mapTypeToName( serviceName), regType, domain);
  	}
  
  	public void	serviceLost( DNSSDService browser, int flags, int ifIndex, 
  							String serviceName, String regType, String domain)
  	// Overridden to make serviceName human-readable.
  	{
  		super.serviceLost( browser, flags, ifIndex, this.mapTypeToName( serviceName), regType, domain);
  	}
  
  	protected String	mapTypeToName( String type)
  	// Convert a registration type into a human-readable string. Returns original string on no-match.
  	{
  		final String[]	namedServices = {
  			"_afpovertcp",	"Apple File Sharing",
  			"_http",		"World Wide Web servers",
  			"_daap",		"Digital Audio Access",
  			"_apple-sasl",	"Apple Password Servers",
  			"_distcc",		"Distributed Compiler nodes",
  			"_finger",		"Finger servers",
  			"_ichat",		"iChat clients",
  			"_presence",	"iChat AV clients",
  			"_ssh",			"SSH servers",
  			"_telnet",		"Telnet servers",
  			"_workstation",	"Macintosh Manager clients",
  			"_bootps",		"BootP servers",
  			"_xserveraid",	"XServe RAID devices",
  			"_eppc",		"Remote AppleEvents",
  			"_ftp",			"FTP services",
  			"_tftp",		"TFTP services"
  		};
  
  		for ( int i = 0; i < namedServices.length; i+=2)
  			if ( namedServices[i].equals( type))
  				return namedServices[i + 1];
  		return type;
  	}
  }
  
  
  class	DomainListModel extends DefaultListModel implements DomainListener
  {
  	/* Called when a domain is discovered. */
  	public void	domainFound( DNSSDService domainEnum, int flags, int ifIndex, String domain)
  	{
  		if ( !this.contains( domain))
  			this.addElement( domain);
  	}
  	
  	public void	domainLost( DNSSDService domainEnum, int flags, int ifIndex, String domain)
  	{
  		if ( this.contains( domain))
  			this.removeElement( domain);
  	}
  
  	public void	operationFailed( DNSSDService service, int errorCode)
  	{
  		// handle failure here
  	}
  }
  
  