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          * &lt;sub&gt;._sub.&lt;app&gt;.&lt;protocol&gt;.&lt;servicedomain&gt;.&lt;parentdomain&gt;.
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