Clover Coverage Report - JmDNS 3.4.1
Coverage timestamp: Thu Aug 25 2011 13:06:33 CEST
../../../img/srcFileCovDistChart7.png 18% of files have more coverage
720   2,214   330   5.5
294   1,389   0.46   21.83
131     2.52  
6    
 
  JmDNSImpl       Line # 56 630 0% 284 360 64.1% 0.6407186
  JmDNSImpl.Operation       Line # 59 0 - 0 0 - -1.0
  JmDNSImpl.ServiceTypeEntry       Line # 112 23 0% 11 22 37.1% 0.37142858
  JmDNSImpl.ServiceTypeEntry.SubTypeEntry       Line # 118 12 0% 12 17 39.3% 0.39285713
  JmDNSImpl.Shutdown       Line # 1662 4 0% 2 5 0% 0.0
  JmDNSImpl.ServiceCollector       Line # 1992 51 0% 21 32 57.3% 0.5733333
 
  (21)
 
1    // /Copyright 2003-2005 Arthur van Hoff, Rick Blair
2    // Licensed under Apache License version 2.0
3    // Original license LGPL
4   
5    package javax.jmdns.impl;
6   
7    import java.io.IOException;
8    import java.net.DatagramPacket;
9    import java.net.Inet4Address;
10    import java.net.Inet6Address;
11    import java.net.InetAddress;
12    import java.net.MulticastSocket;
13    import java.net.SocketException;
14    import java.util.AbstractMap;
15    import java.util.ArrayList;
16    import java.util.Collection;
17    import java.util.Collections;
18    import java.util.HashMap;
19    import java.util.HashSet;
20    import java.util.Iterator;
21    import java.util.LinkedList;
22    import java.util.List;
23    import java.util.Map;
24    import java.util.Properties;
25    import java.util.Random;
26    import java.util.Set;
27    import java.util.concurrent.ConcurrentHashMap;
28    import java.util.concurrent.ConcurrentMap;
29    import java.util.concurrent.ExecutorService;
30    import java.util.concurrent.Executors;
31    import java.util.concurrent.locks.ReentrantLock;
32    import java.util.logging.Level;
33    import java.util.logging.Logger;
34   
35    import javax.jmdns.JmDNS;
36    import javax.jmdns.ServiceEvent;
37    import javax.jmdns.ServiceInfo;
38    import javax.jmdns.ServiceInfo.Fields;
39    import javax.jmdns.ServiceListener;
40    import javax.jmdns.ServiceTypeListener;
41    import javax.jmdns.impl.ListenerStatus.ServiceListenerStatus;
42    import javax.jmdns.impl.ListenerStatus.ServiceTypeListenerStatus;
43    import javax.jmdns.impl.constants.DNSConstants;
44    import javax.jmdns.impl.constants.DNSRecordClass;
45    import javax.jmdns.impl.constants.DNSRecordType;
46    import javax.jmdns.impl.constants.DNSState;
47    import javax.jmdns.impl.tasks.DNSTask;
48   
49    // REMIND: multiple IP addresses
50   
51    /**
52    * mDNS implementation in Java.
53    *
54    * @author Arthur van Hoff, Rick Blair, Jeff Sonstein, Werner Randelshofer, Pierre Frisch, Scott Lewis
55    */
 
