alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

ActiveMQ example source code file (JmDNS.java)

This example ActiveMQ source code file (JmDNS.java) is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Java - ActiveMQ tags/keywords

dnsoutgoing, dnsrecord, dnsrecord, io, ioexception, iterator, iterator, jmdns, list, log, logging, net, network, object, serviceinfo, serviceinfo, string, string, timertask, util

The ActiveMQ JmDNS.java source code

/**
 * Copyright 2003-2005 Arthur van Hoff, Rick Blair
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.activemq.jmdns;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

// REMIND: multiple IP addresses

/**
 * mDNS implementation in Java.
 *
 * @version %I%, %G%
 * @author	Arthur van Hoff, Rick Blair, Jeff Sonstein,
 * Werner Randelshofer, Pierre Frisch, Scott Lewis
 */
public class JmDNS
{
    private static Logger logger = Logger.getLogger(JmDNS.class.toString());
    /**
     * The version of JmDNS.
     */
    public static String VERSION = "2.0";

    /**
     * This is the multicast group, we are listening to for multicast DNS messages.
     */
    private InetAddress group;
    /**
     * This is our multicast socket.
     */
    private MulticastSocket socket;

    /**
     * Used to fix live lock problem on unregester.
     */

     protected boolean closed = false;

    /**
     * Holds instances of JmDNS.DNSListener.
     * Must by a synchronized collection, because it is updated from
     * concurrent threads.
     */
    private List listeners;
    /**
     * Holds instances of ServiceListener's.
     * Keys are Strings holding a fully qualified service type.
     * Values are LinkedList's of ServiceListener's.
     */
    private Map serviceListeners;
    /**
     * Holds instances of ServiceTypeListener's.
     */
    private List typeListeners;


    /**
     * Cache for DNSEntry's.
     */
    private DNSCache cache;

    /**
     * This hashtable holds the services that have been registered.
     * Keys are instances of String which hold an all lower-case version of the
     * fully qualified service name.
     * Values are instances of ServiceInfo.
     */
    Map services;

    /**
     * This hashtable holds the service types that have been registered or
     * that have been received in an incoming datagram.
     * Keys are instances of String which hold an all lower-case version of the
     * fully qualified service type.
     * Values hold the fully qualified service type.
     */
    Map serviceTypes;
    /**
     * This is the shutdown hook, we registered with the java runtime.
     */
    private Thread shutdown;

    /**
     * Handle on the local host
     */
    HostInfo localHost;

    private Thread incomingListener = null;

    /**
     * Throttle count.
     * This is used to count the overall number of probes sent by JmDNS.
     * When the last throttle increment happened .
     */
    private int throttle;
    /**
     * Last throttle increment.
     */
    private long lastThrottleIncrement;

    /**
     * The timer is used to dispatch all outgoing messages of JmDNS.
     * It is also used to dispatch maintenance tasks for the DNS cache.
     */
    private Timer timer;

    /**
     * The source for random values.
     * This is used to introduce random delays in responses. This reduces the
     * potential for collisions on the network.
     */
    private final static Random random = new Random();

    /**
     * This lock is used to coordinate processing of incoming and outgoing
     * messages. This is needed, because the Rendezvous Conformance Test
     * does not forgive race conditions.
     */
    private Object ioLock = new Object();

    /**
     * If an incoming package which needs an answer is truncated, we store it
     * here. We add more incoming DNSRecords to it, until the JmDNS.Responder
     * timer picks it up.
     * Remind: This does not work well with multiple planned answers for packages
     * that came in from different clients.
     */
    private DNSIncoming plannedAnswer;
    
    // State machine
    /**
     * The state of JmDNS.
     * <p/>
     * For proper handling of concurrency, this variable must be
     * changed only using methods advanceState(), revertState() and cancel().
     */
    private DNSState state = DNSState.PROBING_1;

    /**
     * Timer task associated to the host name.
     * This is used to prevent from having multiple tasks associated to the host
     * name at the same time.
     */
    TimerTask task;

    /**
     * This hashtable is used to maintain a list of service types being collected
     * by this JmDNS instance.
     * The key of the hashtable is a service type name, the value is an instance
     * of JmDNS.ServiceCollector.
     *
     * @see #list
     */
    private HashMap serviceCollectors = new HashMap();

    /**
     * Create an instance of JmDNS.
     */
    public JmDNS() throws IOException
    {
        logger.finer("JmDNS instance created");
        try
        {
            InetAddress addr = InetAddress.getLocalHost();
            init(addr.isLoopbackAddress() ? null : addr, addr.getHostName()); // [PJYF Oct 14 2004] Why do we disallow the loopback address?
        }
        catch (IOException e)
        {
            init(null, "computer");
        }
    }

    /**
     * Create an instance of JmDNS and bind it to a
     * specific network interface given its IP-address.
     */
    public JmDNS(InetAddress addr) throws IOException
    {
        try
        {
            init(addr, addr.getHostName());
        }
        catch (IOException e)
        {
            init(null, "computer");
        }
    }

    /**
     * Initialize everything.
     *
     * @param address The interface to which JmDNS binds to.
     * @param name    The host name of the interface.
     */
    private void init(InetAddress address, String name) throws IOException
    {
        // A host name with "." is illegal. so strip off everything and append .local.
        int idx = name.indexOf(".");
        if (idx > 0)
        {
            name = name.substring(0, idx);
        }
        name += ".local.";
        // localHost to IP address binding
        localHost = new HostInfo(address, name);

        cache = new DNSCache(100);

        listeners = Collections.synchronizedList(new ArrayList());
        serviceListeners = new HashMap();
        typeListeners = new ArrayList();

        services = new Hashtable(20);
        serviceTypes = new Hashtable(20);

        timer = new Timer("JmDNS.Timer");
        new RecordReaper().start();
        shutdown = new Thread(new Shutdown(), "JmDNS.Shutdown");
        Runtime.getRuntime().addShutdownHook(shutdown);

        incomingListener = new Thread(new SocketListener(), "JmDNS.SocketListener");

        // Bind to multicast socket
        openMulticastSocket(localHost);
        start(services.values());
    }

    private void start(Collection serviceInfos)
    {
        state = DNSState.PROBING_1;
        incomingListener.start();
        new Prober().start();
        for (Iterator iterator = serviceInfos.iterator(); iterator.hasNext();)
        {
            try
            {
                registerService(new ServiceInfo((ServiceInfo) iterator.next()));
            }
            catch (Exception exception)
            {
                logger.log(Level.WARNING, "start() Registration exception ", exception);
            }
        }
    }

    private void openMulticastSocket(HostInfo hostInfo) throws IOException
    {
        if (group == null)
        {
            group = InetAddress.getByName(DNSConstants.MDNS_GROUP);
        }
        if (socket != null)
        {
            this.closeMulticastSocket();
        }
        socket = new MulticastSocket(DNSConstants.MDNS_PORT);
        if ((hostInfo != null) && (localHost.getInterface() != null))
        {
            socket.setNetworkInterface(hostInfo.getInterface());
        }
        socket.setTimeToLive(255);
        socket.joinGroup(group);
    }

    private void closeMulticastSocket()
    {
        logger.finer("closeMulticastSocket()");
        if (socket != null)
        {
            // close socket
            try
            {
                socket.leaveGroup(group);
                socket.close();
                if (incomingListener != null)
                {
                    incomingListener.join();
                }
            }
            catch (Exception exception)
            {
                logger.log(Level.WARNING, "closeMulticastSocket() Close socket exception ", exception);
            }
            socket = null;
        }
    }
    
    // State machine
    /**
     * Sets the state and notifies all objects that wait on JmDNS.
     */
    synchronized void advanceState()
    {
        state = state.advance();
        notifyAll();
    }

    /**
     * Sets the state and notifies all objects that wait on JmDNS.
     */
    synchronized void revertState()
    {
        state = state.revert();
        notifyAll();
    }

    /**
     * Sets the state and notifies all objects that wait on JmDNS.
     */
    synchronized void cancel()
    {
        state = DNSState.CANCELED;
        notifyAll();
    }

    /**
     * Returns the current state of this info.
     */
    DNSState getState()
    {
        return state;
    }


    /**
     * Return the DNSCache associated with the cache variable
     */
    DNSCache getCache()
    {
        return cache;
    }

