1 /** 2 * 3 */ 4 package javax.jmdns.impl; 5 6 import java.util.EventListener; 7 import java.util.concurrent.ConcurrentHashMap; 8 import java.util.concurrent.ConcurrentMap; 9 import java.util.logging.Logger; 10 11 import javax.jmdns.JmDNS; 12 import javax.jmdns.ServiceEvent; 13 import javax.jmdns.ServiceInfo; 14 import javax.jmdns.ServiceListener; 15 import javax.jmdns.ServiceTypeListener; 16 17 /** 18 * This class track the status of listener.<br/> 19 * The main purpose of this class is to collapse consecutive events so that we can guarantee the correct call back sequence. 20 * 21 * @param <T> 22 * listener type 23 */ 24 public class ListenerStatus<T extends EventListener> { 25 26 public static class ServiceListenerStatus extends ListenerStatus<ServiceListener> { 27 private static Logger logger = Logger.getLogger(ServiceListenerStatus.class.getName()); 28 29 private final ConcurrentMap<String, ServiceInfo> _addedServices; 30 31 /** 32 * @param listener 33 * listener being tracked. 34 * @param synch 35 * true if that listener can be called asynchronously 36 */ ServiceListenerStatus(ServiceListener listener, boolean synch)37 public ServiceListenerStatus(ServiceListener listener, boolean synch) { 38 super(listener, synch); 39 _addedServices = new ConcurrentHashMap<String, ServiceInfo>(32); 40 } 41 42 /** 43 * A service has been added.<br/> 44 * <b>Note:</b>This event is only the service added event. The service info associated with this event does not include resolution information.<br/> 45 * To get the full resolved information you need to listen to {@link #serviceResolved(ServiceEvent)} or call {@link JmDNS#getServiceInfo(String, String, long)} 46 * 47 * <pre> 48 * ServiceInfo info = event.getDNS().getServiceInfo(event.getType(), event.getName()) 49 * </pre> 50 * <p> 51 * Please note that service resolution may take a few second to resolve. 52 * </p> 53 * 54 * @param event 55 * The ServiceEvent providing the name and fully qualified type of the service. 56 */ serviceAdded(ServiceEvent event)57 void serviceAdded(ServiceEvent event) { 58 String qualifiedName = event.getName() + "." + event.getType(); 59 if (null == _addedServices.putIfAbsent(qualifiedName, event.getInfo().clone())) { 60 this.getListener().serviceAdded(event); 61 ServiceInfo info = event.getInfo(); 62 if ((info != null) && (info.hasData())) { 63 this.getListener().serviceResolved(event); 64 } 65 } else { 66 logger.finer("Service Added called for a service already added: " + event); 67 } 68 } 69 70 /** 71 * A service has been removed. 72 * 73 * @param event 74 * The ServiceEvent providing the name and fully qualified type of the service. 75 */ serviceRemoved(ServiceEvent event)76 void serviceRemoved(ServiceEvent event) { 77 String qualifiedName = event.getName() + "." + event.getType(); 78 if (_addedServices.remove(qualifiedName, _addedServices.get(qualifiedName))) { 79 this.getListener().serviceRemoved(event); 80 } else { 81 logger.finer("Service Removed called for a service already removed: " + event); 82 } 83 } 84 85 /** 86 * A service has been resolved. Its details are now available in the ServiceInfo record.<br/> 87 * <b>Note:</b>This call back will never be called if the service does not resolve.<br/> 88 * 89 * @param event 90 * The ServiceEvent providing the name, the fully qualified type of the service, and the service info record. 91 */ serviceResolved(ServiceEvent event)92 synchronized void serviceResolved(ServiceEvent event) { 93 ServiceInfo info = event.getInfo(); 94 if ((info != null) && (info.hasData())) { 95 String qualifiedName = event.getName() + "." + event.getType(); 96 ServiceInfo previousServiceInfo = _addedServices.get(qualifiedName); 97 if (!_sameInfo(info, previousServiceInfo)) { 98 if (null == previousServiceInfo) { 99 if (null == _addedServices.putIfAbsent(qualifiedName, info.clone())) { 100 this.getListener().serviceResolved(event); 101 } 102 } else { 103 if (_addedServices.replace(qualifiedName, previousServiceInfo, info.clone())) { 104 this.getListener().serviceResolved(event); 105 } 106 } 107 } else { 108 logger.finer("Service Resolved called for a service already resolved: " + event); 109 } 110 } else { 111 logger.warning("Service Resolved called for an unresolved event: " + event); 112 113 } 114 } 115 _sameInfo(ServiceInfo info, ServiceInfo lastInfo)116 private static final boolean _sameInfo(ServiceInfo info, ServiceInfo lastInfo) { 117 if (info == null) return false; 118 if (lastInfo == null) return false; 119 if (!info.equals(lastInfo)) return false; 120 byte[] text = info.getTextBytes(); 121 byte[] lastText = lastInfo.getTextBytes(); 122 if (text.length != lastText.length) return false; 123 for (int i = 0; i < text.length; i++) { 124 if (text[i] != lastText[i]) return false; 125 } 126 return true; 127 } 128 129 /* 130 * (non-Javadoc) 131 * @see java.lang.Object#toString() 132 */ 133 @Override toString()134 public String toString() { 135 StringBuilder aLog = new StringBuilder(2048); 136 aLog.append("[Status for "); 137 aLog.append(this.getListener().toString()); 138 if (_addedServices.isEmpty()) { 139 aLog.append(" no type event "); 140 } else { 141 aLog.append(" ("); 142 for (String service : _addedServices.keySet()) { 143 aLog.append(service + ", "); 144 } 145 aLog.append(") "); 146 } 147 aLog.append("]"); 148 return aLog.toString(); 149 } 150 151 } 152 153 public static class ServiceTypeListenerStatus extends ListenerStatus<ServiceTypeListener> { 154 private static Logger logger = Logger.getLogger(ServiceTypeListenerStatus.class.getName()); 155 156 private final ConcurrentMap<String, String> _addedTypes; 157 158 /** 159 * @param listener 160 * listener being tracked. 161 * @param synch 162 * true if that listener can be called asynchronously 163 */ ServiceTypeListenerStatus(ServiceTypeListener listener, boolean synch)164 public ServiceTypeListenerStatus(ServiceTypeListener listener, boolean synch) { 165 super(listener, synch); 166 _addedTypes = new ConcurrentHashMap<String, String>(32); 167 } 168 169 /** 170 * A new service type was discovered. 171 * 172 * @param event 173 * The service event providing the fully qualified type of the service. 174 */ serviceTypeAdded(ServiceEvent event)175 void serviceTypeAdded(ServiceEvent event) { 176 if (null == _addedTypes.putIfAbsent(event.getType(), event.getType())) { 177 this.getListener().serviceTypeAdded(event); 178 } else { 179 logger.finest("Service Type Added called for a service type already added: " + event); 180 } 181 } 182 183 /** 184 * A new subtype for the service type was discovered. 185 * 186 * <pre> 187 * <sub>._sub.<app>.<protocol>.<servicedomain>.<parentdomain>. 188 * </pre> 189 * 190 * @param event 191 * The service event providing the fully qualified type of the service with subtype. 192 */ subTypeForServiceTypeAdded(ServiceEvent event)193 void subTypeForServiceTypeAdded(ServiceEvent event) { 194 if (null == _addedTypes.putIfAbsent(event.getType(), event.getType())) { 195 this.getListener().subTypeForServiceTypeAdded(event); 196 } else { 197 logger.finest("Service Sub Type Added called for a service sub type already added: " + event); 198 } 199 } 200 201 /* 202 * (non-Javadoc) 203 * @see java.lang.Object#toString() 204 */ 205 @Override toString()206 public String toString() { 207 StringBuilder aLog = new StringBuilder(2048); 208 aLog.append("[Status for "); 209 aLog.append(this.getListener().toString()); 210 if (_addedTypes.isEmpty()) { 211 aLog.append(" no type event "); 212 } else { 213 aLog.append(" ("); 214 for (String type : _addedTypes.keySet()) { 215 aLog.append(type + ", "); 216 } 217 aLog.append(") "); 218 } 219 aLog.append("]"); 220 return aLog.toString(); 221 } 222 223 } 224 225 public final static boolean SYNCHONEOUS = true; 226 public final static boolean ASYNCHONEOUS = false; 227 228 private final T _listener; 229 230 private final boolean _synch; 231 232 /** 233 * @param listener 234 * listener being tracked. 235 * @param synch 236 * true if that listener can be called asynchronously 237 */ ListenerStatus(T listener, boolean synch)238 public ListenerStatus(T listener, boolean synch) { 239 super(); 240 _listener = listener; 241 _synch = synch; 242 } 243 244 /** 245 * @return the listener 246 */ getListener()247 public T getListener() { 248 return _listener; 249 } 250 251 /** 252 * Return <cod>true</code> if the listener must be called synchronously. 253 * 254 * @return the synch 255 */ isSynchronous()256 public boolean isSynchronous() { 257 return _synch; 258 } 259 260 /* 261 * (non-Javadoc) 262 * @see java.lang.Object#hashCode() 263 */ 264 @Override hashCode()265 public int hashCode() { 266 return this.getListener().hashCode(); 267 } 268 269 /* 270 * (non-Javadoc) 271 * @see java.lang.Object#equals(java.lang.Object) 272 */ 273 @Override equals(Object obj)274 public boolean equals(Object obj) { 275 return (obj instanceof ListenerStatus) && this.getListener().equals(((ListenerStatus<?>) obj).getListener()); 276 } 277 278 /* 279 * (non-Javadoc) 280 * @see java.lang.Object#toString() 281 */ 282 @Override toString()283 public String toString() { 284 return "[Status for " + this.getListener().toString() + "]"; 285 } 286 } 287