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
482   1,323   231   5.36
200   888   0.48   30
90     2.57  
3    
 
  ServiceInfoImpl       Line # 42 468 0% 222 228 69.5% 0.6947791
  ServiceInfoImpl.Delegate       Line # 68 0 - 0 0 - -1.0
  ServiceInfoImpl.ServiceInfoState       Line # 74 14 0% 9 3 88% 0.88
 
  (31)
 
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.ByteArrayOutputStream;
8    import java.io.IOException;
9    import java.io.OutputStream;
10    import java.net.Inet4Address;
11    import java.net.Inet6Address;
12    import java.net.InetAddress;
13    import java.util.ArrayList;
14    import java.util.Collection;
15    import java.util.Collections;
16    import java.util.Enumeration;
17    import java.util.HashMap;
18    import java.util.Hashtable;
19    import java.util.LinkedHashSet;
20    import java.util.List;
21    import java.util.Map;
22    import java.util.Set;
23    import java.util.Vector;
24    import java.util.logging.Level;
25    import java.util.logging.Logger;
26   
27    import javax.jmdns.ServiceEvent;
28    import javax.jmdns.ServiceInfo;
29    import javax.jmdns.impl.DNSRecord.Pointer;
30    import javax.jmdns.impl.DNSRecord.Service;
31    import javax.jmdns.impl.DNSRecord.Text;
32    import javax.jmdns.impl.constants.DNSRecordClass;
33    import javax.jmdns.impl.constants.DNSRecordType;
34    import javax.jmdns.impl.constants.DNSState;
35    import javax.jmdns.impl.tasks.DNSTask;
36   
37    /**
38    * JmDNS service information.
39    *
40    * @author Arthur van Hoff, Jeff Sonstein, Werner Randelshofer
41    */
 