    /**
     * Return the HostName associated with this JmDNS instance.
     * Note: May not be the same as what started.  The host name is subject to
     * negotiation.
     */
    public String getHostName()
    {
        return localHost.getName();
    }

    public HostInfo getLocalHost()
    {
        return localHost;
    }

    /**
     * Return the address of the interface to which this instance of JmDNS is
     * bound.
     */
    public InetAddress getInterface() throws IOException
    {
        return socket.getInterface();
    }

    /**
     * Get service information. If the information is not cached, the method
     * will block until updated information is received.
     * <p/>
     * Usage note: Do not call this method from the AWT event dispatcher thread.
     * You will make the user interface unresponsive.
     *
     * @param type fully qualified service type, such as <code>_http._tcp.local. .
     * @param name unqualified service name, such as <code>foobar .
     * @return null if the service information cannot be obtained
     */
    public ServiceInfo getServiceInfo(String type, String name)
    {
        return getServiceInfo(type, name, 3 * 1000);
    }

    /**
     * Get service information. If the information is not cached, the method
     * will block for the given timeout until updated information is received.
     * <p/>
     * Usage note: If you call this method from the AWT event dispatcher thread,
     * use a small timeout, or you will make the user interface unresponsive.
     *
     * @param type    full qualified service type, such as <code>_http._tcp.local. .
     * @param name    unqualified service name, such as <code>foobar .
     * @param timeout timeout in milliseconds
     * @return null if the service information cannot be obtained
     */
    public ServiceInfo getServiceInfo(String type, String name, int timeout)
    {
        ServiceInfo info = new ServiceInfo(type, name);
        new ServiceInfoResolver(info).start();

        try
        {
            long end = System.currentTimeMillis() + timeout;
            long delay;
            synchronized (info)
            {
                while (!info.hasData() && (delay = end - System.currentTimeMillis()) > 0)
                {
                    info.wait(delay);
                }
            }
        }
        catch (InterruptedException e)
        {
            // empty
        }

        return (info.hasData()) ? info : null;
    }

    /**
     * Request service information. The information about the service is
     * requested and the ServiceListener.resolveService method is called as soon
     * as it is available.
     * <p/>
     * Usage note: Do not call this method from the AWT event dispatcher thread.
     * You will make the user interface unresponsive.
     *
     * @param type full qualified service type, such as <code>_http._tcp.local. .
     * @param name unqualified service name, such as <code>foobar .
     */
    public void requestServiceInfo(String type, String name)
    {
        requestServiceInfo(type, name, 3 * 1000);
    }

    /**
     * Request service information. The information about the service is requested
     * and the ServiceListener.resolveService method is called as soon as it is available.
     *
     * @param type    full qualified service type, such as <code>_http._tcp.local. .
     * @param name    unqualified service name, such as <code>foobar .
     * @param timeout timeout in milliseconds
     */
    public void requestServiceInfo(String type, String name, int timeout)
    {
        registerServiceType(type);
        ServiceInfo info = new ServiceInfo(type, name);
        new ServiceInfoResolver(info).start();

        try
        {
            long end = System.currentTimeMillis() + timeout;
            long delay;
            synchronized (info)
            {
                while (!info.hasData() && (delay = end - System.currentTimeMillis()) > 0)
                {
                    info.wait(delay);
                }
            }
        }
        catch (InterruptedException e)
        {
            // empty
        }
    }

    void handleServiceResolved(ServiceInfo info)
    {
        List list = (List) serviceListeners.get(info.type.toLowerCase());
        if (list != null)
        {
            ServiceEvent event = new ServiceEvent(this, info.type, info.getName(), info);
            // Iterate on a copy in case listeners will modify it
            final ArrayList listCopy = new ArrayList(list);
            for (Iterator iterator = listCopy.iterator(); iterator.hasNext();)
            {
                ((ServiceListener) iterator.next()).serviceResolved(event);
            }
        }
    }

    /**
     * Listen for service types.
     *
     * @param listener listener for service types
     */
    public void addServiceTypeListener(ServiceTypeListener listener) throws IOException
    {
        synchronized (this)
        {
            typeListeners.remove(listener);
            typeListeners.add(listener);
        }

        // report cached service types
        for (Iterator iterator = serviceTypes.values().iterator(); iterator.hasNext();)
        {
            listener.serviceTypeAdded(new ServiceEvent(this, (String) iterator.next(), null, null));
        }

        new TypeResolver().start();
    }

    /**
     * Remove listener for service types.
     *
     * @param listener listener for service types
     */
    public void removeServiceTypeListener(ServiceTypeListener listener)
    {
        synchronized (this)
        {
            typeListeners.remove(listener);
        }
    }

    /**
     * Listen for services of a given type. The type has to be a fully qualified
     * type name such as <code>_http._tcp.local..
     *
     * @param type     full qualified service type, such as <code>_http._tcp.local..
     * @param listener listener for service updates
     */
    public void addServiceListener(String type, ServiceListener listener)
    {
        String lotype = type.toLowerCase();
        removeServiceListener(lotype, listener);
        List list = null;
        synchronized (this)
        {
            list = (List) serviceListeners.get(lotype);
            if (list == null)
            {
                list = Collections.synchronizedList(new LinkedList());
                serviceListeners.put(lotype, list);
            }
            list.add(listener);
        }

        // report cached service types
        for (Iterator i = cache.iterator(); i.hasNext();)
        {
            for (DNSCache.CacheNode n = (DNSCache.CacheNode) i.next(); n != null; n = n.next())
            {
                DNSRecord rec = (DNSRecord) n.getValue();
                if (rec.type == DNSConstants.TYPE_SRV)
                {
                    if (rec.name.endsWith(type))
                    {
                        listener.serviceAdded(new ServiceEvent(this, type, toUnqualifiedName(type, rec.name), null));
                    }
                }
            }
        }
        new ServiceResolver(type).start();
    }

    /**
     * Remove listener for services of a given type.
     *
     * @param listener listener for service updates
     */
    public void removeServiceListener(String type, ServiceListener listener)
    {
        type = type.toLowerCase();
        List list = (List) serviceListeners.get(type);
        if (list != null)
        {
            synchronized (this)
            {
                list.remove(listener);
                if (list.size() == 0)
                {
                    serviceListeners.remove(type);
                }
            }
        }
    }

    /**
     * Register a service. The service is registered for access by other jmdns clients.
     * The name of the service may be changed to make it unique.
     */
    public void registerService(ServiceInfo info) throws IOException
    {
        registerServiceType(info.type);

        // bind the service to this address
        info.server = localHost.getName();
        info.addr = localHost.getAddress();

        synchronized (this)
        {
            makeServiceNameUnique(info);
            services.put(info.getQualifiedName().toLowerCase(), info);
        }

        new /*Service*/Prober().start();
        try
        {
            synchronized (info)
            {
                while (info.getState().compareTo(DNSState.ANNOUNCED) < 0)
                {
                    info.wait();
                }
            }
        }
        catch (InterruptedException e)
        {
            //empty
        }
        logger.fine("registerService() JmDNS registered service as " + info);
    }

    /**
     * Unregister a service. The service should have been registered.
     */
    public void unregisterService(ServiceInfo info)
    {
        synchronized (this)
        {
            services.remove(info.getQualifiedName().toLowerCase());
        }
        info.cancel();

        // Note: We use this lock object to synchronize on it.
        //       Synchronizing on another object (e.g. the ServiceInfo) does
        //       not make sense, because the sole purpose of the lock is to
        //       wait until the canceler has finished. If we synchronized on
        //       the ServiceInfo or on the Canceler, we would block all
        //       accesses to synchronized methods on that object. This is not
        //       what we want!
        Object lock = new Object();
        new Canceler(info, lock).start();

        // Remind: We get a deadlock here, if the Canceler does not run!
        try
        {
            synchronized (lock)
            {
                lock.wait();
            }
        }
        catch (InterruptedException e)
        {
            // empty
        }
    }

