JmDNSImpl.java :  » Client » daap-client » javax » jmdns » impl » Android Open Source

Android Open Source » Client » daap client 
daap client » javax » jmdns » impl » JmDNSImpl.java
// /Copyright 2003-2005 Arthur van Hoff, Rick Blair
// Licensed under Apache License version 2.0
// Original license LGPL
package javax.jmdns.impl;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;

import javax.jmdns.JmDNS;
import javax.jmdns.ServiceEvent;
import javax.jmdns.ServiceInfo;
import javax.jmdns.ServiceListener;
import javax.jmdns.ServiceTypeListener;
import javax.jmdns.impl.tasks.Announcer;
import javax.jmdns.impl.tasks.Canceler;
import javax.jmdns.impl.tasks.Prober;
import javax.jmdns.impl.tasks.RecordReaper;
import javax.jmdns.impl.tasks.Renewer;
import javax.jmdns.impl.tasks.Responder;
import javax.jmdns.impl.tasks.ServiceInfoResolver;
import javax.jmdns.impl.tasks.ServiceResolver;
import javax.jmdns.impl.tasks.TypeResolver;

import android.util.Log;

// 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 JmDNSImpl extends JmDNS {
  public final static String TAG = JmDNSImpl.class.toString();
  /** 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. */
  private boolean closed = false;
  /** Holds instances of JmDNS.DNSListener. Must by a synchronized collection,
   * because it is updated from concurrent threads. */
  @SuppressWarnings("rawtypes")
  private List listeners;
  /** Holds instances of ServiceListener's. Keys are Strings holding a fully
   * qualified service type. Values are LinkedList's of ServiceListener's. */
  @SuppressWarnings("rawtypes")
  private Map serviceListeners;
  /** Holds instances of ServiceTypeListener's. */
  @SuppressWarnings("rawtypes")
  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. */
  @SuppressWarnings("rawtypes")
  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. */
  @SuppressWarnings("rawtypes")
  Map serviceTypes;
  /** This is the shutdown hook, we registered with the java runtime. */
  private Thread shutdown;
  /** Handle on the local host */
  private 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. */
  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. */
  private 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 */
  @SuppressWarnings("rawtypes")
  private HashMap serviceCollectors = new HashMap();

  /** Create an instance of JmDNS. */
  public JmDNSImpl(InetAddress addr, String hostname) throws IOException {
    Log.d(TAG, "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?
      // init(addr.isLoopbackAddress() ? null : addr, hostname); // [PJYF
      // Oct
      // 14 2004] Why do we disallow the loopback address?
      init(addr, hostname);
    } catch (IOException e) {
      init(null, "computer");
    }
  }

  /** Create an instance of JmDNS and bind it to a specific network interface
   * given its IP-address. */
  public JmDNSImpl(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. */
  @SuppressWarnings({ "rawtypes", "unchecked" })
  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);
    // Log.w(TAG, "one");
    cache = new DNSCache(100);
    listeners = Collections.synchronizedList(new ArrayList());
    serviceListeners = new HashMap();
    typeListeners = new ArrayList();
    // Log.w(TAG, "two");
    services = new Hashtable(20);
    serviceTypes = new Hashtable(20);
    // REMIND: If I could pass in a name for the Timer thread,
    // I would pass 'JmDNS.Timer'.
    timer = new Timer();
    new RecordReaper(this).start(timer);
    shutdown = new Thread(new Shutdown(), "JmDNS.Shutdown");
    Runtime.getRuntime().addShutdownHook(shutdown);
    incomingListener = new Thread(new SocketListener(this),
        "JmDNS.SocketListener");
    // Bind to multicast socket
    Log.d(TAG, "about to openMulticastSocket()");
    openMulticastSocket(getLocalHost());
    start(getServices().values());
    Log.d(TAG, "finished multicast socket");
  }

  @SuppressWarnings("rawtypes")
  private void start(Collection serviceInfos) {
    // Log.d(TAG, "start1");
    setState(DNSState.PROBING_1);
    incomingListener.start();
    new Prober(this).start(timer);
    // Log.d(TAG, "start2");
    for (Iterator iterator = serviceInfos.iterator(); iterator.hasNext();) {
      try {
        // Log.d(TAG, "start3");
        registerService(new ServiceInfoImpl(
            (ServiceInfoImpl) iterator.next()));
        // Log.d(TAG, "start4");
      } catch (Exception exception) {
        Log.d(TAG,
            "start() Registration exception: "
                + exception.getMessage());
      }
    }
    // Log.d(TAG, "start5");
  }

  private void openMulticastSocket(HostInfo hostInfo) throws IOException {
    // Log.d(TAG, "1");
    if (group == null) {
      group = InetAddress.getByName(DNSConstants.MDNS_GROUP);
    }
    if (socket != null) {
      this.closeMulticastSocket();
    }
    // Log.d(TAG, "2");
    socket = new MulticastSocket(DNSConstants.MDNS_PORT);
    if ((hostInfo != null) && (localHost.getInterface() != null)) {
      // Log.d(TAG, "3");
      // socket.setNetworkInterface(hostInfo.getInterface());
    }
    // Log.d(TAG, "4");
    socket.setTimeToLive(255);
    socket.joinGroup(group);
    // Log.d(TAG, "5");
  }

  private void closeMulticastSocket() {
    Log.d(TAG, "closeMulticastSocket()");
    if (socket != null) {
      // close socket
      try {
        socket.leaveGroup(group);
        socket.close();
        if (incomingListener != null) {
          incomingListener.join();
        }
      } catch (Exception exception) {
        Log.d(TAG, "closeMulticastSocket() Close socket exception: "
            + exception.getMessage());
      }
      socket = null;
    }
  }

  // State machine
  /** Sets the state and notifies all objects that wait on JmDNS. */
  public synchronized void advanceState() {
    setState(getState().advance());
    notifyAll();
  }

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

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

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

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

  /** @see javax.jmdns.JmDNS#getHostName() */
  public String getHostName() {
    return localHost.getName();
  }

  public HostInfo getLocalHost() {
    return localHost;
  }

  /** @see javax.jmdns.JmDNS#getInterface() */
  public InetAddress getInterface() throws IOException {
    return socket.getInterface();
  }

  /** @see javax.jmdns.JmDNS#getServiceInfo(java.lang.String, java.lang.String) */
  public ServiceInfo getServiceInfo(String type, String name) {
    return getServiceInfo(type, name, 3 * 1000);
  }

  /** @see javax.jmdns.JmDNS#getServiceInfo(java.lang.String, java.lang.String,
   * int) */
  public ServiceInfo getServiceInfo(String type, String name, int timeout) {
    ServiceInfoImpl info = new ServiceInfoImpl(type, name);
    new ServiceInfoResolver(this, info).start(timer);
    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;
    // return (info.hasData()) ? info : null;
  }

  /** @see javax.jmdns.JmDNS#requestServiceInfo(java.lang.String,
   * java.lang.String) */
  public void requestServiceInfo(String type, String name) {
    requestServiceInfo(type, name, 3 * 1000);
  }

  /** @see javax.jmdns.JmDNS#requestServiceInfo(java.lang.String,
   * java.lang.String, int) */
  public void requestServiceInfo(String type, String name, int timeout) {
    registerServiceType(type);
    ServiceInfoImpl info = new ServiceInfoImpl(type, name);
    new ServiceInfoResolver(this, info).start(timer);
    try {
      long end = System.currentTimeMillis() + timeout;
      long delay;
      synchronized (info) {
        while (!info.hasData()
            && (delay = end - System.currentTimeMillis()) > 0) {
          info.wait(delay);
        }
      }
    } catch (InterruptedException e) {
      Log.d(TAG, "requestServiceInfo() ran out of time");
      // empty
    }
  }

  @SuppressWarnings({ "rawtypes", "unchecked" })
  void handleServiceResolved(ServiceInfoImpl info) {
    List list = (List) serviceListeners.get(info.type.toLowerCase());
    if (list != null) {
      ServiceEvent event = new ServiceEventImpl(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);
      }
    }
  }

  /** @see javax.jmdns.JmDNS#addServiceTypeListener(javax.jmdns.ServiceTypeListener) */
  @SuppressWarnings({ "unchecked", "rawtypes" })
  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 ServiceEventImpl(this,
          (String) iterator.next(), null, null));
    }
    new TypeResolver(this).start(timer);
  }

  /** @see javax.jmdns.JmDNS#removeServiceTypeListener(javax.jmdns.ServiceTypeListener) */
  public void removeServiceTypeListener(ServiceTypeListener listener) {
    synchronized (this) {
      typeListeners.remove(listener);
    }
  }

  /** @see javax.jmdns.JmDNS#addServiceListener(java.lang.String,
   * javax.jmdns.ServiceListener) */
  @SuppressWarnings({ "unchecked", "rawtypes" })
  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 ServiceEventImpl(this, type,
                toUnqualifiedName(type, rec.name), null));
          }
        }
      }
    }
    new ServiceResolver(this, type).start(timer);
  }

  /** @see javax.jmdns.JmDNS#removeServiceListener(java.lang.String,
   * javax.jmdns.ServiceListener) */
  @SuppressWarnings("rawtypes")
  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);
        }
      }
    }
  }

  /** @see javax.jmdns.JmDNS#registerService(javax.jmdns.ServiceInfo) */
  @SuppressWarnings("unchecked")
  public void registerService(ServiceInfo infoAbstract) throws IOException {
    ServiceInfoImpl info = (ServiceInfoImpl) infoAbstract;
    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(this).start(timer);
    try {
      synchronized (info) {
        while (info.getState().compareTo(DNSState.ANNOUNCED) < 0) {
          info.wait();
        }
      }
    } catch (InterruptedException e) {
      // empty
    }
    Log.d(TAG, "registerService() JmDNS registered service as " + info);
  }

  /** @see javax.jmdns.JmDNS#unregisterService(javax.jmdns.ServiceInfo) */
  public void unregisterService(ServiceInfo infoAbstract) {
    ServiceInfoImpl info = (ServiceInfoImpl) infoAbstract;
    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(this, info, lock).start(timer);
    // Remind: We get a deadlock here, if the Canceler does not run!
    try {
      synchronized (lock) {
        lock.wait();
      }
    } catch (InterruptedException e) {
      // empty
    }
  }

  /** @see javax.jmdns.JmDNS#unregisterAllServices() */
  @SuppressWarnings({ "unchecked", "rawtypes" })
  public void unregisterAllServices() {
    Log.d(TAG, "unregisterAllServices()");
    if (services.size() == 0) {
      return;
    }
    Collection list;
    synchronized (this) {
      list = new LinkedList(services.values());
      services.clear();
    }
    for (Iterator iterator = list.iterator(); iterator.hasNext();) {
      ((ServiceInfoImpl) iterator.next()).cancel();
    }
    Object lock = new Object();
    new Canceler(this, list, lock).start(timer);
    // Remind: We get a livelock here, if the Canceler does not run!
    try {
      synchronized (lock) {
        if (!closed) {
          lock.wait();
        }
      }
    } catch (InterruptedException e) {
      // empty
    }
  }

  /** @see javax.jmdns.JmDNS#registerServiceType(java.lang.String) */
  @SuppressWarnings({ "rawtypes", "unchecked" })
  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 ServiceEventImpl(this, type,
                  null, null));
        }
      }
    }
  }

  /** 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(ServiceInfoImpl 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())) {
            Log.d(TAG,
                "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. */
  @SuppressWarnings("unchecked")
  public 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. */
  public 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. */
  @SuppressWarnings({ "unchecked", "rawtypes" })
  public 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 ServiceEventImpl(this, type,
              toUnqualifiedName(type, name), null);
          for (Iterator iterator = serviceListenerList.iterator(); iterator
              .hasNext();) {
            ((ServiceListener) iterator.next()).serviceAdded(event);
          }
        } else {
          // expire record
          ServiceEvent event = new ServiceEventImpl(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. */
  @SuppressWarnings("rawtypes")
  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();
      // Log.d(TAG, String.format("handleResponse rec=%s",
      // rec.toString()));
      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(this).start(timer);
    }
  }

  /** Handle an incoming query. See if we can answer any part of it given our
   * service infos. */
  @SuppressWarnings("rawtypes")
  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)) {
        Log.d(TAG, String.format("someone asked for A or AAAA host=%s",
            answer.getName()));
        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(this, in, addr, port).start();
    }
    if (hostConflictDetected || serviceConflictDetected) {
      new Prober(this).start(timer);
    }
  }

  /** Add an answer to a question. Deal with the case when the outgoing packet
   * overflows */
  public 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. */
  public void send(DNSOutgoing out) throws IOException {
    out.finish();
    if (!out.isEmpty()) {
      DatagramPacket packet = new DatagramPacket(out.data, out.off,
          group, DNSConstants.MDNS_PORT);
      try {
        new DNSIncoming(packet);
      } catch (IOException e) {
        Log.w(TAG,
            "send(DNSOutgoing) - JmDNS can not parse what it sends!!!",
            e);
      }
      socket.send(packet);
    }
  }

  public void startAnnouncer() {
    new Announcer(this).start(timer);
  }

  public void startRenewer() {
    new Renewer(this).start(timer);
  }

  public void schedule(TimerTask task, int delay) {
    timer.schedule(task, delay);
  }

  // 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. */
  @SuppressWarnings({ "rawtypes", "unchecked" })
  public void recover() {
    Log.d(TAG, "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 != getState()) {
      synchronized (this) { // Synchronize only if we are not already in
                  // process to prevent dead locks
        //
        Log.d(TAG, "recover() Cleanning up");
        // Stop JmDNS
        setState(DNSState.CANCELED); // This protects against recursive
                        // calls
        // We need to keep a copy for reregistration
        Collection oldServiceInfos = new ArrayList(getServices()
            .values());
        // Cancel all services
        unregisterAllServices();
        disposeServiceCollectors();
        //
        // close multicast socket
        closeMulticastSocket();
        //
        cache.clear();
        Log.d(TAG, "recover() All is clean");
        //
        // All is clear now start the services
        //
        try {
          openMulticastSocket(getLocalHost());
          start(oldServiceInfos);
        } catch (Exception exception) {
          Log.w(TAG, "recover() Start services exception ", exception);
        }
        Log.i(TAG, "recover() We are back!");
      }
    }
  }

  /** @see javax.jmdns.JmDNS#close() */
  public void close() {
    if (getState() != DNSState.CANCELED) {
      synchronized (this) { // Synchronize only if we are not already in
                  // process to prevent dead locks
        // Stop JmDNS
        setState(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();
  }

  /** @see javax.jmdns.JmDNS#printServices() */
  public void printServices() {
    Log.e(TAG, toString());
  }

  @SuppressWarnings("rawtypes")
  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();
  }

  /** @see javax.jmdns.JmDNS#list(java.lang.String) */
  @SuppressWarnings("unchecked")
  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)</code>.
   * @see #list */
  @SuppressWarnings("rawtypes")
  private void disposeServiceCollectors() {
    Log.d(TAG, "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)</code>.
   * @see #list */
  private static class ServiceCollector implements ServiceListener {
    /** A set of collected service instance names. */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    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. */
    @SuppressWarnings("unchecked")
    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. */
    @SuppressWarnings("unchecked")
    public ServiceInfoImpl[] list() {
      synchronized (infos) {
        return (ServiceInfoImpl[]) infos.values().toArray(
            new ServiceInfoImpl[infos.size()]);
      }
    }

    @SuppressWarnings("rawtypes")
    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;
    }
  }

  public void setState(DNSState state) {
    this.state = state;
  }

  public void setTask(TimerTask task) {
    this.task = task;
  }

  public TimerTask getTask() {
    return task;
  }

  @SuppressWarnings("rawtypes")
  public Map getServices() {
    return services;
  }

  public void setLastThrottleIncrement(long lastThrottleIncrement) {
    this.lastThrottleIncrement = lastThrottleIncrement;
  }

  public long getLastThrottleIncrement() {
    return lastThrottleIncrement;
  }

  public void setThrottle(int throttle) {
    this.throttle = throttle;
  }

  public int getThrottle() {
    return throttle;
  }

  public static Random getRandom() {
    return random;
  }

  public void setIoLock(Object ioLock) {
    this.ioLock = ioLock;
  }

  public Object getIoLock() {
    return ioLock;
  }

  public void setPlannedAnswer(DNSIncoming plannedAnswer) {
    this.plannedAnswer = plannedAnswer;
  }

  public DNSIncoming getPlannedAnswer() {
    return plannedAnswer;
  }

  void setLocalHost(HostInfo localHost) {
    this.localHost = localHost;
  }

  @SuppressWarnings("rawtypes")
  public Map getServiceTypes() {
    return serviceTypes;
  }

  public void setClosed(boolean closed) {
    this.closed = closed;
  }

  public boolean isClosed() {
    return closed;
  }

  public MulticastSocket getSocket() {
    return socket;
  }

  public InetAddress getGroup() {
    return group;
  }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.