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)) {
1201  24 listener.updateRecord(this.getCache(), now, dnsEntry);
1202    }
1203    }
1204    }
1205    }
1206   
1207    /**
1208    * Remove a listener from all outstanding questions. The listener will no longer receive any updates.
1209    *
1210    * @param listener
1211    * DSN listener
1212    */
 
1213  2 toggle public void removeListener(DNSListener listener) {
1214  2 _listeners.remove(listener);
1215    }
1216   
1217    /**
1218    * Renew a service when the record become stale. If there is no service collector for the type this method does nothing.
1219    *
1220    * @param record
1221    * DNS record
1222    */
 
1223  40 toggle public void renewServiceCollector(DNSRecord record) {
1224  40 ServiceInfo info = record.getServiceInfo();
1225  40 if (_serviceCollectors.containsKey(info.getType().toLowerCase())) {
1226    // Create/start ServiceResolver
1227  3 this.startServiceResolver(info.getType());
1228    }
1229    }
1230   
1231    // Remind: Method updateRecord should receive a better name.
1232    /**
1233    * Notify all listeners that a record was updated.
1234    *
1235    * @param now
1236    * update date
1237    * @param rec
1238    * DNS record
1239    * @param operation
1240    * DNS cache operation
1241    */
 
1242  175 toggle public void updateRecord(long now, DNSRecord rec, Operation operation) {
1243    // We do not want to block the entire DNS while we are updating the record for each listener (service info)
1244    {
1245  176 List<DNSListener> listenerList = null;
1246  175 synchronized (_listeners) {
1247  177 listenerList = new ArrayList<DNSListener>(_listeners);
1248    }
1249  177 for (DNSListener listener : listenerList) {
1250  42 listener.updateRecord(this.getCache(), now, rec);
1251    }
1252    }
1253  177 if (DNSRecordType.TYPE_PTR.equals(rec.getRecordType()))
1254    // if (DNSRecordType.TYPE_PTR.equals(rec.getRecordType()) || DNSRecordType.TYPE_SRV.equals(rec.getRecordType()))
1255    {
1256  41 ServiceEvent event = rec.getServiceEvent(this);
1257  40 if ((event.getInfo() == null) || !event.getInfo().hasData()) {
1258    // We do not care about the subtype because the info is only used if complete and the subtype will then be included.
1259  40 ServiceInfo info = this.getServiceInfoFromCache(event.getType(), event.getName(), "", false);
1260  40 if (info.hasData()) {
1261  1 event = new ServiceEventImpl(this, event.getType(), event.getName(), info);
1262    }
1263    }
1264   
1265  41 List<ServiceListenerStatus> list = _serviceListeners.get(event.getType().toLowerCase());
1266  41 final List<ServiceListenerStatus> serviceListenerList;
1267  41 if (list != null) {
1268  15 synchronized (list) {
1269  15 serviceListenerList = new ArrayList<ServiceListenerStatus>(list);
1270    }
1271    } else {
1272  26 serviceListenerList = Collections.emptyList();
1273    }
1274  41 if (logger.isLoggable(Level.FINEST)) {
1275  0 logger.finest(this.getName() + ".updating record for event: " + event + " list " + serviceListenerList + " operation: " + operation);
1276    }
1277  41 if (!serviceListenerList.isEmpty()) {
1278  15 final ServiceEvent localEvent = event;
1279   
1280  15 switch (operation) {
1281  10 case Add:
1282  10 for (final ServiceListenerStatus listener : serviceListenerList) {
1283  18 if (listener.isSynchronous()) {
1284  10 listener.serviceAdded(localEvent);
1285    } else {
1286  8 _executor.submit(new Runnable() {
1287    /** {@inheritDoc} */
 
1288  8 toggle @Override
1289    public void run() {
1290  8 listener.serviceAdded(localEvent);
1291    }
1292    });
1293    }
1294    }
1295  10 break;
1296  5 case Remove:
1297  5 for (final ServiceListenerStatus listener : serviceListenerList) {
1298  5 if (listener.isSynchronous()) {
1299  5 listener.serviceRemoved(localEvent);
1300    } else {
1301  0 _executor.submit(new Runnable() {
1302    /** {@inheritDoc} */
 
1303  0 toggle @Override
1304    public void run() {
1305  0 listener.serviceRemoved(localEvent);
1306    }
1307    });
1308    }
1309    }
1310  5 break;
1311  0 default:
1312  0 break;
1313    }
1314    }
1315    }
1316    }
1317   
 
