Home | History | Annotate | Download | only in impl
      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 }
    287