    /**
     * Unregister all services.
     */
    public void unregisterAllServices()
    {
        logger.finer("unregisterAllServices()");
        if (services.size() == 0)
        {
            return;
        }

        Collection list;
        synchronized (this)
        {
            list = new LinkedList(services.values());
            services.clear();
        }
        for (Iterator iterator = list.iterator(); iterator.hasNext();)
        {
            ((ServiceInfo) iterator.next()).cancel();
        }


        Object lock = new Object();
        new Canceler(list, lock).start();
              // Remind: We get a livelock here, if the Canceler does not run!
        try {
            synchronized (lock) {
                if (!closed) {
                    lock.wait();
                }
            }
        } catch (InterruptedException e) {
            // empty
        }


    }

    /**
     * Register a service type. If this service type was not already known,
     * all service listeners will be notified of the new service type. Service types
     * are automatically registered as they are discovered.
     */
    public void registerServiceType(String type)
    {
        String name = type.toLowerCase();
        if (serviceTypes.get(name) == null)
        {
            if ((type.indexOf("._mdns._udp.") < 0) && !type.endsWith(".in-addr.arpa."))
            {
                Collection list;
                synchronized (this)
                {
                    serviceTypes.put(name, type);
                    list = new LinkedList(typeListeners);
                }
                for (Iterator iterator = list.iterator(); iterator.hasNext();)
                {
                    ((ServiceTypeListener) iterator.next()).serviceTypeAdded(new ServiceEvent(this, type, null, null));
                }
            }
        }
    }

    /**
     * Generate a possibly unique name for a host using the information we
     * have in the cache.
     *
     * @return returns true, if the name of the host had to be changed.
     */
    private boolean makeHostNameUnique(DNSRecord.Address host)
    {
        String originalName = host.getName();
        long now = System.currentTimeMillis();

        boolean collision;
        do
        {
            collision = false;

            // Check for collision in cache
            for (DNSCache.CacheNode j = cache.find(host.getName().toLowerCase()); j != null; j = j.next())
            {
                DNSRecord a = (DNSRecord) j.getValue();
                if (false)
                {
                    host.name = incrementName(host.getName());
                    collision = true;
                    break;
                }
            }
        }
        while (collision);

        if (originalName.equals(host.getName()))
        {
            return false;
        }
        else
        {
            return true;
        }
    }

    /**
     * Generate a possibly unique name for a service using the information we
     * have in the cache.
     *
     * @return returns true, if the name of the service info had to be changed.
     */
    private boolean makeServiceNameUnique(ServiceInfo info)
    {
        String originalQualifiedName = info.getQualifiedName();
        long now = System.currentTimeMillis();

        boolean collision;
        do
        {
            collision = false;

            // Check for collision in cache
            for (DNSCache.CacheNode j = cache.find(info.getQualifiedName().toLowerCase()); j != null; j = j.next())
            {
                DNSRecord a = (DNSRecord) j.getValue();
                if ((a.type == DNSConstants.TYPE_SRV) && !a.isExpired(now))
                {
                    DNSRecord.Service s = (DNSRecord.Service) a;
                    if (s.port != info.port || !s.server.equals(localHost.getName()))
                    {
                        logger.finer("makeServiceNameUnique() JmDNS.makeServiceNameUnique srv collision:" + a + " s.server=" + s.server + " " + localHost.getName() + " equals:" + (s.server.equals(localHost.getName())));
                        info.setName(incrementName(info.getName()));
                        collision = true;
                        break;
                    }
                }
            }

            // Check for collision with other service infos published by JmDNS
            Object selfService = services.get(info.getQualifiedName().toLowerCase());
            if (selfService != null && selfService != info)
            {
                info.setName(incrementName(info.getName()));
                collision = true;
            }
        }
        while (collision);

        return !(originalQualifiedName.equals(info.getQualifiedName()));
    }

    String incrementName(String name)
    {
        try
        {
            int l = name.lastIndexOf('(');
            int r = name.lastIndexOf(')');
            if ((l >= 0) && (l < r))
            {
                name = name.substring(0, l) + "(" + (Integer.parseInt(name.substring(l + 1, r)) + 1) + ")";
            }
            else
            {
                name += " (2)";
            }
        }
        catch (NumberFormatException e)
        {
            name += " (2)";
        }
        return name;
    }

    /**
     * Add a listener for a question. The listener will receive updates
     * of answers to the question as they arrive, or from the cache if they
     * are already available.
     */
    void addListener(DNSListener listener, DNSQuestion question)
    {
        long now = System.currentTimeMillis();

        // add the new listener
        synchronized (this)
        {
            listeners.add(listener);
        }

        // report existing matched records
        if (question != null)
        {
            for (DNSCache.CacheNode i = cache.find(question.name); i != null; i = i.next())
            {
                DNSRecord c = (DNSRecord) i.getValue();
                if (question.answeredBy(c) && !c.isExpired(now))
                {
                    listener.updateRecord(this, now, c);
                }
            }
        }
    }

    /**
     * Remove a listener from all outstanding questions. The listener will no longer
     * receive any updates.
     */
    void removeListener(DNSListener listener)
    {
        synchronized (this)
        {
            listeners.remove(listener);
        }
    }
    
    
    // Remind: Method updateRecord should receive a better name.
    /**
     * Notify all listeners that a record was updated.
     */
    void updateRecord(long now, DNSRecord rec)
    {
        // We do not want to block the entire DNS while we are updating the record for each listener (service info)
        List listenerList = null;
        synchronized (this)
        {
            listenerList = new ArrayList(listeners);
        }
        for (Iterator iterator = listenerList.iterator(); iterator.hasNext();)
        {
            DNSListener listener = (DNSListener) iterator.next();
            listener.updateRecord(this, now, rec);
        }
        if (rec.type == DNSConstants.TYPE_PTR || rec.type == DNSConstants.TYPE_SRV)
        {
            List serviceListenerList = null;
            synchronized (this)
            {
                serviceListenerList = (List) serviceListeners.get(rec.name.toLowerCase());
                // Iterate on a copy in case listeners will modify it
                if (serviceListenerList != null)
                {
                    serviceListenerList = new ArrayList(serviceListenerList);
                }
            }
            if (serviceListenerList != null)
            {
                boolean expired = rec.isExpired(now);
                String type = rec.getName();
                String name = ((DNSRecord.Pointer) rec).getAlias();
                // DNSRecord old = (DNSRecord)services.get(name.toLowerCase());
                if (!expired)
                {
                    // new record
                    ServiceEvent event = new ServiceEvent(this, type, toUnqualifiedName(type, name), null);
                    for (Iterator iterator = serviceListenerList.iterator(); iterator.hasNext();)
                    {
                        ((ServiceListener) iterator.next()).serviceAdded(event);
                    }
                }
                else
                {
                    // expire record
                    ServiceEvent event = new ServiceEvent(this, type, toUnqualifiedName(type, name), null);
                    for (Iterator iterator = serviceListenerList.iterator(); iterator.hasNext();)
                    {
                        ((ServiceListener) iterator.next()).serviceRemoved(event);
                    }
                }
            }
        }
    }

    /**
     * Handle an incoming response. Cache answers, and pass them on to
     * the appropriate questions.
     */
    private void handleResponse(DNSIncoming msg) throws IOException
    {
        long now = System.currentTimeMillis();

        boolean hostConflictDetected = false;
        boolean serviceConflictDetected = false;

        for (Iterator i = msg.answers.iterator(); i.hasNext();)
        {
            boolean isInformative = false;
            DNSRecord rec = (DNSRecord) i.next();
            boolean expired = rec.isExpired(now);

            // update the cache
            DNSRecord c = (DNSRecord) cache.get(rec);
            if (c != null)
            {
                if (expired)
                {
                    isInformative = true;
                    cache.remove(c);
                }
                else
                {
                    c.resetTTL(rec);
                    rec = c;
                }
            }
            else
            {
                if (!expired)
                {
                    isInformative = true;
                    cache.add(rec);
                }
            }
            switch (rec.type)
            {
                case DNSConstants.TYPE_PTR:
                    // handle _mdns._udp records
                    if (rec.getName().indexOf("._mdns._udp.") >= 0)
                    {
                        if (!expired && rec.name.startsWith("_services._mdns._udp."))
                        {
                            isInformative = true;
                            registerServiceType(((DNSRecord.Pointer) rec).alias);
                        }
                        continue;
                    }
                    registerServiceType(rec.name);
                    break;
            }

            if ((rec.getType() == DNSConstants.TYPE_A) || (rec.getType() == DNSConstants.TYPE_AAAA))
            {
                hostConflictDetected |= rec.handleResponse(this);
            }
            else
            {
                serviceConflictDetected |= rec.handleResponse(this);
            }

            // notify the listeners
            if (isInformative)
            {
                updateRecord(now, rec);
            }
        }

        if (hostConflictDetected || serviceConflictDetected)
        {
            new Prober().start();
        }
    }