1318  717 toggle void handleRecord(DNSRecord record, long now) {
1319  724 DNSRecord newRecord = record;
1320   
1321  723 Operation cacheOperation = Operation.Noop;
1322  714 final boolean expired = newRecord.isExpired(now);
1323  722 if (logger.isLoggable(Level.FINE)) {
1324  0 logger.fine(this.getName() + " handle response: " + newRecord);
1325    }
1326   
1327    // update the cache
1328  724 if (!newRecord.isServicesDiscoveryMetaQuery() && !newRecord.isDomainDiscoveryQuery()) {
1329  719 final boolean unique = newRecord.isUnique();
1330  717 final DNSRecord cachedRecord = (DNSRecord) this.getCache().getDNSEntry(newRecord);
1331  719 if (logger.isLoggable(Level.FINE)) {
1332  0 logger.fine(this.getName() + " handle response cached record: " + cachedRecord);
1333    }
1334  722 if (unique) {
1335  589 for (DNSEntry entry : this.getCache().getDNSEntryList(newRecord.getKey())) {
1336  968 if (newRecord.getRecordType().equals(entry.getRecordType()) && newRecord.getRecordClass().equals(entry.getRecordClass()) && (entry != cachedRecord)) {
1337  0 ((DNSRecord) entry).setWillExpireSoon(now);
1338    }
1339    }
1340    }
1341  712 if (cachedRecord != null) {
1342  568 if (expired) {
1343    // if the record has a 0 ttl that means we have a cancel record we need to delay the removal by 1s
1344  54 if (newRecord.getTTL() == 0) {
1345  54 cacheOperation = Operation.Noop;
1346  54 cachedRecord.setWillExpireSoon(now);
1347    // the actual record will be disposed of by the record reaper.
1348    } else {
1349  0 cacheOperation = Operation.Remove;
1350  0 this.getCache().removeDNSEntry(cachedRecord);
1351    }
1352    } else {
1353    // If the record content has changed we need to inform our listeners.
1354  515 if (!newRecord.sameValue(cachedRecord) || (!newRecord.sameSubtype(cachedRecord) && (newRecord.getSubtype().length() > 0))) {
1355  8 if (newRecord.isSingleValued()) {
1356  8 cacheOperation = Operation.Update;
1357  7 this.getCache().replaceDNSEntry(newRecord, cachedRecord);
1358    } else {
1359    // Address record can have more than one value on multi-homed machines
1360  0 cacheOperation = Operation.Add;
1361  0 this.getCache().addDNSEntry(newRecord);
1362    }
1363    } else {
1364  509 cachedRecord.resetTTL(newRecord);
1365  507 newRecord = cachedRecord;
1366    }
1367    }
1368    } else {
1369  150 if (!expired) {
1370  152 cacheOperation = Operation.Add;
1371  150 this.getCache().addDNSEntry(newRecord);
1372    }
1373    }
1374    }
1375   
1376    // Register new service types
1377  717 if (newRecord.getRecordType() == DNSRecordType.TYPE_PTR) {
1378    // handle DNSConstants.DNS_META_QUERY records
1379  155 boolean typeAdded = false;
1380  155 if (newRecord.isServicesDiscoveryMetaQuery()) {
1381    // The service names are in the alias.
1382  0 if (!expired) {
1383  0 typeAdded = this.registerServiceType(((DNSRecord.Pointer) newRecord).getAlias());
1384    }
1385  0 return;
1386    }
1387  153 typeAdded |= this.registerServiceType(newRecord.getName());
1388  155 if (typeAdded && (cacheOperation == Operation.Noop)) {
1389  0 cacheOperation = Operation.RegisterServiceType;
1390    }
1391    }
1392   
1393    // notify the listeners
1394  718 if (cacheOperation != Operation.Noop) {
1395  160 this.updateRecord(now, newRecord, cacheOperation);
1396    }
1397   
1398    }
1399   
1400    /**
1401    * Handle an incoming response. Cache answers, and pass them on to the appropriate questions.
1402    *
1403    * @exception IOException
1404    */
 
1405  302 toggle void handleResponse(DNSIncoming msg) throws IOException {
1406  306 final long now = System.currentTimeMillis();
1407   
1408  303 boolean hostConflictDetected = false;
1409  305 boolean serviceConflictDetected = false;
1410   
1411  310 for (DNSRecord newRecord : msg.getAllAnswers()) {
1412  699 this.handleRecord(newRecord, now);
1413   
1414  707 if (DNSRecordType.TYPE_A.equals(newRecord.getRecordType()) || DNSRecordType.TYPE_AAAA.equals(newRecord.getRecordType())) {
1415  306 hostConflictDetected |= newRecord.handleResponse(this);
1416    } else {
1417  398 serviceConflictDetected |= newRecord.handleResponse(this);
1418    }
1419   
1420    }
1421   
1422  309 if (hostConflictDetected || serviceConflictDetected) {
1423  0 this.startProber();
1424    }
1425    }
1426   
1427    /**
1428    * Handle an incoming query. See if we can answer any part of it given our service infos.
1429    *
1430    * @param in
1431    * @param addr
1432    * @param port
1433    * @exception IOException
1434    */
 
