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