001/*
002 * Copyright 2017-2019, Digi International Inc.
003 *
004 * This Source Code Form is subject to the terms of the Mozilla Public
005 * License, v. 2.0. If a copy of the MPL was not distributed with this
006 * file, you can obtain one at http://mozilla.org/MPL/2.0/.
007 *
008 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 
009 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 
010 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 
011 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 
012 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 
013 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 
014 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
015 */
016package com.digi.xbee.api;
017
018import java.util.ArrayList;
019import java.util.Collection;
020import java.util.List;
021import java.util.Map;
022import java.util.Set;
023import java.util.concurrent.ConcurrentHashMap;
024
025import org.slf4j.Logger;
026import org.slf4j.LoggerFactory;
027
028import com.digi.xbee.api.exceptions.InterfaceNotOpenException;
029import com.digi.xbee.api.exceptions.OperationNotSupportedException;
030import com.digi.xbee.api.exceptions.TimeoutException;
031import com.digi.xbee.api.exceptions.XBeeException;
032import com.digi.xbee.api.listeners.IDiscoveryListener;
033import com.digi.xbee.api.models.DiscoveryOptions;
034import com.digi.xbee.api.models.XBee16BitAddress;
035import com.digi.xbee.api.models.XBee64BitAddress;
036import com.digi.xbee.api.models.XBeeProtocol;
037import com.digi.xbee.api.utils.ByteUtils;
038
039/**
040 * This class represents an XBee Network.
041 *  
042 * <p>The network allows the discovery of remote devices in the same network 
043 * as the local one and stores them.</p>
044 */
045public class XBeeNetwork {
046        
047        // Variables.
048
049        private AbstractXBeeDevice localDevice;
050        
051        private Map<XBee64BitAddress, RemoteXBeeDevice> remotesBy64BitAddr;
052        private Map<XBee16BitAddress, RemoteXBeeDevice> remotesBy16BitAddr;
053        
054        private List<IDiscoveryListener> discoveryListeners = new ArrayList<IDiscoveryListener>();
055        
056        private NodeDiscovery nodeDiscovery;
057        
058        protected Logger logger;
059        
060        /**
061         * Instantiates a new {@code XBeeNetwork} object.
062         * 
063         * @param device Local XBee device to get the network from.
064         * 
065         * @throws NullPointerException if {@code device == null}.
066         * @throws IllegalArgumentException If {@code xbeeDevice.isRemote() == true}.
067         * 
068         * @see AbstractXBeeDevice
069         */
070        XBeeNetwork(AbstractXBeeDevice device) {
071                if (device == null)
072                        throw new NullPointerException("Local XBee device cannot be null.");
073                if (device.isRemote())
074                        throw new IllegalArgumentException("The given local XBee device is remote.");
075                
076                localDevice = device;
077                remotesBy64BitAddr = new ConcurrentHashMap<XBee64BitAddress, RemoteXBeeDevice>();
078                remotesBy16BitAddr = new ConcurrentHashMap<XBee16BitAddress, RemoteXBeeDevice>();
079                nodeDiscovery = new NodeDiscovery(localDevice);
080                
081                logger = LoggerFactory.getLogger(this.getClass());
082        }
083        
084        /**
085         * Discovers and reports the first remote XBee device that matches the 
086         * supplied identifier.
087         * 
088         * <p>This method blocks until the device is discovered or the configured 
089         * timeout expires. To configure the discovery timeout, use the method
090         * {@link #setDiscoveryTimeout(long)}.</p>
091         * 
092         * <p>To configure the discovery options, use the 
093         * {@link #setDiscoveryOptions(Set)} method.</p> 
094         * 
095         * @param id The identifier of the device to be discovered.
096         * 
097         * @return The discovered remote XBee device with the given identifier, 
098         *         {@code null} if the timeout expires and the device was not found.
099         * 
100         * @throws IllegalArgumentException if {@code id.length() == 0}.
101         * @throws InterfaceNotOpenException if the device is not open.
102         * @throws NullPointerException if {@code id == null}.
103         * @throws XBeeException if there is an error discovering the device.
104         * 
105         * @see #discoverDevices(List)
106         * @see #getDevice(String)
107         * @see RemoteXBeeDevice
108         */
109        public RemoteXBeeDevice discoverDevice(String id) throws XBeeException {
110                if (id == null)
111                        throw new NullPointerException("Device identifier cannot be null.");
112                if (id.length() == 0)
113                        throw new IllegalArgumentException("Device identifier cannot be an empty string.");
114                
115                logger.debug("{}Discovering '{}' device.", localDevice.toString(), id);
116                
117                return nodeDiscovery.discoverDevice(id);
118        }
119        
120        /**
121         * Discovers and reports all remote XBee devices that match the supplied 
122         * identifiers.
123         * 
124         * <p>This method blocks until the configured timeout expires. To configure 
125         * the discovery timeout, use the method {@link #setDiscoveryTimeout(long)}.
126         * </p>
127         * 
128         * <p>To configure the discovery options, use the 
129         * {@link #setDiscoveryOptions(Set)} method.</p> 
130         * 
131         * @param ids List which contains the identifiers of the devices to be 
132         *            discovered.
133         * 
134         * @return A list of the discovered remote XBee devices with the given 
135         *         identifiers.
136         * 
137         * @throws IllegalArgumentException if {@code ids.size() == 0}.
138         * @throws InterfaceNotOpenException if the device is not open.
139         * @throws NullPointerException if {@code ids == null}.
140         * @throws XBeeException if there is an error discovering the devices.
141         * 
142         * @see #discoverDevice(String)
143         * @see RemoteXBeeDevice
144         */
145        public List<RemoteXBeeDevice> discoverDevices(List<String> ids) throws XBeeException {
146                if (ids == null)
147                        throw new NullPointerException("List of device identifiers cannot be null.");
148                if (ids.size() == 0)
149                        throw new IllegalArgumentException("List of device identifiers cannot be empty.");
150                
151                logger.debug("{}Discovering all '{}' devices.", localDevice.toString(), ids.toString());
152                
153                return nodeDiscovery.discoverDevices(ids);
154        }
155        
156        /**
157         * Adds the given discovery listener to the list of listeners to be notified 
158         * when the discovery process is running.
159         * 
160         * <p>If the listener has already been included, this method does nothing.
161         * </p>
162         * 
163         * @param listener Listener to be notified when the discovery process is
164         *                 running.
165         * 
166         * @throws NullPointerException if {@code listener == null}.
167         * 
168         * @see com.digi.xbee.api.listeners.IDiscoveryListener
169         * @see #removeDiscoveryListener(IDiscoveryListener)
170         */
171        public void addDiscoveryListener(IDiscoveryListener listener) {
172                if (listener == null)
173                        throw new NullPointerException("Listener cannot be null.");
174                
175                synchronized (discoveryListeners) {
176                        if (!discoveryListeners.contains(listener))
177                                discoveryListeners.add(listener);
178                }
179        }
180        
181        /**
182         * Removes the given discovery listener from the list of discovery 
183         * listeners.
184         * 
185         * <p>If the listener is not included in the list, this method does nothing.
186         * </p>
187         * 
188         * @param listener Discovery listener to remove.
189         * 
190         * @throws NullPointerException if {@code listener == null}.
191         * 
192         * @see com.digi.xbee.api.listeners.IDiscoveryListener
193         * @see #addDiscoveryListener(IDiscoveryListener)
194         */
195        public void removeDiscoveryListener(IDiscoveryListener listener) {
196                if (listener == null)
197                        throw new NullPointerException("Listener cannot be null.");
198                
199                synchronized (discoveryListeners) {
200                        if (discoveryListeners.contains(listener))
201                                discoveryListeners.remove(listener);
202                }
203        }
204        
205        /**
206         * Starts the discovery process with the configured timeout and options.
207         * 
208         * <p>To be notified every time an XBee device is discovered, add a
209         * {@code IDiscoveryListener} using the 
210         * {@link #addDiscoveryListener(IDiscoveryListener)} method before starting
211         * the discovery process.</p>
212         * 
213         * <p>To configure the discovery timeout, use the 
214         * {@link #setDiscoveryTimeout(long)} method.</p>
215         * 
216         * <p>To configure the discovery options, use the 
217         * {@link #setDiscoveryOptions(Set)} method.</p> 
218         * 
219         * @throws IllegalStateException if the discovery process is already running.
220         * @throws InterfaceNotOpenException if the device is not open.
221         * 
222         * @see #addDiscoveryListener(IDiscoveryListener)
223         * @see #stopDiscoveryProcess()
224         */
225        public void startDiscoveryProcess() {
226                if (isDiscoveryRunning())
227                        throw new IllegalStateException("The discovery process is already running.");
228                
229                synchronized (discoveryListeners) {
230                        nodeDiscovery.startDiscoveryProcess(discoveryListeners);
231                }
232        }
233        
234        /**
235         * Stops the discovery process if it is running.
236         * 
237         * <p>Note that DigiMesh/DigiPoint devices are blocked until the discovery
238         * time configured (NT parameter) has elapsed, so if you try to get/set
239         * any parameter during the discovery process you will receive a timeout 
240         * exception.</p>
241         * 
242         * @see #isDiscoveryRunning()
243         * @see #removeDiscoveryListener(IDiscoveryListener)
244         * @see #startDiscoveryProcess()
245         */
246        public void stopDiscoveryProcess() {
247                nodeDiscovery.stopDiscoveryProcess();
248        }
249        
250        /**
251         * Retrieves whether the discovery process is running or not.
252         * 
253         * @return {@code true} if the discovery process is running, {@code false} 
254         *         otherwise.
255         * 
256         * @see #startDiscoveryProcess()
257         * @see #stopDiscoveryProcess()
258         */
259        public boolean isDiscoveryRunning() {
260                return nodeDiscovery.isRunning();
261        }
262        
263        /**
264         * Configures the discovery timeout ({@code NT} parameter) with the given 
265         * value.
266         * 
267         * <p>Note that in some protocols, the discovery process may take longer
268         * than the value set in this method due to the network propagation time.
269         * </p>
270         * 
271         * @param timeout New discovery timeout in milliseconds.
272         * 
273         * @throws TimeoutException if there is a timeout setting the discovery
274         *                          timeout.
275         * @throws XBeeException if there is any other XBee related exception.
276         * 
277         * @see #setDiscoveryOptions(Set)
278         */
279        public void setDiscoveryTimeout(long timeout) throws TimeoutException, XBeeException {
280                if (timeout <= 0)
281                        throw new IllegalArgumentException("Timeout must be bigger than 0.");
282                
283                localDevice.setParameter("NT", ByteUtils.longToByteArray(timeout / 100));
284        }
285        
286        /**
287         * Configures the discovery options ({@code NO} parameter) with the given 
288         * value.
289         * 
290         * @param options New discovery options.
291         * 
292         * @throws TimeoutException if there is a timeout setting the discovery
293         *                          options.
294         * @throws XBeeException if there is any other XBee related exception.
295         * 
296         * @see #setDiscoveryTimeout(long)
297         * @see DiscoveryOptions
298         */
299        public void setDiscoveryOptions(Set<DiscoveryOptions> options) throws TimeoutException, XBeeException {
300                if (options == null)
301                        throw new NullPointerException("Options cannot be null.");
302                
303                int value = DiscoveryOptions.calculateDiscoveryValue(localDevice.getXBeeProtocol(), options);
304                localDevice.setParameter("NO", ByteUtils.intToByteArray(value));
305        }
306        
307        /**
308         * Returns all remote devices already contained in the network.
309         * 
310         * <p>Note that this method <b>does not perform a discovery</b>, only 
311         * returns the devices that have been previously discovered.</p>
312         * 
313         * @return A list with all XBee devices in the network.
314         * 
315         * @see #getDevices(String)
316         * @see RemoteXBeeDevice
317         */
318        public List<RemoteXBeeDevice> getDevices() {
319                List<RemoteXBeeDevice> nodes = new ArrayList<RemoteXBeeDevice>();
320                nodes.addAll(remotesBy64BitAddr.values());
321                nodes.addAll(remotesBy16BitAddr.values());
322                return nodes;
323        }
324        
325        /**
326         * Returns all remote devices that match the supplied identifier.
327         * 
328         * <p>Note that this method <b>does not perform a discovery</b>, only 
329         * returns the devices that have been previously discovered.</p>
330         * 
331         * @param id The identifier of the devices to be retrieved.
332         * 
333         * @return A list of the remote XBee devices contained in the network with 
334         *         the given identifier.
335         * 
336         * @throws IllegalArgumentException if {@code id.length() == 0}.
337         * @throws NullPointerException if {@code id == null}.
338         * 
339         * @see #getDevice(String)
340         * @see RemoteXBeeDevice
341         */
342        public List<RemoteXBeeDevice> getDevices(String id) {
343                if (id == null)
344                        throw new NullPointerException("Device identifier cannot be null.");
345                if (id.length() == 0)
346                        throw new IllegalArgumentException("Device identifier cannot be an empty string.");
347                
348                List<RemoteXBeeDevice> devices = new ArrayList<RemoteXBeeDevice>();
349                
350                // Look in the 64-bit map.
351                for (RemoteXBeeDevice remote : remotesBy64BitAddr.values()) {
352                        if (id.equals(remote.getNodeID()))
353                                devices.add(remote);
354                }
355                // Look in the 16-bit map.
356                for (RemoteXBeeDevice remote : remotesBy16BitAddr.values()) {
357                        if (id.equals(remote.getNodeID()))
358                                devices.add(remote);
359                }
360                // Return the list.
361                return devices;
362        }
363        
364        /**
365         * Returns the first remote device that matches the supplied identifier.
366         * 
367         * <p>Note that this method <b>does not perform a discovery</b>, only 
368         * returns the device that has been previously discovered.</p>
369         * 
370         * @param id The identifier of the device to be retrieved.
371         * 
372         * @return The remote XBee device contained in the network with the given 
373         *         identifier, {@code null} if the network does not contain any 
374         *         device with that Node ID.
375         * 
376         * @throws IllegalArgumentException if {@code id.length() == 0}.
377         * @throws NullPointerException if {@code id == null}.
378         * 
379         * @see #discoverDevice(String)
380         * @see #getDevices(String)
381         * @see RemoteXBeeDevice
382         */
383        public RemoteXBeeDevice getDevice(String id) {
384                if (id == null)
385                        throw new NullPointerException("Device identifier cannot be null.");
386                if (id.length() == 0)
387                        throw new IllegalArgumentException("Device identifier cannot be an empty string.");
388                
389                // Look in the 64-bit map.
390                for (RemoteXBeeDevice remote : remotesBy64BitAddr.values()) {
391                        if (id.equals(remote.getNodeID()))
392                                return remote;
393                }
394                // Look in the 16-bit map.
395                for (RemoteXBeeDevice remote : remotesBy16BitAddr.values()) {
396                        if (id.equals(remote.getNodeID()))
397                                return remote;
398                }
399                // The given ID is not in the network.
400                return null;
401        }
402        
403        /**
404         * Returns the remote device already contained in the network whose 64-bit 
405         * address matches the given one.
406         * 
407         * <p>Note that this method <b>does not perform a discovery</b>, only 
408         * returns the device that has been previously discovered.</p>
409         * 
410         * @param address The 64-bit address of the device to be retrieved.
411         * 
412         * @return The remote device in the network or {@code null} if it is not 
413         *         found.
414         * 
415         * @throws IllegalArgumentException if {@code address.equals(XBee64BitAddress.UNKNOWN_ADDRESS)}.
416         * @throws NullPointerException if {@code address == null}.
417         */
418        public RemoteXBeeDevice getDevice(XBee64BitAddress address) {
419                if (address == null)
420                        throw new NullPointerException("64-bit address cannot be null.");
421                if (address.equals(XBee64BitAddress.UNKNOWN_ADDRESS))
422                        throw new IllegalArgumentException("64-bit address cannot be unknown.");
423                
424                logger.debug("{}Getting device '{}' from network.", localDevice.toString(), address);
425                
426                return remotesBy64BitAddr.get(address);
427        }
428        
429        /**
430         * Returns the remote device already contained in the network whose 16-bit 
431         * address matches the given one.
432         * 
433         * <p>Note that this method <b>does not perform a discovery</b>, only 
434         * returns the device that has been previously discovered.</p>
435         * 
436         * @param address The 16-bit address of the device to be retrieved.
437         * 
438         * @return The remote device in the network or {@code null} if it is not 
439         *         found.
440         * 
441         * @throws IllegalArgumentException if {@code address.equals(XBee16BitAddress.UNKNOWN_ADDRESS)}.
442         * @throws NullPointerException if {@code address == null}.
443         * @throws OperationNotSupportedException if the protocol of the local XBee device is DigiMesh or Point-to-Multipoint.
444         */
445        public RemoteXBeeDevice getDevice(XBee16BitAddress address) throws OperationNotSupportedException {
446                if (localDevice.getXBeeProtocol() == XBeeProtocol.DIGI_MESH)
447                        throw new OperationNotSupportedException("DigiMesh protocol does not support 16-bit addressing.");
448                if (localDevice.getXBeeProtocol() == XBeeProtocol.DIGI_POINT)
449                        throw new OperationNotSupportedException("Point-to-Multipoint protocol does not support 16-bit addressing.");
450                if (address == null)
451                        throw new NullPointerException("16-bit address cannot be null.");
452                if (address.equals(XBee16BitAddress.UNKNOWN_ADDRESS))
453                        throw new IllegalArgumentException("16-bit address cannot be unknown.");
454                
455                logger.debug("{}Getting device '{}' from network.", localDevice.toString(), address);
456                
457                // The preference order is: 
458                //    1.- Look in the 64-bit map 
459                //    2.- Then in the 16-bit map.
460                // This should be maintained in the 'addRemoteDevice' method.
461                
462                RemoteXBeeDevice devInNetwork = null;
463                
464                // Look in the 64-bit map.
465                Collection<RemoteXBeeDevice> devices = remotesBy64BitAddr.values();
466                for (RemoteXBeeDevice d: devices) {
467                        XBee16BitAddress a = get16BitAddress(d);
468                        if (a != null && a.equals(address)) {
469                                devInNetwork = d;
470                                break;
471                        }
472                }
473                
474                // Look in the 16-bit map.
475                if (devInNetwork == null)
476                        devInNetwork = remotesBy16BitAddr.get(address);
477                
478                return devInNetwork;
479        }
480        
481        /**
482         * Adds the given remote device to the network. 
483         * 
484         * <p>Notice that this operation does not join the remote XBee device to the
485         * network; it just tells the network that it contains that device. However, 
486         * the device has only been added to the device list, and may not be 
487         * physically in the same network.</p>
488         * 
489         * <p>The way of adding a device to the network is based on the 64-bit 
490         * address. If it is not configured:</p>
491         * 
492         * <ul>
493         * <li>For 802.15.4 and ZigBee devices, it will use the 16-bit address.</li>
494         * <li>For the rest will return {@code false} as the result of the addition.</li>
495         * </ul>
496         * 
497         * @param remoteDevice The remote device to be added to the network.
498         * 
499         * @return The remote XBee Device instance in the network, {@code null} if
500         *         the device could not be successfully added.
501         * 
502         * @throws NullPointerException if {@code remoteDevice == null}.
503         * 
504         * @see #addRemoteDevices(List)
505         * @see #removeRemoteDevice(RemoteXBeeDevice)
506         * @see RemoteXBeeDevice
507         */
508        public RemoteXBeeDevice addRemoteDevice(RemoteXBeeDevice remoteDevice) {
509                if (remoteDevice == null)
510                        throw new NullPointerException("Remote device cannot be null.");
511                
512                logger.debug("{}Adding device '{}' to network.", localDevice.toString(), remoteDevice.toString());
513                
514                RemoteXBeeDevice devInNetwork = null;
515                XBee64BitAddress addr64 = remoteDevice.get64BitAddress();
516                XBee16BitAddress addr16 = get16BitAddress(remoteDevice);
517                
518                // Check if the device has 64-bit address.
519                if (addr64 != null && !addr64.equals(XBee64BitAddress.UNKNOWN_ADDRESS)) {
520                        // The device has 64-bit address, so look in the 64-bit map.
521                        devInNetwork = remotesBy64BitAddr.get(addr64);
522                        if (devInNetwork != null) {
523                                // The device exists in the 64-bit map, so update the reference and return it.
524                                logger.debug("{}Existing device '{}' in network.", localDevice.toString(), devInNetwork.toString());
525                                devInNetwork.updateDeviceDataFrom(remoteDevice);
526                                return devInNetwork;
527                        } else {
528                                // The device does not exist in the 64-bit map, so check its 16-bit address.
529                                if (addr16 != null && !addr16.equals(XBee16BitAddress.UNKNOWN_ADDRESS)) {
530                                        // The device has 16-bit address, so look in the 16-bit map.
531                                        devInNetwork = remotesBy16BitAddr.get(addr16);
532                                        if (devInNetwork != null) {
533                                                // The device exists in the 16-bit map, so remove it and add it to the 64-bit map.
534                                                logger.debug("{}Existing device '{}' in network.", localDevice.toString(), devInNetwork.toString());
535                                                devInNetwork = remotesBy16BitAddr.remove(addr16);
536                                                devInNetwork.updateDeviceDataFrom(remoteDevice);
537                                                remotesBy64BitAddr.put(addr64, devInNetwork);
538                                                return devInNetwork;
539                                        } else {
540                                                // The device does not exist in the 16-bit map, so add it to the 64-bit map.
541                                                remotesBy64BitAddr.put(addr64, remoteDevice);
542                                                return remoteDevice;
543                                        }
544                                } else {
545                                        // The device has not 16-bit address, so add it to the 64-bit map.
546                                        remotesBy64BitAddr.put(addr64, remoteDevice);
547                                        return remoteDevice;
548                                }
549                        }
550                }
551                
552                // If the device has not 64-bit address, check if it has 16-bit address.
553                if (addr16 != null && !addr16.equals(XBee16BitAddress.UNKNOWN_ADDRESS)) {
554                        // The device has 16-bit address, so look in the 64-bit map.
555                        Collection<RemoteXBeeDevice> devices = remotesBy64BitAddr.values();
556                        for (RemoteXBeeDevice d : devices) {
557                                XBee16BitAddress a = get16BitAddress(d);
558                                if (a != null && a.equals(addr16)) {
559                                        devInNetwork = d;
560                                        break;
561                                }
562                        }
563                        // Check if the device exists in the 64-bit map.
564                        if (devInNetwork != null) {
565                                // The device exists in the 64-bit map, so update the reference and return it.
566                                logger.debug("{}Existing device '{}' in network.", localDevice.toString(), devInNetwork.toString());
567                                devInNetwork.updateDeviceDataFrom(remoteDevice);
568                                return devInNetwork;
569                        } else {
570                                // The device does not exist in the 64-bit map, so look in the 16-bit map.
571                                devInNetwork = remotesBy16BitAddr.get(addr16);
572                                if (devInNetwork != null) {
573                                        // The device exists in the 16-bit map, so update the reference and return it.
574                                        logger.debug("{}Existing device '{}' in network.", localDevice.toString(), devInNetwork.toString());
575                                        devInNetwork.updateDeviceDataFrom(remoteDevice);
576                                        return devInNetwork;
577                                } else {
578                                        // The device does not exist in the 16-bit map, so add it.
579                                        remotesBy16BitAddr.put(addr16, remoteDevice);
580                                        return remoteDevice;
581                                }
582                        }
583                }
584                
585                // If the device does not contain a valid address, return null.
586                logger.error("{}Remote device '{}' cannot be added: 64-bit and 16-bit addresses must be specified.", 
587                                localDevice.toString(), remoteDevice.toString());
588                return null;
589        }
590        
591        /**
592         * Adds the given list of remote devices to the network.
593         * 
594         * <p>Notice that this operation does not join the remote XBee devices to 
595         * the network; it just tells the network that it contains those devices. 
596         * However, the devices have only been added to the device list, and may 
597         * not be physically in the same network.</p>
598         * 
599         * <p>The way of adding a device to the network is based on the 64-bit 
600         * address. If it is not configured:</p>
601         * 
602         * <ul>
603         * <li>For 802.15.4 and ZigBee devices, the 16-bit address will be used instead.</li>
604         * <li>For the rest will return {@code false} as the result of the addition.</li>
605         * </ul>
606         * 
607         * @param list The list of remote devices to be added to the network.
608         * 
609         * @return A list with the successfully added devices to the network.
610         * 
611         * @throws NullPointerException if {@code list == null}.
612         * 
613         * @see #addRemoteDevice(RemoteXBeeDevice)
614         * @see RemoteXBeeDevice
615         */
616        public List<RemoteXBeeDevice> addRemoteDevices(List<RemoteXBeeDevice> list) {
617                if (list == null)
618                        throw new NullPointerException("The list of remote devices cannot be null.");
619                
620                List<RemoteXBeeDevice> addedList = new ArrayList<RemoteXBeeDevice>(list.size());
621                
622                if (list.size() == 0)
623                        return addedList;
624                
625                logger.debug("{}Adding '{}' devices to network.", localDevice.toString(), list.size());
626                
627                for (int i = 0; i < list.size(); i++) {
628                        RemoteXBeeDevice toAdd = list.get(i);
629                        if (toAdd == null)
630                                continue;
631                        
632                        RemoteXBeeDevice d = addRemoteDevice(toAdd);
633                        if (d != null)
634                                addedList.add(d);
635                }
636                
637                return addedList;
638        }
639        
640        /**
641         * Removes the given remote XBee device from the network.
642         * 
643         * <p>Notice that this operation does not remove the remote XBee device 
644         * from the actual XBee network; it just tells the network object that it 
645         * will no longer contain that device. However, next time a discovery is 
646         * performed, it could be added again automatically.</p>
647         * 
648         * <p>This method will check for a device that matches the 64-bit address 
649         * of the provided one, if found, that device will be removed from the 
650         * corresponding list. In case the 64-bit address is not defined, it will 
651         * use the 16-bit address for DigiMesh and ZigBee devices.</p>
652         * 
653         * @param remoteDevice The remote device to be removed from the network.
654         * 
655         * @throws NullPointerException if {@code RemoteDevice == null}.
656         * 
657         * @see #addRemoteDevice(RemoteXBeeDevice)
658         * @see #clearDeviceList()
659         * @see RemoteXBeeDevice
660         */
661        public void removeRemoteDevice(RemoteXBeeDevice remoteDevice) {
662                if (remoteDevice == null)
663                        throw new NullPointerException("Remote device cannot be null.");
664                
665                RemoteXBeeDevice devInNetwork = null;
666                
667                // Look in the 64-bit map.
668                XBee64BitAddress addr64 = remoteDevice.get64BitAddress();
669                if (addr64 != null && !addr64.equals(XBee64BitAddress.UNKNOWN_ADDRESS)) {
670                        devInNetwork = remotesBy64BitAddr.get(addr64);
671                        
672                        // Remove the device.
673                        if (devInNetwork != null) {
674                                remotesBy64BitAddr.remove(addr64);
675                                return;
676                        }
677                }
678                
679                // If not found, look in the 16-bit map.
680                XBee16BitAddress addr16 = get16BitAddress(remoteDevice);
681                if (addr16 != null && !addr16.equals(XBee16BitAddress.UNKNOWN_ADDRESS)) {
682                        
683                        // The preference order is: 
684                        //    1.- Look in the 64-bit map 
685                        //    2.- Then in the 16-bit map.
686                        // This should be maintained in the 'getDeviceBy16BitAddress' method.
687                        
688                        // Look for the 16-bit address in the 64-bit map.
689                        Collection<RemoteXBeeDevice> devices = remotesBy64BitAddr.values();
690                        for (RemoteXBeeDevice d: devices) {
691                                XBee16BitAddress a = get16BitAddress(d);
692                                if (a != null && a.equals(addr16)) {
693                                        remotesBy64BitAddr.remove(d.get64BitAddress());
694                                        return;
695                                }
696                        }
697                        
698                        // If not found, look for the 16-bit address in the 16-bit map. 
699                        devInNetwork = remotesBy16BitAddr.get(addr16);
700                        
701                        // Remove the device.
702                        if (devInNetwork != null) {
703                                remotesBy16BitAddr.remove(addr16);
704                                return;
705                        }
706                }
707                
708                // If the device does not contain a valid address log an error.
709                if ((addr64 == null || addr64.equals(XBee64BitAddress.UNKNOWN_ADDRESS)) 
710                                && (addr16 == null || addr16.equals(XBee16BitAddress.UNKNOWN_ADDRESS)))
711                        logger.error("{}Remote device '{}' cannot be removed: 64-bit and 16-bit addresses must be specified.", 
712                                        localDevice.toString(), remoteDevice.toString());
713        }
714        
715        /**
716         * Removes all the devices from this network. 
717         * 
718         * <p>The network will be empty after this call returns.</p>
719         * 
720         * <p>Notice that this does not imply removing the XBee devices from the 
721         * actual XBee network; it just tells the object that the list should be 
722         * empty now. Next time a discovery is performed, the list could be filled 
723         * with the remote XBee devices found.</p>
724         * 
725         * @see #removeRemoteDevice(RemoteXBeeDevice)
726         */
727        public void clearDeviceList() {
728                logger.debug("{}Clearing the network.", localDevice.toString());
729                remotesBy64BitAddr.clear();
730                remotesBy16BitAddr.clear();
731        }
732        
733        /**
734         * Returns the number of devices already discovered in this network.
735         * 
736         * @return The number of devices already discovered in this network.
737         */
738        public int getNumberOfDevices() {
739                return remotesBy64BitAddr.size() + remotesBy16BitAddr.size();
740        }
741        
742        /**
743         * Retrieves the 16-bit address of the given remote device.
744         * 
745         * @param device The remote device to get the 16-bit address.
746         * 
747         * @return The 16-bit address of the device, {@code null} if it does not
748         *         contain a valid one.
749         */
750        private XBee16BitAddress get16BitAddress(RemoteXBeeDevice device) {
751                if (device == null)
752                        return null;
753                
754                XBee16BitAddress address = null;
755                
756                switch (device.getXBeeProtocol()) {
757                case RAW_802_15_4:
758                        address = ((RemoteRaw802Device)device).get16BitAddress();
759                        break;
760                case ZIGBEE:
761                        address = ((RemoteZigBeeDevice)device).get16BitAddress();
762                        break;
763                default:
764                        // TODO should we allow this operation for general remote devices?
765                        address = device.get16BitAddress();
766                        break;
767                }
768                
769                return address;
770        }
771        
772        /*
773         * (non-Javadoc)
774         * @see java.lang.Object#toString()
775         */
776        @Override
777        public String toString(){
778                return getClass().getName() + " [" + localDevice.toString() + "] @" + 
779                                Integer.toHexString(hashCode());
780        }
781}