1435  335 toggle void handleQuery(DNSIncoming in, InetAddress addr, int port) throws IOException {
1436  344 if (logger.isLoggable(Level.FINE)) {
1437  0 logger.fine(this.getName() + ".handle query: " + in);
1438    }
1439    // Track known answers
1440  346 boolean conflictDetected = false;
1441  345 final long expirationTime = System.currentTimeMillis() + DNSConstants.KNOWN_ANSWER_TTL;
1442  340 for (DNSRecord answer : in.getAllAnswers()) {
1443  240 conflictDetected |= answer.handleQuery(this, expirationTime);
1444    }
1445   
1446  343 this.ioLock();
1447  339 try {
1448   
1449  343 if (_plannedAnswer != null) {
1450  0 _plannedAnswer.append(in);
1451    } else {
1452  347 DNSIncoming plannedAnswer = in.clone();
1453  334 if (in.isTruncated()) {
1454  0 _plannedAnswer = plannedAnswer;
1455    }
1456  338 this.startResponder(plannedAnswer, port);
1457    }
1458   
1459    } finally {
1460  348 this.ioUnlock();
1461    }
1462   
1463  348 final long now = System.currentTimeMillis();
1464  346 for (DNSRecord answer : in.getAnswers()) {
1465  18 this.handleRecord(answer, now);
1466    }
1467   
1468  339 if (conflictDetected) {
1469  0 this.startProber();
1470    }
1471    }
1472   
 
1473  349 toggle public void respondToQuery(DNSIncoming in) {
1474  349 this.ioLock();
1475  348 try {
1476  349 if (_plannedAnswer == in) {
1477  0 _plannedAnswer = null;
1478    }
1479    } finally {
1480  347 this.ioUnlock();
1481    }
1482    }
1483   
1484    /**
1485    * Add an answer to a question. Deal with the case when the outgoing packet overflows
1486    *
1487    * @param in
1488    * @param addr
1489    * @param port
1490    * @param out
1491    * @param rec
1492    * @return outgoing answer
1493    * @exception IOException
1494    */
 
1495  0 toggle public DNSOutgoing addAnswer(DNSIncoming in, InetAddress addr, int port, DNSOutgoing out, DNSRecord rec) throws IOException {
1496  0 DNSOutgoing newOut = out;
1497  0 if (newOut == null) {
1498  0 newOut = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA, false, in.getSenderUDPPayload());
1499    }
1500  0 try {
1501  0 newOut.addAnswer(in, rec);
1502    } catch (final IOException e) {
1503  0 newOut.setFlags(newOut.getFlags() | DNSConstants.FLAGS_TC);
1504  0 newOut.setId(in.getId());
1505  0 send(newOut);
1506   
1507  0 newOut = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA, false, in.getSenderUDPPayload());
1508  0 newOut.addAnswer(in, rec);
1509    }
1510  0 return newOut;
1511    }
1512   
1513    /**
1514    * Send an outgoing multicast DNS message.
1515    *
1516    * @param out
1517    * @exception IOException
1518    */
 
1519  414 toggle public void send(DNSOutgoing out) throws IOException {
1520  414 if (!out.isEmpty()) {
1521  414 byte[] message = out.data();
1522  414 final DatagramPacket packet = new DatagramPacket(message, message.length, _group, DNSConstants.MDNS_PORT);
1523   
1524  414 if (logger.isLoggable(Level.FINEST)) {
1525  0 try {
1526  0 final DNSIncoming msg = new DNSIncoming(packet);
1527  0 if (logger.isLoggable(Level.FINEST)) {
1528  0 logger.finest("send(" + this.getName() + ") JmDNS out:" + msg.print(true));
1529    }
1530    } catch (final IOException e) {
1531  0 logger.throwing(getClass().toString(), "send(" + this.getName() + ") - JmDNS can not parse what it sends!!!", e);
1532    }
1533    }
1534  414 final MulticastSocket ms = _socket;
1535  414 if (ms != null && !ms.isClosed()) {
1536  414 ms.send(packet);
1537    }
1538    }
1539    }
1540   
1541    /*
1542    * (non-Javadoc)
1543    * @see javax.jmdns.impl.DNSTaskStarter#purgeTimer()
1544    */
 
1545  0 toggle @Override
1546    public void purgeTimer() {
1547  0 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).purgeTimer();
1548    }
1549   
1550    /*
1551    * (non-Javadoc)
1552    * @see javax.jmdns.impl.DNSTaskStarter#purgeStateTimer()
1553    */
 
