summaryrefslogtreecommitdiff
path: root/src/javax/jmdns/impl/ServiceInfoImpl.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/javax/jmdns/impl/ServiceInfoImpl.java')
-rw-r--r--src/javax/jmdns/impl/ServiceInfoImpl.java1324
1 files changed, 1324 insertions, 0 deletions
diff --git a/src/javax/jmdns/impl/ServiceInfoImpl.java b/src/javax/jmdns/impl/ServiceInfoImpl.java
new file mode 100644
index 0000000..d9c7a92
--- /dev/null
+++ b/src/javax/jmdns/impl/ServiceInfoImpl.java
@@ -0,0 +1,1324 @@
+// 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<String, byte[]> _props;
+ private final Set<Inet4Address> _ipv4Addresses;
+ private final Set<Inet6Address> _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<String, ?> 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<Fields, String> qualifiedNameMap, int port, int weight, int priority, boolean persistent, Map<String, ?> props) {
+ this(qualifiedNameMap, port, weight, priority, persistent, textFromProperties(props));
+ }
+
+ ServiceInfoImpl(Map<Fields, String> 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<Fields, String> qualifiedNameMap, int port, int weight, int priority, boolean persistent, byte text[]) {
+ Map<Fields, String> 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<Inet4Address>());
+ this._ipv6Addresses = Collections.synchronizedSet(new LinkedHashSet<Inet6Address>());
+ }
+
+ /**
+ * During recovery we need to duplicate service info to reregister them
+ *
+ * @param info
+ */
+ ServiceInfoImpl(ServiceInfo info) {
+ this._ipv4Addresses = Collections.synchronizedSet(new LinkedHashSet<Inet4Address>());
+ this._ipv6Addresses = Collections.synchronizedSet(new LinkedHashSet<Inet6Address>());
+ 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<Fields, String> decodeQualifiedNameMap(String type, String name, String subtype) {
+ Map<Fields, String> qualifiedNameMap = decodeQualifiedNameMapForType(type);
+
+ qualifiedNameMap.put(Fields.Instance, name);
+ qualifiedNameMap.put(Fields.Subtype, subtype);
+
+ return checkQualifiedNameMap(qualifiedNameMap);
+ }
+
+ public static Map<Fields, String> 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<Fields, String> qualifiedNameMap = new HashMap<Fields, String>(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<Fields, String> checkQualifiedNameMap(Map<Fields, String> qualifiedNameMap) {
+ Map<Fields, String> checkedQualifiedNameMap = new HashMap<Fields, String>(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 <code>foobar</code>
+ */
+ 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<InetAddress> aList = new ArrayList<InetAddress>(_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<String, byte[]> 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<String> getPropertyNames() {
+ Map<String, byte[]> properties = this.getProperties();
+ Collection<String> names = (properties != null ? properties.keySet() : Collections.<String> emptySet());
+ return new Vector<String>(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<Fields, String> getQualifiedNameMap() {
+ Map<Fields, String> map = new HashMap<Fields, String>(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<String, byte[]> getProperties() {
+ if ((_props == null) && (this.getTextBytes() != null)) {
+ Hashtable<String, byte[]> properties = new Hashtable<String, byte[]>();
+ 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.<String, byte[]> 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 <code>true</code> if the service info has data, <code>false</code> 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<String, byte[]> 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<DNSRecord> answers(boolean unique, int ttl, HostInfo localHost) {
+ List<DNSRecord> list = new ArrayList<DNSRecord>();
+ 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<String, ?> 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<String, ?> 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;
+ }
+
+}