View Javadoc

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           */
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           */
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           */
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           */
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 
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
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          */
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          */
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          */
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
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      */
238     public ListenerStatus(T listener, boolean synch) {
239         super();
240         _listener = listener;
241         _synch = synch;
242     }
243 
244     /**
245      * @return the listener
246      */
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      */
256     public boolean isSynchronous() {
257         return _synch;
258     }
259 
260     /*
261      * (non-Javadoc)
262      * @see java.lang.Object#hashCode()
263      */
264     @Override
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
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
283     public String toString() {
284         return "[Status for " + this.getListener().toString() + "]";
285     }
286 }