1554  0 toggle @Override
1555    public void purgeStateTimer() {
1556  0 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).purgeStateTimer();
1557    }
1558   
1559    /*
1560    * (non-Javadoc)
1561    * @see javax.jmdns.impl.DNSTaskStarter#cancelTimer()
1562    */
 
1563  29 toggle @Override
1564    public void cancelTimer() {
1565  29 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).cancelTimer();
1566    }
1567   
1568    /*
1569    * (non-Javadoc)
1570    * @see javax.jmdns.impl.DNSTaskStarter#cancelStateTimer()
1571    */
 
1572  29 toggle @Override
1573    public void cancelStateTimer() {
1574  29 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).cancelStateTimer();
1575    }
1576   
1577    /*
1578    * (non-Javadoc)
1579    * @see javax.jmdns.impl.DNSTaskStarter#startProber()
1580    */
 
1581  49 toggle @Override
1582    public void startProber() {
1583  49 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startProber();
1584    }
1585   
1586    /*
1587    * (non-Javadoc)
1588    * @see javax.jmdns.impl.DNSTaskStarter#startAnnouncer()
1589    */
 
1590  51 toggle @Override
1591    public void startAnnouncer() {
1592  51 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startAnnouncer();
1593    }
1594   
1595    /*
1596    * (non-Javadoc)
1597    * @see javax.jmdns.impl.DNSTaskStarter#startRenewer()
1598    */
 
1599  51 toggle @Override
1600    public void startRenewer() {
1601  51 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startRenewer();
1602    }
1603   
1604    /*
1605    * (non-Javadoc)
1606    * @see javax.jmdns.impl.DNSTaskStarter#startCanceler()
1607    */
 
1608  33 toggle @Override
1609    public void startCanceler() {
1610  33 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startCanceler();
1611    }
1612   
1613    /*
1614    * (non-Javadoc)
1615    * @see javax.jmdns.impl.DNSTaskStarter#startReaper()
1616    */
 
1617  29 toggle @Override
1618    public void startReaper() {
1619  29 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startReaper();
1620    }
1621   
1622    /*
1623    * (non-Javadoc)
1624    * @see javax.jmdns.impl.DNSTaskStarter#startServiceInfoResolver(javax.jmdns.impl.ServiceInfoImpl)
1625    */
 
1626  21 toggle @Override
1627    public void startServiceInfoResolver(ServiceInfoImpl info) {
1628  21 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startServiceInfoResolver(info);
1629    }
1630   
1631    /*
1632    * (non-Javadoc)
1633    * @see javax.jmdns.impl.DNSTaskStarter#startTypeResolver()
1634    */
 
1635  0 toggle @Override
1636    public void startTypeResolver() {
1637  0 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startTypeResolver();
1638    }
1639   
1640    /*
1641    * (non-Javadoc)
1642    * @see javax.jmdns.impl.DNSTaskStarter#startServiceResolver(java.lang.String)
1643    */
 
1644  28 toggle @Override
1645    public void startServiceResolver(String type) {
1646  28 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startServiceResolver(type);
1647    }
1648   
1649    /*
1650    * (non-Javadoc)
1651    * @see javax.jmdns.impl.DNSTaskStarter#startResponder(javax.jmdns.impl.DNSIncoming, int)
1652    */
 
1653  329 toggle @Override
1654    public void startResponder(DNSIncoming in, int port) {
1655  342 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startResponder(in, port);
1656    }
1657   
1658    // REMIND: Why is this not an anonymous inner class?
1659    /**
1660    * Shutdown operations.
1661    */
 
1662    protected class Shutdown implements Runnable {
1663    /** {@inheritDoc} */
 
1664  0 toggle @Override
1665    public void run() {
1666  0 try {
1667  0 _shutdown = null;
1668  0 close();
1669    } catch (Throwable exception) {
1670  0 System.err.println("Error while shuting down. " + exception);
1671    }
1672    }
1673    }
1674   
1675    private final Object _recoverLock = new Object();
1676   
1677    /**
1678    * Recover jmdns when there is an error.
1679    */
 
1680  0 toggle public void recover() {
1681  0 logger.finer(this.getName() + "recover()");
1682    // We have an IO error so lets try to recover if anything happens lets close it.
1683    // This should cover the case of the IP address changing under our feet
1684  0 if (this.isClosing() || this.isClosed() || this.isCanceling() || this.isCanceled()) {
1685  0 return;
1686    }
1687   
1688    // We need some definite lock here as we may have multiple timer running in the same thread that will not be stopped by the reentrant lock
1689    // in the state object. This is only a problem in this case as we are going to execute in seperate thread so that the timer can clear.
1690  0 synchronized (_recoverLock) {
1691    // Stop JmDNS
1692    // This protects against recursive calls
1693  0 if (this.cancelState()) {
1694  0 logger.finer(this.getName() + "recover() thread " + Thread.currentThread().getName());
1695  0 Thread recover = new Thread(this.getName() + ".recover()") {
1696    /**
1697    * {@inheritDoc}
1698    */
 
1699  0 toggle @Override
1700    public void run() {
1701  0 __recover();
1702    }
1703    };
1704  0 recover.start();
1705    }
1706    }
1707    }
1708   
 
