// Copyright 2003-2005 Arthur van Hoff, Rick Blair // Licensed under Apache License version 2.0 // Original license LGPL package javax.jmdns.impl; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.Hashtable; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; import javax.jmdns.ServiceEvent; import javax.jmdns.ServiceInfo; import javax.jmdns.impl.DNSRecord.Pointer; import javax.jmdns.impl.DNSRecord.Service; import javax.jmdns.impl.DNSRecord.Text; import javax.jmdns.impl.constants.DNSRecordClass; import javax.jmdns.impl.constants.DNSRecordType; import javax.jmdns.impl.constants.DNSState; import javax.jmdns.impl.tasks.DNSTask; /** * JmDNS service information. * * @author Arthur van Hoff, Jeff Sonstein, Werner Randelshofer */ public class ServiceInfoImpl extends ServiceInfo implements DNSListener, DNSStatefulObject { private static Logger logger = Logger.getLogger(ServiceInfoImpl.class.getName()); private String _domain; private String _protocol; private String _application; private String _name; private String _subtype; private String _server; private int _port; private int _weight; private int _priority; private byte _text[]; private Map _props; private final Set _ipv4Addresses; private final Set _ipv6Addresses; private transient String _key; private boolean _persistent; private boolean _needTextAnnouncing; private final ServiceInfoState _state; private Delegate _delegate; public static interface Delegate { public void textValueUpdated(ServiceInfo target, byte[] value); } private final static class ServiceInfoState extends DNSStatefulObject.DefaultImplementation { private static final long serialVersionUID = 1104131034952196820L; private final ServiceInfoImpl _info; /** * @param info */ public ServiceInfoState(ServiceInfoImpl info) { super(); _info = info; } @Override protected void setTask(DNSTask task) { super.setTask(task); if ((this._task == null) && _info.needTextAnnouncing()) { this.lock(); try { if ((this._task == null) && _info.needTextAnnouncing()) { if (this._state.isAnnounced()) { this.setState(DNSState.ANNOUNCING_1); if (this.getDns() != null) { this.getDns().startAnnouncer(); } } _info.setNeedTextAnnouncing(false); } } finally { this.unlock(); } } } @Override public void setDns(JmDNSImpl dns) { super.setDns(dns); } } /** * @param type * @param name * @param subtype * @param port * @param weight * @param priority * @param persistent * @param text * @see javax.jmdns.ServiceInfo#create(String, String, int, int, int, String) */ public ServiceInfoImpl(String type, String name, String subtype, int port, int weight, int priority, boolean persistent, String text) { this(ServiceInfoImpl.decodeQualifiedNameMap(type, name, subtype), port, weight, priority, persistent, (byte[]) null); _server = text; try { ByteArrayOutputStream out = new ByteArrayOutputStream(text.length()); writeUTF(out, text); this._text = out.toByteArray(); } catch (IOException e) { throw new RuntimeException("unexpected exception: " + e); } } /** * @param type * @param name * @param subtype * @param port * @param weight * @param priority * @param persistent * @param props * @see javax.jmdns.ServiceInfo#create(String, String, int, int, int, Map) */ public ServiceInfoImpl(String type, String name, String subtype, int port, int weight, int priority, boolean persistent, Map props) { this(ServiceInfoImpl.decodeQualifiedNameMap(type, name, subtype), port, weight, priority, persistent, textFromProperties(props)); } /** * @param type * @param name * @param subtype * @param port * @param weight * @param priority * @param persistent * @param text * @see javax.jmdns.ServiceInfo#create(String, String, int, int, int, byte[]) */ public ServiceInfoImpl(String type, String name, String subtype, int port, int weight, int priority, boolean persistent, byte text[]) { this(ServiceInfoImpl.decodeQualifiedNameMap(type, name, subtype), port, weight, priority, persistent, text); } public ServiceInfoImpl(Map qualifiedNameMap, int port, int weight, int priority, boolean persistent, Map props) { this(qualifiedNameMap, port, weight, priority, persistent, textFromProperties(props)); } ServiceInfoImpl(Map qualifiedNameMap, int port, int weight, int priority, boolean persistent, String text) { this(qualifiedNameMap, port, weight, priority, persistent, (byte[]) null); _server = text; try { ByteArrayOutputStream out = new ByteArrayOutputStream(text.length()); writeUTF(out, text); this._text = out.toByteArray(); } catch (IOException e) { throw new RuntimeException("unexpected exception: " + e); } } ServiceInfoImpl(Map qualifiedNameMap, int port, int weight, int priority, boolean persistent, byte text[]) { Map map = ServiceInfoImpl.checkQualifiedNameMap(qualifiedNameMap); this._domain = map.get(Fields.Domain); this._protocol = map.get(Fields.Protocol); this._application = map.get(Fields.Application); this._name = map.get(Fields.Instance); this._subtype = map.get(Fields.Subtype); this._port = port; this._weight = weight; this._priority = priority; this._text = text; this.setNeedTextAnnouncing(false); this._state = new ServiceInfoState(this); this._persistent = persistent; this._ipv4Addresses = Collections.synchronizedSet(new LinkedHashSet()); this._ipv6Addresses = Collections.synchronizedSet(new LinkedHashSet()); } /** * During recovery we need to duplicate service info to reregister them * * @param info */ ServiceInfoImpl(ServiceInfo info) { this._ipv4Addresses = Collections.synchronizedSet(new LinkedHashSet()); this._ipv6Addresses = Collections.synchronizedSet(new LinkedHashSet()); if (info != null) { this._domain = info.getDomain(); this._protocol = info.getProtocol(); this._application = info.getApplication(); this._name = info.getName(); this._subtype = info.getSubtype(); this._port = info.getPort(); this._weight = info.getWeight(); this._priority = info.getPriority(); this._text = info.getTextBytes(); this._persistent = info.isPersistent(); Inet6Address[] ipv6Addresses = info.getInet6Addresses(); for (Inet6Address address : ipv6Addresses) { this._ipv6Addresses.add(address); } Inet4Address[] ipv4Addresses = info.getInet4Addresses(); for (Inet4Address address : ipv4Addresses) { this._ipv4Addresses.add(address); } } this._state = new ServiceInfoState(this); } public static Map decodeQualifiedNameMap(String type, String name, String subtype) { Map qualifiedNameMap = decodeQualifiedNameMapForType(type); qualifiedNameMap.put(Fields.Instance, name); qualifiedNameMap.put(Fields.Subtype, subtype); return checkQualifiedNameMap(qualifiedNameMap); } public static Map decodeQualifiedNameMapForType(String type) { int index; String casePreservedType = type; String aType = type.toLowerCase(); String application = aType; String protocol = ""; String subtype = ""; String name = ""; String domain = ""; if (aType.contains("in-addr.arpa") || aType.contains("ip6.arpa")) { index = (aType.contains("in-addr.arpa") ? aType.indexOf("in-addr.arpa") : aType.indexOf("ip6.arpa")); name = removeSeparators(casePreservedType.substring(0, index)); domain = casePreservedType.substring(index); application = ""; } else if ((!aType.contains("_")) && aType.contains(".")) { index = aType.indexOf('.'); name = removeSeparators(casePreservedType.substring(0, index)); domain = removeSeparators(casePreservedType.substring(index)); application = ""; } else { // First remove the name if it there. if (!aType.startsWith("_") || aType.startsWith("_services")) { index = aType.indexOf('.'); if (index > 0) { // We need to preserve the case for the user readable name. name = casePreservedType.substring(0, index); if (index + 1 < aType.length()) { aType = aType.substring(index + 1); casePreservedType = casePreservedType.substring(index + 1); } } } index = aType.lastIndexOf("._"); if (index > 0) { int start = index + 2; int end = aType.indexOf('.', start); protocol = casePreservedType.substring(start, end); } if (protocol.length() > 0) { index = aType.indexOf("_" + protocol.toLowerCase() + "."); int start = index + protocol.length() + 2; int end = aType.length() - (aType.endsWith(".") ? 1 : 0); domain = casePreservedType.substring(start, end); application = casePreservedType.substring(0, index - 1); } index = application.toLowerCase().indexOf("._sub"); if (index > 0) { int start = index + 5; subtype = removeSeparators(application.substring(0, index)); application = application.substring(start); } } final Map qualifiedNameMap = new HashMap(5); qualifiedNameMap.put(Fields.Domain, removeSeparators(domain)); qualifiedNameMap.put(Fields.Protocol, protocol); qualifiedNameMap.put(Fields.Application, removeSeparators(application)); qualifiedNameMap.put(Fields.Instance, name); qualifiedNameMap.put(Fields.Subtype, subtype); return qualifiedNameMap; } protected static Map checkQualifiedNameMap(Map qualifiedNameMap) { Map checkedQualifiedNameMap = new HashMap(5); // Optional domain String domain = (qualifiedNameMap.containsKey(Fields.Domain) ? qualifiedNameMap.get(Fields.Domain) : "local"); if ((domain == null) || (domain.length() == 0)) { domain = "local"; } domain = removeSeparators(domain); checkedQualifiedNameMap.put(Fields.Domain, domain); // Optional protocol String protocol = (qualifiedNameMap.containsKey(Fields.Protocol) ? qualifiedNameMap.get(Fields.Protocol) : "tcp"); if ((protocol == null) || (protocol.length() == 0)) { protocol = "tcp"; } protocol = removeSeparators(protocol); checkedQualifiedNameMap.put(Fields.Protocol, protocol); // Application String application = (qualifiedNameMap.containsKey(Fields.Application) ? qualifiedNameMap.get(Fields.Application) : ""); if ((application == null) || (application.length() == 0)) { application = ""; } application = removeSeparators(application); checkedQualifiedNameMap.put(Fields.Application, application); // Instance String instance = (qualifiedNameMap.containsKey(Fields.Instance) ? qualifiedNameMap.get(Fields.Instance) : ""); if ((instance == null) || (instance.length() == 0)) { instance = ""; // throw new IllegalArgumentException("The instance name component of a fully qualified service cannot be empty."); } instance = removeSeparators(instance); checkedQualifiedNameMap.put(Fields.Instance, instance); // Optional Subtype String subtype = (qualifiedNameMap.containsKey(Fields.Subtype) ? qualifiedNameMap.get(Fields.Subtype) : ""); if ((subtype == null) || (subtype.length() == 0)) { subtype = ""; } subtype = removeSeparators(subtype); checkedQualifiedNameMap.put(Fields.Subtype, subtype); return checkedQualifiedNameMap; } private static String removeSeparators(String name) { if (name == null) { return ""; } String newName = name.trim(); if (newName.startsWith(".")) { newName = newName.substring(1); } if (newName.startsWith("_")) { newName = newName.substring(1); } if (newName.endsWith(".")) { newName = newName.substring(0, newName.length() - 1); } return newName; } /** * {@inheritDoc} */ @Override public String getType() { String domain = this.getDomain(); String protocol = this.getProtocol(); String application = this.getApplication(); return (application.length() > 0 ? "_" + application + "." : "") + (protocol.length() > 0 ? "_" + protocol + "." : "") + domain + "."; } /** * {@inheritDoc} */ @Override public String getTypeWithSubtype() { String subtype = this.getSubtype(); return (subtype.length() > 0 ? "_" + subtype.toLowerCase() + "._sub." : "") + this.getType(); } /** * {@inheritDoc} */ @Override public String getName() { return (_name != null ? _name : ""); } /** * {@inheritDoc} */ @Override public String getKey() { if (this._key == null) { this._key = this.getQualifiedName().toLowerCase(); } return this._key; } /** * Sets the service instance name. * * @param name * unqualified service instance name, such as foobar */ void setName(String name) { this._name = name; this._key = null; } /** * {@inheritDoc} */ @Override public String getQualifiedName() { String domain = this.getDomain(); String protocol = this.getProtocol(); String application = this.getApplication(); String instance = this.getName(); // String subtype = this.getSubtype(); // return (instance.length() > 0 ? instance + "." : "") + (application.length() > 0 ? "_" + application + "." : "") + (protocol.length() > 0 ? "_" + protocol + (subtype.length() > 0 ? ",_" + subtype.toLowerCase() + "." : ".") : "") + domain // + "."; return (instance.length() > 0 ? instance + "." : "") + (application.length() > 0 ? "_" + application + "." : "") + (protocol.length() > 0 ? "_" + protocol + "." : "") + domain + "."; } /** * @see javax.jmdns.ServiceInfo#getServer() */ @Override public String getServer() { return (_server != null ? _server : ""); } /** * @param server * the server to set */ void setServer(String server) { this._server = server; } /** * {@inheritDoc} */ @Deprecated @Override public String getHostAddress() { String[] names = this.getHostAddresses(); return (names.length > 0 ? names[0] : ""); } /** * {@inheritDoc} */ @Override public String[] getHostAddresses() { Inet4Address[] ip4Aaddresses = this.getInet4Addresses(); Inet6Address[] ip6Aaddresses = this.getInet6Addresses(); String[] names = new String[ip4Aaddresses.length + ip6Aaddresses.length]; for (int i = 0; i < ip4Aaddresses.length; i++) { names[i] = ip4Aaddresses[i].getHostAddress(); } for (int i = 0; i < ip6Aaddresses.length; i++) { names[i + ip4Aaddresses.length] = "[" + ip6Aaddresses[i].getHostAddress() + "]"; } return names; } /** * @param addr * the addr to add */ void addAddress(Inet4Address addr) { _ipv4Addresses.add(addr); } /** * @param addr * the addr to add */ void addAddress(Inet6Address addr) { _ipv6Addresses.add(addr); } /** * {@inheritDoc} */ @Deprecated @Override public InetAddress getAddress() { return this.getInetAddress(); } /** * {@inheritDoc} */ @Deprecated @Override public InetAddress getInetAddress() { InetAddress[] addresses = this.getInetAddresses(); return (addresses.length > 0 ? addresses[0] : null); } /** * {@inheritDoc} */ @Deprecated @Override public Inet4Address getInet4Address() { Inet4Address[] addresses = this.getInet4Addresses(); return (addresses.length > 0 ? addresses[0] : null); } /** * {@inheritDoc} */ @Deprecated @Override public Inet6Address getInet6Address() { Inet6Address[] addresses = this.getInet6Addresses(); return (addresses.length > 0 ? addresses[0] : null); } /* * (non-Javadoc) * @see javax.jmdns.ServiceInfo#getInetAddresses() */ @Override public InetAddress[] getInetAddresses() { List aList = new ArrayList(_ipv4Addresses.size() + _ipv6Addresses.size()); aList.addAll(_ipv4Addresses); aList.addAll(_ipv6Addresses); return aList.toArray(new InetAddress[aList.size()]); } /* * (non-Javadoc) * @see javax.jmdns.ServiceInfo#getInet4Addresses() */ @Override public Inet4Address[] getInet4Addresses() { return _ipv4Addresses.toArray(new Inet4Address[_ipv4Addresses.size()]); } /* * (non-Javadoc) * @see javax.jmdns.ServiceInfo#getInet6Addresses() */ @Override public Inet6Address[] getInet6Addresses() { return _ipv6Addresses.toArray(new Inet6Address[_ipv6Addresses.size()]); } /** * @see javax.jmdns.ServiceInfo#getPort() */ @Override public int getPort() { return _port; } /** * @see javax.jmdns.ServiceInfo#getPriority() */ @Override public int getPriority() { return _priority; } /** * @see javax.jmdns.ServiceInfo#getWeight() */ @Override public int getWeight() { return _weight; } /** * @see javax.jmdns.ServiceInfo#getTextBytes() */ @Override public byte[] getTextBytes() { return (this._text != null && this._text.length > 0 ? this._text : DNSRecord.EMPTY_TXT); } /** * {@inheritDoc} */ @Deprecated @Override public String getTextString() { Map properties = this.getProperties(); for (String key : properties.keySet()) { byte[] value = properties.get(key); if ((value != null) && (value.length > 0)) { return key + "=" + new String(value); } return key; } return ""; } /* * (non-Javadoc) * @see javax.jmdns.ServiceInfo#getURL() */ @Deprecated @Override public String getURL() { return this.getURL("http"); } /* * (non-Javadoc) * @see javax.jmdns.ServiceInfo#getURLs() */ @Override public String[] getURLs() { return this.getURLs("http"); } /* * (non-Javadoc) * @see javax.jmdns.ServiceInfo#getURL(java.lang.String) */ @Deprecated @Override public String getURL(String protocol) { String[] urls = this.getURLs(protocol); return (urls.length > 0 ? urls[0] : protocol + "://null:" + getPort()); } /* * (non-Javadoc) * @see javax.jmdns.ServiceInfo#getURLs(java.lang.String) */ @Override public String[] getURLs(String protocol) { InetAddress[] addresses = this.getInetAddresses(); String[] urls = new String[addresses.length]; for (int i = 0; i < addresses.length; i++) { String url = protocol + "://" + addresses[i].getHostAddress() + ":" + getPort(); String path = getPropertyString("path"); if (path != null) { if (path.indexOf("://") >= 0) { url = path; } else { url += path.startsWith("/") ? path : "/" + path; } } urls[i] = url; } return urls; } /** * {@inheritDoc} */ @Override public synchronized byte[] getPropertyBytes(String name) { return this.getProperties().get(name); } /** * {@inheritDoc} */ @Override public synchronized String getPropertyString(String name) { byte data[] = this.getProperties().get(name); if (data == null) { return null; } if (data == NO_VALUE) { return "true"; } return readUTF(data, 0, data.length); } /** * {@inheritDoc} */ @Override public Enumeration getPropertyNames() { Map properties = this.getProperties(); Collection names = (properties != null ? properties.keySet() : Collections. emptySet()); return new Vector(names).elements(); } /** * {@inheritDoc} */ @Override public String getApplication() { return (_application != null ? _application : ""); } /** * {@inheritDoc} */ @Override public String getDomain() { return (_domain != null ? _domain : "local"); } /** * {@inheritDoc} */ @Override public String getProtocol() { return (_protocol != null ? _protocol : "tcp"); } /** * {@inheritDoc} */ @Override public String getSubtype() { return (_subtype != null ? _subtype : ""); } /** * {@inheritDoc} */ @Override public Map getQualifiedNameMap() { Map map = new HashMap(5); map.put(Fields.Domain, this.getDomain()); map.put(Fields.Protocol, this.getProtocol()); map.put(Fields.Application, this.getApplication()); map.put(Fields.Instance, this.getName()); map.put(Fields.Subtype, this.getSubtype()); return map; } /** * Write a UTF string with a length to a stream. */ static void writeUTF(OutputStream out, String str) throws IOException { for (int i = 0, len = str.length(); i < len; i++) { int c = str.charAt(i); if ((c >= 0x0001) && (c <= 0x007F)) { out.write(c); } else { if (c > 0x07FF) { out.write(0xE0 | ((c >> 12) & 0x0F)); out.write(0x80 | ((c >> 6) & 0x3F)); out.write(0x80 | ((c >> 0) & 0x3F)); } else { out.write(0xC0 | ((c >> 6) & 0x1F)); out.write(0x80 | ((c >> 0) & 0x3F)); } } } } /** * Read data bytes as a UTF stream. */ String readUTF(byte data[], int off, int len) { int offset = off; StringBuffer buf = new StringBuffer(); for (int end = offset + len; offset < end;) { int ch = data[offset++] & 0xFF; switch (ch >> 4) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: // 0xxxxxxx break; case 12: case 13: if (offset >= len) { return null; } // 110x xxxx 10xx xxxx ch = ((ch & 0x1F) << 6) | (data[offset++] & 0x3F); break; case 14: if (offset + 2 >= len) { return null; } // 1110 xxxx 10xx xxxx 10xx xxxx ch = ((ch & 0x0f) << 12) | ((data[offset++] & 0x3F) << 6) | (data[offset++] & 0x3F); break; default: if (offset + 1 >= len) { return null; } // 10xx xxxx, 1111 xxxx ch = ((ch & 0x3F) << 4) | (data[offset++] & 0x0f); break; } buf.append((char) ch); } return buf.toString(); } synchronized Map getProperties() { if ((_props == null) && (this.getTextBytes() != null)) { Hashtable properties = new Hashtable(); try { int off = 0; while (off < getTextBytes().length) { // length of the next key value pair int len = getTextBytes()[off++] & 0xFF; if ((len == 0) || (off + len > getTextBytes().length)) { properties.clear(); break; } // look for the '=' int i = 0; for (; (i < len) && (getTextBytes()[off + i] != '='); i++) { /* Stub */ } // get the property name String name = readUTF(getTextBytes(), off, i); if (name == null) { properties.clear(); break; } if (i == len) { properties.put(name, NO_VALUE); } else { byte value[] = new byte[len - ++i]; System.arraycopy(getTextBytes(), off + i, value, 0, len - i); properties.put(name, value); off += len; } } } catch (Exception exception) { // We should get better logging. logger.log(Level.WARNING, "Malformed TXT Field ", exception); } this._props = properties; } return (_props != null ? _props : Collections. emptyMap()); } /** * JmDNS callback to update a DNS record. * * @param dnsCache * @param now * @param rec */ @Override public void updateRecord(DNSCache dnsCache, long now, DNSEntry rec) { if ((rec instanceof DNSRecord) && !rec.isExpired(now)) { boolean serviceUpdated = false; switch (rec.getRecordType()) { case TYPE_A: // IPv4 if (rec.getName().equalsIgnoreCase(this.getServer())) { _ipv4Addresses.add((Inet4Address) ((DNSRecord.Address) rec).getAddress()); serviceUpdated = true; } break; case TYPE_AAAA: // IPv6 if (rec.getName().equalsIgnoreCase(this.getServer())) { _ipv6Addresses.add((Inet6Address) ((DNSRecord.Address) rec).getAddress()); serviceUpdated = true; } break; case TYPE_SRV: if (rec.getName().equalsIgnoreCase(this.getQualifiedName())) { DNSRecord.Service srv = (DNSRecord.Service) rec; boolean serverChanged = (_server == null) || !_server.equalsIgnoreCase(srv.getServer()); _server = srv.getServer(); _port = srv.getPort(); _weight = srv.getWeight(); _priority = srv.getPriority(); if (serverChanged) { _ipv4Addresses.clear(); _ipv6Addresses.clear(); for (DNSEntry entry : dnsCache.getDNSEntryList(_server, DNSRecordType.TYPE_A, DNSRecordClass.CLASS_IN)) { this.updateRecord(dnsCache, now, entry); } for (DNSEntry entry : dnsCache.getDNSEntryList(_server, DNSRecordType.TYPE_AAAA, DNSRecordClass.CLASS_IN)) { this.updateRecord(dnsCache, now, entry); } // We do not want to trigger the listener in this case as it will be triggered if the address resolves. } else { serviceUpdated = true; } } break; case TYPE_TXT: if (rec.getName().equalsIgnoreCase(this.getQualifiedName())) { DNSRecord.Text txt = (DNSRecord.Text) rec; _text = txt.getText(); _props = null; // set it null for apply update text data serviceUpdated = true; } break; case TYPE_PTR: if ((this.getSubtype().length() == 0) && (rec.getSubtype().length() != 0)) { _subtype = rec.getSubtype(); serviceUpdated = true; } break; default: break; } if (serviceUpdated && this.hasData()) { JmDNSImpl dns = this.getDns(); if (dns != null) { ServiceEvent event = ((DNSRecord) rec).getServiceEvent(dns); event = new ServiceEventImpl(dns, event.getType(), event.getName(), this); dns.handleServiceResolved(event); } } // This is done, to notify the wait loop in method JmDNS.waitForInfoData(ServiceInfo info, int timeout); synchronized (this) { this.notifyAll(); } } } /** * Returns true if the service info is filled with data. * * @return true if the service info has data, false otherwise. */ @Override public synchronized boolean hasData() { return this.getServer() != null && this.hasInetAddress() && this.getTextBytes() != null && this.getTextBytes().length > 0; // return this.getServer() != null && (this.getAddress() != null || (this.getTextBytes() != null && this.getTextBytes().length > 0)); } private final boolean hasInetAddress() { return _ipv4Addresses.size() > 0 || _ipv6Addresses.size() > 0; } // State machine /** * {@inheritDoc} */ @Override public boolean advanceState(DNSTask task) { return _state.advanceState(task); } /** * {@inheritDoc} */ @Override public boolean revertState() { return _state.revertState(); } /** * {@inheritDoc} */ @Override public boolean cancelState() { return _state.cancelState(); } /** * {@inheritDoc} */ @Override public boolean closeState() { return this._state.closeState(); } /** * {@inheritDoc} */ @Override public boolean recoverState() { return this._state.recoverState(); } /** * {@inheritDoc} */ @Override public void removeAssociationWithTask(DNSTask task) { _state.removeAssociationWithTask(task); } /** * {@inheritDoc} */ @Override public void associateWithTask(DNSTask task, DNSState state) { _state.associateWithTask(task, state); } /** * {@inheritDoc} */ @Override public boolean isAssociatedWithTask(DNSTask task, DNSState state) { return _state.isAssociatedWithTask(task, state); } /** * {@inheritDoc} */ @Override public boolean isProbing() { return _state.isProbing(); } /** * {@inheritDoc} */ @Override public boolean isAnnouncing() { return _state.isAnnouncing(); } /** * {@inheritDoc} */ @Override public boolean isAnnounced() { return _state.isAnnounced(); } /** * {@inheritDoc} */ @Override public boolean isCanceling() { return this._state.isCanceling(); } /** * {@inheritDoc} */ @Override public boolean isCanceled() { return _state.isCanceled(); } /** * {@inheritDoc} */ @Override public boolean isClosing() { return _state.isClosing(); } /** * {@inheritDoc} */ @Override public boolean isClosed() { return _state.isClosed(); } /** * {@inheritDoc} */ @Override public boolean waitForAnnounced(long timeout) { return _state.waitForAnnounced(timeout); } /** * {@inheritDoc} */ @Override public boolean waitForCanceled(long timeout) { return _state.waitForCanceled(timeout); } /** * {@inheritDoc} */ @Override public int hashCode() { return getQualifiedName().hashCode(); } /** * {@inheritDoc} */ @Override public boolean equals(Object obj) { return (obj instanceof ServiceInfoImpl) && getQualifiedName().equals(((ServiceInfoImpl) obj).getQualifiedName()); } /** * {@inheritDoc} */ @Override public String getNiceTextString() { StringBuffer buf = new StringBuffer(); for (int i = 0, len = this.getTextBytes().length; i < len; i++) { if (i >= 200) { buf.append("..."); break; } int ch = getTextBytes()[i] & 0xFF; if ((ch < ' ') || (ch > 127)) { buf.append("\\0"); buf.append(Integer.toString(ch, 8)); } else { buf.append((char) ch); } } return buf.toString(); } /* * (non-Javadoc) * @see javax.jmdns.ServiceInfo#clone() */ @Override public ServiceInfoImpl clone() { ServiceInfoImpl serviceInfo = new ServiceInfoImpl(this.getQualifiedNameMap(), _port, _weight, _priority, _persistent, _text); Inet6Address[] ipv6Addresses = this.getInet6Addresses(); for (Inet6Address address : ipv6Addresses) { serviceInfo._ipv6Addresses.add(address); } Inet4Address[] ipv4Addresses = this.getInet4Addresses(); for (Inet4Address address : ipv4Addresses) { serviceInfo._ipv4Addresses.add(address); } return serviceInfo; } /** * {@inheritDoc} */ @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append("[" + this.getClass().getSimpleName() + "@" + System.identityHashCode(this) + " "); buf.append("name: '"); buf.append((this.getName().length() > 0 ? this.getName() + "." : "") + this.getTypeWithSubtype()); buf.append("' address: '"); InetAddress[] addresses = this.getInetAddresses(); if (addresses.length > 0) { for (InetAddress address : addresses) { buf.append(address); buf.append(':'); buf.append(this.getPort()); buf.append(' '); } } else { buf.append("(null):"); buf.append(this.getPort()); } buf.append("' status: '"); buf.append(_state.toString()); buf.append(this.isPersistent() ? "' is persistent," : "',"); buf.append(" has "); buf.append(this.hasData() ? "" : "NO "); buf.append("data"); if (this.getTextBytes().length > 0) { // buf.append("\n"); // buf.append(this.getNiceTextString()); Map properties = this.getProperties(); if (!properties.isEmpty()) { buf.append("\n"); for (String key : properties.keySet()) { buf.append("\t" + key + ": " + new String(properties.get(key)) + "\n"); } } else { buf.append(" empty"); } } buf.append(']'); return buf.toString(); } public Collection answers(boolean unique, int ttl, HostInfo localHost) { List list = new ArrayList(); if (this.getSubtype().length() > 0) { list.add(new Pointer(this.getTypeWithSubtype(), DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE, ttl, this.getQualifiedName())); } list.add(new Pointer(this.getType(), DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE, ttl, this.getQualifiedName())); list.add(new Service(this.getQualifiedName(), DNSRecordClass.CLASS_IN, unique, ttl, _priority, _weight, _port, localHost.getName())); list.add(new Text(this.getQualifiedName(), DNSRecordClass.CLASS_IN, unique, ttl, this.getTextBytes())); return list; } /** * {@inheritDoc} */ @Override public void setText(byte[] text) throws IllegalStateException { synchronized (this) { this._text = text; this._props = null; this.setNeedTextAnnouncing(true); } } /** * {@inheritDoc} */ @Override public void setText(Map props) throws IllegalStateException { this.setText(textFromProperties(props)); } /** * This is used internally by the framework * * @param text */ void _setText(byte[] text) { this._text = text; this._props = null; } private static byte[] textFromProperties(Map props) { byte[] text = null; if (props != null) { try { ByteArrayOutputStream out = new ByteArrayOutputStream(256); for (String key : props.keySet()) { Object val = props.get(key); ByteArrayOutputStream out2 = new ByteArrayOutputStream(100); writeUTF(out2, key); if (val == null) { // Skip } else if (val instanceof String) { out2.write('='); writeUTF(out2, (String) val); } else if (val instanceof byte[]) { byte[] bval = (byte[]) val; if (bval.length > 0) { out2.write('='); out2.write(bval, 0, bval.length); } else { val = null; } } else { throw new IllegalArgumentException("invalid property value: " + val); } byte data[] = out2.toByteArray(); if (data.length > 255) { throw new IOException("Cannot have individual values larger that 255 chars. Offending value: " + key + (val != null ? "" : "=" + val)); } out.write((byte) data.length); out.write(data, 0, data.length); } text = out.toByteArray(); } catch (IOException e) { throw new RuntimeException("unexpected exception: " + e); } } return (text != null && text.length > 0 ? text : DNSRecord.EMPTY_TXT); } public void setDns(JmDNSImpl dns) { this._state.setDns(dns); } /** * {@inheritDoc} */ @Override public JmDNSImpl getDns() { return this._state.getDns(); } /** * {@inheritDoc} */ @Override public boolean isPersistent() { return _persistent; } /** * @param needTextAnnouncing * the needTextAnnouncing to set */ public void setNeedTextAnnouncing(boolean needTextAnnouncing) { this._needTextAnnouncing = needTextAnnouncing; if (this._needTextAnnouncing) { _state.setTask(null); } } /** * @return the needTextAnnouncing */ public boolean needTextAnnouncing() { return _needTextAnnouncing; } /** * @return the delegate */ Delegate getDelegate() { return this._delegate; } /** * @param delegate * the delegate to set */ void setDelegate(Delegate delegate) { this._delegate = delegate; } }