    /**
     * Handle an incoming query. See if we can answer any part of it
     * given our service infos.
     */
    private void handleQuery(DNSIncoming in, InetAddress addr, int port) throws IOException
    {
        // Track known answers
        boolean hostConflictDetected = false;
        boolean serviceConflictDetected = false;
        long expirationTime = System.currentTimeMillis() + DNSConstants.KNOWN_ANSWER_TTL;
        for (Iterator i = in.answers.iterator(); i.hasNext();)
        {
            DNSRecord answer = (DNSRecord) i.next();
            if ((answer.getType() == DNSConstants.TYPE_A) || (answer.getType() == DNSConstants.TYPE_AAAA))
            {
                hostConflictDetected |= answer.handleQuery(this, expirationTime);
            }
            else
            {
                serviceConflictDetected |= answer.handleQuery(this, expirationTime);
            }
        }

        if (plannedAnswer != null)
        {
            plannedAnswer.append(in);
        }
        else
        {
            if (in.isTruncated())
            {
                plannedAnswer = in;
            }

            new Responder(in, addr, port).start();
        }

        if (hostConflictDetected || serviceConflictDetected)
        {
            new Prober().start();
        }
    }

    /**
     * Add an answer to a question. Deal with the case when the
     * outgoing packet overflows
     */
    DNSOutgoing addAnswer(DNSIncoming in, InetAddress addr, int port, DNSOutgoing out, DNSRecord rec) throws IOException
    {
        if (out == null)
        {
            out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA);
        }
        try
        {
            out.addAnswer(in, rec);
        }
        catch (IOException e)
        {
            out.flags |= DNSConstants.FLAGS_TC;
            out.id = in.id;
            out.finish();
            send(out);

            out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA);
            out.addAnswer(in, rec);
        }
        return out;
    }


    /**
     * Send an outgoing multicast DNS message.
     */
    private void send(DNSOutgoing out) throws IOException
    {
        out.finish();
        if (!out.isEmpty())
        {
            DatagramPacket packet = new DatagramPacket(out.data, out.off, group, DNSConstants.MDNS_PORT);

            try
            {
                DNSIncoming msg = new DNSIncoming(packet);
                logger.finest("send() JmDNS out:" + msg.print(true));
            }
            catch (IOException e)
            {
                logger.throwing(getClass().toString(), "send(DNSOutgoing) - JmDNS can not parse what it sends!!!", e);
            }
            socket.send(packet);
        }
    }

    /**
     * Listen for multicast packets.
     */
    class SocketListener implements Runnable
    {
        public void run()
        {
            try
            {
                byte buf[] = new byte[DNSConstants.MAX_MSG_ABSOLUTE];
                DatagramPacket packet = new DatagramPacket(buf, buf.length);
                while (state != DNSState.CANCELED)
                {
                    packet.setLength(buf.length);
                    socket.receive(packet);
                    if (state == DNSState.CANCELED)
                    {
                        break;
                    }
                    try
                    {
                        if (localHost.shouldIgnorePacket(packet))
                        {
                            continue;
                        }

                        DNSIncoming msg = new DNSIncoming(packet);
                        logger.finest("SocketListener.run() JmDNS in:" + msg.print(true));

                        synchronized (ioLock)
                        {
                            if (msg.isQuery())
                            {
                                if (packet.getPort() != DNSConstants.MDNS_PORT)
                                {
                                    handleQuery(msg, packet.getAddress(), packet.getPort());
                                }
                                handleQuery(msg, group, DNSConstants.MDNS_PORT);
                            }
                            else
                            {
                                handleResponse(msg);
                            }
                        }
                    }
                    catch (IOException e)
                    {
                        logger.log(Level.WARNING, "run() exception ", e);
                    }
                }
            }
            catch (IOException e)
            {
                if (state != DNSState.CANCELED)
                {
                    logger.log(Level.WARNING, "run() exception ", e);
                    recover();
                }
            }
        }
    }


    /**
     * Periodicaly removes expired entries from the cache.
     */
    private class RecordReaper extends TimerTask
    {
        public void start()
        {
            timer.schedule(this, DNSConstants.RECORD_REAPER_INTERVAL, DNSConstants.RECORD_REAPER_INTERVAL);
        }

        public void run()
        {
            synchronized (JmDNS.this)
            {
                if (state == DNSState.CANCELED)
                {
                    return;
                }
                logger.finest("run() JmDNS reaping cache");

                // Remove expired answers from the cache
                // -------------------------------------
                // To prevent race conditions, we defensively copy all cache
                // entries into a list.
                List list = new ArrayList();
                synchronized (cache)
                {
                    for (Iterator i = cache.iterator(); i.hasNext();)
                    {
                        for (DNSCache.CacheNode n = (DNSCache.CacheNode) i.next(); n != null; n = n.next())
                        {
                            list.add(n.getValue());
                        }
                    }
                }
                // Now, we remove them.
                long now = System.currentTimeMillis();
                for (Iterator i = list.iterator(); i.hasNext();)
                {
                    DNSRecord c = (DNSRecord) i.next();
                    if (c.isExpired(now))
                    {
                        updateRecord(now, c);
                        cache.remove(c);
                    }
                }
            }
        }
    }


    /**
     * The Prober sends three consecutive probes for all service infos
     * that needs probing as well as for the host name.
     * The state of each service info of the host name is advanced, when a probe has
     * been sent for it.
     * When the prober has run three times, it launches an Announcer.
     * <p/>
     * If a conflict during probes occurs, the affected service infos (and affected
     * host name) are taken away from the prober. This eventually causes the prober
     * tho cancel itself.
     */
    private class Prober extends TimerTask
    {
        /**
         * The state of the prober.
         */
        DNSState taskState = DNSState.PROBING_1;

        public Prober()
        {
            // Associate the host name to this, if it needs probing
            if (state == DNSState.PROBING_1)
            {
                task = this;
            }
            // Associate services to this, if they need probing
            synchronized (JmDNS.this)
            {
                for (Iterator iterator = services.values().iterator(); iterator.hasNext();)
                {
                    ServiceInfo info = (ServiceInfo) iterator.next();
                    if (info.getState() == DNSState.PROBING_1)
                    {
                        info.task = this;
                    }
                }
            }
        }


        public void start()
        {
            long now = System.currentTimeMillis();
            if (now - lastThrottleIncrement < DNSConstants.PROBE_THROTTLE_COUNT_INTERVAL)
            {
                throttle++;
            }
            else
            {
                throttle = 1;
            }
            lastThrottleIncrement = now;

            if (state == DNSState.ANNOUNCED && throttle < DNSConstants.PROBE_THROTTLE_COUNT)
            {
                timer.schedule(this, random.nextInt(1 + DNSConstants.PROBE_WAIT_INTERVAL), DNSConstants.PROBE_WAIT_INTERVAL);
            }
            else
            {
                timer.schedule(this, DNSConstants.PROBE_CONFLICT_INTERVAL, DNSConstants.PROBE_CONFLICT_INTERVAL);
            }
        }

        public boolean cancel()
        {
            // Remove association from host name to this
            if (task == this)
            {
                task = null;
            }

            // Remove associations from services to this
            synchronized (JmDNS.this)
            {
                for (Iterator i = services.values().iterator(); i.hasNext();)
                {
                    ServiceInfo info = (ServiceInfo) i.next();
                    if (info.task == this)
                    {
                        info.task = null;
                    }
                }
            }

            return super.cancel();
        }

        public void run()
        {
            synchronized (ioLock)
            {
                DNSOutgoing out = null;
                try
                {
                    // send probes for JmDNS itself
                    if (state == taskState && task == this)
                    {
                        if (out == null)
                        {
                            out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY);
                        }
                        out.addQuestion(new DNSQuestion(localHost.getName(), DNSConstants.TYPE_ANY, DNSConstants.CLASS_IN));
                        DNSRecord answer = localHost.getDNS4AddressRecord();
                        if (answer != null)
                        {
                            out.addAuthorativeAnswer(answer);
                        }
                        answer = localHost.getDNS6AddressRecord();
                        if (answer != null)
                        {
                            out.addAuthorativeAnswer(answer);
                        }
                        advanceState();
                    }
                    // send probes for services
                    // Defensively copy the services into a local list,
                    // to prevent race conditions with methods registerService
                    // and unregisterService.
                    List list;
                    synchronized (JmDNS.this)
                    {
                        list = new LinkedList(services.values());
                    }
                    for (Iterator i = list.iterator(); i.hasNext();)
                    {
                        ServiceInfo info = (ServiceInfo) i.next();

                        synchronized (info)
                        {
                            if (info.getState() == taskState && info.task == this)
                            {
                                info.advanceState();
                                logger.fine("run() JmDNS probing " + info.getQualifiedName() + " state " + info.getState());
                                if (out == null)
                                {
                                    out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY);
                                    out.addQuestion(new DNSQuestion(info.getQualifiedName(), DNSConstants.TYPE_ANY, DNSConstants.CLASS_IN));
                                }
                                out.addAuthorativeAnswer(new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.priority, info.weight, info.port, localHost.getName()));
                            }
                        }
                    }
                    if (out != null)
                    {
                        logger.finer("run() JmDNS probing #" + taskState);
                        send(out);
                    }
                    else
                    {
                        // If we have nothing to send, another timer taskState ahead
                        // of us has done the job for us. We can cancel.
                        cancel();
                        return;
                    }
                }
                catch (Throwable e)
                {
                    logger.log(Level.WARNING, "run() exception ", e);
                    recover();
                }

                taskState = taskState.advance();
                if (!taskState.isProbing())
                {
                    cancel();

                    new Announcer().start();
                }
            }
        }

    }

    /**
     * The Announcer sends an accumulated query of all announces, and advances
     * the state of all serviceInfos, for which it has sent an announce.
     * The Announcer also sends announcements and advances the state of JmDNS itself.
     * <p/>
     * When the announcer has run two times, it finishes.
     */
    private class Announcer extends TimerTask
    {
        /**
         * The state of the announcer.
         */
        DNSState taskState = DNSState.ANNOUNCING_1;

        public Announcer()
        {
            // Associate host to this, if it needs announcing
            if (state == DNSState.ANNOUNCING_1)
            {
                task = this;
            }
            // Associate services to this, if they need announcing
            synchronized (JmDNS.this)
            {
                for (Iterator s = services.values().iterator(); s.hasNext();)
                {
                    ServiceInfo info = (ServiceInfo) s.next();
                    if (info.getState() == DNSState.ANNOUNCING_1)
                    {
                        info.task = this;
                    }
                }
            }
        }

        public void start()
        {
            timer.schedule(this, DNSConstants.ANNOUNCE_WAIT_INTERVAL, DNSConstants.ANNOUNCE_WAIT_INTERVAL);
        }

        public boolean cancel()
        {
            // Remove association from host to this
            if (task == this)
            {
                task = null;
            }

            // Remove associations from services to this
            synchronized (JmDNS.this)
            {
                for (Iterator i = services.values().iterator(); i.hasNext();)
                {
                    ServiceInfo info = (ServiceInfo) i.next();
                    if (info.task == this)
                    {
                        info.task = null;
                    }
                }
            }

            return super.cancel();
        }

        public void run()
        {
            DNSOutgoing out = null;
            try
            {
                // send probes for JmDNS itself
                if (state == taskState)
                {
                    if (out == null)
                    {
                        out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA);
                    }
                    DNSRecord answer = localHost.getDNS4AddressRecord();
                    if (answer != null)
                    {
                        out.addAnswer(answer, 0);
                    }
                    answer = localHost.getDNS6AddressRecord();
                    if (answer != null)
                    {
                        out.addAnswer(answer, 0);
                    }
                    advanceState();
                }
                // send announces for services
                // Defensively copy the services into a local list,
                // to prevent race conditions with methods registerService
                // and unregisterService.
                List list;
                synchronized (JmDNS.this)
                {
                    list = new ArrayList(services.values());
                }
                for (Iterator i = list.iterator(); i.hasNext();)
                {
                    ServiceInfo info = (ServiceInfo) i.next();
                    synchronized (info)
                    {
                        if (info.getState() == taskState && info.task == this)
                        {
                            info.advanceState();
                            logger.finer("run() JmDNS announcing " + info.getQualifiedName() + " state " + info.getState());
                            if (out == null)
                            {
                                out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA);
                            }
                            out.addAnswer(new DNSRecord.Pointer(info.type, DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.getQualifiedName()), 0);
                            out.addAnswer(new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.priority, info.weight, info.port, localHost.getName()), 0);
                            out.addAnswer(new DNSRecord.Text(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.text), 0);
                        }
                    }
                }
                if (out != null)
                {
                    logger.finer("run() JmDNS announcing #" + taskState);
                    send(out);
                }
                else
                {
                    // If we have nothing to send, another timer taskState ahead
                    // of us has done the job for us. We can cancel.
                    cancel();
                }
            }
            catch (Throwable e)
            {
                logger.log(Level.WARNING, "run() exception ", e);
                recover();
            }

            taskState = taskState.advance();
            if (!taskState.isAnnouncing())
            {
                cancel();

                new Renewer().start();
            }
        }
    }

    /**
     * The Renewer is there to send renewal announcment when the record expire for ours infos.
     */
    private class Renewer extends TimerTask
    {
        /**
         * The state of the announcer.
         */
        DNSState taskState = DNSState.ANNOUNCED;

        public Renewer()
        {
            // Associate host to this, if it needs renewal
            if (state == DNSState.ANNOUNCED)
            {
                task = this;
            }
            // Associate services to this, if they need renewal
            synchronized (JmDNS.this)
            {
                for (Iterator s = services.values().iterator(); s.hasNext();)
                {
                    ServiceInfo info = (ServiceInfo) s.next();
                    if (info.getState() == DNSState.ANNOUNCED)
                    {
                        info.task = this;
                    }
                }
            }
        }

        public void start()
        {
            timer.schedule(this, DNSConstants.ANNOUNCED_RENEWAL_TTL_INTERVAL, DNSConstants.ANNOUNCED_RENEWAL_TTL_INTERVAL);
        }

        public boolean cancel()
        {
            // Remove association from host to this
            if (task == this)
            {
                task = null;
            }

            // Remove associations from services to this
            synchronized (JmDNS.this)
            {
                for (Iterator i = services.values().iterator(); i.hasNext();)
                {
                    ServiceInfo info = (ServiceInfo) i.next();
                    if (info.task == this)
                    {
                        info.task = null;
                    }
                }
            }

            return super.cancel();
        }

        public void run()
        {
            DNSOutgoing out = null;
            try
            {
                // send probes for JmDNS itself
                if (state == taskState)
                {
                    if (out == null)
                    {
                        out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA);
                    }
                    DNSRecord answer = localHost.getDNS4AddressRecord();
                    if (answer != null)
                    {
                        out.addAnswer(answer, 0);
                    }
                    answer = localHost.getDNS6AddressRecord();
                    if (answer != null)
                    {
                        out.addAnswer(answer, 0);
                    }
                    advanceState();
                }
                // send announces for services
                // Defensively copy the services into a local list,
                // to prevent race conditions with methods registerService
                // and unregisterService.
                List list;
                synchronized (JmDNS.this)
                {
                    list = new ArrayList(services.values());
                }
                for (Iterator i = list.iterator(); i.hasNext();)
                {
                    ServiceInfo info = (ServiceInfo) i.next();
                    synchronized (info)
                    {
                        if (info.getState() == taskState && info.task == this)
                        {
                            info.advanceState();
                            logger.finer("run() JmDNS announced " + info.getQualifiedName() + " state " + info.getState());
                            if (out == null)
                            {
                                out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA);
                            }
                            out.addAnswer(new DNSRecord.Pointer(info.type, DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.getQualifiedName()), 0);
                            out.addAnswer(new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.priority, info.weight, info.port, localHost.getName()), 0);
                            out.addAnswer(new DNSRecord.Text(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.text), 0);
                        }
                    }
                }
                if (out != null)
                {
                    logger.finer("run() JmDNS announced");
                    send(out);
                }
                else
                {
                    // If we have nothing to send, another timer taskState ahead
                    // of us has done the job for us. We can cancel.
                    cancel();
                }
            }
            catch (Throwable e)
            {
                logger.log(Level.WARNING, "run() exception ", e);
                recover();
            }

            taskState = taskState.advance();
            if (!taskState.isAnnounced())
            {
                cancel();

            }
        }
    }

    /**
     * The Responder sends a single answer for the specified service infos
     * and for the host name.
     */
    private class Responder extends TimerTask
    {
        private DNSIncoming in;
        private InetAddress addr;
        private int port;

        public Responder(DNSIncoming in, InetAddress addr, int port)
        {
            this.in = in;
            this.addr = addr;
            this.port = port;
        }

        public void start()
        {
            // According to draft-cheshire-dnsext-multicastdns.txt
            // chapter "8 Responding":
            // We respond immediately if we know for sure, that we are
            // the only one who can respond to the query.
            // In all other cases, we respond within 20-120 ms.
            //
            // According to draft-cheshire-dnsext-multicastdns.txt
            // chapter "7.2 Multi-Packet Known Answer Suppression":
            // We respond after 20-120 ms if the query is truncated.

            boolean iAmTheOnlyOne = true;
            for (Iterator i = in.questions.iterator(); i.hasNext();)
            {
                DNSEntry entry = (DNSEntry) i.next();
                if (entry instanceof DNSQuestion)
                {
                    DNSQuestion q = (DNSQuestion) entry;
                    logger.finest("start() question=" + q);
                    iAmTheOnlyOne &= (q.type == DNSConstants.TYPE_SRV
                        || q.type == DNSConstants.TYPE_TXT
                        || q.type == DNSConstants.TYPE_A
                        || q.type == DNSConstants.TYPE_AAAA
                        || localHost.getName().equalsIgnoreCase(q.name)
                        || services.containsKey(q.name.toLowerCase()));
                    if (!iAmTheOnlyOne)
                    {
                        break;
                    }
                }
            }
            int delay = (iAmTheOnlyOne && !in.isTruncated()) ? 0 : DNSConstants.RESPONSE_MIN_WAIT_INTERVAL + random.nextInt(DNSConstants.RESPONSE_MAX_WAIT_INTERVAL - DNSConstants.RESPONSE_MIN_WAIT_INTERVAL + 1) - in.elapseSinceArrival();
            if (delay < 0)
            {
                delay = 0;
            }
            logger.finest("start() Responder chosen delay=" + delay);
            timer.schedule(this, delay);
        }

        public void run()
        {
            synchronized (ioLock)
            {
                if (plannedAnswer == in)
                {
                    plannedAnswer = null;
                }

                // We use these sets to prevent duplicate records
                // FIXME - This should be moved into DNSOutgoing
                HashSet questions = new HashSet();
                HashSet answers = new HashSet();


                if (state == DNSState.ANNOUNCED)
                {
                    try
                    {
                        long now = System.currentTimeMillis();
                        long expirationTime = now + 1; //=now+DNSConstants.KNOWN_ANSWER_TTL;
                        boolean isUnicast = (port != DNSConstants.MDNS_PORT);


                        // Answer questions
                        for (Iterator iterator = in.questions.iterator(); iterator.hasNext();)
                        {
                            DNSEntry entry = (DNSEntry) iterator.next();
                            if (entry instanceof DNSQuestion)
                            {
                                DNSQuestion q = (DNSQuestion) entry;

                                // for unicast responses the question must be included
                                if (isUnicast)
                                {
                                    //out.addQuestion(q);
                                    questions.add(q);
                                }

                                int type = q.type;
                                if (type == DNSConstants.TYPE_ANY || type == DNSConstants.TYPE_SRV)
                                { // I ama not sure of why there is a special case here [PJYF Oct 15 2004]
                                    if (localHost.getName().equalsIgnoreCase(q.getName()))
                                    {
                                        // type = DNSConstants.TYPE_A;
                                        DNSRecord answer = localHost.getDNS4AddressRecord();
                                        if (answer != null)
                                        {
                                            answers.add(answer);
                                        }
                                        answer = localHost.getDNS6AddressRecord();
                                        if (answer != null)
                                        {
                                            answers.add(answer);
                                        }
                                        type = DNSConstants.TYPE_IGNORE;
                                    }
                                    else
                                    {
                                        if (serviceTypes.containsKey(q.getName().toLowerCase()))
                                        {
                                            type = DNSConstants.TYPE_PTR;
                                        }
                                    }
                                }

                                switch (type)
                                {
                                    case DNSConstants.TYPE_A:
                                        {
                                            // Answer a query for a domain name
                                            //out = addAnswer( in, addr, port, out, host );
                                            DNSRecord answer = localHost.getDNS4AddressRecord();
                                            if (answer != null)
                                            {
                                                answers.add(answer);
                                            }
                                            break;
                                        }
                                    case DNSConstants.TYPE_AAAA:
                                        {
                                            // Answer a query for a domain name
                                            DNSRecord answer = localHost.getDNS6AddressRecord();
                                            if (answer != null)
                                            {
                                                answers.add(answer);
                                            }
                                            break;
                                        }
                                    case DNSConstants.TYPE_PTR:
                                        {
                                            // Answer a query for services of a given type

                                            // find matching services
                                            for (Iterator serviceIterator = services.values().iterator(); serviceIterator.hasNext();)
                                            {
                                                ServiceInfo info = (ServiceInfo) serviceIterator.next();
                                                if (info.getState() == DNSState.ANNOUNCED)
                                                {
                                                    if (q.name.equalsIgnoreCase(info.type))
                                                    {
                                                        DNSRecord answer = localHost.getDNS4AddressRecord();
                                                        if (answer != null)
                                                        {
                                                            answers.add(answer);
                                                        }
                                                        answer = localHost.getDNS6AddressRecord();
                                                        if (answer != null)
                                                        {
                                                            answers.add(answer);
                                                        }
                                                        answers.add(new DNSRecord.Pointer(info.type, DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.getQualifiedName()));
                                                        answers.add(new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, DNSConstants.DNS_TTL, info.priority, info.weight, info.port, localHost.getName()));
                                                        answers.add(new DNSRecord.Text(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, DNSConstants.DNS_TTL, info.text));
                                                    }
                                                }
                                            }
                                            if (q.name.equalsIgnoreCase("_services._mdns._udp.local."))
                                            {
                                                for (Iterator serviceTypeIterator = serviceTypes.values().iterator(); serviceTypeIterator.hasNext();)
                                                {
                                                    answers.add(new DNSRecord.Pointer("_services._mdns._udp.local.", DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, (String) serviceTypeIterator.next()));
                                                }
                                            }
                                            break;
                                        }
                                    case DNSConstants.TYPE_SRV:
                                    case DNSConstants.TYPE_ANY:
                                    case DNSConstants.TYPE_TXT:
                                        {
                                            ServiceInfo info = (ServiceInfo) services.get(q.name.toLowerCase());
                                            if (info != null && info.getState() == DNSState.ANNOUNCED)
                                            {
                                                DNSRecord answer = localHost.getDNS4AddressRecord();
                                                if (answer != null)
                                                {
                                                    answers.add(answer);
                                                }
                                                answer = localHost.getDNS6AddressRecord();
                                                if (answer != null)
                                                {
                                                    answers.add(answer);
                                                }
                                                answers.add(new DNSRecord.Pointer(info.type, DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.getQualifiedName()));
                                                answers.add(new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, DNSConstants.DNS_TTL, info.priority, info.weight, info.port, localHost.getName()));
                                                answers.add(new DNSRecord.Text(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, DNSConstants.DNS_TTL, info.text));
                                            }
                                            break;
                                        }
                                    default :
                                        {
                                            //System.out.println("JmDNSResponder.unhandled query:"+q);
                                            break;
                                        }
                                }
                            }
                        }


                        // remove known answers, if the ttl is at least half of
                        // the correct value. (See Draft Cheshire chapter 7.1.).
                        for (Iterator i = in.answers.iterator(); i.hasNext();)
                        {
                            DNSRecord knownAnswer = (DNSRecord) i.next();
                            if (knownAnswer.ttl > DNSConstants.DNS_TTL / 2 && answers.remove(knownAnswer))
                            {
                                logger.log(Level.FINER, "JmDNS Responder Known Answer Removed");
                            }
                        }


                        // responde if we have answers
                        if (answers.size() != 0)
                        {
                            logger.finer("run() JmDNS responding");
                            DNSOutgoing out = null;
                            if (isUnicast)
                            {
                                out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA, false);
                            }

                            for (Iterator i = questions.iterator(); i.hasNext();)
                            {
                                out.addQuestion((DNSQuestion) i.next());
                            }
                            for (Iterator i = answers.iterator(); i.hasNext();)
                            {
                                out = addAnswer(in, addr, port, out, (DNSRecord) i.next());
                            }
                            send(out);
                        }
                        cancel();
                    }
                    catch (Throwable e)
                    {
                        logger.log(Level.WARNING, "run() exception ", e);
                        close();
                    }
                }
            }
        }
    }

    /**
     * Helper class to resolve service types.
     * <p/>
     * The TypeResolver queries three times consecutively for service types, and then
     * removes itself from the timer.
     * <p/>
     * The TypeResolver will run only if JmDNS is in state ANNOUNCED.
     */
    private class TypeResolver extends TimerTask
    {
        public void start()
        {
            timer.schedule(this, DNSConstants.QUERY_WAIT_INTERVAL, DNSConstants.QUERY_WAIT_INTERVAL);
        }

        /**
         * Counts the number of queries that were sent.
         */
        int count = 0;

        public void run()
        {
            try
            {
                if (state == DNSState.ANNOUNCED)
                {
                    if (++count < 3)
                    {
                        logger.finer("run() JmDNS querying type");
                        DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY);
                        out.addQuestion(new DNSQuestion("_services._mdns._udp.local.", DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN));
                        for (Iterator iterator = serviceTypes.values().iterator(); iterator.hasNext();)
                        {
                            out.addAnswer(new DNSRecord.Pointer("_services._mdns._udp.local.", DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, (String) iterator.next()), 0);
                        }
                        send(out);
                    }
                    else
                    {
                        // After three queries, we can quit.
                        cancel();
                    }
                    ;
                }
                else
                {
                    if (state == DNSState.CANCELED)
                    {
                        cancel();
                    }
                }
            }
            catch (Throwable e)
            {
                logger.log(Level.WARNING, "run() exception ", e);
                recover();
            }
        }
    }

    /**
     * The ServiceResolver queries three times consecutively for services of
     * a given type, and then removes itself from the timer.
     * <p/>
     * The ServiceResolver will run only if JmDNS is in state ANNOUNCED.
     * REMIND: Prevent having multiple service resolvers for the same type in the
     * timer queue.
     */
    private class ServiceResolver extends TimerTask
    {
        /**
         * Counts the number of queries being sent.
         */
        int count = 0;
        private String type;

        public ServiceResolver(String type)
        {
            this.type = type;
        }

        public void start()
        {
            timer.schedule(this, DNSConstants.QUERY_WAIT_INTERVAL, DNSConstants.QUERY_WAIT_INTERVAL);
        }

        public void run()
        {
            try
            {
                if (state == DNSState.ANNOUNCED)
                {
                    if (count++ < 3)
                    {
                        logger.finer("run() JmDNS querying service");
                        long now = System.currentTimeMillis();
                        DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY);
                        out.addQuestion(new DNSQuestion(type, DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN));
                        for (Iterator s = services.values().iterator(); s.hasNext();)
                        {
                            final ServiceInfo info = (ServiceInfo) s.next();
                            try
                            {
                                out.addAnswer(new DNSRecord.Pointer(info.type, DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.getQualifiedName()), now);
                            }
                            catch (IOException ee)
                            {
                                break;
                            }
                        }
                        send(out);
                    }
                    else
                    {
                        // After three queries, we can quit.
                        cancel();
                    }
                    ;
                }
                else
                {
                    if (state == DNSState.CANCELED)
                    {
                        cancel();
                    }
                }
            }
            catch (Throwable e)
            {
                logger.log(Level.WARNING, "run() exception ", e);
                recover();
            }
        }
    }

    /**
     * The ServiceInfoResolver queries up to three times consecutively for
     * a service info, and then removes itself from the timer.
     * <p/>
     * The ServiceInfoResolver will run only if JmDNS is in state ANNOUNCED.
     * REMIND: Prevent having multiple service resolvers for the same info in the
     * timer queue.
     */
    private class ServiceInfoResolver extends TimerTask
    {
        /**
         * Counts the number of queries being sent.
         */
        int count = 0;
        private ServiceInfo info;

        public ServiceInfoResolver(ServiceInfo info)
        {
            this.info = info;
            info.dns = JmDNS.this;
            addListener(info, new DNSQuestion(info.getQualifiedName(), DNSConstants.TYPE_ANY, DNSConstants.CLASS_IN));
        }

        public void start()
        {
            timer.schedule(this, DNSConstants.QUERY_WAIT_INTERVAL, DNSConstants.QUERY_WAIT_INTERVAL);
        }

        public void run()
        {
            try
            {
                if (state == DNSState.ANNOUNCED)
                {
                    if (count++ < 3 && !info.hasData())
                    {
                        long now = System.currentTimeMillis();
                        DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY);
                        out.addQuestion(new DNSQuestion(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN));
                        out.addQuestion(new DNSQuestion(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN));
                        if (info.server != null)
                        {
                            out.addQuestion(new DNSQuestion(info.server, DNSConstants.TYPE_A, DNSConstants.CLASS_IN));
                        }
                        out.addAnswer((DNSRecord) cache.get(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN), now);
                        out.addAnswer((DNSRecord) cache.get(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN), now);
                        if (info.server != null)
                        {
                            out.addAnswer((DNSRecord) cache.get(info.server, DNSConstants.TYPE_A, DNSConstants.CLASS_IN), now);
                        }
                        send(out);
                    }
                    else
                    {
                        // After three queries, we can quit.
                        cancel();
                        removeListener(info);
                    }
                    ;
                }
                else
                {
                    if (state == DNSState.CANCELED)
                    {
                        cancel();
                        removeListener(info);
                    }
                }
            }
            catch (Throwable e)
            {
                logger.log(Level.WARNING, "run() exception ", e);
                recover();
            }
        }
    }

    /**
     * The Canceler sends two announces with TTL=0 for the specified services.
     */
    private class Canceler extends TimerTask
    {
        /**
         * Counts the number of announces being sent.
         */
        int count = 0;
        /**
         * The services that need cancelling.
         * Note: We have to use a local variable here, because the services
         * that are canceled, are removed immediately from variable JmDNS.services.
         */
        private ServiceInfo[] infos;
        /**
         * We call notifyAll() on the lock object, when we have canceled the
         * service infos.
         * This is used by method JmDNS.unregisterService() and
         * JmDNS.unregisterAllServices, to ensure that the JmDNS
         * socket stays open until the Canceler has canceled all services.
         * <p/>
         * Note: We need this lock, because ServiceInfos do the transition from
         * state ANNOUNCED to state CANCELED before we get here. We could get
         * rid of this lock, if we added a state named CANCELLING to DNSState.
         */
        private Object lock;
        int ttl = 0;

        public Canceler(ServiceInfo info, Object lock)
        {
            this.infos = new ServiceInfo[]{info};
            this.lock = lock;
            addListener(info, new DNSQuestion(info.getQualifiedName(), DNSConstants.TYPE_ANY, DNSConstants.CLASS_IN));
        }

        public Canceler(ServiceInfo[] infos, Object lock)
        {
            this.infos = infos;
            this.lock = lock;
        }

        public Canceler(Collection infos, Object lock)
        {
            this.infos = (ServiceInfo[]) infos.toArray(new ServiceInfo[infos.size()]);
            this.lock = lock;
        }

        public void start()
        {
            timer.schedule(this, 0, DNSConstants.ANNOUNCE_WAIT_INTERVAL);
        }

        public void run()
        {
            try
            {
                if (++count < 3)
                {
                    logger.finer("run() JmDNS canceling service");
                    // announce the service
                    //long now = System.currentTimeMillis();
                    DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA);
                    for (int i = 0; i < infos.length; i++)
                    {
                        ServiceInfo info = infos[i];
                        out.addAnswer(new DNSRecord.Pointer(info.type, DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, ttl, info.getQualifiedName()), 0);
                        out.addAnswer(new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN, ttl, info.priority, info.weight, info.port, localHost.getName()), 0);
                        out.addAnswer(new DNSRecord.Text(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN, ttl, info.text), 0);
                        DNSRecord answer = localHost.getDNS4AddressRecord();
                        if (answer != null)
                        {
                            out.addAnswer(answer, 0);
                        }
                        answer = localHost.getDNS6AddressRecord();
                        if (answer != null)
                        {
                            out.addAnswer(answer, 0);
                        }
                    }
                    send(out);
                }
                else
                {
                    // After three successful announcements, we are finished.
                    synchronized (lock)
                    {
                        closed=true;
                        lock.notifyAll();
                    }
                    cancel();
                }
            }
            catch (Throwable e)
            {
                logger.log(Level.WARNING, "run() exception ", e);
                recover();
            }
        }
    }
    
    // REMIND: Why is this not an anonymous inner class?
    /**
     * Shutdown operations.
     */
    private class Shutdown implements Runnable
    {
        public void run()
        {
            shutdown = null;
            close();
        }
    }

    /**
     * Recover jmdns when there is an error.
     */
    protected void recover()
    {
        logger.finer("recover()");
        // We have an IO error so lets try to recover if anything happens lets close it.
        // This should cover the case of the IP address changing under our feet
        if (DNSState.CANCELED != state)
        {
            synchronized (this)
            { // Synchronize only if we are not already in process to prevent dead locks
                //
                logger.finer("recover() Cleanning up");
                // Stop JmDNS
                state = DNSState.CANCELED; // This protects against recursive calls

                // We need to keep a copy for reregistration
                Collection oldServiceInfos = new ArrayList(services.values());

                // Cancel all services
                unregisterAllServices();
                disposeServiceCollectors();
                //
                // close multicast socket
                closeMulticastSocket();
                //
                cache.clear();
                logger.finer("recover() All is clean");
                //
                // All is clear now start the services
                //
                try
                {
                    openMulticastSocket(localHost);
                    start(oldServiceInfos);
                }
                catch (Exception exception)
                {
                    logger.log(Level.WARNING, "recover() Start services exception ", exception);
                }
                logger.log(Level.WARNING, "recover() We are back!");
            }
        }
    }

    /**
     * Close down jmdns. Release all resources and unregister all services.
     */
    public void close()
    {
        if (state != DNSState.CANCELED)
        {
            synchronized (this)
            { // Synchronize only if we are not already in process to prevent dead locks
                // Stop JmDNS
                state = DNSState.CANCELED; // This protects against recursive calls

                unregisterAllServices();
                disposeServiceCollectors();

                // close socket
                closeMulticastSocket();

                // Stop the timer
                timer.cancel();

                // remove the shutdown hook
                if (shutdown != null)
                {
                    Runtime.getRuntime().removeShutdownHook(shutdown);
                }

            }
        }
    }

    /**
     * List cache entries, for debugging only.
     */
    void print()
    {
        System.out.println("---- cache ----");
        cache.print();
        System.out.println();
    }

    /**
     * List Services and serviceTypes.
     * Debugging Only
     */

    public void printServices()
    {
        System.err.println(toString());
    }

    public String toString()
    {
        StringBuffer aLog = new StringBuffer();
        aLog.append("\t---- Services -----");
        if (services != null)
        {
            for (Iterator k = services.keySet().iterator(); k.hasNext();)
            {
                Object key = k.next();
                aLog.append("\n\t\tService: " + key + ": " + services.get(key));
            }
        }
        aLog.append("\n");
        aLog.append("\t---- Types ----");
        if (serviceTypes != null)
        {
            for (Iterator k = serviceTypes.keySet().iterator(); k.hasNext();)
            {
                Object key = k.next();
                aLog.append("\n\t\tType: " + key + ": " + serviceTypes.get(key));
            }
        }
        aLog.append("\n");
        aLog.append(cache.toString());
        aLog.append("\n");
        aLog.append("\t---- Service Collectors ----");
        if (serviceCollectors != null)
        {
            synchronized (serviceCollectors)
            {
                for (Iterator k = serviceCollectors.keySet().iterator(); k.hasNext();)
                {
                    Object key = k.next();
                    aLog.append("\n\t\tService Collector: " + key + ": " + serviceCollectors.get(key));
                }
                serviceCollectors.clear();
            }
        }
        return aLog.toString();
    }

    /**
     * Returns a list of service infos of the specified type.
     *
     * @param type Service type name, such as <code>_http._tcp.local..
     * @return An array of service instance names.
     */
    public ServiceInfo[] list(String type)
    {
        // Implementation note: The first time a list for a given type is
        // requested, a ServiceCollector is created which collects service
        // infos. This greatly speeds up the performance of subsequent calls
        // to this method. The caveats are, that 1) the first call to this method
        // for a given type is slow, and 2) we spawn a ServiceCollector
        // instance for each service type which increases network traffic a
        // little.

        ServiceCollector collector;

        boolean newCollectorCreated;
        synchronized (serviceCollectors)
        {
            collector = (ServiceCollector) serviceCollectors.get(type);
            if (collector == null)
            {
                collector = new ServiceCollector(type);
                serviceCollectors.put(type, collector);
                addServiceListener(type, collector);
                newCollectorCreated = true;
            }
            else
            {
                newCollectorCreated = false;
            }
        }

        // After creating a new ServiceCollector, we collect service infos for
        // 200 milliseconds. This should be enough time, to get some service
        // infos from the network.
        if (newCollectorCreated)
        {
            try
            {
                Thread.sleep(200);
            }
            catch (InterruptedException e)
            {
            }
        }

        return collector.list();
    }

    /**
     * This method disposes all ServiceCollector instances which have been
     * created by calls to method <code>list(type).
     *
     * @see #list
     */
    private void disposeServiceCollectors()
    {
        logger.finer("disposeServiceCollectors()");
        synchronized (serviceCollectors)
        {
            for (Iterator i = serviceCollectors.values().iterator(); i.hasNext();)
            {
                ServiceCollector collector = (ServiceCollector) i.next();
                removeServiceListener(collector.type, collector);
            }
            serviceCollectors.clear();
        }
    }

    /**
     * Instances of ServiceCollector are used internally to speed up the
     * performance of method <code>list(type).
     *
     * @see #list
     */
    private static class ServiceCollector implements ServiceListener
    {
        private static Logger logger = Logger.getLogger(ServiceCollector.class.toString());
        /**
         * A set of collected service instance names.
         */
        private Map infos = Collections.synchronizedMap(new HashMap());

        public String type;

        public ServiceCollector(String type)
        {
            this.type = type;
        }

        /**
         * A service has been added.
         */
        public void serviceAdded(ServiceEvent event)
        {
            synchronized (infos)
            {
                event.getDNS().requestServiceInfo(event.getType(), event.getName(), 0);
            }
        }

        /**
         * A service has been removed.
         */
        public void serviceRemoved(ServiceEvent event)
        {
            synchronized (infos)
            {
                infos.remove(event.getName());
            }
        }

        /**
         * A service hase been resolved. Its details are now available in the
         * ServiceInfo record.
         */
        public void serviceResolved(ServiceEvent event)
        {
            synchronized (infos)
            {
                infos.put(event.getName(), event.getInfo());
            }
        }

        /**
         * Returns an array of all service infos which have been collected by this
         * ServiceCollector.
         */
        public ServiceInfo[] list()
        {
            synchronized (infos)
            {
                return (ServiceInfo[]) infos.values().toArray(new ServiceInfo[infos.size()]);
            }
        }

        public String toString()
        {
            StringBuffer aLog = new StringBuffer();
            synchronized (infos)
            {
                for (Iterator k = infos.keySet().iterator(); k.hasNext();)
                {
                    Object key = k.next();
                    aLog.append("\n\t\tService: " + key + ": " + infos.get(key));
                }
            }
            return aLog.toString();
        }
    };
    
    private static String toUnqualifiedName(String type, String qualifiedName)
    {
        if (qualifiedName.endsWith(type))
        {
            return qualifiedName.substring(0, qualifiedName.length() - type.length() - 1);
        }
        else
        {
            return qualifiedName;
        }
    }
}

Other ActiveMQ examples (source code examples)

Here is a short list of links related to this ActiveMQ JmDNS.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.