1709  0 toggle void __recover() {
1710    // Synchronize only if we are not already in process to prevent dead locks
1711    //
1712  0 if (logger.isLoggable(Level.FINER)) {
1713  0 logger.finer(this.getName() + "recover() Cleanning up");
1714    }
1715   
1716  0 logger.warning("RECOVERING");
1717    // Purge the timer
1718  0 this.purgeTimer();
1719   
1720    // We need to keep a copy for reregistration
1721  0 final Collection<ServiceInfo> oldServiceInfos = new ArrayList<ServiceInfo>(getServices().values());
1722   
1723    // Cancel all services
1724  0 this.unregisterAllServices();
1725  0 this.disposeServiceCollectors();
1726   
1727  0 this.waitForCanceled(DNSConstants.CLOSE_TIMEOUT);
1728   
1729    // Purge the canceler timer
1730  0 this.purgeStateTimer();
1731   
1732    //
1733    // close multicast socket
1734  0 this.closeMulticastSocket();
1735   
1736    //
1737  0 this.getCache().clear();
1738  0 if (logger.isLoggable(Level.FINER)) {
1739  0 logger.finer(this.getName() + "recover() All is clean");
1740    }
1741   
1742  0 if (this.isCanceled()) {
1743    //
1744    // All is clear now start the services
1745    //
1746  0 for (ServiceInfo info : oldServiceInfos) {
1747  0 ((ServiceInfoImpl) info).recoverState();
1748    }
1749  0 this.recoverState();
1750   
1751  0 try {
1752  0 this.openMulticastSocket(this.getLocalHost());
1753  0 this.start(oldServiceInfos);
1754    } catch (final Exception exception) {
1755  0 logger.log(Level.WARNING, this.getName() + "recover() Start services exception ", exception);
1756    }
1757  0 logger.log(Level.WARNING, this.getName() + "recover() We are back!");
1758    } else {
1759    // We have a problem. We could not clear the state.
1760  0 logger.log(Level.WARNING, this.getName() + "recover() Could not recover we are Down!");
1761  0 if (this.getDelegate() != null) {
1762  0 this.getDelegate().cannotRecoverFromIOError(this.getDns(), oldServiceInfos);
1763    }
1764    }
1765   
1766    }
1767   
 
1768  90 toggle public void cleanCache() {
1769  90 long now = System.currentTimeMillis();
1770  90 for (DNSEntry entry : this.getCache().allValues()) {
1771  505 try {
1772  505 DNSRecord record = (DNSRecord) entry;
1773  504 if (record.isExpired(now)) {
1774  17 this.updateRecord(now, record, Operation.Remove);
1775  17 this.getCache().removeDNSEntry(record);
1776  488 } else if (record.isStale(now)) {
1777    // we should query for the record we care about i.e. those in the service collectors
1778  40 this.renewServiceCollector(record);
1779    }
1780    } catch (Exception exception) {
1781  0 logger.log(Level.SEVERE, this.getName() + ".Error while reaping records: " + entry, exception);
1782  0 logger.severe(this.toString());
1783    }
1784    }
1785    }
1786   
1787    /**
1788    * {@inheritDoc}
1789    */
 
1790  29 toggle @Override
1791    public void close() {
1792  29 if (this.isClosing()) {
1793  0 return;
1794    }
1795   
1796  29 if (logger.isLoggable(Level.FINER)) {
1797  0 logger.finer("Cancelling JmDNS: " + this);
1798    }
1799    // Stop JmDNS
1800    // This protects against recursive calls
1801  29 if (this.closeState()) {
1802    // We got the tie break now clean up
1803   
1804    // Stop the timer
1805  29 logger.finer("Canceling the timer");
1806  29 this.cancelTimer();
1807   
1808    // Cancel all services
1809  29 this.unregisterAllServices();
1810  29 this.disposeServiceCollectors();
1811   
1812  29 if (logger.isLoggable(Level.FINER)) {
1813  0 logger.finer("Wait for JmDNS cancel: " + this);
1814    }
1815  29 this.waitForCanceled(DNSConstants.CLOSE_TIMEOUT);
1816   
1817    // Stop the canceler timer
1818  29 logger.finer("Canceling the state timer");
1819  29 this.cancelStateTimer();
1820   
1821    // Stop the executor
1822  29 _executor.shutdown();
1823   
1824    // close socket
1825  29 this.closeMulticastSocket();
1826   
1827    // remove the shutdown hook
1828  29 if (_shutdown != null) {
1829  0 Runtime.getRuntime().removeShutdownHook(_shutdown);
1830    }
1831   
1832  29 if (logger.isLoggable(Level.FINER)) {
1833  0 logger.finer("JmDNS closed.");
1834    }
1835    }
1836  29 advanceState(null);
1837    }
1838   
1839    /**
1840    * {@inheritDoc}
1841    */
 