56    public class JmDNSImpl extends JmDNS implements DNSStatefulObject, DNSTaskStarter {
57    private static Logger logger = Logger.getLogger(JmDNSImpl.class.getName());
58   
 
59    public enum Operation {
60    Remove, Update, Add, RegisterServiceType, Noop
61    }
62   
63    /**
64    * This is the multicast group, we are listening to for multicast DNS messages.
65    */
66    private volatile InetAddress _group;
67    /**
68    * This is our multicast socket.
69    */
70    private volatile MulticastSocket _socket;
71   
72    /**
73    * Holds instances of JmDNS.DNSListener. Must by a synchronized collection, because it is updated from concurrent threads.
74    */
75    private final List<DNSListener> _listeners;
76   
77    /**
78    * Holds instances of ServiceListener's. Keys are Strings holding a fully qualified service type. Values are LinkedList's of ServiceListener's.
79    */
80    private final ConcurrentMap<String, List<ServiceListenerStatus>> _serviceListeners;
81   
82    /**
83    * Holds instances of ServiceTypeListener's.
84    */
85    private final Set<ServiceTypeListenerStatus> _typeListeners;
86   
87    /**
88    * Cache for DNSEntry's.
89    */
90    private final DNSCache _cache;
91   
92    /**
93    * 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.
94    */
95    private final ConcurrentMap<String, ServiceInfo> _services;
96   
97    /**
98    * This hashtable holds the service types that have been registered or that have been received in an incoming datagram.<br/>
99    * Keys are instances of String which hold an all lower-case version of the fully qualified service type.<br/>
100    * Values hold the fully qualified service type.
101    */
102    private final ConcurrentMap<String, ServiceTypeEntry> _serviceTypes;
103   
104    private volatile Delegate _delegate;
105   
106    /**
107    * This is used to store type entries. The type is stored as a call variable and the map support the subtypes.
108    * <p>
109    * The key is the lowercase version as the value is the case preserved version.
110    * </p>
111    */
 
112    public static class ServiceTypeEntry extends AbstractMap<String, String> implements Cloneable {
113   
114    private final Set<Map.Entry<String, String>> _entrySet;
115   
116    private final String _type;
117   
 
118    private static class SubTypeEntry implements Entry<String, String>, java.io.Serializable, Cloneable {
119   
120    private static final long serialVersionUID = 9188503522395855322L;
121   
122    private final String _key;
123    private final String _value;
124   
 
125  2 toggle public SubTypeEntry(String subtype) {
126  2 super();
127  2 _value = (subtype != null ? subtype : "");
128  2 _key = _value.toLowerCase();
129    }
130   
131    /**
132    * {@inheritDoc}
133    */
 
134  7 toggle @Override
135    public String getKey() {
136  7 return _key;
137    }
138   
139    /**
140    * {@inheritDoc}
141    */
 
142  0 toggle @Override
143    public String getValue() {
144  0 return _value;
145    }
146   
147    /**
148    * Replaces the value corresponding to this entry with the specified value (optional operation). This implementation simply throws <tt>UnsupportedOperationException</tt>, as this class implements an <i>immutable</i> map entry.
149    *
150    * @param value
151    * new value to be stored in this entry
152    * @return (Does not return)
153    * @exception UnsupportedOperationException
154    * always
155    */
 
156  0 toggle @Override
157    public String setValue(String value) {
158  0 throw new UnsupportedOperationException();
159    }
160   
161    /**
162    * {@inheritDoc}
163    */
 
164  0 toggle @Override
165    public boolean equals(Object entry) {
166  0 if (!(entry instanceof Map.Entry)) {
167  0 return false;
168    }
169  0 return this.getKey().equals(((Map.Entry<?, ?>) entry).getKey()) && this.getValue().equals(((Map.Entry<?, ?>) entry).getValue());
170    }
171   
172    /**
173    * {@inheritDoc}
174    */
 
175  2 toggle @Override
176    public int hashCode() {
177  2 return (_key == null ? 0 : _key.hashCode()) ^ (_value == null ? 0 : _value.hashCode());
178    }
179   
180    /*
181    * (non-Javadoc)
182    * @see java.lang.Object#clone()
183    */
 
184  0 toggle @Override
185    public SubTypeEntry clone() {
186    // Immutable object
187  0 return this;
188    }
189   
190    /**
191    * {@inheritDoc}
192    */
 
193  0 toggle @Override
194    public String toString() {
195  0 return _key + "=" + _value;
196    }
197   
198    }
199   
 
200  27 toggle public ServiceTypeEntry(String type) {
201  27 super();
202  27 this._type = type;
203  27 this._entrySet = new HashSet<Map.Entry<String, String>>();
204    }
205   
206    /**
207    * The type associated with this entry.
208    *
209    * @return the type
210    */
 
211  0 toggle public String getType() {
212  0 return _type;
213    }
214   
215    /*
216    * (non-Javadoc)
217    * @see java.util.AbstractMap#entrySet()
218    */
 
219  13 toggle @Override
220    public Set<Map.Entry<String, String>> entrySet() {
221  13 return _entrySet;
222    }
223   
224    /**
225    * Returns <code>true</code> if this set contains the specified element. More formally, returns <code>true</code> if and only if this set contains an element <code>e</code> such that
226    * <code>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</code>.
227    *
228    * @param subtype
229    * element whose presence in this set is to be tested
230    * @return <code>true</code> if this set contains the specified element
231    */
 
232  13 toggle public boolean contains(String subtype) {
233  13 return subtype != null && this.containsKey(subtype.toLowerCase());
234    }
235   
236    /**
237    * Adds the specified element to this set if it is not already present. More formally, adds the specified element <code>e</code> to this set if this set contains no element <code>e2</code> such that
238    * <code>(e==null&nbsp;?&nbsp;e2==null&nbsp;:&nbsp;e.equals(e2))</code>. If this set already contains the element, the call leaves the set unchanged and returns <code>false</code>.
239    *
240    * @param subtype
241    * element to be added to this set
242    * @return <code>true</code> if this set did not already contain the specified element
243    */
 
244  2 toggle public boolean add(String subtype) {
245  2 if (subtype == null || this.contains(subtype)) {
246  0 return false;
247    }
248  2 _entrySet.add(new SubTypeEntry(subtype));
249  2 return true;
250    }
251   
252    /**
253    * Returns an iterator over the elements in this set. The elements are returned in no particular order (unless this set is an instance of some class that provides a guarantee).
254    *
255    * @return an iterator over the elements in this set
256    */
 
257  0 toggle public Iterator<String> iterator() {
258  0 return this.keySet().iterator();
259    }
260   
261    /*
262    * (non-Javadoc)
263    * @see java.util.AbstractMap#clone()
264    */
 
265  0 toggle @Override
266    public ServiceTypeEntry clone() {
267  0 ServiceTypeEntry entry = new ServiceTypeEntry(this.getType());
268  0 for (Map.Entry<String, String> subTypeEntry : this.entrySet()) {
269  0 entry.add(subTypeEntry.getValue());
270    }
271  0 return entry;
272    }
273   
274    /*
275    * (non-Javadoc)
276    * @see java.util.AbstractMap#toString()
277    */
 
278  0 toggle @Override
279    public String toString() {
280  0 final StringBuilder aLog = new StringBuilder(200);
281  0 if (this.isEmpty()) {
282  0 aLog.append("empty");
283    } else {
284  0 for (String value : this.values()) {
285  0 aLog.append(value);
286  0 aLog.append(", ");
287    }
288  0 aLog.setLength(aLog.length() - 2);
289    }
290  0 return aLog.toString();
291    }
292   
293    }
294   
295    /**
296    * This is the shutdown hook, we registered with the java runtime.
297    */
298    protected Thread _shutdown;
299   
300    /**
301    * Handle on the local host
302    */
303    private HostInfo _localHost;
304   
305    private Thread _incomingListener;
306   
307    /**
308    * Throttle count. This is used to count the overall number of probes sent by JmDNS. When the last throttle increment happened .
309    */
310    private int _throttle;
311   
312    /**
313    * Last throttle increment.
314    */
315    private long _lastThrottleIncrement;
316   
317    private final ExecutorService _executor = Executors.newSingleThreadExecutor();
318   
319    //
320    // 2009-09-16 ldeck: adding docbug patch with slight ammendments
321    // 'Fixes two deadlock conditions involving JmDNS.close() - ID: 1473279'
322    //
323    // ---------------------------------------------------
324    /**
325    * The timer that triggers our announcements. We can't use the main timer object, because that could cause a deadlock where Prober waits on JmDNS.this lock held by close(), close() waits for us to finish, and we wait for Prober to give us back
326    * the timer thread so we can announce. (Patch from docbug in 2006-04-19 still wasn't patched .. so I'm doing it!)
327    */
328    // private final Timer _cancelerTimer;
329    // ---------------------------------------------------
330   
331    /**
332    * The source for random values. This is used to introduce random delays in responses. This reduces the potential for collisions on the network.
333    */
334    private final static Random _random = new Random();
335   
336    /**
337    * 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.
338    */
339    private final ReentrantLock _ioLock = new ReentrantLock();
340   
341    /**
342    * 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.<br/>
343    * FIXME [PJYF June 8 2010]: This does not work well with multiple planned answers for packages that came in from different clients.
344    */
345    private DNSIncoming _plannedAnswer;
346   
347    // State machine
348   
349    /**
350    * 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.
351    *
352    * @see #list
353    */
354    private final ConcurrentMap<String, ServiceCollector> _serviceCollectors;
355   
356    private final String _name;
357   
358    /**
359    * Main method to display API information if run from java -jar
360    *
361    * @param argv
362    * the command line arguments
363    */
 
364  0 toggle public static void main(String[] argv) {
365  0 String version = null;
366  0 try {
367  0 final Properties pomProperties = new Properties();
368  0 pomProperties.load(JmDNSImpl.class.getResourceAsStream("/META-INF/maven/javax.jmdns/jmdns/pom.properties"));
369  0 version = pomProperties.getProperty("version");
370    } catch (Exception e) {
371  0 version = "RUNNING.IN.IDE.FULL";
372    }
373  0 System.out.println("JmDNS version \"" + version + "\"");
374  0 System.out.println(" ");
375   
376  0 System.out.println("Running on java version \"" + System.getProperty("java.version") + "\"" + " (build " + System.getProperty("java.runtime.version") + ")" + " from " + System.getProperty("java.vendor"));
377   
378  0 System.out.println("Operating environment \"" + System.getProperty("os.name") + "\"" + " version " + System.getProperty("os.version") + " on " + System.getProperty("os.arch"));
379   
380  0 System.out.println("For more information on JmDNS please visit https://sourceforge.net/projects/jmdns/");
381    }
382   
383    /**
384    * Create an instance of JmDNS and bind it to a specific network interface given its IP-address.
385    *
386    * @param address
387    * IP address to bind to.
388    * @param name
389    * name of the newly created JmDNS
390    * @exception IOException
391    */
 
392  29 toggle public JmDNSImpl(InetAddress address, String name) throws IOException {
393  29 super();
394  29 if (logger.isLoggable(Level.FINER)) {
395  0 logger.finer("JmDNS instance created");
396    }
397  29 _cache = new DNSCache(100);
398   
399  29 _listeners = Collections.synchronizedList(new ArrayList<DNSListener>());
400  29 _serviceListeners = new ConcurrentHashMap<String, List<ServiceListenerStatus>>();
401  29 _typeListeners = Collections.synchronizedSet(new HashSet<ServiceTypeListenerStatus>());
402  29 _serviceCollectors = new ConcurrentHashMap<String, ServiceCollector>();
403   
404  29 _services = new ConcurrentHashMap<String, ServiceInfo>(20);
405  29 _serviceTypes = new ConcurrentHashMap<String, ServiceTypeEntry>(20);
406   
407  29 _localHost = HostInfo.newHostInfo(address, this, name);
408  29 _name = (name != null ? name : _localHost.getName());
409   
410    // _cancelerTimer = new Timer("JmDNS.cancelerTimer");
411   
412    // (ldeck 2.1.1) preventing shutdown blocking thread
413    // -------------------------------------------------
414    // _shutdown = new Thread(new Shutdown(), "JmDNS.Shutdown");
415    // Runtime.getRuntime().addShutdownHook(_shutdown);
416   
417    // -------------------------------------------------
418   
419    // Bind to multicast socket
420  29 this.openMulticastSocket(this.getLocalHost());
421  29 this.start(this.getServices().values());
422   
423  29 this.startReaper();
424    }
425   
 
426  29 toggle private void start(Collection<? extends ServiceInfo> serviceInfos) {
427  29 if (_incomingListener == null) {
428  29 _incomingListener = new SocketListener(this);
429  29 _incomingListener.start();
430    }
431  29 this.startProber();
432  29 for (ServiceInfo info : serviceInfos) {
433  0 try {
434  0 this.registerService(new ServiceInfoImpl(info));
435    } catch (final Exception exception) {
436  0 logger.log(Level.WARNING, "start() Registration exception ", exception);
437    }
438    }
439    }
440   
 
441  29 toggle private void openMulticastSocket(HostInfo hostInfo) throws IOException {
442  29 if (_group == null) {
443  29 if (hostInfo.getInetAddress() instanceof Inet6Address) {
444  1 _group = InetAddress.getByName(DNSConstants.MDNS_GROUP_IPV6);
445    } else {
446  28 _group = InetAddress.getByName(DNSConstants.MDNS_GROUP);
447    }
448    }
449  29 if (_socket != null) {
450  0 this.closeMulticastSocket();
451    }
452  29 _socket = new MulticastSocket(DNSConstants.MDNS_PORT);
453  29 if ((hostInfo != null) && (hostInfo.getInterface() != null)) {
454  29 try {
455  29 _socket.setNetworkInterface(hostInfo.getInterface());
456    } catch (SocketException e) {
457  0 if (logger.isLoggable(Level.FINE)) {
458  0 logger.fine("openMulticastSocket() Set network interface exception: " + e.getMessage());
459    }
460    }
461    }
462  29 _socket.setTimeToLive(255);
463  29 _socket.joinGroup(_group);
464    }
465   
 
466  29 toggle private void closeMulticastSocket() {
467    // jP: 20010-01-18. See below. We'll need this monitor...
468    // assert (Thread.holdsLock(this));
469  29 if (logger.isLoggable(Level.FINER)) {
470  0 logger.finer("closeMulticastSocket()");
471    }
472  29 if (_socket != null) {
473    // close socket
474  29 try {
475  29 try {
476  29 _socket.leaveGroup(_group);
477    } catch (SocketException exception) {
478    //
479    }
480  29 _socket.close();
481    // jP: 20010-01-18. It isn't safe to join() on the listener
482    // thread - it attempts to lock the IoLock object, and deadlock
483    // ensues. Per issue #2933183, changed this to wait on the JmDNS
484    // monitor, checking on each notify (or timeout) that the
485    // listener thread has stopped.
486    //
487  42 while (_incomingListener != null && _incomingListener.isAlive()) {
488  13 synchronized (this) {
489  13 try {
490  13 if (_incomingListener != null && _incomingListener.isAlive()) {
491    // wait time is arbitrary, we're really expecting notification.
492  13 if (logger.isLoggable(Level.FINER)) {
493  0 logger.finer("closeMulticastSocket(): waiting for jmDNS monitor");
494    }
495  13 this.wait(1000);
496    }
497    } catch (InterruptedException ignored) {
498    // Ignored
499    }
500    }
501    }
502  29 _incomingListener = null;
503    } catch (final Exception exception) {
504  0 logger.log(Level.WARNING, "closeMulticastSocket() Close socket exception ", exception);
505    }
506  29 _socket = null;
507    }
508    }
509   
510    // State machine
511    /**
512    * {@inheritDoc}
513    */
 
514  164 toggle @Override
515    public boolean advanceState(DNSTask task) {
516  164 return this._localHost.advanceState(task);
517    }
518   
519    /**
520    * {@inheritDoc}
521    */
 
522  0 toggle @Override
523    public boolean revertState() {
524  0 return this._localHost.revertState();
525    }
526   
527    /**
528    * {@inheritDoc}
529    */
 
530  0 toggle @Override
531    public boolean cancelState() {
532  0 return this._localHost.cancelState();
533    }
534   
535    /**
536    * {@inheritDoc}
537    */
 
538  29 toggle @Override
539    public boolean closeState() {
540  29 return this._localHost.closeState();
541    }
542   
543    /**
544    * {@inheritDoc}
545    */
 
546  0 toggle @Override
547    public boolean recoverState() {
548  0 return this._localHost.recoverState();
549    }
550   
551    /**
552    * {@inheritDoc}
553    */
 
554  650 toggle @Override
555    public JmDNSImpl getDns() {
556  663 return this;
557    }
558   
559    /**
560    * {@inheritDoc}
561    */
 
562  184 toggle @Override
563    public void associateWithTask(DNSTask task, DNSState state) {
564  184 this._localHost.associateWithTask(task, state);
565    }
566   
567    /**
568    * {@inheritDoc}
569    */
 
570  133 toggle @Override
571    public void removeAssociationWithTask(DNSTask task) {
572  133 this._localHost.removeAssociationWithTask(task);
573    }
574   
575    /**
576    * {@inheritDoc}
577    */
 
578  317 toggle @Override
579    public boolean isAssociatedWithTask(DNSTask task, DNSState state) {
580  317 return this._localHost.isAssociatedWithTask(task, state);
581    }
582   
583    /**
584    * {@inheritDoc}
585    */
 
586  0 toggle @Override
587    public boolean isProbing() {
588  0 return this._localHost.isProbing();
589    }
590   
591    /**
592    * {@inheritDoc}
593    */
 
594  0 toggle @Override
595    public boolean isAnnouncing() {
596  0 return this._localHost.isAnnouncing();
597    }
598   
599    /**
600    * {@inheritDoc}
601    */
 
602  532 toggle @Override
603    public boolean isAnnounced() {
604  534 return this._localHost.isAnnounced();
605    }
606   
607    /**
608    * {@inheritDoc}
609    */
 
610  2378 toggle @Override
611    public boolean isCanceling() {
612  2414 return this._localHost.isCanceling();
613    }
614   
615    /**
616    * {@inheritDoc}
617    */
 
618  2393 toggle @Override
619    public boolean isCanceled() {
620  2417 return this._localHost.isCanceled();
621    }
622   
623    /**
624    * {@inheritDoc}
625    */
 
626  718 toggle @Override
627    public boolean isClosing() {
628  730 return this._localHost.isClosing();
629    }
630   
631    /**
632    * {@inheritDoc}
633    */
 
634  662 toggle @Override
635    public boolean isClosed() {
636  674 return this._localHost.isClosed();
637    }
638   
639    /**
640    * {@inheritDoc}
641    */
 
642  20 toggle @Override
643    public boolean waitForAnnounced(long timeout) {
644  20 return this._localHost.waitForAnnounced(timeout);
645    }
646   
647    /**
648    * {@inheritDoc}
649    */
 
650  29 toggle @Override
651    public boolean waitForCanceled(long timeout) {
652  29 return this._localHost.waitForCanceled(timeout);
653    }
654   
655    /**
656    * Return the DNSCache associated with the cache variable
657    *
658    * @return DNS cache
659    */
 
660  1974 toggle public DNSCache getCache() {
661  1995 return _cache;
662    }
663   
664    /**
665    * {@inheritDoc}
666    */
 
667  835 toggle @Override
668    public String getName() {
669  835 return _name;
670    }
671   
672    /**
673    * {@inheritDoc}
674    */
 
675  0 toggle @Override
676    public String getHostName() {
677  0 return _localHost.getName();
678    }
679   
680    /**
681    * Returns the local host info
682    *
683    * @return local host info
684    */
 
685  1976 toggle public HostInfo getLocalHost() {
686  2011 return _localHost;
687    }
688   
689    /**
690    * {@inheritDoc}
691    */
 
692  0 toggle @Override
693    public InetAddress getInterface() throws IOException {
694  0 return _socket.getInterface();
695    }
696   
697    /**
698    * {@inheritDoc}
699    */
 
700  3 toggle @Override
701    public ServiceInfo getServiceInfo(String type, String name) {
702  3 return this.getServiceInfo(type, name, false, DNSConstants.SERVICE_INFO_TIMEOUT);
703    }
704   
705    /**
706    * {@inheritDoc}
707    */
 
708  0 toggle @Override
709    public ServiceInfo getServiceInfo(String type, String name, long timeout) {
710  0 return this.getServiceInfo(type, name, false, timeout);
711    }
712   
713    /**
714    * {@inheritDoc}
715    */
 
716  0 toggle @Override
717    public ServiceInfo getServiceInfo(String type, String name, boolean persistent) {
718  0 return this.getServiceInfo(type, name, persistent, DNSConstants.SERVICE_INFO_TIMEOUT);
719    }
720   
721    /**
722    * {@inheritDoc}
723    */
 
724  3 toggle @Override
725    public ServiceInfo getServiceInfo(String type, String name, boolean persistent, long timeout) {
726  3 final ServiceInfoImpl info = this.resolveServiceInfo(type, name, "", persistent);
727  3 this.waitForInfoData(info, timeout);
728  3 return (info.hasData() ? info : null);
729    }
730   
 
731  21 toggle ServiceInfoImpl resolveServiceInfo(String type, String name, String subtype, boolean persistent) {
732  21 this.cleanCache();
733  21 String loType = type.toLowerCase();
734  21 this.registerServiceType(type);
735  21 if (_serviceCollectors.putIfAbsent(loType, new ServiceCollector(type)) == null) {
736  2 this.addServiceListener(loType, _serviceCollectors.get(loType), ListenerStatus.SYNCHONEOUS);
737    }
738   
739    // Check if the answer is in the cache.
740  21 final ServiceInfoImpl info = this.getServiceInfoFromCache(type, name, subtype, persistent);
741    // We still run the resolver to do the dispatch but if the info is already there it will quit immediately
742  21 this.startServiceInfoResolver(info);
743   
744  21 return info;
745    }
746   
 
747  59 toggle ServiceInfoImpl getServiceInfoFromCache(String type, String name, String subtype, boolean persistent) {
748    // Check if the answer is in the cache.
749  62 ServiceInfoImpl info = new ServiceInfoImpl(type, name, subtype, 0, 0, 0, persistent, (byte[]) null);
750  62 DNSEntry pointerEntry = this.getCache().getDNSEntry(new DNSRecord.Pointer(type, DNSRecordClass.CLASS_ANY, false, 0, info.getQualifiedName()));
751  62 if (pointerEntry instanceof DNSRecord) {
752  53 ServiceInfoImpl cachedInfo = (ServiceInfoImpl) ((DNSRecord) pointerEntry).getServiceInfo(persistent);
753  54 if (cachedInfo != null) {
754    // To get a complete info record we need to retrieve the service, address and the text bytes.
755   
756  54 Map<Fields, String> map = cachedInfo.getQualifiedNameMap();
757  53 byte[] srvBytes = null;
758  54 String server = "";
759  54 DNSEntry serviceEntry = this.getCache().getDNSEntry(info.getQualifiedName(), DNSRecordType.TYPE_SRV, DNSRecordClass.CLASS_ANY);
760  54 if (serviceEntry instanceof DNSRecord) {
761  13 ServiceInfo cachedServiceEntryInfo = ((DNSRecord) serviceEntry).getServiceInfo(persistent);
762  13 if (cachedServiceEntryInfo != null) {
763  13 cachedInfo = new ServiceInfoImpl(map, cachedServiceEntryInfo.getPort(), cachedServiceEntryInfo.getWeight(), cachedServiceEntryInfo.getPriority(), persistent, (byte[]) null);
764  13 srvBytes = cachedServiceEntryInfo.getTextBytes();
765  13 server = cachedServiceEntryInfo.getServer();
766    }
767    }
768  54 DNSEntry addressEntry = this.getCache().getDNSEntry(server, DNSRecordType.TYPE_A, DNSRecordClass.CLASS_ANY);
769  53 if (addressEntry instanceof DNSRecord) {
770  12 ServiceInfo cachedAddressInfo = ((DNSRecord) addressEntry).getServiceInfo(persistent);
771  12 if (cachedAddressInfo != null) {
772  12 for (Inet4Address address : cachedAddressInfo.getInet4Addresses()) {
773  12 cachedInfo.addAddress(address);
774    }
775  12 cachedInfo._setText(cachedAddressInfo.getTextBytes());
776    }
777    }
778  52 addressEntry = this.getCache().getDNSEntry(server, DNSRecordType.TYPE_AAAA, DNSRecordClass.CLASS_ANY);
779  54 if (addressEntry instanceof DNSRecord) {
780  13 ServiceInfo cachedAddressInfo = ((DNSRecord) addressEntry).getServiceInfo(persistent);
781  13 if (cachedAddressInfo != null) {
782  13 for (Inet6Address address : cachedAddressInfo.getInet6Addresses()) {
783  13 cachedInfo.addAddress(address);
784    }
785  13 cachedInfo._setText(cachedAddressInfo.getTextBytes());
786    }
787    }
788  53 DNSEntry textEntry = this.getCache().getDNSEntry(cachedInfo.getQualifiedName(), DNSRecordType.TYPE_TXT, DNSRecordClass.CLASS_ANY);
789  53 if (textEntry instanceof DNSRecord) {
790  13 ServiceInfo cachedTextInfo = ((DNSRecord) textEntry).getServiceInfo(persistent);
791  13 if (cachedTextInfo != null) {
792  13 cachedInfo._setText(cachedTextInfo.getTextBytes());
793    }
794    }
795  52 if (cachedInfo.getTextBytes().length == 0) {
796  0 cachedInfo._setText(srvBytes);
797    }
798  54 if (cachedInfo.hasData()) {
799  13 info = cachedInfo;
800    }
801    }
802    }
803  61 return info;
804    }
805   
 
806  4 toggle private void waitForInfoData(ServiceInfo info, long timeout) {
807  4 synchronized (info) {
808  4 long loops = (timeout / 200L);
809  4 if (loops < 1) {
810  0 loops = 1;
811    }
812  4 for (int i = 0; i < loops; i++) {
813  4 if (info.hasData()) {
814  4 break;
815    }
816  0 try {
817  0 info.wait(200);
818    } catch (final InterruptedException e) {
819    /* Stub */
820    }
821    }
822    }
823    }
824   
825    /**
826    * {@inheritDoc}
827    */
 
828  1 toggle @Override
829    public void requestServiceInfo(String type, String name) {
830  1 this.requestServiceInfo(type, name, false, DNSConstants.SERVICE_INFO_TIMEOUT);
831    }
832   
833    /**
834    * {@inheritDoc}
835    */
 
836  0 toggle @Override
837    public void requestServiceInfo(String type, String name, boolean persistent) {
838  0 this.requestServiceInfo(type, name, persistent, DNSConstants.SERVICE_INFO_TIMEOUT);
839    }
840   
841    /**
842    * {@inheritDoc}
843    */
 
844  0 toggle @Override
845    public void requestServiceInfo(String type, String name, long timeout) {
846  0 this.requestServiceInfo(type, name, false, DNSConstants.SERVICE_INFO_TIMEOUT);
847    }
848   
849    /**
850    * {@inheritDoc}
851    */
 
852  1 toggle @Override
853    public void requestServiceInfo(String type, String name, boolean persistent, long timeout) {
854  1 final ServiceInfoImpl info = this.resolveServiceInfo(type, name, "", persistent);
855  1 this.waitForInfoData(info, timeout);
856    }
857   
 
858  68 toggle void handleServiceResolved(ServiceEvent event) {
859  68 List<ServiceListenerStatus> list = _serviceListeners.get(event.getType().toLowerCase());
860  68 final List<ServiceListenerStatus> listCopy;
861  68 if ((list != null) && (!list.isEmpty())) {
862  27 if ((event.getInfo() != null) && event.getInfo().hasData()) {
863  27 final ServiceEvent localEvent = event;
864  27 synchronized (list) {
865  27 listCopy = new ArrayList<ServiceListenerStatus>(list);
866    }
867  27 for (final ServiceListenerStatus listener : listCopy) {
868  40 _executor.submit(new Runnable() {
869    /** {@inheritDoc} */
 
870  40 toggle @Override
871    public void run() {
872  40 listener.serviceResolved(localEvent);
873    }
874    });
875    }
876    }
877    }
878    }
879   
880    /**
881    * {@inheritDoc}
882    */
 
883  0 toggle @Override
884    public void addServiceTypeListener(ServiceTypeListener listener) throws IOException {
885  0 ServiceTypeListenerStatus status = new ServiceTypeListenerStatus(listener, ListenerStatus.ASYNCHONEOUS);
886  0 _typeListeners.add(status);
887   
888    // report cached service types
889  0 for (String type : _serviceTypes.keySet()) {
890  0 status.serviceTypeAdded(new ServiceEventImpl(this, type, "", null));
891    }
892   
893  0 this.startTypeResolver();
894    }
895   
896    /**
897    * {@inheritDoc}
898    */
 
899  0 toggle @Override
900    public void removeServiceTypeListener(ServiceTypeListener listener) {
901  0 ServiceTypeListenerStatus status = new ServiceTypeListenerStatus(listener, ListenerStatus.ASYNCHONEOUS);
902  0 _typeListeners.remove(status);
903    }
904   
905    /**
906    * {@inheritDoc}
907    */
 
908  8 toggle @Override
909    public void addServiceListener(String type, ServiceListener listener) {
910  8 this.addServiceListener(type, listener, ListenerStatus.ASYNCHONEOUS);
911    }
912   
 
913  25 toggle private void addServiceListener(String type, ServiceListener listener, boolean synch) {
914  25 ServiceListenerStatus status = new ServiceListenerStatus(listener, synch);
915  25 final String loType = type.toLowerCase();
916  25 List<ServiceListenerStatus> list = _serviceListeners.get(loType);
917  25 if (list == null) {
918  17 if (_serviceListeners.putIfAbsent(loType, new LinkedList<ServiceListenerStatus>()) == null) {
919  17 if (_serviceCollectors.putIfAbsent(loType, new ServiceCollector(type)) == null) {
920    // We have a problem here. The service collectors must be called synchronously so that their cache get cleaned up immediately or we will report .
921  8 this.addServiceListener(loType, _serviceCollectors.get(loType), ListenerStatus.SYNCHONEOUS);
922    }
923    }
924  17 list = _serviceListeners.get(loType);
925    }
926  25 if (list != null) {
927  25 synchronized (list) {
928  25 if (!list.contains(listener)) {
929  25 list.add(status);
930    }
931    }
932    }
933    // report cached service types
934  25 final List<ServiceEvent> serviceEvents = new ArrayList<ServiceEvent>();
935  25 Collection<DNSEntry> dnsEntryLits = this.getCache().allValues();
936  25 for (DNSEntry entry : dnsEntryLits) {
937  41 final DNSRecord record = (DNSRecord) entry;
938  41 if (record.getRecordType() == DNSRecordType.TYPE_SRV) {
939  8 if (record.getKey().endsWith(loType)) {
940    // Do not used the record embedded method for generating event this will not work.
941    // serviceEvents.add(record.getServiceEvent(this));
942  8 serviceEvents.add(new ServiceEventImpl(this, record.getType(), toUnqualifiedName(record.getType(), record.getName()), record.getServiceInfo()));
943    }
944    }
945    }
946    // Actually call listener with all service events added above
947  25 for (ServiceEvent serviceEvent : serviceEvents) {
948  8 status.serviceAdded(serviceEvent);
949    }
950    // Create/start ServiceResolver
951  25 this.startServiceResolver(type);
952    }
953   
954    /**
955    * {@inheritDoc}
956    */
 
957  17 toggle @Override
958    public void removeServiceListener(String type, ServiceListener listener) {
959  17 String loType = type.toLowerCase();
960  17 List<ServiceListenerStatus> list = _serviceListeners.get(loType);
961  17 if (list != null) {
962  17 synchronized (list) {
963  17 ServiceListenerStatus status = new ServiceListenerStatus(listener, ListenerStatus.ASYNCHONEOUS);
964  17 list.remove(status);
965  17 if (list.isEmpty()) {
966  9 _serviceListeners.remove(loType, list);
967    }
968    }
969    }
970    }
971   
972    /**
973    * {@inheritDoc}
974    */
 
975  21 toggle @Override
976    public void registerService(ServiceInfo infoAbstract) throws IOException {
977  21 if (this.isClosing() || this.isClosed()) {
978  0 throw new IllegalStateException("This DNS is closed.");
979    }
980  21 final ServiceInfoImpl info = (ServiceInfoImpl) infoAbstract;
981   
982  21 if (info.getDns() != null) {
983  2 if (info.getDns() != this) {
984  0 throw new IllegalStateException("A service information can only be registered with a single instamce of JmDNS.");
985  2 } else if (_services.get(info.getKey()) != null) {
986  1 throw new IllegalStateException("A service information can only be registered once.");
987    }
988    }
989  20 info.setDns(this);
990   
991  20 this.registerServiceType(info.getTypeWithSubtype());
992   
993    // bind the service to this address
994  20 info.recoverState();
995  20 info.setServer(_localHost.getName());
996  20 info.addAddress(_localHost.getInet4Address());
997  20 info.addAddress(_localHost.getInet6Address());
998   
999  20 this.waitForAnnounced(DNSConstants.SERVICE_INFO_TIMEOUT);
1000   
1001  20 this.makeServiceNameUnique(info);
1002  20 while (_services.putIfAbsent(info.getKey(), info) != null) {
1003  0 this.makeServiceNameUnique(info);
1004    }
1005   
1006  20 this.startProber();
1007  20 info.waitForAnnounced(DNSConstants.SERVICE_INFO_TIMEOUT);
1008   
1009  20 if (logger.isLoggable(Level.FINE)) {
1010  0 logger.fine("registerService() JmDNS registered service as " + info);
1011    }
1012    }
1013   
1014    /**
1015    * {@inheritDoc}
1016    */
 
1017  4 toggle @Override
1018    public void unregisterService(ServiceInfo infoAbstract) {
1019  4 final ServiceInfoImpl info = (ServiceInfoImpl) _services.get(infoAbstract.getKey());
1020   
1021  4 if (info != null) {
1022  4 info.cancelState();
1023  4 this.startCanceler();
1024  4 info.waitForCanceled(DNSConstants.CLOSE_TIMEOUT);
1025   
1026  4 _services.remove(info.getKey(), info);
1027  4 if (logger.isLoggable(Level.FINE)) {
1028  0 logger.fine("unregisterService() JmDNS unregistered service as " + info);
1029    }
1030    } else {
1031  0 logger.warning("Removing unregistered service info: " + infoAbstract.getKey());
1032    }
1033    }
1034   
1035    /**
1036    * {@inheritDoc}
1037    */
 
1038  29 toggle @Override
1039    public void unregisterAllServices() {
1040  29 if (logger.isLoggable(Level.FINER)) {
1041  0 logger.finer("unregisterAllServices()");
1042    }
1043   
1044  29 for (String name : _services.keySet()) {
1045  16 ServiceInfoImpl info = (ServiceInfoImpl) _services.get(name);
1046  16 if (info != null) {
1047  16 if (logger.isLoggable(Level.FINER)) {
1048  0 logger.finer("Cancelling service info: " + info);
1049    }
1050  16 info.cancelState();
1051    }
1052    }
1053  29 this.startCanceler();
1054   
1055  29 for (String name : _services.keySet()) {
1056  16 ServiceInfoImpl info = (ServiceInfoImpl) _services.get(name);
1057  16 if (info != null) {
1058  16 if (logger.isLoggable(Level.FINER)) {
1059  0 logger.finer("Wait for service info cancel: " + info);
1060    }
1061  16 info.waitForCanceled(DNSConstants.CLOSE_TIMEOUT);
1062  16 _services.remove(name, info);
1063    }
1064    }
1065   
1066    }
1067   
1068    /**
1069    * {@inheritDoc}
1070    */
 
1071  193 toggle @Override
1072    public boolean registerServiceType(String type) {
1073  193 boolean typeAdded = false;
1074  193 Map<Fields, String> map = ServiceInfoImpl.decodeQualifiedNameMapForType(type);
1075  199 String domain = map.get(Fields.Domain);
1076  197 String protocol = map.get(Fields.Protocol);
1077  197 String application = map.get(Fields.Application);
1078  198 String subtype = map.get(Fields.Subtype);
1079   
1080  198 final String name = (application.length() > 0 ? "_" + application + "." : "") + (protocol.length() > 0 ? "_" + protocol + "." : "") + domain + ".";
1081  196 final String loname = name.toLowerCase();
1082  196 if (logger.isLoggable(Level.FINE)) {
1083  0 logger.fine(this.getName() + ".registering service type: " + type + " as: " + name + (subtype.length() > 0 ? " subtype: " + subtype : ""));
1084    }
1085  196 if (!_serviceTypes.containsKey(loname) && !application.toLowerCase().equals("dns-sd") && !domain.toLowerCase().endsWith("in-addr.arpa") && !domain.toLowerCase().endsWith("ip6.arpa")) {
1086  27 typeAdded = _serviceTypes.putIfAbsent(loname, new ServiceTypeEntry(name)) == null;
1087  27 if (typeAdded) {
1088  27 final ServiceTypeListenerStatus[] list = _typeListeners.toArray(new ServiceTypeListenerStatus[_typeListeners.size()]);
1089  27 final ServiceEvent event = new ServiceEventImpl(this, name, "", null);
1090  27 for (final ServiceTypeListenerStatus status : list) {
1091  0 _executor.submit(new Runnable() {
1092    /** {@inheritDoc} */
 
1093  0 toggle @Override
1094    public void run() {
1095  0 status.serviceTypeAdded(event);
1096    }
1097    });
1098    }
1099    }
1100    }
1101  197 if (subtype.length() > 0) {
1102  9 ServiceTypeEntry subtypes = _serviceTypes.get(loname);
1103  9 if ((subtypes != null) && (!subtypes.contains(subtype))) {
1104  2 synchronized (subtypes) {
1105  2 if (!subtypes.contains(subtype)) {
1106  2 typeAdded = true;
1107  2 subtypes.add(subtype);
1108  2 final ServiceTypeListenerStatus[] list = _typeListeners.toArray(new ServiceTypeListenerStatus[_typeListeners.size()]);
1109  2 final ServiceEvent event = new ServiceEventImpl(this, "_" + subtype + "._sub." + name, "", null);
1110  2 for (final ServiceTypeListenerStatus status : list) {
1111  0 _executor.submit(new Runnable() {
1112    /** {@inheritDoc} */
 
1113  0 toggle @Override
1114    public void run() {
1115  0 status.subTypeForServiceTypeAdded(event);
1116    }
1117    });
1118    }
1119    }
1120    }
1121    }
1122    }
1123  197 return typeAdded;
1124    }
1125   
1126    /**
1127    * Generate a possibly unique name for a service using the information we have in the cache.
1128    *
1129    * @return returns true, if the name of the service info had to be changed.
1130    */
 
1131  20 toggle private boolean makeServiceNameUnique(ServiceInfoImpl info) {
1132  20 final String originalQualifiedName = info.getKey();
1133  20 final long now = System.currentTimeMillis();
1134   
1135  20 boolean collision;
1136  20 do {
1137  20 collision = false;
1138   
1139    // Check for collision in cache
1140  20 for (DNSEntry dnsEntry : this.getCache().getDNSEntryList(info.getKey())) {
1141  0 if (DNSRecordType.TYPE_SRV.equals(dnsEntry.getRecordType()) && !dnsEntry.isExpired(now)) {
1142  0 final DNSRecord.Service s = (DNSRecord.Service) dnsEntry;
1143  0 if (s.getPort() != info.getPort() || !s.getServer().equals(_localHost.getName())) {
1144  0 if (logger.isLoggable(Level.FINER)) {
1145  0 logger.finer("makeServiceNameUnique() JmDNS.makeServiceNameUnique srv collision:" + dnsEntry + " s.server=" + s.getServer() + " " + _localHost.getName() + " equals:" + (s.getServer().equals(_localHost.getName())));
1146    }
1147  0 info.setName(incrementName(info.getName()));
1148  0 collision = true;
1149  0 break;
1150    }
1151    }
1152    }
1153   
1154    // Check for collision with other service infos published by JmDNS
1155  20 final ServiceInfo selfService = _services.get(info.getKey());
1156  20 if (selfService != null && selfService != info) {
1157  0 info.setName(incrementName(info.getName()));
1158  0 collision = true;
1159    }
1160    }
1161  20 while (collision);
1162   
1163  20 return !(originalQualifiedName.equals(info.getKey()));
1164    }
1165   
 
1166  0 toggle String incrementName(String name) {
1167  0 String aName = name;
1168  0 try {
1169  0 final int l = aName.lastIndexOf('(');
1170  0 final int r = aName.lastIndexOf(')');
1171  0 if ((l >= 0) && (l < r)) {
1172  0 aName = aName.substring(0, l) + "(" + (Integer.parseInt(aName.substring(l + 1, r)) + 1) + ")";
1173    } else {
1174  0 aName += " (2)";
1175    }
1176    } catch (final NumberFormatException e) {
1177  0 aName += " (2)";
1178    }
1179  0 return aName;
1180    }
1181   
1182    /**
1183    * 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.
1184    *
1185    * @param listener
1186    * DSN listener
1187    * @param question
1188    * DNS query
1189    */
 
1190  21 toggle public void addListener(DNSListener listener, DNSQuestion question) {
1191  21 final long now = System.currentTimeMillis();
1192   
1193    // add the new listener
1194  21 _listeners.add(listener);
1195   
1196    // report existing matched records
1197   
1198  21 if (question != null) {
1199  21 for (DNSEntry dnsEntry : this.getCache().getDNSEntryList(question.getName().toLowerCase())) {
1200  24 if (question.answeredBy(dnsEntry) && !dnsEntry.isExpired(now)) {