42    public class ServiceInfoImpl extends ServiceInfo implements DNSListener, DNSStatefulObject {
43    private static Logger logger = Logger.getLogger(ServiceInfoImpl.class.getName());
44   
45    private String _domain;
46    private String _protocol;
47    private String _application;
48    private String _name;
49    private String _subtype;
50    private String _server;
51    private int _port;
52    private int _weight;
53    private int _priority;
54    private byte _text[];
55    private Map<String, byte[]> _props;
56    private final Set<Inet4Address> _ipv4Addresses;
57    private final Set<Inet6Address> _ipv6Addresses;
58   
59    private transient String _key;
60   
61    private boolean _persistent;
62    private boolean _needTextAnnouncing;
63   
64    private final ServiceInfoState _state;
65   
66    private Delegate _delegate;
67   
 
68    public static interface Delegate {
69   
70    public void textValueUpdated(ServiceInfo target, byte[] value);
71   
72    }
73   
 
74    private final static class ServiceInfoState extends DNSStatefulObject.DefaultImplementation {
75   
76    private static final long serialVersionUID = 1104131034952196820L;
77   
78    private final ServiceInfoImpl _info;
79   
80    /**
81    * @param info
82    */
 
83  440 toggle public ServiceInfoState(ServiceInfoImpl info) {
84  439 super();
85  440 _info = info;
86    }
87   
 
88  194 toggle @Override
89    protected void setTask(DNSTask task) {
90  194 super.setTask(task);
91  194 if ((this._task == null) && _info.needTextAnnouncing()) {
92  4 this.lock();
93  4 try {
94  4 if ((this._task == null) && _info.needTextAnnouncing()) {
95  4 if (this._state.isAnnounced()) {
96  4 this.setState(DNSState.ANNOUNCING_1);
97  4 if (this.getDns() != null) {
98  4 this.getDns().startAnnouncer();
99    }
100    }
101  4 _info.setNeedTextAnnouncing(false);
102    }
103    } finally {
104  4 this.unlock();
105    }
106    }
107    }
108   
 
109  150 toggle @Override
110    public void setDns(JmDNSImpl dns) {
111  150 super.setDns(dns);
112    }
113   
114    }
115   
116    /**
117    * @param type
118    * @param name
119    * @param subtype
120    * @param port
121    * @param weight
122    * @param priority
123    * @param persistent
124    * @param text
125    * @see javax.jmdns.ServiceInfo#create(String, String, int, int, int, String)
126    */
 
127  0 toggle public ServiceInfoImpl(String type, String name, String subtype, int port, int weight, int priority, boolean persistent, String text) {
128  0 this(ServiceInfoImpl.decodeQualifiedNameMap(type, name, subtype), port, weight, priority, persistent, (byte[]) null);
129  0 _server = text;
130  0 try {
131  0 ByteArrayOutputStream out = new ByteArrayOutputStream(text.length());
132  0 writeUTF(out, text);
133  0 this._text = out.toByteArray();
134    } catch (IOException e) {
135  0 throw new RuntimeException("unexpected exception: " + e);
136    }
137    }
138   
139    /**
140    * @param type
141    * @param name
142    * @param subtype
143    * @param port
144    * @param weight
145    * @param priority
146    * @param persistent
147    * @param props
148    * @see javax.jmdns.ServiceInfo#create(String, String, int, int, int, Map)
149    */
 
150  30 toggle public ServiceInfoImpl(String type, String name, String subtype, int port, int weight, int priority, boolean persistent, Map<String, ?> props) {
151  30 this(ServiceInfoImpl.decodeQualifiedNameMap(type, name, subtype), port, weight, priority, persistent, textFromProperties(props));
152    }
153   
154    /**
155    * @param type
156    * @param name
157    * @param subtype
158    * @param port
159    * @param weight
160    * @param priority
161    * @param persistent
162    * @param text
163    * @see javax.jmdns.ServiceInfo#create(String, String, int, int, int, byte[])
164    */
 
165  62 toggle public ServiceInfoImpl(String type, String name, String subtype, int port, int weight, int priority, boolean persistent, byte text[]) {
166  62 this(ServiceInfoImpl.decodeQualifiedNameMap(type, name, subtype), port, weight, priority, persistent, text);
167    }
168   
 
169  0 toggle public ServiceInfoImpl(Map<Fields, String> qualifiedNameMap, int port, int weight, int priority, boolean persistent, Map<String, ?> props) {
170  0 this(qualifiedNameMap, port, weight, priority, persistent, textFromProperties(props));
171    }
172   
 
173  112 toggle ServiceInfoImpl(Map<Fields, String> qualifiedNameMap, int port, int weight, int priority, boolean persistent, String text) {
174  112 this(qualifiedNameMap, port, weight, priority, persistent, (byte[]) null);
175  113 _server = text;
176  112 try {
177  112 ByteArrayOutputStream out = new ByteArrayOutputStream(text.length());
178  113 writeUTF(out, text);
179  113 this._text = out.toByteArray();
180    } catch (IOException e) {
181  0 throw new RuntimeException("unexpected exception: " + e);
182    }
183    }
184   
 
185  425 toggle ServiceInfoImpl(Map<Fields, String> qualifiedNameMap, int port, int weight, int priority, boolean persistent, byte text[]) {
186  426 Map<Fields, String> map = ServiceInfoImpl.checkQualifiedNameMap(qualifiedNameMap);
187   
188  426 this._domain = map.get(Fields.Domain);
189  426 this._protocol = map.get(Fields.Protocol);
190  426 this._application = map.get(Fields.Application);
191  426 this._name = map.get(Fields.Instance);
192  426 this._subtype = map.get(Fields.Subtype);
193   
194  426 this._port = port;
195  426 this._weight = weight;
196  426 this._priority = priority;
197  426 this._text = text;
198  425 this.setNeedTextAnnouncing(false);
199  423 this._state = new ServiceInfoState(this);
200  424 this._persistent = persistent;
201  425 this._ipv4Addresses = Collections.synchronizedSet(new LinkedHashSet<Inet4Address>());
202  424 this._ipv6Addresses = Collections.synchronizedSet(new LinkedHashSet<Inet6Address>());
203    }
204   
205    /**
206    * During recovery we need to duplicate service info to reregister them
207    *
208    * @param info
209    */
 
210  14 toggle ServiceInfoImpl(ServiceInfo info) {
211  14 this._ipv4Addresses = Collections.synchronizedSet(new LinkedHashSet<Inet4Address>());
212  14 this._ipv6Addresses = Collections.synchronizedSet(new LinkedHashSet<Inet6Address>());
213  14 if (info != null) {
214  14 this._domain = info.getDomain();
215  14 this._protocol = info.getProtocol();
216  14 this._application = info.getApplication();
217  14 this._name = info.getName();
218  14 this._subtype = info.getSubtype();
219  14 this._port = info.getPort();
220  14 this._weight = info.getWeight();
221  14 this._priority = info.getPriority();
222  14 this._text = info.getTextBytes();
223  14 this._persistent = info.isPersistent();
224  14 Inet6Address[] ipv6Addresses = info.getInet6Addresses();
225  14 for (Inet6Address address : ipv6Addresses) {
226  9 this._ipv6Addresses.add(address);
227    }
228  14 Inet4Address[] ipv4Addresses = info.getInet4Addresses();
229  14 for (Inet4Address address : ipv4Addresses) {
230  9 this._ipv4Addresses.add(address);
231    }
232    }
233  14 this._state = new ServiceInfoState(this);
234    }
235   
 
236  94 toggle public static Map<Fields, String> decodeQualifiedNameMap(String type, String name, String subtype) {
237  94 Map<Fields, String> qualifiedNameMap = decodeQualifiedNameMapForType(type);
238   
239  94 qualifiedNameMap.put(Fields.Instance, name);
240  93 qualifiedNameMap.put(Fields.Subtype, subtype);
241   
242  94 return checkQualifiedNameMap(qualifiedNameMap);
243    }
244   
 
245  3004 toggle public static Map<Fields, String> decodeQualifiedNameMapForType(String type) {
246  3029 int index;
247   
248  3027 String casePreservedType = type;
249   
250  3023 String aType = type.toLowerCase();
251  3014 String application = aType;
252  3016 String protocol = "";
253  3021 String subtype = "";
254  3028 String name = "";
255  3028 String domain = "";
256   
257  3028 if (aType.contains("in-addr.arpa") || aType.contains("ip6.arpa")) {
258  65 index = (aType.contains("in-addr.arpa") ? aType.indexOf("in-addr.arpa") : aType.indexOf("ip6.arpa"));
259  65 name = removeSeparators(casePreservedType.substring(0, index));
260  63 domain = casePreservedType.substring(index);
261  63 application = "";
262  2965 } else if ((!aType.contains("_")) && aType.contains(".")) {
263  1206 index = aType.indexOf('.');
264  1208 name = removeSeparators(casePreservedType.substring(0, index));
265  1208 domain = removeSeparators(casePreservedType.substring(index));
266  1212 application = "";
267    } else {
268    // First remove the name if it there.
269  1753 if (!aType.startsWith("_") || aType.startsWith("_services")) {
270  930 index = aType.indexOf('.');
271  930 if (index > 0) {
272    // We need to preserve the case for the user readable name.
273  928 name = casePreservedType.substring(0, index);
274  926 if (index + 1 < aType.length()) {
275  930 aType = aType.substring(index + 1);
276  928 casePreservedType = casePreservedType.substring(index + 1);
277    }
278    }
279    }
280   
281  1751 index = aType.lastIndexOf("._");
282  1754 if (index > 0) {
283  1751 int start = index + 2;
284  1746 int end = aType.indexOf('.', start);
285  1742 protocol = casePreservedType.substring(start, end);
286    }
287  1751 if (protocol.length() > 0) {
288  1752 index = aType.indexOf("_" + protocol.toLowerCase() + ".");
289  1750 int start = index + protocol.length() + 2;
290  1750 int end = aType.length() - (aType.endsWith(".") ? 1 : 0);
291  1753 domain = casePreservedType.substring(start, end);
292  1754 application = casePreservedType.substring(0, index - 1);
293    }
294  1754 index = application.toLowerCase().indexOf("._sub");
295  1755 if (index > 0) {
296  21 int start = index + 5;
297  21 subtype = removeSeparators(application.substring(0, index));
298  21 application = application.substring(start);
299    }
300    }
301   
302  3024 final Map<Fields, String> qualifiedNameMap = new HashMap<Fields, String>(5);
303  3024 qualifiedNameMap.put(Fields.Domain, removeSeparators(domain));
304  3022 qualifiedNameMap.put(Fields.Protocol, protocol);
305  3033 qualifiedNameMap.put(Fields.Application, removeSeparators(application));
306  3041 qualifiedNameMap.put(Fields.Instance, name);
307  3037 qualifiedNameMap.put(Fields.Subtype, subtype);
308   
309  3036 return qualifiedNameMap;
310    }
311   
 
312  518 toggle protected static Map<Fields, String> checkQualifiedNameMap(Map<Fields, String> qualifiedNameMap) {
313  519 Map<Fields, String> checkedQualifiedNameMap = new HashMap<Fields, String>(5);
314   
315    // Optional domain
316  517 String domain = (qualifiedNameMap.containsKey(Fields.Domain) ? qualifiedNameMap.get(Fields.Domain) : "local");
317  516 if ((domain == null) || (domain.length() == 0)) {
318  1 domain = "local";
319    }
320  516 domain = removeSeparators(domain);
321  517 checkedQualifiedNameMap.put(Fields.Domain, domain);
322    // Optional protocol
323  519 String protocol = (qualifiedNameMap.containsKey(Fields.Protocol) ? qualifiedNameMap.get(Fields.Protocol) : "tcp");
324  516 if ((protocol == null) || (protocol.length() == 0)) {
325  115 protocol = "tcp";
326    }
327  520 protocol = removeSeparators(protocol);
328  518 checkedQualifiedNameMap.put(Fields.Protocol, protocol);
329    // Application
330  520 String application = (qualifiedNameMap.containsKey(Fields.Application) ? qualifiedNameMap.get(Fields.Application) : "");
331  519 if ((application == null) || (application.length() == 0)) {
332  124 application = "";
333    }
334  518 application = removeSeparators(application);
335  519 checkedQualifiedNameMap.put(Fields.Application, application);
336    // Instance
337  520 String instance = (qualifiedNameMap.containsKey(Fields.Instance) ? qualifiedNameMap.get(Fields.Instance) : "");
338  520 if ((instance == null) || (instance.length() == 0)) {
339  0 instance = "";
340    // throw new IllegalArgumentException("The instance name component of a fully qualified service cannot be empty.");
341    }
342  520 instance = removeSeparators(instance);
343  520 checkedQualifiedNameMap.put(Fields.Instance, instance);
344    // Optional Subtype
345  519 String subtype = (qualifiedNameMap.containsKey(Fields.Subtype) ? qualifiedNameMap.get(Fields.Subtype) : "");
346  520 if ((subtype == null) || (subtype.length() == 0)) {
347  498 subtype = "";
348    }
349  519 subtype = removeSeparators(subtype);
350  519 checkedQualifiedNameMap.put(Fields.Subtype, subtype);
351   
352  519 return checkedQualifiedNameMap;
353    }
354   
 
355  11139 toggle private static String removeSeparators(String name) {
356  11160 if (name == null) {
357  0 return "";
358    }
359  11168 String newName = name.trim();
360  11181 if (newName.startsWith(".")) {
361  1235 newName = newName.substring(1);
362    }
363  11195 if (newName.startsWith("_")) {
364  1802 newName = newName.substring(1);
365    }
366  11194 if (newName.endsWith(".")) {
367  1350 newName = newName.substring(0, newName.length() - 1);
368    }
369  11179 return newName;
370    }
371   
372    /**
373    * {@inheritDoc}
374    */
 
375  415 toggle @Override
376    public String getType() {
377  415 String domain = this.getDomain();
378  416 String protocol = this.getProtocol();
379  415 String application = this.getApplication();
380  412 return (application.length() > 0 ? "_" + application + "." : "") + (protocol.length() > 0 ? "_" + protocol + "." : "") + domain + ".";
381    }
382   
383    /**
384    * {@inheritDoc}
385    */
 
386  32 toggle @Override
387    public String getTypeWithSubtype() {
388  32 String subtype = this.getSubtype();
389  32 return (subtype.length() > 0 ? "_" + subtype.toLowerCase() + "._sub." : "") + this.getType();
390    }
391   
392    /**
393    * {@inheritDoc}
394    */
 
395  1489 toggle @Override
396    public String getName() {
397  1491 return (_name != null ? _name : "");
398    }
399   
400    /**
401    * {@inheritDoc}
402    */
 
403  110 toggle @Override
404    public String getKey() {
405  110 if (this._key == null) {
406  23 this._key = this.getQualifiedName().toLowerCase();
407    }
408  110 return this._key;
409    }
410   
411    /**
412    * Sets the service instance name.
413    *
414    * @param name
415    * unqualified service instance name, such as <code>foobar</code>
416    */
 
417  0 toggle void setName(String name) {
418  0 this._name = name;
419  0 this._key = null;
420    }
421   
422    /**
423    * {@inheritDoc}
424    */
 
425  1261 toggle @Override
426    public String getQualifiedName() {
427  1262 String domain = this.getDomain();
428  1262 String protocol = this.getProtocol();
429  1262 String application = this.getApplication();
430  1262 String instance = this.getName();
431    // String subtype = this.getSubtype();
432    // return (instance.length() > 0 ? instance + "." : "") + (application.length() > 0 ? "_" + application + "." : "") + (protocol.length() > 0 ? "_" + protocol + (subtype.length() > 0 ? ",_" + subtype.toLowerCase() + "." : ".") : "") + domain
433    // + ".";
434  1261 return (instance.length() > 0 ? instance + "." : "") + (application.length() > 0 ? "_" + application + "." : "") + (protocol.length() > 0 ? "_" + protocol + "." : "") + domain + ".";
435    }
436   
437    /**
438    * @see javax.jmdns.ServiceInfo#getServer()
439    */
 
440  491 toggle @Override
441    public String getServer() {
442  490 return (_server != null ? _server : "");
443    }
444   
445    /**
446    * @param server
447    * the server to set
448    */
 
449  20 toggle void setServer(String server) {
450  20 this._server = server;
451    }
452   
453    /**
454    * {@inheritDoc}
455    */
 
456  0 toggle @Deprecated
457    @Override
458    public String getHostAddress() {
459  0 String[] names = this.getHostAddresses();
460  0 return (names.length > 0 ? names[0] : "");
461    }
462   
463    /**
464    * {@inheritDoc}
465    */
 
466  0 toggle @Override
467    public String[] getHostAddresses() {
468  0 Inet4Address[] ip4Aaddresses = this.getInet4Addresses();
469  0 Inet6Address[] ip6Aaddresses = this.getInet6Addresses();
470  0 String[] names = new String[ip4Aaddresses.length + ip6Aaddresses.length];
471  0 for (int i = 0; i < ip4Aaddresses.length; i++) {
472  0 names[i] = ip4Aaddresses[i].getHostAddress();
473    }
474  0 for (int i = 0; i < ip6Aaddresses.length; i++) {
475  0 names[i + ip4Aaddresses.length] = "[" + ip6Aaddresses[i].getHostAddress() + "]";
476    }
477  0 return names;
478    }
479   
480    /**
481    * @param addr
482    * the addr to add
483    */
 
484  71 toggle void addAddress(Inet4Address addr) {
485  71 _ipv4Addresses.add(addr);
486    }
487   
488    /**
489    * @param addr
490    * the addr to add
491    */
 
492  84 toggle void addAddress(Inet6Address addr) {
493  84 _ipv6Addresses.add(addr);
494    }
495   
496    /**
497    * {@inheritDoc}
498    */
 
499  0 toggle @Deprecated
500    @Override
501    public InetAddress getAddress() {
502  0 return this.getInetAddress();
503    }
504   
505    /**
506    * {@inheritDoc}
507    */
 
508  0 toggle @Deprecated
509    @Override
510    public InetAddress getInetAddress() {
511  0 InetAddress[] addresses = this.getInetAddresses();
512  0 return (addresses.length > 0 ? addresses[0] : null);
513    }
514   
515    /**
516    * {@inheritDoc}
517    */
 
518  0 toggle @Deprecated
519    @Override
520    public Inet4Address getInet4Address() {
521  0 Inet4Address[] addresses = this.getInet4Addresses();
522  0 return (addresses.length > 0 ? addresses[0] : null);
523    }
524   
525    /**
526    * {@inheritDoc}
527    */
 
528  0 toggle @Deprecated
529    @Override
530    public Inet6Address getInet6Address() {
531  0 Inet6Address[] addresses = this.getInet6Addresses();
532  0 return (addresses.length > 0 ? addresses[0] : null);
533    }
534   
535    /*
536    * (non-Javadoc)
537    * @see javax.jmdns.ServiceInfo#getInetAddresses()
538    */
 
539  7 toggle @Override
540    public InetAddress[] getInetAddresses() {
541  7 List<InetAddress> aList = new ArrayList<InetAddress>(_ipv4Addresses.size() + _ipv6Addresses.size());
542  7 aList.addAll(_ipv4Addresses);
543  7 aList.addAll(_ipv6Addresses);
544  7 return aList.toArray(new InetAddress[aList.size()]);
545    }
546   
547    /*
548    * (non-Javadoc)
549    * @see javax.jmdns.ServiceInfo#getInet4Addresses()
550    */
 
551  85 toggle @Override
552    public Inet4Address[] getInet4Addresses() {
553  85 return _ipv4Addresses.toArray(new Inet4Address[_ipv4Addresses.size()]);
554    }
555   
556    /*
557    * (non-Javadoc)
558    * @see javax.jmdns.ServiceInfo#getInet6Addresses()
559    */
 
560  86 toggle @Override
561    public Inet6Address[] getInet6Addresses() {
562  86 return _ipv6Addresses.toArray(new Inet6Address[_ipv6Addresses.size()]);
563    }
564   
565    /**
566    * @see javax.jmdns.ServiceInfo#getPort()
567    */
 
568  207 toggle @Override
569    public int getPort() {
570  207 return _port;
571    }
572   
573    /**
574    * @see javax.jmdns.ServiceInfo#getPriority()
575    */
 
576  87 toggle @Override
577    public int getPriority() {
578  87 return _priority;
579    }
580   
581    /**
582    * @see javax.jmdns.ServiceInfo#getWeight()
583    */
 
584  87 toggle @Override
585    public int getWeight() {
586  87 return _weight;
587    }
588   
589    /**
590    * @see javax.jmdns.ServiceInfo#getTextBytes()
591    */
 
592  1160 toggle @Override
593    public byte[] getTextBytes() {
594  1163 return (this._text != null && this._text.length > 0 ? this._text : DNSRecord.EMPTY_TXT);
595    }
596   
597    /**
598    * {@inheritDoc}
599    */
 
600  0 toggle @Deprecated
601    @Override
602    public String getTextString() {
603  0 Map<String, byte[]> properties = this.getProperties();
604  0 for (String key : properties.keySet()) {
605  0 byte[] value = properties.get(key);
606  0 if ((value != null) && (value.length > 0)) {
607  0 return key + "=" + new String(value);
608    }
609  0 return key;
610    }
611  0 return "";
612    }
613   
614    /*
615    * (non-Javadoc)
616    * @see javax.jmdns.ServiceInfo#getURL()
617    */
 
618  0 toggle @Deprecated
619    @Override
620    public String getURL() {
621  0 return this.getURL("http");
622    }
623   
624    /*
625    * (non-Javadoc)
626    * @see javax.jmdns.ServiceInfo#getURLs()
627    */
 
628  0 toggle @Override
629    public String[] getURLs() {
630  0 return this.getURLs("http");
631    }
632   
633    /*
634    * (non-Javadoc)
635    * @see javax.jmdns.ServiceInfo#getURL(java.lang.String)
636    */
 
637  0 toggle @Deprecated
638    @Override
639    public String getURL(String protocol) {
640  0 String[] urls = this.getURLs(protocol);
641  0 return (urls.length > 0 ? urls[0] : protocol + "://null:" + getPort());
642    }
643   
644    /*
645    * (non-Javadoc)
646    * @see javax.jmdns.ServiceInfo#getURLs(java.lang.String)
647    */
 
648  0 toggle @Override
649    public String[] getURLs(String protocol) {
650  0 InetAddress[] addresses = this.getInetAddresses();
651  0 String[] urls = new String[addresses.length];
652  0 for (int i = 0; i < addresses.length; i++) {
653  0 String url = protocol + "://" + addresses[i].getHostAddress() + ":" + getPort();
654  0 String path = getPropertyString("path");
655  0 if (path != null) {
656  0 if (path.indexOf("://") >= 0) {
657  0 url = path;
658    } else {
659  0 url += path.startsWith("/") ? path : "/" + path;
660    }
661    }
662  0 urls[i] = url;
663    }
664  0 return urls;
665    }
666   
667    /**
668    * {@inheritDoc}
669    */
 
670  0 toggle @Override
671    public synchronized byte[] getPropertyBytes(String name) {
672  0 return this.getProperties().get(name);
673    }
674   
675    /**
676    * {@inheritDoc}
677    */
 
678  16 toggle @Override
679    public synchronized String getPropertyString(String name) {
680  16 byte data[] = this.getProperties().get(name);
681  16 if (data == null) {
682  1 return null;
683    }
684  15 if (data == NO_VALUE) {
685  0 return "true";
686    }
687  15 return readUTF(data, 0, data.length);
688    }
689   
690    /**
691    * {@inheritDoc}
692    */
 
693  0 toggle @Override
694    public Enumeration<String> getPropertyNames() {
695  0 Map<String, byte[]> properties = this.getProperties();
696  0 Collection<String> names = (properties != null ? properties.keySet() : Collections.<String> emptySet());
697  0 return new Vector<String>(names).elements();
698    }
699   
700    /**
701    * {@inheritDoc}
702    */
 
703  1799 toggle @Override
704    public String getApplication() {
705  1806 return (_application != null ? _application : "");
706    }
707   
708    /**
709    * {@inheritDoc}
710    */
 
711  1804 toggle @Override
712    public String getDomain() {
713  1805 return (_domain != null ? _domain : "local");
714    }
715   
716    /**
717    * {@inheritDoc}
718    */
 
719  1798 toggle @Override
720    public String getProtocol() {
721  1803 return (_protocol != null ? _protocol : "tcp");
722    }
723   
724    /**
725    * {@inheritDoc}
726    */
 
727  319 toggle @Override
728    public String getSubtype() {
729  319 return (_subtype != null ? _subtype : "");
730    }
731   
732    /**
733    * {@inheritDoc}
734    */
 
735  113 toggle @Override
736    public Map<Fields, String> getQualifiedNameMap() {
737  113 Map<Fields, String> map = new HashMap<Fields, String>(5);
738   
739  113 map.put(Fields.Domain, this.getDomain());
740  113 map.put(Fields.Protocol, this.getProtocol());
741  113 map.put(Fields.Application, this.getApplication());
742  112 map.put(Fields.Instance, this.getName());
743  112 map.put(Fields.Subtype, this.getSubtype());
744  113 return map;
745    }
746   
747    /**
748    * Write a UTF string with a length to a stream.
749    */
 
750  145 toggle static void writeUTF(OutputStream out, String str) throws IOException {
751  4048 for (int i = 0, len = str.length(); i < len; i++) {
752  3905 int c = str.charAt(i);
753  3899 if ((c >= 0x0001) && (c <= 0x007F)) {
754  3902 out.write(c);
755    } else {
756  0 if (c > 0x07FF) {
757  0 out.write(0xE0 | ((c >> 12) & 0x0F));
758  0 out.write(0x80 | ((c >> 6) & 0x3F));
759  0 out.write(0x80 | ((c >> 0) & 0x3F));
760    } else {
761  0 out.write(0xC0 | ((c >> 6) & 0x1F));
762  0 out.write(0x80 | ((c >> 0) & 0x3F));
763    }
764    }
765    }
766    }
767   
768    /**
769    * Read data bytes as a UTF stream.
770    */
 
771  33 toggle String readUTF(byte data[], int off, int len) {
772  33 int offset = off;
773  33 StringBuffer buf = new StringBuffer();
774  592 for (int end = offset + len; offset < end;) {
775  559 int ch = data[offset++] & 0xFF;
776  559 switch (ch >> 4) {
777  0 case 0:
778  0 case 1:
779  48 case 2:
780  0 case 3:
781  0 case 4:
782  27 case 5:
783  276 case 6:
784  208 case 7:
785    // 0xxxxxxx
786  559 break;
787  0 case 12:
788  0 case 13:
789  0 if (offset >= len) {
790  0 return null;
791    }
792    // 110x xxxx 10xx xxxx
793  0 ch = ((ch & 0x1F) << 6) | (data[offset++] & 0x3F);
794  0 break;
795  0 case 14:
796  0 if (offset + 2 >= len) {
797  0 return null;
798    }
799    // 1110 xxxx 10xx xxxx 10xx xxxx
800  0 ch = ((ch & 0x0f) << 12) | ((data[offset++] & 0x3F) << 6) | (data[offset++] & 0x3F);
801  0 break;
802  0 default:
803  0 if (offset + 1 >= len) {
804  0 return null;
805    }
806    // 10xx xxxx, 1111 xxxx
807  0 ch = ((ch & 0x3F) << 4) | (data[offset++] & 0x0f);
808  0 break;
809    }
810  559 buf.append((char) ch);
811    }
812  33 return buf.toString();
813    }
814   
 
815  23 toggle synchronized Map<String, byte[]> getProperties() {
816  23 if ((_props == null) && (this.getTextBytes() != null)) {
817  19 Hashtable<String, byte[]> properties = new Hashtable<String, byte[]>();
818  19 try {
819  19 int off = 0;
820  37 while (off < getTextBytes().length) {
821    // length of the next key value pair
822  19 int len = getTextBytes()[off++] & 0xFF;
823  19 if ((len == 0) || (off + len > getTextBytes().length)) {
824  1 properties.clear();
825  1 break;
826    }
827    // look for the '='
828  18 int i = 0;
829  144 for (; (i < len) && (getTextBytes()[off + i] != '='); i++) {
830    /* Stub */
831    }
832   
833    // get the property name
834  18 String name = readUTF(getTextBytes(), off, i);
835  18 if (name == null) {
836  0 properties.clear();
837  0 break;
838    }
839  18 if (i == len) {
840  0 properties.put(name, NO_VALUE);
841    } else {
842  18 byte value[] = new byte[len - ++i];
843  18 System.arraycopy(getTextBytes(), off + i, value, 0, len - i);
844  18 properties.put(name, value);
845  18 off += len;
846    }
847    }
848    } catch (Exception exception) {
849    // We should get better logging.
850  0 logger.log(Level.WARNING, "Malformed TXT Field ", exception);
851    }
852  19 this._props = properties;
853    }
854  23 return (_props != null ? _props : Collections.<String, byte[]> emptyMap());
855    }
856   
857    /**
858    * JmDNS callback to update a DNS record.
859    *
860    * @param dnsCache
861    * @param now
862    * @param rec
863    */
 
864  107 toggle @Override
865    public void updateRecord(DNSCache dnsCache, long now, DNSEntry rec) {
866  107 if ((rec instanceof DNSRecord) && !rec.isExpired(now)) {
867  94 boolean serviceUpdated = false;
868  94 switch (rec.getRecordType()) {
869  21 case TYPE_A: // IPv4
870  21 if (rec.getName().equalsIgnoreCase(this.getServer())) {
871  20 _ipv4Addresses.add((Inet4Address) ((DNSRecord.Address) rec).getAddress());
872  20 serviceUpdated = true;
873    }
874  21 break;
875  22 case TYPE_AAAA: // IPv6
876  22 if (rec.getName().equalsIgnoreCase(this.getServer())) {
877  21 _ipv6Addresses.add((Inet6Address) ((DNSRecord.Address) rec).getAddress());
878  21 serviceUpdated = true;
879    }
880  22 break;
881  22 case TYPE_SRV:
882  22 if (rec.getName().equalsIgnoreCase(this.getQualifiedName())) {
883  22 DNSRecord.Service srv = (DNSRecord.Service) rec;
884  22 boolean serverChanged = (_server == null) || !_server.equalsIgnoreCase(srv.getServer());
885  22 _server = srv.getServer();
886  22 _port = srv.getPort();
887  22 _weight = srv.getWeight();
888  22 _priority = srv.getPriority();
889  22 if (serverChanged) {
890  21 _ipv4Addresses.clear();
891  21 _ipv6Addresses.clear();
892  21 for (DNSEntry entry : dnsCache.getDNSEntryList(_server, DNSRecordType.TYPE_A, DNSRecordClass.CLASS_IN)) {
893  20 this.updateRecord(dnsCache, now, entry);
894    }
895  21 for (DNSEntry entry : dnsCache.getDNSEntryList(_server, DNSRecordType.TYPE_AAAA, DNSRecordClass.CLASS_IN)) {
896  21 this.updateRecord(dnsCache, now, entry);
897    }
898    // We do not want to trigger the listener in this case as it will be triggered if the address resolves.
899    } else {
900  1 serviceUpdated = true;
901    }
902    }
903  22 break;
904  26 case TYPE_TXT:
905  26 if (rec.getName().equalsIgnoreCase(this.getQualifiedName())) {
906  26 DNSRecord.Text txt = (DNSRecord.Text) rec;
907  26 _text = txt.getText();
908  26 serviceUpdated = true;
909    }
910  26 break;
911  3 case TYPE_PTR:
912  3 if ((this.getSubtype().length() == 0) && (rec.getSubtype().length() != 0)) {
913  0 _subtype = rec.getSubtype();
914  0 serviceUpdated = true;
915    }
916  3 break;
917  0 default:
918  0 break;
919    }
920  94 if (serviceUpdated && this.hasData()) {
921  68 JmDNSImpl dns = this.getDns();
922  68 if (dns != null) {
923  68 ServiceEvent event = ((DNSRecord) rec).getServiceEvent(dns);
924  68 event = new ServiceEventImpl(dns, event.getType(), event.getName(), this);
925  68 dns.handleServiceResolved(event);
926    }
927    }
928    // This is done, to notify the wait loop in method JmDNS.waitForInfoData(ServiceInfo info, int timeout);
929  94 synchronized (this) {
930  94 this.notifyAll();
931    }
932    }
933    }
934   
935    /**
936    * Returns true if the service info is filled with data.
937    *
938    * @return <code>true</code> if the service info has data, <code>false</code> otherwise.
939    */
 
940  436 toggle @Override
941    public synchronized boolean hasData() {
942  436 return this.getServer() != null && this.hasInetAddress() && this.getTextBytes() != null && this.getTextBytes().length > 0;
943    // return this.getServer() != null && (this.getAddress() != null || (this.getTextBytes() != null && this.getTextBytes().length > 0));
944    }
945   
 
946  435 toggle private final boolean hasInetAddress() {
947  436 return _ipv4Addresses.size() > 0 || _ipv6Addresses.size() > 0;
948    }
949   
950    // State machine
951   
952    /**
953    * {@inheritDoc}
954    */
 
955  168 toggle @Override
956    public boolean advanceState(DNSTask task) {
957  168 return _state.advanceState(task);
958    }
959   
960    /**
961    * {@inheritDoc}
962    */
 
963  0 toggle @Override
964    public boolean revertState() {
965  0 return _state.revertState();
966    }
967   
968    /**
969    * {@inheritDoc}
970    */
 
971  20 toggle @Override
972    public boolean cancelState() {
973  20 return _state.cancelState();
974    }
975   
976    /**
977    * {@inheritDoc}
978    */
 
979  0 toggle @Override
980    public boolean closeState() {
981  0 return this._state.closeState();
982    }
983   
984    /**
985    * {@inheritDoc}
986    */
 
987  20 toggle @Override
988    public boolean recoverState() {
989  20 return this._state.recoverState();
990    }
991   
992    /**
993    * {@inheritDoc}
994    */
 
995  64 toggle @Override
996    public void removeAssociationWithTask(DNSTask task) {
997  64 _state.removeAssociationWithTask(task);
998    }
999   
1000    /**
1001    * {@inheritDoc}
1002    */
 
1003  89 toggle @Override
1004    public void associateWithTask(DNSTask task, DNSState state) {
1005  89 _state.associateWithTask(task, state);
1006    }
1007   
1008    /**
1009    * {@inheritDoc}
1010    */
 
1011  168 toggle @Override
1012    public boolean isAssociatedWithTask(DNSTask task, DNSState state) {
1013  168 return _state.isAssociatedWithTask(task, state);
1014    }
1015   
1016    /**
1017    * {@inheritDoc}
1018    */
 
1019  0 toggle @Override
1020    public boolean isProbing() {
1021  0 return _state.isProbing();
1022    }
1023   
1024    /**
1025    * {@inheritDoc}
1026    */
 
1027  60 toggle @Override
1028    public boolean isAnnouncing() {
1029  60 return _state.isAnnouncing();
1030    }
1031   
1032    /**
1033    * {@inheritDoc}
1034    */
 
1035  130 toggle @Override
1036    public boolean isAnnounced() {
1037  130 return _state.isAnnounced();
1038    }
1039   
1040    /**
1041    * {@inheritDoc}
1042    */
 
1043  0 toggle @Override
1044    public boolean isCanceling() {
1045  0 return this._state.isCanceling();
1046    }
1047   
1048    /**
1049    * {@inheritDoc}
1050    */
 
1051  0 toggle @Override
1052    public boolean isCanceled() {
1053  0 return _state.isCanceled();
1054    }
1055   
1056    /**
1057    * {@inheritDoc}
1058    */
 
1059  0 toggle @Override
1060    public boolean isClosing() {
1061  0 return _state.isClosing();
1062    }
1063   
1064    /**
1065    * {@inheritDoc}
1066    */
 
1067  0 toggle @Override
1068    public boolean isClosed() {
1069  0 return _state.isClosed();
1070    }
1071   
1072    /**
1073    * {@inheritDoc}
1074    */
 
1075  20 toggle @Override
1076    public boolean waitForAnnounced(long timeout) {
1077  20 return _state.waitForAnnounced(timeout);
1078    }
1079   
1080    /**
1081    * {@inheritDoc}
1082    */
 
1083  20 toggle @Override
1084    public boolean waitForCanceled(long timeout) {
1085  20 return _state.waitForCanceled(timeout);
1086    }
1087   
1088    /**
1089    * {@inheritDoc}
1090    */
 
1091  0 toggle @Override
1092    public int hashCode() {
1093  0 return getQualifiedName().hashCode();
1094    }
1095   
1096    /**
1097    * {@inheritDoc}
1098    */
 
1099  121 toggle @Override
1100    public boolean equals(Object obj) {
1101  121 return (obj instanceof ServiceInfoImpl) && getQualifiedName().equals(((ServiceInfoImpl) obj).getQualifiedName());
1102    }
1103   
1104    /**
1105    * {@inheritDoc}
1106    */
 
1107  0 toggle @Override
1108    public String getNiceTextString() {
1109  0 StringBuffer buf = new StringBuffer();
1110  0 for (int i = 0, len = this.getTextBytes().length; i < len; i++) {
1111  0 if (i >= 200) {
1112  0 buf.append("...");
1113  0 break;
1114    }
1115  0 int ch = getTextBytes()[i] & 0xFF;
1116  0 if ((ch < ' ') || (ch > 127)) {
1117  0 buf.append("\\0");
1118  0 buf.append(Integer.toString(ch, 8));
1119    } else {
1120  0 buf.append((char) ch);
1121    }
1122    }
1123  0 return buf.toString();
1124    }
1125   
1126    /*
1127    * (non-Javadoc)
1128    * @see javax.jmdns.ServiceInfo#clone()
1129    */
 
1130  59 toggle @Override
1131    public ServiceInfoImpl clone() {
1132  59 ServiceInfoImpl serviceInfo = new ServiceInfoImpl(this.getQualifiedNameMap(), _port, _weight, _priority, _persistent, _text);
1133  59 Inet6Address[] ipv6Addresses = this.getInet6Addresses();
1134  59 for (Inet6Address address : ipv6Addresses) {
1135  34 serviceInfo._ipv6Addresses.add(address);
1136    }
1137  59 Inet4Address[] ipv4Addresses = this.getInet4Addresses();
1138  59 for (Inet4Address address : ipv4Addresses) {
1139  33 serviceInfo._ipv4Addresses.add(address);
1140    }
1141  59 return serviceInfo;
1142    }
1143   
1144    /**
1145    * {@inheritDoc}
1146    */
 
1147  7 toggle @Override
1148    public String toString() {
1149  7 StringBuilder buf = new StringBuilder();
1150  7 buf.append("[" + this.getClass().getSimpleName() + "@" + System.identityHashCode(this) + " ");
1151  7 buf.append("name: '");
1152  7 buf.append((this.getName().length() > 0 ? this.getName() + "." : "") + this.getTypeWithSubtype());
1153  7 buf.append("' address: '");
1154  7 InetAddress[] addresses = this.getInetAddresses();
1155  7 if (addresses.length > 0) {
1156  7 for (InetAddress address : addresses) {
1157  14 buf.append(address);
1158  14 buf.append(':');
1159  14 buf.append(this.getPort());
1160  14 buf.append(' ');
1161    }
1162    } else {
1163  0 buf.append("(null):");
1164  0 buf.append(this.getPort());
1165    }
1166  7 buf.append("' status: '");
1167  7 buf.append(_state.toString());
1168  7 buf.append(this.isPersistent() ? "' is persistent," : "',");
1169  7 buf.append(" has ");
1170  7 buf.append(this.hasData() ? "" : "NO ");
1171  7 buf.append("data");
1172  7 if (this.getTextBytes().length > 0) {
1173    // buf.append("\n");
1174    // buf.append(this.getNiceTextString());
1175  7 Map<String, byte[]> properties = this.getProperties();
1176  7 if (!properties.isEmpty()) {
1177  7 buf.append("\n");
1178  7 for (String key : properties.keySet()) {
1179  7 buf.append("\t" + key + ": " + new String(properties.get(key)) + "\n");
1180    }
1181    } else {
1182  0 buf.append(" empty");
1183    }
1184    }
1185  7 buf.append(']');
1186  7 return buf.toString();
1187    }
1188   
 
1189  138 toggle public Collection<DNSRecord> answers(boolean unique, int ttl, HostInfo localHost) {
1190  138 List<DNSRecord> list = new ArrayList<DNSRecord>();
1191  138 if (this.getSubtype().length() > 0) {
1192  5 list.add(new Pointer(this.getTypeWithSubtype(), DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE, ttl, this.getQualifiedName()));
1193    }
1194  138 list.add(new Pointer(this.getType(), DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE, ttl, this.getQualifiedName()));
1195  138 list.add(new Service(this.getQualifiedName(), DNSRecordClass.CLASS_IN, unique, ttl, _priority, _weight, _port, localHost.getName()));
1196  138 list.add(new Text(this.getQualifiedName(), DNSRecordClass.CLASS_IN, unique, ttl, this.getTextBytes()));
1197  138 return list;
1198    }
1199   
1200    /**
1201    * {@inheritDoc}
1202    */
 
1203  4 toggle @Override
1204    public void setText(byte[] text) throws IllegalStateException {
1205  4 synchronized (this) {
1206  4 this._text = text;
1207  4 this._props = null;
1208  4 this.setNeedTextAnnouncing(true);
1209    }
1210    }
1211   
1212    /**
1213    * {@inheritDoc}
1214    */
 
1215  4 toggle @Override
1216    public void setText(Map<String, ?> props) throws IllegalStateException {
1217  4 this.setText(textFromProperties(props));
1218    }
1219   
1220    /**
1221    * This is used internally by the framework
1222    *
1223    * @param text
1224    */
 
1225  38 toggle void _setText(byte[] text) {
1226  38 this._text = text;
1227  38 this._props = null;
1228    }
1229   
 
1230  34 toggle private static byte[] textFromProperties(Map<String, ?> props) {
1231  34 byte[] text = null;
1232  34 if (props != null) {
1233  34 try {
1234  34 ByteArrayOutputStream out = new ByteArrayOutputStream(256);
1235  34 for (String key : props.keySet()) {
1236  33 Object val = props.get(key);
1237  33 ByteArrayOutputStream out2 = new ByteArrayOutputStream(100);
1238  33 writeUTF(out2, key);
1239  33 if (val == null) {
1240    // Skip
1241  33 } else if (val instanceof String) {
1242  0 out2.write('=');
1243  0 writeUTF(out2, (String) val);
1244  33 } else if (val instanceof byte[]) {
1245  33 byte[] bval = (byte[]) val;
1246  33 if (bval.length > 0) {
1247  33 out2.write('=');
1248  33 out2.write(bval, 0, bval.length);
1249    } else {
1250  0 val = null;
1251    }
1252    } else {
1253  0 throw new IllegalArgumentException("invalid property value: " + val);
1254    }
1255  33 byte data[] = out2.toByteArray();
1256  33 if (data.length > 255) {
1257  0 throw new IOException("Cannot have individual values larger that 255 chars. Offending value: " + key + (val != null ? "" : "=" + val));
1258    }
1259  33 out.write((byte) data.length);
1260  33 out.write(data, 0, data.length);
1261    }
1262  34 text = out.toByteArray();
1263    } catch (IOException e) {
1264  0 throw new RuntimeException("unexpected exception: " + e);
1265    }
1266    }
1267  34 return (text != null && text.length > 0 ? text : DNSRecord.EMPTY_TXT);
1268    }
1269   
 
1270  149 toggle public void setDns(JmDNSImpl dns) {
1271  150 this._state.setDns(dns);
1272    }
1273   
1274    /**
1275    * {@inheritDoc}
1276    */
 
1277  91 toggle @Override
1278    public JmDNSImpl getDns() {
1279  91 return this._state.getDns();
1280    }
1281   
1282    /**
1283    * {@inheritDoc}
1284    */
 
1285  39 toggle @Override
1286    public boolean isPersistent() {
1287  39 return _persistent;
1288    }
1289   
1290    /**
1291    * @param needTextAnnouncing
1292    * the needTextAnnouncing to set
1293    */
 
1294  434 toggle public void setNeedTextAnnouncing(boolean needTextAnnouncing) {
1295  434 this._needTextAnnouncing = needTextAnnouncing;
1296  433 if (this._needTextAnnouncing) {
1297  4 _state.setTask(null);
1298    }
1299    }
1300   
1301    /**
1302    * @return the needTextAnnouncing
1303    */
 
1304  111 toggle public boolean needTextAnnouncing() {
1305  111 return _needTextAnnouncing;
1306    }
1307   
1308    /**
1309    * @return the delegate
1310    */
 
1311  0 toggle Delegate getDelegate() {
1312  0 return this._delegate;
1313    }
1314   
1315    /**
1316    * @param delegate
1317    * the delegate to set
1318    */
 
1319  0 toggle void setDelegate(Delegate delegate) {
1320  0 this._delegate = delegate;
1321    }
1322   
1323    }