1842  0 toggle @Override
1843    @Deprecated
1844    public void printServices() {
1845  0 System.err.println(toString());
1846    }
1847   
1848    /**
1849    * {@inheritDoc}
1850    */
 
1851  0 toggle @Override
1852    public String toString() {
1853  0 final StringBuilder aLog = new StringBuilder(2048);
1854  0 aLog.append("\t---- Local Host -----");
1855  0 aLog.append("\n\t");
1856  0 aLog.append(_localHost);
1857  0 aLog.append("\n\t---- Services -----");
1858  0 for (String key : _services.keySet()) {
1859  0 aLog.append("\n\t\tService: ");
1860  0 aLog.append(key);
1861  0 aLog.append(": ");
1862  0 aLog.append(_services.get(key));
1863    }
1864  0 aLog.append("\n");
1865  0 aLog.append("\t---- Types ----");
1866  0 for (String key : _serviceTypes.keySet()) {
1867  0 ServiceTypeEntry subtypes = _serviceTypes.get(key);
1868  0 aLog.append("\n\t\tType: ");
1869  0 aLog.append(subtypes.getType());
1870  0 aLog.append(": ");
1871  0 aLog.append(subtypes.isEmpty() ? "no subtypes" : subtypes);
1872    }
1873  0 aLog.append("\n");
1874  0 aLog.append(_cache.toString());
1875  0 aLog.append("\n");
1876  0 aLog.append("\t---- Service Collectors ----");
1877  0 for (String key : _serviceCollectors.keySet()) {
1878  0 aLog.append("\n\t\tService Collector: ");
1879  0 aLog.append(key);
1880  0 aLog.append(": ");
1881  0 aLog.append(_serviceCollectors.get(key));
1882    }
1883  0 aLog.append("\n");
1884  0 aLog.append("\t---- Service Listeners ----");
1885  0 for (String key : _serviceListeners.keySet()) {
1886  0 aLog.append("\n\t\tService Listener: ");
1887  0 aLog.append(key);
1888  0 aLog.append(": ");
1889  0 aLog.append(_serviceListeners.get(key));
1890    }
1891  0 return aLog.toString();
1892    }
1893   
1894    /**
1895    * {@inheritDoc}
1896    */
 
1897  18 toggle @Override
1898    public ServiceInfo[] list(String type) {
1899  18 return this.list(type, DNSConstants.SERVICE_INFO_TIMEOUT);
1900    }
1901   
1902    /**
1903    * {@inheritDoc}
1904    */
 
1905  18 toggle @Override
1906    public ServiceInfo[] list(String type, long timeout) {
1907  18 this.cleanCache();
1908    // Implementation note: The first time a list for a given type is
1909    // requested, a ServiceCollector is created which collects service
1910    // infos. This greatly speeds up the performance of subsequent calls
1911    // to this method. The caveats are, that 1) the first call to this
1912    // method for a given type is slow, and 2) we spawn a ServiceCollector
1913    // instance for each service type which increases network traffic a
1914    // little.
1915   
1916  18 String loType = type.toLowerCase();
1917   
1918  18 boolean newCollectorCreated = false;
1919  18 if (this.isCanceling() || this.isCanceled()) {
1920  0 return new ServiceInfo[0];
1921    }
1922   
1923  18 ServiceCollector collector = _serviceCollectors.get(loType);
1924  18 if (collector == null) {
1925  7 newCollectorCreated = _serviceCollectors.putIfAbsent(loType, new ServiceCollector(type)) == null;
1926  7 collector = _serviceCollectors.get(loType);
1927  7 if (newCollectorCreated) {
1928  7 this.addServiceListener(type, collector, ListenerStatus.SYNCHONEOUS);
1929    }
1930    }
1931  18 if (logger.isLoggable(Level.FINER)) {
1932  0 logger.finer(this.getName() + ".collector: " + collector);
1933    }
1934    // At this stage the collector should never be null but it keeps findbugs happy.
1935  18 return (collector != null ? collector.list(timeout) : new ServiceInfo[0]);
1936    }
1937   
1938    /**
1939    * {@inheritDoc}
1940    */
 
1941  0 toggle @Override
1942    public Map<String, ServiceInfo[]> listBySubtype(String type) {
1943  0 return this.listBySubtype(type, DNSConstants.SERVICE_INFO_TIMEOUT);
1944    }
1945   
1946    /**
1947    * {@inheritDoc}
1948    */
 
1949  0 toggle @Override
1950    public Map<String, ServiceInfo[]> listBySubtype(String type, long timeout) {
1951  0 Map<String, List<ServiceInfo>> map = new HashMap<String, List<ServiceInfo>>(5);
1952  0 for (ServiceInfo info : this.list(type, timeout)) {
1953  0 String subtype = info.getSubtype().toLowerCase();
1954  0 if (!map.containsKey(subtype)) {
1955  0 map.put(subtype, new ArrayList<ServiceInfo>(10));
1956    }
1957  0 map.get(subtype).add(info);
1958    }
1959   
1960  0 Map<String, ServiceInfo[]> result = new HashMap<String, ServiceInfo[]>(map.size());
1961  0 for (String subtype : map.keySet()) {
1962  0 List<ServiceInfo> infoForSubType = map.get(subtype);
1963  0 result.put(subtype, infoForSubType.toArray(new ServiceInfo[infoForSubType.size()]));
1964    }
1965   
1966  0 return result;
1967    }
1968   
1969    /**
1970    * This method disposes all ServiceCollector instances which have been created by calls to method <code>list(type)</code>.
1971    *
1972    * @see #list
1973    */
 
1974  29 toggle private void disposeServiceCollectors() {
1975  29 if (logger.isLoggable(Level.FINER)) {
1976  0 logger.finer("disposeServiceCollectors()");
1977    }
1978  29 for (String type : _serviceCollectors.keySet()) {
1979  17 ServiceCollector collector = _serviceCollectors.get(type);
1980  17 if (collector != null) {
1981  17 this.removeServiceListener(type, collector);
1982  17 _serviceCollectors.remove(type, collector);
1983    }
1984    }
1985    }
1986   
1987    /**
1988    * Instances of ServiceCollector are used internally to speed up the performance of method <code>list(type)</code>.
1989    *
1990    * @see #list
1991    */
 
1992    private static class ServiceCollector implements ServiceListener {
1993    // private static Logger logger = Logger.getLogger(ServiceCollector.class.getName());
1994   
1995    /**
1996    * A set of collected service instance names.
1997    */
1998    private final ConcurrentMap<String, ServiceInfo> _infos;
1999   
2000    /**
2001    * A set of collected service event waiting to be resolved.
2002    */
2003    private final ConcurrentMap<String, ServiceEvent> _events;
2004   
2005    /**
2006    * This is the type we are listening for (only used for debugging).
2007    */
2008    private final String _type;
2009   
2010    /**
2011    * This is used to force a wait on the first invocation of list.
2012    */
2013    private volatile boolean _needToWaitForInfos;
2014   
 
2015  45 toggle public ServiceCollector(String type) {
2016  45 super();
2017  45 _infos = new ConcurrentHashMap<String, ServiceInfo>();
2018  45 _events = new ConcurrentHashMap<String, ServiceEvent>();
2019  45 _type = type;
2020  45 _needToWaitForInfos = true;
2021    }
2022   
2023    /**
2024    * A service has been added.
2025    *
2026    * @param event
2027    * service event
2028    */
 
2029  18 toggle @Override
2030    public void serviceAdded(ServiceEvent event) {
2031  18 synchronized (this) {
2032  18 ServiceInfo info = event.getInfo();
2033  18 if ((info != null) && (info.hasData())) {
2034  1 _infos.put(event.getName(), info);
2035    } else {
2036  17 String subtype = (info != null ? info.getSubtype() : "");
2037  17 info = ((JmDNSImpl) event.getDNS()).resolveServiceInfo(event.getType(), event.getName(), subtype, true);
2038  17 if (info != null) {
2039  17 _infos.put(event.getName(), info);
2040    } else {
2041  0 _events.put(event.getName(), event);
2042    }
2043    }
2044    }
2045    }
2046   
2047    /**
2048    * A service has been removed.
2049    *
2050    * @param event
2051    * service event
2052    */
 
2053  5 toggle @Override
2054    public void serviceRemoved(ServiceEvent event) {
2055  5 synchronized (this) {
2056  5 _infos.remove(event.getName());
2057  5 _events.remove(event.getName());
2058    }
2059    }
2060   
2061    /**
2062    * A service has been resolved. Its details are now available in the ServiceInfo record.
2063    *
2064    * @param event
2065    * service event
2066    */
 
2067  22 toggle @Override
2068    public void serviceResolved(ServiceEvent event) {
2069  22 synchronized (this) {
2070  22 _infos.put(event.getName(), event.getInfo());
2071  22 _events.remove(event.getName());
2072    }
2073    }
2074   
2075    /**
2076    * Returns an array of all service infos which have been collected by this ServiceCollector.
2077    *
2078    * @param timeout
2079    * timeout if the info list is empty.
2080    * @return Service Info array
2081    */
 
2082  18 toggle public ServiceInfo[] list(long timeout) {
2083  18 if (_infos.isEmpty() || !_events.isEmpty() || _needToWaitForInfos) {
2084  15 long loops = (timeout / 200L);
2085  15 if (loops < 1) {
2086  0 loops = 1;
2087    }
2088  465 for (int i = 0; i < loops; i++) {
2089  450 try {
2090  450 Thread.sleep(200);
2091    } catch (final InterruptedException e) {
2092    /* Stub */
2093    }
2094  450 if (_events.isEmpty() && !_infos.isEmpty() && !_needToWaitForInfos) {
2095  0 break;
2096    }
2097    }
2098    }
2099  18 _needToWaitForInfos = false;
2100  18 return _infos.values().toArray(new ServiceInfo[_infos.size()]);
2101    }
2102   
2103    /**
2104    * {@inheritDoc}
2105    */
 
2106  0 toggle @Override
2107    public String toString() {
2108  0 final StringBuffer aLog = new StringBuffer();
2109  0 aLog.append("\n\tType: ");
2110  0 aLog.append(_type);
2111  0 if (_infos.isEmpty()) {
2112  0 aLog.append("\n\tNo services collected.");
2113    } else {
2114  0 aLog.append("\n\tServices");
2115  0 for (String key : _infos.keySet()) {
2116  0 aLog.append("\n\t\tService: ");
2117  0 aLog.append(key);
2118  0 aLog.append(": ");
2119  0 aLog.append(_infos.get(key));
2120    }
2121    }
2122  0 if (_events.isEmpty()) {
2123  0 aLog.append("\n\tNo event queued.");
2124    } else {
2125  0 aLog.append("\n\tEvents");
2126  0 for (String key : _events.keySet()) {
2127  0 aLog.append("\n\t\tEvent: ");
2128  0 aLog.append(key);
2129  0 aLog.append(": ");
2130  0 aLog.append(_events.get(key));
2131    }
2132    }
2133  0 return aLog.toString();
2134    }
2135    }
2136   
 
2137  47 toggle static String toUnqualifiedName(String type, String qualifiedName) {
2138  49 String loType = type.toLowerCase();
2139  49 String loQualifiedName = qualifiedName.toLowerCase();
2140  49 if (loQualifiedName.endsWith(loType) && !(loQualifiedName.equals(loType))) {
2141  41 return qualifiedName.substring(0, qualifiedName.length() - type.length() - 1);
2142    }
2143  8 return qualifiedName;
2144    }
2145   
 
2146  1133 toggle public Map<String, ServiceInfo> getServices() {
2147  1138 return _services;
2148    }
2149   
 
2150  49 toggle public void setLastThrottleIncrement(long lastThrottleIncrement) {
2151  49 this._lastThrottleIncrement = lastThrottleIncrement;
2152    }
2153   
 
2154  49 toggle public long getLastThrottleIncrement() {
2155  49 return _lastThrottleIncrement;
2156    }
2157   
 
2158  49 toggle public void setThrottle(int throttle) {
2159  49 this._throttle = throttle;
2160    }
2161   
 
2162  20 toggle public int getThrottle() {
2163  20 return _throttle;
2164    }
2165   
 
2166  180 toggle public static Random getRandom() {
2167  176 return _random;
2168    }
2169   
 
2170  688 toggle public void ioLock() {
2171  694 _ioLock.lock();
2172    }
2173   
 
2174  691 toggle public void ioUnlock() {
2175  698 _ioLock.unlock();
2176    }
2177   
 
2178  0 toggle public void setPlannedAnswer(DNSIncoming plannedAnswer) {
2179  0 this._plannedAnswer = plannedAnswer;
2180    }
2181   
 
2182  0 toggle public DNSIncoming getPlannedAnswer() {
2183  0 return _plannedAnswer;
2184    }
2185   
 
2186  0 toggle void setLocalHost(HostInfo localHost) {
2187  0 this._localHost = localHost;
2188    }
2189   
 
2190  95 toggle public Map<String, ServiceTypeEntry> getServiceTypes() {
2191  95 return _serviceTypes;
2192    }
2193   
 
2194  679 toggle public MulticastSocket getSocket() {
2195  685 return _socket;
2196    }
2197   
 
2198  331 toggle public InetAddress getGroup() {
2199  343 return _group;
2200    }
2201   
 
2202  0 toggle @Override
2203    public Delegate getDelegate() {
2204  0 return this._delegate;
2205    }
2206   
 
2207  0 toggle @Override
2208    public Delegate setDelegate(Delegate delegate) {
2209  0 Delegate previous = this._delegate;
2210  0 this._delegate = delegate;
2211  0 return previous;
2212    }
2213   
2214    }